diff --git a/.eslintignore b/.eslintignore index 9f9b48d05b..c71f54c11a 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1 +1,2 @@ public/**/*.js +client/main.js diff --git a/.eslintrc b/.eslintrc index 5d7667557f..92fad475c3 100644 --- a/.eslintrc +++ b/.eslintrc @@ -224,8 +224,8 @@ "no-plusplus": 0, "react/display-name": 1, - "react/jsx-boolean-value": 1, - "react/jsx-quotes": [1, "single", "avoid-escape"], + "react/jsx-boolean-value": [1, "always"], + "jsx-quotes": [1, "prefer-single"], "react/jsx-no-undef": 1, "react/jsx-sort-props": 1, "react/jsx-uses-react": 1, diff --git a/common/app/components/Flash/Queue.jsx b/common/app/components/Flash/Queue.jsx index 718733a923..ac36ca3f46 100644 --- a/common/app/components/Flash/Queue.jsx +++ b/common/app/components/Flash/Queue.jsx @@ -1,7 +1,7 @@ -import React, { createClass, PropTypes } from 'react'; +import React, { PropTypes } from 'react'; import { Alert } from 'react-bootstrap'; -export default createClass({ +export default React.createClass({ displayName: 'FlashQueue', propTypes: { @@ -9,9 +9,9 @@ export default createClass({ }, renderMessages(messages) { - return messages.map(message => { + return messages.map(() => { return ( - + ); }); }, diff --git a/common/app/flux/Actions.js b/common/app/flux/Actions.js index 6f4754224e..52334c4876 100644 --- a/common/app/flux/Actions.js +++ b/common/app/flux/Actions.js @@ -8,11 +8,19 @@ export default Actions({ return { title: title + '| Free Code Camp' }; }, - setUser({ username, picture, progressTimestamps = [] }) { + setUser({ + username, + picture, + progressTimestamps = [], + isFrontEndCert, + isFullStackCert + }) { return { username, picture, - points: progressTimestamps.length + points: progressTimestamps.length, + isFrontEndCert, + isFullStackCert }; }, diff --git a/common/app/routes/Bonfires/Actions.js b/common/app/routes/Bonfires/Actions.js deleted file mode 100644 index 1930b48890..0000000000 --- a/common/app/routes/Bonfires/Actions.js +++ /dev/null @@ -1,63 +0,0 @@ -var Action = require('thundercats').Action, - executeBonfire = require('./executeBonfire'), - getModel = require('../../utils/getModel'), - debug = require('debug')('freecc:common:bonfires'); - -var BonfireActions = Action.createActions([ - 'setUserCode', - 'testUserCode', - 'setResults', - 'setDisplay', - 'setBonfire', - 'getBonfire', - 'handleBonfireError', - 'openCompletionModal' -]); - -BonfireActions - .getBonfire - .subscribe(function(params) { - var Bonfire = getModel('bonfire'); - var bonfireName = params.bonfireName ? - params.bonfireName.replace(/\-/g, ' ') : - 'meet bonfire'; - debug('getting bonfire for: ', bonfireName); - var regQuery = { name: { like: bonfireName, options: 'i' } }; - Bonfire.find( - { where: regQuery }, - function(err, bonfire) { - if (err) { - return debug('bonfire get err', err); - } - if (!bonfire || bonfire.length < 1) { - return debug('404 no bonfire found for ', bonfireName); - } - bonfire = bonfire.pop(); - if (bonfire) { - debug( - 'found bonfire %s for route %s', - bonfire.name, - bonfireName - ); - } - BonfireActions.setBonfire(bonfire); - } - ); - }); - - -BonfireActions - .testUserCode - .subscribe(function({ userCode, tests }) { - debug('test bonfire'); - executeBonfire(userCode, tests, function(err, { output, results }) { - if (err) { - debug('error running tests', err); - return BonfireActions.setDisplay(err); - } - BonfireActions.setDisplay(output); - BonfireActions.setResults(results); - }); - }); - -module.exports = BonfireActions; diff --git a/common/app/routes/Bonfires/Store.js b/common/app/routes/Bonfires/Store.js deleted file mode 100644 index 4a83c223d6..0000000000 --- a/common/app/routes/Bonfires/Store.js +++ /dev/null @@ -1,67 +0,0 @@ -var BonfiresActions = require('./Actions'); -var { Store, setStateUtil } = require('thundercats'); - -var BonfiresStore = Store.create({ - - getInitialValue: function() { - return { - userCode: 'console.log(\'FreeCodeCamp!\')', - difficulty: 0, - description: [ - 'default state' - ], - tests: [], - results: null - }; - }, - - getOperations: function() { - var { - setBonfire, - setUserCode, - setResults, - setDisplay - } = BonfiresActions; - - return [ - setBonfire - .map(function(bonfire) { - var { - name, - description, - difficulty, - tests - } = bonfire; - var userCode = bonfire.challengeSeed; - return { - name, - userCode, - tests, - description, - difficulty - }; - }) - .map(setStateUtil), - - setUserCode - .map(function(userCode) { - return { userCode }; - }) - .map(setStateUtil), - - setDisplay - .map(function(display) { - return { display }; - }) - .map(setStateUtil), - - setResults - .map(function(results) { - return { results }; - }) - .map(setStateUtil) - ]; - } -}); - -module.exports = BonfiresStore; diff --git a/common/app/routes/Bonfires/components/Bonfires.jsx b/common/app/routes/Bonfires/components/Bonfires.jsx deleted file mode 100644 index 9da6db9320..0000000000 --- a/common/app/routes/Bonfires/components/Bonfires.jsx +++ /dev/null @@ -1,99 +0,0 @@ -var React = require('react'), - - // ## mixins - { ObservableStateMixin } = require('thundercats'), - - // ## components - SidePanel = require('./SidePanel.jsx'), - Results = require('./Results.jsx'), - Display = require('../displayCode'), - Editor = require('../editor'), - { Grid, Row, Col } = require('react-bootstrap'), - - // ## flux - BonfireActions = require('./Actions'), - BonfireStore = require('./Store'); - -var Bonfire = React.createClass({ - - mixins: [ObservableStateMixin], - - contextTypes: { - makePath: React.PropTypes.func.isRequired, - replaceWith: React.PropTypes.func.isRequired - }, - - getObservable: function() { - return BonfireStore; - }, - - componentDidMount: function() { - // get history object - var his = typeof window !== 'undefined' ? window.history : null; - // spinal-case bonfireName - var bonfireName = this.state.name.toLowerCase().replace(/\s/g, '-'); - // create proper URI from react-router - var path = this.context.makePath('bonfires', { bonfireName: bonfireName }); - - // if html5 push state exists, update URI - // else we are using hash location and should just cause a re render - if (his) { - his.replaceState({ path: path }, '', path); - } else { - this.context.replaceWith('bonfires', { bonfireName: bonfireName}); - } - }, - - _onTestBonfire: function() { - BonfireActions.testUserCode({ - userCode: this.state.userCode, - tests: this.state.tests - }); - }, - - render: function() { - var { - name, - userCode, - difficulty, - description, - results, - display - } = this.state; - var brief = description.slice(0, 1).pop(); - - // convert bonfire difficulty from floating point string - // to integer. - var difficultyInt = Math.floor(+difficulty); - - return ( - - - - 1 ? description : [] }/> - - - - - - - - - ); - } -}); - -module.exports = Bonfire; diff --git a/common/app/routes/Bonfires/components/Results.jsx b/common/app/routes/Bonfires/components/Results.jsx deleted file mode 100644 index 8e0e11b449..0000000000 --- a/common/app/routes/Bonfires/components/Results.jsx +++ /dev/null @@ -1,62 +0,0 @@ -var React = require('react'), - classNames = require('classnames'), - { Grid, Row, Col } = require('react-bootstrap'); - -var Results = React.createClass({ - - propTypes: { - results: React.PropTypes.array - }, - - _renderText: function(text, textClass) { - return ( - - { text } - - ); - }, - - _renderResult: function(results) { - return results.map(function(result, idx) { - var err = result.err; - var iconClass = { - 'ion-close-circled big-error-icon': err, - 'ion-checkmark-circled big-success-icon': !err - }; - var textClass = { - 'test-output wrappable': true, - 'test-vertical-center': !err - }; - return ( -
- - - - - { this._renderText(result.text, textClass) } - { err ? this._renderText(err, textClass) : null } - -
-
- ); - }.bind(this)); - }, - - render: function() { - var results = this.props.results; - if (!results || results.length && results.length === 0) { - return null; - } - return ( - - { this._renderResult(this.props.results) } - - ); - } -}); - -module.exports = Results; diff --git a/common/app/routes/Bonfires/components/SidePanel.jsx b/common/app/routes/Bonfires/components/SidePanel.jsx deleted file mode 100644 index 078e9fe72b..0000000000 --- a/common/app/routes/Bonfires/components/SidePanel.jsx +++ /dev/null @@ -1,129 +0,0 @@ -var React = require('react'), - - // ## components - { - Well, - Row, - Col, - Button, - } = require('react-bootstrap'); - -var SidePanel = React.createClass({ - - propTypes: { - name: React.PropTypes.string, - brief: React.PropTypes.string, - description: React.PropTypes.array, - difficulty: React.PropTypes.number, - onTestBonfire: React.PropTypes.func - }, - - getDefaultProps: function() { - return { - name: 'Welcome to Bonfires!', - difficulty: 5, - brief: 'This is a brief description' - }; - }, - - getInitialState: function() { - return { - isMoreInfoOpen: false - }; - }, - - _toggleMoreInfo: function() { - this.setState({ - isMoreInfoOpen: !this.state.isMoreInfoOpen - }); - }, - - _renderFlames: function() { - var difficulty = this.props.difficulty; - - return [1, 2, 3, 4, 5].map(num => { - var className = 'ion-ios-flame'; - if (num > difficulty) { - className += '-outline'; - } - return ( - - ); - }); - }, - - _renderMoreInfo: function(isDescription) { - var description = this.props.description.map((sentance, index) => { - return

{ sentance }

; - }); - - if (isDescription && this.state.isMoreInfoOpen) { - return ( - - - { description } - - - ); - } - return null; - }, - - _renderMoreInfoButton: function(isDescription) { - if (isDescription) { - return ( - - ); - } - return null; - }, - - render: function() { - var isDescription = this.props.description && - this.props.description.length > 1; - - return ( -
-

{ this.props.name }

-

-
- Difficulty:  - { this._renderFlames() } -
-

- - - -
-

{ this.props.brief }

-
- { this._renderMoreInfo(isDescription) } - { this._renderMoreInfoButton(isDescription) } -
-
- -
-
- -
-
- ); - } -}); - -module.exports = SidePanel; diff --git a/common/app/routes/Bonfires/executeBonfire.js b/common/app/routes/Bonfires/executeBonfire.js deleted file mode 100644 index 0d1795894a..0000000000 --- a/common/app/routes/Bonfires/executeBonfire.js +++ /dev/null @@ -1,27 +0,0 @@ -var debug = require('debug')('freecc:executebonfire'); -var { - addTests, - runTests, - testCode -} = require('../../utils'); - -module.exports = executeBonfire; - -function executeBonfire(userCode, tests, cb) { - - // TODO: move this into componentDidMount - // ga('send', 'event', 'Bonfire', 'ran-code', bonfireName); - var testSalt = Math.random(); - var { preppedCode, userTests } = addTests(userCode, tests, testSalt); - - debug('sending code to web worker for testing'); - testCode(preppedCode, function(err, data) { - if (err) { return cb(err); } - var results = runTests(userTests, data, testSalt); - debug('testing complete', results); - cb(null, { - output: data.output, - results - }); - }); -} diff --git a/common/app/routes/Bonfires/index.js b/common/app/routes/Bonfires/index.js deleted file mode 100644 index ec633552eb..0000000000 --- a/common/app/routes/Bonfires/index.js +++ /dev/null @@ -1,8 +0,0 @@ -/* -export default { - path: 'bonfires/(:bonfireName)' - getComponents(cb) { - // TODO(berks): add bonfire component - } -}; -*/ diff --git a/common/app/routes/Jobs/components/JobNotFound.jsx b/common/app/routes/Jobs/components/JobNotFound.jsx index b921dfd4dc..c4981d22f8 100644 --- a/common/app/routes/Jobs/components/JobNotFound.jsx +++ b/common/app/routes/Jobs/components/JobNotFound.jsx @@ -1,8 +1,8 @@ -import React, { createClass } from 'react'; +import React from 'react'; import { LinkContainer } from 'react-router-bootstrap'; import { Button, Row, Col, Panel } from 'react-bootstrap'; -export default createClass({ +export default React.createClass({ displayName: 'NoJobFound', render() { diff --git a/common/app/routes/Jobs/components/Jobs.jsx b/common/app/routes/Jobs/components/Jobs.jsx index 09c03030c7..af47b0c2b0 100644 --- a/common/app/routes/Jobs/components/Jobs.jsx +++ b/common/app/routes/Jobs/components/Jobs.jsx @@ -74,8 +74,8 @@ export default contain( mdOffset= { 1 } xs={ 12 }>

- Talented web developers with strong portfolios are eager - to work for your company + Hire JavaScript engineers experienced in + HTML5, Node.js, MongoDB, and Agile Development

{ // if value exist, check if it is valid - if (props[prop].value && props[prop].type !== 'boolean') { - valid = valid && !!props[prop].valid; + if (pros[prop].value && pros[prop].type !== 'boolean') { + valid = valid && !!pros[prop].valid; } }); - if (!valid) { + if (!valid || !pros.isFrontEndCert && !pros.isFullStackCert ) { debug('form not valid'); return; } @@ -198,9 +187,9 @@ export default contain({ url, logo, company, - isHighlighted, - isFullStackCert, isFrontEndCert, + isFullStackCert, + isHighlighted, isRemoteOk, howToApply } = this.props; @@ -215,10 +204,10 @@ export default contain({ logo: formatUrl(uriInSingleQuotedAttr(logo.value), false), company: inHTMLData(company.value), isHighlighted: !!isHighlighted.value, - isFrontEndCert: !!isFrontEndCert.value, - isFullStackCert: !!isFullStackCert.value, isRemoteOk: !!isRemoteOk.value, - howToApply: inHTMLData(howToApply.value) + howToApply: inHTMLData(howToApply.value), + isFrontEndCert, + isFullStackCert }; const job = Object.keys(jobValues).reduce((accu, prop) => { @@ -245,6 +234,18 @@ export default contain({ handleForm({ [name]: value }); }, + handleCertClick(name) { + const { jobActions: { handleForm } } = this.props; + const otherButton = name === 'isFrontEndCert' ? + 'isFullStackCert' : + 'isFrontEndCert'; + + handleForm({ + [name]: true, + [otherButton]: false + }); + }, + render() { const { position, @@ -255,12 +256,13 @@ export default contain({ logo, company, isHighlighted, - isFrontEndCert, - isFullStackCert, isRemoteOk, howToApply, + isFrontEndCert, + isFullStackCert, jobActions: { handleForm } } = this.props; + const { handleChange } = this; const labelClass = 'col-sm-offset-1 col-sm-2'; const inputClass = 'col-sm-6'; @@ -277,7 +279,52 @@ export default contain({ onSubmit={ this.handleSubmit }>
-

First, tell us about the position

+

First, select your ideal applicant:

+
+ + + + + + +
+ + + + + +
+

Tell us about the position

- handleForm({ - isFrontEndCert: !!checked - }) - } - type='checkbox' - wrapperClassName={ checkboxClass } /> - handleForm({ - isFullStackCert: !!checked - }) - } - type='checkbox' - wrapperClassName={ checkboxClass } /> - - * { foo } -
diff --git a/common/app/routes/Jobs/components/Show.jsx b/common/app/routes/Jobs/components/Show.jsx index 2c4825bcff..85034e3242 100644 --- a/common/app/routes/Jobs/components/Show.jsx +++ b/common/app/routes/Jobs/components/Show.jsx @@ -1,4 +1,4 @@ -import React, { createClass } from 'react'; +import React, { PropTypes } from 'react'; import { History } from 'react-router'; import { contain } from 'thundercats-react'; @@ -6,12 +6,66 @@ import ShowJob from './ShowJob.jsx'; import JobNotFound from './JobNotFound.jsx'; import { isJobValid } from '../utils'; +function shouldShowApply( + { + isFrontEndCert: isFrontEndCertReq = false, + isFullStackCert: isFullStackCertReq = false + }, { + isFrontEndCert = false, + isFullStackCert = false + } +) { + return (!isFrontEndCertReq && !isFullStackCertReq) || + (isFullStackCertReq && isFullStackCert) || + (isFrontEndCertReq && isFrontEndCert); +} + +function generateMessage( + { + isFrontEndCert: isFrontEndCertReq = false, + isFullStackCert: isFullStackCertReq = false + }, + { + isFrontEndCert = false, + isFullStackCert = false, + isSignedIn = false + } +) { + + if (!isSignedIn) { + return 'Must be signed in to apply'; + } + if (isFrontEndCertReq && !isFrontEndCert) { + return 'This employer requires Free Code Camp’s Front ' + + 'End Development Certification in order to apply'; + } + if (isFullStackCertReq && !isFullStackCert) { + return 'This employer requires Free Code Camp’s Full ' + + 'Stack Development Certification in order to apply'; + } + if (isFrontEndCertReq && isFrontEndCertReq) { + return 'This employer requires the Front End Development Certification. ' + + "You've earned it, so feel free to apply."; + } + return 'This employer requires the Full Stack Development Certification. ' + + "You've earned it, so feel free to apply."; +} + export default contain( { - store: 'jobsStore', + stores: ['appStore', 'jobsStore'], + fetchWaitFor: 'jobsStore', fetchAction: 'jobActions.getJob', - map({ currentJob }) { - return { job: currentJob }; + combineLatest( + { username, isFrontEndCert, isFullStackCert }, + { currentJob } + ) { + return { + username, + job: currentJob, + isFrontEndCert, + isFullStackCert + }; }, getPayload({ params: { id }, job = {} }) { return { @@ -25,9 +79,16 @@ export default contain( return job.id !== id; } }, - createClass({ + React.createClass({ displayName: 'Show', + propTypes: { + job: PropTypes.object, + isFullStackCert: PropTypes.bool, + isFrontEndCert: PropTypes.bool, + username: PropTypes.string + }, + mixins: [History], componentDidMount() { @@ -39,12 +100,36 @@ export default contain( }, render() { - const { job } = this.props; + const { + isFullStackCert, + isFrontEndCert, + job, + username + } = this.props; if (!isJobValid(job)) { return ; } - return ; + + const isSignedIn = !!username; + + const showApply = shouldShowApply( + job, + { isFrontEndCert, isFullStackCert } + ); + + const message = generateMessage( + job, + { isFrontEndCert, isFullStackCert, isSignedIn } + ); + + return ( + + ); } }) ); diff --git a/common/app/routes/Jobs/components/ShowJob.jsx b/common/app/routes/Jobs/components/ShowJob.jsx index e9cddc55fc..db9cf511e5 100644 --- a/common/app/routes/Jobs/components/ShowJob.jsx +++ b/common/app/routes/Jobs/components/ShowJob.jsx @@ -22,7 +22,10 @@ export default React.createClass({ displayName: 'ShowJob', propTypes: { job: PropTypes.object, - params: PropTypes.object + params: PropTypes.object, + showApply: PropTypes.bool, + preview: PropTypes.bool, + message: PropTypes.string }, renderHeader({ company, position }) { @@ -38,8 +41,45 @@ export default React.createClass({ ); }, + renderHowToApply(showApply, preview, message, howToApply) { + if (!showApply) { + return ( + + +

{ message }

+ +
+ ); + } + + return ( + + + + { preview ? 'How do I apply?' : message } +
+
+ +
+ +
+ ); + }, + render() { - const { job = {} } = this.props; + const { + showApply = true, + message, + preview = true, + job = {} + } = this.props; + const { logo, position, @@ -93,20 +133,7 @@ export default React.createClass({

{ description }

- - - - How do I apply? -
-
- - -
-
+ { this.renderHowToApply(showApply, preview, message, howToApply) } diff --git a/common/app/routes/Jobs/components/TwitterBtn.jsx b/common/app/routes/Jobs/components/TwitterBtn.jsx index fb5f8edf12..7998353ffc 100644 --- a/common/app/routes/Jobs/components/TwitterBtn.jsx +++ b/common/app/routes/Jobs/components/TwitterBtn.jsx @@ -1,4 +1,4 @@ -import React, { createClass, PropTypes } from 'react'; +import React, { PropTypes } from 'react'; import { Button } from 'react-bootstrap'; const followLink = 'https://twitter.com/intent/follow?' + @@ -9,7 +9,7 @@ function commify(count) { return Number(count).toLocaleString('en'); } -export default createClass({ +export default React.createClass({ displayName: 'FollowButton', diff --git a/common/app/shared/displayCode/Display.jsx b/common/app/shared/displayCode/Display.jsx deleted file mode 100644 index 9eb60db8da..0000000000 --- a/common/app/shared/displayCode/Display.jsx +++ /dev/null @@ -1,51 +0,0 @@ -var React = require('react'), - Tailspin = require('tailspin'); - -var Editor = React.createClass({ - - propTypes: { - value: React.PropTypes.string - }, - - getDefaultProps: function() { - return { - value: [ - '/**', - '* Your output will go here.', - '* Console.log() -type statements', - '* will appear in your browser\'s', - '* DevTools JavaScript console.', - '**/' - ].join('\n') - }; - }, - - render: function() { - var value = this.props.value; - var options = { - lineNumbers: false, - lineWrapping: true, - mode: 'text', - readOnly: 'noCursor', - textAreaClassName: 'hide-textarea', - theme: 'monokai', - value: value - }; - - var config = { - setSize: ['100%', '100%'] - }; - - return ( -
-
- -
-
- ); - } -}); - -module.exports = Editor; diff --git a/common/app/shared/displayCode/index.js b/common/app/shared/displayCode/index.js deleted file mode 100644 index d3e3560174..0000000000 --- a/common/app/shared/displayCode/index.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('./Display.jsx'); diff --git a/common/app/shared/editor/Editor.jsx b/common/app/shared/editor/Editor.jsx deleted file mode 100644 index 8e72f5d4a0..0000000000 --- a/common/app/shared/editor/Editor.jsx +++ /dev/null @@ -1,91 +0,0 @@ -var React = require('react'), - debug = require('debug')('freecc:comp:editor'), - jshint = require('jshint').JSHINT, - Tailspin = require('tailspin'); - -var Editor = React.createClass({ - - propTypes: { - onValueChange: React.PropTypes.func, - value: React.PropTypes.string - }, - - getDefaultProps: function() { - return { - value: 'console.log(\'freeCodeCamp is awesome\')' - }; - }, - - getInitialState: function() { - return { - value: this.props.value - }; - }, - - render: function() { - var options = { - autoCloseBrackets: true, - gutters: ['CodeMirror-lint-markers'], - lint: true, - linter: jshint, - lineNumbers: true, - lineWrapping: true, - mode: 'javascript', - matchBrackets: true, - runnable: true, - scrollbarStyle: 'null', - theme: 'monokai', - textAreaClassName: 'hide-textarea', - value: this.state.value, - onChange: e => { - this.setState({ value: e.target.value}); - if (typeof this.props.onValueChange === 'function') { - this.props.onValueChange(e.target.value); - } - } - }; - - var config = { - setSize: ['100%', 'auto'], - extraKeys: { - Tab: function(cm) { - debug('tab pressed'); - if (cm.somethingSelected()) { - cm.indentSelection('add'); - } else { - var spaces = new Array(cm.getOption('indentUnit') + 1).join(' '); - cm.replaceSelection(spaces); - } - }, - 'Shift-Tab': function(cm) { - debug('shift-tab pressed'); - if (cm.somethingSelected()) { - cm.indentSelection('subtract'); - } else { - var spaces = new Array(cm.getOption('indentUnit') + 1).join(' '); - cm.replaceSelection(spaces); - } - }, - 'Ctrl-Enter': function() { - debug('C-enter pressed'); - // execute bonfire action - return false; - } - } - }; - - return ( -
-
-
- -
-
-
- ); - } -}); - -module.exports = Editor; diff --git a/common/app/shared/editor/index.js b/common/app/shared/editor/index.js deleted file mode 100644 index 5e431fabff..0000000000 --- a/common/app/shared/editor/index.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('./Editor.jsx'); diff --git a/package.json b/package.json index 537afc32fd..cae0dc2347 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "lint-server": "jsonlint -q server/*.json", "lint-resources": "jsonlint -q server/resources/*.json", "lint-utils": "jsonlint -q server/utils/*.json", - "lint-js": "eslint --ext=.js,.jsx server/ common/models common/utils config/", + "lint-js": "eslint --ext=.js,.jsx server/ common/ common/utils config/ client/", "lint-json": "npm run lint-server && npm run lint-nonprofits && npm run lint-challenges && npm run lint-resources && npm run lint-utils", "test-challenges": "babel-node seed/test-challenges.js | tnyan", "pretest": "npm run lint",