├── .gitignore
├── LICENSE
├── README.md
├── cli
├── bin.js
├── cli.js
├── commands
│ ├── __nomethod__.js
│ ├── create.js
│ ├── down.js
│ ├── download.js
│ ├── endpoints
│ │ └── create.js
│ ├── hostnames
│ │ ├── add.js
│ │ ├── list.js
│ │ └── remove.js
│ ├── http.js
│ ├── init.js
│ ├── login.js
│ ├── logout.js
│ ├── logs.js
│ ├── rebuild.js
│ ├── release.js
│ ├── tokens
│ │ ├── _.js
│ │ ├── add-to-env.js
│ │ └── list.js
│ ├── up.js
│ ├── user.js
│ └── version.js
├── config.js
├── credentials.js
├── env.js
├── error_log.js
├── fileio.js
├── local_gateway.js
├── local_http.js
├── parser.js
├── registry.js
├── service_config.js
├── tabler.js
├── tar.js
├── templates
│ └── functionscript
│ │ ├── ..gitignore
│ │ ├── README.md
│ │ ├── env.json
│ │ ├── functions
│ │ └── __main__.js
│ │ ├── package.json
│ │ └── stdlib.json
└── transformers.js
├── index.js
└── package.json
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules/
3 | npm-debug.log
4 | .stdlib
5 | package-lock.json
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 - 2021 Polybit Inc.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | #
2 |
3 | **Autocode Setup** |
4 | [Node](https://github.com/acode/lib-node) |
5 | [Web](https://github.com/acode/lib-js) |
6 | [Python (alpha)](https://github.com/acode/lib-python) |
7 | [Ruby (alpha)](https://github.com/acode/lib-ruby)
8 |
9 | # Introduction
10 |
11 | [Autocode](https://autocode.com) is a fastest and easy way to build web
12 | services and APIs that respond to external SaaS events. The Autocode ecosystem
13 | treats external SaaS APIs as single-line function calls with the use of the
14 | [lib](https://github.com/acode/lib-node) package in NPM. The Autocode CLI allows
15 | you to interact seamlessly with the following components of Autocode:
16 |
17 | 1. Executing APIs on the Autocode standard library
18 | 2. Uploading new APIs / web services to Autocode's hosting platform
19 |
20 | Autocode is based on Function as a Service ("serverless") architecture,
21 | initially popularized by AWS Lambda. You can use Autocode to build modular, scalable APIs
22 | for yourself and other developers in *minutes* without having to manage servers,
23 | gateways, domains, write documentation, or build SDKs. Your development workflow
24 | has never been easier - focus on writing code you love, let Autocode handle
25 | everything else.
26 |
27 | Autocode uses an **open specification** called
28 | [FunctionScript](https://github.com/acode/FunctionScript) for function definitions and
29 | execution. If you run into concerns or questions as you're building from this
30 | guide, please reference the FunctionScript repository. :)
31 |
32 | You can view services published by our large and growing developer community
33 | [on the Autocode standard library page](https://autocode.com/lib).
34 |
35 | 
36 |
37 | # Table of contents
38 |
39 | 1. [Getting started](#getting-started)
40 | 1. [Creating your first service](#creating-your-first-service)
41 | 1. [Connecting service endpoints](#connecting-service-endpoints)
42 | 1. [Accessing your APIs from other applications](#accessing-your-apis-from-other-applications)
43 | 1. [Accessing your APIs over HTTP](#accessing-your-apis-over-http)
44 | 1. [Version control and package management](#version-control-and-package-management)
45 | 1. [Logging](#logging)
46 | 1. [Additional functionality](#additional-functionality)
47 | 1. [Acknowledgements](#acknowledgements)
48 | 1. [Contact](#contact)
49 |
50 | # Getting started
51 |
52 | To get started with Autocode, first make sure you have Node 8.x or later installed,
53 | [available from the official Node.js website](https://nodejs.org). Next install
54 | the Autocode CLI tools with:
55 |
56 | ```
57 | $ npm install lib.cli -g
58 | ```
59 |
60 | And you're now ready to start building!
61 |
62 | # Creating your first service
63 |
64 | The first thing you'll want to do is create a workspace. Create a new directory
65 | you intend to build your services in and initialize the workspace.
66 |
67 | ```
68 | $ mkdir autocode-workspace
69 | $ cd autocode-workspace
70 | $ lib init
71 | ```
72 |
73 | You'll be asked for an e-mail address to log in to the Autocode registry.
74 | If you don't yet have an account, you can create one by going to https://autocode.com/.
75 | Note that you can skip account creation with `lib init --no-login`.
76 | You'll be unable to use the registry, but it's useful for creating workspaces
77 | when you don't have internet access.
78 |
79 | Next, create your service:
80 |
81 | ```
82 | $ lib create
83 | ```
84 |
85 | You'll be asked for a default function name, which is the entry point
86 | into your service (useful if you only want a single entry point). This will automatically
87 | generate a service project scaffold in `autocode-workspace//`.
88 |
89 | Once created, enter the service directory:
90 |
91 | ```
92 | $ cd your_username/your_service
93 | ```
94 |
95 | In this directory, you'll see something like:
96 |
97 | ```
98 | - functions/
99 | - __main__.js
100 | - package.json
101 | - env.json
102 | - WELCOME.md
103 | - README.md
104 | ```
105 |
106 | At this point, there's a "hello world" function that's been automatically
107 | created (`__main__.js`). Autocode comes paired with a simple `lib` command for
108 | testing your functions locally and running them in the cloud.
109 | To test your function:
110 |
111 | ```shell
112 | $ lib .
113 | "hello world"
114 | ```
115 |
116 | If we examine the `functions/__main__.js` file, we see the following:
117 |
118 | ```javascript
119 | /**
120 | * A basic Hello World function
121 | * @param {string} name Who you're saying hello to
122 | * @returns {string}
123 | */
124 | module.exports = async (name = 'world', context) => {
125 | return `hello ${name}`;
126 | };
127 | ```
128 |
129 | We can pass parameters to it using the CLI by specifying named parameters:
130 |
131 | ```shell
132 | $ lib . --name "dolores abernathy"
133 | "hello dolores abernathy"
134 | ```
135 |
136 | Note that `context` is a magic parameter (automatically populated with
137 | execution details, when provided) as is `callback` (terminates execution),
138 | so these **don't need to be documented** and **can not be specified as
139 | parameters when executing the function**.
140 |
141 | ## Pushing to the cloud
142 |
143 | To push your function to a development environment in the cloud...
144 |
145 | ```shell
146 | $ lib up dev
147 | $ lib your_username.your_service[@dev]
148 | "hello world"
149 | ```
150 |
151 | And to release it (when you're ready!)
152 |
153 | ```shell
154 | $ lib release
155 | $ lib your_username.your_service
156 | "hello world"
157 | ```
158 |
159 | You can check out your service on the web, and use it in applications using our
160 | functions gateway, `api.stdlib.com`.
161 |
162 | ```
163 | https://your_username.api.stdlib.com/your_service/
164 | ```
165 |
166 | That's it! You haven't written a line of code yet, and you have mastery over
167 | building a service, testing it in a development (staging) environment online,
168 | and releasing it for private (or public) consumption.
169 |
170 | **Note:** By default, APIs that you publish with `lib release` will have a
171 | documentation page in the Autocode public registry. You can keep your page private,
172 | as well as restrict execution access or add collaborators to your API,
173 | by modifying your API's permissions. For more information, see this [docs page](https://docs.stdlib.com/main/#/access-control/api-permissions).
174 |
175 | **Another Note:** Staging environments (like the one created with `lib up dev`)
176 | are *mutable* and can be replaced indefinitely. Releases (`lib release`) are
177 | *immutable* and can never be overwritten. However, any service can be torn down
178 | with `lib down ` or `lib down -r ` (but releases
179 | can't be replaced once removed, to prevent mistakes and / or bad actors).
180 |
181 | # Connecting service endpoints
182 |
183 | You'll notice that you can create more than one function per service. While
184 | you can structure your project however you'd like internally, it should also
185 | be noted that these functions have zero-latency access to each other. You
186 | can access them internally with the `lib` [package on NPM](https://github.com/stdlib/lib-node),
187 | which behaves similarly to the `lib` command for testing. Use:
188 |
189 | ```
190 | $ npm install lib --save
191 | ```
192 |
193 | In your main service directory to add it, and use it like so:
194 |
195 | #### functions/add.js
196 | ```javascript
197 | module.exports = async (a = 0, b = 0) => {
198 | return a + b;
199 | };
200 | ```
201 |
202 | #### functions/add_double.js
203 | ```javascript
204 | const lib = require('lib');
205 |
206 | module.exports = async (a = 0, b = 0, context) => {
207 | let result = await lib[`${context.service.identifier}.add`]({a: a, b: b});
208 | return result * 2;
209 | };
210 | ```
211 |
212 | In this case, calling `lib .add --a 1 --b 2` will return `3` and `lib .add_double --a 1 --b 2`
213 | will return `6`. The `context` magic parameter is used for its
214 | `context.service.identifier` property, which will return the string `"your_username.your_service[@local]"`
215 | in the case of local execution, `"your_username.your_service[@ENV]"` when deployed to an
216 | environment or release (where `ENV` is your environment name or semver).
217 |
218 | # Accessing your APIs from other applications
219 |
220 | As mentioned in the previous section, you can use the NPM `lib` package that's
221 | [available on GitHub and NPM](https://github.com/stdlib/lib-node) to access your
222 | APIs from legacy Node.js applications and even the web browser. We'll
223 | have more SDKs coming out in the following months.
224 |
225 | An existing app would call a function (username.bestTrekChar with version 0.2.1):
226 |
227 | ```javascript
228 | const lib = require('lib');
229 |
230 | let result;
231 |
232 | try {
233 | result = await lib.username.bestTrekChar['@0.2.1']({name: 'spock'});
234 | } catch (err) {
235 | // handle error
236 | }
237 |
238 | // do something with result
239 | ```
240 |
241 | Which would speak to your API...
242 |
243 | ```javascript
244 | module.exports = async (name = 'kirk') => {
245 |
246 | if (name === 'kirk') {
247 | return 'why, thank, you, too, kind';
248 | } else if (name === 'spock') {
249 | return 'i think this feeling is called "pleased"';
250 | } else {
251 | throw new Error('Only kirk and spock supported.');
252 | }
253 |
254 | };
255 | ```
256 |
257 | # Accessing your APIs over HTTP
258 |
259 | We definitely recommend using the [lib library on NPM](https://github.com/stdlib/lib-node)
260 | to make API calls as specified above, but you can also make HTTPS
261 | requests directly to the Autocode gateway. HTTP query parameters are mapped
262 | automatically to parameters by name.
263 |
264 | ```
265 | https://username.api.stdlib.com/liveService@1.12.2/?name=BATMAN
266 | ```
267 |
268 | Maps directly to:
269 |
270 | ```javascript
271 | /**
272 | * Hello World
273 | * @param {string} name
274 | * @returns {string}
275 | */
276 | module.exports = async (name = 'world') => {
277 | // returns "HELLO BATMAN" from above HTTP query
278 | return `Hello ${name}`;
279 | };
280 | ```
281 |
282 | # Version control and package management
283 |
284 | A quick note on version control - Autocode is *not* a replacement for normal
285 | git-based workflows, it is a supplement focused around service creation and
286 | execution.
287 |
288 | You have unlimited access to any release (that hasn't been torn down)
289 | with `lib download ` to download and unpack the
290 | tarball to a working directory.
291 |
292 | Tarballs (and package contents) are *closed-source*.
293 | Nobody but you (and potentially your teammates) has access to these. It's up to
294 | you whether or not you share the guts of your service with others on GitHub or NPM.
295 |
296 | As mentioned above: releases are *immutable* and can not be overwritten (but can
297 | be removed, just not replaced afterwards) and development / staging environments
298 | are *mutable*, you can overwrite them as much as you'd like.
299 |
300 | # Logging
301 |
302 | Logging for services is enabled by default. When running a service locally with
303 | `lib .` or `lib .functionname`, all logs will be output in your console. The very
304 | last output (normally a JSON-compatible string) is the return value of the function.
305 |
306 | To view remote logs (in dev or release environments), use the following syntax:
307 |
308 | ```shell
309 | :: Lists all logs for the service
310 | $ lib logs username.servicename
311 |
312 | :: Lists main service endpoint logs for "dev" environment
313 | $ lib logs username.servicename[@dev]
314 |
315 | :: Lists service endpoint named "test" logs for "dev" environment
316 | $ lib logs username.servicename[@dev].test
317 |
318 | :: Lists all logs for "dev" environment
319 | $ lib logs username.servicename[@dev]*
320 | $ lib logs username.servicename[@dev].*
321 | ```
322 |
323 | The default log type is `stdout`, though you can specify `stderr` with
324 | `lib logs username.servicename -t stderr`.
325 |
326 | Limit the number of lines to show with the `-l` argument (or `--lines`).
327 |
328 | # Additional functionality
329 |
330 | Autocode comes packed with a bunch of other goodies - as we roll out updates to
331 | the platform the serverless builds we're using may change. You can update
332 | your service to our latest build using `lib rebuild`. If for any reason your
333 | service goes down and is unrecoverable, you can fix it with this command.
334 |
335 | To see a full list of commands available for the CLI tools, type:
336 |
337 | ```
338 | $ lib help
339 | ```
340 |
341 | We've conveniently copy-and-pasted the output here for you to peruse;
342 |
343 | ```
344 | *
345 | -b Execute as a Background Function
346 | -d Specify debug mode (prints Gateway logs locally, response logs remotely)
347 | -i Specify information mode (prints tar packing and execution request progress)
348 | -t Specify an Identity Token to use manually
349 | -x Unauthenticated - Execute without a token (overrides active token and -t flag)
350 | --* all verbose flags converted to named keyword parameters
351 |
352 | Runs an Autocode function, i.e. "lib user.service[@env]" (remote) or "lib ." (local)
353 |
354 | create [service]
355 | -n No login - don't require an internet connection
356 | -w Write over - overwrite the current directory contents
357 | --no-login No login - don't require an internet connection
358 | --write-over Write over - overwrite the current directory contents
359 |
360 | Creates a new (local) service
361 |
362 | down [environment]
363 | -r Remove a release version (provide number)
364 | --release Remove a release version (provide number)
365 |
366 | Removes Autocode package from registry and cloud environment
367 |
368 | download [username/name OR username/name@env OR username/name@version]
369 | -w Write over - overwrite the target directory contents
370 | --write-over Write over - overwrite the target directory contents
371 |
372 | Retrieves and extracts Autocode package
373 |
374 | endpoints:create [name] [description] [param_1] [param_2] [...] [param_n]
375 | -n New directory: Create as a __main__.js file, with the name representing the directory
376 | --new New directory: Create as a __main__.js file, with the name representing the directory
377 |
378 | Creates a new endpoint for a service
379 |
380 | hostnames:add [source] [target]
381 | Adds a new hostname route from a source custom hostname to a target service you own.
382 | Accepts wildcards wrapped in curly braces ("{}") or "*" at the front of the hostname.
383 |
384 | hostnames:list
385 | Displays created hostname routes from source custom hostnames to target services you own
386 |
387 | hostnames:remove
388 | Removes a hostname route from a source custom hostname to a target service you own
389 |
390 | http
391 | -p Port (default 8170)
392 | --port Port (default 8170)
393 |
394 | Creates HTTP Server for Current Service
395 |
396 | init [environment]
397 | -f Force command to overwrite existing workspace
398 | -n No login - don't require an internet connection
399 | --force Force command to overwrite existing workspace
400 | --no-login No login - don't require an internet connection
401 |
402 | Initializes Autocode workspace
403 |
404 | login
405 | --email E-mail
406 | --password Password
407 |
408 | Logs in to Autocode
409 |
410 | logout
411 | -f Force - clears information even if current Access Token invalid
412 | --force Force - clears information even if current Access Token invalid
413 |
414 | Logs out of Autocode in this workspace
415 |
416 | logs [service]
417 | -l The number of log lines you want to retrieve
418 | -t The log type you want to retrieve. Allowed values are "stdout" and "stderr".
419 | --lines The number of log lines you want to retrieve
420 | --type The log type you want to retrieve. Allowed values are "stdout" and "stderr".
421 |
422 | Retrieves logs for a given service
423 |
424 | rebuild [environment]
425 | -r Rebuild a release package
426 | --release Rebuild a release package
427 |
428 | Rebuilds a service (useful for registry performance updates), alias of `lib restart -b`
429 |
430 | release
431 | Pushes release of Autocode package to registry and cloud (Alias of `lib up -r`)
432 |
433 | tokens
434 | Selects an active Identity Token for API Authentication
435 |
436 | tokens:add-to-env
437 | Sets STDLIB_SECRET_TOKEN in env.json "local" field to the value of an existing token
438 |
439 | tokens:list
440 | -a All - show invalidated tokens as well
441 | -s Silent mode - do not display information
442 | --all All - show invalidated tokens as well
443 | --silent Silent mode - do not display information
444 |
445 | Lists your remotely generated Identity Tokens (Authentication)
446 |
447 | up [environment]
448 | -f Force deploy
449 | -r Upload a release package
450 | --force Force deploy
451 | --release Upload a release package
452 |
453 | Pushes Autocode package to registry and cloud environment
454 |
455 | user
456 | -s Sets a specified key-value pair
457 | --new-password Sets a new password via a prompt
458 | --reset-password Sends a password reset request for the specified e-mail address
459 | --set Sets a specified key-value pair
460 |
461 | Retrieves (and sets) current user information
462 |
463 | version
464 | Returns currently installed version of Autocode command line tools
465 | ```
466 |
467 | # Upgrading from previous versions
468 |
469 | If you're running a previous version and are having issues with the CLI,
470 | try cleaning up the old CLI binary links first;
471 |
472 | ```
473 | $ rm /usr/local/bin/f
474 | $ rm /usr/local/bin/lib
475 | $ rm /usr/local/bin/stdlib
476 | ```
477 |
478 | # That's it!
479 |
480 | Yep, it's really that easy. To keep up-to-date on developments, please
481 | star us here on GitHub, and sign up a user account for the registry. You
482 | can read more about service hosting and keep track of official updates on
483 | [the official Autocode website, autocode.com](https://autocode.com).
484 |
485 | # Acknowledgements
486 |
487 | Autocode is a product of and © 2021 Polybit Inc.
488 |
489 | We'd love for you to pay attention to [@AutocodeHQ](https://twitter.com/AutocodeHQ) and
490 | what we're building next! If you'd consider joining the team, [shoot us an e-mail](mailto:careers@autocode.com).
491 |
492 | You can also follow our team on Twitter:
493 |
494 | - [@keithwhor (Keith Horwood)](https://twitter.com/keithwhor)
495 | - [@hacubu (Jacob Lee)](https://twitter.com/hacubu)
496 | - [@YusufMusleh (Yusuf Musleh)](https://twitter.com/YusufMusleh)
497 | - [@threesided (Scott Gamble)](https://twitter.com/threesided)
498 |
499 | Issues encouraged, PRs welcome, and we're happy to have you on board!
500 | Enjoy and happy building :)
501 |
502 | # Thanks
503 |
504 | Special thanks to the people and companies that have believed in and supported our
505 | vision and development over the years.
506 |
507 | - Slack [@SlackHQ](https://twitter.com/SlackHQ)
508 | - Stripe [@Stripe](https://twitter.com/Stripe)
509 | - Romain Huet [@romainhuet](https://twitter.com/romainhuet)
510 | - Chad Fowler [@chadfowler](https://twitter.com/chadfowler)
511 | - Brian LeRoux [@brianleroux](https://twitter.com/brianleroux)
512 | - Ahmad Nassri [@AhmadNassri](https://twitter.com/AhmadNassri)
513 |
514 | ... and many more!
515 |
--------------------------------------------------------------------------------
/cli/bin.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | 'use strict';
4 |
5 | let cmd = process.argv[1];
6 |
7 | const CLI = require('./cli.js');
8 | CLI.run(process.argv.slice(2));
9 |
--------------------------------------------------------------------------------
/cli/cli.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const CommandLineInterface = require('cmnd').CommandLineInterface;
4 | const CLI = new CommandLineInterface();
5 |
6 | CLI.load(__dirname, './commands');
7 |
8 | module.exports = CLI;
9 |
--------------------------------------------------------------------------------
/cli/commands/__nomethod__.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const fs = require('fs');
4 | const path = require('path');
5 |
6 | const FunctionParser = require('functionscript').FunctionParser;
7 | const chalk = require('chalk');
8 | const Command = require('cmnd').Command;
9 |
10 | const LocalGateway = require('../local_gateway.js');
11 | const config = require('../config.js');
12 | const serviceConfig = require('../service_config');
13 | const Tar = require('../tar.js');
14 |
15 | async function parseFileFromArg(arg, infoMode) {
16 | if (arg.indexOf('file:') === 0) {
17 | let filename = arg.slice('file:'.length);
18 | let file;
19 | try {
20 | file = fs.readFileSync(filename);
21 | file = JSON.stringify({_base64: file.toString('base64')});
22 | } catch (e) {
23 | return new Error(`Can not read file: "${filename}"`);
24 | }
25 | return file;
26 | } else if (arg.indexOf('pack:') === 0) {
27 | let filename = arg.slice('pack:'.length);
28 | let dir;
29 | try {
30 | dir = fs.statSync(filename);
31 | } catch (e) {
32 | throw new Error(`Can not pack "${filename}", directory not found.`);
33 | }
34 | if (!dir.isDirectory()) {
35 | throw new Error(`Can not pack "${filename}", is not a directory.`);
36 | }
37 | let tarball = await Tar.pack(filename, infoMode);
38 | let file = JSON.stringify({_base64: tarball.toString('base64')});
39 | return file;
40 | }
41 | return arg;
42 | }
43 |
44 | const lib = require('lib');
45 |
46 | class __nomethod__Command extends Command {
47 |
48 | constructor() {
49 |
50 | super('*');
51 |
52 | }
53 |
54 | help() {
55 |
56 | return {
57 | description: 'Runs an Autocode function, i.e. "lib user.service[@env]" (remote) or "lib ." (local)',
58 | flags: {
59 | b: 'Execute as a Background Function',
60 | d: 'Specify debug mode (prints Gateway logs locally, response logs remotely)',
61 | t: 'Specify an Identity Token to use manually',
62 | x: 'Unauthenticated - Execute without a token (overrides active token and -t flag)',
63 | i: 'Specify information mode (prints tar packing and execution request progress)',
64 | h: 'Specify headers in format key1 value1 key2 value2 ...',
65 | a: 'Specify header "Authorization: Bearer [token]" token'
66 | },
67 | vflags: {
68 | '*': 'all verbose flags converted to named keyword parameters'
69 | }
70 | };
71 |
72 | }
73 |
74 | run (params, callback) {
75 |
76 | let debug = !!params.flags.d;
77 | let infoMode = !!params.flags.i;
78 | let isLocal = false;
79 | let gateway;
80 |
81 | if (params.name.indexOf('.') === -1) {
82 | if (params.name.indexOf('/') > -1) {
83 | let names = params.name.split('/');
84 | if (names[1].indexOf('@') > -1) {
85 | names[1] = names[1].split('@');
86 | if (names[1].length > 1) {
87 | names[1][1] = names[1][1] && `[@${names[1][1]}]`;
88 | }
89 | names[1] = names[1].slice(0, 2).join('');
90 | }
91 | return callback(new Error(`Deprecated service path usage, please try \`lib ${names.join('.')}\` instead`));
92 | }
93 | return callback(new Error(`Command "${params.name}" does not exist.`));
94 | } else if (params.name[0] === '.') {
95 | isLocal = true;
96 | let pkg;
97 | let env;
98 | try {
99 | pkg = serviceConfig.get()
100 | } catch (e) {
101 | if (!config.workspace()) {
102 | return callback(new Error([
103 | 'You have not set up a Autocode workspace yet.',
104 | '\nTry running `lib init` in a directory that you would like to use as a workspace.'
105 | ].join('')));
106 | } else if (!config.location(2)) {
107 | return callback(
108 | new Error(
109 | [
110 | 'There was an error parsing "package.json" from this directory.',
111 | '\nIt could be malformed, but it\'s more likely you\'re running',
112 | ' this command from the wrong directory.',
113 | '\n\nYour Autocode workspace is located in:',
114 | '\n ' + config.workspace(),
115 | '\nAnd you\'re currently in:',
116 | '\n ' + process.cwd(),
117 | '\n\nAutocode services are normally two levels down from your workspace directory.',
118 | '\n (i.e. workspace/username/servicename)'
119 | ].join('')
120 | )
121 | );
122 | } else if (!fs.existsSync(path.join(process.cwd(), 'package.json'))) {
123 | return callback(new Error(
124 | [
125 | 'There was no "package.json" found in this directory, you may have deleted it.',
126 | '\nTry creating a new service (using `lib create`) from your Autocode workspace directory:',
127 | '\n ' + config.workspace()
128 | ].join(''))
129 | );
130 | } else {
131 | return callback(new Error('Invalid "package.json" in this directory, your JSON syntax is likely malformed.'));
132 | }
133 | }
134 | try {
135 | env = require(path.join(process.cwd(), 'env.json'));
136 | } catch (e) {
137 | if (!config.workspace()) {
138 | return callback(new Error([
139 | 'You have not set up a Autocode workspace yet.',
140 | '\nTry running `lib init` in a directory that you would like to use as a workspace.'
141 | ].join('')));
142 | } else if (!config.location(2)) {
143 | return callback(
144 | new Error(
145 | [
146 | 'There was an error parsing "env.json" from this directory.',
147 | '\nIt could be malformed, but it\'s more likely you\'re running',
148 | ' this command from the wrong directory.',
149 | '\n\nYour Autocode workspace is located in:',
150 | '\n ' + config.workspace(),
151 | '\nAnd you\'re currently in:',
152 | '\n ' + process.cwd(),
153 | '\n\nAutocode services are normally two levels down from your workspace directory.',
154 | '\n (i.e. workspace/username/servicename)'
155 | ].join('')
156 | )
157 | );
158 | } else if (!fs.existsSync(path.join(process.cwd(), 'env.json'))) {
159 | return callback(new Error(
160 | [
161 | 'There was no "env.json" found in this directory, you may have deleted it.',
162 | '\nTry creating a new service (using `lib create`) from your Autocode workspace directory:',
163 | '\n ' + config.workspace()
164 | ].join(''))
165 | );
166 | } else {
167 | return callback(new Error('Invalid "env.json" in this directory, your JSON syntax is likely malformed.'));
168 | }
169 | }
170 | let build = pkg.stdlib.build;
171 | let serviceName = pkg.stdlib.name;
172 |
173 | if (build !== 'legacy') {
174 | gateway = new LocalGateway({debug: debug});
175 | let fp = new FunctionParser();
176 | let names = serviceName.split('/');
177 | while (names.length < 2) {
178 | names.unshift('_');
179 | }
180 | params.name = `${names.join('.')}[@local]${params.name.length > 1 ? params.name : ''}`;
181 | try {
182 | gateway.service(names.join('/'));
183 | gateway.environment(env.local || {});
184 | gateway.define(fp.load(process.cwd(), 'functions', 'www'));
185 | } catch (e) {
186 | return callback(e);
187 | }
188 | }
189 | }
190 |
191 | if (params.args.length) {
192 | return callback(new Error('Must pass in named parameters with `--name value` or flags with `-f`, unnamed arguments not supported.'));
193 | }
194 |
195 | let kwargs = {};
196 |
197 | let kwargsCheck = async () => {
198 | let keys = Object.keys(params.vflags);
199 | for (let i = 0; i < keys.length; i++) {
200 | let key = keys[i];
201 | kwargs[key] = await parseFileFromArg(params.vflags[key].join(' '), infoMode);
202 | }
203 | if (params.flags.a) {
204 | kwargs['__headers'] = kwargs['__headers'] || {};
205 | kwargs['__headers']['Authorization'] = `Bearer ${params.flags.a.join(' ')}`;
206 | }
207 | if (params.flags.h) {
208 | let headerPairs = params.flags.h;
209 | while (headerPairs.length) {
210 | kwargs['__headers'] = kwargs['__headers'] || {};
211 | let key = (headerPairs.shift() || '')
212 | .split('-')
213 | .map(str => str[0].toUpperCase() + str.slice(1))
214 | .join('-');
215 | let value = headerPairs.shift() || '';
216 | kwargs['__headers'][key] = value;
217 | }
218 | }
219 | };
220 |
221 | kwargsCheck().catch(e => callback(e)).then(() => {
222 |
223 | let errors = Object.keys(kwargs).map(key => kwargs[key])
224 | .filter(arg => arg instanceof Error);
225 |
226 | if (errors.length) {
227 | return callback(errors[0]);
228 | }
229 |
230 | let activeToken = config.get('ACTIVE_LIBRARY_TOKEN');
231 | let unauth = !!params.flags.x;
232 |
233 | if (!activeToken && !unauth) {
234 | console.log();
235 | console.log(chalk.bold.red('Oops!'));
236 | console.log();
237 | console.log(`It seems like you\'re trying to run a Autocode function,`);
238 | console.log(` but you don't have an Active Identity Token (API Key) set.`);
239 | console.log();
240 | console.log('You can run this command again without authentication by specifying:');
241 | console.log(`\t${chalk.bold('lib ' + params.name + ' -x')}`);
242 | console.log();
243 | console.log(`But we recommend setting an Active Identity Token with:`);
244 | console.log(`\t${chalk.bold('lib tokens')}`);
245 | console.log();
246 | return callback(new Error(`No Identity Token value set.`));
247 | }
248 |
249 | let setToken = unauth ? false : !!params.flags.t;
250 | let token = unauth ?
251 | null :
252 | (params.flags.t && params.flags.t[0]) || activeToken || null;
253 | let webhook = (params.flags.w && params.flags.w[0]) || null;
254 | let bg = params.flags.b ? (params.flags.b[0] || true) : null;
255 | let hostname = (params.flags.h && params.flags.h[0]) || '';
256 | let matches = hostname.match(/^(https?:\/\/)?(.*?)(:\d+)?$/);
257 | let host;
258 | let port;
259 |
260 | if (setToken && isLocal) {
261 | console.log();
262 | console.log(chalk.bold.red('Oops!'));
263 | console.log();
264 | console.log(`It seems like you\'re trying to run an authenticated request with an Identity Token (-t),`);
265 | console.log(` but the function you're running is ${chalk.green('running locally')}.`);
266 | console.log();
267 | console.log('Local authentication via Autocode is not supported.');
268 | console.log('Please ship your service to a cloud-based development environment using:');
269 | console.log(`\t${chalk.bold('lib up dev')}`);
270 | console.log();
271 | console.log(`Or simply run your service locally again ${chalk.red('without the ')}${chalk.bold.red('-t')}${chalk.red(' flag')}.`);
272 | console.log();
273 | return callback(new Error(`Can not use Identity Tokens locally.`));
274 | } else if (setToken && !token) {
275 | console.log();
276 | console.log(chalk.bold.red('Oops!'));
277 | console.log();
278 | console.log(
279 | `It seems like you\'re trying to run an authenticated request with` +
280 | ` an Identity Token (-t), but have not provided a value`
281 | );
282 | console.log();
283 | console.log(`Try running this command again using the flag:`);
284 | console.log(`\t${chalk.bold('-t ')}`);
285 | console.log();
286 | console.log(`Or learn more about setting an active Identity Token using:`);
287 | console.log(`\t${chalk.bold('lib help tokens')}`);
288 | console.log();
289 | return callback(new Error(`No Identity Token value set.`));
290 | }
291 |
292 | if (hostname && matches) {
293 | host = matches[2];
294 | port = parseInt((matches[3] || '').substr(1) || (hostname.indexOf('https') === 0 ? 443 : 80));
295 | }
296 |
297 | let debugLog = msg => {
298 | if (!debug) {
299 | return;
300 | }
301 | msg = msg || '';
302 | let prefix = '> ';
303 | return console.log(
304 | msg
305 | .split('\n')
306 | .map(line => chalk.grey(prefix + (line || '')))
307 | .join('\n')
308 | );
309 | }
310 |
311 | let cb = (err, result, headers) => {
312 |
313 | let responseMessage = `Response Received `;
314 | let localityMessage = isLocal ? `(local)` : `(remote)`;
315 | let localityFormatted = isLocal ?
316 | chalk.green(localityMessage) :
317 | chalk.cyan(localityMessage);
318 |
319 | if (headers) {
320 | let content = headers['content-type'];
321 | let size = headers['content-length'];
322 | let data = [
323 | `Content-Type: ${content}`,
324 | `Content-Length: ${size} bytes`
325 | ];
326 | let separator = Array(Math.max.apply(null, data.map(s => s.length)) + 1).join('-')
327 | debugLog(`${responseMessage}${localityFormatted}`);
328 | debugLog(separator);
329 | debugLog(data.join('\n'));
330 | debugLog(separator);
331 | } else {
332 | debugLog(`${responseMessage}${localityFormatted}`);
333 | debugLog(Array(responseMessage.length + localityMessage.length + 1).join('-'));
334 | }
335 |
336 | if (err) {
337 | if (err.code == 'HPE_INVALID_CONSTANT') {
338 | err.message = [
339 | err.message,
340 | 'Received HTTP error code "HPE_INVALID_CONSTANT"',
341 | 'This is likely due to an invalid "Content-Length" header field',
342 | 'Autocode will set this field for you, you do not need to write it manually'
343 | ].join('\n');
344 | } else {
345 | let message = err.message || '';
346 | if (err.type === 'ParameterError' || err.type === 'ValueError') {
347 | let params = err.details || {};
348 | Object.keys(params).forEach(name => {
349 | message += `\n - [${name}] ${params[name].message}`;
350 | });
351 | delete err.details;
352 | }
353 | err.message = message;
354 | }
355 | } else {
356 | if (result instanceof Buffer) {
357 | console.log(` ${result.toString()}`);
358 | } else {
359 | console.log(
360 | JSON.stringify(
361 | result,
362 | function (name, value) {
363 | if (
364 | value &&
365 | typeof value === 'object' &&
366 | value.type === 'Buffer' &&
367 | Array.isArray(value.data) &&
368 | Object.keys(value).length === 2
369 | ) {
370 | let buffer = Buffer.from(value.data);
371 | return ` ${buffer.toString()}`;
372 | } else {
373 | return value;
374 | }
375 | },
376 | 2
377 | )
378 | );
379 | }
380 | }
381 |
382 | if (gateway && gateway._requestCount) {
383 | gateway.once('empty', () => {
384 | err && console.error(err);
385 | callback(err)
386 | });
387 | } else {
388 | err && console.error(err);
389 | callback(err);
390 | }
391 |
392 | };
393 |
394 | let hostString = host ? (port ? `${host}:${port}` : host) : '';
395 |
396 | debugLog();
397 | debugLog(
398 | `Running ${chalk[isLocal ? 'green' : 'cyan'](params.name)}` +
399 | (hostString ? ` on ${chalk.cyan(hostString)}` : ``) +
400 | `...`
401 | );
402 | debugLog(
403 | token ?
404 | `(authenticating using Identity Token ${chalk.yellow(token.substr(0, 8) + '...')})` :
405 | `(unauthenticated request)`
406 | );
407 | debugLog();
408 |
409 | let completeCallback = function () {
410 | if (gateway) {
411 | params.name = params.name.replace(/(\[\@local\])/gi, '[@local:' + gateway.port + ']');
412 | }
413 | try {
414 | let cfg = {token: token, host: host, port: port, webhook: webhook, bg: bg, convert: true};
415 | lib(cfg)[params.name](kwargs, cb);
416 | } catch(e) {
417 | console.error(e);
418 | return callback(e);
419 | }
420 | };
421 |
422 | if (isLocal) {
423 | gateway.listen(null, completeCallback, {retry: true});
424 | } else {
425 | completeCallback();
426 | }
427 |
428 | });
429 |
430 | }
431 |
432 | }
433 |
434 | module.exports = __nomethod__Command;
435 |
--------------------------------------------------------------------------------
/cli/commands/create.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Command = require('cmnd').Command;
4 |
5 | const APIResource = require('api-res');
6 | const config = require('../config.js');
7 | const fileio = require('../fileio.js');
8 |
9 | const async = require('async');
10 | const inquirer = require('inquirer');
11 |
12 | const fs = require('fs');
13 | const path = require('path');
14 | const chalk = require('chalk');
15 |
16 | const spawnSync = require('child_process').spawnSync;
17 |
18 | const DEFAULT_BUILD = 'functionscript';
19 |
20 | function deepAssign(o1, o2) {
21 | Object.keys(o2).forEach(k => {
22 | if (
23 | o1[k] && o2[k] &&
24 | typeof o1[k] === 'object' && typeof o2[k] === 'object'
25 | ) {
26 | deepAssign(o1[k], o2[k]);
27 | } else {
28 | o1[k] = o2[k];
29 | }
30 | });
31 | }
32 |
33 | class CreateCommand extends Command {
34 |
35 | constructor() {
36 |
37 | super('create');
38 |
39 | }
40 |
41 | help() {
42 |
43 | return {
44 | description: 'Creates a new (local) service',
45 | args: [
46 | 'service'
47 | ],
48 | flags: {
49 | n: 'No login - don\'t require an internet connection',
50 | w: 'Write over - overwrite the current directory contents'
51 | },
52 | vflags: {
53 | 'no-login': 'No login - don\'t require an internet connection',
54 | 'write-over': 'Write over - overwrite the current directory contents'
55 | }
56 | };
57 |
58 | }
59 |
60 | run(params, callback) {
61 |
62 | let name = params.args[0];
63 |
64 | let host = params.flags.h ? params.flags.h[0] : 'https://api.autocode.com';
65 | let port = params.flags.p && params.flags.p[0];
66 |
67 | let nologin = params.flags.hasOwnProperty('n') || params.vflags.hasOwnProperty('no-login');
68 |
69 | let write = params.flags.hasOwnProperty('w') || params.vflags.hasOwnProperty('write-over');
70 |
71 | let build = DEFAULT_BUILD;
72 |
73 | if (!config.location(0)) {
74 | console.log();
75 | console.log(chalk.bold.red('Oops!'));
76 | console.log();
77 | console.log(`You're trying to create a new service in development,`);
78 | console.log(`But you're not in your root Autocode project directory.`);
79 | console.log();
80 | if (!config.workspace()) {
81 | console.log(`Initialize a workspace first with:`);
82 | console.log(`\t${chalk.bold('lib init')}`);
83 | } else {
84 | console.log('Visit your workspace directory with:');
85 | console.log(`\t${chalk.bold('cd ' + config.workspace())}`);
86 | }
87 | console.log();
88 | return callback(null);
89 | }
90 |
91 | console.log();
92 | console.log(`Awesome! Let's create an ${chalk.bold.green('Autocode')} service!`);
93 | console.log();
94 |
95 | let questions = [];
96 |
97 | name || questions.push({
98 | name: 'name',
99 | type: 'input',
100 | default: '',
101 | message: 'Service Name'
102 | });
103 |
104 | let login = [];
105 | !nologin && login.push((cb) => {
106 |
107 | let resource = new APIResource(host, port);
108 | resource.authorize(config.get('ACCESS_TOKEN'));
109 |
110 | resource.request('v1/users').index({me: true}, (err, response) => {
111 |
112 | if (err) {
113 | return cb(err);
114 | }
115 | return cb(null, response.data[0]);
116 |
117 | });
118 |
119 | });
120 |
121 | // NOTE: Not offline friendly. Always log in user...
122 | // login = username ? [] : login;
123 |
124 | async.series(login, async (err, results) => {
125 |
126 | if (err) {
127 | console.log(chalk.bold.red('Oops!'));
128 | console.log();
129 | console.log(`It seems like there's an issue trying to create a service.`);
130 | console.log(`Are you sure you're logged in? Your access token may have expired.`);
131 | console.log();
132 | console.log('Try logging in with:');
133 | console.log(`\t${chalk.bold('lib login')}`);
134 | console.log();
135 | return callback(err);
136 | }
137 |
138 | let defaultUser = {
139 | username: 'dev',
140 | email: ''
141 | };
142 |
143 | let user = nologin ? defaultUser : results[0];
144 | user = user || defaultUser;
145 |
146 | let promptResult = await inquirer.prompt(questions);
147 |
148 | name = name || promptResult.name;
149 | let username;
150 |
151 | if (name.indexOf('/') > -1) {
152 | username = name.split('/')[0];
153 | name = name.split('/').slice(1).join('/').replace(/\//g, '-');
154 | }
155 |
156 | username = username || user.username;
157 |
158 | !fs.existsSync(username) && fs.mkdirSync(username);
159 | let serviceName = [username, name].join('/');
160 | let servicePath = path.join(process.cwd(), username, name);
161 | let fPath = path.join(servicePath, 'functions');
162 |
163 | if (fs.existsSync(servicePath)) {
164 |
165 | if (!write) {
166 |
167 | console.log();
168 | console.log(chalk.bold.red('Oops!'));
169 | console.log();
170 | console.log(`The directory you're creating a Autocode project in already exists:`);
171 | console.log(` ${chalk.bold(servicePath)}`);
172 | console.log();
173 | console.log(`Try removing the existing directory first.`);
174 | console.log();
175 | console.log(`Use ${chalk.bold('lib create --write-over')} to override.`);
176 | console.log();
177 | return callback(null);
178 |
179 | }
180 |
181 | } else {
182 |
183 | fs.mkdirSync(servicePath);
184 | fs.mkdirSync(fPath);
185 |
186 | }
187 |
188 | let packageJSON = require(path.join(__dirname, `../templates/${build}/package.json`));
189 | let stdlibJSON = require(path.join(__dirname, `../templates/${build}/stdlib.json`));
190 |
191 | packageJSON.name = name;
192 | packageJSON.author = user.username + (user.email ? ` <${user.email}>` : '');
193 | stdlibJSON.name = [username, name].join('/');
194 | stdlibJSON.build = build;
195 |
196 | fileio.writeFiles(
197 | serviceName,
198 | fileio.readTemplateFiles(
199 | path.join(__dirname, '..', 'templates', build)
200 | )
201 | );
202 |
203 | fs.writeFileSync(
204 | path.join(servicePath, 'package.json'),
205 | JSON.stringify(packageJSON, null, 2)
206 | );
207 |
208 | fs.writeFileSync(
209 | path.join(servicePath, 'stdlib.json'),
210 | JSON.stringify(stdlibJSON, null, 2)
211 | );
212 |
213 | if (
214 | (packageJSON.dependencies && Object.keys(packageJSON.dependencies).length) ||
215 | (packageJSON.devDependencies && Object.keys(packageJSON.devDependencies).length)
216 | ) {
217 | console.log(`Installing npm packages...`);
218 | console.log();
219 | let command = spawnSync(
220 | /^win/.test(process.platform) ? 'npm.cmd' : 'npm', ['install'], {
221 | stdio: [0, 1, 2],
222 | cwd: servicePath,
223 | env: process.env
224 | }
225 | );
226 | if (command.status !== 0) {
227 | console.log(command.error);
228 | console.log(chalk.bold.yellow('Warn: ') + 'Error with npm install');
229 | }
230 | }
231 |
232 | console.log(chalk.bold.green('Success!'));
233 | console.log();
234 | console.log(`Service ${chalk.bold([username, name].join('/'))} created at:`);
235 | console.log(` ${chalk.bold(servicePath)}`);
236 | console.log();
237 | console.log(`Use the following to enter your service directory:`);
238 | console.log(` ${chalk.bold('cd ' + [username, name].join('/'))}`);
239 | console.log();
240 | console.log(`Type ${chalk.bold('lib help')} for more commands.`);
241 | console.log();
242 | return callback(null);
243 |
244 | });
245 |
246 | }
247 |
248 | }
249 |
250 | module.exports = CreateCommand;
251 |
--------------------------------------------------------------------------------
/cli/commands/down.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Command = require('cmnd').Command;
4 | const Registry = require('../registry.js');
5 |
6 | const chalk = require('chalk');
7 |
8 | const config = require('../config.js');
9 | const serviceConfig = require('../service_config');
10 |
11 | class DownCommand extends Command {
12 |
13 | constructor() {
14 |
15 | super('down');
16 |
17 | }
18 |
19 | help() {
20 |
21 | return {
22 | description: 'Removes Autocode package from registry and cloud environment',
23 | args: [
24 | 'environment'
25 | ],
26 | flags: {
27 | r: 'Remove a release version (provide number)',
28 | },
29 | vflags: {
30 | release: 'Remove a release version (provide number)',
31 | }
32 | };
33 |
34 | }
35 |
36 | run(params, callback) {
37 |
38 | let environment = params.args[0];
39 | let version;
40 | let release = params.flags.r || params.vflags.release;
41 |
42 | if (release && environment) {
43 | return callback(new Error('Can not remove an release with an environment'));
44 | }
45 |
46 | if (!release && !environment) {
47 | return callback(new Error('Please specify an environment'));
48 | }
49 |
50 | if (release) {
51 | version = release[0];
52 | environment = null;
53 | }
54 |
55 | let host = 'packages.stdlib.com';
56 | let port = 443;
57 |
58 | let hostname = (params.flags.h && params.flags.h[0]) || '';
59 | let matches = hostname.match(/^(https?:\/\/)?(.*?)(:\d+)?$/);
60 |
61 | if (hostname && matches) {
62 | host = matches[2];
63 | port = parseInt((matches[3] || '').substr(1) || (hostname.indexOf('https') === 0 ? 443 : 80));
64 | }
65 |
66 | let pkg;
67 |
68 | try {
69 | pkg = serviceConfig.get();
70 | } catch(err) {
71 | return callback(err);
72 | }
73 |
74 | let registry = new Registry(host, port, config.get('ACCESS_TOKEN'));
75 |
76 | let registryParams = {name: pkg.stdlib.name};
77 | if (environment) {
78 | registryParams.environment = environment;
79 | } else {
80 | registryParams.version = version;
81 | }
82 |
83 | return registry.request(
84 | 'down',
85 | registryParams,
86 | null,
87 | (err, response) => {
88 |
89 | if (err) {
90 | return callback(err);
91 | } else {
92 | console.log(`${chalk.bold(`${response.name}@${response.environment || response.version}`)} torn down successfully!`);
93 | return callback(null);
94 | }
95 |
96 | }
97 | );
98 |
99 | }
100 |
101 | }
102 |
103 | module.exports = DownCommand;
104 |
--------------------------------------------------------------------------------
/cli/commands/download.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const fs = require('fs');
4 | const path = require('path');
5 | const tar = require('tar-stream');
6 | const zlib = require('zlib');
7 | const stream = require('stream');
8 |
9 | const Command = require('cmnd').Command;
10 | const Registry = require('../registry.js');
11 | const config = require('../config.js');
12 |
13 | const chalk = require('chalk');
14 |
15 | function rmdir(dir) {
16 | if (!fs.existsSync(dir)) {
17 | return null;
18 | }
19 | fs.readdirSync(dir).forEach(f => {
20 | let pathname = path.join(dir, f);
21 | if (!fs.existsSync(pathname)) {
22 | return fs.unlinkSync(pathname);
23 | }
24 | if (fs.statSync(pathname).isDirectory()) {
25 | return rmdir(pathname);
26 | } else {
27 | return fs.unlinkSync(pathname);
28 | }
29 | });
30 | return fs.rmdirSync(dir);
31 | };
32 |
33 | class GetCommand extends Command {
34 |
35 | constructor() {
36 |
37 | super('download');
38 |
39 | }
40 |
41 | help() {
42 |
43 | return {
44 | description: 'Retrieves and extracts Autocode package',
45 | args: [
46 | 'username/name OR username/name@env OR username/name@version'
47 | ],
48 | flags: {
49 | w: 'Write over - overwrite the target directory contents'
50 | },
51 | vflags: {
52 | 'write-over': 'Write over - overwrite the target directory contents'
53 | }
54 | };
55 |
56 | }
57 |
58 | run(params, callback) {
59 |
60 | let service = params.args[0] || '';
61 | if (!service) {
62 | console.log();
63 | console.log(chalk.bold.red('Oops!'));
64 | console.log();
65 | console.log(`Please specify a service name`);
66 | console.log(`Use ${chalk.bold('username/name')} for latest release,`);
67 | console.log(`Or ${chalk.bold('username/name@env')} or ${chalk.bold('username/name@version')} for specific environments and versions`);
68 | console.log();
69 | return callback(null);
70 | }
71 |
72 | let outputPath = params.flags.o || params.vflags.output || [];
73 | outputPath = outputPath[0] || '.';
74 |
75 | let write = params.flags.hasOwnProperty('w') || params.vflags.hasOwnProperty('write-over');
76 |
77 | let pathname = path.join(outputPath, service);
78 | pathname = outputPath[0] !== '/' ? path.join(process.cwd(), pathname) : pathname;
79 |
80 | if (!config.location(0)) {
81 | console.log();
82 | console.log(chalk.bold.red('Oops!'));
83 | console.log();
84 | console.log(`You're trying to retrieve a package,`);
85 | console.log(`But you're not in your root Autocode project directory.`);
86 | console.log();
87 | if (!config.workspace()) {
88 | console.log(`Initialize a workspace first with:`);
89 | console.log(`\t${chalk.bold('lib init')}`);
90 | } else {
91 | console.log('Visit your workspace directory with:');
92 | console.log(`\t${chalk.bold('cd ' + config.workspace())}`);
93 | }
94 | console.log();
95 | return callback(null);
96 | }
97 |
98 | if (!write && fs.existsSync(pathname)) {
99 | console.log();
100 | console.log(chalk.bold.red('Oops!'));
101 | console.log();
102 | console.log(`The directory you're retrieving to already exists:`);
103 | console.log(` ${chalk.bold(pathname)}`);
104 | console.log();
105 | console.log(`Try removing the existing directory first.`);
106 | console.log();
107 | console.log(`Use ${chalk.bold('lib download ' + service + ' --write-over')} to override.`);
108 | console.log();
109 | return callback(null);
110 | }
111 |
112 | let host = 'packages.stdlib.com';
113 | let port = 443;
114 |
115 | let hostname = (params.flags.h && params.flags.h[0]) || '';
116 | let matches = hostname.match(/^(https?:\/\/)?(.*?)(:\d+)?$/);
117 |
118 | if (hostname && matches) {
119 | host = matches[2];
120 | port = parseInt((matches[3] || '').substr(1) || (hostname.indexOf('https') === 0 ? 443 : 80));
121 | }
122 |
123 | console.log();
124 | console.log(`Retrieving ${chalk.bold(service)}...`);
125 | console.log();
126 |
127 | let registry = new Registry(host, port, config.get('ACCESS_TOKEN'));
128 | let name = service.split('@')[0];
129 | let environment = service.split('@')[1] || null;
130 | let registryParams = {name: name};
131 | if (environment) {
132 | if (environment.indexOf('.') === -1) {
133 | registryParams.environment = environment;
134 | } else {
135 | registryParams.version = environment;
136 | }
137 | }
138 | registryParams.format = 'tgz';
139 |
140 | return registry.request(
141 | 'download',
142 | registryParams,
143 | null,
144 | (err, response) => {
145 |
146 | if (err) {
147 |
148 | return callback(err);
149 |
150 | } else {
151 |
152 | console.log(`${chalk.bold(`${response.name}@${response.environment || response.version}`)} downloaded successfully...`);
153 |
154 | if (err) {
155 | return callback(err);
156 | }
157 |
158 | if (fs.existsSync(pathname)) {
159 | rmdir(pathname);
160 | }
161 |
162 | let directories = pathname.split(path.sep);
163 | for (let i = 1; i < directories.length; i++) {
164 | let relpath = pathname.split(path.sep).slice(0, i + 1).join(path.sep);
165 | try {
166 | !fs.existsSync(relpath) && fs.mkdirSync(relpath);
167 | } catch (e) {
168 | console.error(e);
169 | return callback(new Error(`Could not create directory ${relpath}`));
170 | }
171 | }
172 |
173 | zlib.gunzip(response, (err, result) => {
174 |
175 | if (err) {
176 | return callback(new Error(`Error decompressing package`));
177 | }
178 |
179 | let files = {};
180 | let extract = tar.extract();
181 | let tarStream = new stream.PassThrough();
182 |
183 | extract.on('entry', (header, stream, cb) => {
184 | let buffers = [];
185 | stream.on('data', data => { buffers.push(data); });
186 | stream.on('end', () => {
187 | files[header.name] = Buffer.concat(buffers);
188 | return cb();
189 | });
190 | });
191 |
192 | extract.on('finish', () => {
193 | Object.keys(files).forEach(filename => {
194 | let outputPath = path.join(pathname, filename);
195 | let directories = outputPath.split(path.sep).slice(0, -1);
196 | for (let i = 1; i < directories.length; i++) {
197 | let relpath = outputPath.split(path.sep).slice(0, i + 1).join(path.sep);
198 | try {
199 | !fs.existsSync(relpath) && fs.mkdirSync(relpath);
200 | } catch (e) {
201 | console.error(e);
202 | return callback(new Error(`Could not create directory ${relpath}`));
203 | }
204 | }
205 | fs.writeFileSync(outputPath, files[filename], {mode: 0o777});
206 | });
207 | console.log(chalk.bold.green('Success!'));
208 | console.log();
209 | console.log(`${chalk.bold(service)} package retrieved to:`);
210 | console.log(` ${chalk.bold(pathname)}`);
211 | console.log();
212 | return callback(null);
213 | });
214 |
215 | tarStream.end(result);
216 | tarStream.pipe(extract);
217 |
218 | });
219 |
220 | }
221 |
222 | }
223 | );
224 |
225 | }
226 |
227 | }
228 |
229 | module.exports = GetCommand;
230 |
--------------------------------------------------------------------------------
/cli/commands/endpoints/create.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Command = require('cmnd').Command;
4 |
5 | const inquirer = require('inquirer');
6 |
7 | const fs = require('fs');
8 | const path = require('path');
9 | const chalk = require('chalk');
10 |
11 | function generateFunction(name, description, params) {
12 |
13 | params = (params || []).map(p => {
14 | p = p.split(':');
15 | return {
16 | name: p[0],
17 | type: p[1] || 'any'
18 | };
19 | });
20 |
21 | return [
22 | '/**',
23 | description ? `* ${description}` : '',
24 | params.map(param => {
25 | return `* @param {${param.type}} ${param.name}`
26 | }).join('\n'),
27 | `* @returns {any}`,
28 | `*/`,
29 | `module.exports = async (${params.map(p => p.name).concat('context').join(', ')}) => {`,
30 | ` return 'output of new endpoint: ${name}';`,
31 | `};`,
32 | ].filter(v => !!v).join('\n') + '\n';
33 |
34 | }
35 |
36 | class EndpointsCreateCommand extends Command {
37 |
38 | constructor() {
39 |
40 | super('endpoints', 'create');
41 |
42 | }
43 |
44 | help() {
45 |
46 | return {
47 | description: 'Creates a new endpoint for a service',
48 | args: [
49 | 'name',
50 | 'description',
51 | 'param_1',
52 | 'param_2',
53 | '...',
54 | 'param_n'
55 | ],
56 | flags: {
57 | 'n': 'New directory: Create as a __main__.js file, with the name representing the directory'
58 | },
59 | vflags: {
60 | 'new': 'New directory: Create as a __main__.js file, with the name representing the directory'
61 | }
62 | };
63 |
64 | }
65 |
66 | async run(params, callback) {
67 |
68 | let functionName = params.args[0] || '';
69 | let functionDescription = params.args[1] || '';
70 | let functionParams = params.args.slice(2) || [];
71 | let newDir = !!(params.flags.n || params.vflags['new'] || false);
72 |
73 | if (!fs.existsSync('package.json') && !fs.existsSync('stdlib.json')) {
74 | return callback(new Error('Not in valid Autocode directory'));
75 | }
76 |
77 | let questions = [];
78 |
79 | functionName || questions.push({
80 | name: 'functionName',
81 | type: 'input',
82 | default: '',
83 | message: 'Endpoint name'
84 | });
85 |
86 | let promptResult = await inquirer.prompt(questions);
87 |
88 | functionName = functionName || promptResult.functionName;
89 |
90 | if (!functionName.split('/').pop().match(/^[A-Z]/i)) {
91 | return callback(new Error(`Invalid function name: ${functionName}`));
92 | }
93 |
94 | let fPath = path.join(process.cwd(), 'functions');
95 | let functionPath = fPath;
96 |
97 | !fs.existsSync(fPath) && fs.mkdirSync(fPath);
98 |
99 | let directories = functionName.split('/');
100 |
101 | for (let i = 0; i < directories.length - 1; i++) {
102 | let relpath = path.join.apply(path, [fPath].concat(directories.slice(0, i + 1)));
103 | !fs.existsSync(relpath) && fs.mkdirSync(relpath);
104 | functionPath = relpath;
105 | }
106 |
107 | let name = directories[directories.length - 1];
108 | let checkPaths = [
109 | path.join(functionPath, `${name}.js`),
110 | path.join(functionPath, `${name}`, `__main__.js`)
111 | ];
112 |
113 | for (let i = 0; i < checkPaths.length; i++) {
114 | let pathname = checkPaths[i];
115 | if (fs.existsSync(pathname)) {
116 | console.log();
117 | console.log(chalk.bold.red('Oops!'));
118 | console.log();
119 | console.log(`The endpoint you're trying to create already seems to exist:`);
120 | console.log(` ${chalk.bold(pathname)}`);
121 | console.log();
122 | console.log(`Try removing the existing file first.`);
123 | console.log();
124 | return callback(new Error('Could not create function'));
125 | }
126 | }
127 |
128 | if (!newDir) {
129 | functionPath = checkPaths[0];
130 | } else {
131 | let pathname = path.join(functionPath, name);
132 | !fs.existsSync(pathname) && fs.mkdirSync(pathname);
133 | functionPath = checkPaths[1];
134 | }
135 |
136 | fs.writeFileSync(functionPath, generateFunction(functionName, functionDescription, functionParams));
137 |
138 | console.log();
139 | console.log(chalk.bold.green('Success!'));
140 | console.log();
141 | console.log(`Endpoint ${chalk.bold(functionName)} created at:`);
142 | console.log(` ${chalk.bold(functionPath)}`);
143 | console.log();
144 | return callback(null);
145 |
146 | }
147 |
148 | }
149 |
150 | module.exports = EndpointsCreateCommand;
151 |
--------------------------------------------------------------------------------
/cli/commands/hostnames/add.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Command = require('cmnd').Command;
4 | const APIResource = require('api-res');
5 |
6 | const config = require('../../config.js');
7 |
8 | class HostnamesAddCommand extends Command {
9 |
10 | constructor() {
11 |
12 | super('hostnames', 'add');
13 |
14 | }
15 |
16 | help() {
17 |
18 | return {
19 | description: [
20 | 'Adds a new hostname route from a source custom hostname to a target service you own.',
21 | 'Accepts wildcards wrapped in curly braces ("{}") or "*" at the front of the hostname.'
22 | ].join('\n'),
23 | args: [
24 | 'source',
25 | 'target'
26 | ]
27 | };
28 |
29 | }
30 |
31 | run(params, callback) {
32 |
33 | let host = 'api.autocode.com';
34 | let port = 443;
35 |
36 | let source = params.args[0] || '';
37 | let target = params.args[1] || '';
38 |
39 | let versionString = target.split('[@')[1];
40 | versionString = versionString && versionString.replace(']', '');
41 | let service = target.split('[@')[0];
42 | let urlComponentArray = [service.split('.')[1], service.split('.')[0], 'api.stdlib.com'];
43 |
44 | if (versionString) {
45 | versionString = versionString.split('.').join('-');
46 | urlComponentArray.unshift(versionString);
47 | }
48 |
49 | let hostname = (params.flags.h && params.flags.h[0]) || '';
50 | let matches = hostname.match(/^(https?:\/\/)?(.*?)(:\d+)?$/);
51 |
52 | if (hostname && matches) {
53 | host = matches[2];
54 | port = parseInt((matches[3] || '').substr(1) || (hostname.indexOf('https') === 0 ? 443 : 80));
55 | }
56 |
57 | let resource = new APIResource(host, port);
58 | resource.authorize(config.get('ACCESS_TOKEN'));
59 |
60 | resource.request('v1/hostname_routes').create({}, {
61 | hostname: source,
62 | target: urlComponentArray.join('.')
63 | }, (err, response) => {
64 |
65 | if (err) {
66 | return callback(err);
67 | }
68 |
69 | return callback(null, `Successfully added route from "${response.data[0].formatted_hostname}" to "${target}"!`);
70 |
71 | });
72 |
73 | }
74 |
75 | }
76 |
77 | module.exports = HostnamesAddCommand;
78 |
--------------------------------------------------------------------------------
/cli/commands/hostnames/list.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Command = require('cmnd').Command;
4 | const APIResource = require('api-res');
5 | const config = require('../../config.js');
6 | const tabler = require('../../tabler.js');
7 |
8 | class HostnamesListCommand extends Command {
9 |
10 | constructor() {
11 |
12 | super('hostnames', 'list');
13 |
14 | }
15 |
16 | help() {
17 |
18 | return {
19 | description: 'Displays created hostname routes from source custom hostnames to target services you own'
20 | };
21 |
22 | }
23 |
24 | run(params, callback) {
25 |
26 | let host = 'api.autocode.com';
27 | let port = 443;
28 |
29 | let hostname = (params.flags.h && params.flags.h[0]) || '';
30 | let matches = hostname.match(/^(https?:\/\/)?(.*?)(:\d+)?$/);
31 | let JSONoutput = params.flags.hasOwnProperty('j') || params.vflags.hasOwnProperty('json');
32 |
33 | if (hostname && matches) {
34 | host = matches[2];
35 | port = parseInt((matches[3] || '').substr(1) || (hostname.indexOf('https') === 0 ? 443 : 80));
36 | }
37 |
38 | let resource = new APIResource(host, port);
39 | resource.authorize(config.get('ACCESS_TOKEN'));
40 |
41 | resource.request('v1/hostname_routes').index({}, (err, response) => {
42 |
43 | if (err) {
44 | return callback(err);
45 | }
46 |
47 | if (JSONoutput) {
48 | return callback(null, response.data);
49 | }
50 |
51 | let fields = ['Hostname', 'Target', 'Created At'];
52 |
53 | return callback(null, tabler(fields, response.data.map((hostnameRoute) => {
54 | return {
55 | Hostname: hostnameRoute.formatted_hostname,
56 | Target: hostnameRoute.target,
57 | 'Created At': hostnameRoute.created_at
58 | };
59 | }), true) + '\n');
60 |
61 | });
62 |
63 | }
64 |
65 | }
66 |
67 | module.exports = HostnamesListCommand;
68 |
--------------------------------------------------------------------------------
/cli/commands/hostnames/remove.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Command = require('cmnd').Command;
4 | const APIResource = require('api-res');
5 | const chalk = require('chalk');
6 | const inquirer = require('inquirer');
7 |
8 | const config = require('../../config.js');
9 | const ListCommand = require('./list.js');
10 | const tabler = require('../../tabler.js');
11 |
12 | class HostnamesRemoveCommand extends Command {
13 |
14 | constructor() {
15 |
16 | super('hostnames', 'remove');
17 |
18 | }
19 |
20 | help() {
21 |
22 | return {
23 | description: 'Removes a hostname route from a source custom hostname to a target service you own'
24 | };
25 |
26 | }
27 |
28 | run(params, callback) {
29 |
30 | let host = 'api.autocode.com';
31 | let port = 443;
32 |
33 | let listCommandFlags = {
34 | h: params.flags.h,
35 | p: params.flags.p
36 | };
37 |
38 | let hostname = (params.flags.h && params.flags.h[0]) || '';
39 | let matches = hostname.match(/^(https?:\/\/)?(.*?)(:\d+)?$/);
40 |
41 | if (hostname && matches) {
42 | host = matches[2];
43 | port = parseInt((matches[3] || '').substr(1) || (hostname.indexOf('https') === 0 ? 443 : 80));
44 | }
45 |
46 | ListCommand.prototype.run.call(this, {flags: listCommandFlags, vflags: {json: true}}, async (err, results) => {
47 |
48 | if (err) {
49 | return callback(err);
50 | }
51 |
52 | let sids = results.map(host => host.sid);
53 | let answers = await inquirer.prompt(
54 | [
55 | {
56 | name: 'route',
57 | type: 'list',
58 | pageSize: 100,
59 | message: `Select a route to ${chalk.bold.red('Destroy (Permanently)')}`,
60 | choices: tabler(
61 | ['?', 'Hostname', 'Target', 'Created At'],
62 | results.map((hostnameRoute, index) => {
63 | return {
64 | '?': ['✖', chalk.bold.red],
65 | Hostname: hostnameRoute.formatted_hostname,
66 | Target: hostnameRoute.target,
67 | 'Created At': hostnameRoute.created_at,
68 | value: sids[index]
69 | };
70 | }),
71 | true,
72 | true
73 | )
74 | .map(row => (row.value === null ? new inquirer.Separator(row.name) : row))
75 | .concat({
76 | name: '○ ' + chalk.grey('(cancel)'),
77 | value: 0
78 | })
79 | },
80 | {
81 | name: 'verify',
82 | type: 'confirm',
83 | message: answers => {
84 | return (
85 | `Are you sure you want to ${chalk.bold.red('permanently destroy')} ` +
86 | `the route from "${chalk.bold(answers.route.Hostname)}"?`
87 | );
88 | },
89 | when: answers => !!answers.route
90 | }
91 | ]
92 | );
93 |
94 | if (!answers.verify || answers.route === 0) {
95 | return callback(null);
96 | }
97 |
98 | let resource = new APIResource(host, port);
99 | resource.authorize(config.get('ACCESS_TOKEN'));
100 | resource.request('/v1/hostname_routes').destroy(null, {
101 | sid: answers.route.value
102 | }, (err, response) => {
103 | if (err) {
104 | return callback(err);
105 | }
106 | console.log();
107 | console.log('Route successfully deleted');
108 | console.log();
109 | return callback(null);
110 | });
111 |
112 | });
113 |
114 | }
115 |
116 | }
117 |
118 | module.exports = HostnamesRemoveCommand;
119 |
--------------------------------------------------------------------------------
/cli/commands/http.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Command = require('cmnd').Command;
4 | const path = require('path');
5 | const child_process = require('child_process');
6 |
7 | const parser = require('../parser.js');
8 | const serviceConfig = require('../service_config');
9 |
10 | const DEFAULT_BUILD = 'faaslang';
11 |
12 | class HTTPCommand extends Command {
13 |
14 | constructor() {
15 |
16 | super('http');
17 |
18 | }
19 |
20 | help() {
21 |
22 | return {
23 | description: 'Creates HTTP Server for Current Service',
24 | flags: {
25 | p: 'Port (default 8170)'
26 | },
27 | vflags: {
28 | port: 'Port (default 8170)'
29 | }
30 | };
31 |
32 | }
33 |
34 | run(params, callback) {
35 |
36 | let pkg;
37 | let env;
38 |
39 | try {
40 | pkg = serviceConfig.get();
41 | } catch(err) {
42 | return callback(err);
43 | }
44 |
45 | try {
46 | env = require(path.join(process.cwd(), 'env.json'));
47 | } catch (e) {
48 | console.error(e);
49 | return callback(new Error('Invalid env.json in this directory'));
50 | }
51 |
52 | let build = pkg.stdlib.build;
53 | let serviceName = pkg.stdlib.name;
54 | let localRoute = pkg.stdlib.local.route;
55 | let route = localRoute || serviceName;
56 | let port = (params.flags.p || params.vflags.port || [])[0] || parseInt(pkg.stdlib.local.port) || 8170;
57 |
58 | route = route.startsWith('/') ? route : '/' + route;
59 |
60 | if (build !== 'legacy') {
61 | child_process.fork(path.join(__dirname, '../local_http.js'), [`PORT=${port}`, `ROUTE=${route}`, `NAME=${serviceName}`]);
62 | } else {
63 | parser.check(err => parser.createServer(pkg, port, !!err));
64 | }
65 |
66 | }
67 |
68 | }
69 |
70 | module.exports = HTTPCommand;
71 |
--------------------------------------------------------------------------------
/cli/commands/init.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Command = require('cmnd').Command;
4 | const APIResource = require('api-res');
5 |
6 | const inquirer = require('inquirer');
7 | const async = require('async');
8 | const chalk = require('chalk');
9 |
10 | const config = require('../config.js');
11 |
12 | class InitCommand extends Command {
13 |
14 | constructor() {
15 |
16 | super('init');
17 |
18 | }
19 |
20 | help() {
21 |
22 | return {
23 | description: 'Initializes Autocode workspace',
24 | args: [
25 | 'environment'
26 | ],
27 | flags: {
28 | f: 'Force command to overwrite existing workspace',
29 | n: 'No login - don\'t require an internet connection'
30 | },
31 | vflags: {
32 | 'force': 'Force command to overwrite existing workspace',
33 | 'no-login': 'No login - don\'t require an internet connection'
34 | }
35 | };
36 |
37 | }
38 |
39 | async run (params, callback) {
40 |
41 | let host = params.flags.h ? params.flags.h[0] : 'https://api.autocode.com';
42 | let port = params.flags.p && params.flags.p[0];
43 |
44 | let force = params.flags.hasOwnProperty('f') || params.vflags.hasOwnProperty('force');
45 | let nologin = params.flags.hasOwnProperty('n') || params.vflags.hasOwnProperty('no-login');
46 |
47 | if (!force && config.workspace()) {
48 | console.log();
49 | console.log(chalk.bold.red('Oops!'));
50 | console.log();
51 | console.log(`An Autocode workspace has already been set.`);
52 | console.log(`The path of the Autocode workspace is:`)
53 | console.log(` ${chalk.bold(config.workspace())}`);
54 | console.log();
55 | console.log(`Use ${chalk.bold('lib init --force')} to override and set a new workspace.`);
56 | console.log();
57 | return callback(null);
58 | }
59 |
60 | config.initialize(process.cwd());
61 |
62 | let cb = (err) => {
63 | if (err) {
64 | return callback(err);
65 | }
66 | console.log();
67 | console.log(chalk.bold.green(`Congratulations!`));
68 | console.log(`Your Autocode development environment has been initialized.`);
69 | console.log();
70 | console.log(`Use ${chalk.bold('lib create ')} to create a new (local) service package.`);
71 | console.log(`or type ${chalk.bold('lib download ')} to download an existing service package.`);
72 | console.log()
73 | console.log(`Additionally, use ${chalk.bold('lib help')} to see more commands.`)
74 | console.log();
75 | console.log(chalk.bold('Happy building! :)'));
76 | console.log();
77 | callback(null);
78 | };
79 |
80 | if (nologin) {
81 | return cb();
82 | }
83 |
84 | console.log();
85 | console.log(chalk.bold.green('Welcome to Autocode! :)'))
86 | console.log();
87 | console.log(`To use the ${chalk.bold('Autocode')} registry, you must have a registered account.`);
88 | console.log(`It will allow you to push your services to the cloud and manage environments.`);
89 | console.log(`If you don\'t have an account, it\'s ${chalk.bold.underline.green('free')} to sign up!`)
90 | console.log(`Please go to https://autocode.com/signup to get started.`);
91 | console.log();
92 | console.log(`If you already have an account, please enter your e-mail to login.`);
93 | console.log();
94 |
95 | let questions = [];
96 |
97 | questions.push({
98 | name: 'email',
99 | type: 'input',
100 | default: '',
101 | message: 'E-mail'
102 | });
103 |
104 | let promptResult = await inquirer.prompt(questions);
105 |
106 | let email = promptResult.email;
107 |
108 | let resource = new APIResource(host, port);
109 | resource.request('v1/user_exists').index({email: email}, (err, response) => {
110 |
111 | if (err) {
112 | return callback(err);
113 | }
114 |
115 | params.flags.e = [email];
116 | params.vflags.email = [email];
117 |
118 | if (!response.data.length) {
119 | console.log();
120 | console.log(`It appears you do not yet have an account.`);
121 | return cb(new Error(`It appears you do not yet have an account.`));
122 | } else {
123 | console.log();
124 | console.log(`Welcome back, ${chalk.bold.green(response.data[0].username)}!`);
125 | console.log('Please enter your password.');
126 | console.log();
127 | require('./login.js').prototype.run(params, cb);
128 | }
129 |
130 | });
131 |
132 | }
133 |
134 | }
135 |
136 | module.exports = InitCommand;
137 |
--------------------------------------------------------------------------------
/cli/commands/login.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Command = require('cmnd').Command;
4 | const APIResource = require('api-res');
5 | const errorLog = require('../error_log.js');
6 |
7 | const inquirer = require('inquirer');
8 | const async = require('async');
9 | const chalk = require('chalk');
10 |
11 | const config = require('../config.js');
12 |
13 | class LoginCommand extends Command {
14 |
15 | constructor() {
16 |
17 | super('login');
18 |
19 | }
20 |
21 | help() {
22 |
23 | return {
24 | description: 'Logs in to Autocode',
25 | vflags: {
26 | email: 'E-mail',
27 | password: 'Password'
28 | }
29 | };
30 |
31 | }
32 |
33 | run(params, callback) {
34 |
35 | let host = params.flags.h ? params.flags.h[0] : 'https://api.autocode.com';
36 | let port = params.flags.p && params.flags.p[0];
37 |
38 | let email = params.vflags.email || params.vflags.email || []
39 | email = email[0];
40 | let password = params.vflags.password || params.vflags.password || [];
41 | password = password[0];
42 |
43 | let questions = [];
44 |
45 | email || questions.push({
46 | name: 'email',
47 | type: 'input',
48 | default: '',
49 | message: 'Username or E-mail',
50 | });
51 |
52 | password || questions.push({
53 | name: 'password',
54 | type: 'password',
55 | message: 'Password',
56 | });
57 |
58 | let resource = new APIResource(host, port);
59 |
60 | let setAccessToken = (accessToken, cb) => {
61 |
62 | config.set('ACCESS_TOKEN', accessToken);
63 | config.set('ACTIVE_LIBRARY_TOKEN', '');
64 | config.unset('LIBRARY_TOKENS');
65 | config.write();
66 |
67 | console.log();
68 | console.log(chalk.bold.green('Logged in successfully!') + ' Retrieving default Identity Token (API Key)...');
69 |
70 | resource.authorize(config.get('ACCESS_TOKEN'));
71 | resource.request('/v1/library_tokens').index({default: true}, (err, response) => {
72 |
73 | if (err) {
74 | return cb(err);
75 | }
76 |
77 | let tokens = (response && response.data) || [];
78 |
79 | if (!tokens.length) {
80 | console.log('Logged in, but could not retrieve default Identity Token.');
81 | } else {
82 | config.save('ACTIVE_LIBRARY_TOKEN', tokens[0].token);
83 | console.log(`Active Identity Token (API Key) set to: ${chalk.bold(tokens[0].label)}`);
84 | }
85 |
86 | console.log();
87 | return cb();
88 |
89 | });
90 | }
91 |
92 | let loopCb = async (err) => {
93 |
94 | err && errorLog(err);
95 |
96 | let promptResult = await inquirer.prompt(questions);
97 |
98 | email = promptResult.email || email;
99 | password = promptResult.password || password;
100 |
101 | resource.request('v1/login').create({}, {grant_type: 'password', username: email, password: password}, (err, response) => {
102 |
103 | if (err) {
104 | questions.filter(q => q.name === 'email').forEach(q => q.default = email);
105 | password = null;
106 | return loopCb(err);
107 | }
108 |
109 | if (!!response.data[0].factor_identifier) {
110 | console.log();
111 | console.log(`Your account has two-factor authentication enabled. Please enter a valid verification code from your device to finish logging in.`);
112 | console.log();
113 | inquirer.prompt({
114 | name: 'verificationCode',
115 | type: 'input',
116 | default: '',
117 | message: 'Verification Code',
118 | }).then((promptResult) => {
119 | resource.request('v1/login').create({}, {
120 | grant_type: 'password',
121 | username: email,
122 | password: password,
123 | factor_verification_check_sid: response.data[0].sid,
124 | factor_verification_code: promptResult.verificationCode,
125 | }, (err, response) => {
126 | if (err) {
127 | questions.filter(q => q.name === 'email').forEach(q => q.default = email);
128 | password = null;
129 | return loopCb(err);
130 | }
131 | setAccessToken(response.data[0].access_token, callback);
132 | });
133 | });
134 | } else {
135 | setAccessToken(response.data[0].access_token, callback);
136 | }
137 |
138 | });
139 |
140 | };
141 |
142 | loopCb();
143 |
144 | }
145 |
146 | }
147 |
148 | module.exports = LoginCommand;
149 |
--------------------------------------------------------------------------------
/cli/commands/logout.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Command = require('cmnd').Command;
4 | const APIResource = require('api-res');
5 |
6 | const async = require('async');
7 |
8 | const config = require('../config.js');
9 |
10 | class LogoutCommand extends Command {
11 |
12 | constructor() {
13 |
14 | super('logout');
15 |
16 | }
17 |
18 | help() {
19 |
20 | return {
21 | description: 'Logs out of Autocode in this workspace',
22 | flags: {
23 | 'f': 'Force - clears information even if current Access Token invalid'
24 | },
25 | vflags: {
26 | 'force': 'Force - clears information even if current Access Token invalid'
27 | }
28 | };
29 |
30 | }
31 |
32 | run(params, callback) {
33 |
34 | let host = params.flags.h ? params.flags.h[0] : 'https://api.autocode.com';
35 | let port = params.flags.p && params.flags.p[0];
36 |
37 | let force = !!(params.flags.f || params.vflags.force);
38 |
39 | let resource = new APIResource(host, port);
40 | resource.authorize(config.get('ACCESS_TOKEN'));
41 |
42 | resource.request('v1/access_tokens').destroy(null, {}, (err, response) => {
43 |
44 | if (!force && err) {
45 | return callback(err);
46 | }
47 |
48 | config.set('ACCESS_TOKEN', '');
49 | config.set('ACTIVE_LIBRARY_TOKEN', '');
50 | config.unset('LIBRARY_TOKENS');
51 | config.write();
52 | return callback(null, 'Logged out successfully');
53 |
54 | });
55 |
56 | }
57 |
58 | }
59 |
60 | module.exports = LogoutCommand;
61 |
--------------------------------------------------------------------------------
/cli/commands/logs.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Command = require('cmnd').Command;
4 | const APIResource = require('api-res');
5 |
6 | const chalk = require('chalk');
7 |
8 | const config = require('../config.js');
9 |
10 | const VALID_LOG_TYPES = ['stdout', 'stderr'];
11 | const LOG_TYPE_COLORS = {
12 | 'stdout': 'grey',
13 | 'stderr': 'red',
14 | 'result': 'grey'
15 | };
16 |
17 | class LogsCommand extends Command {
18 |
19 | constructor() {
20 |
21 | super('logs');
22 |
23 | }
24 |
25 | help() {
26 |
27 | return {
28 | description: 'Retrieves logs for a given service',
29 | args: ['service'],
30 | flags: {
31 | t: 'The log type you want to retrieve. Allowed values are "stdout" and "stderr".',
32 | l: 'The number of log lines you want to retrieve'
33 | },
34 | vflags: {
35 | type: 'The log type you want to retrieve. Allowed values are "stdout" and "stderr".',
36 | lines: 'The number of log lines you want to retrieve'
37 | }
38 | };
39 |
40 | }
41 |
42 | run(params, callback) {
43 |
44 | let host = 'api.autocode.com';
45 | let port = 443;
46 |
47 | let hostname = (params.flags.h && params.flags.h[0]) || '';
48 | let matches = hostname.match(/^(https?:\/\/)?(.*?)(:\d+)?$/);
49 |
50 | if (hostname && matches) {
51 | host = matches[2];
52 | port = parseInt((matches[3] || '').substr(1) || (hostname.indexOf('https') === 0 ? 443 : 80));
53 | }
54 |
55 | let logType = (params.flags.t || params.vflags.type || [])[0];
56 | let lines = Math.max(parseInt((params.flags.l || params.vflags.lines || [])[0]) || 100, 1);
57 |
58 | let queryParams = {
59 | count: lines
60 | };
61 |
62 | if (logType) {
63 | if (VALID_LOG_TYPES.indexOf(logType) === -1) {
64 | return callback(new Error(`Log type must be one of: ${VALID_LOG_TYPES.join(', ')}`));
65 | } else {
66 | queryParams.log_type = logType;
67 | }
68 | }
69 |
70 | let serviceFilter = params.args[0];
71 | if (!serviceFilter) {
72 | return callback(new Error('Please enter a service to check logs for in the format: username.service[@environment].*'));
73 | }
74 |
75 | let wildcard = serviceFilter && serviceFilter[serviceFilter.length - 1] === '*';
76 | if (wildcard) {
77 | serviceFilter = serviceFilter.substr(0, serviceFilter.length -1);
78 | if (['.', ']'].indexOf(serviceFilter[serviceFilter.length - 1]) === -1) {
79 | return callback(new Error('Sorry, can not wildcard incomplete service or function names'));
80 | }
81 | }
82 |
83 | let serviceParts = (serviceFilter || '').split('.');
84 | let username = serviceParts[0];
85 | let service = serviceParts[1];
86 | let pathname = serviceParts.slice(1).join('.');
87 | if (pathname) {
88 | let env = /^(.+?)\[@(.+?)\](?:\.(.*?))?$/.exec(pathname);
89 | if (env) {
90 | service = env[1];
91 | let environment = env[2];
92 | let functionName = env[3] || '';
93 | if (/^\d+/.exec(environment)) {
94 | queryParams.version = environment;
95 | } else {
96 | queryParams.environment = environment;
97 | }
98 | if (!wildcard || functionName) {
99 | queryParams.function_name = functionName.split('.').join('/');
100 | }
101 | }
102 | }
103 | queryParams.service_name = [username, service].join('/');
104 |
105 | let resource = new APIResource(host, port);
106 | resource.authorize(config.get('ACCESS_TOKEN'));
107 |
108 | resource.request('v1/logs/read').index(queryParams, (err, results) => {
109 |
110 | if (err) {
111 | return callback(err);
112 | }
113 |
114 | console.log(
115 | results.data.map(log => {
116 | let date = log.created_at.split('T');
117 | date[1] = date[1].slice(0, date[1].length - 1);
118 | date = date.join(' ');
119 | let color = LOG_TYPE_COLORS[log.log_type] || 'grey';
120 | let service = chalk.cyan(log.service_name.replace('/', '.')) +
121 | chalk.green('[@' + (log.version || log.environment) + ']') +
122 | chalk.yellow(log.function_name ? ('.' + log.function_name.replace('/', '.')) : '');
123 | return chalk[color](`${date} `) +
124 | service +
125 | chalk[color]('> ') +
126 | log.value;
127 | }).join('\n')
128 | );
129 |
130 | return callback(null);
131 |
132 | });
133 | }
134 |
135 | }
136 |
137 | module.exports = LogsCommand;
138 |
--------------------------------------------------------------------------------
/cli/commands/rebuild.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Command = require('cmnd').Command;
4 | const Registry = require('../registry.js');
5 |
6 | const chalk = require('chalk');
7 |
8 | const serviceConfig = require('../service_config');
9 | const config = require('../config.js');
10 |
11 | const RELEASE_ENV = 'release';
12 |
13 | class RebuildCommand extends Command {
14 |
15 | constructor() {
16 |
17 | super('rebuild');
18 |
19 | }
20 |
21 | help() {
22 |
23 | return {
24 | description: 'Rebuilds a service (useful for registry performance updates), alias of `lib restart -b`',
25 | args: [
26 | 'environment'
27 | ],
28 | flags: {
29 | r: 'Rebuild a release package'
30 | },
31 | vflags: {
32 | release: 'Rebuild a release package'
33 | }
34 | };
35 |
36 | }
37 |
38 | run(params, callback) {
39 |
40 | let environment = params.args[0];
41 | let release = params.flags.r || params.vflags.release;
42 | let version = null;
43 |
44 | if (environment) {
45 | if (environment === RELEASE_ENV) {
46 | if (release[0]) {
47 | version = release[0];
48 | }
49 | } else if (release) {
50 | return callback(new Error('Can not release to an environment'));
51 | }
52 | } else if (release) {
53 | environment = RELEASE_ENV;
54 | if (release[0]) {
55 | version = release[0];
56 | }
57 | } else {
58 | return callback(new Error('Please specify an environment'));
59 | }
60 |
61 | let host = 'packages.stdlib.com';
62 | let port = 443;
63 |
64 | let hostname = (params.flags.h && params.flags.h[0]) || '';
65 | let matches = hostname.match(/^(https?:\/\/)?(.*?)(:\d+)?$/);
66 |
67 | if (hostname && matches) {
68 | host = matches[2];
69 | port = parseInt((matches[3] || '').substr(1) || (hostname.indexOf('https') === 0 ? 443 : 80));
70 | }
71 |
72 | let pkg;
73 |
74 | try {
75 | pkg = serviceConfig.get();
76 | } catch(err) {
77 | return callback(err);
78 | }
79 |
80 | let registry = new Registry(host, port, config.get('ACCESS_TOKEN'));
81 | console.log();
82 | console.log(`Rebuilding ${chalk.bold(`${pkg.stdlib.name}@${environment === RELEASE_ENV ? version || pkg.stdlib.version : environment}`)} to Autocode at ${host}:${port}...`);
83 |
84 | let registryParams = {name: pkg.stdlib.name};
85 | if (environment !== RELEASE_ENV) {
86 | registryParams.environment = environment;
87 | } else {
88 | registryParams.version = version;
89 | }
90 |
91 | return registry.request(
92 | 'rebuild',
93 | registryParams,
94 | null,
95 | (err, response) => {
96 |
97 | if (err) {
98 | console.log()
99 | return callback(err);
100 | } else {
101 | console.log()
102 | console.log(`${chalk.bold(`${response.name}@${response.environment || response.version}`)} rebuilt successfully!`);
103 | return callback(null);
104 | }
105 |
106 | },
107 | (data) => {
108 | console.log(`Registry :: ${data.message}`);
109 | }
110 | );
111 |
112 | }
113 |
114 | }
115 |
116 | module.exports = RebuildCommand;
117 |
--------------------------------------------------------------------------------
/cli/commands/release.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Command = require('cmnd').Command;
4 | const UpCommand = require('./up.js');
5 |
6 | const fs = require('fs');
7 | const inquirer = require('inquirer');
8 |
9 | class ReleaseCommand extends Command {
10 |
11 | constructor() {
12 |
13 | super('release');
14 |
15 | }
16 |
17 | help() {
18 |
19 | return {
20 | description: 'Pushes release of Autocode package to registry and cloud (Alias of `lib up -r`)'
21 | };
22 |
23 | }
24 |
25 | run(params, callback) {
26 |
27 | params.flags.r = [];
28 | params.args = [];
29 | UpCommand.prototype.run.call(this, params, callback);
30 |
31 | }
32 |
33 | }
34 |
35 | module.exports = ReleaseCommand;
36 |
--------------------------------------------------------------------------------
/cli/commands/tokens/_.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | const Command = require('cmnd').Command;
4 | const APIResource = require('api-res');
5 | const chalk = require('chalk');
6 | const inquirer = require('inquirer');
7 |
8 | const config = require('../../config.js');
9 | const tabler = require('../../tabler.js');
10 |
11 | const TokensListCommand = require('./list.js');
12 |
13 | class TokensCommand extends Command {
14 |
15 | constructor() {
16 | super('tokens');
17 | }
18 |
19 | help() {
20 | return {
21 | description: 'Selects an active Identity Token for API Authentication',
22 | };
23 | }
24 |
25 | run(params, callback) {
26 |
27 | let activeToken = config.get('ACTIVE_LIBRARY_TOKEN');
28 |
29 | let host = params.flags.h ? params.flags.h[0] : 'https://api.autocode.com';
30 | let port = params.flags.p && params.flags.p[0];
31 |
32 | let resource = new APIResource(host, port);
33 |
34 | resource.authorize(config.get('ACCESS_TOKEN'));
35 | resource.request('/v1/library_tokens').index({}, async (err, response) => {
36 |
37 | if (err) {
38 | return callback(err);
39 | }
40 |
41 | let tokens = (response && response.data) || [];
42 |
43 | if (!tokens.length) {
44 | console.log();
45 | console.log(chalk.bold.red('Oops!'));
46 | console.log();
47 | console.log(`It doesn't look like you have any Identity Tokens.`);
48 | console.log(`This usually means you've removed them.`);
49 | console.log();
50 | console.log(`Try typing `);
51 | console.log(`\t${chalk.bold('lib tokens:create')}`);
52 | console.log();
53 | console.log(`To create a new Identity Token.`);
54 | console.log();
55 | return callback(new Error('No Identity Tokens.'));
56 | }
57 |
58 | console.log();
59 | console.log(`Here's a list of your available ${chalk.bold('Identity Tokens')}.`);
60 | console.log(`These are your API keys that provide authentication and access to functions.`);
61 | console.log();
62 | console.log(`Here you can change your active authentication token, simply choose from the list.`);
63 | console.log();
64 |
65 | let answers = await inquirer.prompt(
66 | [
67 | {
68 | name: 'libraryToken',
69 | type: 'list',
70 | pageSize: 100,
71 | message: `Select a Identity Token to use for Authentication`,
72 | choices: tabler(
73 | ['Active', 'User', 'Label', 'Token', 'Valid', 'Created'],
74 | tokens.map(libraryToken => {
75 | return {
76 | 'Active': activeToken === libraryToken.token ? ['(active)', chalk.yellow] : '',
77 | 'User': libraryToken.user.username,
78 | 'Label': libraryToken.label ?
79 | libraryToken.label.length > 36 ?
80 | libraryToken.label.substr(0, 33) + '...' :
81 | libraryToken.label :
82 | '',
83 | 'Token': libraryToken.token.substr(0, 16) + '...',
84 | 'Valid': libraryToken.is_valid ?
85 | ['✔', chalk.bold.green] :
86 | ['✖', chalk.bold.red],
87 | 'Created': libraryToken.created_at,
88 | 'token': libraryToken.token
89 | };
90 | }),
91 | true,
92 | true
93 | ).map(row => row.value === null ? new inquirer.Separator(row.name) : row)
94 | .concat(
95 | {
96 | name: '✖ ' + chalk.red('(unset active token)'),
97 | value: null
98 | },
99 | {
100 | name: '○ ' + chalk.grey('(cancel)'),
101 | value: 0
102 | }
103 | )
104 | }
105 | ]
106 | );
107 |
108 | let libraryToken = answers.libraryToken;
109 |
110 | // If we didn't cancel...
111 | if (libraryToken !== 0) {
112 | activeToken = libraryToken ? libraryToken.token : '';
113 | config.save('ACTIVE_LIBRARY_TOKEN', activeToken);
114 | }
115 |
116 | // set silent flag.
117 | params.flags.s = [];
118 | TokensListCommand.prototype.run.call(this, params, callback);
119 |
120 | });
121 |
122 | }
123 | }
124 |
125 | module.exports = TokensCommand;
126 |
--------------------------------------------------------------------------------
/cli/commands/tokens/add-to-env.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | const Command = require('cmnd').Command;
4 | const APIResource = require('api-res');
5 | const chalk = require('chalk');
6 | const inquirer = require('inquirer');
7 | const fs = require('fs');
8 | const path = require('path');
9 |
10 | const config = require('../../config.js');
11 | const tabler = require('../../tabler.js');
12 | const serviceConfig = require('../../service_config');
13 |
14 |
15 | const TokensListCommand = require('./list.js');
16 |
17 | class TokensAddToEnvCommand extends Command {
18 |
19 | constructor() {
20 | super('tokens', 'add-to-env');
21 | }
22 |
23 | help() {
24 | return {
25 | description: 'Sets STDLIB_SECRET_TOKEN in env.json "local" field to the value of an existing token',
26 | };
27 | }
28 |
29 | run(params, callback) {
30 |
31 | let host = params.flags.h ? params.flags.h[0] : 'https://api.autocode.com';
32 | let port = params.flags.p && params.flags.p[0];
33 |
34 | let resource = new APIResource(host, port);
35 |
36 | let pkg;
37 | let env;
38 |
39 | try {
40 | pkg = serviceConfig.get();
41 | } catch (e) {
42 | return callback(e);
43 | }
44 |
45 | try {
46 | env = require(path.join(process.cwd(), 'env.json'));
47 | } catch (e) {
48 | return callback(new Error(`Could not parse the "env.json" file in this directory.`))
49 | }
50 |
51 | let serviceName = pkg.stdlib.name;
52 | if (!serviceName) {
53 | return callback(new Error(`No "name" field found in package.json`));
54 | }
55 |
56 | resource.authorize(config.get('ACCESS_TOKEN'));
57 | resource.request('/v1/library_tokens').index({
58 | environment: 'dev'
59 | }, async (err, response) => {
60 |
61 | if (err) {
62 | return callback(err);
63 | }
64 |
65 | let libraryTokens = response.data;
66 | let matchingLibraryToken = libraryTokens.find((libraryToken) => {
67 | return libraryToken.cachedToken &&
68 | libraryToken.cachedToken.services &&
69 | libraryToken.cachedToken.services.find((service) => {
70 | return service.name === serviceName.toLowerCase() && service.environment === 'dev';
71 | });
72 | });
73 |
74 | if (!!matchingLibraryToken) {
75 | let newEnv = {
76 | local: env.local || {}
77 | };
78 | Object.keys(env).forEach((key) => {
79 | newEnv[key] = env[key];
80 | });
81 | newEnv.local.STDLIB_SECRET_TOKEN = matchingLibraryToken.token;
82 | fs.writeFileSync(path.join(process.cwd(), 'env.json'), JSON.stringify(newEnv, null, 2));
83 | console.log();
84 | console.log(chalk.bold.green('Success!'));
85 | console.log();
86 | console.log(`Added the Development Identity Token associated with ${chalk.bold(serviceName)}\nto your local "env.json" file as ${chalk.bold('STDLIB_SECRET_TOKEN')}.`);
87 | console.log();
88 | console.log(`Your API will now use this token when you test locally with ${chalk.bold('lib .')}`);
89 | console.log();
90 | return callback();
91 | } else {
92 | console.log();
93 | console.log(`There is currently no Development Identity Token associated with ${chalk.bold(serviceName)}.`);
94 | console.log();
95 | console.log(`Please select one from this list that you would like to use with ${chalk.bold(serviceName)}:`);
96 | console.log();
97 |
98 | let answers = await inquirer.prompt(
99 | [
100 | {
101 | name: 'libraryToken',
102 | type: 'list',
103 | pageSize: 100,
104 | message: `Select a Development Identity Token to use for Authenticated API calls`,
105 | choices: tabler(
106 | ['User', 'Label', 'Token', 'Valid', 'Created'],
107 | libraryTokens.filter((libraryToken) => {
108 | return libraryToken.cachedToken && !libraryToken.cachedToken.is_project_token;
109 | }).map(libraryToken => {
110 | return {
111 | 'User': libraryToken.user.username,
112 | 'Label': libraryToken.label ?
113 | libraryToken.label.length > 36 ?
114 | libraryToken.label.substr(0, 33) + '...' :
115 | libraryToken.label :
116 | '',
117 | 'Token': libraryToken.token.substr(0, 16) + '...',
118 | 'Valid': libraryToken.is_valid ?
119 | ['✔', chalk.bold.green] :
120 | ['✖', chalk.bold.red],
121 | 'Created': libraryToken.created_at,
122 | 'token': libraryToken.token
123 | };
124 | }),
125 | true,
126 | true
127 | ).map(row => row.value === null ? new inquirer.Separator(row.name) : row)
128 | .concat(
129 | {
130 | name: '○ ' + chalk.grey('(cancel)'),
131 | value: 0
132 | }
133 | )
134 | }
135 | ]
136 | );
137 |
138 | let libraryToken = answers.libraryToken;
139 |
140 | // If we didn't cancel...
141 | if (libraryToken !== 0) {
142 | let newEnv = {
143 | local: env.local || {}
144 | };
145 | Object.keys(env).forEach((key) => {
146 | newEnv[key] = env[key];
147 | });
148 | newEnv.local.STDLIB_SECRET_TOKEN = libraryToken.token;
149 | fs.writeFileSync(path.join(process.cwd(), 'env.json'), JSON.stringify(newEnv, null, 2));
150 | console.log();
151 | console.log(chalk.bold.green('Success!'));
152 | console.log();
153 | console.log(`Added the Development Identity Token "${chalk.bold(libraryToken.Label)}"\nto your local "env.json" file as ${chalk.bold('STDLIB_SECRET_TOKEN')}.`);
154 | console.log();
155 | console.log(`Your API will now use this token when you test locally with ${chalk.bold('lib .')}`);
156 | console.log();
157 | return callback();
158 | }
159 |
160 | return callback();
161 |
162 | }
163 |
164 | });
165 |
166 | }
167 | }
168 |
169 | module.exports = TokensAddToEnvCommand;
170 |
--------------------------------------------------------------------------------
/cli/commands/tokens/list.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | const Command = require('cmnd').Command;
4 | const APIResource = require('api-res');
5 | const chalk = require('chalk');
6 | const inquirer = require('inquirer');
7 |
8 | const config = require('../../config.js');
9 | const tabler = require('../../tabler.js');
10 |
11 | class TokensListCommand extends Command {
12 |
13 | constructor() {
14 | super('tokens', 'list');
15 | }
16 |
17 | help() {
18 | return {
19 | description: 'Lists your remotely generated Identity Tokens (Authentication)',
20 | flags: {
21 | 's': 'Silent mode - do not display information',
22 | 'a': 'All - show invalidated tokens as well'
23 | },
24 | vflags: {
25 | 'silent': 'Silent mode - do not display information',
26 | 'all': 'All - show invalidated tokens as well'
27 | }
28 | };
29 | }
30 |
31 | run(params, callback) {
32 |
33 | let activeToken = config.get('ACTIVE_LIBRARY_TOKEN');
34 |
35 | let host = params.flags.h ? params.flags.h[0] : 'https://api.autocode.com';
36 | let port = params.flags.p && params.flags.p[0];
37 | let silent = !!(params.flags.s || params.vflags.silent);
38 | let all = !!(params.flags.a || params.vflags.all);
39 |
40 | let resource = new APIResource(host, port);
41 | let reqParams = {};
42 | all && (reqParams.all = true);
43 |
44 | resource.authorize(config.get('ACCESS_TOKEN'));
45 | resource.request('/v1/library_tokens').index(reqParams, (err, response) => {
46 |
47 | if (err) {
48 | return callback(err);
49 | }
50 |
51 | let tokens = (response && response.data) || [];
52 |
53 | if (!tokens.length) {
54 | console.log();
55 | console.log(chalk.bold.red('Oops!'));
56 | console.log();
57 | console.log(`It doesn't look like you have any remotely generated Identity Tokens.`);
58 | console.log(`This usually means you've removed them.`);
59 | console.log();
60 | console.log(`Try typing `);
61 | console.log(`\t${chalk.bold('lib tokens:create')}`);
62 | console.log();
63 | console.log(`To create a new Identity Token (remote).`);
64 | console.log();
65 | return callback(new Error('No remotely generated tokens.'));
66 | }
67 |
68 | if (!silent) {
69 | console.log();
70 | console.log(`Here's a list of your available ${chalk.bold('Identity Tokens')}.`);
71 | console.log(`These are your API keys that provide authentication and access to functions.`);
72 | }
73 |
74 | return callback(
75 | null,
76 | `\n` + tabler(
77 | ['Active', 'User', 'Label', 'Token', 'Valid', 'Created'],
78 | tokens.map(libraryToken => {
79 | let label = libraryToken.label ?
80 | libraryToken.label.length > 36 ?
81 | libraryToken.label.substr(0, 33) + '...' :
82 | libraryToken.label :
83 | '';
84 | let Token = libraryToken.token.substr(0, 16) + '...';
85 | return {
86 | 'Active': activeToken === libraryToken.token ? ['(active)', chalk.yellow] : '',
87 | 'User': libraryToken.is_valid ?
88 | libraryToken.user.username :
89 | [libraryToken.user.username, chalk.dim],
90 | 'Label': libraryToken.is_valid ?
91 | label :
92 | [label, chalk.dim],
93 | 'Token': libraryToken.is_valid ?
94 | Token :
95 | [Token, chalk.dim],
96 | 'Valid': libraryToken.is_valid ?
97 | ['✔', chalk.bold.green] :
98 | ['✖', chalk.bold.red],
99 | 'Created': libraryToken.is_valid ?
100 | libraryToken.created_at :
101 | [libraryToken.created_at, chalk.dim],
102 | 'token': libraryToken.token
103 | };
104 | }),
105 | true
106 | ) + `\n`
107 | );
108 |
109 | });
110 |
111 | }
112 | }
113 |
114 | module.exports = TokensListCommand;
115 |
--------------------------------------------------------------------------------
/cli/commands/up.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Command = require('cmnd').Command;
4 | const Registry = require('../registry.js');
5 | const Transformers = require('../transformers.js');
6 |
7 | const fs = require('fs');
8 | const zlib = require('zlib');
9 | const path = require('path');
10 |
11 | const async = require('async');
12 | const tar = require('tar-stream');
13 | const chalk = require('chalk');
14 | const minimatch = require('minimatch');
15 |
16 | const config = require('../config.js');
17 | const serviceConfig = require('../service_config');
18 |
19 | const RELEASE_ENV = 'release';
20 |
21 | function readFiles (base, properties, dir, data) {
22 |
23 | dir = dir || '/';
24 | data = data || [];
25 | properties = properties || {};
26 |
27 | let ignore = properties.ignore || [];
28 |
29 | return fs.readdirSync(path.join(base, dir)).reduce((data, f) => {
30 |
31 | let pathname = path.join(dir, f);
32 | let fullpath = path.join(base, pathname);
33 |
34 | for (let i = 0; i < ignore.length; i++) {
35 | let filename = pathname.split(path.sep).join('/').slice(1);
36 | let pattern = ignore[i];
37 | if (minimatch(filename, pattern, {matchBase: true, dot: true})) {
38 | return data;
39 | }
40 | }
41 |
42 | if (fs.statSync(fullpath).isDirectory()) {
43 | return readFiles(base, properties, pathname, data);
44 | } else {
45 | let filename = pathname[0] === path.sep ? pathname.substr(1) : pathname;
46 | let buffer = fs.readFileSync(fullpath);
47 | filename = filename.split(path.sep).join('/'); // Windows
48 | data.push({filename: filename, buffer: buffer});
49 | return data;
50 | }
51 |
52 | }, data);
53 |
54 | };
55 |
56 | class UpCommand extends Command {
57 |
58 | constructor() {
59 |
60 | super('up');
61 |
62 | }
63 |
64 | help() {
65 |
66 | return {
67 | description: 'Pushes Autocode package to registry and cloud environment',
68 | args: [
69 | 'environment'
70 | ],
71 | flags: {
72 | r: 'Upload a release package',
73 | f: 'Force deploy'
74 | },
75 | vflags: {
76 | release: 'Upload a release package',
77 | force: 'Force deploy'
78 | }
79 | };
80 |
81 | }
82 |
83 | run(params, callback) {
84 |
85 | let environment = params.args[0];
86 | let release = params.flags.r || params.vflags.release;
87 | let force = params.flags.f || params.vflags.force;
88 | let version = null;
89 |
90 | if (environment) {
91 | if (environment === RELEASE_ENV) {
92 | if (release[0]) {
93 | version = release[0];
94 | }
95 | } else if (release) {
96 | return callback(new Error('Can not release to an environment'));
97 | }
98 | } else if (release) {
99 | environment = RELEASE_ENV;
100 | if (release[0]) {
101 | version = release[0];
102 | }
103 | } else {
104 | return callback(new Error('Please specify an environment'));
105 | }
106 |
107 | let host = 'packages.stdlib.com';
108 | let port = 443;
109 |
110 | let hostname = (params.flags.h && params.flags.h[0]) || '';
111 | let matches = hostname.match(/^(https?:\/\/)?(.*?)(:\d+)?$/);
112 |
113 | if (hostname && matches) {
114 | host = matches[2];
115 | port = parseInt((matches[3] || '').substr(1) || (hostname.indexOf('https') === 0 ? 443 : 80));
116 | }
117 |
118 | let pkg;
119 |
120 | try {
121 | pkg = serviceConfig.get();
122 | } catch(err) {
123 | return callback(err);
124 | }
125 |
126 | let registry = new Registry(host, port, config.get('ACCESS_TOKEN'));
127 | console.log();
128 | console.log(`Packaging ${pkg.stdlib.name}@${environment === RELEASE_ENV ? version || pkg.stdlib.version : environment}...`);
129 |
130 | !fs.existsSync('/tmp') && fs.mkdirSync('/tmp');
131 | !fs.existsSync('/tmp/stdlib') && fs.mkdirSync('/tmp/stdlib', 0o777);
132 | let serviceName = (pkg.stdlib.name).replace(/\//g, '.');
133 | let tmpPath = `/tmp/stdlib/${serviceName}.${new Date().valueOf()}.tar.gz`;
134 |
135 | let start = new Date().valueOf();
136 |
137 | let tarball = fs.createWriteStream(tmpPath, {mode: 0o777});
138 |
139 | let pack = tar.pack();
140 |
141 | let ignore = ['node_modules/', '.stdlib', '.git', '.DS_Store'];
142 | if (fs.existsSync(path.join(process.cwd(), '.acignore'))) {
143 | ignore = ignore.concat(
144 | fs.readFileSync(path.join(process.cwd(), '.acignore')).toString()
145 | .split('\n')
146 | .map(line => line.trim())
147 | .filter(line => !!line)
148 | );
149 | }
150 | ignore = ignore.map(v => v.endsWith('/') ? `${v}*` : v);
151 |
152 | // Load transformers
153 | let env, stdlib;
154 |
155 | try {
156 | env = require(path.join(process.cwd(), 'env.json'));
157 | } catch (e) {
158 | console.error(e);
159 | console.error(new Error('Invalid env.json in this directory'));
160 | process.exit(1);
161 | }
162 |
163 | try {
164 | stdlib = require(path.join(process.cwd(), 'stdlib.json'));
165 | } catch (e) {
166 | console.error(e);
167 | console.error(new Error('Invalid stdlib.json in this directory'));
168 | process.exit(1);
169 | }
170 |
171 | const transformers = new Transformers(env, stdlib, environment);
172 | transformers.compile()
173 | .then(
174 | preloadFiles => {
175 |
176 | let data = readFiles(
177 | process.cwd(),
178 | {ignore: ignore},
179 | );
180 |
181 | data.forEach(file => {
182 | if (preloadFiles[file.filename]) {
183 | throw new Error(
184 | `Error with file "${file.filename}":` +
185 | `This file was preloaded as part of a transformer, ` +
186 | `it can not be overwritten.`
187 | );
188 | }
189 | });
190 |
191 | Object.keys(preloadFiles).forEach(filename => {
192 | data.push({filename: filename, buffer: preloadFiles[filename]});
193 | });
194 |
195 | // pipe the pack stream to your file
196 | pack.pipe(tarball);
197 |
198 | // Run everything in parallel...
199 |
200 | async.parallel(data.map(file => {
201 | return (callback) => {
202 | pack.entry({name: file.filename}, file.buffer, callback);
203 | };
204 | }), (err) => {
205 |
206 | if (err) {
207 | return callback(err);
208 | }
209 |
210 | pack.finalize();
211 |
212 | });
213 |
214 | tarball.on('close', () => {
215 |
216 | let buffer = fs.readFileSync(tmpPath);
217 | fs.unlinkSync(tmpPath);
218 |
219 | zlib.gzip(buffer, (err, result) => {
220 |
221 | if (err) {
222 | return callback(err);
223 | }
224 |
225 | console.log(`Packaging complete, total size is ${result.byteLength} bytes!`);
226 | console.log(`Uploading ${chalk.bold(`${pkg.stdlib.name}@${environment === RELEASE_ENV ? version || pkg.stdlib.version : environment}`)} to Autocode at ${host}:${port}...`);
227 |
228 | let registryParams = {channel: '1234'};
229 | if (environment === RELEASE_ENV) {
230 | registryParams.release = 't';
231 | if (version) {
232 | registryParams.version = version;
233 | }
234 | } else {
235 | registryParams.environment = environment;
236 | }
237 |
238 | if (force) {
239 | registryParams.force = 't';
240 | }
241 |
242 | return registry.request(
243 | 'up/verify',
244 | registryParams,
245 | result,
246 | (err, response) => {
247 |
248 | if (err) {
249 | console.log();
250 | return callback(err);
251 | } else {
252 | let t = new Date().valueOf() - start;
253 | console.log()
254 | console.log(`${chalk.bold(`${response.name}@${response.environment || response.version}`)} uploaded successfully in ${t} ms!`);
255 | console.log(`${chalk.bold.green('Live URL:')} https://${response.name.split('/')[0]}.api.stdlib.com/${response.name.split('/')[1]}@${response.environment || response.version}/`);
256 | console.log();
257 | return callback(null);
258 | }
259 |
260 | },
261 | (data) => {
262 | console.log(`Registry :: ${data.message}`);
263 | }
264 | );
265 |
266 | });
267 |
268 | });
269 |
270 | },
271 | (err) => {
272 | callback(err);
273 | }
274 | );
275 |
276 | }
277 |
278 | }
279 |
280 | module.exports = UpCommand;
281 |
--------------------------------------------------------------------------------
/cli/commands/user.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Command = require('cmnd').Command;
4 | const APIResource = require('api-res');
5 |
6 | const chalk = require('chalk');
7 | const inquirer = require('inquirer');
8 |
9 | const config = require('../config.js');
10 |
11 | let formatDigits = (num, figs) => {
12 |
13 | num = (parseInt(Math.max(num, 0)) || 0).toString();
14 | let zeroes = Math.max(figs - num.length, 0);
15 | return `${Array(zeroes + 1).join('0')}${num}`;
16 |
17 | };
18 |
19 | let formatDate = function(str) {
20 |
21 | let date = new Date(str);
22 | let months = 'January February March April May June July August September October November December'.split(' ');
23 | let ends = ['th', 'st', 'nd', 'rd', 'th'];
24 |
25 | let y = chalk.bold(date.getFullYear());
26 | let m = chalk.bold(months[date.getMonth()]);
27 | let d = chalk.bold(date.getDate());
28 | let e = chalk.bold(ends[d] || 'th');
29 | let hh = chalk.bold(formatDigits(date.getHours(), 2));
30 | let mm = chalk.bold(formatDigits(date.getMinutes(), 2));
31 | let ss = chalk.bold(formatDigits(date.getSeconds(), 2));
32 | let ms = chalk.bold(formatDigits(date.valueOf() % 1000, 3));
33 |
34 | return `${m} ${d}${e}, ${y} at ${hh}:${mm}:${ss}.${ms}`;
35 |
36 | };
37 |
38 | class UserCommand extends Command {
39 |
40 | constructor() {
41 |
42 | super('user');
43 |
44 | }
45 |
46 | help() {
47 |
48 | return {
49 | description: 'Retrieves (and sets) current user information',
50 | flags: {
51 | s: ' Sets a specified key-value pair'
52 | },
53 | vflags: {
54 | set: ' Sets a specified key-value pair',
55 | 'new-password': 'Sets a new password via a prompt',
56 | 'reset-password': ' Sends a password reset request for the specified e-mail address'
57 | }
58 | };
59 |
60 | }
61 |
62 | async run(params, callback) {
63 |
64 | let host = 'api.autocode.com';
65 | let port = 443;
66 |
67 | let hostname = (params.flags.h && params.flags.h[0]) || '';
68 | let matches = hostname.match(/^(https?:\/\/)?(.*?)(:\d+)?$/);
69 |
70 | if (hostname && matches) {
71 | host = matches[2];
72 | port = parseInt((matches[3] || '').substr(1) || (hostname.indexOf('https') === 0 ? 443 : 80));
73 | }
74 |
75 | let resource = new APIResource(host, port);
76 | resource.authorize(config.get('ACCESS_TOKEN'));
77 |
78 | // If resetting password
79 | if (params.vflags['reset-password']) {
80 | let resetEmail = params.vflags['reset-password'][0];
81 | return resource.request('v1/password_reset_requests').create({}, {email: resetEmail}, (err, response) => {
82 |
83 | if (err) {
84 | return callback(err);
85 | }
86 |
87 | console.log('Password reset e-mail sent. Check the inbox for ' + resetEmail + ' for more details.');
88 | return callback(null);
89 |
90 | });
91 | }
92 |
93 | if (params.vflags['new-password']) {
94 |
95 | let promptResult = await inquirer.prompt(
96 | [
97 | {
98 | name: 'old_password',
99 | type: 'password',
100 | default: '',
101 | message: 'Old Password'
102 | },
103 | {
104 | name: 'password',
105 | type: 'password',
106 | default: '',
107 | message: 'New Password'
108 | },
109 | {
110 | name: 'repeat_password',
111 | type: 'password',
112 | default: '',
113 | message: 'Repeat Password'
114 | }
115 | ]
116 | );
117 |
118 | resource.request('v1/users').index({me: true}, (err, response) => {
119 |
120 | if (err) {
121 | return callback(err);
122 | }
123 |
124 | let user = response.data[0];
125 | if (!user) {
126 | return callback(new Error('We couldn\'t retrieve your user data. Try again shortly.'));
127 | }
128 |
129 | resource.request('v1/users').update(user.id, {}, promptResult, (err, response) => {
130 |
131 | if (err) {
132 | return callback(err);
133 | }
134 |
135 | let user = response.data[0];
136 | if (!user) {
137 | return callback(new Error('We couldn\'t change your password. Try again shortly.'));
138 | }
139 |
140 | return callback(null, 'Password changed successfully.');
141 |
142 | });
143 |
144 | });
145 |
146 | return;
147 |
148 | }
149 |
150 | let set = params.vflags.set || params.flags.s || [];
151 | let update = null;
152 |
153 | if (set.length) {
154 | update = {};
155 | update[set[0]] = set.slice(1).join(' ');
156 | if (update.password) {
157 | return callback(new Error('Please use --new-password to set your password'));
158 | }
159 | }
160 |
161 | let fnComplete = (user, callback) => {
162 |
163 | delete user.wallet;
164 | delete user.sourceBookmarks;
165 | delete user.serviceBookmarks;
166 |
167 | var len = 20;
168 |
169 | Object.keys(user).forEach(function(k) {
170 | var alen = Math.max(1, len - k.length + 1);
171 | console.log(k + ': ' + Array(alen).join(' ') + user[k]);
172 | });
173 |
174 | console.log();
175 | callback(null);
176 |
177 | };
178 |
179 | resource.request('v1/users').index({me: true}, (err, response) => {
180 |
181 | if (err) {
182 | return callback(err);
183 | }
184 |
185 | let user = response.data[0];
186 | if (!user) {
187 | return callback(new Error('We couldn\'t retrieve your user data. Try again shortly.'));
188 | }
189 |
190 | if (!update) {
191 | return fnComplete(user, callback);
192 | }
193 |
194 | resource.request('v1/users').update(user.id, {}, update, (err, response) => {
195 |
196 | if (err) {
197 | return callback(err);
198 | }
199 |
200 | let user = response.data[0];
201 | if (!user) {
202 | return callback(new Error('We couldn\'t set your user data. Try again shortly.'));
203 | }
204 |
205 | fnComplete(user, callback);
206 |
207 | });
208 |
209 | });
210 |
211 | }
212 |
213 | }
214 |
215 | module.exports = UserCommand;
216 |
--------------------------------------------------------------------------------
/cli/commands/version.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Command = require('cmnd').Command;
4 |
5 | class VersionCommand extends Command {
6 |
7 | constructor() {
8 |
9 | super('version');
10 |
11 | }
12 |
13 | help() {
14 |
15 | return {
16 | description: 'Returns currently installed version of Autocode command line tools'
17 | };
18 |
19 | }
20 |
21 | run(params, callback) {
22 |
23 | let pkg = require('../../package.json');
24 | callback(null, pkg.version);
25 |
26 | }
27 |
28 | }
29 |
30 | module.exports = VersionCommand;
31 |
--------------------------------------------------------------------------------
/cli/config.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const path = require('path');
3 | const DEFAULT_FILENAME = '.librc';
4 | const DEFAULT_PATHNAME = process.env[(process.platform == 'win32') ? 'USERPROFILE' : 'HOME'] || '/tmp';
5 | const LEGACY_FILENAME = '.stdlib';
6 | const CONFIG_VAR_WORKSPACE = 'WORKSPACE_PATH';
7 | const CONFIG_VAR_TIMESTAMP = 'CREATED_AT';
8 |
9 | class Config {
10 |
11 | constructor (pathname, filename) {
12 | this.pathname = pathname || DEFAULT_PATHNAME;
13 | this.filename = filename || DEFAULT_FILENAME;
14 | this.data = this.load();
15 | }
16 |
17 | fullpath () {
18 | return path.join(this.pathname, this.filename);
19 | }
20 |
21 | location (depth) {
22 | depth = depth || 0;
23 | let loc = process.cwd();
24 | let pathnames = loc.split(path.sep);
25 | // If Window directory drive, don't add starting "/"
26 | let fullpath = pathnames[0].indexOf(':') > -1 ?
27 | path.join.apply(path, pathnames.slice(0, pathnames.length - depth)) :
28 | path.join.apply(path, ['/'].concat(pathnames.slice(0, pathnames.length - depth)));
29 | return this.workspace() &&
30 | depth <= pathnames.length &&
31 | fullpath.toLowerCase() === this.workspace().toLowerCase();
32 | }
33 |
34 | workspace () {
35 | return this.get(CONFIG_VAR_WORKSPACE);
36 | }
37 |
38 | legacypath () {
39 | let cwd = process.cwd();
40 | let directories = cwd.split(path.sep);
41 | let pathname;
42 | for (let i = directories.length; i > 0; i--) {
43 | let relpath = path.join(directories.slice(0, i).join(path.sep), LEGACY_FILENAME);
44 | if (fs.existsSync(relpath)) {
45 | pathname = relpath;
46 | break;
47 | }
48 | }
49 | return pathname;
50 | }
51 |
52 | load () {
53 | if (!fs.existsSync(this.fullpath())) {
54 | let legacypath = this.legacypath();
55 | if (legacypath) {
56 | let legacydirs = legacypath.split(path.sep);
57 | legacydirs.pop();
58 | let legacydir = legacydirs.join(path.sep);
59 | this.initialize(
60 | legacydir,
61 | this.read(legacypath)
62 | )
63 | } else {
64 | this.write({});
65 | }
66 | }
67 | return this.read();
68 | }
69 |
70 | initialize (workpath, data) {
71 | data = data || {};
72 | data[CONFIG_VAR_WORKSPACE] = workpath;
73 | data[CONFIG_VAR_TIMESTAMP] = Math.floor(new Date().valueOf() / 1000);
74 | this.write(data);
75 | }
76 |
77 | read (pathname) {
78 | pathname = pathname || this.fullpath();
79 | return fs.readFileSync(pathname).toString()
80 | .split('\n')
81 | .filter(v => v)
82 | .map(line => line.split('='))
83 | .reduce((data, values) => {
84 | if (values.length > 1) {
85 | data[values[0]] = values.slice(1).join('=');
86 | }
87 | return data;
88 | }, {})
89 | }
90 |
91 | write (data) {
92 | this.data = data = data || this.data;
93 | fs.writeFileSync(
94 | this.fullpath(),
95 | Object.keys(data)
96 | .map(key => `${key}=${data[key]}`)
97 | .join('\n') + '\n'
98 | );
99 | return data;
100 | }
101 |
102 | get (key, defaultValue) {
103 | return key in this.data ? this.data[key] : defaultValue;
104 | }
105 |
106 | set (key, value, log) {
107 | let oldValue = this.get(key);
108 | log && console.log(
109 | `[${this.fullpath()}] Setting "${key}=${value}"` +
110 | oldValue !== newValue ? ` (was "${key}=${oldValue}")` : ''
111 | );
112 | return this.data[key] = value;
113 | }
114 |
115 | unset (key) {
116 | return delete this.data[key];
117 | }
118 |
119 | save (key, value, log) {
120 | this.set(key, value, log);
121 | return this.write()[key];
122 | }
123 |
124 | }
125 |
126 | module.exports = new Config();
127 |
--------------------------------------------------------------------------------
/cli/credentials.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const path = require('path');
4 | const fs = require('fs');
5 | const FILENAME = '.stdlib';
6 |
7 | function findPath (maxDepth) {
8 |
9 | maxDepth = parseInt(maxDepth) || 0;
10 |
11 | let cwd = process.cwd();
12 | let directories = cwd.split(path.sep);
13 | let stdlibPath = '';
14 |
15 | for (let i = directories.length; i > 0; i--) {
16 | let relpath = path.join(directories.slice(0, i).join(path.sep), FILENAME);
17 | if (fs.existsSync(relpath)) {
18 | stdlibPath = relpath;
19 | break;
20 | }
21 | if (!(--maxDepth)) {
22 | break;
23 | }
24 | }
25 |
26 | return stdlibPath;
27 |
28 | }
29 |
30 | function readCredentials() {
31 |
32 | if (process.env.hasOwnProperty('STDLIB_ACCESS_TOKEN')) {
33 | let prefix = 'STDLIB_';
34 |
35 | return Object.keys(process.env)
36 | .filter(key => key.indexOf(prefix) === 0)
37 | .map(key => key.substr(prefix.length))
38 | .reduce((obj, key) => {
39 | obj[key] = process.env[prefix + key];
40 | return obj;
41 | }, {});
42 | } else {
43 | let cred = '';
44 | let stdlibPath = findPath();
45 |
46 | if (!stdlibPath) {
47 | throw new Error(`Please initialize stdlib in directory tree or set STDLIB_ACCESS_TOKEN as environment variable`);
48 | }
49 |
50 | cred = fs.readFileSync(stdlibPath).toString();
51 |
52 | return cred
53 | .split('\n')
54 | .filter(v => v)
55 | .map(l => l.split('='))
56 | .reduce((p, c) => {
57 | p[c[0]] = c.slice(1).join('=');
58 | return p;
59 | }, {});
60 | }
61 | }
62 |
63 | function writeCredentials(obj, pathname) {
64 |
65 | let stdlibPath = pathname ? path.join(pathname, FILENAME) : findPath();
66 |
67 | if (!stdlibPath) {
68 | throw new Error(`Please initialize stdlib in directory tree`);
69 | }
70 |
71 | let str = Object.keys(obj).map(k => `${k}=${obj[k]}`).join('\n') + '\n';
72 | fs.writeFileSync(stdlibPath, str);
73 |
74 | }
75 |
76 | module.exports = {
77 |
78 | create: () => {
79 |
80 | writeCredentials({CREATED_AT: Math.floor(new Date().valueOf() / 1000)}, process.cwd());
81 | return true;
82 |
83 | },
84 |
85 | location: (depth) => {
86 |
87 | let loc = findPath(depth).split(path.sep);
88 | loc.pop();
89 | return loc.join(path.sep);
90 |
91 | },
92 |
93 | read: (key) => {
94 |
95 | return readCredentials()[key];
96 |
97 | },
98 |
99 | write: (key, value) => {
100 |
101 | let cred = readCredentials();
102 | cred[key] = value;
103 | writeCredentials(cred);
104 | return true;
105 |
106 | }
107 |
108 | };
109 |
--------------------------------------------------------------------------------
/cli/env.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const fs = require('fs');
3 |
4 | module.exports = () => {
5 |
6 | let env = {};
7 |
8 | if (fs.existsSync(path.join(process.cwd(), 'env.json'))) {
9 | let envName = 'dev';
10 | try {
11 | env = require(path.join(process.cwd(), 'env.json'))[envName] || {};
12 | } catch (e) {
13 | env = {};
14 | console.warn('Warning: invalid JSON in env.json');
15 | }
16 | env.ENV = envName;
17 | }
18 |
19 | return env;
20 |
21 | };
22 |
--------------------------------------------------------------------------------
/cli/error_log.js:
--------------------------------------------------------------------------------
1 | const chalk = require('chalk');
2 |
3 | module.exports = (err) => {
4 |
5 | console.log();
6 | err.message && console.log(`${chalk.bold.red('Error: ')}${err.message}`);
7 | err.details && Object.keys(err.details).forEach(k => {
8 | let details = err.details[k];
9 | details = details instanceof Array ? details : [details];
10 | console.log(` ${chalk.bold(k)}`);
11 | details.forEach(d => console.log(` - ${d}`))
12 | });
13 | console.log();
14 |
15 | };
16 |
--------------------------------------------------------------------------------
/cli/fileio.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const zlib = require('zlib');
3 |
4 | const tar = require('tar-stream');
5 | const stream = require('stream');
6 | const path = require('path');
7 |
8 | // Figure out what function Directory we're using, and what we're fetching
9 | // from the template
10 | const functionDir = fs.existsSync('f') ? 'f' : 'functions';
11 | const unusedFunctionDir = functionDir !== 'f' ? 'f' : 'functions';
12 |
13 | function writeFile(directory, pathname, buffer, dirs) {
14 |
15 | pathname = path.join.apply(path, [].concat(directory.split('/'), pathname.split('/')));
16 | let paths = pathname.split(path.sep);
17 |
18 | for (let i = 1; i < paths.length; i++) {
19 | let dirpath = path.join.apply(path, [process.cwd()].concat(paths.slice(0, i)));
20 | if (!dirs[dirpath]) {
21 | !fs.existsSync(dirpath) && fs.mkdirSync(dirpath);
22 | dirs[dirpath] = true;
23 | }
24 | }
25 |
26 | fs.writeFileSync(path.join(process.cwd(), pathname), buffer, {mode: 0o777});
27 | return dirs;
28 |
29 | }
30 |
31 | function writeFiles(directory, files) {
32 |
33 | Object.keys(files)
34 | .reduce((dirs, pathname) => {
35 | // Makes sure templates work with new directory names
36 | let writePath = pathname;
37 | let unused = `${unusedFunctionDir}/`;
38 | if (pathname.substr(0, unused.length) === unused) {
39 | writePath = `${functionDir}/${pathname.substr(unused.length)}`;
40 | }
41 | return writeFile(directory, writePath, files[pathname], dirs);
42 | }, {});
43 |
44 | }
45 |
46 | function readFiles(basepath, pathname, files, condenseDots) {
47 | basepath = basepath || '.';
48 | pathname = pathname || '.';
49 | condenseDots = !!condenseDots;
50 | return fs.readdirSync(path.join(basepath, pathname)).reduce((files, filename) => {
51 | let savename = (condenseDots && filename.substr(0, 2) === '..') ?
52 | filename.substr(1) :
53 | filename;
54 | let savepath = path.join(pathname, savename).split(path.sep).join('/');
55 | let filepath = path.join(pathname, filename);
56 | let fullpath = path.join(basepath, filepath);
57 | if (fs.statSync(fullpath).isDirectory()) {
58 | return readFiles(basepath, filepath, files, condenseDots);
59 | } else {
60 | files[savepath] = fs.readFileSync(fullpath);
61 | return files;
62 | }
63 | }, files || {});
64 |
65 | };
66 |
67 | module.exports = {
68 | readTemplateFiles: (directory) => readFiles(directory, '.', {}, true),
69 | readFiles: (directory) => readFiles(directory),
70 | writeFiles: (directory, files) => writeFiles(directory, files),
71 | extract: (directory, tarball, callback) => {
72 |
73 | zlib.gunzip(tarball, (err, result) => {
74 |
75 | if (err) {
76 | return callback(new Error(`Error decompressing package`));
77 | }
78 |
79 | let files = {};
80 | let extract = tar.extract();
81 | let tarStream = new stream.PassThrough();
82 |
83 | extract.on('entry', (header, stream, cb) => {
84 | let buffers = [];
85 | stream.on('data', (chunk) => buffers.push(chunk));
86 | stream.on('end', () => {
87 | files[header.name] = Buffer.concat(buffers);
88 | cb();
89 | });
90 | });
91 |
92 | extract.on('finish', () => {
93 | try {
94 | writeFiles(directory, files);
95 | callback();
96 | } catch (e) {
97 | callback(e);
98 | }
99 | });
100 |
101 | tarStream.end(result);
102 | tarStream.pipe(extract);
103 |
104 | });
105 |
106 | }
107 | };
108 |
--------------------------------------------------------------------------------
/cli/local_gateway.js:
--------------------------------------------------------------------------------
1 | const url = require('url');
2 |
3 | const chalk = require('chalk');
4 |
5 | const config = require('./config.js');
6 | const Gateway = require('functionscript').Daemon.Gateway;
7 |
8 | class LocalGateway extends Gateway {
9 |
10 | constructor (cfg) {
11 | cfg = cfg || {};
12 | cfg.name = 'LocalGateway';
13 | cfg.defaultTimeout = 120000;
14 | super(cfg);
15 | this._maxResultLogLength = 128;
16 | }
17 |
18 | formatName (name) {
19 | return chalk.grey(`[${chalk.green(this.name)}]`);
20 | }
21 |
22 | formatRequest (req) {
23 | return chalk.grey(`(${chalk.yellow(req ? (req._background ? chalk.bold('bg:') : '') + req._uuid.split('-')[0] : 'GLOBAL')}) ${this.routename(req)}`);
24 | }
25 |
26 | formatMessage (message, logType) {
27 | let color = {result: 'cyan', error: 'red'}[logType] || 'grey';
28 | return chalk[color](super.formatMessage(message, logType));
29 | }
30 |
31 | service (serviceName) {
32 | this.serviceName = serviceName.replace(/^\//gi, '');
33 | }
34 |
35 | environment (env) {
36 | Object.keys(env).forEach(key => process.env[key] = env[key]);
37 | return true;
38 | }
39 |
40 | listen (port, callback, opts) {
41 | port = port || this.port;
42 | process.env.STDLIB_LOCAL_PORT = port;
43 | super.listen(port, callback, opts);
44 | }
45 |
46 | createContext (req, definitions, params, data, buffer) {
47 | let context = super.createContext(req, definitions, params, data, buffer);
48 | context.service = {};
49 | context.service.name = this.serviceName;
50 | context.service.path = this.serviceName.split('/');
51 | context.service.version = null;
52 | context.service.environment = 'local';
53 | context.service.identifier = `${context.service.path.join('.')}[@${context.service.version || context.service.environment}]`;
54 | context.service.uuid = '000000';
55 | context.service.hash = '000000';
56 | context.providers = context.providers || {};
57 | return context;
58 | }
59 |
60 | resolve (req, res, buffer, callback) {
61 | let urlinfo = url.parse(req.url, true);
62 | let pathname = urlinfo.pathname;
63 | if (this.serviceName && pathname.indexOf(this.serviceName) !== 1) {
64 | let e = new Error(`Local Service Not Loaded: ${pathname}`);
65 | e.statusCode = 404;
66 | return callback(e);
67 | } else {
68 | pathname = pathname.substr(1 + this.serviceName.length);
69 | }
70 | let definition;
71 | try {
72 | definition = this.findDefinition(this.definitions, pathname);
73 | } catch (e) {
74 | e.statusCode = 404;
75 | return callback(e);
76 | }
77 | return callback(null, definition, {}, buffer);
78 | }
79 |
80 | end (req, value) {
81 | value = value === undefined ? null : value;
82 | value = value + '';
83 | if (value.length > this._maxResultLogLength) {
84 | value = value.substr(0, this._maxResultLogLength) +
85 | ` ... (truncated ${value.length - this._maxResultLogLength} bytes)`;
86 | }
87 | this.log(req, value.replace(/\u0007/gi, ''), 'result');
88 | }
89 |
90 | }
91 |
92 | module.exports = LocalGateway;
93 |
--------------------------------------------------------------------------------
/cli/local_http.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | const { Daemon, FunctionParser } = require('functionscript');
4 | const LocalGateway = require('./local_gateway');
5 | const Transformers = require('./transformers');
6 | const chalk = require('chalk');
7 |
8 | const processArgs = process.argv.slice(2).reduce((processArgs, val) => {
9 | let [key, value] = val.split('=');
10 | processArgs[key] = value;
11 | return processArgs;
12 | }, {});
13 |
14 | const cluster = require('cluster');
15 | const PORT = processArgs.PORT || process.env.PORT || 8000;
16 | const ROUTE = processArgs.ROUTE || process.env.ROUTE || '/';
17 | const NAME = processArgs.NAME || 'Unnamed API Project';
18 | const MAX_REQUEST_SIZE = process.env.MAX_REQUEST_SIZE && parseInt(process.env.MAX_REQUEST_SIZE) || null;
19 |
20 | if (cluster.isMaster) {
21 |
22 | // Start HTTP Daemon
23 | new Daemon(1).start(PORT);
24 |
25 | } else {
26 |
27 | let env, stdlib;
28 |
29 | try {
30 | env = require(path.join(process.cwd(), 'env.json'));
31 | } catch (e) {
32 | console.error(e);
33 | console.error(new Error('Invalid env.json in this directory'));
34 | process.exit(1);
35 | }
36 |
37 | try {
38 | stdlib = require(path.join(process.cwd(), 'stdlib.json'));
39 | } catch (e) {
40 | console.error(e);
41 | console.error(new Error('Invalid stdlib.json in this directory'));
42 | process.exit(1);
43 | }
44 |
45 | const transformers = new Transformers(env, stdlib, 'local');
46 |
47 | // Cluster to Gateway
48 | let gateway = new LocalGateway({
49 | port: PORT,
50 | maxRequestSizeMB: MAX_REQUEST_SIZE,
51 | debug: true
52 | });
53 | let functionParser = new FunctionParser();
54 | transformers.compile()
55 | .then(
56 | preloadFiles => {
57 | gateway.service(ROUTE);
58 | gateway.environment(env.local || {});
59 | gateway.define(
60 | functionParser.load(
61 | process.cwd(),
62 | 'functions',
63 | 'www',
64 | null,
65 | preloadFiles
66 | ),
67 | preloadFiles
68 | );
69 | gateway.listen(PORT);
70 | console.log();
71 | console.log(`Autocode API:`);
72 | console.log(`\t${chalk.bold.blue(NAME)}`);
73 | console.log();
74 | console.log(`Running on:`);
75 | console.log(`\t${chalk.bold.green(`localhost:${PORT}${ROUTE}`)}`);
76 | console.log();
77 | },
78 | err => {
79 | console.error(err),
80 | process.exit(1);
81 | }
82 | );
83 |
84 | }
85 |
--------------------------------------------------------------------------------
/cli/parser.js:
--------------------------------------------------------------------------------
1 | // Uses stdlib reflect service to parse args, kwargs
2 | const https = require('https');
3 | const http = require('http');
4 | const path = require('path');
5 | const url = require('url');
6 | const fs = require('fs');
7 |
8 | const chalk = require('chalk');
9 | const lib = require('lib');
10 |
11 | const env = require('./env.js');
12 |
13 | module.exports = {
14 | createServer: function createServer(pkg, port, offline) {
15 |
16 | offline = !!offline;
17 |
18 | let serviceName = pkg.stdlib.name;
19 | process.env = env();
20 |
21 | if (offline) {
22 | console.warn(
23 | chalk.bold.yellow('Info:') +
24 | ' Operating in offline mode, kwargs can only be processed via query parameters'
25 | );
26 | }
27 |
28 | let server = http.createServer((req, res) => {
29 |
30 | let urlParts = url.parse(req.url, true);
31 | let pathname = req.url[0] !== '/' ? `/${req.url}` : req.url;
32 | pathname = pathname.split('?')[0];
33 | let libname = pathname.split('/');
34 | libname[libname.length - 1] || libname.pop();
35 | libname = libname.join('.');
36 |
37 | let response = (err, params) => {
38 |
39 | if (err) {
40 | res.writeHead(400, {'Content-Type': 'text/plain'});
41 | return res.end(`Error: ${err.message}`);
42 | }
43 |
44 | console.log(`[function: ${libname}] ${JSON.stringify({args: params.args, kwargs: params.kwargs})}`);
45 |
46 | lib[`${libname}`](...params.args, params.kwargs, (err, result, headers) => {
47 |
48 | if (err) {
49 | res.writeHead(400, {'Content-Type': 'text/plain'});
50 | res.end(`Error: ${err.message}`);
51 | } else {
52 | res.writeHead(200, headers);
53 | if (result instanceof Buffer || typeof result !== 'object') {
54 | res.end(result);
55 | } else {
56 | try {
57 | result = JSON.stringify(result);
58 | } catch (e) {
59 | result = '{}';
60 | }
61 | res.end(result);
62 | }
63 | }
64 |
65 | });
66 |
67 | };
68 |
69 | if (offline) {
70 | response(null, {args: [], kwargs: urlParts.query, remoteAddress: '::1'});
71 | } else {
72 | this.reflect(req, response);
73 | }
74 |
75 | });
76 |
77 | server.listen(port);
78 | console.log();
79 | console.log(`HTTP development server listening for service ${chalk.bold.green(serviceName)} on port ${chalk.bold(port)}`);
80 | console.log();
81 |
82 | },
83 | check: function check(callback) {
84 |
85 | this.send(null, null, null, null, callback);
86 |
87 | },
88 | send: function send(search, method, headers, buffer, callback) {
89 |
90 | search = search || '';
91 | method = method || 'GET';
92 | headers = headers || {};
93 | buffer = buffer || Buffer.from([]);
94 | delete headers['accept-encoding']; // no gzip
95 | delete headers['host']; // no host
96 |
97 | let libreq = https.request({
98 | hostname: 'f.stdlib.com',
99 | port: 443,
100 | path: `/utils/reflect/${search}`,
101 | method: method,
102 | headers: headers
103 | }, (libres) => {
104 |
105 | let lbuffers = [];
106 |
107 | libres.on('data', chunk => lbuffers.push(chunk));
108 | libres.on('end', () => {
109 |
110 | let lbuffer = Buffer.concat(lbuffers);
111 | let json = {};
112 |
113 | try {
114 | json = JSON.parse(lbuffer.toString());
115 | } catch (e) {
116 | return callback(new Error('Unexpected stdlib reflect response: ' + lbuffer.toString()));
117 | }
118 |
119 | callback(null, json);
120 |
121 | });
122 |
123 | });
124 |
125 | libreq.on('error', (err) => callback(new Error('Could not connect to stdlib reflect')));
126 | libreq.write(buffer)
127 | libreq.end();
128 |
129 | },
130 | reflect: function reflect(req, callback) {
131 |
132 | let buffers = [];
133 | let search = url.parse(req.url, true).search;
134 |
135 | req.on('data', chunk => buffers.push(chunk));
136 | req.on('end', () => {
137 |
138 | let buffer = Buffer.concat(buffers);
139 | req.headers['user-agent'] = null;
140 | this.send(search, req.method, req.headers, buffer, callback);
141 |
142 | });
143 |
144 | }
145 | };
146 |
--------------------------------------------------------------------------------
/cli/registry.js:
--------------------------------------------------------------------------------
1 |
2 | const http = require('http');
3 | const https = require('https');
4 |
5 | const uuid = require('uuid');
6 | const Pusher = require('pusher-js');
7 |
8 | class Registry {
9 |
10 | constructor (host, port, accessToken) {
11 | this.host = host;
12 | this.port = parseInt(port) || 0;
13 | this.accessToken = accessToken;
14 | this.http = this.port === 443 ? https : http;
15 | this.pusher = new Pusher('676bca06a7eb744a334f', {
16 | cluster: 'us3',
17 | forceTLS: true
18 | });
19 | }
20 |
21 | request (action, params, body, completeCallback, progressCallback) {
22 |
23 | let pusher = this.pusher;
24 |
25 | let completed = false;
26 | let callback = function () {
27 | completed = true;
28 | completeCallback.apply(this, arguments);
29 | };
30 |
31 | let performAction = function () {
32 |
33 | let uriString = Object.keys(params)
34 | .filter(function (key) {
35 | return params[key] !== undefined && params[key] !== null;
36 | })
37 | .map(function (key) {
38 | let value = params[key];
39 | value = typeof value === 'object'
40 | ? JSON.stringify(value)
41 | : value;
42 | return [encodeURIComponent(key), encodeURIComponent(value)].join('=');
43 | })
44 | .join('&');
45 |
46 | let req = this.http.request(
47 | {
48 | hostname: this.host,
49 | port: this.port,
50 | path: '/' + action + '?' + uriString,
51 | method: 'POST',
52 | headers: {
53 | 'Authorization': `Bearer ${this.accessToken}`,
54 | 'Content-Length': body ? body.byteLength : 0
55 | }
56 | },
57 | res => {
58 | let buffers = [];
59 | res.on('data', chunk => buffers.push(chunk));
60 | res.on('end', () => {
61 | let buffer = Buffer.concat(buffers);
62 | let text = buffer.toString();
63 | let json;
64 | let error;
65 | try {
66 | json = JSON.parse(text);
67 | } catch (e) {
68 | error = new Error('Could not ' + action + ', invalid response: "' + text +'"');
69 | }
70 | if (res.statusCode !== 200) {
71 | return callback(
72 | error
73 | ? new Error(text)
74 | : new Error(
75 | json && json.error
76 | ? json.error.message
77 | : text
78 | )
79 | );
80 | } else {
81 | return callback(null, json || buffer);
82 | }
83 | });
84 | }
85 | );
86 | req.on('error', error => {
87 | return callback(new Error('Could not ' + action + ' project: HTTP request failed'));
88 | })
89 | req.end(body)
90 |
91 | }.bind(this);
92 |
93 | if (progressCallback) {
94 | let lastIndex = 0;
95 | let messages = [];
96 | let channelId = uuid.v4();
97 | let channelName = 'registry@' + channelId;
98 | params.channel = channelId;
99 | let channel = pusher.subscribe(channelName);
100 | channel.bind('announce', function (data) {
101 | while (messages.length < data.index + 1) {
102 | messages.push(null);
103 | }
104 | messages[data.index] = data;
105 | let emptyIndex = messages.indexOf(null);
106 | emptyIndex = emptyIndex === -1
107 | ? messages.length
108 | : emptyIndex;
109 | if (!completed) {
110 | messages.slice(lastIndex, emptyIndex).forEach(function (data) {
111 | progressCallback(data);
112 | });
113 | lastIndex = emptyIndex;
114 | }
115 | });
116 | let active = false;
117 | setTimeout(function () {
118 | if (!active) {
119 | performAction();
120 | active = true;
121 | }
122 | }, 500);
123 | channel.bind('pusher:subscription_succeeded', function() {
124 | if (!active) {
125 | performAction();
126 | active = true;
127 | }
128 | });
129 | } else {
130 | performAction();
131 | }
132 |
133 | }
134 |
135 | }
136 |
137 | module.exports = Registry;
138 |
--------------------------------------------------------------------------------
/cli/service_config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const DEFAULT_BUILD = 'faaslang';
3 | const DEFAULT_VERSION = '0.0.0';
4 |
5 | module.exports = {
6 | get: () => {
7 | let stdlibJSON;
8 | let packageJSON;
9 |
10 | try {
11 | packageJSON = require(path.join(process.cwd(), 'package.json'));
12 | } catch(err) {
13 | throw new Error('Invalid package.json');
14 | }
15 |
16 | try {
17 | stdlibJSON = require(path.join(process.cwd(), 'stdlib.json'));
18 | } catch (err) {
19 | stdlibJSON = null;
20 | }
21 |
22 | if (packageJSON.hasOwnProperty('stdlib') && stdlibJSON) {
23 | throw new Error('Please remove property "stdlib" from package.json since stdlib.json is present.');
24 | }
25 |
26 | packageJSON.stdlib = packageJSON.stdlib || stdlibJSON || {};
27 |
28 | // Set from package.json (legacy path)
29 | packageJSON.stdlib.build = packageJSON.stdlib.build || packageJSON.build || 'faaslang';
30 | packageJSON.stdlib.version = packageJSON.stdlib.version || packageJSON.version || '0.0.0';
31 | packageJSON.stdlib.name = packageJSON.stdlib.name || packageJSON.name || '';
32 |
33 | // Set fields that are needed
34 | packageJSON.stdlib.local = packageJSON.stdlib.local || {};
35 |
36 | return packageJSON;
37 | }
38 | };
39 |
--------------------------------------------------------------------------------
/cli/tabler.js:
--------------------------------------------------------------------------------
1 | const chalk = require('chalk');
2 |
3 | function isDate (dt) {
4 | return dt instanceof Date ||
5 | dt.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}\w$/);
6 | }
7 |
8 | function zeroPad (n, l, s) {
9 | s = s === undefined ? '0' : s;
10 | n = n.toString();
11 | let delta = Math.max(0, l - n.length);
12 | return Array(delta + 1).join(s) + n;
13 | }
14 |
15 | function formatDate (dt) {
16 | dt = dt instanceof Date ? dt : new Date(dt);
17 | let months = 'Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov'.split(' ');
18 | let suffix = 'th st nd rd th th th th th th'.split(' ');
19 | let m = months[dt.getMonth()];
20 | let y = dt.getFullYear();
21 | let d = zeroPad(dt.getDate(), 2);
22 | let th = suffix[dt.getDate() % 10];
23 | let hh = zeroPad(dt.getHours(), 2);
24 | let mm = zeroPad(dt.getMinutes(), 2);
25 | let ss = zeroPad(dt.getSeconds(), 2);
26 | return `${m} ${d} ${y} ${hh}:${mm}`;
27 | }
28 |
29 | module.exports = (fields, objects, consoleOutput, isOption) => {
30 |
31 | isOption = !!isOption;
32 |
33 | let sizes = fields.map(f => {
34 | let rowSizes = [f.length].concat(
35 | objects.map(o => {
36 | if (!o) {
37 | return 0;
38 | } else {
39 | let val = o[f];
40 | val = Array.isArray(val) ? val[0] : val;
41 | val = isDate(val) ? formatDate(val) : val;
42 | return val.toString().length;
43 | }
44 | })
45 | );
46 | return Math.max.apply(null, rowSizes);
47 | });
48 |
49 | let delims = {
50 | vertical: ['┬', '│', '┴'],
51 | horizontal: ['├', '─', '┤'],
52 | top: ['┌', '┐'],
53 | bottom: ['└', '┘'],
54 | cross: ['┼']
55 | };
56 |
57 | let headerFormat = h => h;
58 |
59 | if (consoleOutput) {
60 | for (key in delims) {
61 | delims[key] = delims[key].map(s => chalk.dim(s));
62 | }
63 | headerFormat = h => chalk.dim(h);
64 | }
65 |
66 | let result = [
67 | {
68 | name: delims.top[0] + fields.map((f, i) => Array(sizes[i] + 3).join(delims.horizontal[1])).join(delims.vertical[0]) + delims.top[1],
69 | value: null
70 | },
71 | {
72 | name: delims.vertical[1] + fields.map((f, i) => ' ' + headerFormat(f) + Array(sizes[i] - f.length + 1).join(' ') + ' ').join(delims.vertical[1]) + delims.vertical[1],
73 | value: null
74 | },
75 | {
76 | name: delims.horizontal[0] + fields.map((f, i) => Array(sizes[i] + 3).join(delims.horizontal[1])).join(delims.cross[0]) + delims.horizontal[2],
77 | value: null
78 | }
79 | ].concat(
80 | objects.map((o, i) => {
81 | if (!o) {
82 | return {
83 | name: delims.horizontal[0] + fields.map((f, i) => Array(sizes[i] + 3).join(delims.horizontal[1])).join(delims.cross[0]) + delims.horizontal[2],
84 | value: null
85 | };
86 | } else {
87 | return {
88 | name: delims.vertical[1] + fields.map((f, i) => {
89 | let val = o[f];
90 | let fmt = v => v;
91 | if (Array.isArray(val)) {
92 | fmt = val[1];
93 | val = val[0];
94 | }
95 | val = val.toString();
96 | val = isDate(val) ? formatDate(val) : val;
97 | return ' ' + fmt(val) + Array(sizes[i] - val.length + 1).join(' ') + ' '
98 | }).join(delims.vertical[1]) + delims.vertical[1],
99 | value: o
100 | }
101 | }
102 | }),
103 | {
104 | name: delims.bottom[0] + fields.map((f, i) => Array(sizes[i] + 3).join(delims.horizontal[1])).join(delims.vertical[2]) + delims.bottom[1],
105 | value: null
106 | }
107 | );
108 |
109 | return isOption ? result : result.map(r => r.name).join('\n');
110 |
111 | };
112 |
--------------------------------------------------------------------------------
/cli/tar.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const fs = require('fs');
4 | const zlib = require('zlib');
5 | const path = require('path');
6 |
7 | const async = require('async');
8 | const tar = require('tar-stream');
9 |
10 | const formatSize = size => {
11 | let sizes = [[2, 'M'], [1, 'k']];
12 | while (sizes.length) {
13 | let checkSize = sizes.shift();
14 | let limit = Math.pow(1024, checkSize[0]);
15 | if (size > limit) {
16 | return `${(size / limit).toFixed(2)} ${checkSize[1]}B`
17 | }
18 | }
19 | return `${size} B`;
20 | };
21 |
22 | function readFiles (base, properties, dir, data) {
23 |
24 | dir = dir || '/';
25 | data = data || [];
26 | data._size = data._size || 0;
27 | properties = properties || {};
28 |
29 | let ignore = properties.ignore || {};
30 |
31 | return fs.readdirSync(path.join(base, dir)).reduce((data, f) => {
32 |
33 | let pathname = path.join(dir, f);
34 | let fullpath = path.join(base, pathname);
35 |
36 | for (let i = 0; i < ignore.length; i++) {
37 | if (ignore[i][0] === '/') {
38 | if (pathname.split(path.sep).join('/') === ignore[i]) {
39 | return data;
40 | }
41 | } else {
42 | if (f === ignore[i]) {
43 | return data;
44 | }
45 | }
46 | }
47 |
48 | if (fs.statSync(fullpath).isDirectory()) {
49 | return readFiles(base, properties, pathname, data);
50 | } else {
51 | let filename = pathname[0] === path.sep ? pathname.substr(1) : pathname;
52 | let buffer = fs.readFileSync(fullpath);
53 | filename = filename.split(path.sep).join('/'); // Windows
54 | data._size += buffer.byteLength;
55 | data.push({filename: filename, buffer: buffer});
56 | return data;
57 | }
58 |
59 | }, data);
60 |
61 | };
62 |
63 | module.exports = {
64 |
65 | pack: async function (pathname, showProgress) {
66 |
67 | showProgress = !!showProgress;
68 | const progress = {log: function () { showProgress && console.log.apply(null, arguments); }};
69 |
70 | return new Promise((resolve, reject) => {
71 |
72 | !fs.existsSync('/tmp') && fs.mkdirSync('/tmp');
73 | !fs.existsSync('/tmp/packit') && fs.mkdirSync('/tmp/packit', 0o777);
74 | let tmpPath = `/tmp/packit/newpack.${new Date().valueOf()}.tar.gz`;
75 |
76 | let start = new Date().valueOf();
77 |
78 | let tarball = fs.createWriteStream(tmpPath, {mode: 0o777});
79 | let pack = tar.pack();
80 |
81 |
82 | let ignoreList = fs.existsSync('.libignore') ? fs.readFileSync('.libignore').toString() : '';
83 | ignoreList = ignoreList.split('\n').map(v => v.replace(/^\s(.*)\s$/, '$1')).filter(v => v);
84 |
85 | let data = readFiles(path.join(process.cwd(), pathname), {ignore: ignoreList});
86 | let packSize = 0;
87 |
88 | // pipe the pack stream to your file
89 | pack.pipe(tarball);
90 |
91 | // Run everything in parallel...
92 | async.parallel(data.map((file) => {
93 | return (callback) => {
94 | pack.entry({name: file.filename}, file.buffer, () => {
95 | packSize += file.buffer.byteLength;
96 | progress.log(`Packing "${file.filename}" (${((packSize / data._size) * 100).toFixed(2)}%) ...`);
97 | callback();
98 | });
99 | };
100 | }), (err) => {
101 | if (err) {
102 | return reject(err);
103 | }
104 | pack.finalize();
105 | });
106 | tarball.on('close', () => {
107 | let buffer = fs.readFileSync(tmpPath);
108 | fs.unlinkSync(tmpPath);
109 | progress.log(`Package size: ${formatSize(buffer.byteLength)}`);
110 | progress.log(`Compressing ...`);
111 | zlib.gzip(buffer, (err, result) => {
112 | if (err) {
113 | return reject(err);
114 | }
115 | let t = new Date().valueOf() - start;
116 | progress.log(`Compressed size: ${formatSize(result.byteLength)}`);
117 | progress.log(`Compression: ${((result.byteLength / buffer.byteLength) * 100).toFixed(2)}%`);
118 | progress.log(`Pack complete, took ${t}ms!`);
119 | resolve(result);
120 | });
121 | });
122 |
123 | });
124 |
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/cli/templates/functionscript/..gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | env.json
3 |
--------------------------------------------------------------------------------
/cli/templates/functionscript/README.md:
--------------------------------------------------------------------------------
1 | # README
2 |
3 | Use this to describe your project to other members of your team. :)
4 |
--------------------------------------------------------------------------------
/cli/templates/functionscript/env.json:
--------------------------------------------------------------------------------
1 | {
2 | "local": {
3 | "key": "value"
4 | },
5 | "dev": {
6 | "key": "value"
7 | },
8 | "release": {
9 | "key": "value"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/cli/templates/functionscript/functions/__main__.js:
--------------------------------------------------------------------------------
1 | /**
2 | * A basic Hello World function
3 | * @param {string} name Who you're saying hello to
4 | * @returns {string}
5 | */
6 | module.exports = async (name = 'world', context) => {
7 | return `hello ${name}`;
8 | };
9 |
--------------------------------------------------------------------------------
/cli/templates/functionscript/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "autocode webservice",
3 | "description": "",
4 | "author": "Author",
5 | "main": "functions/__main__.js",
6 | "dependencies": {
7 | "lib": "latest"
8 | },
9 | "publish": false
10 | }
11 |
--------------------------------------------------------------------------------
/cli/templates/functionscript/stdlib.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "username/service",
3 | "version": "0.0.0",
4 | "timeout": 10000
5 | }
6 |
--------------------------------------------------------------------------------
/cli/transformers.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const fs = require('fs');
3 |
4 | class Transformers {
5 |
6 | constructor (env, stdlib, environment) {
7 | this.env = env;
8 | this.stdlib = stdlib;
9 | this.environment = environment;
10 | this.list = [];
11 | this.load();
12 | }
13 |
14 | load () {
15 | let stdlib = this.stdlib;
16 | let transformers = [];
17 | if (stdlib.transformers) {
18 | if (!Array.isArray(stdlib.transformers)) {
19 | throw new Error(`"stdlib.json": "transformers" must be an array`);
20 | }
21 | transformers = stdlib.transformers.map(transformerData => {
22 | let Transformer;
23 | let transformer;
24 | if (!transformerData.pathname) {
25 | throw new Error(`"stdlib.json": "transformers" object must contain "pathname"`);
26 | }
27 | try {
28 | Transformer = require(path.join(process.cwd(), transformerData.pathname));
29 | let config = transformerData.config || {};
30 | if (!config || typeof config !== 'object') {
31 | throw new Error(`"stdlib.json": "transformers[].config" must be empty or contain an object`);
32 | }
33 | let configEnv = config[this.environment] || {};
34 | if (!config[this.environment] || typeof config[this.environment] !== 'object') {
35 | throw new Error(`"stdlib.json": "transformers[].config['${this.environment}']" must be empty or contain an object`);
36 | }
37 | transformer = new Transformer(config[this.environment], stdlib, this.environment);
38 | transformer.config = transformerData.config[this.environment];
39 | } catch (e) {
40 | console.error(e);
41 | throw new Error(`Could not load transformer: "${transformerData.pathname}"`);
42 | }
43 | return transformer;
44 | });
45 | }
46 | return this.list = transformers;
47 | }
48 |
49 | async compile () {
50 | let preloadFiles = {};
51 | let metadata = {};
52 | for (let i = 0; i < this.list.length; i++) {
53 | let transformer = this.list[i];
54 | let name = transformer.name || transformer.constructor.name;
55 | let t = new Date().valueOf();
56 | console.log(`\n[Transformer: ${name}] Execution starting`);
57 | console.log(
58 | `[Transformer: ${name}] Using config from stdlib.json: ` +
59 | `transformers[].config['${this.environment}']\n` +
60 | `${JSON.stringify(transformer.config, null, 2)}`
61 | );
62 | let result = await transformer.compile(process.cwd(), this.env[this.environment], metadata);
63 | let files = result.files || {};
64 | metadata = result.metadata || {};
65 | Object.keys(files).forEach(pathname => {
66 | if (preloadFiles[pathname]) {
67 | throw new Error(`[Transformer: ${name}]: Previous Transformer has already defined "${pathname}"`);
68 | } else {
69 | preloadFiles[pathname] = files[pathname];
70 | }
71 | if (!pathname.startsWith('www/') && !pathname.startsWith('functions/')) {
72 | throw new Error(`[Transformer: ${name}]: Invalid pathname "${pathname}", can only add endpoints in "functions/" and "www/"`);
73 | }
74 | });
75 | let t0 = new Date().valueOf() - t;
76 | console.log(`[Transformer: ${name}] Executed in ${t0} ms`);
77 | };
78 | return preloadFiles;
79 | }
80 |
81 | }
82 |
83 | module.exports = Transformers;
84 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {};
2 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "lib.cli",
3 | "version": "5.7.1",
4 | "description": "Command Line tools for Autocode - autocode.com",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "engines": {
10 | "node": ">=6.0.0"
11 | },
12 | "keywords": [
13 | "lib",
14 | "serverless",
15 | "api",
16 | "autocode",
17 | "autocode.com",
18 | "stdlib",
19 | "stdlib.com",
20 | "standard",
21 | "library",
22 | "node",
23 | "ruby",
24 | "python"
25 | ],
26 | "author": "Keith Horwood",
27 | "repository": {
28 | "type": "git",
29 | "url": "git://github.com/acode/cli.git"
30 | },
31 | "license": "MIT",
32 | "bin": {
33 | "stdlib": "cli/bin.js",
34 | "lib": "cli/bin.js"
35 | },
36 | "dependencies": {
37 | "api-res": "^0.0.8",
38 | "async": "^2.6.4",
39 | "chalk": "^1.1.3",
40 | "cmnd": "~0.3.0",
41 | "functionscript": "^2.10.6",
42 | "inquirer": "^7.3.3",
43 | "lib": "^5.1.0",
44 | "minimatch": "^3.0.4",
45 | "ncp": "^2.0.0",
46 | "pusher-js": "^7.0.2",
47 | "stream": "0.0.2",
48 | "tar-stream": "^1.5.2",
49 | "uuid": "^8.3.2"
50 | }
51 | }
52 |
--------------------------------------------------------------------------------