├── .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 | ![lib-process](https://content.public.files.stdlib.com/shared/static/images/lib-usage.gif) 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 | --------------------------------------------------------------------------------