diff options
author | Jan Lehnardt <jan@apache.org> | 2013-09-21 15:42:47 +0200 |
---|---|---|
committer | Jan Lehnardt <jan@apache.org> | 2013-10-03 17:21:29 +0200 |
commit | dc375701d02db659b6364f1d2aab4d6631c1e285 (patch) | |
tree | 852c6bd61d842d703dbc92a782e07c0a1b147c8e | |
parent | 3e19db216ce44ae1c6729ed68d1316b12b5d1875 (diff) | |
download | couchdb-dc375701d02db659b6364f1d2aab4d6631c1e285.tar.gz |
add couchjs-node from Jason
-rw-r--r-- | src/couchjs-node/LICENSE | 202 | ||||
-rw-r--r-- | src/couchjs-node/README.md | 54 | ||||
-rw-r--r-- | src/couchjs-node/checkout.sh | 36 | ||||
-rwxr-xr-x | src/couchjs-node/cli.js | 85 | ||||
-rw-r--r-- | src/couchjs-node/console.js | 65 | ||||
-rwxr-xr-x | src/couchjs-node/couchdb.js | 29 | ||||
-rw-r--r-- | src/couchjs-node/couchjs.js | 116 | ||||
-rwxr-xr-x | src/couchjs-node/extra.js | 346 | ||||
-rwxr-xr-x | src/couchjs-node/inspector.js | 92 | ||||
-rw-r--r-- | src/couchjs-node/package.json | 30 | ||||
-rw-r--r-- | src/couchjs-node/stream.js | 107 | ||||
-rw-r--r-- | src/couchjs-node/test/experiment.js | 105 | ||||
-rw-r--r-- | src/couchjs-node/xml.js | 23 |
13 files changed, 1290 insertions, 0 deletions
diff --git a/src/couchjs-node/LICENSE b/src/couchjs-node/LICENSE new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/src/couchjs-node/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/src/couchjs-node/README.md b/src/couchjs-node/README.md new file mode 100644 index 000000000..c686e8089 --- /dev/null +++ b/src/couchjs-node/README.md @@ -0,0 +1,54 @@ +# CouchJS + +## Drop-in replacement JavaScript engine for Apache CouchDB + +CouchJS is a command-line Node.js program. It is 100% compatible with Apache CouchDB's built-in JavaScript system. + +By using CouchJS, you will get 100% CouchDB compatibility (the test suite completely passes) but your JavaScript environment is V8, or Node.js. + +CouchJS is available as an npm module. + + $ npm install -g couchjs + +## Usage + +Install CouchDB. Install this package with npm. Confirm your `couchjs` install location. + + $ which couchjs # Note, your path will be different from mine. + /home/jhs/node/bin/couchjs + +Look at the CouchDB config for the JavaScript query server. + + $ curl http://localhost:5984/_config/query_servers/javascript + "/home/jhs/couchdb/bin/couchjs /home/jhs/couchdb/share/couchdb/server/main.js" + +Change that to this `couchjs`. **Leave the second argument the same.** + + $ curl -X PUT http://localhost:5984/_config/query_servers/javascript \ + -H content-type:application/json \ + -d "\"`which couchjs` /home/jhs/couchdb/share/couchdb/server/main.js\"" + +Done! + +## Idea + +JavaScript is decoupled from the CouchDB core. To do JavaScript stuff, CouchDB runs a normal Unix subprocess, `couchjs`. This subprocess is just a read-eval-print loop on standard i/o. CouchDB passes `couchjs` a file name, and *that file* contains the view server implementation. + +This tool duplicates the "REPL" look and feel of `couchjs` and supports the exact same view server implementation. + +## Security + +I have no idea. I would not trust it for production use. + +## Log + +If you create a file, `/tmp/couchjs.log` then *couchjs* will output debugging messages there. + +## License + +Apache 2.0 + +See the [Apache 2.0 license](named/blob/master/LICENSE). + +[tap]: https://github.com/isaacs/node-tap +[def]: https://github.com/iriscouch/defaultable diff --git a/src/couchjs-node/checkout.sh b/src/couchjs-node/checkout.sh new file mode 100644 index 000000000..0076b5161 --- /dev/null +++ b/src/couchjs-node/checkout.sh @@ -0,0 +1,36 @@ +# Copyright 2011 Iris Couch +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +repo="$1" +commit="$2" + +if [ -z "$repo" -o ! -d "$repo" ]; then + echo "Not a valid repo: $repo" >&2 + exit 1 +fi +if [ -z "$commit" ]; then + echo "Not a valid commit: $commit" >&2 + exit 1 +fi + +echo "Clone $repo:$commit to $(pwd)" + +set -e + +git clone "$repo" . +git checkout "$commit" + +if [ -z "$skip_npm_install" ]; then + npm install --production +fi diff --git a/src/couchjs-node/cli.js b/src/couchjs-node/cli.js new file mode 100755 index 000000000..14a6d97be --- /dev/null +++ b/src/couchjs-node/cli.js @@ -0,0 +1,85 @@ +#!/usr/bin/env node +// +// couchjs replacement +// +// Copyright 2011 Iris Couch +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +var fs = require('fs') +var util = require('util') +var Fiber = require('fibers') +var optimist = require('optimist') +var child_process = require('child_process') + +var couchjs = require('./couchjs') +var package_json = require('./package.json') +var couchdb_extra = require('./extra') +var LineStream = require('./stream') +var inspector = require('./inspector') +var log = require('./console').log + + +var opts = optimist.boolean(['h', 'V', 'H']) + .describe({ 'h': 'display a short help message and exit' + , 'V': 'display version information and exit' + , 'H': 'enable couchjs cURL bindings (not implemented)' + }) + .boolean('extra') + .describe('extra', 'Extra features for CouchDB, for os_daemons') + .usage('$0 <path to main.js>') + + +function main() { + if(opts.argv.extra) + return couchdb_extra() + + var main_js = opts.argv._[0] + if(!main_js) + return console.error(opts.help()) + + log('couchjs/%s %s: %s', package_json.version, process.pid, main_js) + if(process.env.COUCHJS_DEBUG_PORT) + inspector(+process.env.COUCHJS_DEBUG_PORT) + + fs.readFile(main_js, 'utf8', function(er, body) { + if(er) + throw er + + var stdin = new LineStream.v2 + stdin.on('readable', function() { + var buf = stdin.read() + if(buf) + couchjs.stdin(buf) + }) + stdin.on('end', function() { + log('Terminate; connection to parent closed') + process.exit(0) + }) + + process.stdin.setEncoding('utf8') + process.stdin.pipe(stdin) + + var main_func = Function(['print', 'readline', 'evalcx', 'gc', 'quit'], body) + + log('Call main') + Fiber(function() { main_func(couchjs.print, couchjs.readline, couchjs.evalcx, couchjs.gc) }).run() + }) + + process.on('uncaughtException', function(er) { + log('Error:\n%s', er.stack) + }) +} + +if(require.main === module) + main() diff --git a/src/couchjs-node/console.js b/src/couchjs-node/console.js new file mode 100644 index 000000000..2a0a17a1c --- /dev/null +++ b/src/couchjs-node/console.js @@ -0,0 +1,65 @@ +// Copyright 2011 Iris Couch +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +var fs = require('fs') +var util = require('util') + + +module.exports = {} +module.exports.log = noop +module.exports.debug = noop +module.exports.info = noop +module.exports.warn = noop +module.exports.error = noop + +var LOG_PATH = '/tmp/couchjs.log' + , stat = null + , LOG = null + +try { + stat = fs.statSync(LOG_PATH) +} catch(er) {} + +if(stat) { + LOG = fs.createWriteStream(LOG_PATH, {'flags':'a'}) + + module.exports.log = log + module.exports.debug = log + module.exports.info = log + module.exports.warn = log + module.exports.error = log + + process.on('exit', function() { + module.exports.log('Exit %d', process.pid) + }) + + process.on('uncaughtException', on_err) +} + +function log() { + var str = util.format.apply(this, arguments) + LOG.write(str + '\n') +} + +function on_err(er) { + module.exports.error('Uncaught error:\n%s', er.stack || er.message || JSON.stringify(er)) + + if(er.stack) + er = ['fatal', 'unknown_error', er.stack] + + process.stdout.write(JSON.stringify(er) + '\n') + process.exit(1) +} + +function noop() {} diff --git a/src/couchjs-node/couchdb.js b/src/couchjs-node/couchdb.js new file mode 100755 index 000000000..d297112c3 --- /dev/null +++ b/src/couchjs-node/couchdb.js @@ -0,0 +1,29 @@ +// Copyright 2011 Iris Couch +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +module.exports = handler + +var http = require('http') + +function handler(req, res) { + res.writeHead(200) + res.end('Hello: ' + req.url + '\n') +} + +if(require.main === module) { + var http = require('http') + var server = http.createServer(handler) + server.listen(3000) + console.log('Listening on :3000') +} diff --git a/src/couchjs-node/couchjs.js b/src/couchjs-node/couchjs.js new file mode 100644 index 000000000..d95341dab --- /dev/null +++ b/src/couchjs-node/couchjs.js @@ -0,0 +1,116 @@ +// Copyright 2011 Iris Couch +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +module.exports = { 'print' : print + , 'readline': readline + , 'stdin' : stdin + , 'evalcx' : evalcx + , 'quit' : quit + , 'gc' : gc + } + + +var vm = require('vm') +var Fiber = require('fibers') + +var XML = require('./xml') +var log = require('./console').log + +var INPUT = {'queue':[], 'waiting':null} + +Error.prototype.toSource = Error.prototype.toSource || toSource +Error.prototype.toString = Error.prototype.toString || toSource +Function.prototype.toSource = Function.prototype.toSource || toSource +Function.prototype.toString = Function.prototype.toString || toSource + + +function print(line) { + log('STDOUT %s: %s', process.pid, line) + process.stdout.write(line + '\n') + + try { + line = JSON.parse(line) + } catch(er) { return } + + if(line[0] == 'log') + log('LOG: %s', line[1]) +} + +function stdin(line) { + log('STDIN %s: %s', process.pid, line.trim()) + if(INPUT.waiting) + INPUT.waiting.run(line) + else + INPUT.queue.push(line) +} + +function readline() { + var line = INPUT.queue.shift() + if(line) + return line + + INPUT.waiting = Fiber.current + line = Fiber.yield() + INPUT.waiting = null + + return line +} + + +function evalcx(source, sandbox) { + sandbox = sandbox || {} + //log('evalcx in %j: %j', Object.keys(sandbox), source) + + if(source == '') + return sandbox + + // source might be "function(doc) { emit(doc._id, 1) }" + source = source.replace(/;+$/, '') + + sandbox.XML = sandbox.XML || XML + source = '(' + source + ')' + + try { + var id = Math.floor(Math.random() * 1000*1000) + var filename = '_couchdb:' + id + '.js' + var script = vm.createScript(source, filename) + var func = script.runInNewContext(sandbox) + } catch (er) { + log('Error making code: %s', er.stack) + return sandbox + } + + return func +} + +function quit(code) { + code = code || 1 + if(code < 0) + code = -code + + process.exit(code) +} + +function gc() { } + + +function toSource() { + if(typeof this == 'function') + return '' + this + + if(this instanceof Error) + return this.stack + + return util.inspect(this) +} diff --git a/src/couchjs-node/extra.js b/src/couchjs-node/extra.js new file mode 100755 index 000000000..2df0168d6 --- /dev/null +++ b/src/couchjs-node/extra.js @@ -0,0 +1,346 @@ +#!/usr/bin/env node +// Copyright 2011 Iris Couch +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +module.exports = main + +var os = require('os') +var fs = require('fs') +var URL = require('url') +var util = require('util') +var http = require('http') +var async = require('async') +var mkdirp = require('mkdirp') +var request = require('request') +var optimist = require('optimist') +var pushover = require('pushover') +var child_process = require('child_process') + +var console = require('./console') +var VER = require('./package.json').version + +var couch = { 'log': mk_couch_log('info') + , 'warn' : mk_couch_log('warn') + , 'error': mk_couch_log('error') + , 'debug': mk_couch_log('debug') + } + + +var opts = optimist.usage('$0') + +var COUCH = null +var GIT_DIR = null +var COUCH_PASSWORD = null +var GIT_PORT = null +var APPLICATION = null + +function main() { + if(opts.argv.help) + return console.log(opts.help()) + + console.log('Extra CouchDB daemon: %s', process.pid) + couch.debug('CouchDB daemon %s: %s', VER, process.pid) + + var env = {} + for (var k in process.env) { + var match = k.match(/^_couchdb_app_(.*)$/) + if(match) + env[match[1]] = process.env[k] + } + + for (k in env) + couch.debug(' %s = %s', k, env[k]) + + if(env.port && env.password && env.couch && env.dir) + return git(env) + + setInterval(function() { + console.log('Still here') + couch.log('Still in couch') + }, 60000) +} + +function git(env) { + GIT_PORT = +env.port + COUCH = env.couch + COUCH_DIR = util.format('%s/couchjs-%s', env.dir, VER) + COUCH_PASSWORD = env.password + + //var couch_url = util.format('http://_nodejs:%s@127.0.0.1:%d', password, couch_port) + //couch.log('couch url %j', couch_url) + + auth('_nodejs', COUCH_PASSWORD, function(er, userCtx) { + if(er) + throw er + + var roles = userCtx.roles || [] + if(userCtx.name != '_nodejs' || !~roles.indexOf('_admin')) + throw new Error('Not admin: ' + JSON.stringify(res.body.userCtx)) + + var repos = pushover(COUCH_DIR) + repos.on('push', function(push) { + couch.log('Push %s/%s: %s', push.repo, push.commit, push.branch) + push.accept() + //couch.log('Response: %j', Object.keys(push.response)) + push.response.on('finish', function() { + //couch.log('Finished!') + publish(push) + }) + }) + + repos.on('fetch', function(fetch) { + couch.log('fetch %j', fetch.commit) + fetch.accept() + }) + + var server = http.createServer(function(req, res) { + if(! req.url.match(/^\/_nodejs\/_git(\/|$)/)) + return handle_http(req, res) + + req.pause() + auth_req(req, function(er, userCtx) { + if(er && er.statusCode) { + res.writeHead(er.statusCode, er.headers) + return res.end(er.body) + } + + if(er) { + couch.log('Bad req %s: %s', req.url, er.message) + return res.end() + } + + var roles = userCtx.roles || [] + if(!~ roles.indexOf('_admin')) { + couch.log('Not admin: %s, %j', req.url, userCtx) + res.writeHead(401, 'Unauthorized', {'content-type':'application/json'}) + return res.end('{"error":"not_authorized"}\n') + } + + //couch.log('Handle Git: %j', req.url) + repos.handle(req, res) + req.resume() + }) + }) + + server.listen(GIT_PORT) + }) +} + +function handle_http(req, res) { + if(! APPLICATION) { + var headers = { 'content-type': 'application/json' + , 'server': 'NodeJS-CouchDB/'+VER + } + res.writeHead(200, 'OK', headers) + var body = {'ok':true} + return res.end(JSON.stringify(body) + '\n') + } + + // Clean up the vhost changes. + var vhost_path = req.headers['x-couchdb-vhost-path'] + if(vhost_path) { + req.url = vhost_path + delete req.headers['x-couchdb-vhost-path'] + } + + APPLICATION(req, res) +} + +function auth(user, pass, callback) { + if(!COUCH) + return process.nextTick(function() { callback(new Error('No _couchdb_port')) }) + + var url = COUCH + '/_session' + if(user || pass) { + url = URL.parse(url) + url.auth = util.format('%s:%s', user || '', pass || '') + url = URL.format(url) + } + + //couch.log('auth: %j', url) + request({'url':url, 'json':true}, function(er, res) { + //couch.log('auth result: %j', res.body) + if(er) + return callback(er) + + if(res.statusCode != 200) { + er = new Error('Bad status '+res.statusCode+' for auth: ' + res.body) + er.statusCode = res.statusCode + er.body = JSON.stringify(res.body) + '\n' + er.headers = res.headers + return callback(er) + } + + return callback(null, res.body.userCtx) + }) +} + +function auth_req(req, callback) { + var headers = req.headers || {} + var auth_str = req.headers.authorization || '' + + var match = auth_str.match(/^Basic (.+)$/) + if(!match) + return auth(null, null, callback) + + try { + auth_str = new Buffer(match[1], 'base64').toString() + match = auth_str.match(/^([^:]+):(.+)$/) + } catch (er) { + return callback(er) + } + + if(!match) + return callback(new Error('Bad auth string: ' + auth_str)) + + auth(match[1], match[2], callback) +} + + +function publish(push) { + var script = __dirname + '/checkout.sh' + var repo = COUCH_DIR + '/' + push.repo + + var id = Math.floor(Math.random() * 1000 * 1000) + var work = util.format('%s/%s/%s', COUCH_DIR, push.commit, id) + + mkdirp(work, function(er) { + if(er) { + couch.error('Failed to make working dir: %s', work) + throw er + } + + checkout() + }) + + function checkout() { + var args = [script, repo, push.commit] + var opts = { 'cwd':work, 'stdio':'pipe' } + + var child = child_process.spawn('bash', args, opts) + + var output = [] + + child.stdout.on('data', function(x) { + var msg = util.format('OUT %s', x.toString().trim()) + couch.debug(msg) + output.push(msg) + }) + child.stderr.on('data', function(x) { + var msg = util.format('ERR %s', x.toString().trim()) + couch.debug(msg) + output.push(msg) + }) + + child.on('exit', function(code) { + if(code !== 0) { + couch.error('Bad checkout: %d', code) + output.forEach(function(line) { + couch.error(line) + }) + + throw new Error('Bad checkout') + } + + couch.log('Checked out push: %s', work) + fs.readFile(work+'/package.json', 'utf8', function(er, body) { + if(er) + throw er + + body = JSON.parse(body) + if(!body.couchdb) + return couch.warn('No "couchdb" value in pushed package.json') + + run_app(work, body) + }) + }) + } +} + + +function run_app(work_dir, pkg) { + var vhosts = [] + , main = null + + if(typeof pkg.couchdb == 'string') + main = pkg.couchdb + else { + vhosts = pkg.couchdb.vhosts || [] + main = pkg.couchdb.main + } + + couch.log('Run app %s: %j', main, vhosts) + + var mod_path = util.format('%s/%s', work_dir, main) + try { + var ok = require.resolve(mod_path) + } catch (er) { + return couch.error('Bad module path: %s', mod_path) + } + + couch_mod = require(mod_path) + APPLICATION = couch_mod + couch.log('Installed CouchDB application') + + return async.forEach(vhosts, set_vhost, vhosts_set) + + function set_vhost(vhost, to_async) { + var couch_url = URL.parse(COUCH) + couch_url.auth = '_nodejs:' + COUCH_PASSWORD + couch_url = URL.format(couch_url) + couch.log('couch_url: %j', couch_url) + + var url = couch_url + '_config/vhosts/' + vhost + var body = '/_nodejs' + request.put({'url':url, 'json':body}, function(er, res) { + if(er) + return to_async(er) + if(res.statusCode != 200) + return to_async(new Error('Bad response '+res.statusCode+' to vhost: ' + vhost)) + + couch.log('Set vhost: %s', vhost) + return to_async() + }) + } + + function vhosts_set(er) { + if(er) + throw er + + couch.log('Set %d vhosts for CouchDB application', vhosts.length) + } +} + + +// +// Utilities +// + +function mk_couch_log(level) { + if(level == 'warn') + level = 'error' + + return logger + + function logger() { + var str = util.format.apply(util, arguments) + var msg = ['log', str, {'level':level}] + msg = JSON.stringify(msg) + process.stdout.write(msg + '\n') + } +} + + +if(require.main === module) + main() diff --git a/src/couchjs-node/inspector.js b/src/couchjs-node/inspector.js new file mode 100755 index 000000000..efa98566f --- /dev/null +++ b/src/couchjs-node/inspector.js @@ -0,0 +1,92 @@ +#!/usr/bin/env node +// +// couchjs replacement +// +// Copyright 2011 Iris Couch +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +module.exports = start + +if(require.main === module) + main() + + +var fs = require('fs') +var util = require('util') +var child_process = require('child_process') + +var log = require('./console').log + +function start(debugPort) { + if(!debugPort || typeof debugPort != 'number') + throw new Error('Need a listen debugPort') + var webPort = debugPort + 1 + + var cmd = __filename + var args = [debugPort, webPort] + var opts = + { 'cwd': __dirname + , 'stdio': 'pipe' + , 'detached': false + } + + log('Start inspector: %s %j %j', cmd, args, opts) + var inspector = child_process.spawn(cmd, args, opts) + watch_inspector(inspector) + + log('Enable remote debug pid=%d port=%d', process.pid, debugPort) + process.debugPort = debugPort + process.kill(process.pid, 'SIGUSR1') +} + +function watch_inspector(child) { + child.stderr.on('data', function(body) { + log('Inspector STDERR: %s', body) + }) + child.stdout.on('data', function(body) { + log('Inspector STDOUT: %s', body) + }) + + child.on('exit', function(code, signal) { + log('Inspector exited %d signal=%j', code, signal) + process.exit(code) + }) + + process.on('exit', function() { + log('Kill inspector upon exit: %d', child.pid) + process.kill(child.pid, 'SIGTERM') + }) +} + + +function main() { + var debugPort = +process.argv[2] + var webPort = +process.argv[3] + + if(!debugPort || !webPort) + throw new Error('Bad arguments: need debugPort and webPort') + + console.log('Start inspector debugPort=%j webPort=%j', debugPort, webPort) + var DebugServer = require('node-inspector/lib/debug-server') + var server = new DebugServer + server.on('close', function() { + console.log('Server closed') + process.exit(0) + }) + + server.start({'webPort':webPort, 'debugPort':debugPort}) + process.on('uncaughtException', function(er) { + console.log('Error:\n%s', er.stack) + }) +} diff --git a/src/couchjs-node/package.json b/src/couchjs-node/package.json new file mode 100644 index 000000000..665888cae --- /dev/null +++ b/src/couchjs-node/package.json @@ -0,0 +1,30 @@ +{ "name": "couchjs" +, "description": "Drop-in replacement for CouchDB JavaScript view server" +, "keywords": [ "couchdb", "couchjs" ] +, "version": "0.3.2" +, "author": "Jason Smith <jhs@iriscouch.com> (http://www.iriscouch.com)" +, "repository": { "type":"git", "url":"https://github.com/iriscouch/couchjs" } + +, "engines": { "node": ">= 0.8" } +, "main": "./couchjs.js" +, "bin": {"couchjs-node":"./cli.js", "couchjs-extra":"./extra.js"} + +, "couchdb": { "main":"./couchdb.js" + , "vhosts": ["127.0.0.1.xip.io"] + } + +, "bundledDependencies": ["node-inspector"] + +, "dependencies": { "optimist": "~0.3.4" + , "async" : "~0.2.5" + , "mkdirp" : "~0.3.4" + , "fibers" : "~1.0.0" + , "request" : "~2.9.203" + , "pushover": "~1.2.1" + , "defaultable": "~0.7.2" + , "node-inspector": "git://github.com/iriscouch/node-inspector#couchjs" + } + +, "devDependencies": { "tap": "~0.2.5" + } +} diff --git a/src/couchjs-node/stream.js b/src/couchjs-node/stream.js new file mode 100644 index 000000000..90deda17e --- /dev/null +++ b/src/couchjs-node/stream.js @@ -0,0 +1,107 @@ +// Text line stream +// +// Copyright 2011 Iris Couch +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +module.exports = LineStream +module.exports.v2 = LineStream2 + +var stream = require('stream') +var util = require('util') + + +util.inherits(LineStream2, stream.Transform) +function LineStream2 () { + if(! (this instanceof LineStream2)) + return new LineStream2 + + stream.Transform.call(this) + this.setEncoding('utf8') +} + +LineStream2.prototype._transform = function(message, encoding, done) { + var self = this + + message = message.toString(encoding) + var lines = message.split(/\n/) + + // If the data ends in "\n" this will be ""; otherwise the final partial line. + var remainder = lines.pop() + if(remainder) + this.unshift(remainder) + + lines.forEach(function(line) { + self.push(line) + }) + + done() +} + +util.inherits(LineStream, stream) +function LineStream () { + var self = this + stream.call(self) + + self.readable = true + self.writable = true + + self.buffer = '' + self.downstream = null + + self.on('pipe', function(upstream) { + upstream.on('end', function(data, encoding) { + self.emit('end', data, encoding) + }) + }) +} + + +LineStream.prototype.write = function(data, encoding) { + var self = this + + data = data || '' + if(typeof data != 'string') + return self.error(new Error('Data was not a string: ' + util.inspect(data))) + + self.buffer += data + var lines = self.buffer.split(/\n/) + self.buffer = lines.pop() // If the data ended in "\n" this will be ""; otherwise the final partial line. + + lines.forEach(function(line) { + self.emit('data', line) + }) +} + + +LineStream.prototype.end = function(data, encoding) { + var self = this + + self.is_ending = true + self.writable = false + + // Always call write, even with no data, so it can fire the "end" event. + self.write(data) +} + + +LineStream.prototype.error = function(er) { + var self = this + + self.readable = false + self.writable = false + self.emit('error', er) + + // The write() method sometimes returns this value, so if there was an error, make write() return false. + return false +} diff --git a/src/couchjs-node/test/experiment.js b/src/couchjs-node/test/experiment.js new file mode 100644 index 000000000..54a5ab4ff --- /dev/null +++ b/src/couchjs-node/test/experiment.js @@ -0,0 +1,105 @@ +var vm = require('vm') +var util = require('util') + +var STATE = 'wait' + , v = 'vm' + +function main() { + process.debugPort = 5859 + process.kill(process.pid, 'SIGUSR1') + + setTimeout(function() { stuff(0) }, 1000) +} + +function stuff(count) { + console.log('Doing stuff: %d', count) + //debugger + STATE = 'vm' + console.log('More stuff: %d', count) + if(STATE == 'done') + console.log('Done') + else if(STATE == 'code') + setTimeout(code, 1000) + else if(STATE == 'eval') + test_eval() + else if(STATE == 'vm') + test_vm() + else if(STATE == 'wait') + setTimeout(function() { stuff(count+1) }, 1000) + else + throw new Error('Unknown state: ' + STATE) +} + +function code() { + var code = + [ 'var foo = "in the code"' + , 'console.log("This is some code")' + , 'debugger' + , 'console.log("foo = " + foo)' + ].join('\n') + + var runner = Function([], code) + console.log('Run runner in 1s') + setTimeout(run_runner, 1000) + + function run_runner() { + console.log('About to run runner') + debugger + runner() + console.log('Runner done') + } +} + +function test_eval() { + console.log('Test eval in 1s') + setTimeout(run_eval, 1000) + + var code = + [ 'var foo = "in eval"' + , 'console.log("This is eval")' + , 'debugger' + , 'console.log("foo = " + foo)' + ].join('\n') + + function run_eval() { + console.log('Run eval now') + debugger + eval(code) + } +} + +function test_vm() { + console.log('Test vm') + + var code = + [ 'var i = 10' + , 'setTimeout(hello, 1000)' + , '' + , 'function hello() {' + , ' debugger' + , ' console.log("Hello: " + i)' + , ' if(--i)' + , ' setTimeout(hello, 1000)' + , '}' + ].join('\n') + + console.log('Run vm now') + var filename = '_couchdb:code.js' + + var sandbox = {} + , ok = ['console', 'setTimeout'] + + ok.forEach(function(key) { + sandbox[key] = global[key] + }) + + var ctx = vm.createContext(sandbox) + var script = vm.createScript(code, filename) + + var r = script.runInNewContext(sandbox) + console.log('Result:\n%s', util.inspect(r, false, 10)) + return r +} + +if(require.main === module) + main() diff --git a/src/couchjs-node/xml.js b/src/couchjs-node/xml.js new file mode 100644 index 000000000..c81525b9a --- /dev/null +++ b/src/couchjs-node/xml.js @@ -0,0 +1,23 @@ +// Copyright 2011 Iris Couch +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +module.exports = XML + +function XML () { + this.foo = 'bar' +} + +XML.prototype.toXMLString = function() { + return '<xml>\n <title>test</title>\n</xml>' +} |