Skip to content

Commit

Permalink
v0.5.0-alpha.2 (#24)
Browse files Browse the repository at this point in the history
* feat(core): add support for setting project through `FIREBASE_CI_PROJECT` environment variable
* feat(deploy): add skipDependenciesInstall, skipToolsInstall, and skipFunctionsInstall options
* feat(createConfig): add gitlab support to createConfig
  • Loading branch information
prescottprue authored Jul 29, 2018
1 parent 7e213d3 commit aa75237
Show file tree
Hide file tree
Showing 7 changed files with 135 additions and 79 deletions.
28 changes: 26 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@

```yaml
after_success:
- npm i -g firebase-ci
- firebase-ci deploy
- npm i firebase-ci
- $(npm bin)/firebase-ci deploy
```

**NOTE**: `firebase-ci` can be used through the nodejs `bin` instead of being installed globally
Expand All @@ -49,6 +49,22 @@
}
```


## Setting Project
There are a number of ways to set which Firebase project within `.firebaserc` is being used when running actions. Below is the order of for how the project is determined (default at bottom):

* `FIREBASE_CI_PROJECT` environment variable (overrides all)
* branch name (dependent on CI provider):
* Travis-CI - `TRAVIS_BRANCH`
* Gitlab - `CI_COMMIT_REF_SLUG`
* Circle-CI - `CIRCLE_BRANCH`
* fallback name (dependent on CI provider)
* Gitlab - `CI_ENVIRONMENT_SLUG`
* Other - `master`
* `master`
* `default` (must be set within `.firebaserc`)


<!-- Uncomment when next version is applicable
## Other Versions
Default installation uses `@latest` tag, but there are also others:
Expand Down Expand Up @@ -224,6 +240,14 @@ CI variable is SOME_TOKEN="asdf" and you would like to set it to `some.token` on

Internally calls `firebase functions:config:set some.token="asdf"`. This will happen for every variable you provide within mapEnv.

### skipDependenciesInstall
Skip installing of dependencies including `firebase-tools` and `node_modules` within `functions` folder

### skipToolsInstall
Skip installing of `firebase-tools` (installed by default when calling `firebase-ci deploy` without simple mode)

### skipFunctionsInstall
Skip running `npm install` within `functions` folder (`npm install` is called within `functions` folder by default when calling `firebase-ci deploy`).

### Roadmap
* `setCORS` option for copying CORS config file to Cloud Storage Bucket
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "firebase-ci",
"version": "0.5.0-alpha",
"version": "0.5.0-alpha.2",
"description": "Simplified Firebase interaction for continuous integration including deploying hosting, functions, and database/storage rules.",
"main": "lib/index.js",
"bin": {
Expand Down Expand Up @@ -32,10 +32,12 @@
"keywords": [
"webpack",
"firebase",
"firebase ci",
"firebase-tools",
"firebase-functions",
"deploy",
"ci",
"gitlab",
"travis-ci",
"circle-ci"
],
Expand Down
6 changes: 4 additions & 2 deletions src/actions/copyVersion.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,16 @@ import path from 'path'
import { log, info, success, error, warn } from '../utils/logger'
import { functionsExists } from '../utils/files'

const createPath = filePath => path.join(process.cwd(), filePath)
function createPath(filePath) {
return path.join(process.cwd(), filePath)
}

/**
* Copy version from main package file into functions package file
* @param {String} opts - name of project
* @param {Boolean} opts.silent - Whether or not to warn
*/
export default (config = { silence: false }) => {
export default function copyVersion(config = { silence: false }) {
if (!functionsExists()) {
if (config.silence) {
return
Expand Down
28 changes: 22 additions & 6 deletions src/actions/createConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,15 @@ import { reduce, template, mapValues, get, isString } from 'lodash'
import { getFile } from '../utils/files'
import { error, info, warn } from '../utils/logger'

const { TRAVIS_BRANCH, CIRCLE_BRANCH } = process.env

const tryTemplating = (str, name) => {
const {
FIREBASE_CI_PROJECT,
TRAVIS_BRANCH,
CIRCLE_BRANCH,
CI_COMMIT_REF_SLUG,
CI_ENVIRONMENT_SLUG
} = process.env

function tryTemplating(str, name) {
try {
return template(str)(process.env)
} catch (err) {
Expand Down Expand Up @@ -34,7 +40,7 @@ const tryTemplating = (str, name) => {
* }
* @private
*/
export default config => {
export default function createConfigFile(config) {
const settings = getFile('.firebaserc')

// Check for .firebaserc settings file
Expand All @@ -52,15 +58,25 @@ export default config => {
// Set options object for later use (includes path for config file)
const opts = {
path: get(config, 'path', './src/config.js'),
project: get(config, 'project', TRAVIS_BRANCH || CIRCLE_BRANCH)
project: get(
config,
'project',
FIREBASE_CI_PROJECT ||
TRAVIS_BRANCH ||
CIRCLE_BRANCH ||
CI_COMMIT_REF_SLUG
)
}

// Get environment config from settings file based on settings or branch
// default is used if TRAVIS_BRANCH env not provided, master used if default not set
const {
ci: { createConfig }
} = settings
const fallBackConfigName = createConfig.default ? 'default' : 'master'

// Fallback to different project name
const fallBackConfigName =
CI_ENVIRONMENT_SLUG || (createConfig.master ? 'master' : 'default')

info(`Attempting to load config for project: "${opts.project}"`)

Expand Down
90 changes: 46 additions & 44 deletions src/actions/deploy.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ import { getFile, functionsExists } from '../utils/files'
import { error, info, warn } from '../utils/logger'
import { runCommand, shellescape } from '../utils/commands'
import { installDeps } from '../utils/deps'
import { to } from '../utils/async'
import copyVersion from './copyVersion'
import mapEnv from './mapEnv'

const {
FIREBASE_CI_PROJECT,
TRAVIS_BRANCH,
TRAVIS_PULL_REQUEST,
TRAVIS_COMMIT_MESSAGE,
Expand All @@ -28,7 +30,7 @@ const skipPrefix = 'Skipping Firebase Deploy'
* @return {Promise}
* @private
*/
export const runActions = () => {
export function runActions() {
copyVersion()
const settings = getFile('.firebaserc')
if (functionsExists() && settings.ci && settings.ci.mapEnv) {
Expand All @@ -51,7 +53,7 @@ export const runActions = () => {
* to deploy (hosting, functions, database)
* @param {Function} cb - Callback called when complete (err, stdout)
*/
export default opts => {
export default async function deploy(opts) {
const settings = getFile('.firebaserc')
const firebaseJson = getFile('firebase.json')
if (
Expand All @@ -62,7 +64,7 @@ export default opts => {
) {
const nonCiMessage = `${skipPrefix} - Not a supported CI environment`
warn(nonCiMessage)
return Promise.resolve(nonCiMessage)
return nonCiMessage
}

if (
Expand All @@ -71,23 +73,23 @@ export default opts => {
) {
const pullRequestMessage = `${skipPrefix} - Build is a Pull Request`
info(pullRequestMessage)
return Promise.resolve(pullRequestMessage)
return pullRequestMessage
}

if (!settings) {
error('.firebaserc file is required')
return Promise.reject(new Error('.firebaserc file is required'))
throw new Error('.firebaserc file is required')
}

if (!firebaseJson) {
error('firebase.json file is required')
return Promise.reject(new Error('firebase.json file is required'))
throw new Error('firebase.json file is required')
}

const branchName = TRAVIS_BRANCH || CIRCLE_BRANCH || CI_COMMIT_REF_SLUG
const fallbackProjectName = CI_ENVIRONMENT_SLUG
// Get project from passed options, falling back to branch name
const projectName = get(opts, 'project', branchName)
const projectName = FIREBASE_CI_PROJECT || get(opts, 'project', branchName)
// Get project setting from settings file based on branchName falling back
// to fallbackProjectName
const projectSetting = get(settings, `projects.${projectName}`)
Expand All @@ -106,9 +108,9 @@ export default opts => {
opts.project ? 'Project' : 'Branch'
}: ${fallbackProjectName} exiting...`
info(nonFallbackBranch)
return Promise.resolve(nonProjectBranch)
return nonProjectBranch
}
return Promise.resolve(nonProjectBranch)
return nonProjectBranch
}

