diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..46d56d3 --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,329 @@ + +library 'aplazame-shared-library' + +app = "apidocs" +repoName = scm.getUserRemoteConfigs()[0].getUrl().tokenize('/').last().split("\\.")[0] +githubBranch = env.CHANGE_BRANCH ? env.CHANGE_BRANCH : env.BRANCH_NAME +githubRepo = "https://github.com/aplazame/" + repoName + "/tree/" + githubBranch + +// GIT_COMMIT_HASH = sh (script: "git log -n 1 --pretty=format:'%H'", returnStdout: true) + +foldersCache = '"node_modules/"' +s3BucketCache = "aplazameshared-jenkins-cache" + +branch_like_master = 'only/master-is-master-πŸš€' +branch_like_release = 'only/release-is-release-πŸš€' + +aws_profile_by_env = [ + pre: 'AplazameStaging', + default: 'Aplazame', +] + +ephe_suffix_by_env = [ + squad: '-squad', + dev: '-dev', + default: '', +] + +ephe_bucket_name_by_env = [ + squad: 'aplazame-ephemeral-environments', + default: 'ephemeral-environments', +] + +bucket_name_by_env = [ + pre: 'apidocs-pre.aplazame.org', + staging: 'aplazame-apidocs-staging', // 'checkout-staging.aplazame.com' + squad: 'aplazame-apidocs-squad', // 'checkout-squad?.aplazame.org' + // prod: <- ⚠️ IS VERY DANGEROUS TO PUT IT HERE ⚠️ + default: 'aplazame-apidocs-dev', // `dev` by default 'checkout-dev.aplazame.org' +] +PROD_bucket_name = 'aplazame-apidocs' // 'checkout.aplazame.com' + +envs_by_branch = [ + master: ['pre', 'staging', 'dev'], + release: ['prod'], + default: ['staging', 'dev', 'squad'], // 'public/staging public/dev public/squad', // ephemerals +] + +envs_by_branch[branch_like_master] = envs_by_branch.master +envs_by_branch[branch_like_release] = envs_by_branch.release + +branch_envs = getKey(envs_by_branch, githubBranch) +public_dirs = branch_envs.collect({env -> "public/${env}"}).join(' ') +num_public_dirs = branch_envs.size() + +sc_story = getStoryIdFromBranchName(githubBranch) + +def getKey (from = [:], key = 'default') { + return from[key] ?: from.default +} + +def getEphemeralsDeployMessage () { + def msg = [ + "πŸš€ *EfΓ­mero desplegado* πŸš€", + "", + "Github: " + repoName + "/" + githubBranch, + "\t${githubRepo}", + ]; + + if (sc_story) { + msg.push("\nShortcut: https://app.shortcut.com/aplazame/story/${sc_story}") + } else { + msg.push("⚠️ rama sin HU de Shortcut") + } + + msg.push("\nDemos:\n") + + branch_envs + .each({env -> + def ephe_subdomain = 'sc-' + sc_story + '-' + app + '-' + env + msg.push("- https://${ephe_subdomain}.demo.aplazame.org/demo/") + }) + + return msg.join('\n') +} + +pipeline { + options { + skipDefaultCheckout() + disableConcurrentBuilds() + ansiColor('xterm') + } + + agent { + kubernetes { + yamlFile "jenkins/node.yaml" + } + } + + environment { + SHORTCUT_API_TOKEN = credentials('CLUBHOUSE_API_TOKEN') + APP_RELEASY = "webapp-apidocs" + SLACK_TOKEN = credentials('SLACK_TOKEN_FRONTEND') + SLACK_HOOK = "https://hooks.slack.com/services/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" + } + + stages { + stage('⭐') { + when { + anyOf { + changeRequest(target: 'master') + branch 'master' + // branch 'release' + } + // not { + // tag "*" + // } + beforeAgent true + } + + stages { + // stage('Cache βš™') { + // steps { + // // checkout scm + + // script { + // HASH = sh(script: 'md5sum package.json | awk \'{print \$1}\'', returnStdout: true).trim() + // CACHE_KEY = 'node_14_vite-' + HASH + // } + + // container('node') { + // loadCache(CACHE_KEY) + // sh "ls -la" + + // sh "load-config" + // } + // } + // } + + stage('Install βš™') { + environment { + AWS_PROFILE = "AplazameSharedServices" + } + + steps { + checkout scm + // script { + // commitMsg = sh(returnStdout: true, script: "git log -2 --pretty=%B").trim() + // forceNotifyShortcut = commitMsg.matches("[notify shortcut]") ? true : false + // forceNotifySlack = commitMsg.matches("[notify slack]") ? true : false + + // echo "commitMsg: '${commitMsg}'" + // echo "forceNotifyShortcut: '${forceNotifyShortcut}'" + // echo "forceNotifySlack: '${forceNotifySlack}'" + // } + + container('node') { + sshagent(['ssh-github']) { + logEnvVars() + + sh "ssh-keyscan -t rsa github.com >> ~/.ssh/known_hosts" + + sh "[ -d ./node_modules ] && ls -ls node_modules || echo 'missing node_modules'" + + sh "make install" + } + } + } + } + + stage('βœ… & πŸ“Š') { + steps { + container('node') { + sshagent(['ssh-github']) { + sh "npm audit || true" + sh "npx browserslist || true" + sh "make count.lines" + sh "make lint" + sh "make ci.test" + // stash includes: 'coverage/**/*', name: 'coverage' + } + } + } + } + + stage('Build 🍳') { + environment { + AWS_PROFILE = "AplazameSharedServices" + } + + steps { + container('node') { + script { + branch_envs.each{env -> + // sh "ENV=${env} make log.ENV_DATA" + sh "ENV=${env} OUT_DIR=public/${env} make build" + } + } + + // sh branch_envs + // .collect({env -> "ENV=${env} OUT_DIR=public/${env} make build"}) + // .join(' & \\\n') + + // """ & \\\n wait; """ + } + } + } + + stage('Ephemerals πŸͺ£ S3') { + when { not { anyOf { + // { branch 'master' } not working propertly in PRs + expression { githubBranch == 'master' } + expression { githubBranch == branch_like_master } + expression { githubBranch == 'release' } + expression { githubBranch == branch_like_release } + } } } + steps { + container('node') { + script { + sh """ + load-config + export AWS_PROFILE=Aplazame + """ + + def first_deploy = !folderExistsInS3( + 's3://' + getKey(ephe_bucket_name_by_env, 'staging') + getKey(ephe_suffix_by_env, 'staging') + '/' + app + '/sc-' + sc_story + ) + + branch_envs.each { env -> + def s3_path = 's3://' + getKey(ephe_bucket_name_by_env, env) + getKey(ephe_suffix_by_env, env) + '/' + app + '/sc-' + sc_story + + echo "πŸš€ [[ deploying 'public/${env}' to '${s3_path}' ]] πŸͺ£" + uploadFolderToS3('public/' + env, s3_path, + acl: 'public-read', + files_no_cache: '*.html', + ) + } + + def message = getEphemeralsDeployMessage() + def curl_message = message.replaceAll('\\n', '\\\\n') + + if (first_deploy) { + sh (returnStdout: true, script: """ + curl -X POST https://api.app.shortcut.com/api/v3/stories/${sc_story}/comments \ + -H "Shortcut-Token: \$SHORTCUT_API_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ "text": "${curl_message}" }' + """.stripIndent()) + } + + if (first_deploy) { + slackSend( + failOnError: true, + color: '#8000FF', + channel: '#frontend-environments', + message: message, + username: "Jenkins CI", + ) + } + } + } + } + } + + stage('[master] πŸͺ£ S3') { + when { anyOf { + // { branch 'master' } not working propertly in PRs + expression { githubBranch == 'master' } + expression { githubBranch == branch_like_master } + } } + steps { + container('node') { + script { + branch_envs.each { env -> + def s3_path = 's3://' + getKey(bucket_name_by_env, env) + + echo "πŸš€ [[ deploying 'public/${env}' to '${s3_path}' ]] πŸͺ£" + def result = uploadFolderToS3('public/' + env, s3_path, + aws_profile: getKey(aws_profile_by_env, env), + files_no_cache: '*.html', + ) + } + } + } + } + } + + // stage('[PROD] πŸͺ£ S3') { + // when { anyOf { + // // branch not working propertly in PRs + // expression { githubBranch == 'release' } + // expression { githubBranch == branch_like_release } + // } } + // steps { + // container('node') { + // script { + // def s3_path = 's3://' + PROD_bucket_name + + // echo "πŸš€ [[ deploying 'public/prod' to '${s3_path}' ]] πŸš€" + // uploadFolderToS3('public/prod', 's3://' + PROD_bucket_name, + // // aws_profile: 'Aplazame', + // files_no_cache: '*.html', + // ) + // } + + // releasySteps() + // } + // } + // } + + // stage('cache πŸ’Ύ') { + // environment { + // AWS_PROFILE = "AplazameSharedServices" + // } + + // steps { + // container("node") { + // saveCache(CACHE_KEY,["${foldersCache}"]) + // } + // } + // } + + stage('Done 🀘') { + steps { + echo "That's all folks!!" + } + } + } + } + } +} diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..65393fa --- /dev/null +++ b/Makefile @@ -0,0 +1,282 @@ +#!make +SHELL := env PATH=$(shell npm bin):$(PATH) /bin/bash -O extglob + +.SILENT: +.PHONY: count.lines lint install assets messages css js test paypal-popup sass html app app-prod build-dev dev prod watch live app-stripe-3ds server/config.json public/prod public/pre public/staging public/dev public/squad + +default: install count.lines test log.ENV_DATA log.DEMO_DATA build +.DEFAULT_GOAL := default + +export UNAME_S=$(shell uname -s) +export BRANCH_NAME=$(shell git symbolic-ref --short HEAD) + +export BUILD_TIMESTAMP=$(date +%s) +export BUILD_HASH=$(shell git rev-parse --short HEAD) + +ifeq ($(wildcard ./.env),./.env) +include .env +export $(shell sed 's/=.*//' .env) +endif + +ifndef ENV + export ENV=staging +endif + +ifdef API + export ENV=${API} +endif + +ifndef API + export API=${ENV} +endif + +ifndef BUNDLE + export BUNDLE=development +endif + +ifndef OUT_DIR + export OUT_DIR=public +endif + +ifndef SANDBOX + export SANDBOX=true +endif + +ifndef IS_TEST + export IS_TEST=false +endif + +ifndef FORCE_COLOR + export FORCE_COLOR=true +endif + +ifndef CHECKOUT_URL + export CHECKOUT_URL=http://checkout.aplazame.local/demo/es/ +endif + +# https://www.npmjs.com/package/sloc#usage +count.lines:; sloc .make app + +node_modules:; npm install +install:; npm install +i: install + +lint: node_modules + eslint 'app/{,**/}*.{js,vue}' + +list.mocha: + fsdir -d app \ + --each '{,**/}*{-test,.test}.js' 'echo $$FILE_PATH' + +mocha: node_modules + mocha \ + --require @babel/register \ + --require module-alias/register \ + --reporter mocha-multi \ + --reporter-options spec=-,xunit=test-results/mocha/results.xml \ + --color \ + "./app/{,**/}{*-tests,*.test}.js" + +jest: export IS_TEST=true +jest: export JEST_JUNIT_OUTPUT_DIR=./coverage + +# Execute single file: $(npm bin)/jest --testPathPattern '.+TypeSelector.ui.spec.js' + +jest: node_modules + jest --coverage --testPathPattern '.+\.ui\.spec\.js' + +quick.jest: export IS_TEST=true +quick.jest: + jest --testPathPattern '.+\.ui\.spec\.js' + +ci.test: export JEST_JUNIT_OUTPUT_DIR=./coverage +ci.test: node_modules + jest \ + --coverage \ + --testResultsProcessor="jest-junit" \ + --testPathPattern '.+\.ui\.spec\.js' \ + --maxWorkers 1 + +test: jest + +# include ./.make/*.mk + +local-ui-components: node_modules + cd node_modules/@aplazame; \ + rm -rf ui; \ + ln -s ../../../ui-components ui + +log.ENV_DATA: + @echo + @node -e "console.log('ENV_DATA\n\n', require('./.make/_env'))" + @echo + +log.DEMO_DATA: + @echo + @node -e "console.log('DEMO_DATA\n\n', require('./.make/_demo-data'))" + @echo + +clean: + rm -rf public + +public:; mkdir -p public +public/assets:; mkdir -p public/assets + +public/mock-data:; cp -r mock-data public + +assets: public/assets public/mock-data + cp -r node_modules/@aplazame/ui/assets/* public/assets/ + cp -r assets/* public/assets/ +ifdef LOCAL_CHECKOUT_JSON_FILE + json5 ${LOCAL_CHECKOUT_JSON_FILE} -o public/assets/checkout.json +endif + +messages: + node .make/messages.js es-ES -o static/messages/es-ES.json + cat static/messages/es-ES.json | node .make/messages.js pt-PT -o static/messages/pt-PT.json + +inject.checkout.css: + echo "// DO NOT EDIT THIS FILE" > ./app/checkout/.injected.sass + echo "// this content is auto-generated" >> ./app/checkout/.injected.sass + echo "" >> ./app/checkout/.injected.sass + + fsdir -d app/checkout \ + --each '{components,components-vue,sections,modals,services}/{,**/}*.{sass,scss}; !{,**/}*_.{sass,scss}' \ + 'echo "@import \"./$$FILE_DIR/$$FILE_NAME\"" >> ./app/checkout/.injected.sass' + +checkout.css: inject.checkout.css + mkdir -p public/_/$$BUILD_HASH + sass --indented \ + app/checkout/checkout.sass | postcss > public/_/$$BUILD_HASH/checkout.css + +upload-docs.css: + mkdir -p public/upload-docs + sass --indented \ + app/upload-docs/upload-docs.sass | postcss > public/upload-docs/upload-docs.$$BUILD_HASH.css + +stripe-3ds.css: + mkdir -p public/stripe-3ds + sass --indented \ + app/stripe-3ds/stripe-3ds.sass | postcss > public/stripe-3ds/stripe-3ds.$$BUILD_HASH.css + +css: + $(MAKE) checkout.css & \ + $(MAKE) upload-docs.css & \ + $(MAKE) stripe-3ds.css & \ + wait < <(jobs -p) + +live-watch-css: + fsdir -d app \ + --watch '{,**/}*.{scss,sass}' 'make css && browser-sync reload -p 8088 --files="*.css"' + +js: + webpack ${WEBPACK_FLAGS} --no-stats + +build.webpack: node_modules clean public + $(MAKE) assets & \ + $(MAKE) messages & \ + $(MAKE) css & \ + $(MAKE) js + wait + +live-server: + # webpack serve --color + browser-sync start -s public --no-notify --host $$LOCAL_IP --port 8088 + +server/config.json: + node ./server/create.config.js + +api.proxy: server/config.json + cd server && node ./server.js + +dist/lambda.zip: server/config.json + cat server/config.json + + mkdir -p dist + zip -r -j dist/lambda.zip server/* \ + --exclude='*.yml' \ + --exclude='*create.config.js' + +# live: export WEBPACK_SERVER_PORT=8080 +live.webpack: export WEBPACK_FLAGS=--watch +live.webpack: export WEBPACK_MAIN_ON_FIRST_BUILD=make live-server +live.webpack: export WEBPACK_MAIN_ON_NEXT_BUILDS=browser-sync reload -p 8088 +live.webpack: node_modules + @$(MAKE) log.DEMO_DATA + @$(MAKE) log.ENV_DATA + + $(MAKE) build & \ + $(MAKE) live-watch-css & \ + $(MAKE) api.proxy + wait < <(jobs -p) + +prod: export ENV=production +prod: export BUNDLE=production +prod: install build + +storybook: + start-storybook -p 6006 + +storybook-build: + mkdir public/demo/storybook + build-storybook -o public/demo/storybook + +jsdoc: + mkdir -p public/demo/jsdoc + jsdoc --configure .jsdoc.json --verbose app -d public/demo/jsdoc + +e2e: + e2e-checkout --base-url=${CHECKOUT_URL} + +cypress-e2e: + cd e2e && npm run start + +cypress: + export LIVE_FLAGS=--no-open + $(MAKE) build + $(MAKE) live-server & \ + $(MAKE) cypress-e2e & \ + wait < <(jobs -p) + +release: + @echo "\n UOU!! vanilla is in development, maybe you need a coffee β˜• Β―\_(ツ)_/Β― \n" + +npmrc: + sed -i 's/$${NPM_TOKEN}/'$$NPM_TOKEN'/' .npmrc + +pre.build: node_modules log.ENV_DATA messages inject.checkout.css + +up: pre.build + vite -c ./vite.config.js + +index.build: ./$(OUT_DIR)/index.html ./$(OUT_DIR)/*/index.html ./$(OUT_DIR)/*/*/index.html + echo "" +ifeq ($(UNAME_S),Darwin) + for file in $^ ; do \ + sed -i '' 's/