├── .dockerignore ├── .editorconfig ├── .gitattributes ├── .github ├── github.css ├── issue_template.md └── pull_request_template.md ├── .gitignore ├── .nlu.json ├── .npmignore ├── .npmrc ├── .npp.json ├── .r2g ├── config.js ├── exec.sh ├── fixtures │ ├── foo.js │ └── readme.md └── tests │ └── smoke-test.js ├── .travis.yml ├── Dockerfile.r2g ├── assets ├── Dockerfile.r2g.original ├── Dockerfile.r2g.original.old ├── completion.sh ├── config.original.js ├── contents │ ├── config.js │ ├── fixtures │ │ └── readme.md │ ├── readme.md │ └── tests │ │ ├── smoke-test.1.js │ │ └── smoke-test.2.js ├── default.package.json ├── default.r2g.config.js ├── exec.sh ├── postinstall.sh ├── realpath.c └── shell.sh ├── cli ├── exp.js ├── exp.sh ├── r2g.sh ├── r2g_get_project_root.js └── r2g_symlink.sh ├── dist ├── .gitkeep └── README.md ├── docs ├── faq.md ├── r2g-runtime-steps.md ├── r2g-smoke-test-exported-main-fn-type-a.md ├── r2g-smoke-test-type-b.md ├── r2g-type-a-troubleshooting.md └── r2g-using-local-deps.md ├── license.md ├── package-lock.json ├── package.json ├── readme.md ├── scripts ├── idea │ └── .gitkeep ├── npm │ ├── .gitkeep │ ├── install.sh │ └── publish.sh ├── tarzan │ └── add.sh ├── travis │ ├── after_script.sh │ ├── before_install.sh │ ├── install.sh │ └── script.sh └── vscode │ └── .gitkeep ├── src ├── commands │ ├── basic │ │ ├── cli-options.ts │ │ ├── index.ts │ │ └── parse-cli-opts.ts │ ├── clean │ │ ├── cli-options.ts │ │ ├── index.ts │ │ ├── parse-cli-options.ts │ │ └── run.ts │ ├── init │ │ ├── cli-options.ts │ │ ├── index.ts │ │ ├── parse-cli-options.ts │ │ └── run.ts │ ├── inspect │ │ ├── cli-options.ts │ │ ├── index.ts │ │ ├── parse-cli-options.ts │ │ └── run.ts │ ├── publish │ │ ├── cli-options.ts │ │ ├── index.ts │ │ ├── parse-cli-options.ts │ │ └── run.ts │ └── run │ │ ├── cli-options.ts │ │ ├── copy-deps.ts │ │ ├── get-fs-map.ts │ │ ├── index.ts │ │ ├── parse-cli-options.ts │ │ ├── rename-deps.ts │ │ └── run.ts ├── index.ts ├── logger.ts └── smoke-tester.ts ├── test ├── .gitkeep ├── foo.sh ├── index.js ├── index.sh ├── modify.sh ├── simple.sh ├── test.ts └── x.js └── tsconfig.json /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | charset = utf-8 6 | trim_trailing_whitespace = true 7 | insert_final_newline = true 8 | 9 | [*.js] 10 | indent_style = space 11 | indent_size = 2 12 | 13 | [*.ts] 14 | indent_style = space 15 | indent_size = 2 16 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.github/github.css: -------------------------------------------------------------------------------- 1 | 2 | 3 | .markdown-body kbd { 4 | padding: .2em .5em; 5 | font-size: 1em; 6 | line-height: 1.75; 7 | display: inline-block; 8 | /*padding: 3px 5px;*/ 9 | /*font-size: 11px;*/ 10 | /*line-height: 10px;*/ 11 | color: #555; 12 | vertical-align: middle; 13 | background-color: #fcfcfc; 14 | border: solid 1px #ccc; 15 | border-bottom-color: #bbb; 16 | border-radius: 3px; 17 | box-shadow: inset 0 -1px 0 #bbb 18 | } 19 | 20 | 21 | .boxBorder { 22 | border: 2px solid #990066; 23 | padding: 10px; 24 | outline: #990066 solid 5px; 25 | outline-offset: 5px; 26 | } 27 | -------------------------------------------------------------------------------- /.github/issue_template.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | #### Feature, bug, or other? 4 | 5 | 6 | #### Please run the bash function and copy the output here: 7 | 8 | ```bash 9 | get_my_ores_versions(){ 10 | echo "nodejs version: $(node --version)" 11 | echo "r2g version: $(r2g --version)" 12 | echo "npm version: $(npm --version)" 13 | } 14 | ``` 15 | 16 | 17 | 18 | #### What is the issue? How to replicate? 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ### Thanks for submitting a PR 4 | 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | ### folders 3 | .idea 4 | .vscode 5 | node_modules 6 | logs 7 | .nyc_output 8 | coverage 9 | dist/* 10 | 11 | ### files 12 | path 13 | assert 14 | 15 | ### matching 16 | *.log 17 | *.tgz 18 | Dockerfile* 19 | 20 | ### negation 21 | !dist/README.md 22 | -------------------------------------------------------------------------------- /.nlu.json: -------------------------------------------------------------------------------- 1 | { 2 | "npm-link-up": true, 3 | "searchRoots": [ 4 | "../.." 5 | ], 6 | "ignore": [], 7 | "comments": [], 8 | "list": [ 9 | "clean-trace", 10 | "@oresoftware/shell", 11 | "residence" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | 2 | ### folders 3 | node_modules 4 | scripts 5 | test 6 | .idea 7 | .vscode 8 | src 9 | .r2g 10 | .circleci 11 | .github 12 | 13 | ### files 14 | .dockerignore 15 | .editorconfig 16 | .gitattributes 17 | .gitignore 18 | .npmignore 19 | .travis.yml 20 | .nlu.json 21 | .npp.json 22 | 23 | ### matching 24 | Dockerfile* 25 | *.log 26 | *.md 27 | tsconfig*.json 28 | *.DS_Store 29 | *.tgz 30 | 31 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=true 2 | -------------------------------------------------------------------------------- /.npp.json: -------------------------------------------------------------------------------- 1 | { 2 | "vcsType": "git", 3 | "vcsInfo": { 4 | "remote":"origin", 5 | "master": "remotes/origin/master", 6 | "integration": "remotes/origin/dev" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.r2g/config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // note: only include dependencies in this file, which are in your project's package.json file 4 | const path = require('path'); 5 | 6 | const envLookup = 'R2G_SEARCH_ROOT'; 7 | const searchRootPath = path.resolve(process.env[envLookup] || ''); 8 | console.log('r2g search root path:',searchRootPath); 9 | 10 | if (!path.isAbsolute(searchRootPath)) { 11 | throw new Error(`Please set the env var "${envLookup}" to an absolute folder path.`); 12 | } 13 | 14 | exports.default = { 15 | 16 | searchRoot: searchRootPath, 17 | tests: '', 18 | packages: { 19 | 'clean-trace': true, 20 | 'residence': true 21 | } 22 | 23 | }; 24 | -------------------------------------------------------------------------------- /.r2g/exec.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e; 4 | 5 | if [ ! -f package.json ]; then 6 | echo "there is no package.json file in your PWD." >&2; 7 | false; // since there is no package.json file, probably should abort here 8 | fi 9 | 10 | 11 | map="$docker_r2g_fs_map" 12 | search_root="$docker_r2g_search_root" 13 | shared="$docker_r2g_shared_dir"; 14 | name="$docker_r2g_package_name" # your project's package.json name field 15 | base_image="node:$r2g_node_version" 16 | 17 | 18 | container="docker_r2g.$name"; 19 | docker stop "$container" || echo "no container with name $container running." 20 | docker rm "$container" || echo "no container with name $container could be removed." 21 | 22 | tag="docker_r2g_image/$name"; 23 | 24 | docker build \ 25 | -f Dockerfile.r2g \ 26 | -t "$tag" \ 27 | --build-arg base_image="$base_image" \ 28 | --build-arg CACHEBUST="$(date +%s)" . 29 | 30 | 31 | docker run \ 32 | -v "$search_root:$shared:ro" \ 33 | -e docker_r2g_fs_map="$map" \ 34 | -e r2g_container_id="$container" \ 35 | --entrypoint "dkr2g" \ 36 | --name "$container" "$tag" \ 37 | run --allow-unknown "$@" 38 | 39 | 40 | 41 | ## to debug: 42 | # docker exec -ti /bin/bash 43 | -------------------------------------------------------------------------------- /.r2g/fixtures/foo.js: -------------------------------------------------------------------------------- 1 | 2 | // this is called by ../tests/smoke-test.js 3 | exports.foo = 3; 4 | -------------------------------------------------------------------------------- /.r2g/fixtures/readme.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ORESoftware/r2g/2f20124bfcd9613230023c9d10476f7251ae9a23/.r2g/fixtures/readme.md -------------------------------------------------------------------------------- /.r2g/tests/smoke-test.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | /* 5 | 6 | docker.r2g notes: 7 | 8 | this file will be copied to this location: 9 | 10 | $HOME/.r2g/temp/project/smoke-test.js 11 | 12 | and it will then be executed with: 13 | 14 | node smoke-test.js 15 | 16 | 17 | so, write a smoke test in this file, which only calls require() against your library. 18 | for example if your library is named "foo.bar", then the *only* require call you 19 | should make is to require('foo.bar'). If you make require calls to any other library 20 | in node_modules, then you will got non-deterministic results. require calls to core/built-in libraries are fine. 21 | 22 | */ 23 | 24 | 25 | const assert = require('assert'); 26 | const path = require('path'); 27 | const cp = require('child_process'); 28 | const os = require('os'); 29 | const fs = require('fs'); 30 | const EE = require('events'); 31 | 32 | const v = require('../fixtures/foo.js'); 33 | assert.strictEqual(v.foo, 3, 'foo value is not 3, but it should be 3.'); 34 | 35 | 36 | 37 | // your test goes here 38 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | 2 | language: node_js 3 | 4 | node_js: 5 | - '10' 6 | - '9' 7 | - '8' 8 | - '7' 9 | - '6' 10 | 11 | 12 | env: 13 | - CXX=g++-4.8 14 | 15 | 16 | addons: 17 | apt: 18 | sources: 19 | - ubuntu-toolchain-r-test 20 | packages: 21 | - g++-4.8 22 | 23 | 24 | before_install: './scripts/travis/before_install.sh' 25 | install: './scripts/travis/install.sh' 26 | script: './scripts/travis/script.sh' 27 | after_script: './scripts/travis/after_script.sh' 28 | 29 | 30 | git: 31 | depth: 1 32 | 33 | cache: 34 | bundler: true 35 | directories: 36 | - node_modules 37 | - /home/travis/.npm 38 | -------------------------------------------------------------------------------- /Dockerfile.r2g: -------------------------------------------------------------------------------- 1 | 2 | ARG base_image 3 | FROM $base_image 4 | 5 | ENV ores_bash_utils "https://raw.githubusercontent.com/oresoftware/npm.bash.utils" 6 | ENV r2g_tarballs_base_url="https://raw.githubusercontent.com/oresoftware/tarballs" 7 | ENV FORCE_COLOR=1 8 | ENV docker_r2g_in_container=yes 9 | ENV MY_DOCKER_R2G_SEARCH_ROOT="/home/node" 10 | 11 | RUN curl -sS -o- "$ores_bash_utils/master/assets/install-basics.sh" | bash 12 | RUN sudo echo "node ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers 13 | 14 | USER node 15 | ENV USER="node" 16 | ENV HOME="/home/node" 17 | RUN mkdir -p /home/node/.docker_r2g_cache 18 | RUN mkdir -p /home/node/app/node_modules 19 | WORKDIR /home/node/app 20 | 21 | RUN sudo chmod -R 777 /home/node 22 | 23 | RUN curl -sS -o- "$ores_bash_utils/master/assets/run-non-root-chown.sh" | bash 24 | RUN curl -sS -o- "$ores_bash_utils/master/assets/run-npm-config-settings.sh" | bash 25 | 26 | ARG CACHEBUST=1 27 | 28 | #RUN npm i -g "@oresoftware/read.json" 29 | #RUN npm i -g "@oresoftware/r2g.docker" 30 | #RUN npm i -g "@oresoftware/r2g" 31 | 32 | RUN npm i -g "$r2g_tarballs_base_url/master/tgz/oresoftware/read.json.tgz?$(date +%s)" 33 | RUN npm i -g "$r2g_tarballs_base_url/master/tgz/oresoftware/r2g.docker.tgz?$(date +%s)" 34 | 35 | COPY . . 36 | 37 | RUN sudo chmod -R 777 /home/node 38 | 39 | RUN npm i -s -g "." 40 | 41 | ENTRYPOINT dkr2g run --allow-unknown $dkr2g_run_args 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /assets/Dockerfile.r2g.original: -------------------------------------------------------------------------------- 1 | 2 | ARG base_image 3 | FROM $base_image 4 | 5 | ENV ores_bash_utils "https://raw.githubusercontent.com/oresoftware/npm.bash.utils" 6 | ENV r2g_tarballs_base_url="https://raw.githubusercontent.com/oresoftware/tarballs" 7 | ENV FORCE_COLOR=1 8 | ENV docker_r2g_in_container=yes 9 | ENV MY_DOCKER_R2G_SEARCH_ROOT="/home/node" 10 | 11 | RUN curl -sS -o- "$ores_bash_utils/master/assets/install-basics.sh" | bash 12 | RUN sudo echo "node ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers 13 | 14 | USER node 15 | ENV USER="node" 16 | ENV HOME="/home/node" 17 | RUN mkdir -p /home/node/.docker_r2g_cache 18 | RUN mkdir -p /home/node/app/node_modules 19 | WORKDIR /home/node/app 20 | 21 | RUN sudo chmod -R 777 /home/node 22 | 23 | RUN curl -sS -o- "$ores_bash_utils/master/assets/run-non-root-chown.sh" | bash 24 | RUN curl -sS -o- "$ores_bash_utils/master/assets/run-npm-config-settings.sh" | bash 25 | 26 | ARG CACHEBUST=1 27 | 28 | #RUN npm i -g "@oresoftware/read.json" 29 | #RUN npm i -g "@oresoftware/r2g.docker" 30 | #RUN npm i -g "@oresoftware/r2g" 31 | 32 | RUN npm i -g "$r2g_tarballs_base_url/master/tgz/oresoftware/read.json.tgz?$(date +%s)" 33 | RUN npm i -g "$r2g_tarballs_base_url/master/tgz/oresoftware/r2g.tgz?$(date +%s)" 34 | RUN npm i -g "$r2g_tarballs_base_url/master/tgz/oresoftware/r2g.docker.tgz?$(date +%s)" 35 | 36 | ARG NODE_ENV 37 | ENV NODE_ENV ${NODE_ENV:-production} 38 | 39 | COPY . . 40 | 41 | RUN sudo chmod -R 777 /home/node 42 | 43 | # RUN npm i -s -g "." 44 | # RUN npm link 45 | 46 | ENTRYPOINT dkr2g run --allow-unknown $dkr2g_run_args 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /assets/Dockerfile.r2g.original.old: -------------------------------------------------------------------------------- 1 | FROM node:10 2 | 3 | RUN apt-get -y update 4 | RUN apt-get -y install sudo 5 | RUN sudo apt-get -y update 6 | RUN apt-get install -y netcat 7 | RUN apt-get install -y rsync 8 | 9 | RUN sudo echo "node ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers 10 | 11 | ENV FORCE_COLOR=1 12 | ENV docker_r2g_in_container=yes 13 | ENV MY_DOCKER_R2G_SEARCH_ROOT="/home/node" 14 | 15 | USER node 16 | ENV USER="node" 17 | ENV HOME="/home/node" 18 | RUN mkdir -p /home/node/.docker_r2g_cache 19 | RUN mkdir -p /home/node/app/node_modules 20 | WORKDIR /home/node/app 21 | 22 | RUN sudo chmod -R 777 /home/node 23 | RUN sudo chown -R $(whoami) $(npm config get prefix)/lib 24 | RUN sudo chown -R $(whoami) $(npm config get prefix)/lib/node_modules 25 | RUN sudo chown -R $(whoami) $(npm config get prefix)/bin 26 | RUN sudo chown -R $(whoami) $(npm config get prefix)/share 27 | RUN sudo chown -R $(whoami) /usr/local/lib 28 | RUN sudo chown -R $(whoami) /usr/local/etc 29 | 30 | RUN npm set unsafe-perm true 31 | RUN npm set cache-min 9999999 32 | RUN npm set progress=false 33 | 34 | ARG CACHEBUST=1 35 | 36 | # RUN npm install --loglevel=warn -g \ 37 | # "https://raw.githubusercontent.com/oresoftware/tarballs/master/tgz/oresoftware/npm.cache.tgz?$(date +%s)" 38 | # 39 | # COPY package.json . 40 | # COPY .r2g .r2g 41 | # RUN update_npm_cache 42 | 43 | RUN npm i -s -g "@oresoftware/r2g.docker@latest" 44 | RUN npm i -s -g "@oresoftware/r2g@latest" 45 | 46 | COPY . . 47 | 48 | RUN sudo chmod -R 777 /home/node 49 | 50 | ENTRYPOINT dkr2g run --allow-unknown 51 | 52 | -------------------------------------------------------------------------------- /assets/completion.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Bash completion generated for 'r2g' at Fri Aug 03 2018 19:00:01 GMT-0700 (Pacific Daylight Time). 4 | # 5 | # The original template lives here: 6 | # https://github.com/trentm/node-dashdash/blob/master/etc/dashdash.bash_completion.in 7 | # 8 | 9 | # 10 | # Copyright 2016 Trent Mick 11 | # Copyright 2016 Joyent, Inc. 12 | # 13 | # 14 | # A generic Bash completion driver script. 15 | # 16 | # This is meant to provide a re-usable chunk of Bash to use for 17 | # "etc/bash_completion.d/" files for individual tools. Only the "Configuration" 18 | # section with tool-specific info need differ. Features: 19 | # 20 | # - support for short and long opts 21 | # - support for knowing which options take arguments 22 | # - support for subcommands (e.g. 'git log ' to show just options for the 23 | # log subcommand) 24 | # - does the right thing with "--" to stop options 25 | # - custom optarg and arg types for custom completions 26 | # - (TODO) support for shells other than Bash (tcsh, zsh, fish?, etc.) 27 | # 28 | # 29 | # Examples/design: 30 | # 31 | # 1. Bash "default" completion. By default Bash's 'complete -o default' is 32 | # enabled. That means when there are no completions (e.g. if no opts match 33 | # the current word), then you'll get Bash's default completion. Most notably 34 | # that means you get filename completion. E.g.: 35 | # $ tool ./ 36 | # $ tool READ 37 | # 38 | # 2. all opts and subcmds: 39 | # $ tool 40 | # $ tool -v # assuming '-v' doesn't take an arg 41 | # $ tool - # matching opts 42 | # $ git lo # matching subcmds 43 | # 44 | # Long opt completions are given *without* the '=', i.e. we prefer space 45 | # separated because that's easier for good completions. 46 | # 47 | # 3. long opt arg with '=' 48 | # $ tool --file= 49 | # $ tool --file=./d 50 | # We maintain the "--file=" prefix. Limitation: With the attached prefix 51 | # the 'complete -o filenames' doesn't know to do dirname '/' suffixing. Meh. 52 | # 53 | # 4. envvars: 54 | # $ tool $ 55 | # $ tool $P 56 | # Limitation: Currently only getting exported vars, so we miss "PS1" and 57 | # others. 58 | # 59 | # 5. Defer to other completion in a subshell: 60 | # $ tool --file $(cat ./ 61 | # We get this from 'complete -o default ...'. 62 | # 63 | # 6. Custom completion types from a provided bash function. 64 | # $ tool --profile # complete available "profiles" 65 | # 66 | # 67 | # Dev Notes: 68 | # - compgen notes, from http://unix.stackexchange.com/questions/151118/understand-compgen-builtin-command 69 | # - https://www.gnu.org/software/bash/manual/html_node/Programmable-Completion-Builtins.html 70 | # 71 | 72 | 73 | # Debugging this completion: 74 | # 1. Uncomment the "_r2g_log_file=..." line. 75 | # 2. 'tail -f /var/tmp/dashdash-completion.log' in one terminal. 76 | # 3. Re-source this bash completion file. 77 | #_r2g_log=/var/tmp/dashdash-completion.log 78 | 79 | function _r2g_completer { 80 | 81 | # ---- cmd definition 82 | 83 | local cmd_shortopts="-h -h -h -s -t -v -v -v -z" 84 | local cmd_longopts="--allow-unknown --allow-unknown --allow-unknown --bash-completion --completion --docker --full --help --help --help --keep --multi --pack --search --search --search-root --search-root --skip --verbosity --verbosity --verbosity --version --vn" 85 | local cmd_optargs="--search-root=string --search-root=string --search=string --search=string --skip=string --verbosity=integer --verbosity=integer --verbosity=integer -v=integer -v=integer -v=integer" 86 | 87 | 88 | # ---- locals 89 | 90 | declare -a argv 91 | 92 | 93 | # ---- support functions 94 | 95 | function trace { 96 | [[ -n "$_r2g_log" ]] && echo "$*" >&2 97 | } 98 | 99 | function _dashdash_complete { 100 | local idx context 101 | idx=$1 102 | context=$2 103 | 104 | local shortopts longopts optargs subcmds allsubcmds argtypes 105 | shortopts="$(eval "echo \${cmd${context}_shortopts}")" 106 | longopts="$(eval "echo \${cmd${context}_longopts}")" 107 | optargs="$(eval "echo \${cmd${context}_optargs}")" 108 | subcmds="$(eval "echo \${cmd${context}_subcmds}")" 109 | allsubcmds="$(eval "echo \${cmd${context}_allsubcmds}")" 110 | IFS=', ' read -r -a argtypes <<< "$(eval "echo \${cmd${context}_argtypes}")" 111 | 112 | trace "" 113 | trace "_dashdash_complete(idx=$idx, context=$context)" 114 | trace " shortopts: $shortopts" 115 | trace " longopts: $longopts" 116 | trace " optargs: $optargs" 117 | trace " subcmds: $subcmds" 118 | trace " allsubcmds: $allsubcmds" 119 | 120 | # Get 'state' of option parsing at this COMP_POINT. 121 | # Copying "dashdash.js#parse()" behaviour here. 122 | local state= 123 | local nargs=0 124 | local i=$idx 125 | local argtype 126 | local optname 127 | local prefix 128 | local word 129 | local dashdashseen= 130 | while [[ $i -lt $len && $i -le $COMP_CWORD ]]; do 131 | argtype= 132 | optname= 133 | prefix= 134 | word= 135 | 136 | arg=${argv[$i]} 137 | trace " consider argv[$i]: '$arg'" 138 | 139 | if [[ "$arg" == "--" && $i -lt $COMP_CWORD ]]; then 140 | trace " dashdash seen" 141 | dashdashseen=yes 142 | state=arg 143 | word=$arg 144 | elif [[ -z "$dashdashseen" && "${arg:0:2}" == "--" ]]; then 145 | arg=${arg:2} 146 | if [[ "$arg" == *"="* ]]; then 147 | optname=${arg%%=*} 148 | val=${arg##*=} 149 | trace " long opt: optname='$optname' val='$val'" 150 | state=arg 151 | argtype=$(echo "$optargs" | awk -F "-$optname=" '{print $2}' | cut -d' ' -f1) 152 | word=$val 153 | prefix="--$optname=" 154 | else 155 | optname=$arg 156 | val= 157 | trace " long opt: optname='$optname'" 158 | state=longopt 159 | word=--$optname 160 | 161 | if [[ "$optargs" == *"-$optname="* && $i -lt $COMP_CWORD ]]; then 162 | i=$(( $i + 1 )) 163 | state=arg 164 | argtype=$(echo "$optargs" | awk -F "-$optname=" '{print $2}' | cut -d' ' -f1) 165 | word=${argv[$i]} 166 | trace " takes arg (consume argv[$i], word='$word')" 167 | fi 168 | fi 169 | elif [[ -z "$dashdashseen" && "${arg:0:1}" == "-" ]]; then 170 | trace " short opt group" 171 | state=shortopt 172 | word=$arg 173 | 174 | local j=1 175 | while [[ $j -lt ${#arg} ]]; do 176 | optname=${arg:$j:1} 177 | trace " consider index $j: optname '$optname'" 178 | 179 | if [[ "$optargs" == *"-$optname="* ]]; then 180 | argtype=$(echo "$optargs" | awk -F "-$optname=" '{print $2}' | cut -d' ' -f1) 181 | if [[ $(( $j + 1 )) -lt ${#arg} ]]; then 182 | state=arg 183 | word=${arg:$(( $j + 1 ))} 184 | trace " takes arg (rest of this arg, word='$word', argtype='$argtype')" 185 | elif [[ $i -lt $COMP_CWORD ]]; then 186 | state=arg 187 | i=$(( $i + 1 )) 188 | word=${argv[$i]} 189 | trace " takes arg (word='$word', argtype='$argtype')" 190 | fi 191 | break 192 | fi 193 | 194 | j=$(( $j + 1 )) 195 | done 196 | elif [[ $i -lt $COMP_CWORD && -n "$arg" ]] && $(echo "$allsubcmds" | grep -w "$arg" >/dev/null); then 197 | trace " complete subcmd: recurse _dashdash_complete" 198 | _dashdash_complete $(( $i + 1 )) "${context}__${arg/-/_}" 199 | return 200 | else 201 | trace " not an opt or a complete subcmd" 202 | state=arg 203 | word=$arg 204 | nargs=$(( $nargs + 1 )) 205 | if [[ ${#argtypes[@]} -gt 0 ]]; then 206 | argtype="${argtypes[$(( $nargs - 1 ))]}" 207 | if [[ -z "$argtype" ]]; then 208 | # If we have more args than argtypes, we use the 209 | # last type. 210 | argtype="${argtypes[@]: -1:1}" 211 | fi 212 | fi 213 | fi 214 | 215 | trace " state=$state prefix='$prefix' word='$word'" 216 | i=$(( $i + 1 )) 217 | done 218 | 219 | trace " parsed: state=$state optname='$optname' argtype='$argtype' prefix='$prefix' word='$word' dashdashseen=$dashdashseen" 220 | local compgen_opts= 221 | if [[ -n "$prefix" ]]; then 222 | compgen_opts="$compgen_opts -P $prefix" 223 | fi 224 | 225 | case $state in 226 | shortopt) 227 | compgen $compgen_opts -W "$shortopts $longopts" -- "$word" 228 | ;; 229 | longopt) 230 | compgen $compgen_opts -W "$longopts" -- "$word" 231 | ;; 232 | arg) 233 | # If we don't know what completion to do, then emit nothing. We 234 | # expect that we are running with: 235 | # complete -o default ... 236 | # where "default" means: "Use Readline's default completion if 237 | # the compspec generates no matches." This gives us the good filename 238 | # completion, completion in subshells/backticks. 239 | # 240 | # We cannot support an argtype="directory" because 241 | # compgen -S '/' -A directory -- "$word" 242 | # doesn't give a satisfying result. It doesn't stop at the trailing '/' 243 | # so you cannot descend into dirs. 244 | if [[ "${word:0:1}" == '$' ]]; then 245 | # By default, Bash will complete '$' to all envvars. Apparently 246 | # 'complete -o default' does *not* give us that. The following 247 | # gets *close* to the same completions: '-A export' misses envvars 248 | # like "PS1". 249 | trace " completing envvars" 250 | compgen $compgen_opts -P '$' -A export -- "${word:1}" 251 | elif [[ -z "$argtype" ]]; then 252 | # Only include opts in completions if $word is not empty. 253 | # This is to avoid completing the leading '-', which foils 254 | # using 'default' completion. 255 | if [[ -n "$dashdashseen" ]]; then 256 | trace " completing subcmds, if any (no argtype, dashdash seen)" 257 | compgen $compgen_opts -W "$subcmds" -- "$word" 258 | elif [[ -z "$word" ]]; then 259 | trace " completing subcmds, if any (no argtype, empty word)" 260 | compgen $compgen_opts -W "$subcmds" -- "$word" 261 | else 262 | trace " completing opts & subcmds (no argtype)" 263 | compgen $compgen_opts -W "$shortopts $longopts $subcmds" -- "$word" 264 | fi 265 | elif [[ $argtype == "none" ]]; then 266 | # We want *no* completions, i.e. some way to get the active 267 | # 'complete -o default' to not do filename completion. 268 | trace " completing 'none' (hack to imply no completions)" 269 | echo "##-no-completion- -results-##" 270 | elif [[ $argtype == "file" ]]; then 271 | # 'complete -o default' gives the best filename completion, at least 272 | # on Mac. 273 | trace " completing 'file' (let 'complete -o default' handle it)" 274 | echo "" 275 | elif ! type complete_$argtype 2>/dev/null >/dev/null; then 276 | trace " completing '$argtype' (fallback to default b/c complete_$argtype is unknown)" 277 | echo "" 278 | else 279 | trace " completing custom '$argtype'" 280 | completions=$(complete_$argtype "$word") 281 | if [[ -z "$completions" ]]; then 282 | trace " no custom '$argtype' completions" 283 | # These are in ascii and "dictionary" order so they sort 284 | # correctly. 285 | echo "##-no-completion- -results-##" 286 | else 287 | echo $completions 288 | fi 289 | fi 290 | ;; 291 | *) 292 | trace " unknown state: $state" 293 | ;; 294 | esac 295 | } 296 | 297 | 298 | trace "" 299 | trace "-- $(date)" 300 | #trace "\$IFS: '$IFS'" 301 | #trace "\$@: '$@'" 302 | #trace "COMP_WORDBREAKS: '$COMP_WORDBREAKS'" 303 | trace "COMP_CWORD: '$COMP_CWORD'" 304 | trace "COMP_LINE: '$COMP_LINE'" 305 | trace "COMP_POINT: $COMP_POINT" 306 | 307 | # Guard against negative COMP_CWORD. This is a Bash bug at least on 308 | # Mac 10.10.4's bash. See 309 | # . 310 | if [[ $COMP_CWORD -lt 0 ]]; then 311 | trace "abort on negative COMP_CWORD" 312 | exit 1; 313 | fi 314 | 315 | # I don't know how to do array manip on argv vars, 316 | # so copy over to argv array to work on them. 317 | shift # the leading '--' 318 | i=0 319 | len=$# 320 | while [[ $# -gt 0 ]]; do 321 | argv[$i]=$1 322 | shift; 323 | i=$(( $i + 1 )) 324 | done 325 | trace "argv: '${argv[@]}'" 326 | trace "argv[COMP_CWORD-1]: '${argv[$(( $COMP_CWORD - 1 ))]}'" 327 | trace "argv[COMP_CWORD]: '${argv[$COMP_CWORD]}'" 328 | trace "argv len: '$len'" 329 | 330 | _dashdash_complete 1 "" 331 | } 332 | 333 | 334 | # ---- mainline 335 | 336 | # Note: This if-block to help work with 'compdef' and 'compctl' is 337 | # adapted from 'npm completion'. 338 | if type complete &>/dev/null; then 339 | function _r2g_completion { 340 | local _log_file=/dev/null 341 | [[ -z "$_r2g_log" ]] || _log_file="$_r2g_log" 342 | COMPREPLY=($(COMP_CWORD="$COMP_CWORD" \ 343 | COMP_LINE="$COMP_LINE" \ 344 | COMP_POINT="$COMP_POINT" \ 345 | _r2g_completer -- "${COMP_WORDS[@]}" \ 346 | 2>$_log_file)) || return $? 347 | } 348 | complete -o default -F _r2g_completion r2g 349 | elif type compdef &>/dev/null; then 350 | function _r2g_completion { 351 | local _log_file=/dev/null 352 | [[ -z "$_r2g_log" ]] || _log_file="$_r2g_log" 353 | compadd -- $(COMP_CWORD=$((CURRENT-1)) \ 354 | COMP_LINE=$BUFFER \ 355 | COMP_POINT=0 \ 356 | _r2g_completer -- "${words[@]}" \ 357 | 2>$_log_file) 358 | } 359 | compdef _r2g_completion r2g 360 | elif type compctl &>/dev/null; then 361 | function _r2g_completion { 362 | local cword line point words si 363 | read -Ac words 364 | read -cn cword 365 | let cword-=1 366 | read -l line 367 | read -ln point 368 | local _log_file=/dev/null 369 | [[ -z "$_r2g_log" ]] || _log_file="$_r2g_log" 370 | reply=($(COMP_CWORD="$cword" \ 371 | COMP_LINE="$line" \ 372 | COMP_POINT="$point" \ 373 | _r2g_completer -- "${words[@]}" \ 374 | 2>$_log_file)) || return $? 375 | } 376 | compctl -K _r2g_completion r2g 377 | fi 378 | 379 | 380 | ## 381 | ## This is a Bash completion file for the 'r2g' command. You can install 382 | ## with either: 383 | ## 384 | ## cp FILE /usr/local/etc/bash_completion.d/r2g # Mac 385 | ## cp FILE /etc/bash_completion.d/r2g # Linux 386 | ## 387 | ## or: 388 | ## 389 | ## cp FILE > ~/.r2g.completion 390 | ## echo "source ~/.r2g.completion" >> ~/.bashrc 391 | ## 392 | -------------------------------------------------------------------------------- /assets/config.original.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // note: only include dependencies in this file, which are in your project's package.json file 4 | const path = require('path'); 5 | 6 | if (!path.isAbsolute(process.env.MY_DOCKER_R2G_SEARCH_ROOT || '')) { 7 | throw new Error('Please set the env var "MY_DOCKER_R2G_SEARCH_ROOT" to an absolute folder path.'); 8 | } 9 | 10 | exports.default = { 11 | 12 | searchRoot: path.resolve(process.env.MY_DOCKER_R2G_SEARCH_ROOT), 13 | tests: '', 14 | packages: {} 15 | 16 | }; 17 | -------------------------------------------------------------------------------- /assets/contents/config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // NOTE : the only dependencies you should import here are core/built-in modules 4 | const path = require('path'); 5 | 6 | const searchRoot = path.resolve(process.env.MY_DOCKER_R2G_SEARCH_ROOT || process.env.HOME || ''); 7 | 8 | if (!path.isAbsolute(searchRoot)) { 9 | throw new Error('Please set the env var "MY_DOCKER_R2G_SEARCH_ROOT" to an absolute folder path,' + 10 | ' (note that user $HOME is usually not specific enough, and also that $HOME env var is empty).'); 11 | } 12 | 13 | exports.default = { 14 | 15 | searchRoot, 16 | tests: '', 17 | packages: {} 18 | 19 | }; 20 | -------------------------------------------------------------------------------- /assets/contents/fixtures/readme.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ### Using the fixtures dir 4 | 5 | 6 | *The "fixtures" dir is the only place where you scripts in the "tests" dir can load files from. 7 | 8 | Initially, your "tests" and "fixtures" dirs are here: 9 | 10 | 11 | ``` 12 | "$HOME/.r2g/temp/project/node_modules/X/.r2g/{fixtures,tests}" 13 | ``` 14 | 15 | they will be copied to: 16 | 17 | ``` 18 | "$HOME/.r2g/temp/project/fixtures" 19 | "$HOME/.r2g/temp/project/tests" 20 | ``` 21 | 22 | The all the files in the tests dir will be executed directly, and those tests 23 | may load files from the fixtures dir. 24 | -------------------------------------------------------------------------------- /assets/contents/readme.md: -------------------------------------------------------------------------------- 1 | 2 | ### Welcome to using r2g! 3 | 4 | ## Creating multiple test scripts in the "tests" dir 5 | 6 | These test scripts run as PhaseT of r2g. 7 | 8 | * The "tests" folder contains executable test files 9 | * Only files will be run, r2g will not look into subfolders 10 | * Only executable test files should exist in the "tests" dir, make sure they are executable (use `chmod`). 11 | * The tests dir and the fixtures dir will both be copied from `T/node_modules/X/.r2g/{fixtures,tests} => T/{fixtures,tests}` 12 | * r2g will not look into any folders in "tests", just files. 13 | * The only other files you should require are in the fixtures directory 14 | * Your scripts must have a hashbang/shebang like so: 15 | 16 | ``` 17 | #!/usr/bin/env node 18 | 19 | ``` 20 | 21 | or 22 | 23 | ``` 24 | #!/usr/bin/env bash 25 | 26 | ``` 27 | 28 | 29 | That way you can run scripts in pretty much any language, including bash etc. 30 | 31 |
32 | 33 | An example of requiring a file from the adjacent fixtures directory: 34 | 35 | 36 | ```js 37 | const f = require('../fixtures/file.json'); 38 | ``` 39 | 40 |
41 | 42 | You can also see the readme.md file in fixtures dir for more info. 43 | 44 | -------------------------------------------------------------------------------- /assets/contents/tests/smoke-test.1.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | /* 5 | 6 | READ ME: 7 | 8 | => the files in .r2g/tests will be copied to this location: 9 | 10 | $HOME/.r2g/temp/project/tests/* 11 | 12 | => they do not need to be .js files, but they need to have a hashbang, 13 | so that r2g knows how to run the file. 14 | 15 | => the test files in .r2g/tests can load non-test files from .r2g/fixtures. 16 | 17 | */ 18 | 19 | 20 | const assert = require('assert'); 21 | const path = require('path'); 22 | const cp = require('child_process'); 23 | const os = require('os'); 24 | const fs = require('fs'); 25 | const EE = require('events'); 26 | 27 | 28 | process.on('unhandledRejection', (reason, p) => { 29 | // note: unless we force process to exit with 1, process may exit with 0 upon an unhandledRejection 30 | console.error(reason); 31 | process.exit(1); 32 | }); 33 | 34 | 35 | const to = setTimeout(() => { 36 | console.error('r2g phase-T test timed out.'); 37 | process.exit(1); 38 | }, 4000); 39 | 40 | 41 | /* 42 | your test goes here, e.g.: 43 | assert.strictEqual(true, false, 'whoops'); 44 | */ 45 | -------------------------------------------------------------------------------- /assets/contents/tests/smoke-test.2.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | // see .r2g/tests/smoke-test.1.js for some instructions 5 | // essentially, do not import any code here except core libraries 6 | 7 | const assert = require('assert'); 8 | const path = require('path'); 9 | const cp = require('child_process'); 10 | const os = require('os'); 11 | const fs = require('fs'); 12 | const EE = require('events'); 13 | 14 | 15 | process.on('unhandledRejection', (reason, p) => { 16 | // note: unless we force process to exit with 1, process may exit with 0 upon an unhandledRejection 17 | console.error(reason); 18 | process.exit(1); 19 | }); 20 | 21 | const to = setTimeout(() => { 22 | console.error('r2g phase-T test timed out.'); 23 | process.exit(1); 24 | }, 4000); 25 | -------------------------------------------------------------------------------- /assets/default.package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@oresoftware/shell", 3 | "version": "0.0.101", 4 | "description": "Semver-oriented TypeScript library skeleton.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo 'you should override this, b/c this is auto-fail as is.'; exit 1;" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/ORESoftware/r2g.git" 12 | }, 13 | "keywords": [], 14 | "author": "Yo.Mama", 15 | "license": "SEE LICENSE IN LICENSE.md", 16 | "bugs": { 17 | "url": "https://github.com/ORESoftware/r2g/issues" 18 | }, 19 | "homepage": "https://github.com/ORESoftware/r2g#readme", 20 | "dependencies": {}, 21 | "devDependencies": {}, 22 | "nlu":{ 23 | "linkable": false 24 | }, 25 | "r2g":{ 26 | "npm.test": false, 27 | "linkable": false 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /assets/default.r2g.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // NOTE : only include dependencies in this file, which are: 4 | // 1. core modules 5 | // 2. in your project's package.json file as dependencies (not devDependencies) 6 | 7 | const path = require('path'); 8 | 9 | const searchRoot = path.resolve(process.env.MY_DOCKER_R2G_SEARCH_ROOT || process.env.HOME || ''); 10 | 11 | if (!path.isAbsolute(searchRoot)) { 12 | throw new Error('Please set the env var "MY_DOCKER_R2G_SEARCH_ROOT" to an absolute folder path,' + 13 | ' (note that user $HOME is usually not specific enough, and also that $HOME env var is empty).'); 14 | } 15 | 16 | exports.default = { 17 | 18 | searchRoot, 19 | tests: '', 20 | packages: {} 21 | 22 | }; 23 | -------------------------------------------------------------------------------- /assets/exec.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e; 4 | 5 | if [ ! -f package.json ]; then 6 | echo "there is no package.json file in your PWD." >&2; 7 | false; // since there is no package.json file, probably should abort here 8 | fi 9 | 10 | 11 | map="$docker_r2g_fs_map" 12 | search_root="$docker_r2g_search_root" 13 | shared="$docker_r2g_shared_dir"; 14 | name="$docker_r2g_package_name" # your project's package.json name field 15 | base_image="node:$r2g_node_version" 16 | 17 | 18 | container="docker_r2g.$name"; 19 | docker stop "$container" || echo "no container with name $container running." 20 | docker rm "$container" || echo "no container with name $container could be removed." 21 | 22 | tag="docker_r2g_image/$name"; 23 | 24 | docker build \ 25 | -f Dockerfile.r2g \ 26 | -t "$tag" \ 27 | --build-arg base_image="$base_image" \ 28 | --build-arg CACHEBUST="$(date +%s)" . 29 | 30 | 31 | docker run \ 32 | -v "$search_root:$shared:ro" \ 33 | -e docker_r2g_fs_map="$map" \ 34 | -e dkr2g_run_args="" \ 35 | -e r2g_container_id="$container" \ 36 | --entrypoint "dkr2g" \ 37 | --name "$container" "$tag" \ 38 | run --allow-unknown "$@" 39 | 40 | 41 | 42 | ## to debug: 43 | # docker exec -ti /bin/bash 44 | -------------------------------------------------------------------------------- /assets/postinstall.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e; 4 | 5 | if [[ "$r2g_skip_postinstall" == "yes" ]]; then 6 | echo "skipping r2g postinstall routine."; 7 | exit 0; 8 | fi 9 | 10 | export r2g_skip_postinstall="yes"; 11 | 12 | 13 | mkdir -p "$HOME/.r2g/temp/project" || { 14 | echo "could not create directory => '$HOME/.r2g/temp/project'..."; 15 | } 16 | 17 | 18 | mkdir -p "$HOME/.oresoftware/bash" || { 19 | echo "could not create oresoftware/bash dir." 20 | exit 1; 21 | } 22 | 23 | 24 | if [[ "$(uname -s)" == "Darwin" ]]; then 25 | 26 | if [ ! -f "$HOME/.oresoftware/bin/realpath" ]; then 27 | ( 28 | curl --silent -o- https://raw.githubusercontent.com/oresoftware/realpath/master/assets/install.sh | bash || { 29 | echo "Could not install realpath on your system."; 30 | exit 1; 31 | } 32 | ) 33 | fi 34 | fi 35 | 36 | 37 | cat "assets/shell.sh" > "$HOME/.oresoftware/bash/r2g.sh" || { 38 | echo "Could not create oresoftware/bash/r2g.sh file." 39 | exit 1; 40 | } 41 | 42 | cat "assets/completion.sh" > "$HOME/.oresoftware/bash/r2g.completion.sh" || { 43 | echo "Could not copy to ~/.oresoftware/bash." 44 | exit 1; 45 | } 46 | 47 | ( 48 | 49 | shell_file="node_modules/@oresoftware/shell/assets/shell.sh"; 50 | [ -f "$shell_file" ] && cat "$shell_file" > "$HOME/.oresoftware/shell.sh" && { 51 | echo "Successfully copied @oresoftware/shell/assets/shell.sh to $HOME/.oresoftware/shell.sh"; 52 | exit 0; 53 | } 54 | 55 | shell_file="../shell/assets/shell.sh"; 56 | [ -f "$shell_file" ] && cat "../shell/assets/shell.sh" > "$HOME/.oresoftware/shell.sh" && { 57 | echo "Successfully copied @oresoftware/shell/assets/shell.sh to $HOME/.oresoftware/shell.sh"; 58 | exit 0; 59 | } 60 | 61 | curl -H 'Cache-Control: no-cache' \ 62 | "https://raw.githubusercontent.com/oresoftware/shell/master/assets/shell.sh?$(date +%s)" \ 63 | --output "$HOME/.oresoftware/shell.sh" 2> /dev/null || { 64 | echo "curl command failed to read shell.sh"; 65 | exit 1; 66 | } 67 | ) 68 | 69 | 70 | 71 | echo; echo -e "${r2g_green}r2g was installed successfully.${r2g_no_color}"; 72 | echo -e "Add the following line to your .bashrc/.bash_profile files:"; 73 | echo -e "${r2g_cyan} . \"\$HOME/.oresoftware/shell.sh\"${r2g_no_color}"; echo; 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /assets/realpath.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main (int argc, char* argv[]){ 5 | 6 | if (argc > 1) { 7 | for (int argIter = 1; argIter < argc; ++argIter) { 8 | char *resolved_path_buffer = NULL; 9 | char *result = realpath(argv[argIter], resolved_path_buffer); 10 | 11 | puts(result); 12 | 13 | if (result != NULL) { 14 | free(result); 15 | } 16 | } 17 | } 18 | 19 | return 0; 20 | 21 | } 22 | -------------------------------------------------------------------------------- /assets/shell.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | 4 | all_export="yep"; 5 | 6 | if [[ ! "$SHELLOPTS" =~ "allexport" ]]; then 7 | all_export="nope"; 8 | set -a; 9 | fi 10 | 11 | 12 | r2g_get_latest(){ 13 | . "$BASH_SOURCE"; # source this file 14 | } 15 | 16 | r2g_get_home_dir(){ 17 | echo "$HOME/.r2g" 18 | } 19 | 20 | r2g_get_temp_dir(){ 21 | echo "$HOME/.r2g/temp" 22 | } 23 | 24 | r2g_get_project_dir(){ 25 | echo "$HOME/.r2g/temp/project" 26 | } 27 | 28 | r2g_open_project_dir(){ 29 | subl "$(r2g_get_project_dir)" 30 | } 31 | 32 | r2g_open_temp_dir(){ 33 | subl "$(r2g_get_temp_dir)" 34 | } 35 | 36 | r2g_open_home_dir(){ 37 | subl "$(r2g_get_home_dir)" 38 | } 39 | 40 | r2g_view_log(){ 41 | open "$HOME/.r2g/logs/r2g.log" 42 | } 43 | 44 | 45 | r2g(){ 46 | 47 | if ! type -f r2g &> /dev/null || ! which r2g &> /dev/null; then 48 | 49 | echo -e "Installing the 'r2g' NPM package globally..." >&2; 50 | 51 | npm i -s -g 'r2g' || { 52 | 53 | echo -e "Could not install the 'r2g' NPM package globally." >&2; 54 | echo -e "Check your user permissions to install global NPM packages." >&2; 55 | return 1; 56 | 57 | } 58 | 59 | fi 60 | 61 | command r2g "$@"; 62 | 63 | } 64 | 65 | 66 | if [ "$all_export" == "nope" ]; then 67 | set +a; 68 | fi 69 | 70 | 71 | -------------------------------------------------------------------------------- /cli/exp.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | const cp = require('child_process'); 5 | 6 | const k = cp.spawn('bash'); 7 | 8 | k.stdin.end(` 9 | 10 | ( cd . && find . -type f ) | xargs du --threshold=5KB 11 | 12 | `); 13 | 14 | k.stdout.pipe(process.stdout); 15 | -------------------------------------------------------------------------------- /cli/exp.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | 4 | 5 | handle_json(){ 6 | while read line; do 7 | cat <&2 "You need to install 'rsync' for r2g to work its magic."; 66 | exit 1; 67 | fi 68 | 69 | if [[ -z "$(which curl)" ]]; then 70 | echo >&2 "You need to install 'curl' for r2g to work its magic."; 71 | exit 1; 72 | fi 73 | 74 | . "$HOME/.oresoftware/bash/r2g.sh" || { 75 | echo >&2 "Could not source r2g bash functions from .oresoftware/bash/r2g.sh."; 76 | exit 1; 77 | } 78 | 79 | node "$commands/run" "$@" 2> >(r2g_stderr) 1> >(r2g_stdout) # |& r2g_zmx_all 80 | 81 | elif [[ "$cmd" == "init" ]]; then 82 | 83 | node "$commands/init" "$@" 2> >(r2g_stderr) 1> >(r2g_stdout) 84 | 85 | elif [[ "$cmd" == "symlink" ]] || [[ "$cmd" == "link" ]]; then 86 | 87 | r2g_symlink "$@" 2> >(r2g_stderr) 1> >(r2g_stdout) 88 | 89 | elif [[ "$cmd" == "docker" ]]; then 90 | 91 | if ! which dkr2g; then 92 | npm install -g 'r2g.docker' || { 93 | echo "Could not install r2g.docker, exiting."; 94 | exit 1; 95 | } 96 | fi 97 | 98 | dkr2g exec --allow-unknown "$@" 99 | 100 | elif [[ "$cmd" == "publish" ]]; then 101 | 102 | node "$commands/publish" "$@" 2> >(r2g_stderr) 1> >(r2g_stdout) 103 | 104 | elif [[ "$cmd" == "inspect" ]]; then 105 | 106 | node "$commands/inspect" "$@" 2> >(r2g_stderr) 1> >(r2g_stdout) 107 | 108 | elif [[ "$cmd" == "clean" ]]; then 109 | 110 | node "$commands/clean" "$@" 2> >(r2g_stderr) 1> >(r2g_stdout) 111 | 112 | else 113 | 114 | echo "r2g info: no subcommand was recognized, available commands: (r2g run, r2g init, r2g docker)." 115 | node "$commands/basic" "${my_args[@]}" 2> >(r2g_stderr) 1> >(r2g_stdout) 116 | 117 | fi 118 | 119 | exit_code="$?" 120 | 121 | if [[ "$exit_code" != "0" ]]; then 122 | echo -e "${r2g_magenta}Your r2g process is exiting with code $exit_code.${r2g_no_color}"; 123 | exit "$exit_code"; 124 | fi 125 | 126 | -------------------------------------------------------------------------------- /cli/r2g_get_project_root.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | Object.defineProperty(exports, "__esModule", { value: true }); 5 | 6 | const fs = require("fs"); 7 | const path = require("path"); 8 | 9 | (function findRoot(pth) { 10 | let possiblePkgDotJsonPath = path.resolve(String(pth) + '/package.json'); 11 | try { 12 | if(fs.lstatSync(possiblePkgDotJsonPath).isFile()){ 13 | console.log(pth); 14 | process.exit(0); 15 | } 16 | } 17 | catch (err) { 18 | let subPath = path.resolve(String(pth) + '/../'); 19 | if (subPath === pth) { 20 | console.error(' => Cannot find path to project root.'); 21 | process.exit(1); 22 | } 23 | else { 24 | return findRoot(subPath); 25 | } 26 | } 27 | })(process.cwd()); 28 | -------------------------------------------------------------------------------- /cli/r2g_symlink.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | my_args=( "$@" ); 4 | dir_name="$(dirname "$0")" 5 | read_link="$(readlink "$0")"; 6 | exec_dir="$(dirname $(dirname "$read_link"))"; 7 | my_path="$dir_name/$exec_dir"; 8 | basic_path="$(cd $(dirname ${my_path}) && pwd)/$(basename ${my_path})" 9 | commands="$basic_path/dist/commands" 10 | 11 | if ! type -f read_json &> /dev/null ; then 12 | npm i -g -s '@oresoftware/read.json' || { 13 | echo "Could not install @oresoftware/read.json"; 14 | exit 1; 15 | } 16 | fi 17 | 18 | project_root="$(r2g_get_project_root)" 19 | if [[ -z "$project_root" ]]; then 20 | echo "Could not find an NPM project root given your pwd: $PWD"; 21 | exit 1; 22 | fi 23 | 24 | package_name="$(read_json "$project_root/package.json" 'name')" 25 | if [[ -z "$package_name" ]]; then 26 | echo "Could not find an package.json name in $PWD."; 27 | exit 1; 28 | fi 29 | 30 | cd "$project_root"; 31 | 32 | package_dir="$project_root/node_modules/$package_name"; 33 | mkdir -p "$package_dir"; 34 | rm -rf "$package_dir"; 35 | 36 | if r2g_match_arg "--install" "${my_args[@]}"; then 37 | npm install --loglevel=warn 38 | fi 39 | 40 | r2g_has_pack="nope"; 41 | 42 | if ! r2g_match_arg "--pack" "${my_args[@]}"; then 43 | 44 | ln -sf "$project_root" "$package_dir" 45 | exit 0; 46 | 47 | fi 48 | 49 | 50 | symlinkable="$HOME/.r2g/temp/symlinkable" 51 | mkdir -p "$symlinkable"; 52 | rm -rf "$symlinkable"; 53 | mkdir -p "$symlinkable"; 54 | 55 | packable="$HOME/.r2g/temp/packable"; 56 | mkdir -p "$packable"; 57 | rm -rf "$packable"; 58 | mkdir -p "$packable"; 59 | 60 | rsync --perms --copy-links -r --exclude=".git" --exclude="node_modules" "$project_root" "$packable" 61 | 62 | base_name="$(basename "$project_root")" 63 | 64 | ( 65 | set -e; 66 | cd "$packable/$base_name"; 67 | pack="$(npm pack)" 68 | full_pack_path="$packable/$base_name/$pack"; 69 | cd "$symlinkable"; 70 | npm init --silent -f 71 | npm install --loglevel=warn "$full_pack_path"; 72 | ) 73 | 74 | 75 | mkdir -p "$package_dir"; 76 | rm -rf "$package_dir"; 77 | 78 | ln -sf "$symlinkable/node_modules/$package_name" "$package_dir" 79 | -------------------------------------------------------------------------------- /dist/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ORESoftware/r2g/2f20124bfcd9613230023c9d10476f7251ae9a23/dist/.gitkeep -------------------------------------------------------------------------------- /dist/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ### Here is where the target goes 4 | -------------------------------------------------------------------------------- /docs/faq.md: -------------------------------------------------------------------------------- 1 | 2 | ## FAQ 3 | 4 | 1. What about symlinked files/folders? 5 | 6 | Symlinks in node_modules will be ignored. But if you have symlinks elsewhere, they will be copied and tested and also published to the tarball, 7 | because the symlinks are copied with `rsync --copy-links`, which actually copies the target files not the links. 8 | -------------------------------------------------------------------------------- /docs/r2g-runtime-steps.md: -------------------------------------------------------------------------------- 1 | 2 | ## A Better Workflow 3 | 4 | One nice thing about testing locally instead of on a remote CI/CD server, is you don't have to leave your IDE to see the results, the dev loop is a bit tighter. 5 | You can run this tool before pushing to a Git remote. r2g will smoke test your library in about as much time as it takes to `npm install --production` your project. 6 | If r2g smoke tests do not pass, it means your package is not publishable!
7 | 8 | Running tests in local Docker containers has some advantages, but you can also run r2g as part of your regular test suite on CI/CD servers, 9 | just make sure you have write access to `"$HOME/.r2g"` 10 | 11 | How you use this tool locally: 12 | 13 | *What you do:* Write some smoke tests that will run after (a) your library is in the published format, and (b) is 14 | installed in another project as dependency. This provides the answer to two important questions: 1. does it actually install 15 | properly when --production is used?, and 2. can it be loaded and run with at least some basic functionality by another package? 16 | 17 | To re-iterate: here are the 3 big benefits of using r2g in combination with your existing CI/CD test process: 18 | 19 | * Uses `npm pack` which will convert the project into published format which help avoid problems with overly-aggressive `.npmignore` files, or an overly-passive `"files"` property in X-package.json 20 | * Tests your dependency in the actual format, which is as a dependency residing in `node_modules` of another project Y. 21 | * Uses the `--production` flag, as in `npm install --production`, when it installs your package to Y. 22 | 23 | 24 | ## r2g steps / order of operations 25 | 26 | This is how r2g works when you run `r2g run`. 27 | 28 | --------------------------------------------------------------------------------------------- 29 | 30 | r2g uses the following steps to do its thing: 31 | 32 | 1. Copies X to `"$HOME/.r2g/temp/copy"` with `rsync --copy-links -r --exclude=".git" --exclude="node_modules"` 33 | 34 | 2. Next, `npm pack` is run against X, which provides us with a `.tgz` file 35 | 36 | 3. We `rm -rf $HOME/.r2g/temp`, then do `mkdir -p $HOME/.r2g/temp`, then generate a fresh dummy NPM project there 37 | 38 | 4. We take the above .tgz file, and install it to T, using: `npm install --production /path/to/tarball.tgz` 39 | 40 | 5. To be completed later. 41 | 42 | -------------------------------------------------------------------------------- /docs/r2g-smoke-test-exported-main-fn-type-a.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Exporting a r2gSmokeTest() function from main 4 | 5 | You might be here, because you saw the following error in your console: 6 | `r2g smoke test failed => one of your exported r2gSmokeTest function calls failed to resolve to true` 7 | 8 | If you got the above error, it's most likely because you haven't added a function that looks like this to your main: 9 | 10 | ``` 11 | exports.r2gSmokeTest = () => { 12 | return true; 13 | }; 14 | ``` 15 | 16 | The purpose of the above function is to run some simple statements to make sure basic functionality is met. 17 | If you need to troubleshoot this further, see: `docs/r2g-type-a-troubleshooting.md`. 18 | 19 | 20 | ### More details 21 | 22 | Your project has an index file, which is designated by 23 | 24 | ```json 25 | { 26 | "name":"X", 27 | "main": "lib/index.js" 28 | } 29 | ``` 30 | 31 | in package.json. If "main" is omitted, require() defaults to index.js (not lib/index.js). 32 | 33 | 34 | Some packages might do this in their main: 35 | 36 | ```js 37 | module.exports = function(){ 38 | // ... 39 | }; 40 | ``` 41 | 42 | or they might do this: 43 | 44 | ```js 45 | module.exports = {}; 46 | ``` 47 | 48 | a quick a dirty solution, would just do this: 49 | 50 | ```js 51 | module.exports.r2gSmokeTest = () => { 52 | return true; 53 | } 54 | ``` 55 | 56 | however you may wish to refactor your main file if you 57 | want a more generic library design. 58 | 59 | 60 | ## The purpose of r2gSmokeTest function: 61 | 62 | 63 | 1. In your r2gSmokeTest implementation, you can require() your project's dependencies. (But *not* devDependencies or optionalDependencies). 64 | 2. You write a simple inline test that imports your library and does something basic. 65 | 66 | 67 | For example: 68 | 69 | ```js 70 | exports.r2gSmokeTest = () => { 71 | const main = require(__filename); // load your main file, this file of course 72 | const v = main.boogle(); 73 | return v.then(oogle => { 74 | return oogle === false; // your must return `true` to pass. 75 | }); 76 | }; 77 | ``` 78 | 79 | 80 | So as you can see, r2g executes your r2gSmokeTest function as: 81 | 82 | ```js 83 | Promise.resolve(r2gSmokeTest()); 84 | ``` 85 | 86 | 87 | Your `r2gSmokeTest` function can look like: 88 | 89 | ```js 90 | export const r2gSmokeTest = async () => { // this function can be async 91 | return true; 92 | }; 93 | ``` 94 | 95 | or just like: 96 | 97 | ```js 98 | export const r2gSmokeTest = () => { 99 | return true; 100 | }; 101 | ``` 102 | -------------------------------------------------------------------------------- /docs/r2g-smoke-test-type-b.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## User defined test files 4 | 5 | You may be here because you saw this error in your console: 6 | 7 | `=> Your r2g test(s) have failed.` 8 | 9 | or 10 | 11 | `the file here failed to exit with code 0: /home/oleg/.r2g/temp/project/user_defined_smoke_test` 12 | 13 | 14 | # How to fix the error / more details 15 | 16 | r2g will copy the file from here: 17 | 18 | `"$HOME/.r2g/temp/project/node_modules/X/.r2g/smoke-test.js"` 19 | 20 | to here: 21 | 22 | `"$HOME/.r2g/temp/project/user-defined-smoke-test"` 23 | 24 | 25 | and then execute it directly - (it must have a hashbang/shebang). 26 | 27 | To fix the error, you may need to modify the file in your package/project X here: 28 | 29 | `.r2g/smoke-test.js` 30 | 31 | or it could be a problem with your package itself - is it missing a dependency? 32 | is your package missing a file? 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /docs/r2g-type-a-troubleshooting.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Exported r2gSmokeTest troubleshooting 4 | 5 | So you have this function exported from X-main 6 | 7 | ``` 8 | exports.r2gSmokeTest = () => { 9 | return true; 10 | }; 11 | ``` 12 | 13 | but when it runs it fails. If you can't require a dependency - 14 | it's likely because you are trying to load a dependency that is not 15 | available after installing your package with the --production flag. 16 | 17 | In your r2gSmokeTest implementation, you should only require dependencies that are in "dependencies", 18 | not "devDependencies" or "optionalDependencies". 19 | -------------------------------------------------------------------------------- /docs/r2g-using-local-deps.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Installing local deps (instead of using them from NPM) 4 | 5 | Many OSS devs will have multiple projects, some interdependent, on their local fs. 6 | 7 | Say we have this on our local fs: 8 | 9 | ``` 10 | your_projects/ 11 | a/package.json 12 | b/package.json 13 | c/package.json 14 | ``` 15 | 16 | say we want to test package `a`. package `a` has this in package.json: 17 | 18 | ```json 19 | { 20 | "dependencies":{ 21 | "b":"latest", 22 | "c":"latest" 23 | } 24 | } 25 | ```` 26 | 27 | so normally, when we run this for package a: 28 | 29 | ```bash 30 | r2g run 31 | ``` 32 | 33 | the above command will install `b` and `c` via NPM cache or registry. boring. however, if we do this: 34 | 35 | ```bash 36 | r2g run --full 37 | ``` 38 | 39 | then `b` and `c` will be **discovered** on your fs, and installed to `a`, for testing! 40 | 41 | And if we do this: 42 | 43 | 44 | ```bash 45 | r2g run --full --pack 46 | ``` 47 | 48 | then `b` and `c` will themselves be tarballed with `npm pack` before being installed. So how do we accomplish this? 49 | 50 | In project/package `a`, run `r2g init`..then in `a/.r2g/config.js`, add: 51 | 52 | ```js 53 | 54 | exports.default = { 55 | 56 | searchRoot: path.resolve(process.env.HOME + '/my_projects'), 57 | 58 | packages: { 59 | 'b': true, 60 | 'c': true 61 | } 62 | } 63 | 64 | 65 | ``` 66 | 67 | 68 | now run this from project `a`: 69 | 70 | ```bash 71 | r2g run --full 72 | ``` 73 | 74 | Project `a's` package.json will be tested in this format: 75 | 76 | ```json 77 | "dependencies": { 78 | "b": "file:///home/oleg/.r2g/temp/deps/b", 79 | "c": "file:///home/oleg/.r2g/temp/deps/c", 80 | }, 81 | ``` 82 | 83 | or if you do this: 84 | 85 | ```bash 86 | r2g run --full --pack 87 | ``` 88 | 89 | then project `a's` package will be installed with this package.json: 90 | 91 | 92 | ```json 93 | "dependencies": { 94 | "b": "file:///home/oleg/.r2g/temp/deps/b-x.y.z.tgz", 95 | "c": "file:///home/oleg/.r2g/temp/deps/c-x.y.z.tgz", 96 | }, 97 | ``` 98 | 99 | 100 | Now you can test multiple projects together as dependencies of each other. 101 | 102 | For local development (not testing), see npm-link-up, which does amazing things with symlinks: 103 | https://github.com/ORESoftware/npm-link-up 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, 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, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "r2g", 3 | "version": "0.1.199", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "r2g", 9 | "version": "0.1.199", 10 | "hasInstallScript": true, 11 | "license": "SEE LICENSE IN LICENSE.md", 12 | "dependencies": { 13 | "@oresoftware/deep.mixin": "0.0.107", 14 | "@oresoftware/shell": "latest", 15 | "async": "^2.6.1", 16 | "chalk": "^2.4.1", 17 | "clean-trace": "latest", 18 | "dashdash": "^1.14.1", 19 | "json-stdio": "latest", 20 | "prepend-transform": "0.0.1016", 21 | "residence": "latest", 22 | "shortid": "^2.2.8" 23 | }, 24 | "bin": { 25 | "r2g": "cli/r2g.sh", 26 | "r2g_get_project_root": "cli/r2g_get_project_root.js", 27 | "r2g_symlink": "cli/r2g_symlink.sh" 28 | }, 29 | "devDependencies": { 30 | "@types/async": "^2.0.49", 31 | "@types/node": "^9.6.2", 32 | "@types/shortid": "0.0.29" 33 | } 34 | }, 35 | "node_modules/@oresoftware/deep.mixin": { 36 | "version": "0.0.107", 37 | "resolved": "https://registry.npmjs.org/@oresoftware/deep.mixin/-/deep.mixin-0.0.107.tgz", 38 | "integrity": "sha512-4reuZcCN6r4GZCl6VLuSKrZahtg7HA/q2wy4YoX+T/G5gHzuEpMD3/d5daoV/Vj6KP1F76iWOsn2H8EYz7g1Cg==", 39 | "hasInstallScript": true 40 | }, 41 | "node_modules/@oresoftware/json-stream-parser": { 42 | "version": "0.0.111", 43 | "resolved": "https://registry.npmjs.org/@oresoftware/json-stream-parser/-/json-stream-parser-0.0.111.tgz", 44 | "integrity": "sha512-C6qIGvw81wk22QC9bKc7r+aTNLkgTeQFPpLVB+t3owcBcKg8v5m717XlVqIUAvw/1UcsjNg8BG9DWaQqrn34Fg==", 45 | "bin": { 46 | "json_parser": "cli/json-parser.js" 47 | } 48 | }, 49 | "node_modules/@oresoftware/safe-stringify": { 50 | "version": "0.0.1006", 51 | "resolved": "https://registry.npmjs.org/@oresoftware/safe-stringify/-/safe-stringify-0.0.1006.tgz", 52 | "integrity": "sha512-PSbElr5oxmQS+byg7qdg7ZPEbfbHSzVSvKnweMTXfv6P2QXXJWXbPjWCQL8yZD0v6DFXnQW+oLTG2BVGxJXDwA==", 53 | "hasInstallScript": true 54 | }, 55 | "node_modules/@oresoftware/shell": { 56 | "version": "0.0.110", 57 | "resolved": "https://registry.npmjs.org/@oresoftware/shell/-/shell-0.0.110.tgz", 58 | "integrity": "sha512-rLt2xV0KS4hMkGsZbxALJ1wJG7tEuD9KtdL5VPp497M5fIwipgUXhLgzuw7Q0L985AeUOM/KjvE9rfi1xnXZBw==", 59 | "bin": { 60 | "oresoftware_shell_generate": "assets/cli.sh" 61 | } 62 | }, 63 | "node_modules/@types/async": { 64 | "version": "2.0.49", 65 | "resolved": "https://registry.npmjs.org/@types/async/-/async-2.0.49.tgz", 66 | "integrity": "sha512-Benr3i5odUkvpFkOpzGqrltGdbSs+EVCkEBGXbuR7uT0VzhXKIkhem6PDzHdx5EonA+rfbB3QvP6aDOw5+zp5Q==", 67 | "dev": true 68 | }, 69 | "node_modules/@types/node": { 70 | "version": "9.6.23", 71 | "resolved": "https://registry.npmjs.org/@types/node/-/node-9.6.23.tgz", 72 | "integrity": "sha512-d2SJJpwkiPudEQ3+9ysANN2Nvz4QJKUPoe/WL5zyQzI0RaEeZWH5K5xjvUIGszTItHQpFPdH+u51f6G/LkS8Cg==", 73 | "dev": true 74 | }, 75 | "node_modules/@types/shortid": { 76 | "version": "0.0.29", 77 | "resolved": "https://registry.npmjs.org/@types/shortid/-/shortid-0.0.29.tgz", 78 | "integrity": "sha1-gJPuBBam4r8qpjOBCRFLP7/6Dps=", 79 | "dev": true 80 | }, 81 | "node_modules/ansi-styles": { 82 | "version": "3.2.1", 83 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 84 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 85 | "dependencies": { 86 | "color-convert": "^1.9.0" 87 | }, 88 | "engines": { 89 | "node": ">=4" 90 | } 91 | }, 92 | "node_modules/assert-plus": { 93 | "version": "1.0.0", 94 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", 95 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", 96 | "engines": { 97 | "node": ">=0.8" 98 | } 99 | }, 100 | "node_modules/async": { 101 | "version": "2.6.1", 102 | "resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz", 103 | "integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==", 104 | "dependencies": { 105 | "lodash": "^4.17.10" 106 | } 107 | }, 108 | "node_modules/chalk": { 109 | "version": "2.4.1", 110 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", 111 | "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", 112 | "dependencies": { 113 | "ansi-styles": "^3.2.1", 114 | "escape-string-regexp": "^1.0.5", 115 | "supports-color": "^5.3.0" 116 | }, 117 | "engines": { 118 | "node": ">=4" 119 | } 120 | }, 121 | "node_modules/clean-trace": { 122 | "version": "0.0.104", 123 | "resolved": "https://registry.npmjs.org/clean-trace/-/clean-trace-0.0.104.tgz", 124 | "integrity": "sha512-2CDboR5vTLvZPwXiGfjbh4P/GWewd5x/CBrJprPFw6GN5YGZ9yp4sc5i7F7bj3yHLcMV0b0RRFDBrRdVk7wUpA==" 125 | }, 126 | "node_modules/color-convert": { 127 | "version": "1.9.2", 128 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.2.tgz", 129 | "integrity": "sha512-3NUJZdhMhcdPn8vJ9v2UQJoH0qqoGUkYTgFEPZaPjEtwmmKUfNV46zZmgB2M5M4DCEQHMaCfWHCxiBflLm04Tg==", 130 | "dependencies": { 131 | "color-name": "1.1.1" 132 | } 133 | }, 134 | "node_modules/color-name": { 135 | "version": "1.1.1", 136 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.1.tgz", 137 | "integrity": "sha1-SxQVMEz1ACjqgWQ2Q72C6gWANok=" 138 | }, 139 | "node_modules/dashdash": { 140 | "version": "1.14.1", 141 | "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", 142 | "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", 143 | "dependencies": { 144 | "assert-plus": "^1.0.0" 145 | }, 146 | "engines": { 147 | "node": ">=0.10" 148 | } 149 | }, 150 | "node_modules/escape-string-regexp": { 151 | "version": "1.0.5", 152 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 153 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", 154 | "engines": { 155 | "node": ">=0.8.0" 156 | } 157 | }, 158 | "node_modules/has-flag": { 159 | "version": "3.0.0", 160 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 161 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", 162 | "engines": { 163 | "node": ">=4" 164 | } 165 | }, 166 | "node_modules/json-stdio": { 167 | "version": "0.1.1011", 168 | "resolved": "https://registry.npmjs.org/json-stdio/-/json-stdio-0.1.1011.tgz", 169 | "integrity": "sha512-Cb4Uca/3/170gnot82DVJDzIBagHeBnRoNnHuzKsWCdW1kTVT4F+c33+yi5uLxWaUJZl3fiuCThRnwqaeLHfFA==", 170 | "dependencies": { 171 | "@oresoftware/json-stream-parser": "latest", 172 | "@oresoftware/safe-stringify": "latest" 173 | }, 174 | "bin": { 175 | "json_stdio": "cli/stdio.js", 176 | "json_stdio_parse": "cli/parse.js", 177 | "stdio": "cli/stdio.js" 178 | } 179 | }, 180 | "node_modules/lodash": { 181 | "version": "4.17.15", 182 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", 183 | "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" 184 | }, 185 | "node_modules/nanoid": { 186 | "version": "1.1.0", 187 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-1.1.0.tgz", 188 | "integrity": "sha512-iOCqgXieGrk8/wDt1n9rZS2KB1dYVssemY0NTWjfzVr+1t1gAmdTp1u2+YHppKro3Bk5S+Gs+xmYCfpuXauYXQ==" 189 | }, 190 | "node_modules/prepend-transform": { 191 | "version": "0.0.1016", 192 | "resolved": "https://registry.npmjs.org/prepend-transform/-/prepend-transform-0.0.1016.tgz", 193 | "integrity": "sha512-+I6PBx9i/1o/B3Jtz9CmhJ0vmJGaNywO7H7wz03Abg+9phNb8Q3yuElP+xASrUV1P6X4xFzM+huuQisxMSr42g==", 194 | "dependencies": { 195 | "chalk": "^2.4.1" 196 | }, 197 | "bin": { 198 | "prepend_with": "dist/cli.js" 199 | } 200 | }, 201 | "node_modules/residence": { 202 | "version": "0.0.216", 203 | "resolved": "https://registry.npmjs.org/residence/-/residence-0.0.216.tgz", 204 | "integrity": "sha512-/Wu9RCZeO9KbcCW4RsyGIrnPez5w+GLbtQ8rKoFdRbnF/ZEN3LBPDwDtsJHz1x6cLfaoHRLoPZCkiItA0L/hag==", 205 | "bin": { 206 | "residence_find_proj_root": "dist/cli.js" 207 | } 208 | }, 209 | "node_modules/shortid": { 210 | "version": "2.2.12", 211 | "resolved": "https://registry.npmjs.org/shortid/-/shortid-2.2.12.tgz", 212 | "integrity": "sha512-sw0knB/ioTu/jVYgJz1IP1b5uhPZtZYwQ9ir/EqXZHI4+Jh8rzzGLM3LKptGHBKoDsgTBDfr4yCRNUX7hEIksQ==", 213 | "dependencies": { 214 | "nanoid": "^1.0.7" 215 | } 216 | }, 217 | "node_modules/supports-color": { 218 | "version": "5.4.0", 219 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", 220 | "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", 221 | "dependencies": { 222 | "has-flag": "^3.0.0" 223 | }, 224 | "engines": { 225 | "node": ">=4" 226 | } 227 | } 228 | }, 229 | "dependencies": { 230 | "@oresoftware/deep.mixin": { 231 | "version": "0.0.107", 232 | "resolved": "https://registry.npmjs.org/@oresoftware/deep.mixin/-/deep.mixin-0.0.107.tgz", 233 | "integrity": "sha512-4reuZcCN6r4GZCl6VLuSKrZahtg7HA/q2wy4YoX+T/G5gHzuEpMD3/d5daoV/Vj6KP1F76iWOsn2H8EYz7g1Cg==" 234 | }, 235 | "@oresoftware/json-stream-parser": { 236 | "version": "0.0.111", 237 | "resolved": "https://registry.npmjs.org/@oresoftware/json-stream-parser/-/json-stream-parser-0.0.111.tgz", 238 | "integrity": "sha512-C6qIGvw81wk22QC9bKc7r+aTNLkgTeQFPpLVB+t3owcBcKg8v5m717XlVqIUAvw/1UcsjNg8BG9DWaQqrn34Fg==" 239 | }, 240 | "@oresoftware/safe-stringify": { 241 | "version": "0.0.1006", 242 | "resolved": "https://registry.npmjs.org/@oresoftware/safe-stringify/-/safe-stringify-0.0.1006.tgz", 243 | "integrity": "sha512-PSbElr5oxmQS+byg7qdg7ZPEbfbHSzVSvKnweMTXfv6P2QXXJWXbPjWCQL8yZD0v6DFXnQW+oLTG2BVGxJXDwA==" 244 | }, 245 | "@oresoftware/shell": { 246 | "version": "0.0.110", 247 | "resolved": "https://registry.npmjs.org/@oresoftware/shell/-/shell-0.0.110.tgz", 248 | "integrity": "sha512-rLt2xV0KS4hMkGsZbxALJ1wJG7tEuD9KtdL5VPp497M5fIwipgUXhLgzuw7Q0L985AeUOM/KjvE9rfi1xnXZBw==" 249 | }, 250 | "@types/async": { 251 | "version": "2.0.49", 252 | "resolved": "https://registry.npmjs.org/@types/async/-/async-2.0.49.tgz", 253 | "integrity": "sha512-Benr3i5odUkvpFkOpzGqrltGdbSs+EVCkEBGXbuR7uT0VzhXKIkhem6PDzHdx5EonA+rfbB3QvP6aDOw5+zp5Q==", 254 | "dev": true 255 | }, 256 | "@types/node": { 257 | "version": "9.6.23", 258 | "resolved": "https://registry.npmjs.org/@types/node/-/node-9.6.23.tgz", 259 | "integrity": "sha512-d2SJJpwkiPudEQ3+9ysANN2Nvz4QJKUPoe/WL5zyQzI0RaEeZWH5K5xjvUIGszTItHQpFPdH+u51f6G/LkS8Cg==", 260 | "dev": true 261 | }, 262 | "@types/shortid": { 263 | "version": "0.0.29", 264 | "resolved": "https://registry.npmjs.org/@types/shortid/-/shortid-0.0.29.tgz", 265 | "integrity": "sha1-gJPuBBam4r8qpjOBCRFLP7/6Dps=", 266 | "dev": true 267 | }, 268 | "ansi-styles": { 269 | "version": "3.2.1", 270 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 271 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 272 | "requires": { 273 | "color-convert": "^1.9.0" 274 | } 275 | }, 276 | "assert-plus": { 277 | "version": "1.0.0", 278 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", 279 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" 280 | }, 281 | "async": { 282 | "version": "2.6.1", 283 | "resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz", 284 | "integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==", 285 | "requires": { 286 | "lodash": "^4.17.10" 287 | } 288 | }, 289 | "chalk": { 290 | "version": "2.4.1", 291 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", 292 | "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", 293 | "requires": { 294 | "ansi-styles": "^3.2.1", 295 | "escape-string-regexp": "^1.0.5", 296 | "supports-color": "^5.3.0" 297 | } 298 | }, 299 | "clean-trace": { 300 | "version": "0.0.104", 301 | "resolved": "https://registry.npmjs.org/clean-trace/-/clean-trace-0.0.104.tgz", 302 | "integrity": "sha512-2CDboR5vTLvZPwXiGfjbh4P/GWewd5x/CBrJprPFw6GN5YGZ9yp4sc5i7F7bj3yHLcMV0b0RRFDBrRdVk7wUpA==" 303 | }, 304 | "color-convert": { 305 | "version": "1.9.2", 306 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.2.tgz", 307 | "integrity": "sha512-3NUJZdhMhcdPn8vJ9v2UQJoH0qqoGUkYTgFEPZaPjEtwmmKUfNV46zZmgB2M5M4DCEQHMaCfWHCxiBflLm04Tg==", 308 | "requires": { 309 | "color-name": "1.1.1" 310 | } 311 | }, 312 | "color-name": { 313 | "version": "1.1.1", 314 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.1.tgz", 315 | "integrity": "sha1-SxQVMEz1ACjqgWQ2Q72C6gWANok=" 316 | }, 317 | "dashdash": { 318 | "version": "1.14.1", 319 | "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", 320 | "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", 321 | "requires": { 322 | "assert-plus": "^1.0.0" 323 | } 324 | }, 325 | "escape-string-regexp": { 326 | "version": "1.0.5", 327 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 328 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" 329 | }, 330 | "has-flag": { 331 | "version": "3.0.0", 332 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 333 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" 334 | }, 335 | "json-stdio": { 336 | "version": "0.1.1011", 337 | "resolved": "https://registry.npmjs.org/json-stdio/-/json-stdio-0.1.1011.tgz", 338 | "integrity": "sha512-Cb4Uca/3/170gnot82DVJDzIBagHeBnRoNnHuzKsWCdW1kTVT4F+c33+yi5uLxWaUJZl3fiuCThRnwqaeLHfFA==", 339 | "requires": { 340 | "@oresoftware/json-stream-parser": "latest", 341 | "@oresoftware/safe-stringify": "latest" 342 | } 343 | }, 344 | "lodash": { 345 | "version": "4.17.15", 346 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", 347 | "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" 348 | }, 349 | "nanoid": { 350 | "version": "1.1.0", 351 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-1.1.0.tgz", 352 | "integrity": "sha512-iOCqgXieGrk8/wDt1n9rZS2KB1dYVssemY0NTWjfzVr+1t1gAmdTp1u2+YHppKro3Bk5S+Gs+xmYCfpuXauYXQ==" 353 | }, 354 | "prepend-transform": { 355 | "version": "0.0.1016", 356 | "resolved": "https://registry.npmjs.org/prepend-transform/-/prepend-transform-0.0.1016.tgz", 357 | "integrity": "sha512-+I6PBx9i/1o/B3Jtz9CmhJ0vmJGaNywO7H7wz03Abg+9phNb8Q3yuElP+xASrUV1P6X4xFzM+huuQisxMSr42g==", 358 | "requires": { 359 | "chalk": "^2.4.1" 360 | } 361 | }, 362 | "residence": { 363 | "version": "0.0.216", 364 | "resolved": "https://registry.npmjs.org/residence/-/residence-0.0.216.tgz", 365 | "integrity": "sha512-/Wu9RCZeO9KbcCW4RsyGIrnPez5w+GLbtQ8rKoFdRbnF/ZEN3LBPDwDtsJHz1x6cLfaoHRLoPZCkiItA0L/hag==" 366 | }, 367 | "shortid": { 368 | "version": "2.2.12", 369 | "resolved": "https://registry.npmjs.org/shortid/-/shortid-2.2.12.tgz", 370 | "integrity": "sha512-sw0knB/ioTu/jVYgJz1IP1b5uhPZtZYwQ9ir/EqXZHI4+Jh8rzzGLM3LKptGHBKoDsgTBDfr4yCRNUX7hEIksQ==", 371 | "requires": { 372 | "nanoid": "^1.0.7" 373 | } 374 | }, 375 | "supports-color": { 376 | "version": "5.4.0", 377 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", 378 | "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", 379 | "requires": { 380 | "has-flag": "^3.0.0" 381 | } 382 | } 383 | } 384 | } 385 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "r2g", 3 | "version": "0.2.011", 4 | "description": "Semver-oriented TypeScript library skeleton.", 5 | "main": "dist/index.js", 6 | "bin": { 7 | "r2g": "cli/r2g.sh", 8 | "r2g_symlink": "cli/r2g_symlink.sh", 9 | "r2g_get_project_root": "cli/r2g_get_project_root.js" 10 | }, 11 | "types": "dist/index.d.ts", 12 | "typings": "dist/index.d.ts", 13 | "scripts": { 14 | "postinstall": "./assets/postinstall.sh", 15 | "test": "suman test" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "git+https://github.com/ORESoftware/r2g.git" 20 | }, 21 | "keywords": [ 22 | "typescript", 23 | "library", 24 | "skeleton", 25 | "scaffold" 26 | ], 27 | "author": "TODO Yo.Mama", 28 | "license": "SEE LICENSE IN LICENSE.md", 29 | "bugs": { 30 | "url": "https://github.com/ORESoftware/r2g/issues" 31 | }, 32 | "homepage": "https://github.com/ORESoftware/r2g#readme", 33 | "dependencies": { 34 | "@oresoftware/deep.mixin": "0.0.107", 35 | "@oresoftware/shell": "latest", 36 | "async": "^2.6.1", 37 | "chalk": "^2.4.1", 38 | "clean-trace": "latest", 39 | "dashdash": "^1.14.1", 40 | "json-stdio": "latest", 41 | "prepend-transform": "0.0.1016", 42 | "residence": "latest", 43 | "shortid": "^2.2.8" 44 | }, 45 | "devDependencies": { 46 | "@types/async": "^2.0.49", 47 | "@types/node": "^9.6.2", 48 | "@types/shortid": "0.0.29" 49 | }, 50 | "r2g": { 51 | "test": "echo 'this is great'" 52 | }, 53 | "npp": { 54 | "publishable": true 55 | }, 56 | "b3val": 3 57 | } 58 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | [](https://oresoftware.slack.com/messages/CCAHLN77B) 5 | 6 | [![Gitter](https://badges.gitter.im/oresoftware/r2g.svg)](https://gitter.im/oresoftware/r2g?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) 7 | [![Version](https://img.shields.io/npm/v/r2g.svg?colorB=green)](https://www.npmjs.com/package/r2g) 8 | 9 |
10 | 11 | # @oresoftware/r2g 12 | 13 | > 14 | > Properly test your NPM packages before publishing.
15 | > This CLI tool allows you to easily test your package in the published format, without having to publish to an NPM registry. 16 | 17 |
18 | 19 | #### Caveats + Disclaimer 20 | 21 | > 22 | > This will not work with MS Windows. Only MacOS and *nix. 23 | > If you are interested in getting to work on Windows, pls file a ticket. 24 | > 25 | 26 |
27 | 28 | ## Video demo 29 | 30 | Watch this video to learn how to use r2g: TBD (video demo coming in the future)
31 | 32 | The video will reference this example repo:
33 | https://github.com/ORESoftware/r2g.example 34 | 35 |
36 | 37 | ### Installation 38 | 39 | ```console 40 | $ npm i -g r2g 41 | ``` 42 | 43 | You can add the following to your ~/.bashrc and/or ~/.bash_profile files: 44 | 45 | ```shell 46 | . "$HOME/.oresoftware/shell.sh" 47 | ``` 48 | 49 | => Note you will also get bash completion for r2g, if you source the above shell script. 50 | 51 |
52 | _____________________________________________________________________________________________ 53 | 54 | ### FAQ 55 | 56 | see docs/faq.md 57 | 58 |
59 | 60 | ### About The Tool 61 | 62 | r2g tests your package after using `npm pack` and `npm install --production`. You can use your current test suite for testing, and also write some new smoke tests 63 | that are specific to r2g. r2g current has 3 phases, each phase is optional: 64 | 65 |
66 | 67 | * phase-Z: packs your project and installs the packed project as a dependency of itself then runs `npm test` on your project. You can override `npm test` with `r2g.test` in package.json. 68 | * phase-S: installs your project as a dependency of a dummy package in `$HOME/.r2g/temp/project`, then it executes the `r2gSmokeTest` function exported from your main. 69 | * phase-T: Copies the test scripts from `.r2g/tests` in your project, to `$HOME/.r2g/temp/project/tests`, and runs them. 70 | 71 |
72 | 73 | By default all phases are run, but you can skip phases with the `--skip=z,s,t` option. 74 | 75 |
76 | 77 | r2g is one of several tools that makes managing multiple locally developed NPM packages easier. 78 | 79 | The current pieces are: 80 | 81 | * [npm-link-up (NLU)](https://github.com/ORESoftware/npm-link-up) => links multiple NPM packages together for local development 82 | * [r2g](https://github.com/ORESoftware/r2g) => tests local packages properly before publishing to NPM 83 | * [npp](https://github.com/ORESoftware/npp) => publish multiple packages and sync their semver versions 84 | 85 |
86 | 87 | ## Quick reference 88 | 89 |
90 | 91 | > 92 | >```console 93 | >$ r2g run ### note: `r2g test` is an alias of `r2g run` 94 | >``` 95 | > 96 | > * Runs the tool, and runs all phases. 97 | > 98 | 99 |
100 | 101 | > 102 | >```console 103 | >$ r2g run --skip=z,s 104 | >``` 105 | > 106 | > * This will skip phases Z and S 107 | > 108 | 109 |
110 | 111 | > 112 | >```console 113 | >$ r2g run -z -s 114 | >``` 115 | > 116 | > * This will also skip phases Z and S 117 | > 118 | 119 |
120 | 121 | > 122 | >```console 123 | >$ r2g run --full 124 | >``` 125 | > 126 | > * Installs other locally developed dependencies to your main package, defined in `.r2g/config.js`, and tests everything together 127 | > 128 | 129 |
130 | 131 | > 132 | >```console 133 | >$ r2g run --full --pack 134 | >``` 135 | > 136 | > * Installs other locally developed dependencies to your main package, *npm packs them too*, and tests everything together 137 | > 138 | 139 |
140 | 141 | > 142 | >```console 143 | >$ r2g inspect 144 | >``` 145 | > 146 | > * Copies your project to a temp folder and logs info about a temp tarball that gets created 147 | > * AKA, you can see which contents/folders/files will get included in the tarball 148 | > * Also warns you about any especially large files that you may have accidentally included. 149 | > 150 | 151 |
152 | 153 | > 154 | >```console 155 | >$ r2g publish 156 | >``` 157 | > 158 | > * Publish your package and ignore the .r2g folder for an even leaner tarball 159 | > * Copies your project to a temp folder and the .r2g folder is excluded/ignored 160 | > * Also copies symlinks so you can include symlinked files/folders easily when publishing 161 | > 162 | 163 |
164 | 165 | 166 | ### Important Info 167 | 168 | * This tool is only proven on MacOS/*nix, not tested on Windows. If you do Windows and want something to do - fork this and make it work for Windows - it won't be hard. 169 | * You can use r2g with zero-config, depending on what you want to do. 170 | * Testing does not happen in your local codebase - before anything, your codebase is copied to `"$HOME/.r2g/temp/copy"`, and all writes happen within `"$HOME/.r2g/temp"`. 171 | * If you use the `--full` option, the local deps of your package will copied to: `"$HOME/.r2g/temp/deps"` 172 | * You can and should put your regular tests in `.npmignore`, but your .r2g folder should not be in `.npmignore` 173 | 174 | To make this README as clear and concise as possible: 175 | 176 | * Your NPM package is referred to as `X`. X is the name of the package you publish to NPM, which is the "name" field of your package.json. 177 | * The package.json file for X is simply referred to as `X-package.json`. 178 | * Your index.js file (or whatever the "main" property points to in X-package.json), is referred to as `X-main` 179 | 180 |
181 | 182 | ______________________________________________________________________________________________ 183 | 184 | ## Purpose 185 | 186 | This tool complements your standard CI/CD testing for NPM libraries. You might already be using Travis, CircleCI, etc, to test your library 187 | when you do a `git push`. Keep doing that. However, what you are already doing is likely to be insufficient because: 188 | 189 | 1. You install using `npm install` instead of `npm install --production`, because you need your devDependencies for your tests. (whoops!). 190 | 2. You are testing your package directly, instead of testing it as a dependency of another project. In reality, someone will be using your package via `node_modules/X`, and for example, your postinstall routine may behave differently here. 191 | 3. You are not using `npm pack` to package your project before testing it. Your [`.npmignore`](https://docs.npmjs.com/misc/developers#keeping-files-out-of-your-package) file could mean you will be missing files, when someone goes to use your package in the wild. Likewise, if 192 | the ["files"](https://docs.npmjs.com/files/package.json#files) property in X-package.json is too passive, you might be missing files as well. Using `npm pack` before testing solves that. 193 | 4. It is possible to check out a branch that has passed on a remote CI/CD platform but locally does not have the built/transpiled target files. This means the files will not make it into the tarball that gets published to NPM. 194 | Testing with r2g locally before publishing a local branch means you avoid this problem, because if the target files are not built, r2g tests will fail. 195 | 196 | The above things are why you need to take some extra pre-cautions before publishing NPM packages. I think everyone has had an `.npmignore` file that accidentally ignored files we need in production. 197 | And we have all had dependencies listed in devDependencies instead of dependencies, which caused problems when people try to use the library. Those are the motivations for using this tool, 198 | to *prove* that X works in its final format. 199 | 200 | * There is a secret feature which is extremely badass - install other locally developed packages which are dependencies of X, as part of r2g testing. 201 | See "Linking with existing local dependencies" below. 202 | 203 |
204 | 205 | ### How it works in detail 206 | To learn more about how r2g works in detail, see: `docs/r2g-runtime-steps.md` 207 | 208 |
209 | 210 | # Basic usage / Getting started 211 | 212 | You can use r2g with zero-config - you just need to implement a single function. 213 | 214 |
215 | 216 | To start, execute this in a shell at the root of your project: 217 | 218 | ```bash 219 | $ r2g run 220 | ``` 221 | 222 | This command will then fail. That's expected. 223 | 224 |
225 | 226 | To get your test to pass, add this to X-main (your package's index file, whatever "main" in package.json points to): 227 | 228 | ```js 229 | exports.r2gSmokeTest = async () => { 230 | return Promise.resolve(true); 231 | }; 232 | ``` 233 | 234 | the above function is called with `Promise.resolve(X.r2gSmokeTest())`, and in order to pass it must resolve to `true` (not just truthy). 235 | 236 |
237 | 238 | To read more about the exported r2gSmokeTest function, see: `docs/r2g-smoke-test-exported-main-function.md` 239 | 240 |
241 | 242 | Note: the exported function `r2gSmokeTest` allows you to smoke test your package. When this function is run you may use the production dependencies declared in your project. 243 | However, for other r2g tests, you may *not* directly use the dependencies declared in X-package.json, *you may only require X itself*. 244 | 245 |
246 | 247 | ## Adding more tests beyond the `r2gSmokeTest` function 248 | 249 | To do more sophisticated tests, we add some configuration in a folder called .r2g in the root of your project. 250 | 251 |
252 | 253 | To do this, run: 254 | 255 | ```bash 256 | r2g init 257 | ``` 258 | 259 | this will add a folder to your project called `.r2g`. Your `.r2g` folder should never be in `.npmignore`. (Running `r2g init` is safe, it will not overwrite any existing files). 260 | Your new `.r2g` folder contains a file called: `.r2g/smoke-test.js`. 261 | 262 |
263 | 264 | Now when `r2g run` executes, it will run `.r2g/smoke-test.js`, *but* it will run this test in the context of the main project, meaning it will copy: 265 | 266 |
267 | 268 | `$HOME/.r2g/temp/project/node_modules/X/.r2g/smoke-test.js` --> `$HOME/.r2g/temp/project/smoke-test.js` 269 | 270 |
271 | 272 | The above is very important to understand, because it means that this smoke test *should not include any dependencies* from X-package.json. 273 | In fact, the *only* dependency `.r2g/smoke-test.js` should require, besides core modules, is X itself. 274 | 275 |
276 | 277 | ## Linking with existing local dependencies: 278 | 279 | Run `r2g init` and in your `.r2g/config.js` file, add local package names to the packages property: 280 | 281 | ```js 282 | exports.default = { 283 | 284 | // ... 285 | 286 | packages: { 287 | // local packages will be installed to your project --> test your local dependencies instead of installing from NPM 288 | 'your-local-package-b': true, 289 | 'your-local-package-c': true 290 | } 291 | } 292 | ``` 293 | 294 | If you execute `r2g run --full` these packages (b,c) will be *discovered* on your local fs, and copied to `"$HOME/.r2g/temp/deps/*"`, and then X's package.json will look like this: 295 | 296 | ```json 297 | { 298 | "dependencies": { 299 | "your-local-package-b": "file:///home/user/.r2g/temp/deps/your-local-package-b", 300 | "your-local-package-c": "file:///home/user/.r2g/temp/deps/your-local-package-c", 301 | } 302 | } 303 | ``` 304 | 305 | If you execute `r2g run --full --pack`, then this awesomeness happens: 306 | 307 | 308 | ```json 309 | { 310 | "dependencies": { 311 | "your-local-package-b": "file:///home/user/.r2g/temp/deps/your-local-package-b.tgz", 312 | "your-local-package-c": "file:///home/user/.r2g/temp/deps/your-local-package-c.tgz", 313 | } 314 | } 315 | ``` 316 | 317 | So using `r2g run --full` => we install local deps instead of the deps from NPM. 318 | 319 |
320 | 321 | And using `r2g run --full --pack` => we pack the local deps before installing them. 322 | 323 |
324 | 325 | Awesome. 326 | 327 | To read more about using local deps for testing instead of installing your local deps from NPM, see:
=> `docs/r2g-using-local-deps.md` 328 | 329 |
330 | 331 | ## Usage in a Docker image/container 332 | 333 | Use a Docker container for a fresh/independent/isolated testing env. For packages that do more complex/system things, it will be useful to use a locally running Docker container. 334 | To use r2g in a Docker container, see: https://github.com/ORESoftware/r2g.docker 335 | 336 |
337 | 338 | Alternatively, you can just run r2g as part of your normal CI/CD library testing on remote servers. 339 | First, make sure you have Docker installed on your local machine. See standard installation instructions for MacOS/*nix. 340 | 341 |
342 | 343 | Run this in the root of your project: 344 | 345 | ```bash 346 | $ r2g init --docker # you only need to run this once per project, but won't hurt if you do it more than once 347 | ``` 348 | 349 | Then run this: 350 | 351 | ```bash 352 | $ r2g docker 353 | ``` 354 | 355 | The above command actually uses this command line tool:
356 | https://github.com/ORESoftware/r2g.docker 357 | 358 | 359 |
360 | 361 | ### For the future: 362 | 363 | * Instead a dummy NPM project which will depend on X, we can allow users to use their own test projects, and pull those in with `git clone` or what not. 364 | 365 | 366 | ### TBD 367 | Just adding some spaces here 368 | 369 | -------------------------------------------------------------------------------- /scripts/idea/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ORESoftware/r2g/2f20124bfcd9613230023c9d10476f7251ae9a23/scripts/idea/.gitkeep -------------------------------------------------------------------------------- /scripts/npm/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ORESoftware/r2g/2f20124bfcd9613230023c9d10476f7251ae9a23/scripts/npm/.gitkeep -------------------------------------------------------------------------------- /scripts/npm/install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | npm install --no-optional 4 | -------------------------------------------------------------------------------- /scripts/npm/publish.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | 4 | set -e; 5 | 6 | npm install --no-optional 7 | tsc 8 | npm version patch 9 | ./scripts/git/push.sh 10 | npm publish 11 | -------------------------------------------------------------------------------- /scripts/tarzan/add.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e; 4 | v=`npm pack`; 5 | 6 | tarzan use "oresoftware/tarballs" 7 | tarzan add "$v" "tgz/oresoftware/r2g.tgz" 8 | -------------------------------------------------------------------------------- /scripts/travis/after_script.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "this is the travis 'after script'." 4 | -------------------------------------------------------------------------------- /scripts/travis/before_install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | 4 | set -e; 5 | 6 | echo "this is the travis 'before install'." 7 | 8 | npm install -g typescript@2.8.3; 9 | 10 | -------------------------------------------------------------------------------- /scripts/travis/install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e; 4 | 5 | echo "this is the travis 'install'."; 6 | 7 | npm install --loglevel="warn" || { 8 | echo "could not run npm install successfully..."; 9 | } 10 | 11 | tsc || echo "tsc command compiled with errors."; 12 | -------------------------------------------------------------------------------- /scripts/travis/script.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | 4 | echo "this is the travis 'script' script." 5 | 6 | #. "$HOME/.oresoftware/bash/r2g.sh" 7 | #r2g run 8 | 9 | 10 | npm link -f --loglevel="warn" 11 | r2g run 12 | -------------------------------------------------------------------------------- /scripts/vscode/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ORESoftware/r2g/2f20124bfcd9613230023c9d10476f7251ae9a23/scripts/vscode/.gitkeep -------------------------------------------------------------------------------- /src/commands/basic/cli-options.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | export default [ 4 | 5 | { 6 | names: ['version', 'vn'], 7 | type: 'bool', 8 | help: 'Print tool version and exit.' 9 | }, 10 | { 11 | names: ['help', 'h'], 12 | type: 'bool', 13 | help: 'Print this help and exit.' 14 | }, 15 | { 16 | names: ['bash-completion', 'completion'], 17 | type: 'bool', 18 | help: 'Generate bash completion code.' 19 | }, 20 | { 21 | names: ['json'], 22 | type: 'bool', 23 | help: 'Write stdout info as JSON (use json-stdio to parse it).', 24 | default: false, 25 | hidden: true, 26 | env: 'nlu_setting_json' 27 | }, 28 | { 29 | names: ['verbosity', 'v'], 30 | type: 'integer', 31 | help: 'Verbosity level, 1-3 inclusive.' 32 | }, 33 | { 34 | names: ['allow-unknown'], 35 | type: 'bool', 36 | help: 'Allow unknown arguments to the command line.', 37 | env: 'r2g_allow_unknown' 38 | } 39 | 40 | ] 41 | 42 | 43 | export interface R2GBasicOpts { 44 | bash_completion: boolean, 45 | version: boolean, 46 | debug: boolean, 47 | allow_unknown: boolean, 48 | force: boolean, 49 | help: boolean, 50 | verbosity: number 51 | } 52 | -------------------------------------------------------------------------------- /src/commands/basic/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | 5 | import chalk from 'chalk'; 6 | import log from '../../logger'; 7 | 8 | process.once('exit', code => { 9 | log.info('r2g is exiting with code:', code); 10 | }); 11 | 12 | import {opts, projectRoot, cwd} from './parse-cli-opts'; 13 | 14 | log.info('Your project root:', projectRoot); 15 | log.info(chalk.bold.cyan('To see help for r2g run, using "r2g run --help".')); 16 | log.info(chalk.bold.cyan('To see help for r2g init, using "r2g init --help".')); 17 | log.info(chalk.bold('Otherwise, run "r2g --help"')); 18 | process.exit(1); 19 | -------------------------------------------------------------------------------- /src/commands/basic/parse-cli-opts.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import basicOpts from './cli-options'; 4 | import initOpts from '../init/cli-options'; 5 | import runOpts from '../run/cli-options'; 6 | 7 | const dashdash = require('dashdash'); 8 | import residence = require('residence'); 9 | import chalk from "chalk"; 10 | 11 | const allowUnknown = process.argv.indexOf('--allow-unknown') > 0; 12 | const parser = dashdash.createParser({options: basicOpts, allowUnknown}); 13 | const pkgJSON = require('../../../package.json'); 14 | import log from '../../logger'; 15 | import * as stdio from 'json-stdio'; 16 | 17 | let opts: any; 18 | 19 | try { 20 | opts = parser.parse(process.argv); 21 | } 22 | catch (e) { 23 | console.error(chalk.magenta('r2g: cli parsing error:'), chalk.magentaBright.bold(e.message)); 24 | process.exit(1); 25 | } 26 | 27 | if (opts.help) { 28 | log.warning(chalk.bold.cyan('To get help for r2g run, use:', chalk.bold.blueBright('r2g run --help'))); 29 | log.warning(chalk.bold.cyan('To get help for r2g init, use:', chalk.bold.blueBright('r2g init --help'))); 30 | let help = parser.help({includeEnv: true}).trimRight(); 31 | console.log(); 32 | console.log('usage: r2g [OPTIONS]\n' + help); 33 | process.exit(0); 34 | } 35 | 36 | if (opts.version) { 37 | if (opts.json) { 38 | stdio.log({versions: {r2g: pkgJSON.version}}); 39 | } 40 | else { 41 | console.log('r2g version:', chalk.bold(pkgJSON.version)); 42 | } 43 | process.exit(0); 44 | } 45 | 46 | const flattenDeep = function (arr1: Array): Array { 47 | return arr1.reduce((acc, val) => Array.isArray(val) ? acc.concat(flattenDeep(val)) : acc.concat(val), []); 48 | }; 49 | 50 | if (opts.bash_completion) { 51 | 52 | const allOpts = flattenDeep([initOpts, runOpts, basicOpts]); 53 | 54 | let generatedBashCode = dashdash.bashCompletionFromOptions({ 55 | name: 'r2g', 56 | options: allOpts, 57 | includeHidden: false 58 | }); 59 | 60 | console.log(generatedBashCode); 61 | process.exit(0); 62 | } 63 | 64 | const cwd = process.cwd(); 65 | const projectRoot = residence.findProjectRoot(cwd); 66 | 67 | if (!projectRoot) { 68 | throw chalk.magenta('Could not find a project root given your current working directory => ') + chalk.magenta.bold(cwd); 69 | } 70 | 71 | export {opts, cwd, projectRoot}; 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /src/commands/clean/cli-options.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | export default [ 4 | 5 | { 6 | names: ['help', 'h'], 7 | type: 'bool', 8 | help: 'Print this help and exit.' 9 | }, 10 | { 11 | names: ['verbosity', 'v'], 12 | type: 'integer', 13 | help: 'Verbosity level, 1-3 inclusive.' 14 | }, 15 | { 16 | names: ['force', 'f'], 17 | type: 'integer', 18 | help: 'Verbosity level, 1-3 inclusive.' 19 | }, 20 | { 21 | names: ['allow-unknown'], 22 | type: 'bool', 23 | help: 'Allow unknown arguments to the command line.', 24 | env: 'r2g_allow_unknown' 25 | } 26 | 27 | ] 28 | 29 | 30 | export interface R2GInitOpts { 31 | allow_unknown: boolean, 32 | force: boolean, 33 | help: boolean, 34 | verbosity: number 35 | } 36 | -------------------------------------------------------------------------------- /src/commands/clean/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | 5 | import log from "../../logger"; 6 | 7 | process.once('exit', code => { 8 | log.info('r2g is exiting with code:', code); 9 | }); 10 | 11 | import {opts, projectRoot, cwd} from './parse-cli-options'; 12 | import * as m from './run'; 13 | m.run(cwd, projectRoot, opts); 14 | -------------------------------------------------------------------------------- /src/commands/clean/parse-cli-options.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import options from './cli-options'; 4 | const dashdash = require('dashdash'); 5 | import residence = require('residence'); 6 | import chalk from "chalk"; 7 | const allowUnknown = process.argv.indexOf('--allow-unknown') > 0; 8 | const parser = dashdash.createParser({options, allowUnknown}); 9 | const pkgJSON = require('../../../package.json'); 10 | import log from '../../logger'; 11 | 12 | let opts: any; 13 | 14 | try { 15 | opts = parser.parse(process.argv); 16 | } catch (e) { 17 | log.error(chalk.magenta('r2g init: cli parsing error:'), chalk.magentaBright.bold(e.message)); 18 | process.exit(1); 19 | } 20 | 21 | if (opts.help) { 22 | let help = parser.help({includeEnv: true}).trimRight(); 23 | console.log('usage: r2g init [OPTIONS]\n' + help); 24 | process.exit(0); 25 | } 26 | 27 | 28 | const cwd = process.cwd(); 29 | const projectRoot = residence.findProjectRoot(cwd); 30 | 31 | if(!projectRoot){ 32 | throw chalk.magenta('Could not find a project root given your current working directory => ') + chalk.magenta.bold(cwd); 33 | } 34 | 35 | 36 | export {opts, cwd, projectRoot}; 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/commands/clean/run.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import cp = require('child_process'); 4 | import path = require("path"); 5 | import fs = require('fs'); 6 | import async = require('async'); 7 | import {getCleanTrace} from 'clean-trace'; 8 | 9 | // project 10 | import log from '../../logger'; 11 | import chalk from "chalk"; 12 | import * as util from "util"; 13 | import shortid = require("shortid"); 14 | import pt from 'prepend-transform'; 15 | 16 | /////////////////////////////////////////////// 17 | 18 | export const run = function (cwd: string, projectRoot: string, opts: any) { 19 | 20 | const dir = path.resolve(process.env.HOME + `/.r2g/temp`); 21 | 22 | async.autoInject({ 23 | 24 | clean(cb: any) { 25 | 26 | const k = cp.spawn('bash'); 27 | k.stdin.end(`set -e; mkdir -p "${dir}"; rm -rf "${dir}"; mkdir -p "${dir}"`); 28 | k.stderr.pipe(pt('clean: ')).pipe(process.stderr); 29 | k.once('exit', code => { 30 | if (code > 0) { 31 | log.error('Could not clean dir at path:', dir); 32 | } 33 | cb(code); 34 | }); 35 | 36 | }, 37 | 38 | }, 39 | 40 | (err: any, results) => { 41 | 42 | if (err && err.OK) { 43 | log.warn(chalk.blueBright('Your package may have been published with some problems:')); 44 | log.warn(util.inspect(err)); 45 | } 46 | else if (err) { 47 | throw getCleanTrace(err); 48 | } 49 | else { 50 | log.info(chalk.green('Successfully cleaned all contents in dir:', chalk.bold(dir))) 51 | } 52 | 53 | }); 54 | 55 | }; 56 | 57 | -------------------------------------------------------------------------------- /src/commands/init/cli-options.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | export default [ 4 | 5 | { 6 | names: ['help', 'h'], 7 | type: 'bool', 8 | help: 'Print this help and exit.' 9 | }, 10 | { 11 | names: ['verbosity', 'v'], 12 | type: 'integer', 13 | help: 'Verbosity level, 1-3 inclusive.' 14 | }, 15 | { 16 | names: ['force', 'f'], 17 | type: 'integer', 18 | help: 'Verbosity level, 1-3 inclusive.' 19 | }, 20 | { 21 | names: ['docker'], 22 | type: 'bool', 23 | help: 'Include docker-related files during init.', 24 | }, 25 | { 26 | names: ['search-root', 'search'], 27 | type: 'arrayOfString', 28 | help: 'Search root path on your fs, to look for local dependencies.', 29 | }, 30 | { 31 | names: ['allow-unknown'], 32 | type: 'bool', 33 | help: 'Allow unknown arguments to the command line.', 34 | env: 'r2g_allow_unknown' 35 | } 36 | 37 | ] 38 | 39 | 40 | export interface R2GInitOpts { 41 | search_root: Array, 42 | version: boolean, 43 | docker: boolean, 44 | allow_unknown: boolean, 45 | force: boolean, 46 | help: boolean, 47 | verbosity: number 48 | } 49 | -------------------------------------------------------------------------------- /src/commands/init/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | 5 | import log from "../../logger"; 6 | 7 | process.once('exit', code => { 8 | log.info('r2g is exiting with code:', code); 9 | }); 10 | 11 | 12 | import {opts, projectRoot, cwd} from './parse-cli-options'; 13 | import * as m from './run'; 14 | m.run(cwd, projectRoot, opts); 15 | -------------------------------------------------------------------------------- /src/commands/init/parse-cli-options.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import options from './cli-options'; 4 | const dashdash = require('dashdash'); 5 | import residence = require('residence'); 6 | import chalk from "chalk"; 7 | const allowUnknown = process.argv.indexOf('--allow-unknown') > 0; 8 | const parser = dashdash.createParser({options, allowUnknown}); 9 | const pkgJSON = require('../../../package.json'); 10 | import log from '../../logger'; 11 | 12 | let opts: any; 13 | 14 | try { 15 | opts = parser.parse(process.argv); 16 | } catch (e) { 17 | log.error(chalk.magenta('r2g init: cli parsing error:'), chalk.magentaBright.bold(e.message)); 18 | process.exit(1); 19 | } 20 | 21 | if (opts.help) { 22 | let help = parser.help({includeEnv: true}).trimRight(); 23 | console.log('usage: r2g init [OPTIONS]\n' + help); 24 | process.exit(0); 25 | } 26 | 27 | if (opts.version) { 28 | console.log('r2g version:', pkgJSON.version); 29 | process.exit(0); 30 | } 31 | 32 | 33 | const cwd = process.cwd(); 34 | const projectRoot = residence.findProjectRoot(cwd); 35 | 36 | if(!projectRoot){ 37 | throw chalk.magenta('Could not find a project root given your current working directory => ') + chalk.magenta.bold(cwd); 38 | } 39 | 40 | 41 | export {opts, cwd, projectRoot}; 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /src/commands/init/run.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import cp = require('child_process'); 4 | import path = require("path"); 5 | import fs = require('fs'); 6 | import async = require('async'); 7 | import {getCleanTrace} from 'clean-trace'; 8 | 9 | // project 10 | const execSh = path.resolve(__dirname + '/../../../assets/exec.sh'); 11 | const contents = path.resolve(__dirname + '/../../../assets/contents'); 12 | const Dockerfile = path.resolve(__dirname + '/../../../assets/Dockerfile.r2g.original'); 13 | const docker_r2g = '.r2g'; 14 | import log from '../../logger'; 15 | import chalk from "chalk"; 16 | import * as util from "util"; 17 | 18 | /////////////////////////////////////////////// 19 | 20 | export const run = function (cwd: string, projectRoot: string, opts: any) { 21 | 22 | const dockerfileDest = path.resolve(projectRoot + '/Dockerfile.r2g'); 23 | const execShDest = path.resolve(projectRoot + '/.r2g/exec.sh'); 24 | 25 | async.autoInject({ 26 | 27 | mkdir(cb: any) { 28 | 29 | const k = cp.spawn('bash'); 30 | k.stdin.end(`mkdir "${projectRoot}/${docker_r2g}"`); 31 | k.once('exit', code => cb(null, code)); 32 | 33 | }, 34 | 35 | copyContents(mkdir: any, cb: any) { 36 | 37 | if (mkdir) { 38 | log.info(chalk.yellow('Could not create .r2g folder (already exists?).')); 39 | return process.nextTick(cb); 40 | } 41 | 42 | const k = cp.spawn('bash'); 43 | k.stdin.end(`cp -R ${contents}/* "${cwd}/${docker_r2g}"`); 44 | k.once('exit', cb); 45 | }, 46 | 47 | checkIfExecShExists(cb: any) { 48 | 49 | if (!opts.docker) { 50 | return process.nextTick(cb); 51 | } 52 | 53 | fs.lstat(execShDest, (err, stats) => cb(null, stats)); 54 | }, 55 | 56 | copyExecSh(mkdir: any, checkIfExecShExists: any, copyContents: any, cb: any) { 57 | 58 | if (!opts.docker) { 59 | return process.nextTick(cb); 60 | } 61 | 62 | if (checkIfExecShExists) { 63 | log.info(chalk.yellow('Could not create .r2g/exec.sh file (already exists?).')); 64 | return process.nextTick(cb); 65 | } 66 | 67 | fs.createReadStream(execSh) 68 | .pipe(fs.createWriteStream(execShDest)) 69 | .once('error', cb) 70 | .once('end', cb); 71 | 72 | }, 73 | 74 | checkIfDockerfileExists: function (cb: any) { 75 | fs.lstat(dockerfileDest, function (err, stats) { 76 | cb(null, stats); 77 | }); 78 | }, 79 | 80 | createDockerfile (checkIfDockerfileExists: any, cb: any) { 81 | 82 | if (!opts.docker) { 83 | return process.nextTick(cb); 84 | } 85 | 86 | if (checkIfDockerfileExists) { 87 | log.info(chalk.yellow('Could not create Dockerfile.r2g file (already exists?).')); 88 | return process.nextTick(cb); 89 | } 90 | 91 | fs.createReadStream(Dockerfile) 92 | .pipe(fs.createWriteStream(dockerfileDest)) 93 | .once('error', cb) 94 | .once('end', cb); 95 | } 96 | 97 | }, 98 | 99 | function (err: any, results) { 100 | 101 | if (err && err.OK) { 102 | log.warn(chalk.blueBright('r2g/r2g.docker may have been initialized with some problems.')); 103 | log.warn(util.inspect(err)); 104 | } 105 | else if (err) { 106 | throw getCleanTrace(err); 107 | } 108 | else { 109 | log.info(chalk.green('Successfully initialized r2g/r2g.docker')) 110 | } 111 | 112 | }); 113 | 114 | }; 115 | 116 | -------------------------------------------------------------------------------- /src/commands/inspect/cli-options.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | export default [ 4 | 5 | { 6 | names: ['help', 'h'], 7 | type: 'bool', 8 | help: 'Print this help and exit.' 9 | }, 10 | { 11 | names: ['verbosity', 'v'], 12 | type: 'integer', 13 | help: 'Verbosity level, 1-3 inclusive.' 14 | }, 15 | { 16 | names: ['force', 'f'], 17 | type: 'integer', 18 | help: 'Verbosity level, 1-3 inclusive.' 19 | }, 20 | { 21 | names: ['allow-unknown'], 22 | type: 'bool', 23 | help: 'Allow unknown arguments to the command line.', 24 | env: 'r2g_allow_unknown' 25 | } 26 | 27 | ] 28 | 29 | 30 | export interface R2GInitOpts { 31 | allow_unknown: boolean, 32 | force: boolean, 33 | help: boolean, 34 | verbosity: number 35 | } 36 | -------------------------------------------------------------------------------- /src/commands/inspect/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | 5 | import log from "../../logger"; 6 | 7 | process.once('exit', code => { 8 | log.info('r2g is exiting with code:', code); 9 | }); 10 | 11 | import {opts, projectRoot, cwd} from './parse-cli-options'; 12 | import * as m from './run'; 13 | m.run(cwd, projectRoot, opts); 14 | -------------------------------------------------------------------------------- /src/commands/inspect/parse-cli-options.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import options from './cli-options'; 4 | const dashdash = require('dashdash'); 5 | import residence = require('residence'); 6 | import chalk from "chalk"; 7 | const allowUnknown = process.argv.indexOf('--allow-unknown') > 0; 8 | const parser = dashdash.createParser({options, allowUnknown}); 9 | const pkgJSON = require('../../../package.json'); 10 | import log from '../../logger'; 11 | 12 | let opts: any; 13 | 14 | try { 15 | opts = parser.parse(process.argv); 16 | } catch (e) { 17 | log.error(chalk.magenta('r2g init: cli parsing error:'), chalk.magentaBright.bold(e.message)); 18 | process.exit(1); 19 | } 20 | 21 | if (opts.help) { 22 | let help = parser.help({includeEnv: true}).trimRight(); 23 | console.log('usage: r2g init [OPTIONS]\n' + help); 24 | process.exit(0); 25 | } 26 | 27 | 28 | const cwd = process.cwd(); 29 | const projectRoot = residence.findProjectRoot(cwd); 30 | 31 | if(!projectRoot){ 32 | throw chalk.magenta('Could not find a project root given your current working directory => ') + chalk.magenta.bold(cwd); 33 | } 34 | 35 | 36 | export {opts, cwd, projectRoot}; 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/commands/inspect/run.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import cp = require('child_process'); 4 | import path = require("path"); 5 | import fs = require('fs'); 6 | import async = require('async'); 7 | import {getCleanTrace} from 'clean-trace'; 8 | import * as stdio from 'json-stdio'; 9 | 10 | // project 11 | const execSh = path.resolve(__dirname + '/../../../assets/exec.sh'); 12 | const contents = path.resolve(__dirname + '/../../../assets/contents'); 13 | const Dockerfile = path.resolve(__dirname + '/../../../assets/Dockerfile.r2g.original'); 14 | const docker_r2g = '.r2g'; 15 | import log from '../../logger'; 16 | import chalk from "chalk"; 17 | import * as util from "util"; 18 | import shortid = require("shortid"); 19 | import pt from 'prepend-transform'; 20 | import {EVCb} from '../../index'; 21 | 22 | /////////////////////////////////////////////// 23 | 24 | interface CreateTarball { 25 | code: number, 26 | pack: { 27 | value: string 28 | } 29 | } 30 | 31 | export const run = function (cwd: string, projectRoot: string, opts: any) { 32 | 33 | const id = shortid.generate(); 34 | 35 | const fifoDir = path.resolve(process.env.HOME + `/.r2g/temp/fifo/${id}`); 36 | const extractDir = path.resolve(process.env.HOME + `/.r2g/temp/extract/${id}`); 37 | const publishDir = path.resolve(process.env.HOME + `/.r2g/temp/inspect/${id}`); 38 | 39 | async.autoInject({ 40 | 41 | mkdir(cb: any) { 42 | 43 | const k = cp.spawn('bash'); 44 | k.stdin.end(` 45 | 46 | set -e; 47 | mkdir -p "${publishDir}"; 48 | mkdir -p "${extractDir}"; 49 | mkdir -p "${fifoDir}"; 50 | mkfifo "${fifoDir}/fifo"; 51 | 52 | `); 53 | 54 | k.stderr.pipe(pt('mkdir: ')).pipe(process.stderr); 55 | k.once('exit', code => { 56 | if (code > 0) { 57 | log.error('Could not create dir at path:', publishDir); 58 | } 59 | cb(code); 60 | }); 61 | 62 | }, 63 | 64 | copyProject(mkdir: any, cb: any) { 65 | 66 | const k = cp.spawn('bash'); 67 | const cmd = `rsync --perms --copy-links -r --exclude=".r2g" --exclude="node_modules" --exclude=".git" "${projectRoot}/" "${publishDir}/";`; 68 | k.stdin.end(cmd); 69 | k.stderr.pipe(pt('rsync: ')).pipe(process.stderr); 70 | k.once('exit', code => { 71 | if (code > 0) { 72 | log.error('Could not run the following command:'); 73 | log.error(cmd); 74 | } 75 | cb(code); 76 | }); 77 | }, 78 | 79 | createTarball(copyProject: any, cb: EVCb) { 80 | 81 | const k = cp.spawn('bash'); 82 | const cmd = ` 83 | set -e; 84 | cd "${publishDir}"; 85 | pack=$(npm pack --loglevel=warn); 86 | cat < { 96 | if (!(v && typeof v === 'string')) { 97 | return log.error('type of json-parsed value was not a string:', util.inspect(v)); 98 | } 99 | pack.value = path.resolve(publishDir + '/' + v); 100 | }); 101 | 102 | k.stderr.pipe(pt(chalk.magenta('npm pack: '))).pipe(process.stderr); 103 | k.stdin.end(cmd); 104 | 105 | k.once('exit', code => { 106 | if (code > 0) { 107 | log.error('Could not pack tarball.'); 108 | log.error('Could not run the following command:'); 109 | log.error(cmd); 110 | } 111 | cb(null, {code, pack}); 112 | }); 113 | }, 114 | 115 | inspectAllFiles(createTarball: CreateTarball, cb: EVCb) { 116 | 117 | if (!(createTarball.pack && createTarball.pack.value)) { 118 | log.error('No tarball could be found.'); 119 | return process.nextTick(cb); 120 | } 121 | 122 | log.info(); 123 | log.info('Tarball was packed here:', chalk.blueBright.bold((createTarball.pack.value))); 124 | 125 | const k = cp.spawn('bash'); 126 | 127 | log.info(); 128 | log.info(chalk.bold.underline.italic('Results from the "tar --list" command:')); 129 | log.info(); 130 | 131 | const cmd = `tar -z --list --file="${createTarball.pack.value}" | grep '^package/' | cut -c 9- ;`; 132 | 133 | k.stdin.end(` 134 | ${cmd} 135 | `); 136 | 137 | k.stdout.pipe(pt(chalk.gray('the tarball contents: '))).pipe(process.stdout); 138 | k.stderr.pipe(pt(chalk.magenta('the tarball: '))).pipe(process.stderr); 139 | 140 | k.once('exit', code => { 141 | 142 | if (code > 0) { 143 | log.error('Could not run this command:', cmd); 144 | } 145 | 146 | cb(createTarball.code); 147 | }); 148 | }, 149 | 150 | 151 | extract(inspectAllFiles: any, createTarball: CreateTarball, cb: EVCb) { 152 | 153 | if (!(createTarball.pack && createTarball.pack.value)) { 154 | log.error('No tarball could be found.'); 155 | return process.nextTick(cb); 156 | } 157 | 158 | log.info(); 159 | log.info('Tarball will be extracted here:', chalk.blueBright.bold(extractDir)); 160 | 161 | const cmd = ` cd "${extractDir}/package" && find . -type f | xargs du --threshold=5KB | sort`; 162 | console.log('Running this command to inspect your tarball for large files:'); 163 | console.log(chalk.blueBright(cmd)); 164 | 165 | const fifo = cp.spawn('bash'); 166 | fifo.stdout.setEncoding('utf8'); 167 | 168 | fifo.stdout 169 | .pipe(pt(chalk.yellow.bold('warning: this is a big file (>5KB) according to du: '))) 170 | .pipe(process.stdout); 171 | 172 | fifo.stdin.end(` 173 | 174 | on_sigint(){ 175 | export has_sigint=yes; 176 | sleep 0.25; 177 | exit 0; 178 | } 179 | 180 | export -f on_sigint; 181 | 182 | trap on_sigint INT 183 | trap on_sigint SIGINT 184 | 185 | while [[ "$has_sigint" != "yes" ]]; do 186 | exec cat "${fifoDir}/fifo" 187 | done 188 | 189 | `); 190 | 191 | const fifoStatus = { 192 | exited: false 193 | }; 194 | 195 | fifo.once('exit', code => { 196 | fifoStatus.exited = true; 197 | }); 198 | 199 | { 200 | 201 | const k = cp.spawn('bash'); 202 | 203 | k.stdin.end(` 204 | 205 | set -e; 206 | 207 | echo; 208 | echo -e ${chalk.italic.underline('results from the du file-size check command:')}; 209 | tar -xzvf "${createTarball.pack.value}" -C "${extractDir}" > /dev/null; 210 | ${cmd} > "${fifoDir}/fifo"; 211 | kill -INT ${fifo.pid} | cat; 212 | 213 | `); 214 | 215 | k.stdout 216 | .pipe(process.stdout); 217 | 218 | k.stderr 219 | .pipe(pt(chalk.magenta('du stderr: '))) 220 | .pipe(process.stderr); 221 | 222 | k.once('exit', code => { 223 | 224 | if (code > 0) { 225 | log.error('Could not rimraf dir at path:', publishDir); 226 | } 227 | 228 | cb(code); 229 | }); 230 | } 231 | 232 | 233 | }, 234 | 235 | inspectOnlyFolders(extract: any, createTarball: CreateTarball, cb: EVCb) { 236 | 237 | if (!(createTarball.pack && createTarball.pack.value)) { 238 | log.error('No tarball could be found.'); 239 | return process.nextTick(cb); 240 | } 241 | 242 | log.info(); 243 | log.info('Tarball was packed here:', chalk.blueBright.bold((createTarball.pack.value))); 244 | 245 | const k = cp.spawn('bash'); 246 | 247 | log.info(); 248 | log.info(chalk.bold.underline.italic('Results from the "find . -type d -maxdepth 2" command:')); 249 | log.info(); 250 | 251 | k.stdin.end(` 252 | 253 | cd "${extractDir}/package" && find . -type d -mindepth 1 -maxdepth 2 | sort ; 254 | 255 | `); 256 | 257 | k.stdout.pipe(pt(chalk.gray('the tarball folders: '))).pipe(process.stdout); 258 | k.stderr.pipe(pt(chalk.magenta('the tarball folders stderr: '))).pipe(process.stderr); 259 | 260 | k.once('exit', code => { 261 | 262 | if (code > 0) { 263 | log.error('Could not rimraf dir at path:', publishDir); 264 | } 265 | 266 | cb(createTarball.code); 267 | }); 268 | }, 269 | 270 | 271 | duOnFolders(inspectOnlyFolders: any, createTarball: CreateTarball, cb: EVCb) { 272 | 273 | if (!(createTarball.pack && createTarball.pack.value)) { 274 | log.error('No tarball could be found.'); 275 | return process.nextTick(cb); 276 | } 277 | 278 | log.info(); 279 | log.info('Tarball was packed here:', chalk.blueBright.bold((createTarball.pack.value))); 280 | 281 | const k = cp.spawn('bash'); 282 | 283 | log.info(); 284 | log.info(chalk.bold.underline.italic('Results from the "find . -type d -maxdepth 2" command:')); 285 | log.info(); 286 | 287 | k.stdin.end(` 288 | 289 | cd "${extractDir}/package" && ( find . -type d -mindepth 1 -maxdepth 2 | du --max-depth=1 -h --threshold=2KB ) ; 290 | 291 | `); 292 | 293 | k.stdout.pipe(pt(chalk.gray('the tarball folders: '))).pipe(process.stdout); 294 | k.stderr.pipe(pt(chalk.magenta('the tarball folders stderr: '))).pipe(process.stderr); 295 | 296 | k.once('exit', code => { 297 | 298 | if (code > 0) { 299 | log.error('Could not rimraf dir at path:', publishDir); 300 | } 301 | 302 | cb(createTarball.code); 303 | }); 304 | }, 305 | 306 | rimraf(duOnFolders: any, createTarball: CreateTarball, cb: EVCb) { 307 | 308 | const k = cp.spawn('bash'); 309 | 310 | k.stdin.end(`rm -rf "${publishDir}"; rm -rf "${extractDir}"; rm -rf "${fifoDir}";`); 311 | 312 | k.once('exit', code => { 313 | 314 | if (code > 0) { 315 | log.error('Could not rimraf dir at path:', extractDir); 316 | log.error('Could not rimraf dir at path:', publishDir); 317 | } 318 | 319 | cb(createTarball.code); 320 | }); 321 | } 322 | }, 323 | 324 | (err: any, results) => { 325 | 326 | console.log(); 327 | 328 | if (err && err.OK) { 329 | log.warn(chalk.blueBright('Your package may have been published with some problems:')); 330 | log.warn(util.inspect(err)); 331 | } 332 | else if (err) { 333 | throw getCleanTrace(err); 334 | } 335 | else { 336 | log.info(chalk.green('Successfully inspected tarball.')) 337 | } 338 | 339 | // process.exit(0); 340 | 341 | }); 342 | 343 | }; 344 | 345 | -------------------------------------------------------------------------------- /src/commands/publish/cli-options.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | export default [ 4 | 5 | { 6 | names: ['help', 'h'], 7 | type: 'bool', 8 | help: 'Print this help and exit.' 9 | }, 10 | { 11 | names: ['verbosity', 'v'], 12 | type: 'integer', 13 | help: 'Verbosity level, 1-3 inclusive.' 14 | }, 15 | { 16 | names: ['force', 'f'], 17 | type: 'integer', 18 | help: 'Verbosity level, 1-3 inclusive.' 19 | }, 20 | { 21 | names: ['otp'], 22 | type: 'string', 23 | help: 'Provide/pass this OTP (one-time passcode) to NPM.', 24 | default: '' 25 | }, 26 | { 27 | names: ['access'], 28 | type: 'string', 29 | help: 'Allow public/restricted access to package.', 30 | default: 'restricted' 31 | }, 32 | { 33 | names: ['allow-unknown'], 34 | type: 'bool', 35 | help: 'Allow unknown arguments to the command line.', 36 | env: 'r2g_allow_unknown' 37 | }, 38 | { 39 | names: ['ignore-dirty-git-index', 'ignore-dirty-index'], 40 | type: 'bool', 41 | default: false, 42 | env: 'ignore_dirty_index' 43 | } 44 | 45 | ] 46 | 47 | 48 | export interface R2GInitOpts { 49 | allow_unknown: boolean, 50 | force: boolean, 51 | help: boolean, 52 | verbosity: number 53 | } 54 | -------------------------------------------------------------------------------- /src/commands/publish/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | 5 | import log from "../../logger"; 6 | 7 | process.once('exit', code => { 8 | log.info('r2g is exiting with code:', code); 9 | }); 10 | 11 | import {opts, projectRoot, cwd} from './parse-cli-options'; 12 | import * as m from './run'; 13 | m.run(cwd, projectRoot, opts); 14 | -------------------------------------------------------------------------------- /src/commands/publish/parse-cli-options.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import options from './cli-options'; 4 | const dashdash = require('dashdash'); 5 | import residence = require('residence'); 6 | import chalk from "chalk"; 7 | const allowUnknown = process.argv.indexOf('--allow-unknown') > 0; 8 | const parser = dashdash.createParser({options, allowUnknown}); 9 | const pkgJSON = require('../../../package.json'); 10 | import log from '../../logger'; 11 | 12 | let opts: any; 13 | 14 | try { 15 | opts = parser.parse(process.argv); 16 | } catch (e) { 17 | log.error(chalk.magenta('r2g init: cli parsing error:'), chalk.magentaBright.bold(e.message)); 18 | process.exit(1); 19 | } 20 | 21 | if (opts.help) { 22 | let help = parser.help({includeEnv: true}).trimRight(); 23 | console.log('usage: r2g init [OPTIONS]\n' + help); 24 | process.exit(0); 25 | } 26 | 27 | 28 | const cwd = process.cwd(); 29 | const projectRoot = residence.findProjectRoot(cwd); 30 | 31 | if(!projectRoot){ 32 | throw chalk.magenta('Could not find a project root given your current working directory => ') + chalk.magenta.bold(cwd); 33 | } 34 | 35 | 36 | export {opts, cwd, projectRoot}; 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/commands/publish/run.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import cp = require('child_process'); 4 | import path = require("path"); 5 | import fs = require('fs'); 6 | import async = require('async'); 7 | import {getCleanTrace} from 'clean-trace'; 8 | 9 | // project 10 | const execSh = path.resolve(__dirname + '/../../../assets/exec.sh'); 11 | const contents = path.resolve(__dirname + '/../../../assets/contents'); 12 | const Dockerfile = path.resolve(__dirname + '/../../../assets/Dockerfile.r2g.original'); 13 | const docker_r2g = '.r2g'; 14 | import log from '../../logger'; 15 | import chalk from "chalk"; 16 | import * as util from "util"; 17 | import shortid = require("shortid"); 18 | import pt from 'prepend-transform'; 19 | import {EVCb} from "../../index"; 20 | 21 | /////////////////////////////////////////////// 22 | 23 | export const run = function (cwd: string, projectRoot: string, opts: any) { 24 | 25 | const id = shortid.generate(); 26 | const publishDir = path.resolve(process.env.HOME + `/.r2g/temp/publish/${id}`); 27 | 28 | async.autoInject({ 29 | 30 | checkForUntrackedFiles(cb: EVCb) { 31 | 32 | if (opts.ignore_dirty_git_index) { 33 | return process.nextTick(cb); 34 | } 35 | 36 | log.info('Checking for dirty git status...'); 37 | 38 | const k = cp.spawn('bash'); 39 | k.stderr.pipe(process.stderr); 40 | 41 | k.stdin.end(` 42 | set -e; 43 | cd ${projectRoot}; 44 | if [[ ! -d '.git' ]]; then 45 | exit 0; 46 | fi 47 | 48 | if test $(git status --porcelain | wc -l) != '0'; then 49 | exit 1; 50 | fi 51 | `); 52 | 53 | k.once('exit', code => { 54 | if (code > 0) { 55 | log.error('Changes to (untracked) files need to be committed. Check your git index using the `git status` command.'); 56 | log.error('Looks like the git index was dirty. Use "--ignore-dirty-git-index" to skip this warning.'); 57 | } 58 | cb(code); 59 | }); 60 | }, 61 | 62 | checkForDirtyGitIndex(checkForUntrackedFiles: any, cb: EVCb) { 63 | 64 | if (opts.ignore_dirty_git_index) { 65 | return process.nextTick(cb); 66 | } 67 | 68 | log.info('Checking for dirty git index...'); 69 | const k = cp.spawn('bash'); 70 | k.stderr.pipe(process.stderr); 71 | 72 | k.stdin.end(` 73 | set -e; 74 | cd ${projectRoot}; 75 | if [[ -d '.git' ]]; then 76 | git diff --quiet 77 | fi 78 | `); 79 | 80 | k.once('exit', code => { 81 | if (code > 0) { 82 | log.error('Looks like the git index was dirty. Use "--ignore-dirty-git-index" to skip this warning.'); 83 | } 84 | cb(code); 85 | }); 86 | 87 | }, 88 | 89 | mkdir(checkForDirtyGitIndex: any, cb: any) { 90 | 91 | const k = cp.spawn('bash'); 92 | k.stdin.end(`mkdir -p "${publishDir}"`); 93 | k.stderr.pipe(pt('mkdir: ')).pipe(process.stderr); 94 | k.once('exit', code => { 95 | if (code > 0) { 96 | log.error('Could not create dir at path:', publishDir); 97 | } 98 | cb(code); 99 | }); 100 | 101 | }, 102 | 103 | copyProject(mkdir: any, cb: any) { 104 | 105 | const k = cp.spawn('bash'); 106 | const cmd = `rsync --perms --copy-links -r --exclude=".r2g" --exclude="node_modules" --exclude=".git" "${projectRoot}/" "${publishDir}/";`; 107 | k.stdin.end(cmd); 108 | k.stderr.pipe(pt('rsync: ')).pipe(process.stderr); 109 | k.once('exit', code => { 110 | if (code > 0) { 111 | log.error('Could not run the following command:'); 112 | log.error(cmd); 113 | } 114 | cb(code); 115 | }); 116 | }, 117 | 118 | packAndPublish(copyProject: any, cb: any) { 119 | 120 | const npmOpts = opts.otp ? ` --otp="${opts.otp}" ` : ''; 121 | 122 | const k = cp.spawn('bash'); 123 | const cmd = ` 124 | set -e; 125 | cd "${publishDir}"; 126 | npm publish --loglevel=warn --access="${opts.access}" --otp="${opts.otp}"; 127 | `; 128 | 129 | k.stdout.pipe(pt('npm publish:')).pipe(process.stdout); 130 | k.stderr.pipe(pt('npm publish: ')).pipe(process.stderr); 131 | k.stdin.end(cmd); 132 | 133 | k.once('exit', code => { 134 | if (code > 0) { 135 | log.error('Could not publish tarball.'); 136 | log.error('Could not run the following command:'); 137 | log.error(cmd); 138 | } 139 | cb(null, code); 140 | }); 141 | }, 142 | 143 | rimraf(packAndPublish: number, cb: any) { 144 | 145 | const k = cp.spawn('bash'); 146 | k.stdin.end(`rm -rf "${publishDir}"`); 147 | k.once('exit', code => { 148 | 149 | if (code > 0) { 150 | log.error('Could not rimraf dir at path:', publishDir); 151 | } 152 | 153 | cb(packAndPublish); 154 | }); 155 | } 156 | }, 157 | 158 | (err: any, results) => { 159 | 160 | if (err && err.OK) { 161 | log.warn(chalk.blueBright('Your package may have been published with some problems:')); 162 | log.warn(util.inspect(err)); 163 | } 164 | else if (err) { 165 | throw getCleanTrace(err); 166 | } 167 | else { 168 | log.info(chalk.green('Successfully published your package.')) 169 | } 170 | 171 | }); 172 | 173 | }; 174 | 175 | -------------------------------------------------------------------------------- /src/commands/run/cli-options.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | export default [ 4 | 5 | { 6 | names: ['help', 'h'], 7 | type: 'bool', 8 | help: 'Print this help and exit.' 9 | }, 10 | { 11 | names: ['verbosity', 'v'], 12 | type: 'integer', 13 | help: 'Verbosity level, 1-3 inclusive.' 14 | }, 15 | { 16 | names: ['ignore-dirty-git-index', 'ignore-dirty-index'], 17 | type: 'bool', 18 | default: false, 19 | env: 'ignore_dirty_index' 20 | }, 21 | { 22 | names: ['search-root','search'], 23 | type: 'string', 24 | help: 'Search root path on your fs, to look for local dependencies.', 25 | }, 26 | { 27 | names: ['pack'], 28 | type: 'bool', 29 | help: 'Pack dependencies (with npm pack) before installing them; this option is only active if --full is used.', 30 | }, 31 | { 32 | names: ['allow-unknown'], 33 | type: 'bool', 34 | help: 'Allow unknown arguments to the command line.', 35 | env: 'r2g_allow_unknown' 36 | }, 37 | { 38 | names: ['skip'], 39 | type: 'string', 40 | help : 'Skip phases, using --skip=s,t,z.', 41 | env: 'r2g_skip' 42 | }, 43 | { 44 | names: ['z'], 45 | type: 'bool', 46 | help : 'Skip phase-Z.' 47 | }, 48 | { 49 | names: ['t'], 50 | type: 'bool', 51 | help : 'Skip phase-T.' 52 | }, 53 | { 54 | names: ['s'], 55 | type: 'bool', 56 | help : 'Skip phase-S.' 57 | }, 58 | { 59 | names: ['full'], 60 | type: 'bool', 61 | help: 'Install local copies of select dependencies/packages, instead of using NPM registry/cache.', 62 | default: false 63 | }, 64 | { 65 | names: ['keep', 'multi'], 66 | type: 'bool', 67 | help: 'Do not remove previous installations before installing the new/next one.', 68 | } 69 | 70 | ] 71 | -------------------------------------------------------------------------------- /src/commands/run/copy-deps.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import cp = require('child_process'); 4 | import path = require("path"); 5 | import async = require('async'); 6 | import log from "../../logger"; 7 | import chalk from "chalk"; 8 | import shortid = require("shortid"); 9 | 10 | ///////////////////////////////////////////////////////////////// 11 | 12 | export const installDeps = (createProjectMap: any, dependenciesToInstall: Array, opts: any, cb: any) => { 13 | 14 | const finalMap = {} as any; 15 | 16 | if (dependenciesToInstall.length < 1) { 17 | return process.nextTick(cb, null, finalMap); 18 | } 19 | 20 | const c = opts.pack ? 4 : 4; 21 | 22 | async.eachLimit(dependenciesToInstall, c, (dep, cb) => { 23 | 24 | if (!createProjectMap[dep]) { 25 | log.info('dependency is not in the local map:', dep); 26 | return process.nextTick(cb, null); 27 | } 28 | 29 | const d = createProjectMap[dep]; 30 | const c = path.dirname(d); 31 | 32 | const k = cp.spawn('bash'); 33 | const id = shortid.generate(); 34 | const dest = path.resolve(process.env.HOME + `/.r2g/temp/deps/${id}`); 35 | const basename = path.basename(c); 36 | const depRoot = path.resolve(dest + '/' + basename); 37 | 38 | const pack = (depRoot: string, cb: any) => { 39 | 40 | if (!opts.pack) { 41 | // the map just points to the root of the project 42 | finalMap[dep] = depRoot; 43 | return process.nextTick(cb); 44 | } 45 | 46 | const k = cp.spawn('bash', [], { 47 | cwd: depRoot 48 | }); 49 | 50 | const cmd = [ 51 | `npm pack --loglevel=warn;`, 52 | ] 53 | .join(' '); 54 | 55 | log.info(`Running the following command: '${chalk.cyan.bold(cmd)}', in this directory: "${depRoot}".`); 56 | 57 | let stdout = ''; 58 | 59 | k.stdout.on('data', d => stdout += String(d).trim()); 60 | k.stdin.end(cmd); 61 | k.stderr.pipe(process.stderr); 62 | k.once('exit', function (code) { 63 | 64 | if (code > 0) { 65 | return cb(new Error('"npm pack" command exited with code greater than 0.')); 66 | } 67 | 68 | // the map points to the .tgz file in the root of the project, where stdout should be the .tgz file name 69 | finalMap[dep] = path.resolve(dest + '/' + basename + '/' + stdout); 70 | cb(null); 71 | 72 | }); 73 | }; 74 | 75 | const cmd = [ 76 | `set -e`, 77 | `mkdir -p "${dest}"`, 78 | `rsync --perms --copy-links -r --exclude="node_modules" --exclude=".git" "${c}" "${dest}";`, 79 | ] 80 | .join('; '); 81 | 82 | log.info(`About to run the following command: '${chalk.cyan.bold(cmd)}'`); 83 | 84 | k.stdin.end(cmd); 85 | k.stderr.pipe(process.stderr); 86 | 87 | k.once('exit', code => { 88 | 89 | if (code < 1) { 90 | return pack(depRoot, cb); 91 | } 92 | 93 | log.error('The following command failed:'); 94 | log.error(chalk.magentaBright.bold(cmd)); 95 | cb(new Error(`The following command '${cmd}', exited with code: "${code}".`)) 96 | 97 | }); 98 | 99 | }, 100 | 101 | function (err) { 102 | process.nextTick(cb, err, finalMap); 103 | }); 104 | 105 | }; 106 | -------------------------------------------------------------------------------- /src/commands/run/get-fs-map.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import path = require("path"); 4 | import fs = require('fs'); 5 | import async = require('async'); 6 | import log from "../../logger"; 7 | import {Packages} from "./run"; 8 | import * as util from "util"; 9 | import chalk from "chalk"; 10 | import {EVCb} from "../../index"; 11 | 12 | ///////////////////////////////////////////////////////////////////// 13 | 14 | const searchedPaths = {} as { [key: string]: true }; 15 | 16 | const isPathSearchableBasic = function (item: string) { 17 | 18 | item = path.normalize(item); 19 | 20 | if (!path.isAbsolute(item)) { 21 | throw new Error('Path to be searched is not absolute:' + item); 22 | } 23 | 24 | if (searchedPaths[item]) { 25 | return false; 26 | } 27 | 28 | return true; 29 | }; 30 | 31 | ///////////////////////////////////////////////////////////////////// 32 | 33 | export interface MapType { 34 | [key: string]: string 35 | } 36 | 37 | type Task = (cb: EVCb) => void; 38 | // searchQueue used to prevent too many dirs searched at once 39 | const q = async.queue((task, cb) => task(cb), 8); 40 | 41 | export const getFSMap = function (opts: any, searchRoots: Array, packages: Packages, cb: EVCb) { 42 | 43 | const pths: Array = []; 44 | 45 | searchRoots.map(d => String(d || '').trim()) 46 | .filter(Boolean) 47 | .sort((a, b) => (a.length - b.length)) 48 | .forEach(v => { 49 | 50 | const s = !pths.some(p => { 51 | return p.startsWith(v + '/'); 52 | }); 53 | 54 | if (s) { 55 | pths.push(v); 56 | } 57 | }); 58 | 59 | const map = {} as MapType; 60 | 61 | const searchDir = function (dir: string, cb: EVCb) { 62 | 63 | searchedPaths[dir] = true; 64 | 65 | q.push(callback => { 66 | 67 | fs.readdir(dir, (err, items) => { 68 | 69 | callback(null); 70 | 71 | if (err) { 72 | log.warn('Could not read directory at path:', dir); 73 | if (String(err.message || err).match(/permission denied/)) { 74 | return cb(null); 75 | } 76 | return cb(err); 77 | } 78 | 79 | const mappy = (itemv: string, cb: EVCb) => { 80 | 81 | const item = path.resolve(dir + '/' + itemv); 82 | 83 | fs.lstat(item, (err, stats) => { 84 | 85 | if (err) { 86 | log.warn(err.message); 87 | return cb(null); 88 | } 89 | 90 | if (stats.isSymbolicLink()) { 91 | opts.verbosity > 2 && log.warn('looks like a symbolic link:', item); 92 | return cb(null); 93 | } 94 | 95 | if (stats.isDirectory()) { 96 | 97 | if (!isPathSearchableBasic(item)) { 98 | return cb(null); 99 | } 100 | 101 | for (let v of ['/.npm', '/.cache', '/node_modules', '/.nvm']) { 102 | if (item.endsWith(v)) { 103 | return cb(null); 104 | } 105 | } 106 | 107 | return searchDir(item, cb); 108 | } 109 | 110 | if (!item.endsWith('/package.json')) { 111 | return cb(null); 112 | } 113 | 114 | if (!stats.isFile()) { 115 | log.warn('unexpected non-file here:', item); 116 | return cb(null); 117 | } 118 | 119 | fs.readFile(item, (err, data) => { 120 | 121 | if (err) { 122 | return cb(err); 123 | } 124 | 125 | let parsed = null, linkable = null; 126 | 127 | try { 128 | parsed = JSON.parse(String(data)); 129 | } 130 | catch (err) { 131 | log.error('trouble parsing package.json file at path => ', item); 132 | log.error(err.message); 133 | return cb(err); 134 | } 135 | 136 | try { 137 | linkable = parsed.r2g.linkable; 138 | } 139 | catch (err) { 140 | 141 | } 142 | 143 | if (linkable === false) { 144 | return cb(null); 145 | } 146 | 147 | if (parsed && parsed.name) { 148 | 149 | let nm = parsed.name; 150 | 151 | if (map[nm] && packages[nm]) { 152 | 153 | log.warn('the following package may exist in more than one place on your fs =>', chalk.magenta(nm)); 154 | log.warn('pre-existing place => ', chalk.blueBright(map[nm])); 155 | log.warn('new place => ', chalk.blueBright(item)); 156 | 157 | return cb( 158 | new Error('The following requested package name exists in more than 1 location on disk, and r2g does not know which one to use ... ' + 159 | chalk.magentaBright.bold(util.inspect({name: nm, locations: [map[nm], item]}))) 160 | ) 161 | } 162 | else if (packages[parsed.name]) { 163 | log.info('added the following package name to the map:', parsed.name); 164 | map[parsed.name] = item; 165 | } 166 | } 167 | 168 | cb(null); 169 | 170 | }); 171 | 172 | }); 173 | 174 | }; 175 | 176 | async.eachLimit( 177 | items, 178 | 4, 179 | mappy, 180 | cb 181 | ); 182 | 183 | }); 184 | 185 | }); 186 | 187 | }; 188 | 189 | pths.forEach(v => { 190 | if (isPathSearchableBasic(v)) { 191 | searchDir(v, function (err) { 192 | err && log.error('unexpected error:', err.message || err); 193 | cb(err, map); 194 | }); 195 | } 196 | }); 197 | 198 | }; 199 | -------------------------------------------------------------------------------- /src/commands/run/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | import log from "../../logger"; 5 | 6 | process.once('exit', code => { 7 | log.info('r2g is exiting with code:', code); 8 | }); 9 | 10 | 11 | import {opts, projectRoot, cwd} from './parse-cli-options'; 12 | import * as m from './run'; 13 | m.run(cwd, projectRoot, opts); 14 | -------------------------------------------------------------------------------- /src/commands/run/parse-cli-options.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import options from './cli-options'; 4 | const dashdash = require('dashdash'); 5 | import residence = require('residence'); 6 | import chalk from "chalk"; 7 | import log from '../../logger'; 8 | const allowUnknown = process.argv.indexOf('--allow-unknown') > 0; 9 | const parser = dashdash.createParser({options, allowUnknown}); 10 | 11 | let opts: any; 12 | 13 | try { 14 | opts = parser.parse(process.argv); 15 | } catch (e) { 16 | log.error('r2g: error: %s', e.message); 17 | process.exit(1); 18 | } 19 | 20 | if (opts.help) { 21 | let help = parser.help({includeEnv: true}).trimRight(); 22 | console.log('usage: r2g run [OPTIONS]\n' + help); 23 | process.exit(0); 24 | } 25 | 26 | 27 | const cwd = process.cwd(); 28 | const projectRoot = residence.findProjectRoot(cwd); 29 | 30 | if(!projectRoot){ 31 | throw chalk.magenta('Could not find a project root given your current working directory => ') + chalk.magenta.bold(cwd); 32 | } 33 | 34 | 35 | export {opts, cwd, projectRoot}; 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/commands/run/rename-deps.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import path = require("path"); 4 | import fs = require('fs'); 5 | import async = require('async'); 6 | import log from "../../logger"; 7 | import * as util from "util"; 8 | import chalk from "chalk"; 9 | import {AsyncAutoTaskFunction} from "async"; 10 | 11 | ///////////////////////////////////////////////////////////////// 12 | 13 | export const renameDeps = function (projectMap: any, pkgJSONPath: string, cb: any) { 14 | 15 | log.info('here is the project map now:'); 16 | 17 | Object.keys(projectMap).forEach(function (k) { 18 | log.info(chalk.bold(k), chalk.blueBright(util.inspect(projectMap[k]))); 19 | }); 20 | 21 | async.autoInject({ 22 | 23 | rereadPkgJSON: function (cb: AsyncAutoTaskFunction) { 24 | 25 | fs.readFile(pkgJSONPath, function (err, data) { 26 | 27 | if (err) { 28 | return cb(err, null); 29 | } 30 | 31 | try { 32 | return cb(null, JSON.parse(String(data))); 33 | } 34 | catch (err) { 35 | return cb(err, null); 36 | } 37 | 38 | }); 39 | 40 | }, 41 | 42 | saveNewJSONFileToDisk: function (rereadPkgJSON: any, cb: AsyncAutoTaskFunction) { 43 | 44 | const updateTheDepKV = function () { 45 | [ 46 | rereadPkgJSON.dependencies, 47 | rereadPkgJSON.devDependencies, 48 | rereadPkgJSON.optionalDependencies 49 | 50 | ] 51 | .forEach(function (d) { 52 | 53 | d = d || {}; 54 | 55 | Object.keys(d).forEach(function (k) { 56 | 57 | const v = d[k]; 58 | 59 | if (v && projectMap[k]) { 60 | d[k] = 'file://' + projectMap[k]; 61 | } 62 | else if (v && String(v).startsWith('file:')) { 63 | log.error('The following dep has a file:// key, but does not exist in generated map => ' + k); 64 | throw 'Please check your package.json file: ' + util.inspect(rereadPkgJSON); 65 | } 66 | 67 | // if (String(v).startsWith('file:')) { 68 | // 69 | // if (projectMap[k]) { 70 | // d[k] = 'file://' + path.dirname(projectMap[k]); 71 | // } 72 | // else { 73 | // log.error('The following dep has a file:// key, but does not exist in generated map => ' + k); 74 | // throw 'Please check your package.json file: ' + util.inspect(rereadPkgJSON) 75 | // } 76 | // 77 | // } 78 | 79 | }); 80 | 81 | }); 82 | }; 83 | 84 | let str = null; 85 | 86 | try { 87 | updateTheDepKV(); 88 | str = JSON.stringify(rereadPkgJSON, null, 2); 89 | log.debug('Updated package.json file:', rereadPkgJSON); 90 | } 91 | catch (err) { 92 | return process.nextTick(cb, err, null); 93 | } 94 | 95 | // save the json object back to disk 96 | fs.writeFile(pkgJSONPath, str, function (err) { 97 | 98 | if (err) { 99 | return cb(err, null); 100 | } 101 | 102 | fs.readFile(pkgJSONPath, 'utf8', function (err, data) { 103 | 104 | if (err) { 105 | return cb(err, null); 106 | } 107 | 108 | log.info(chalk.bold('here is the updated package.json file:'), data); 109 | cb(null, null); 110 | 111 | }); 112 | 113 | }); 114 | 115 | } 116 | 117 | }, cb); 118 | 119 | }; 120 | -------------------------------------------------------------------------------- /src/commands/run/run.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import cp = require('child_process'); 4 | import path = require("path"); 5 | import fs = require('fs'); 6 | import async = require('async'); 7 | import {getCleanTrace} from 'clean-trace'; 8 | import * as util from "util"; 9 | import * as assert from 'assert'; 10 | import * as Domain from 'domain'; 11 | 12 | // project 13 | import log from '../../logger'; 14 | import chalk from "chalk"; 15 | import {EVCb} from "../../index"; 16 | import {getFSMap} from "./get-fs-map"; 17 | import {renameDeps} from "./rename-deps"; 18 | import {installDeps} from "./copy-deps"; 19 | import pt from "prepend-transform"; 20 | import {timeout} from "async"; 21 | import deepMixin from "@oresoftware/deep.mixin"; 22 | 23 | /////////////////////////////////////////////// 24 | 25 | const r2gProject = path.resolve(process.env.HOME + '/.r2g/temp/project'); 26 | const r2gProjectCopy = path.resolve(process.env.HOME + '/.r2g/temp/copy'); 27 | const smokeTester = require.resolve('../../smoke-tester.js'); 28 | const defaultPackageJSONPath = require.resolve('../../../assets/default.package.json'); 29 | 30 | export interface Packages { 31 | [key: string]: boolean | string 32 | } 33 | 34 | interface BinFieldObject { 35 | [key: string]: string 36 | } 37 | 38 | const flattenDeep = (a: Array): Array => { 39 | return a.reduce((acc, val) => Array.isArray(val) ? acc.concat(flattenDeep(val)) : acc.concat(val), []); 40 | }; 41 | 42 | const handleTask = (r2gProject: string) => { 43 | return (t: (root: string, cb: EVCb) => void, cb: (err: any) => void) => { 44 | 45 | process.nextTick(() => { 46 | 47 | const d = Domain.create(); 48 | 49 | let first = true; 50 | const finish = (err: any, isTimeout: boolean) => { 51 | 52 | clearTimeout(to); 53 | 54 | if (err) { 55 | log.error(err); 56 | log.error('The following function experienced an error:', t); 57 | } 58 | 59 | if (isTimeout) { 60 | log.error('The following task timed out:', t); 61 | } 62 | 63 | if (first) { 64 | process.nextTick(() => { 65 | d.exit(); 66 | cb(err); 67 | }); 68 | } 69 | 70 | }; 71 | 72 | const to = setTimeout(() => { 73 | finish(new Error('timeout'), true); 74 | }, 5000); 75 | 76 | d.once('error', err => { 77 | log.error('Could not successfully run task:'); 78 | log.error(String(t)); 79 | finish(err, false); 80 | }); 81 | 82 | d.run(() => { 83 | t(r2gProject, function (err) { 84 | if (arguments.length > 1) { 85 | log.error('The following callback arguments were ignored:', Array.from(arguments).slice(0)); 86 | } 87 | finish(err, false); 88 | }); 89 | }); 90 | }); 91 | } 92 | 93 | }; 94 | 95 | export const run = (cwd: string, projectRoot: string, opts: any): void => { 96 | 97 | const userHome = path.resolve(process.env.HOME);`` 98 | 99 | let pkgJSON: any = null, r2gConf: any = null, 100 | packages: Packages = null, searchRoots: Array = null, 101 | pkgName = '', cleanPackageName = '', zTest: string = null; 102 | 103 | if (opts.skip) { 104 | const skipped = String(opts.skip).split(',').map(v => String(v || '').trim().toLowerCase()).filter(Boolean); 105 | opts.z = opts.z || skipped.includes('z'); 106 | opts.s = opts.s || skipped.includes('s'); 107 | opts.t = opts.t || skipped.includes('t'); 108 | } 109 | 110 | const pkgJSONOverridePth = path.resolve(projectRoot + '/.r2g/package.override.js'); 111 | const pkgJSONPth = path.resolve(projectRoot + '/package.json'); 112 | const customActionsPath = path.resolve(projectRoot + '/.r2g/custom.actions.js'); 113 | 114 | try { 115 | pkgJSON = require(pkgJSONPth); 116 | pkgName = pkgJSON.name; 117 | cleanPackageName = pkgJSON.name || ''; 118 | } catch (err) { 119 | log.error(chalk.magentaBright('Could not read your projects package.json file.')); 120 | throw getCleanTrace(err); 121 | } 122 | 123 | if (!(pkgName && typeof pkgName === 'string')) { 124 | throw new Error( 125 | 'Your package.json file does not appear to have a proper name field. Here is the file:\n' + util.inspect(pkgJSON) 126 | ); 127 | } 128 | 129 | interface CustomActionExports { 130 | default?: CustomActionExports, 131 | inCopyBeforeInstall: Array, 132 | inProjectBeforeInstall: Array, 133 | inCopyAfterInstall: Array, 134 | inProjectAfterInstall: Array, 135 | } 136 | 137 | type CustomAction = (root: string, cb: EVCb) => void; 138 | let customActions: CustomActionExports = null, customActionsStats = null; 139 | 140 | try { 141 | customActionsStats = fs.statSync(customActionsPath); 142 | } catch (err) { 143 | //ignore 144 | } 145 | 146 | try { 147 | if (customActionsStats) { 148 | customActions = require(customActionsPath); 149 | customActions = customActions.default || customActions; 150 | assert(customActions, 'custom.actions.js is missing certain exports.'); 151 | } 152 | } catch (err) { 153 | log.error('Could not load custom.actions.js'); 154 | log.error(err.message); 155 | process.exit(1); 156 | } 157 | 158 | let packageJSONOverride: any = null, packageJSONOverrideStats = null; 159 | 160 | try { 161 | packageJSONOverrideStats = fs.statSync(pkgJSONOverridePth); 162 | } catch (err) { 163 | // ignore 164 | } 165 | 166 | try { 167 | if (packageJSONOverrideStats) { 168 | assert(packageJSONOverrideStats.isFile(), 'package.override.js should be a file, but it is not.'); 169 | packageJSONOverride = require(pkgJSONOverridePth); 170 | packageJSONOverride = packageJSONOverride.default || packageJSONOverride; 171 | assert(packageJSONOverride && !Array.isArray(packageJSONOverride) && typeof packageJSONOverride === 'object', 172 | 'package.override.js does not export the right object.'); 173 | } 174 | } catch (err) { 175 | log.error('Could not run stat on file at path:', pkgJSONOverridePth); 176 | log.error(err.message); 177 | process.exit(1); 178 | } 179 | 180 | try { 181 | zTest = pkgJSON.r2g.test; 182 | } catch (err) { 183 | if (!opts.z) { 184 | log.info('using "npm test" to run phase-Z.'); 185 | } 186 | } 187 | 188 | zTest = zTest || 'npm test'; 189 | assert(typeof zTest === 'string', 'z-test is not a string => check the r2g.test property in your package.json.'); 190 | 191 | pkgName = String(pkgName).replace(/[^0-9a-z]/gi, '_'); 192 | 193 | if (pkgName.startsWith('_')) { 194 | pkgName = pkgName.slice(1); 195 | } 196 | 197 | const confPath = path.resolve(projectRoot + '/.r2g/config.js'); 198 | 199 | try { 200 | r2gConf = require(confPath); 201 | r2gConf = r2gConf.default || r2gConf; 202 | } catch (err) { 203 | 204 | if (opts.verbosity > 0) { 205 | log.warning(err.message); 206 | } 207 | 208 | log.warning(chalk.yellow('Could not read your .r2g/config.js file at path:', chalk.bold(confPath))); 209 | 210 | if (process.env.r2g_is_docker === 'yes') { 211 | throw getCleanTrace(err); 212 | } 213 | 214 | r2gConf = require('../../../assets/default.r2g.config.js'); 215 | r2gConf = r2gConf.default || r2gConf; 216 | 217 | process.once('exit', code => { 218 | if (code < 1) { 219 | if (opts.verbosity > 2) { 220 | log.warning(chalk.yellow.bold('Note that during this run, r2g could not read your .r2g/config.js file.')) 221 | } 222 | } 223 | }); 224 | 225 | } 226 | 227 | packages = r2gConf.packages; 228 | 229 | searchRoots = flattenDeep([r2gConf.searchRoots, path.resolve(r2gConf.searchRoot || '')]) 230 | .map(v => String(v || '').trim()) 231 | .filter(Boolean); 232 | 233 | if (!(packages && typeof packages === 'object')) { 234 | log.error(r2gConf); 235 | throw new Error('You need a property called "packages" in your .r2g/config.js file.'); 236 | } 237 | 238 | searchRoots.forEach(v => { 239 | 240 | if (!(v && typeof v === 'string')) { 241 | log.error(r2gConf); 242 | throw chalk.redBright('You need a property called "searchRoot"/"searchRoots" in your .r2g/config.js file.'); 243 | } 244 | 245 | if (!path.isAbsolute(v)) { 246 | log.error(r2gConf); 247 | log.error('The following path is not absolute:', chalk.magenta(v)); 248 | throw chalk.redBright('Your "searchRoot"/"searchRoots" property in your .r2g/config.js file, needs to be an absolute path.'); 249 | } 250 | 251 | try { 252 | assert(fs.lstatSync(v).isDirectory()); 253 | } catch (err) { 254 | log.error('Your "searchRoot" property does not seem to exist as a directory on the local/host filesystem.'); 255 | log.error('In other words, the following path does not seem to be a directory:'); 256 | log.error(v); 257 | throw getCleanTrace(err); 258 | } 259 | 260 | if (!v.startsWith(userHome)) { 261 | throw new Error('Your searchRoot needs to be within your user home directory.'); 262 | } 263 | 264 | }); 265 | 266 | const dependenciesToInstall = Object.keys(packages); 267 | if (dependenciesToInstall.length < 1) { 268 | log.debug('There were no local dependencies to install.'); 269 | log.debug('Here is your configuration:\n', r2gConf); 270 | } 271 | 272 | const deps = [ 273 | pkgJSON.dependencies || {}, 274 | pkgJSON.devDependencies || {}, 275 | pkgJSON.optionalDependencies || {} 276 | ]; 277 | 278 | const allDeps = deps.reduce(Object.assign, {}); 279 | 280 | Object.keys(packages).forEach(function (k) { 281 | if (!allDeps[k]) { 282 | log.warn(chalk.gray('You have the following packages key in your .r2g/config.js file:'), chalk.magentaBright(k)); 283 | log.warn(chalk.bold(`But "${chalk.magentaBright(k)}" is not present as a dependency in your package.json file.`)); 284 | } 285 | }); 286 | 287 | const depsDir = path.resolve(process.env.HOME + `/.r2g/temp/deps`); 288 | 289 | 290 | async.autoInject({ 291 | 292 | checkForUntrackedFiles(cb: EVCb) { 293 | 294 | if (opts.ignore_dirty_git_index) { 295 | return process.nextTick(cb); 296 | } 297 | 298 | log.info('Checking for dirty git status...'); 299 | 300 | const k = cp.spawn('bash'); 301 | k.stderr.pipe(process.stderr); 302 | 303 | k.stdin.end(` 304 | set -e; 305 | cd ${projectRoot}; 306 | if [[ ! -d '.git' ]]; then 307 | exit 0; 308 | fi 309 | 310 | if test $(git status --porcelain | wc -l) != '0'; then 311 | exit 1; 312 | fi 313 | `); 314 | 315 | k.once('exit', code => { 316 | if (code > 0) { 317 | log.error('Changes to (untracked) files need to be committed. Check your git index using the `git status` command.'); 318 | log.error('Looks like the git index was dirty. Use "--ignore-dirty-git-index" to skip this warning.'); 319 | } 320 | cb(code); 321 | }); 322 | }, 323 | 324 | checkForDirtyGitIndex(checkForUntrackedFiles: any, cb: EVCb) { 325 | 326 | if (opts.ignore_dirty_git_index) { 327 | return process.nextTick(cb); 328 | } 329 | 330 | log.info('Checking for dirty git index...'); 331 | const k = cp.spawn('bash'); 332 | k.stderr.pipe(process.stderr); 333 | 334 | k.stdin.end(` 335 | set -e; 336 | cd ${projectRoot}; 337 | if [[ -d '.git' ]]; then 338 | git diff --quiet 339 | fi 340 | `); 341 | 342 | k.once('exit', code => { 343 | if (code > 0) { 344 | log.error('Looks like the git index was dirty. Use "--ignore-dirty-git-index" to skip this warning.'); 345 | } 346 | cb(code); 347 | }); 348 | 349 | }, 350 | 351 | removeExistingProject(checkForDirtyGitIndex: any, cb: EVCb) { 352 | 353 | if (opts.keep || opts.multi) { 354 | log.info("We are keeping the previously installed packages because '--keep' / '--multi' was used."); 355 | return process.nextTick(cb); 356 | } 357 | 358 | log.info('Removing existing files within "$HOME/.r2g/temp"...'); 359 | const k = cp.spawn('bash'); 360 | k.stdin.end(`rm -rf "$HOME/.r2g/temp"`); 361 | k.once('exit', cb); 362 | 363 | }, 364 | 365 | mkdirpProject(removeExistingProject: any, cb: EVCb) { 366 | 367 | log.info('Making sure the right folders exist using mkdir -p ...'); 368 | const k = cp.spawn('bash'); 369 | k.stderr.pipe(process.stderr); 370 | k.stdin.end(`mkdir -p "${r2gProject}"; mkdir -p "${r2gProjectCopy}";`); 371 | k.once('exit', code => { 372 | if (code > 0) { 373 | log.error("Could not create temp/project or temp/copy directory."); 374 | } 375 | cb(code); 376 | }); 377 | 378 | }, 379 | 380 | rimrafDeps(mkdirpProject: any, cb: EVCb) { 381 | 382 | log.info('Removing existing files within "$HOME/.r2g.temp"...'); 383 | const k = cp.spawn('bash'); 384 | k.stdin.end(`rm -rf "${depsDir}"`); 385 | k.once('exit', cb); 386 | 387 | }, 388 | 389 | mkdirDeps(rimrafDeps: any, cb: EVCb) { 390 | 391 | log.info('Re-creating folders "$HOME/.r2g/temp"...'); 392 | const k = cp.spawn('bash'); 393 | k.stdin.end(`mkdir -p "${depsDir}"`); 394 | k.once('exit', cb); 395 | 396 | }, 397 | 398 | getMap(cb: EVCb) { 399 | 400 | if (!opts.full) { 401 | log.info('we are not creating a deps map since the --full option was not used.'); 402 | return process.nextTick(cb, null, {}); 403 | } 404 | 405 | if (process.env.r2g_is_docker === 'yes') { 406 | log.info('we are not creating a deps map since we are using r2g.docker'); 407 | return process.nextTick(cb, null, {}); 408 | } 409 | 410 | getFSMap(opts, searchRoots, packages, cb); 411 | }, 412 | 413 | copyProjectsInMap(getMap: any, cb: EVCb) { 414 | 415 | if (Object.keys(getMap).length < 1) { 416 | return process.nextTick(cb, null, {}); 417 | } 418 | 419 | installDeps(getMap, dependenciesToInstall, opts, cb); 420 | }, 421 | 422 | renamePackagesToAbsolute(copyProjectsInMap: any, copyProject: any, cb: EVCb) { 423 | 424 | if (Object.keys(copyProjectsInMap).length < 1) { 425 | return process.nextTick(cb, null, {}); 426 | } 427 | 428 | const pkgJSONPath = path.resolve(copyProject + '/package.json'); 429 | renameDeps(copyProjectsInMap, pkgJSONPath, cb); 430 | }, 431 | 432 | copyProject(mkdirpProject: any, cb: EVCb) { 433 | 434 | if (process.env.r2g_is_docker === 'yes') { 435 | log.info('We are not copying the project since we are using r2g.docker'); 436 | return process.nextTick(cb, null, projectRoot); 437 | } 438 | 439 | log.info('Copying your project to "$HOME/.r2g/temp/copy" using rsync ...'); 440 | 441 | const k = cp.spawn('bash'); 442 | k.stderr.pipe(process.stderr); 443 | k.stdin.end(` 444 | rm -rf "${r2gProjectCopy}"; 445 | rsync --perms --copy-links -r --exclude=".git" --exclude="node_modules" "${projectRoot}" "${r2gProjectCopy}"; 446 | `); 447 | 448 | k.once('exit', code => { 449 | if (code > 0) { 450 | log.error('Could not rimraf project copy path or could not copy to it using rsync.'); 451 | } 452 | cb(code, path.resolve(r2gProjectCopy + '/' + path.basename(projectRoot))); 453 | }); 454 | 455 | }, 456 | 457 | runNpmPack(renamePackagesToAbsolute: any, copyProject: string, cb: EVCb) { 458 | 459 | const cmd = `npm pack --loglevel=warn;`; 460 | log.info(chalk.bold('Running the following command from your project copy root:'), chalk.cyan.bold(cmd)); 461 | 462 | const k = cp.spawn('bash'); 463 | k.stdin.end(`cd "${copyProject}" && ` + cmd); 464 | let stdout = ''; 465 | k.stdout.on('data', d => { 466 | stdout += String(d || '').trim(); 467 | }); 468 | k.stderr.pipe(process.stderr); 469 | k.once('exit', code => { 470 | if (code > 0) { 471 | log.error(`Could not run "npm pack" for this project => ${copyProject}.`); 472 | } 473 | cb(code, path.resolve(copyProject + '/' + stdout)); 474 | }); 475 | }, 476 | 477 | linkPackage(runNPMInstallInCopy: any, copyProject: string, cb: EVCb) { 478 | 479 | if (opts.z) { 480 | return process.nextTick(cb); 481 | } 482 | 483 | const getBinMap = function (bin: string | BinFieldObject, path: string, name: string) { 484 | 485 | if (!bin) { 486 | return ` echo "no bin items in package.json for package with name: ${name}" `; 487 | } 488 | 489 | if (typeof bin === 'string') { 490 | return ` mkdir -p "node_modules/.bin" && ln -s "${path}/${bin}" "node_modules/.bin/${name}" ` 491 | } 492 | 493 | const keys = Object.keys(bin); 494 | 495 | if (keys.length < 1) { 496 | return ` echo "no bin items in package.json for package with name: ${name}" `; 497 | } 498 | 499 | return keys.map(function (k) { 500 | return ` mkdir -p node_modules/.bin && ln -sf "${path}/${bin[k]}" "node_modules/.bin/${k}" ` 501 | }) 502 | .join(' && '); 503 | }; 504 | 505 | const cmd = [ 506 | `mkdir -p "node_modules/${cleanPackageName}"`, 507 | `rm -rf "node_modules/${cleanPackageName}"`, 508 | `ln -sf "${r2gProject}/node_modules/${cleanPackageName}" "node_modules/${cleanPackageName}"`, 509 | // `rsync -r "${r2gProject}/node_modules/${cleanPackageName}" "node_modules"`, 510 | getBinMap(pkgJSON.bin, `${copyProject}/node_modules/${cleanPackageName}`, cleanPackageName) 511 | ] 512 | .join(' && '); 513 | 514 | const cwd = String(copyProject).slice(0); 515 | log.info(chalk.bold(`Running the following command from "${cwd}":`), chalk.bold.cyan(cmd)); 516 | 517 | const k = cp.spawn('bash'); 518 | 519 | k.stderr.pipe(process.stderr); 520 | k.stdin.end(`cd "${cwd}" && ` + cmd); 521 | 522 | k.once('exit', code => { 523 | if (code > 0) { 524 | log.error('Could not link from project to copy.'); 525 | } 526 | cb(code); 527 | }); 528 | 529 | }, 530 | 531 | runNPMInstallInCopy(runNpmInstall: any, copyProject: string, cb: EVCb) { 532 | 533 | if (opts.z) { 534 | return process.nextTick(cb); 535 | } 536 | 537 | const cmd = `npm install --cache-min 9999999 --loglevel=warn`; 538 | log.info(`Running "${cmd}" in project copy.`); 539 | 540 | const k = cp.spawn('bash'); 541 | k.stderr.pipe(process.stderr); 542 | k.stdin.end(`cd "${copyProject}" && ` + cmd); 543 | 544 | k.once('exit', code => { 545 | if (code > 0) { 546 | log.error('Could not link from project to copy.'); 547 | } 548 | cb(code); 549 | }); 550 | 551 | }, 552 | 553 | runZTest(linkPackage: any, copyProject: string, cb: EVCb) { 554 | 555 | if (opts.z) { 556 | log.warn('Skipping phase-Z'); 557 | return process.nextTick(cb); 558 | } 559 | 560 | const cmd = String(zTest).slice(0); 561 | 562 | log.info(chalk.bold('Running the following command from the copy project dir:'), chalk.cyan.bold(cmd)); 563 | 564 | const k = cp.spawn('bash', [], { 565 | env: Object.assign({}, process.env, { 566 | PATH: path.resolve(copyProject + '/node_modules/.bin') + ':' + process.env.PATH 567 | }) 568 | }); 569 | 570 | k.stdin.end(`cd "${copyProject}" && ${cmd}`); 571 | k.stdout.pipe(pt(chalk.gray('phase-Z: '))).pipe(process.stdout); 572 | k.stderr.pipe(pt(chalk.yellow('phase-Z: '))).pipe(process.stderr); 573 | 574 | k.once('exit', code => { 575 | if (code > 0) { 576 | log.error(`Could not run your z-test command: ${cmd}`); 577 | } 578 | cb(code); 579 | }); 580 | 581 | }, 582 | 583 | copySmokeTester(mkdirpProject: any, cb: EVCb) { 584 | 585 | if (opts.s) { 586 | return process.nextTick(cb); 587 | } 588 | 589 | log.info(`Copying the smoke-tester.js file to "${r2gProject}" ...`); 590 | 591 | fs.createReadStream(smokeTester) 592 | .pipe(fs.createWriteStream(path.resolve(r2gProject + '/smoke-tester.js'))) 593 | .once('error', cb) 594 | .once('finish', cb); 595 | }, 596 | 597 | copyPackageJSON(mkdirpProject: any, cb: EVCb) { 598 | 599 | if (opts.keep) { 600 | log.warn(`Re-using the existing package.json file at path '${r2gProject}/package.json'...`); 601 | return process.nextTick(cb); 602 | } 603 | 604 | log.info(`Copying package.json file to "${r2gProject}" ...`); 605 | 606 | const defaultPkgJSON = require(defaultPackageJSONPath); 607 | const packageJSONPath = path.resolve(r2gProject + '/package.json'); 608 | 609 | 610 | let override = null; 611 | 612 | if (packageJSONOverride) { 613 | override = deepMixin(defaultPkgJSON, packageJSONOverride); 614 | log.warning('package.json overriden with:', {override}); 615 | } else { 616 | override = Object.assign({}, defaultPkgJSON); 617 | } 618 | 619 | const strm = fs.createWriteStream(packageJSONPath) 620 | .once('error', cb) 621 | .once('finish', cb); 622 | 623 | strm.end(JSON.stringify(override, null, 2) + '\n'); 624 | 625 | }, 626 | 627 | customActionsBeforeInstall(copyPackageJSON: any, cb: EVCb) { 628 | 629 | if (!customActions) { 630 | log.info('No custom actions registered. Use custom.actions.js to add custom actions.'); 631 | return process.nextTick(cb); 632 | } 633 | 634 | log.info('Running custom actions...'); 635 | 636 | async.series({ 637 | inProject(cb) { 638 | 639 | const tasks = flattenDeep([customActions.inProjectBeforeInstall]).filter(Boolean); 640 | 641 | if (tasks.length < 1) { 642 | return process.nextTick(cb); 643 | } 644 | 645 | async.eachSeries(tasks, handleTask(r2gProject), cb); 646 | 647 | } 648 | }, 649 | cb); 650 | 651 | }, 652 | 653 | runNpmInstall(copyPackageJSON: any, customActionsBeforeInstall: any, runNpmPack: string, cb: EVCb) { 654 | 655 | if (opts.t && opts.s) { 656 | return process.nextTick(cb); 657 | } 658 | 659 | // note that runNpmPack is the path to .tgz file 660 | const cmd = `npm install --loglevel=warn --cache-min 9999999 --no-optional --production "${runNpmPack}";\n` + 661 | `npm i --loglevel=warn --cache-min 9999999 --no-optional --production;`; 662 | 663 | log.info(`Running the following command via this dir: "${r2gProject}" ...`); 664 | log.info(chalk.blueBright(cmd)); 665 | 666 | const k = cp.spawn('bash'); 667 | k.stdin.end(`cd "${r2gProject}" && ` + cmd); 668 | k.stderr.pipe(process.stderr); 669 | k.once('exit', code => { 670 | if (code > 0) { 671 | log.error(`Could not run the following command: ${cmd}.`); 672 | } 673 | cb(code); 674 | }); 675 | }, 676 | 677 | customActionsAfterInstall(runNpmInstall: any, cb: EVCb) { 678 | 679 | if (!customActions) { 680 | log.info('No custom actions registered. Use custom.actions.js to add custom actions.'); 681 | return process.nextTick(cb); 682 | } 683 | 684 | log.info('Running custom actions...'); 685 | 686 | async.series({ 687 | inProject(cb) { 688 | 689 | const tasks = flattenDeep([customActions.inProjectAfterInstall]).filter(Boolean); 690 | 691 | if (tasks.length < 1) { 692 | return process.nextTick(cb); 693 | } 694 | 695 | async.eachSeries(tasks, handleTask(r2gProject), cb); 696 | 697 | } 698 | }, 699 | cb); 700 | 701 | }, 702 | 703 | r2gSmokeTest(runZTest: any, customActionsAfterInstall: any, copySmokeTester: any, cb: EVCb) { 704 | 705 | if (opts.s) { 706 | log.warn('Skipping phase-S.'); 707 | return process.nextTick(cb); 708 | } 709 | 710 | log.info(`Running your exported r2gSmokeTest function(s) in "${r2gProject}" ...`); 711 | 712 | const k = cp.spawn('bash', [], { 713 | env: Object.assign({}, process.env, { 714 | PATH: path.resolve(r2gProject + '/node_modules/.bin') + ':' + process.env.PATH 715 | }) 716 | }); 717 | 718 | k.stderr.pipe(pt(chalk.yellow('phase-S: '))).pipe(process.stderr); 719 | k.stdout.pipe(pt(chalk.gray('phase-S: '))).pipe(process.stdout); 720 | 721 | k.stdin.end(`cd "${r2gProject}" && node smoke-tester.js;`); 722 | k.once('exit', code => { 723 | if (code > 0) { 724 | log.error('r2g smoke test failed => one of your exported r2gSmokeTest function calls failed to resolve to true.'); 725 | log.error(chalk.magenta('for help fixing this error, see: https://github.com/ORESoftware/r2g/blob/master/docs/r2g-smoke-test-exported-main-fn-type-a.md')); 726 | } 727 | cb(code); 728 | }); 729 | }, 730 | 731 | copyUserDefinedTests(copyProject: string, cb: EVCb) { 732 | 733 | if (opts.t) { 734 | return process.nextTick(cb); 735 | } 736 | 737 | log.info(`Copying your user defined tests to: "${r2gProject}" ...`); 738 | 739 | const k = cp.spawn('bash'); 740 | k.stdout.pipe(process.stdout); 741 | k.stderr.pipe(process.stderr); 742 | 743 | k.stdin.end([ 744 | `cd "${copyProject}"`, 745 | `mkdir -p .r2g/tests`, 746 | `mkdir -p .r2g/fixtures`, 747 | `rsync --perms --copy-links -r .r2g/tests "${r2gProject}"`, 748 | `rsync --perms --copy-links -r .r2g/fixtures "${r2gProject}"` 749 | ].join(' && ')); 750 | 751 | k.once('exit', cb); 752 | 753 | }, 754 | 755 | runUserDefinedTests(copyUserDefinedTests: any, r2gSmokeTest: any, runNpmInstall: any, cb: EVCb) { 756 | 757 | if (opts.t) { 758 | log.warn('Skipping phase-T'); 759 | return process.nextTick(cb); 760 | } 761 | 762 | log.info(`Running user defined tests in "${r2gProject}/tests" ...`); 763 | 764 | const k = cp.spawn('bash', [], { 765 | env: Object.assign({}, process.env, { 766 | PATH: path.resolve(r2gProject + '/node_modules/.bin') + ':' + process.env.PATH 767 | }) 768 | }); 769 | 770 | k.stdout.pipe(pt(chalk.gray('phase-T: '))).pipe(process.stdout); 771 | k.stderr.pipe(pt(chalk.yellow('phase-T: '))).pipe(process.stderr); 772 | 773 | k.once('exit', code => { 774 | if (code > 0) { 775 | log.error('an r2g test failed => a script in this dir failed to exit with code 0:', chalk.bold(path.resolve(process.env.HOME + '/.r2g/temp/project/tests'))); 776 | log.error(chalk.magenta('for help fixing this error, see: https://github.com/ORESoftware/r2g/blob/master/docs/r2g-smoke-test-type-b.md')); 777 | } 778 | cb(code); 779 | }); 780 | 781 | const tests = path.resolve(r2gProject + '/tests'); 782 | 783 | let items: Array; 784 | try { 785 | 786 | items = fs.readdirSync(tests); 787 | 788 | if (false) { 789 | const cmd = ` set -e;\n cd "${r2gProject}";\n echo 'Now we are in phase-T...'; \n` + 790 | items 791 | // .map(v => path.resolve()) 792 | .filter(v => fs.lstatSync(tests + '/' + v).isFile()) 793 | .map(v => ` chmod u+x ./tests/${v} && ./tests/${v}; `) 794 | .join('\n'); 795 | // .concat(' exit "$?" ').join('\n'); 796 | 797 | log.info('About to run tests in your .r2g/tests dir, the command is:'); 798 | log.info(chalk.blueBright(cmd)); 799 | k.stdin.end(cmd); 800 | } 801 | 802 | } catch (err) { 803 | return process.nextTick(cb, err); 804 | } 805 | 806 | // return; 807 | 808 | const cmd = items.filter(v => { 809 | try { 810 | return fs.lstatSync(path.resolve(tests + '/' + v)).isFile() 811 | } catch (err) { 812 | log.error(err.message); 813 | return false; 814 | } 815 | 816 | }) 817 | .map(v => ` ( echo 'running test' && chmod u+x './tests/${v}' && './tests/${v}' ) | r2g_handle_stdio '${v}' ; `) 818 | .join(' '); 819 | 820 | log.info('About to run tests in your .r2g/tests dir.'); 821 | k.stdin.end(` 822 | 823 | set -eo pipefail 824 | 825 | echo 'Now we are in phase-T...'; 826 | 827 | r2g_handle_stdio() { 828 | # REPLY is a built-in, see: 829 | while read; do echo -e "\${r2g_gray}$1:\${r2g_no_color} $REPLY"; done 830 | } 831 | 832 | export -f r2g_handle_stdio; 833 | 834 | cd "${r2gProject}"; 835 | ${cmd} 836 | 837 | `); 838 | 839 | } 840 | 841 | }, 842 | 843 | (err: any, results) => { 844 | 845 | if (err && err.OK) { 846 | log.warn(chalk.blueBright(' => r2g may have run with some problems.')); 847 | log.warn(util.inspect(err)); 848 | } else if (err) { 849 | throw getCleanTrace(err); 850 | } else { 851 | log.info(chalk.green('Successfully ran r2g.')) 852 | } 853 | 854 | process.exit(0); 855 | 856 | }); 857 | 858 | }; 859 | 860 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | 4 | export type EVCb = (err: E, T?: any) => void; 5 | 6 | export const r2gSmokeTest = function () { 7 | return true; 8 | }; 9 | -------------------------------------------------------------------------------- /src/logger.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import chalk from "chalk"; 4 | 5 | const isDebug = process.env.r2g_is_debug === 'yes'; 6 | 7 | export const log = { 8 | info: console.log.bind(console), 9 | warning: console.error.bind(console, chalk.bold.yellow.bold('r2g warn:')), 10 | warn: console.error.bind(console, chalk.bold.magenta.bold('r2g warn:')), 11 | error: console.error.bind(console, chalk.redBright.bold('r2g error:')), 12 | debug: function (...args: any[]) { 13 | isDebug && console.log(chalk.yellowBright('r2g debug:'), ...arguments); 14 | } 15 | }; 16 | 17 | export default log; 18 | -------------------------------------------------------------------------------- /src/smoke-tester.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | import fs = require('fs'); 5 | import path = require('path'); 6 | import * as util from "util"; 7 | import * as assert from "assert"; 8 | 9 | //////////////////////////////////////////////////////////// 10 | 11 | const colors = <{ [key: string]: [number, number] }>{ 12 | 'bold': [1, 22], 13 | 'italic': [3, 23], 14 | 'underline': [4, 24], 15 | 'inverse': [7, 27], 16 | 'white': [37, 39], 17 | 'grey': [90, 39], 18 | 'black': [30, 39], 19 | 'blue': [34, 39], 20 | 'cyan': [36, 39], 21 | 'green': [32, 39], 22 | 'magenta': [35, 39], 23 | 'red': [31, 39], 24 | 'yellow': [33, 39] 25 | }; 26 | 27 | const stylize = (color: string, str: string) => { 28 | const [start, end] = colors[color]; 29 | return `\u001b[${start}m${str}\u001b[${end}m`; 30 | }; 31 | 32 | ///////////////////// 33 | 34 | process.chdir(__dirname); 35 | const nm = path.resolve(__dirname + '/node_modules'); 36 | const pkgJSON = require(__dirname + '/package.json'); 37 | const deps = Object.assign({}, pkgJSON.dependencies || {}, pkgJSON.devDependencies || {}); 38 | const links = Object.keys(deps); 39 | 40 | if (links.length < 1) { 41 | throw new Error(stylize('red', 'no requireable packages in package.json to smoke test with r2g.')); 42 | } 43 | 44 | const getAllPromises = (links: Array) => { 45 | return Promise.all(links.map(l => { 46 | 47 | let mod: any; 48 | 49 | try { 50 | console.log('loading the following module:', l); 51 | mod = require(l); 52 | } 53 | catch (err) { 54 | 55 | if (new RegExp(l).test(err.message)) { 56 | console.error(stylize('red', 'Could not load your package with name:'), stylize('bold', l)); 57 | console.error(stylize('red', 'Because your module could not be loaded, it is likely that you have not built/compiled your project, or that your package.json name/main field is incorrect.')); 58 | } 59 | else { 60 | console.error(stylize('red', 'You may have a missing dependency in your project, or a dependency that should be in "dependencies" not in "devDependencies".')); 61 | } 62 | throw err; 63 | } 64 | 65 | try { 66 | assert.equal(typeof mod.r2gSmokeTest, 'function'); 67 | } 68 | catch (err) { 69 | console.error(stylize('red', 'A module failed to export a function from "main" with key "r2gSmokeTest".')); 70 | console.error('The module/package missing this export has the following name:'); 71 | console.error(stylize('red', l)); 72 | throw err; 73 | } 74 | 75 | return Promise.resolve(mod.r2gSmokeTest()).then((v: any) => { 76 | console.log('resolved result for:', l, 'result is:', v); 77 | return {path: l, result: v}; 78 | }); 79 | })); 80 | }; 81 | 82 | getAllPromises(links) 83 | .then(results => { 84 | 85 | console.log('This many packages were tested:', results.length); 86 | 87 | const failures = results.filter(v => { 88 | return !(v && v.result === true); 89 | }); 90 | 91 | if (failures.length > 0) { 92 | console.error('At least one exported "r2gSmokeTest" function failed.'); 93 | throw new Error(util.inspect(failures, {breakLength: Infinity})); 94 | } 95 | 96 | console.log('Your exported r2gSmokeTest function(s) have all passed'); 97 | process.exit(0); 98 | 99 | }) 100 | .catch(err => { 101 | console.error(err); 102 | process.exit(1); 103 | }); 104 | 105 | 106 | -------------------------------------------------------------------------------- /test/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ORESoftware/r2g/2f20124bfcd9613230023c9d10476f7251ae9a23/test/.gitkeep -------------------------------------------------------------------------------- /test/foo.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | #if shopt -qo errexit; then 4 | # echo enabled 5 | # # do something 6 | #fi 7 | 8 | # 9 | #exec > >( while read line; do echo " stdout: $line"; done ) 10 | #exec 2> >( while read line; do echo " stderr: $line"; done ) 11 | # 12 | ## echo "rolo" 13 | #echo "cholo" >&2 14 | 15 | 16 | t=`npm pack`; 17 | wc -c ${t}; 18 | tar tvf ${t}; 19 | #rm "${t}"; 20 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | // const {x} = require('../dist'); 2 | // 3 | // console.log('this is the end.'); 4 | // 5 | // x().then(function () { 6 | // console.log('no error yet.'); 7 | // process.exit(0); 8 | // }) 9 | // .catch(function (err) { 10 | // console.error('this was caught.', err); 11 | // process.exit(0); 12 | // }); 13 | // 14 | 15 | 16 | // Object.prototype.getCleanStack = function(){ 17 | // return 'foo'; 18 | // }; 19 | // 20 | // 21 | // console.log(typeof Object('age').getCleanStack()); 22 | 23 | const d = Date.now(); 24 | 25 | console.log(d); 26 | console.log(String(d).slice(0,-3)); 27 | -------------------------------------------------------------------------------- /test/index.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | #npm install async 4 | 5 | set -e; 6 | 7 | file="/host_user_home/WebstormProjects/oresoftware/clean-trace"; 8 | npm install "$file" 9 | 10 | modify.json --disk -f package.json -x "dependencies.clean-trace" -z `cat </dev/stdout # persistent file descriptor 5 | 6 | 7 | 8 | #!/bin/bash 9 | foo(){ 10 | my_args_array=("$@") 11 | export my_args="${my_args_array[@]}" 12 | node test/x.js # "${my_args[@]}" 13 | # bar "${my_args[@]}" 14 | } 15 | 16 | bar(){ 17 | echo "number of args: $#"; 18 | echo "args: $@" 19 | node test/x.js "$@" 20 | } 21 | 22 | 23 | foo a b 'c d e' 24 | 25 | 26 | -------------------------------------------------------------------------------- /test/test.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | // export class Foo { 5 | // aField = 'abc'; 6 | // aMethod = () => 42; 7 | // } 8 | // 9 | // const acceptsFooArg = (f: Foo) => { 10 | // return f.aMethod(); 11 | // }; 12 | // 13 | // acceptsFooArg(new Foo()); 14 | // acceptsFooArg({aField: 'abcd', aMethod: () => 2}); 15 | // 16 | // 17 | // const v = acceptsFooArg(new Foo()); 18 | 19 | 20 | const v = {zoom:'bar'}; 21 | console.log({...v, zoom:3}); 22 | -------------------------------------------------------------------------------- /test/x.js: -------------------------------------------------------------------------------- 1 | 2 | // const args = process.argv.slice(2); 3 | // console.log(args.length); 4 | // console.log(args); 5 | // 6 | // console.log('my args:',process.env.my_args); 7 | // 8 | // exports.foo = 'bar'; 9 | // 10 | // setTimeout(function () { 11 | // console.log(require(__filename).foo); 12 | // },100); 13 | 14 | 15 | const double = function(val, count){ 16 | if(count > 0){ 17 | return double(2*val,--count); 18 | } 19 | return val; 20 | }; 21 | 22 | 23 | console.log(double(0.004, 100)); 24 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "dist", 4 | "resolveJsonModule": true, 5 | "allowJs": false, 6 | "pretty": true, 7 | "skipLibCheck": true, 8 | "declaration": true, 9 | "baseUrl": ".", 10 | "target": "es6", 11 | "module": "commonjs", 12 | "noImplicitAny": true, 13 | "removeComments": true, 14 | "allowUnreachableCode": true, 15 | "lib": [ 16 | "es2015", 17 | "es2016", 18 | "es2017" 19 | ] 20 | }, 21 | "compileOnSave": false, 22 | "include": [ 23 | "src" 24 | ] 25 | } 26 | --------------------------------------------------------------------------------