if (!FIREBASE_TOKEN) {
Expand All @@ -117,50 +119,50 @@ export default opts => {
'Run firebase login:ci (from firebase-tools) to generate a token' +
'and place it travis environment variables as FIREBASE_TOKEN'
)
return Promise.reject(
new Error('Error: FIREBASE_TOKEN env variable not found.')
)
throw new Error('Error: FIREBASE_TOKEN env variable not found.')
}

const originalMessage =
TRAVIS_COMMIT_MESSAGE || CIRCLE_SHA1 || CI_COMMIT_MESSAGE

const onlyString = opts && opts.only ? `--only ${opts.only}` : ''
const project = branchName || settings.projects.default
// // First 300 characters of travis commit message or "Update"
const message = originalMessage
? originalMessage.replace(/"/g, "'").substring(0, 300)
: 'Update'
info('Installing dependencies...')
return installDeps(opts, settings)
.then(() => {
if (opts.simple) {
info('Simple mode enabled. Skipping CI actions')
return {}
}
return runActions(opts.actions)
})
.then(() =>
// Wait until all other commands are complete before calling deploy
runCommand({
command: '$(npm bin)/firebase',
args: compact([
'deploy',
onlyString,
'--token',
FIREBASE_TOKEN || 'Invalid.Token',
'--project',
project,
'--message',
shellescape([message])
]),
beforeMsg: 'Deploying to Firebase...',
errorMsg: 'Error deploying to firebase.',
successMsg: `Successfully Deployed to ${project}`
})
)
.catch(err => {
error('Error in firebase-ci:\n ', err)
return Promise.reject(err)
// Install firebase-tools and functions dependencies if enabled
if (!settings.skipDependencyInstall) {
await installDeps(opts, settings)
} else {
info('Dependency install skipped')
}
// Run CI actions if enabled (i.e. copyVersion, createConfig)
if (!opts.simple) {
runActions(opts.actions)
} else {
info('Simple mode enabled. Skipping CI actions')
}
const [deployErr] = await to(
runCommand({
command: ['$(npm bin)/firebase'],
args: compact([
'deploy',
onlyString,
'--token',
FIREBASE_TOKEN || 'Invalid.Token',
'--project',
projectName,
'--message',
shellescape([message])
]),
beforeMsg: 'Deploying to Firebase...',
errorMsg: 'Error deploying to firebase.',
successMsg: `Successfully Deployed ${branchName} branch to ${projectName} Firebase project`
})
)
if (deployErr) {
error('Error in firebase-ci:\n ', deployErr)
throw deployErr
}
return null
}
25 changes: 14 additions & 11 deletions src/utils/commands.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,27 @@
/* eslint-disable no-console */
import { drop, compact } from 'lodash'
import { drop, compact, isArray } from 'lodash'
import stream from 'stream'
import { info } from '../utils/logger'
const { spawn } = require('child_process')

process.env.FORCE_COLOR = true

export const isPromise = obj => obj && typeof obj.then === 'function'
export function isPromise(obj) {
return obj && typeof obj.then === 'function'
}

/**
* @description Run a bash command using exec.
* @description Run a bash command using spawn pipeing the results to the main
* process
* @param {String} command - Command to be executed
* @private
*/
export const runCommand = command => {
if (command.beforeMsg) info(command.beforeMsg)
export function runCommand({ beforeMsg, successMsg, command, errorMsg, args }) {
if (beforeMsg) info(beforeMsg)
return new Promise((resolve, reject) => {
const child = spawn(
command.command.split(' ')[0],
command.args || compact(drop(command.command.split(' ')))
isArray(command) ? command : command.split(' ')[0],
args || compact(drop(command.split(' ')))
)
var customStream = new stream.Writable()
var customErrorStream = new stream.Writable()
Expand All @@ -40,13 +43,13 @@ export const runCommand = command => {
if (code !== 0) {
// Resolve for npm warnings
if (output && output.indexOf('npm WARN') !== -1) {
return resolve(command.successMsg || output)
return resolve(successMsg || output)
}
reject(command.errorMsg || error)
reject(errorMsg || error)
} else {
// resolve(null, stdout)
if (command.successMsg) info(command.successMsg)
resolve(command.successMsg || output)
if (successMsg) info(successMsg)
resolve(successMsg || output)
}
})
})
Expand Down
Loading

0 comments on commit aa75237

Please sign in to comment.