Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
18deca4
chore: add prettier style as usual for graasp apps
Jul 9, 2020
d248ef0
feat: add basic new command
Jul 9, 2020
70c76dc
feat: load dev env and deploy to aws
Jul 10, 2020
305efa0
refactor: rm test file and rm obsolet promisify()
Jul 10, 2020
53925c3
refactor: remove accidentally added bash script
Jul 10, 2020
68fb33b
feat: add options and validation
Jul 10, 2020
00f48a7
feat: invalidate cache after deployment
Jul 10, 2020
8ba8262
fix: allow console output in eslint
Jul 11, 2020
96d3531
feat: add a fancy progress bar
Jul 11, 2020
7ac7faa
fix: tag validation and accidental async
Jul 11, 2020
dc383ad
chore: add comment to RegExp and backshlashes
Jul 11, 2020
c37afb8
refactor: rename varIsDefined() to isDefined()
Jul 11, 2020
305cb3b
refactor: remove unused env varas from import
Jul 11, 2020
19eb223
refactor: replace unnamed func with arrow func
Jul 11, 2020
820b5f5
fix: minor changes based on feedback on pr
Jul 11, 2020
ad0f6f6
chore: remove obsolete comment
Jul 11, 2020
5687b3b
refactor: factor validation out
Jul 11, 2020
cb5650a
fix: use proper tag validation
Jul 17, 2020
6b38e41
chore: add explanation to regexp
Jul 17, 2020
f4b79ef
fix: remove default env
Jul 17, 2020
3cf0366
refactor: replace isdefined() with lodash
Jul 17, 2020
e85ac36
chore: enable linting warning for console log
Jul 18, 2020
baaa792
Merge branch 'master' of github.com:graasp/graasp-cli into deploy-cmd
Jul 26, 2020
2338ead
chore: try out promisify
Jul 26, 2020
4491092
chore: force commit with eslint errors
Jul 26, 2020
62ea0f8
feat: load sync aws credentials
Jul 26, 2020
89e8821
chore: update flag descriptions
Jul 26, 2020
7a288e8
chore: assemble error outputs in together
Jul 26, 2020
28a0dc5
chore: remove obsolete default env variable
Jul 26, 2020
cfa7135
Merge pull request #45 from graasp/promisify
ugGit Jul 26, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"extends": "airbnb-base",
"extends": ["airbnb-base", "prettier"],
"env": {
"node": true,
"mocha": true
Expand All @@ -9,9 +9,7 @@
"no-underscore-dangle": [
"error",
{
"allow": [
"_id"
]
"allow": ["_id"]
}
],
"import/no-named-as-default": 0
Expand Down
6 changes: 6 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"trailingComma": "all",
"tabWidth": 2,
"semi": true,
"singleQuote": true
}
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,21 +48,25 @@
"babel-eslint": "10.1.0",
"eslint": "6.8.0",
"eslint-config-airbnb-base": "14.1.0",
"eslint-config-prettier": "6.11.0",
"eslint-plugin-import": "2.20.1",
"husky": "4.2.3",
"npm-run-all": "4.1.5",
"standard-version": "7.1.0"
},
"dependencies": {
"@babel/polyfill": "7.8.7",
"aws-sdk": "2.713.0",
"bson-objectid": "1.3.0",
"del": "4.1.1",
"dotenv": "8.2.0",
"execa": "1.0.0",
"fs-exists-cached": "1.0.0",
"fs-extra": "7.0.1",
"hosted-git-info": "2.7.1",
"inquirer": "6.2.2",
"lodash": "4.17.15",
"s3-node-client": "4.4.4",
"yargs": "12.0.5"
}
}
63 changes: 36 additions & 27 deletions src/createCli.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,8 @@
import yargs from 'yargs';
import prompt from './prompt';
import deploy from './deploy';
import { DEFAULT_STARTER } from './config';

const promisify = (fn) => (...args) => {
Promise.resolve(fn(...args)).then(
() => process.exit(0),
// err => report.panic(err)
);
};

