├── .gitignore ├── .npmignore ├── Cakefile ├── LICENCE ├── README.md ├── bin └── cjsx ├── example ├── car.cjsx ├── cool-component.cjsx ├── cool-component.coffee ├── log-component.coffee ├── named-component.coffee ├── neat-component.cjsx ├── neat-component.js ├── some-class.coffee └── watch.cjsx ├── package.json ├── register.js ├── src ├── browser.coffee ├── coffee-react-script.coffee ├── command.coffee ├── helpers.coffee └── register.coffee ├── test ├── compile.coffee ├── expected │ ├── compiled-output.js │ └── rendered-output.html ├── require.coffee └── run.coffee └── update.sh /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore docs files 2 | _gh_pages 3 | _site 4 | .ruby-version 5 | 6 | # Numerous always-ignore extensions 7 | *.diff 8 | *.err 9 | *.orig 10 | *.log 11 | *.rej 12 | *.swo 13 | *.swp 14 | *.zip 15 | *.vi 16 | *~ 17 | 18 | # OS or Editor folders 19 | .DS_Store 20 | ._* 21 | Thumbs.db 22 | .cache 23 | .project 24 | .settings 25 | .tmproj 26 | *.esproj 27 | nbproject 28 | *.sublime-project 29 | *.sublime-workspace 30 | .idea 31 | 32 | # Komodo 33 | *.komodoproject 34 | .komodotools 35 | 36 | # Folders to ignore 37 | node_modules 38 | bower_components 39 | 40 | lib/ 41 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src/ 2 | test/ 3 | examples/ 4 | Cakefile 5 | *.sh -------------------------------------------------------------------------------- /Cakefile: -------------------------------------------------------------------------------- 1 | fs = require 'fs' 2 | path = require 'path' 3 | {spawn, exec} = require 'child_process' 4 | 5 | {DEBUG} = process.env 6 | 7 | COFFEE_EXECUTABLE = require.resolve('coffee-script/bin/coffee') 8 | 9 | # ANSI Terminal Colors. 10 | bold = red = green = reset = '' 11 | unless process.env.NODE_DISABLE_COLORS 12 | bold = '\x1B[0;1m' 13 | red = '\x1B[0;31m' 14 | green = '\x1B[0;32m' 15 | reset = '\x1B[0m' 16 | 17 | # Log a message with a color. 18 | log = (message, color, explanation) -> 19 | console.log color + message + reset + ' ' + (explanation or '') 20 | 21 | # Build transformer from source. 22 | build = (cb) -> 23 | files = fs.readdirSync('./src') 24 | .filter((f) -> /\.coffee$/.test(f)) 25 | .map(((f) -> path.basename(f, '.coffee'))) 26 | 27 | run 'mkdir', ['-p','bin', 'lib'], -> 28 | compile files, 'src/', 'lib/', cb 29 | 30 | compile = (srcFiles, srcDir, destDir, cb) -> 31 | srcFilePaths = srcFiles.map (filename) -> path.join(srcDir, "#{filename}.coffee") 32 | args = ['--bare', '-o', destDir, '--compile'].concat srcFilePaths 33 | coffee args, cb 34 | 35 | # Run CoffeeScript command 36 | coffee = (args, cb) -> run COFFEE_EXECUTABLE, args, cb 37 | 38 | run = (executable, args = [], cb) -> 39 | console.log "#{path.basename executable} #{args.join(' ')}" if DEBUG 40 | proc = spawn executable, args 41 | proc.stdout.on 'data', (buffer) -> log buffer.toString(), green 42 | proc.stderr.on 'data', (buffer) -> log buffer.toString(), red 43 | proc.on 'exit', (status) -> cb(status) if typeof cb is 'function' 44 | 45 | test = -> 46 | run 'tap', ['test/*.coffee'], (status) -> 47 | if status == 0 48 | log 'pass', green 49 | else 50 | log 'fail', red 51 | 52 | task 'build', 'build coffee-react from source', build 53 | 54 | task 'test', 'test coffee-react', test 55 | 56 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 James Friend 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 13 | all 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 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Coffee-React 2 | 3 | **[CoffeeScript 2 has JSX built in. You should use that instead](http://coffeescript.org/v2/#jsx)** 4 | 5 | # STATUS: DEPRECATED 6 | 7 | This tool is no longer maintained. If you need to transition your codebase from 8 | it, a codemod is available to do so: [cjsx-codemod](https://github.com/jsdf/cjsx-codemod). You can also use [CoffeeScript 2, which has JSX built in](http://coffeescript.org/v2/#jsx). 9 | 10 | This project started as a way for me to explore how JSX could fit into 11 | Coffeescript syntax, as a quickly hacked together prototype. While I never 12 | really promoted it, it quickly took on a life of its own, and before long people 13 | were asking for it to support all kinds of different use cases. On top of that I 14 | had no experience writing parsers, so the result is something with 15 | [insurmountable limitations](https://github.com/jsdf/coffee-react/issues/32). 16 | 17 | As I eventually stopped using Coffeescript I ended up neglecting this project, 18 | but as people were using it I didn't want to kill it. I really should have, 19 | however, because it meant that people were using a crappy, ill-conceived, 20 | unmaintained tool. Now, long overdue, I'm putting it out to pasture. 21 | 22 | Original readme follows: 23 | 24 | Coffee-React provides a JSX-like syntax for building [React](http://facebook.github.io/react/) components with the full awesomeness of CoffeeScript. 25 | 26 | [Try it out](https://jsdf.github.io/coffee-react-transform/). 27 | 28 | Included is the `cjsx` executable, which is wrapper for `coffee`, using 29 | [coffee-react-transform](https://github.com/jsdf/coffee-react-transform) and 30 | [coffee-script](https://github.com/jashkenas/coffeescript) to transform CJSX to Javascript. 31 | You can also `require()` CJSX components under [node](http://nodejs.org) for server-side rendering. 32 | 33 | ### Example 34 | 35 | neat-component.cjsx 36 | ```coffee 37 | NeatComponent = React.createClass 38 | render: -> 39 |
40 | {

A Component is I

if @props.showTitle} 41 |
42 | {

This line has been printed {n} times

for n in [1..5]} 43 |
44 | ``` 45 | 46 | compile it 47 | ```bash 48 | $ cjsx -cb neat-component.cjsx 49 | ``` 50 | 51 | neat-component.js 52 | ```js 53 | // Generated by CoffeeScript 1.9.1 54 | var NeatComponent; 55 | 56 | NeatComponent = React.createClass({displayName: "NeatComponent", 57 | render: function() { 58 | var n; 59 | return React.createElement("div", { 60 | "className": "neat-component" 61 | }, (this.props.showTitle ? React.createElement("h1", null, "A Component is I") : void 0), React.createElement("hr", null), (function() { 62 | var i, results; 63 | results = []; 64 | for (n = i = 1; i <= 5; n = ++i) { 65 | results.push(React.createElement("p", { 66 | "key": n 67 | }, "This line has been printed ", n, " times")); 68 | } 69 | return results; 70 | })()); 71 | } 72 | }); 73 | ``` 74 | 75 | ### Installation 76 | ```bash 77 | npm install -g coffee-react 78 | ``` 79 | 80 | #### Version compatibility 81 | - 5.x - React 0.13.x - 0.15.x 82 | - 4.x - React 0.13.x - 0.14.x 83 | - 3.x - React 0.13.x - 0.14.x 84 | - 2.1.x - React 0.12.1 85 | - 2.x - React 0.12 86 | - 1.x - React 0.11.2 87 | - 0.x - React 0.11 and below 88 | 89 | ### Usage 90 | 91 | ``` 92 | $ cjsx -h 93 | 94 | Usage: cjsx [options] path/to/script.cjsx -- [args] 95 | 96 | If called without options, `cjsx` will run your script. 97 | 98 | -b, --bare compile without a top-level function wrapper 99 | -c, --compile compile to JavaScript and save as .js files 100 | -e, --eval pass a string from the command line as input 101 | -h, --help display this help message 102 | -j, --join concatenate the source CoffeeScript before compiling 103 | -m, --map generate source map and save as .map files 104 | -n, --nodes print out the parse tree that the parser produces 105 | --nodejs pass options directly to the "node" binary 106 | --no-header suppress the "Generated by" header 107 | -o, --output set the output directory for compiled JavaScript 108 | -p, --print print out the compiled JavaScript 109 | -s, --stdio listen for and compile scripts over stdio 110 | -l, --literate treat stdio as literate style coffee-script 111 | -t, --tokens print out the tokens that the lexer/rewriter produce 112 | -v, --version display the version number 113 | -w, --watch watch scripts for changes and rerun commands 114 | 115 | ``` 116 | 117 | Output compiled JS to a file of the same name: 118 | ```bash 119 | $ cjsx -c my-component.cjsx 120 | ``` 121 | 122 | #### Require .cjsx files under node 123 | As with the `coffee-script` module, you need to register `.cjsx` with the module loader: 124 | ```coffee 125 | require('coffee-react/register') 126 | 127 | Component = require('./component.cjsx') 128 | 129 | ``` 130 | 131 | ### Spread attributes 132 | JSX/CJSX 'spread attributes' allow merging in an object of props when creating an element, eg: 133 | ```coffee 134 | extraProps = color: 'red', speed: 'fast' 135 |
136 | ``` 137 | which is transformed to: 138 | ```coffee 139 | extraProps = color: 'red', speed: 'fast' 140 | React.createElement("div", Object.assign({"color": "blue"}, extraProps) 141 | ``` 142 | 143 | If you use this syntax in your code, be sure to include a shim for `Object.assign` for browsers/environments which don't yet support it. [object.assign](https://www.npmjs.org/package/object.assign), [core-js](https://github.com/zloirock/core-js) and 144 | [es6-shim](https://github.com/es-shims/es6-shim) are some possible choices. 145 | 146 | ### React.createElement 147 | 148 | React 0.12 introduced changes to the way component descriptors are constructed, where the return value of `React.createClass` is not a descriptor factory but simply the component class itself, and descriptors must be created manually using `React.createElement` or by wrapping the component class with `React.createDescriptor`. 149 | 150 | coffee-react-transform (and as a result, coffee-react) now outputs calls to `React.createElement` to construct element descriptors from component classes for you, so you won't need to [wrap your classes using `React.createFactory`](https://gist.github.com/sebmarkbage/ae327f2eda03bf165261). However, for this to work you will need to be using at least React 0.11.2, which adds `React.createElement`. 151 | 152 | If you want the older style JSX output (which just desugars into function calls) then you need to use the 0.x branch, eg. 0.5.1. 153 | 154 | Additionally, as of 1.0.0, all input files will be CJSX transformed, even if they don't have a `.cjsx` extension or `# @cjsx` pragma. 155 | 156 | ### Related projects 157 | - [coffee-react-transform](https://github.com/jsdf/coffee-react-transform), the underlying parser/transformer package. 158 | - [node-cjsx](https://github.com/SimonDegraeve/node-cjsx): `require` CJSX files on the server (also possible with [coffee-react/register](https://github.com/jsdf/coffee-react)). 159 | - [coffee-reactify](https://github.com/jsdf/coffee-reactify): bundle CJSX files via [browserify](https://github.com/substack/node-browserify), see also [cjsxify](https://github.com/SimonDegraeve/cjsxify). 160 | - [react-coffee-quickstart](https://github.com/SimonDegraeve/react-coffee-quickstart): equivalent to [react-quickstart](https://github.com/andreypopp/react-quickstart). 161 | - [sprockets preprocessor](https://github.com/jsdf/sprockets-coffee-react): use CJSX with Rails/Sprockets 162 | - [ruby coffee-react gem](https://github.com/jsdf/ruby-coffee-react): transform CJSX to Coffeescript under Ruby 163 | - [vim plugin](https://github.com/mtscout6/vim-cjsx) for syntax highlighting 164 | - [sublime text package](https://github.com/Guidebook/sublime-cjsx) for syntax highlighting 165 | - [mimosa plugin](https://github.com/mtscout6/mimosa-cjsx) for the mimosa build tool 166 | - [gulp plugin](https://github.com/mtscout6/gulp-cjsx) for the gulp build tool 167 | - [karma preprocessor](https://github.com/mtscout6/karma-cjsx-preprocessor) for karma test runner 168 | -------------------------------------------------------------------------------- /bin/cjsx: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var path = require('path'); 4 | var fs = require('fs'); 5 | var lib = path.join(path.dirname(fs.realpathSync(__filename)), '../lib'); 6 | 7 | require(lib + '/command').run(); 8 | -------------------------------------------------------------------------------- /example/car.cjsx: -------------------------------------------------------------------------------- 1 | # @cjsx React.DOM 2 | 3 | React = require('react') 4 | 5 | Car = React.createClass 6 | render: -> 7 | 8 | 9 | 10 |

Which seat can I take? {@props.seat}

11 |
12 | 13 | React.renderComponent , 14 | document.getElementById 'container' 15 | -------------------------------------------------------------------------------- /example/cool-component.cjsx: -------------------------------------------------------------------------------- 1 | React = require 'react' 2 | 3 | CoolComponent = React.createClass 4 | render: -> 5 |
6 |

is this component neat? {@props.neat}

7 |
8 | 9 | module.exports = CoolComponent -------------------------------------------------------------------------------- /example/cool-component.coffee: -------------------------------------------------------------------------------- 1 | React = require 'react' 2 | 3 | CoolComponent = React.createClass 4 | render: -> 5 |
6 |

is this component neat? {@props.neat}

7 |
8 | 9 | module.exports = CoolComponent -------------------------------------------------------------------------------- /example/log-component.coffee: -------------------------------------------------------------------------------- 1 | util = require('util') 2 | 3 | SomeClass = require './some-class.coffee' 4 | React = require 'react' 5 | 6 | process.stdout.write React.renderToStaticMarkup(React.createElement(SomeClass)) 7 | -------------------------------------------------------------------------------- /example/named-component.coffee: -------------------------------------------------------------------------------- 1 | React = require 'react' 2 | 3 | NamedComponent = React.createClass 4 | render: -> 5 | extraProps = 6 | style: 7 | color: 'red' 8 | width: 10 9 | 12 | -------------------------------------------------------------------------------- /example/neat-component.cjsx: -------------------------------------------------------------------------------- 1 | NeatComponent = React.createClass 2 | render: -> 3 |
4 | {

A Component is I

if @props.showTitle} 5 |
6 | {

This line has been printed {n} times

for n in [1..5]} 7 |
-------------------------------------------------------------------------------- /example/neat-component.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.8.0 2 | var NeatComponent; 3 | 4 | NeatComponent = React.createClass({ 5 | render: function() { 6 | var n; 7 | return React.createElement("div", { 8 | "className": "neat-component" 9 | }, (this.props.showTitle ? React.createElement("h1", null, "A Component is I") : void 0), React.createElement("hr", null), (function() { 10 | var _i, _results; 11 | _results = []; 12 | for (n = _i = 1; _i <= 10; n = ++_i) { 13 | _results.push(React.createElement("p", null, "This line has been printed ", n, " times")); 14 | } 15 | return _results; 16 | })()); 17 | } 18 | }); 19 | -------------------------------------------------------------------------------- /example/some-class.coffee: -------------------------------------------------------------------------------- 1 | React = require 'react' 2 | 3 | module.exports = React.createClass 4 | render: -> 5 | extraProps = 6 | style: 7 | color: 'red' 8 | width: 10 9 | 12 | -------------------------------------------------------------------------------- /example/watch.cjsx: -------------------------------------------------------------------------------- 1 | {classSet} = React.addons 2 | 3 | Watch = React.createClass 4 | componentDidMount: -> @tick new Date 5 | 6 | tick: (prevTime) -> 7 | return unless @isMounted() 8 | currentTime = new Date 9 | @setState {currentTime} 10 | timeTilNextTick = currentTime.getTime() - prevTime.getTime() - 1000 11 | setInterval (=> @tick currentTime), timeTilNextTick 12 | 13 | renderDate: -> 14 |
{@state.currentTime.toLocaleDateString()}
15 | 16 | render: -> 17 | {currentTime} = @state 18 |
19 | {currentTime.getHours()} 20 | : {currentTime.getMinutes()} 21 | { : {currentTime.getSeconds()} if @props.showSeconds} 22 | {@renderDate()} 23 |
24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "coffee-react", 3 | "description": "Coffeescript compiler wrapper adding coffee-react-transform CJSX support", 4 | "keywords": [ 5 | "coffeescript", 6 | "react", 7 | "jsx", 8 | "cjsx", 9 | "coffee-react" 10 | ], 11 | "author": "James Friend", 12 | "version": "5.0.1", 13 | "licenses": [ 14 | { 15 | "type": "MIT", 16 | "url": "https://raw.github.com/jsdf/coffee-react/master/LICENSE" 17 | } 18 | ], 19 | "files": [ 20 | "register.js", 21 | "bin/", 22 | "lib/" 23 | ], 24 | "main": "./lib/coffee-react-script", 25 | "bin": { 26 | "cjsx": "./bin/cjsx" 27 | }, 28 | "scripts": { 29 | "prepublish": "cake build", 30 | "test": "tap test/*.coffee" 31 | }, 32 | "homepage": "https://github.com/jsdf/coffee-react", 33 | "bugs": "https://github.com/jsdf/coffee-react/issues", 34 | "repository": { 35 | "type": "git", 36 | "url": "git://github.com/jsdf/coffee-react.git" 37 | }, 38 | "dependencies": { 39 | "coffee-react-jstransform": "^1.0.0", 40 | "coffee-react-transform": "^4.0.0", 41 | "coffee-script": "^1.10.0", 42 | "mkdirp": "^0.5.0" 43 | }, 44 | "devDependencies": { 45 | "concat-stream": "^1.4.5", 46 | "react": "^0.13.1", 47 | "tap": "^0.7.1" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /register.js: -------------------------------------------------------------------------------- 1 | require('./lib/register'); 2 | -------------------------------------------------------------------------------- /src/browser.coffee: -------------------------------------------------------------------------------- 1 | # This **Browser** compatibility layer extends core CoffeeScript functions 2 | # to make things work smoothly when compiling code directly in the browser. 3 | # We add support for loading remote Coffee scripts via **XHR**, and 4 | # `text/coffeescript` script tags, source maps via data-URLs, and so on. 5 | 6 | CoffeeScript = require './coffee-react-script' 7 | CoffeeScript.require = require 8 | compile = CoffeeScript.compile 9 | 10 | # Use standard JavaScript `eval` to eval code. 11 | CoffeeScript.eval = (code, options = {}) -> 12 | options.bare ?= on 13 | eval compile code, options 14 | 15 | # Running code does not provide access to this scope. 16 | CoffeeScript.run = (code, options = {}) -> 17 | options.bare = on 18 | options.shiftLine = on 19 | Function(compile code, options)() 20 | 21 | # If we're not in a browser environment, we're finished with the public API. 22 | return unless window? 23 | 24 | # Include source maps where possible. If we've got a base64 encoder, a 25 | # JSON serializer, and tools for escaping unicode characters, we're good to go. 26 | # Ported from https://developer.mozilla.org/en-US/docs/DOM/window.btoa 27 | if btoa? and JSON? and unescape? and encodeURIComponent? 28 | compile = (code, options = {}) -> 29 | options.sourceMap = true 30 | options.inline = true 31 | {js, v3SourceMap} = CoffeeScript.compile code, options 32 | "#{js}\n//# sourceMappingURL=data:application/json;base64,#{btoa unescape encodeURIComponent v3SourceMap}\n//# sourceURL=coffeescript" 33 | 34 | # Load a remote script from the current domain via XHR. 35 | CoffeeScript.load = (url, callback, options = {}, hold = false) -> 36 | options.sourceFiles = [url] 37 | xhr = if window.ActiveXObject 38 | new window.ActiveXObject('Microsoft.XMLHTTP') 39 | else 40 | new window.XMLHttpRequest() 41 | xhr.open 'GET', url, true 42 | xhr.overrideMimeType 'text/plain' if 'overrideMimeType' of xhr 43 | xhr.onreadystatechange = -> 44 | if xhr.readyState is 4 45 | if xhr.status in [0, 200] 46 | param = [xhr.responseText, options] 47 | CoffeeScript.run param... unless hold 48 | else 49 | throw new Error "Could not load #{url}" 50 | callback param if callback 51 | xhr.send null 52 | 53 | # Activate CoffeeScript in the browser by having it compile and evaluate 54 | # all script tags with a content-type of `text/coffeescript`. 55 | # This happens on page load. 56 | runScripts = -> 57 | scripts = window.document.getElementsByTagName 'script' 58 | coffeetypes = ['text/cjsx', 'text/coffeescript', 'text/literate-coffeescript'] 59 | coffees = (s for s in scripts when s.type in coffeetypes) 60 | index = 0 61 | 62 | execute = -> 63 | param = coffees[index] 64 | if param instanceof Array 65 | CoffeeScript.run param... 66 | index++ 67 | execute() 68 | 69 | for script, i in coffees 70 | do (script, i) -> 71 | options = literate: script.type is coffeetypes[1] 72 | source = script.src or script.getAttribute('data-src') 73 | if source 74 | CoffeeScript.load source, 75 | (param) -> 76 | coffees[i] = param 77 | execute() 78 | options 79 | true 80 | else 81 | options.sourceFiles = ['embedded'] 82 | coffees[i] = [script.innerHTML, options] 83 | 84 | execute() 85 | 86 | # Listen for window load, both in decent browsers and in IE. 87 | if window.addEventListener 88 | window.addEventListener 'DOMContentLoaded', runScripts, no 89 | else 90 | window.attachEvent 'onload', runScripts -------------------------------------------------------------------------------- /src/coffee-react-script.coffee: -------------------------------------------------------------------------------- 1 | # patches coffee-script module 2 | 3 | fs = require 'fs' 4 | transform = require 'coffee-react-transform' 5 | 6 | helpers = require './helpers' 7 | 8 | CoffeeScript = require 'coffee-script/lib/coffee-script/coffee-script' 9 | 10 | jsSyntaxTransform = require 'coffee-react-jstransform' 11 | 12 | unless CoffeeScript._cjsx 13 | 14 | CoffeeScript._cjsx = yes 15 | 16 | CoffeeScript.FILE_EXTENSIONS.push '.cjsx' 17 | 18 | CoffeeScript.register = -> require './register' 19 | 20 | # real coffeescript compile func, which we're wrapping 21 | CoffeeScript._csCompile = CoffeeScript.compile 22 | 23 | CoffeeScript.compile = (code, options = {}) -> 24 | input = transform(code, options) 25 | 26 | output = CoffeeScript._csCompile input, options 27 | 28 | return output if options.noJSTransforms 29 | 30 | if typeof output == 'string' 31 | jsSyntaxTransform(output) 32 | else 33 | output.js = jsSyntaxTransform(output.js) 34 | output 35 | 36 | CoffeeScript._compileFile = (filename, sourceMap = no) -> 37 | raw = fs.readFileSync filename, 'utf8' 38 | stripped = if raw.charCodeAt(0) is 0xFEFF then raw.substring 1 else raw 39 | 40 | try 41 | answer = CoffeeScript.compile(stripped, {filename, sourceMap, literate: helpers.isLiterate filename}) 42 | catch err 43 | # As the filename and code of a dynamically loaded file will be different 44 | # from the original file compiled with CoffeeScript.run, add that 45 | # information to error so it can be pretty-printed later. 46 | throw helpers.updateSyntaxError err, stripped, filename 47 | 48 | answer 49 | 50 | CoffeeScript.hasCJSXPragma = helpers.hasCJSXPragma 51 | CoffeeScript.hasCJSXExtension = helpers.hasCJSXExtension 52 | CoffeeScript.transform = transform 53 | 54 | module.exports = CoffeeScript 55 | -------------------------------------------------------------------------------- /src/command.coffee: -------------------------------------------------------------------------------- 1 | # The `cjsx` utility. Handles command-line compilation of CoffeeScript 2 | # into various forms: saved into `.js` files or printed to stdout 3 | # or recompiled every time the source is saved, 4 | # printed as a token stream or as the syntax tree, or launch an 5 | # interactive REPL. 6 | 7 | # External dependencies. 8 | fs = require 'fs' 9 | path = require 'path' 10 | helpers = require './helpers' 11 | optparse = require 'coffee-script/lib/coffee-script/optparse' 12 | CoffeeScript = require './coffee-react-script' 13 | mkdirp = require 'mkdirp' 14 | {spawn, exec} = require 'child_process' 15 | {EventEmitter} = require 'events' 16 | cjsxTransform = require 'coffee-react-transform' 17 | 18 | useWinPathSep = path.sep is '\\' 19 | 20 | # Allow CoffeeScript to emit Node.js events. 21 | helpers.extend CoffeeScript, new EventEmitter 22 | 23 | printLine = (line) -> process.stdout.write line + '\n' 24 | printWarn = (line) -> process.stderr.write line + '\n' 25 | 26 | hidden = (file) -> /^\.|~$/.test file 27 | 28 | # The help banner that is printed in conjunction with `-h`/`--help`. 29 | BANNER = ''' 30 | Usage: cjsx [options] path/to/script.cjsx -- [args] 31 | 32 | If called without options, `cjsx` will run your script. 33 | ''' 34 | 35 | # The list of all the valid option flags that `cjsx` knows how to handle. 36 | SWITCHES = [ 37 | ['-b', '--bare', 'compile without a top-level function wrapper'] 38 | ['-c', '--compile', 'compile to JavaScript and save as .js files'] 39 | ['-e', '--eval', 'pass a string from the command line as input'] 40 | ['-h', '--help', 'display this help message'] 41 | # ['-i', '--interactive', 'run an interactive CoffeeScript REPL'] 42 | ['-j', '--join [FILE]', 'concatenate the source CoffeeScript before compiling'] 43 | ['-m', '--map', 'generate source map and save as .map files'] 44 | ['-n', '--nodes', 'print out the parse tree that the parser produces'] 45 | [ '--nodejs [ARGS]', 'pass options directly to the "node" binary'] 46 | [ '--no-header', 'suppress the "Generated by" header'] 47 | ['-o', '--output [DIR]', 'set the output directory for compiled JavaScript'] 48 | ['-p', '--print', 'print out the compiled JavaScript'] 49 | ['-s', '--stdio', 'listen for and compile scripts over stdio'] 50 | ['-l', '--literate', 'treat stdio as literate style coffee-script'] 51 | ['-t', '--tokens', 'print out the tokens that the lexer/rewriter produce'] 52 | ['-v', '--version', 'display the version number'] 53 | ['-w', '--watch', 'watch scripts for changes and rerun commands'] 54 | ] 55 | 56 | # Top-level objects shared by all the functions. 57 | opts = {} 58 | sources = [] 59 | sourceCode = [] 60 | notSources = {} 61 | watchedDirs = {} 62 | optionParser = null 63 | 64 | # Run `cjsx` by parsing passed options and determining what action to take. 65 | # Many flags cause us to divert before compiling anything. Flags passed after 66 | # `--` will be passed verbatim to your script as arguments in `process.argv` 67 | exports.run = -> 68 | parseOptions() 69 | # Make the REPL *CLI* use the global context so as to (a) be consistent with the 70 | # `node` REPL CLI and, therefore, (b) make packages that modify native prototypes 71 | # (such as 'colors' and 'sugar') work as expected. 72 | replCliOpts = useGlobal: yes 73 | return forkNode() if opts.nodejs 74 | return usage() if opts.help 75 | return version() if opts.version 76 | # return require('./repl').start(replCliOpts) if opts.interactive 77 | return compileStdio() if opts.stdio 78 | return compileScript null, opts.arguments[0] if opts.eval 79 | # return require('./repl').start(replCliOpts) unless opts.arguments.length 80 | return usage() unless opts.arguments.length 81 | literals = if opts.run then opts.arguments.splice 1 else [] 82 | process.argv = process.argv[0..1].concat literals 83 | process.argv[0] = 'cjsx' 84 | 85 | opts.output = path.resolve opts.output if opts.output 86 | opts.join = path.resolve opts.join if opts.join 87 | for source in opts.arguments 88 | source = path.resolve source 89 | compilePath source, yes, source 90 | 91 | # Compile a path, which could be a script or a directory. If a directory 92 | # is passed, recursively compile all '.coffee', '.litcoffee', and '.coffee.md' 93 | # extension source files in it and all subdirectories. 94 | compilePath = (source, topLevel, base) -> 95 | return if source in sources or 96 | watchedDirs[source] or 97 | not topLevel and (notSources[source] or hidden source) 98 | try 99 | stats = fs.statSync source 100 | catch err 101 | if err.code is 'ENOENT' 102 | console.error "File not found: #{source}" 103 | process.exit 1 104 | throw err 105 | if stats.isDirectory() 106 | if path.basename(source) is 'node_modules' 107 | notSources[source] = yes 108 | return 109 | if opts.run 110 | compilePath findDirectoryIndex(source), topLevel, base 111 | return 112 | watchDir source, base if opts.watch 113 | try 114 | files = fs.readdirSync source 115 | catch err 116 | if err.code is 'ENOENT' then return else throw err 117 | for file in files 118 | compilePath (path.join source, file), no, base 119 | else if topLevel or helpers.isCoffee source 120 | sources.push source 121 | sourceCode.push null 122 | delete notSources[source] 123 | watch source, base if opts.watch 124 | try 125 | code = fs.readFileSync source 126 | catch err 127 | if err.code is 'ENOENT' then return else throw err 128 | compileScript(source, code.toString(), base) 129 | else 130 | notSources[source] = yes 131 | 132 | findDirectoryIndex = (source) -> 133 | for ext in CoffeeScript.FILE_EXTENSIONS 134 | index = path.join source, "index#{ext}" 135 | try 136 | return index if (fs.statSync index).isFile() 137 | catch err 138 | throw err unless err.code is 'ENOENT' 139 | console.error "Missing index.coffee or index.litcoffee in #{source}" 140 | process.exit 1 141 | 142 | # Compile a single source script, containing the given code, according to the 143 | # requested options. If evaluating the script directly sets `__filename`, 144 | # `__dirname` and `module.filename` to be correct relative to the script's path. 145 | compileScript = (file, cjsxinput, base = null) -> 146 | o = opts 147 | options = compileOptions file, base 148 | try 149 | # detect and transform cjsx 150 | if (file? and helpers.hasCJSXExtension file) or helpers.hasCJSXPragma cjsxinput 151 | input = cjsxTransform cjsxinput 152 | else 153 | input = cjsxinput 154 | 155 | t = task = {file, input, options} 156 | CoffeeScript.emit 'compile', task 157 | if o.tokens 158 | printTokens CoffeeScript.tokens t.input, t.options 159 | else if o.nodes 160 | printLine CoffeeScript.nodes(t.input, t.options).toString().trim() 161 | else if o.run 162 | CoffeeScript.register() 163 | CoffeeScript.run t.input, t.options 164 | else if o.join and t.file isnt o.join 165 | t.input = helpers.invertLiterate t.input if helpers.isLiterate file 166 | sourceCode[sources.indexOf(t.file)] = t.input 167 | compileJoin() 168 | else 169 | compiled = CoffeeScript.compile t.input, t.options 170 | t.output = compiled 171 | if o.map 172 | t.output = compiled.js 173 | t.sourceMap = compiled.v3SourceMap 174 | 175 | CoffeeScript.emit 'success', task 176 | if o.print 177 | printLine t.output.trim() 178 | else if o.compile or o.map 179 | writeJs base, t.file, t.output, options.jsPath, t.sourceMap 180 | catch err 181 | CoffeeScript.emit 'failure', err, task 182 | return if CoffeeScript.listeners('failure').length 183 | message = err.stack or "#{err}" 184 | if o.watch 185 | printLine message + '\x07' 186 | else 187 | printWarn message 188 | process.exit 1 189 | 190 | # Attach the appropriate listeners to compile scripts incoming over **stdin**, 191 | # and write them back to **stdout**. 192 | compileStdio = -> 193 | code = '' 194 | stdin = process.openStdin() 195 | stdin.on 'data', (buffer) -> 196 | code += buffer.toString() if buffer 197 | stdin.on 'end', -> 198 | compileScript null, code 199 | 200 | # If all of the source files are done being read, concatenate and compile 201 | # them together. 202 | joinTimeout = null 203 | compileJoin = -> 204 | return unless opts.join 205 | unless sourceCode.some((code) -> code is null) 206 | clearTimeout joinTimeout 207 | joinTimeout = wait 100, -> 208 | compileScript opts.join, sourceCode.join('\n'), opts.join 209 | 210 | # Watch a source CoffeeScript file using `fs.watch`, recompiling it every 211 | # time the file is updated. May be used in combination with other options, 212 | # such as `--print`. 213 | watch = (source, base) -> 214 | watcher = null 215 | prevStats = null 216 | compileTimeout = null 217 | 218 | watchErr = (err) -> 219 | throw err unless err.code is 'ENOENT' 220 | return unless source in sources 221 | try 222 | rewatch() 223 | compile() 224 | catch 225 | removeSource source, base 226 | compileJoin() 227 | 228 | compile = -> 229 | clearTimeout compileTimeout 230 | compileTimeout = wait 25, -> 231 | fs.stat source, (err, stats) -> 232 | return watchErr err if err 233 | return rewatch() if prevStats and 234 | stats.size is prevStats.size and 235 | stats.mtime.getTime() is prevStats.mtime.getTime() 236 | prevStats = stats 237 | fs.readFile source, (err, code) -> 238 | return watchErr err if err 239 | compileScript(source, code.toString(), base) 240 | rewatch() 241 | 242 | startWatcher = -> 243 | watcher = fs.watch source 244 | .on 'change', compile 245 | .on 'error', (err) -> 246 | throw err unless err.code is 'EPERM' 247 | removeSource source, base 248 | 249 | rewatch = -> 250 | watcher?.close() 251 | startWatcher() 252 | 253 | try 254 | startWatcher() 255 | catch err 256 | watchErr err 257 | 258 | # Watch a directory of files for new additions. 259 | watchDir = (source, base) -> 260 | watcher = null 261 | readdirTimeout = null 262 | 263 | startWatcher = -> 264 | watcher = fs.watch source 265 | .on 'error', (err) -> 266 | throw err unless err.code is 'EPERM' 267 | stopWatcher() 268 | .on 'change', -> 269 | clearTimeout readdirTimeout 270 | readdirTimeout = wait 25, -> 271 | try 272 | files = fs.readdirSync source 273 | catch err 274 | throw err unless err.code is 'ENOENT' 275 | return stopWatcher() 276 | for file in files 277 | compilePath (path.join source, file), no, base 278 | 279 | stopWatcher = -> 280 | watcher.close() 281 | removeSourceDir source, base 282 | 283 | watchedDirs[source] = yes 284 | try 285 | startWatcher() 286 | catch err 287 | throw err unless err.code is 'ENOENT' 288 | 289 | removeSourceDir = (source, base) -> 290 | delete watchedDirs[source] 291 | sourcesChanged = no 292 | for file in sources when source is path.dirname file 293 | removeSource file, base 294 | sourcesChanged = yes 295 | compileJoin() if sourcesChanged 296 | 297 | # Remove a file from our source list, and source code cache. Optionally remove 298 | # the compiled JS version as well. 299 | removeSource = (source, base) -> 300 | index = sources.indexOf source 301 | sources.splice index, 1 302 | sourceCode.splice index, 1 303 | unless opts.join 304 | silentUnlink outputPath source, base 305 | silentUnlink outputPath source, base, '.map' 306 | timeLog "removed #{source}" 307 | 308 | silentUnlink = (path) -> 309 | try 310 | fs.unlinkSync path 311 | catch err 312 | throw err unless err.code in ['ENOENT', 'EPERM'] 313 | 314 | # Get the corresponding output JavaScript path for a source file. 315 | outputPath = (source, base, extension=".js") -> 316 | basename = helpers.baseFileName source, yes, useWinPathSep 317 | srcDir = path.dirname source 318 | if not opts.output 319 | dir = srcDir 320 | else if source is base 321 | dir = opts.output 322 | else 323 | dir = path.join opts.output, path.relative base, srcDir 324 | path.join dir, basename + extension 325 | 326 | # Write out a JavaScript source file with the compiled code. By default, files 327 | # are written out in `cwd` as `.js` files with the same name, but the output 328 | # directory can be customized with `--output`. 329 | # 330 | # If `generatedSourceMap` is provided, this will write a `.map` file into the 331 | # same directory as the `.js` file. 332 | writeJs = (base, sourcePath, js, jsPath, generatedSourceMap = null) -> 333 | sourceMapPath = outputPath sourcePath, base, ".map" 334 | jsDir = path.dirname jsPath 335 | compile = -> 336 | if opts.compile 337 | js = ' ' if js.length <= 0 338 | if generatedSourceMap then js = "#{js}\n//# sourceMappingURL=#{helpers.baseFileName sourceMapPath, no, useWinPathSep}\n" 339 | fs.writeFile jsPath, js, (err) -> 340 | if err 341 | printLine err.message 342 | else if opts.compile and opts.watch 343 | timeLog "compiled #{sourcePath}" 344 | if generatedSourceMap 345 | fs.writeFile sourceMapPath, generatedSourceMap, (err) -> 346 | if err 347 | printLine "Could not write source map: #{err.message}" 348 | fs.exists jsDir, (itExists) -> 349 | if itExists then compile() else mkdirp jsDir, compile 350 | 351 | # Convenience for cleaner setTimeouts. 352 | wait = (milliseconds, func) -> setTimeout func, milliseconds 353 | 354 | # When watching scripts, it's useful to log changes with the timestamp. 355 | timeLog = (message) -> 356 | console.log "#{(new Date).toLocaleTimeString()} - #{message}" 357 | 358 | # Pretty-print a stream of tokens, sans location data. 359 | printTokens = (tokens) -> 360 | strings = for token in tokens 361 | tag = token[0] 362 | value = token[1].toString().replace(/\n/, '\\n') 363 | "[#{tag} #{value}]" 364 | printLine strings.join(' ') 365 | 366 | # Use the [OptionParser module](optparse.html) to extract all options from 367 | # `process.argv` that are specified in `SWITCHES`. 368 | parseOptions = -> 369 | optionParser = new optparse.OptionParser SWITCHES, BANNER 370 | o = opts = optionParser.parse process.argv[2..] 371 | o.compile or= !!o.output 372 | o.run = not (o.compile or o.print or o.map) 373 | o.print = !! (o.print or (o.eval or o.stdio and o.compile)) 374 | 375 | # The compile-time options to pass to the CoffeeScript compiler. 376 | compileOptions = (filename, base) -> 377 | answer = { 378 | filename 379 | literate: opts.literate or helpers.isLiterate(filename) 380 | bare: opts.bare 381 | header: opts.compile and not opts['no-header'] 382 | sourceMap: opts.map 383 | } 384 | if filename 385 | if base 386 | cwd = process.cwd() 387 | jsPath = outputPath filename, base 388 | jsDir = path.dirname jsPath 389 | answer = helpers.merge answer, { 390 | jsPath 391 | sourceRoot: path.relative jsDir, cwd 392 | sourceFiles: [path.relative cwd, filename] 393 | generatedFile: helpers.baseFileName(jsPath, no, useWinPathSep) 394 | } 395 | else 396 | answer = helpers.merge answer, 397 | sourceRoot: "" 398 | sourceFiles: [helpers.baseFileName filename, no, useWinPathSep] 399 | generatedFile: helpers.baseFileName(filename, yes, useWinPathSep) + ".js" 400 | answer 401 | 402 | # Start up a new Node.js instance with the arguments in `--nodejs` passed to 403 | # the `node` binary, preserving the other options. 404 | forkNode = -> 405 | nodeArgs = opts.nodejs.split /\s+/ 406 | args = process.argv[1..] 407 | args.splice args.indexOf('--nodejs'), 2 408 | p = spawn process.execPath, nodeArgs.concat(args), 409 | cwd: process.cwd() 410 | env: process.env 411 | customFds: [0, 1, 2] 412 | p.on 'exit', (code) -> process.exit code 413 | 414 | # Print the `--help` usage message and exit. Deprecated switches are not 415 | # shown. 416 | usage = -> 417 | printLine (new optparse.OptionParser SWITCHES, BANNER).help() 418 | 419 | # Print the `--version` message and exit. 420 | version = -> 421 | version = require('../package.json').version 422 | cjsxversion = require('coffee-react-transform/package.json').version 423 | printLine "coffee-react version #{version}" 424 | printLine "coffee-react-transform version #{cjsxversion}" 425 | printLine "coffee-script version #{CoffeeScript.VERSION}" 426 | 427 | -------------------------------------------------------------------------------- /src/helpers.coffee: -------------------------------------------------------------------------------- 1 | helpers = require 'coffee-script/lib/coffee-script/helpers' 2 | 3 | helpers.isCoffee = (filepath) -> /\.((lit)?coffee|coffee\.md|cjsx)$/.test filepath 4 | 5 | helpers.hasCJSXExtension = (filepath) -> /\.(cjsx)$/.test filepath 6 | 7 | helpers.hasCJSXPragma = (src) -> /^\s*#\s*@(cjsx)/.test src 8 | 9 | module.exports = helpers -------------------------------------------------------------------------------- /src/register.coffee: -------------------------------------------------------------------------------- 1 | CoffeeScript = require './coffee-react-script' 2 | child_process = require 'child_process' 3 | helpers = require './helpers' 4 | path = require 'path' 5 | 6 | # Load and run a CoffeeScript file for Node, stripping any `BOM`s. 7 | loadFile = (module, filename) -> 8 | answer = CoffeeScript._compileFile filename, false 9 | module._compile answer, filename 10 | 11 | # If the installed version of Node supports `require.extensions`, register 12 | # CoffeeScript as an extension. 13 | if require.extensions 14 | for ext in CoffeeScript.FILE_EXTENSIONS 15 | require.extensions[ext] = loadFile 16 | 17 | # Patch Node's module loader to be able to handle mult-dot extensions. 18 | # This is a horrible thing that should not be required. Perhaps, one day, 19 | # when a truly benevolent dictator comes to rule over the Republik of Node, 20 | # it won't be. 21 | Module = require 'module' 22 | 23 | findExtension = (filename) -> 24 | extensions = path.basename(filename).split '.' 25 | # Remove the initial dot from dotfiles. 26 | extensions.shift() if extensions[0] is '' 27 | # Start with the longest possible extension and work our way shortwards. 28 | while extensions.shift() 29 | curExtension = '.' + extensions.join '.' 30 | return curExtension if Module._extensions[curExtension] 31 | '.js' 32 | 33 | Module::load = (filename) -> 34 | @filename = filename 35 | @paths = Module._nodeModulePaths path.dirname filename 36 | extension = findExtension filename 37 | Module._extensions[extension](this, filename) 38 | @loaded = true 39 | 40 | # If we're on Node, patch `child_process.fork` so that Coffee scripts are able 41 | # to fork both CoffeeScript files, and JavaScript files, directly. 42 | if child_process 43 | {fork} = child_process 44 | binary = require.resolve '../bin/cjsx' 45 | child_process.fork = (path, args, options) -> 46 | if helpers.isCoffee path 47 | unless Array.isArray args 48 | options = args or {} 49 | args = [] 50 | args = [path].concat args 51 | path = binary 52 | fork path, args, options -------------------------------------------------------------------------------- /test/compile.coffee: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env coffee 2 | 3 | fs = require 'fs' 4 | {spawn, exec} = require 'child_process' 5 | {test} = require('tap') 6 | concat = require 'concat-stream' 7 | 8 | run = (executable, args = [], outStream, errStream, cb) -> 9 | proc = spawn executable, args 10 | proc.stdout.pipe outStream 11 | proc.stderr.pipe errStream 12 | proc.on 'err', (err) -> cb(err) 13 | proc.on 'exit', (status) -> cb(null, status) 14 | 15 | test 'compile cjsx file', (t) -> 16 | t.plan(1) 17 | 18 | file = '../example/named-component.coffee' 19 | expected = fs.readFileSync './expected/compiled-output.js', encoding: 'utf8' 20 | 21 | outStream = concat {encoding: 'buffer'}, (output) -> 22 | t.equal output.toString(), expected, 'got output' 23 | 24 | errStream = concat {encoding: 'buffer'}, (output) -> 25 | if output.toString().length 26 | t.fail output.toString() 27 | 28 | run require.resolve('../bin/cjsx'), ['-cp', file], outStream, errStream, (err, status) -> 29 | t.fail(err) if err 30 | t.fail(status) if status isnt 0 31 | t.end() 32 | -------------------------------------------------------------------------------- /test/expected/compiled-output.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.10.0 2 | (function() { 3 | var NamedComponent, React; 4 | 5 | React = require('react'); 6 | 7 | NamedComponent = React.createClass({displayName: "NamedComponent", 8 | render: function() { 9 | var extraProps, i; 10 | extraProps = { 11 | style: { 12 | color: 'red' 13 | }, 14 | width: 10 15 | }; 16 | return React.createElement("ul", Object.assign({}, extraProps, { 17 | "width": 12. 18 | }), (function() { 19 | var j, len, ref, results; 20 | ref = [2, 3, 5]; 21 | results = []; 22 | for (j = 0, len = ref.length; j < len; j++) { 23 | i = ref[j]; 24 | results.push(React.createElement("li", { 25 | "key": i 26 | }, i, " ", React.createElement("input", { 27 | "type": "checkbox", 28 | "defaultChecked": true 29 | }))); 30 | } 31 | return results; 32 | })()); 33 | } 34 | }); 35 | 36 | }).call(this); 37 | -------------------------------------------------------------------------------- /test/expected/rendered-output.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/require.coffee: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env coffee 2 | 3 | fs = require 'fs' 4 | {test} = require('tap') 5 | 6 | require('../register') 7 | 8 | React = require 'react' 9 | 10 | test 'require cjsx coffee file', (t) -> 11 | t.plan(1) 12 | 13 | expected = fs.readFileSync './expected/rendered-output.html', encoding: 'utf8' 14 | 15 | SomeClass = require '../example/some-class' 16 | rendered = React.renderToStaticMarkup(React.createElement(SomeClass)) 17 | 18 | t.equal rendered, expected, 'correct output' 19 | t.end() 20 | -------------------------------------------------------------------------------- /test/run.coffee: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env coffee 2 | 3 | fs = require 'fs' 4 | {spawn, exec} = require 'child_process' 5 | {test} = require('tap') 6 | concat = require 'concat-stream' 7 | 8 | run = (executable, args = [], outStream, errStream, cb) -> 9 | proc = spawn executable, args 10 | proc.stdout.pipe outStream 11 | proc.stderr.pipe errStream 12 | proc.on 'err', (err) -> cb(err) 13 | proc.on 'exit', (status) -> cb(null, status) 14 | 15 | test 'run cjsx file', (t) -> 16 | t.plan(1) 17 | 18 | file = '../example/log-component.coffee' 19 | expected = fs.readFileSync './expected/rendered-output.html', encoding: 'utf8' 20 | 21 | outStream = concat {encoding: 'buffer'}, (output) -> 22 | t.equal output.toString(), expected, 'got output' 23 | 24 | errStream = concat {encoding: 'buffer'}, (output) -> 25 | if output.toString().length 26 | t.fail output.toString() 27 | 28 | run require.resolve('../bin/cjsx'), [file], outStream, errStream, (err, status) -> 29 | t.fail(err) if err 30 | t.fail(status) if status isnt 0 31 | t.end() 32 | -------------------------------------------------------------------------------- /update.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | git pull origin master 4 | DEPENDENCY="coffee-react-transform" 5 | VERSION=`npm view ${DEPENDENCY} version` 6 | echo "updating to $VERSION" 7 | npm install --save "${DEPENDENCY}@${VERSION}" 8 | npm test 9 | git add ./package.json 10 | git commit -m "updated ${DEPENDENCY} to v${VERSION}" 11 | npm version $VERSION 12 | read -p "will publish $VERSION. are you sure? " -n 1 -r 13 | echo 14 | if [[ $REPLY =~ ^[Yy]$ ]] 15 | then 16 | git push origin master 17 | npm publish . 18 | fi --------------------------------------------------------------------------------