const createCli = (argv) => {
const cli = yargs();

Expand All @@ -33,25 +27,41 @@ const createCli = (argv) => {
.command({
command: 'new',
desc: 'Create new Graasp app.',
builder: (_) => _.option('s', {
alias: 'starter',
type: 'string',
default: DEFAULT_STARTER,
describe: `Set starter. Defaults to ${DEFAULT_STARTER}`,
}).option('f', {
alias: 'framework',
type: 'string',
describe: 'Set development framework (e.g. React, Angular)',
}).option('t', {
alias: 'type',
choices: ['app', 'lab'],
describe: 'Type of application (app or lab)',
}).option('p', {
alias: 'path',
type: 'string',
describe: 'Path where project directory will be set up.',
}),
handler: promisify(prompt),
builder: (_) =>
_.option('s', {
alias: 'starter',
type: 'string',
default: DEFAULT_STARTER,
describe: `Set starter. Defaults to ${DEFAULT_STARTER}`,
})
.option('f', {
Comment thread
juancarlosfarah marked this conversation as resolved.
alias: 'framework',
type: 'string',
describe: 'Set development framework (e.g. React, Angular)',
})
.option('t', {
alias: 'type',
choices: ['app', 'lab'],
describe: 'Type of application (app or lab)',
})
.option('p', {
alias: 'path',
type: 'string',
describe: 'Path where project directory will be set up.',
}),
handler: prompt,
})
.command({
command: 'deploy',
desc: 'Deploy the Graasp app',
builder: (_) =>
_.option('p', {
alias: 'path',
type: 'string',
default: '.',
describe: 'Path to the Graasp app that shall be deployed',
}),
handler: deploy,
})
.wrap(cli.terminalWidth())
.demandCommand(1, 'Pass --help to see all available commands and options.')
Expand All @@ -61,5 +71,4 @@ const createCli = (argv) => {
.parse(argv.slice(2));
};


export default createCli;
86 changes: 86 additions & 0 deletions src/deploy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import AWS from 'aws-sdk';
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's stick to either aws or Aws.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to aws

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ugGit, I don't see the change.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just pushed the latest changes. Please check again

import s3 from 's3-node-client';
import dotenv from 'dotenv';

const path = require('path');
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why use require and not import?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pathis not used anymore in the latest changes.


// default build directory
const BUILD = 'build/';
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two options:

  1. Make default -p be ./build and -p be described as path/to/build/folder that will be deployed.
  2. Leave -p as is and allow for overriding this with a -b.

I think that 1 might be cleaner, but I'm up for discussing this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. Currently I changed it to -b. This keeps the syntax from the original deploy script which might be appreciated by users of the script. Furthermore, i factored the default value out into the config.js file.

As the changes are easy to make, let me know if you prefer the -p variant.


/**
* Returns an object with all variables loaded from a environment
* @param {string} environmentName is the suffix after .env.*
*/

const deploy = async (opts) => {
// const { path } = opts;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove?


const usageMessage = console.log(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need this anymore.

'usage: $0 [-e <path/to/file>] [-v <version string>] [-b <path/to/build>]',
);

console.log(`Exectued with path: ${opts.path}`);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in executed. Also, we use all small caps in logs. I would suggest:

deploying app in {...} directory

or something similar.

console.log(usageMessage);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need this.


// fetch environment variables
dotenv.config({ path: path.resolve(process.cwd(), '.env.dev') });
/* eslint-disable no-unused-vars */
const {
REACT_APP_GRAASP_DEVELOPER_ID,
REACT_APP_GRAASP_APP_ID,
REACT_APP_GRAASP_DOMAIN,
REACT_APP_HOST,
REACT_APP_VERSION,
REACT_APP_BASE,
NODE_ENV,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you need NODE_ENV?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn't sure yet. It's removed now.

BUCKET,
AWS_DEFAULT_REGION,
AWS_ACCESS_KEY_ID,
AWS_SECRET_ACCESS_KEY,
DISTRIBUTION,
} = process.env;
/* eslint-enable no-unused-vars */

AWS.config.getCredentials(function (err) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you using this? Maybe promisify in order not to have to shove everything inside the callback.

Copy link
Copy Markdown
Contributor Author

@ugGit ugGit Jul 11, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. This reloads the credentials from the .env.xxx set before. Hence, the below dev note in the sdk doc does not apply.

Note: If you configure the SDK with static or environment credentials, the credential data should already be present in credentials attribute. This method is primarily necessary to load credentials from asynchronous sources, or sources that can refresh credentials periodically.

However, I'm still looking for a proper way to refactor this parts and maybe promisify will help me to do so. The challenge thereby is to check for the error and stop the further promise chain execution properly...

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By rethinking the look we already assure that the configuration can be loaded since we're manually checking that the env variables (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY) exist. I will implement a first draft that we might discuss during the next meeting.

if (err) console.error(err.stack);
// credentials not loaded
else {
console.log('Access key:', AWS.config.credentials.accessKeyId);
}
});

const APP_PATH = `${REACT_APP_GRAASP_DEVELOPER_ID}/${REACT_APP_GRAASP_APP_ID}/${REACT_APP_VERSION}`;

const client = s3.createClient({ s3Client: new AWS.S3() });

const params = {
localDir: BUILD,
deleteRemoved: true, // default false, whether to remove s3 objects
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Put both lines of comment above deleteRemoved.

// that have no corresponding local file.

s3Params: {
Bucket: BUCKET,
Prefix: APP_PATH,
// other options supported by putObject, except Body and ContentLength.
// See: http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#putObject-property
},
};
const uploader = client.uploadDir(params);
uploader.on('error', function (err) {
console.error('unable to sync:', err.stack);
});
uploader.on('progress', function () {
console.log('progress', uploader.progressAmount, uploader.progressTotal);
});
uploader.on('end', function () {
console.log('done uploading');
});

console.log(
`published app to https://${REACT_APP_HOST}/${APP_PATH}/index.html`,
);

return true;
};

export default deploy;
125 changes: 125 additions & 0 deletions src/deploy.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
#!/bin/bash
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we still need this file?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have removed it.


# fail the build on any failed command
set -e

usage() {
echo "usage: $0 [-e <path/to/file>] [-v <version string>] [-b <path/to/build>]" 1>&2
exit 1
}

# default build directory
BUILD="build/"

# default version
REACT_APP_VERSION="latest"

# validates versioning e.g. v0.1.0
validate_version() {
# regex matching version numbers
rx='^v([0-9]+\.){0,2}(\*|[0-9]+)$'
if [[ $1 =~ $rx ]]; then
echo "info: validated version $1"
REACT_APP_VERSION=$1
else
echo "error: unable to validate version '$1'" 1>&2
echo "format is '${rx}'"
exit 1
fi
}


# validates that build directory exists
validate_build() {
if [ -d $1 ]; then
echo "info: validated build directory $1"
BUILD=$1
else
echo "error: build directory '$1' does not exist" 1>&2
exit 1
fi
}

# validates that environment file exists
validate_env() {
if [ -f $1 ]; then
echo "info: validated environment file $1"
source ${1}
else
echo "error: environment file '$1' does not exist" 1>&2
exit 1
fi
}

# parse command line arguments
while getopts "e:v:b:" opt; do
case ${opt} in
e)
e=${OPTARG}
validate_env ${e}
;;
b)
b=${OPTARG}
validate_build ${b}
;;
v)
v=${OPTARG}
validate_version ${v}
;;
\?)
echo "error: invalid option '-$OPTARG'" 1>&2
exit 1
;;
esac
done

# ensure the correct variables are defined
if \
[ -z "${REACT_APP_HOST}" ] || \
[ -z "${REACT_APP_GRAASP_DEVELOPER_ID}" ] || \
[ -z "${REACT_APP_GRAASP_APP_ID}" ]; then
echo "error: environment variables REACT_APP_GRAASP_APP_ID, REACT_APP_GRAASP_DEVELOPER_ID and/or REACT_APP_HOST are not defined" 1>&2
echo "error: you can specify them through a .env file in the app root folder" 1>&2
echo "error: or through another file specified with the -e flag" 1>&2
exit 1
fi

# ensure the correct aws credentials are defined
if \
[ -z "${BUCKET}" ] || \
[ -z "${AWS_ACCESS_KEY_ID}" ] || \
[ -z "${AWS_SECRET_ACCESS_KEY}" ]; then
echo "error: environment variables BUCKET, AWS_ACCESS_KEY_ID and/or AWS_SECRET_ACCESS_KEY are not defined" 1>&2
echo "error: make sure you setup your credentials file correctly using the scripts/setup.sh script" 1>&2
echo "error: and contact your favourite Graasp engineer if you keep running into trouble" 1>&2
exit 1
fi

export AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}
export AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}

echo "info: publishing app ${REACT_APP_GRAASP_APP_ID} version ${REACT_APP_VERSION}"

APP_DIR=${BUCKET}/${REACT_APP_GRAASP_DEVELOPER_ID}/${REACT_APP_GRAASP_APP_ID}/${REACT_APP_VERSION}/

# make sure you do not use the word PATH as a variable because it overrides the PATH environment variable
APP_PATH=${REACT_APP_GRAASP_DEVELOPER_ID}/${REACT_APP_GRAASP_APP_ID}/${REACT_APP_VERSION}

# sync s3 bucket
aws s3 sync ${BUILD} s3://${APP_DIR} --delete

# todo: allow cache invalidations per app once it is supported by cloudfront
# see: https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_aws-services-that-work-with-iam.html

# ensure the correct distribution variables are defined
if \
[ -z "${DISTRIBUTION}" ]; then
echo "error: environment variable DISTRIBUTION is not defined" 1>&2
echo "error: contact your favourite Graasp engineer if you keep running into trouble" 1>&2
exit 1
fi

# invalidate cloudfront distribution
aws cloudfront create-invalidation --distribution-id ${DISTRIBUTION} --paths /${APP_PATH}/*

echo "published app to https://${REACT_APP_HOST}/${APP_PATH}/index.html"
2 changes: 2 additions & 0 deletions src/utils.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import execa from 'execa';

// TODO: this is not optimal naming since it may cause confusion with 'spawn()' from 'child_process' module;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactor if needed to something like spawnProcess in a future commit.


// use execa to spawn a better child process
/* eslint-disable-next-line import/prefer-default-export */
export const spawn = (cmd, opts = { stdio: 'inherit' }) => {
Expand Down
Loading