import * as actions from '../actions/view'

import path from 'path'
import { shell } from 'electron'
import { isFile } from 'common/filesystem'
import * as actions from '../actions/help'
import { checkUpdates } from '../actions/marktext'

/// Check whether the package is updatable at runtime.
const isUpdatable = () => {
  // TODO: If not updatable, allow to check whether there is a new version available.

  const resFile = isFile(path.join(process.resourcesPath, 'app-update.yml'))
  if (!resFile) {
    // No update resource file available.
    return false
  } else if (process.env.APPIMAGE) {
    // We are running as AppImage.
    return true
  } else if (process.platform === 'win32' && isFile(path.join(process.resourcesPath, 'md.ico'))) {
    // Windows is a little but tricky. The update resource file is always available and
    // there is no way to check the target type at runtime (electron-builder#4119).
    // As workaround we check whether "md.ico" exists that is only included in the setup.
    return true
  }

  // Otherwise assume that we cannot perform an auto update (standalone binary, archives,
  // packed for package manager).
  return false
}

export default function () {
  const helpMenu = {
    label: 'Help',
    role: 'help',
    submenu: [{
      label: 'Learn More',
      click () {
        shell.openExternal('https://marktext.app')
      }
    }, {
      label: 'Source Code on GitHub',
      click () {
        shell.openExternal('https://github.com/marktext/marktext')
      }
    }, {
      label: 'License',
      click () {
        shell.openExternal('https://github.com/marktext/marktext/blob/master/LICENSE')
      }
    }, {
      label: 'Changelog',
      click () {
        shell.openExternal('https://github.com/marktext/marktext/blob/master/.github/CHANGELOG.md')
      }
    }, {
      label: 'Markdown syntax',
      click () {
        shell.openExternal('https://spec.commonmark.org/0.29/')
      }
    }, {
      type: 'separator'
    }, {
      label: 'Feedback via Twitter',
      click (item, win) {
        actions.showTweetDialog(win, 'twitter')
      }
    }, {
      label: 'Report Issue or Feature request',
      click () {
        shell.openExternal('https://github.com/marktext/marktext/issues')
      }
    }, {
      type: 'separator'
    }, {
      label: 'Follow @Jocs on Github',
      click () {
        shell.openExternal('https://github.com/Jocs')
      }
    }]
  }

  if (isUpdatable()) {
    helpMenu.submenu.push({
      type: 'separator'
    }, {
      label: 'Check for updates...',
      click (menuItem, browserWindow) {
        checkUpdates(menuItem, browserWindow)
      }
    })
  }

  if (process.platform !== 'darwin') {
    helpMenu.submenu.push({
      type: 'separator'
    }, {
      label: 'About Mark Text',
      click (menuItem, browserWindow) {
        actions.showAboutDialog(browserWindow)
      }
    })
  }
  return helpMenu
}


keep keep keep keep replace keep keep keep keep

import { app, Menu } from 'electron'
import * as actions from '../actions/file'

const dockMenu = Menu.buildFromTemplate([{
  label: 'Open...',
  click (menuItem, browserWindow) {
    actions.openFile(browserWindow)
  }
}, {
  label: 'Clear Recent',
  click () {
    app.clearRecentDocuments()
  }
}])

export default dockMenu


import fs from 'fs-extra'

/**
 * Test whether or not the given path exists.
 *
 * @param {string} p The path to the file or directory.
 * @returns {boolean}
 */
export const exists = async p => {
  // Nodes fs.exists is deprecated.
  try {
    await fs.access(p)
    return true
  } catch (_) {
    return false
  }
}

/**
 * Ensure that a directory exist.
 *
 * @param {string} dirPath The directory path.
 */
export const ensureDirSync = dirPath => {
  try {
    fs.ensureDirSync(dirPath)
  } catch (e) {
    if (e.code !== 'EEXIST') {
      throw e
    }
  }
}

/**
 * Returns true if the path is a directory with read access.
 *
 * @param {string} dirPath The directory path.
 */
export const isDirectory = dirPath => {
  try {
    return fs.existsSync(dirPath) && fs.lstatSync(dirPath).isDirectory()
  } catch (_) {
    return false
  }
}

/**
 * Returns true if the path is a file with read access.
 *
 * @param {string} filepath The file path.
 */
export const isFile = filepath => {
  try {
    return fs.existsSync(filepath) && fs.lstatSync(filepath).isFile()
  } catch (_) {
    return false
  }
}

/**
 * Returns true if the path is a symbolic link with read access.
 *
 * @param {string} filepath The link path.
 */
export const isSymbolicLink = filepath => {
  try {
    return fs.existsSync(filepath) && fs.lstatSync(filepath).isSymbolicLink()
  } catch (_) {
    return false
  }
}


// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE

const multiplexMode = CodeMirror => {
  CodeMirror.multiplexingMode = function (outer /*, others */) {
    // Others should be {open, close, mode [, delimStyle] [, innerStyle]} objects
    const others = Array.prototype.slice.call(arguments, 1)

    function indexOf (string, pattern, from, returnEnd) {
      if (typeof pattern === 'string') {
        const found = string.indexOf(pattern, from)
        return returnEnd && found > -1 ? found + pattern.length : found
      }
      const m = pattern.exec(from ? string.slice(from) : string)
      return m ? m.index + from + (returnEnd ? m[0].length : 0) : -1
    }

    return {
      startState () {
        return {
          outer: CodeMirror.startState(outer),
          innerActive: null,
          inner: null
        }
      },

      copyState (state) {
        return {
          outer: CodeMirror.copyState(outer, state.outer),
          innerActive: state.innerActive,
          inner: state.innerActive && CodeMirror.copyState(state.innerActive.mode, state.inner)
        }
      },

      token (stream, state) {
        if (!state.innerActive) {
          let cutOff = Infinity
          const oldContent = stream.string
          for (let i = 0; i < others.length; ++i) {
            const other = others[i]
            const found = indexOf(oldContent, other.open, stream.pos)
            if (found === stream.pos) {
              if (!other.parseDelimiters) stream.match(other.open)
              state.innerActive = other

              // Get the outer indent, making sure to handle CodeMirror.Pass
              let outerIndent = 0
              if (outer.indent) {
                const possibleOuterIndent = outer.indent(state.outer, '')
                if (possibleOuterIndent !== CodeMirror.Pass) outerIndent = possibleOuterIndent
              }

              state.inner = CodeMirror.startState(other.mode, outerIndent)
              return other.delimStyle && (other.delimStyle + ' ' + other.delimStyle + '-open')
            } else if (found !== -1 && found < cutOff) {
              cutOff = found
            }
          }
          if (cutOff !== Infinity) stream.string = oldContent.slice(0, cutOff)
          const outerToken = outer.token(stream, state.outer)
          if (cutOff !== Infinity) stream.string = oldContent
          return outerToken
        } else {
          const curInner = state.innerActive
          const oldContent = stream.string
          if (!curInner.close && stream.sol()) {
            state.innerActive = state.inner = null
            return this.token(stream, state)
          }
          const found = curInner.close ? indexOf(oldContent, curInner.close, stream.pos, curInner.parseDelimiters) : -1
          if (found === stream.pos && !curInner.parseDelimiters) {
            stream.match(curInner.close)
            state.innerActive = state.inner = null
            return curInner.delimStyle && (curInner.delimStyle + ' ' + curInner.delimStyle + '-close')
          }
          if (found > -1) stream.string = oldContent.slice(0, found)
          let innerToken = curInner.mode.token(stream, state.inner)
          if (found > -1) stream.string = oldContent

          if (found === stream.pos && curInner.parseDelimiters) {
            state.innerActive = state.inner = null
          }

          if (curInner.innerStyle) {
            if (innerToken) innerToken = innerToken + ' ' + curInner.innerStyle
            else innerToken = curInner.innerStyle
          }

          return innerToken
        }
      },

      indent (state, textAfter) {
        const mode = state.innerActive ? state.innerActive.mode : outer
        if (!mode.indent) return CodeMirror.Pass
        return mode.indent(state.innerActive ? state.inner : state.outer, textAfter)
      },

      blankLine (state) {
        const mode = state.innerActive ? state.innerActive.mode : outer
        if (mode.blankLine) {
          mode.blankLine(state.innerActive ? state.inner : state.outer)
        }
        if (!state.innerActive) {
          for (let i = 0; i < others.length; ++i) {
            const other = others[i]
            if (other.open === '\n') {
              state.innerActive = other
              state.inner = CodeMirror.startState(other.mode, mode.indent ? mode.indent(state.outer, '') : 0)
            }
          }
        } else if (state.innerActive.close === '\n') {
          state.innerActive = state.inner = null
        }
      },

      electricChars: outer.electricChars,

      innerMode (state) {
        return state.inner ? { state: state.inner, mode: state.innerActive.mode } : { state: state.outer, mode: outer }
      }
    }
  }
}

export default multiplexMode


import * as actions from '../actions/edit'
import { isOsx } from '../../config'

export default function (keybindings, userPreference) {
  const { aidou } = userPreference.getAll()
  return {
    label: 'Edit',
    submenu: [{
      label: 'Undo',
      accelerator: keybindings.getAccelerator('editUndo'),
      click: (menuItem, browserWindow) => {
        actions.edit(browserWindow, 'undo')
      }
    }, {
      label: 'Redo',
      accelerator: keybindings.getAccelerator('editRedo'),
      click: (menuItem, browserWindow) => {
        actions.edit(browserWindow, 'redo')
      }
    }, {
      type: 'separator'
    }, {
      label: 'Cut',
      accelerator: keybindings.getAccelerator('editCut'),
      role: 'cut'
    }, {
      label: 'Copy',
      accelerator: keybindings.getAccelerator('editCopy'),
      role: 'copy'
    }, {
      label: 'Paste',
      accelerator: keybindings.getAccelerator('editPaste'),
      role: 'paste'
    }, {
      type: 'separator'
    }, {
      label: 'Copy As Markdown',
      accelerator: keybindings.getAccelerator('editCopyAsMarkdown'),
      click (menuItem, browserWindow) {
        actions.edit(browserWindow, 'copyAsMarkdown')
      }
    }, {
      label: 'Copy As HTML',
      click (menuItem, browserWindow) {
        actions.edit(browserWindow, 'copyAsHtml')
      }
    }, {
      label: 'Paste As Plain Text',
      accelerator: keybindings.getAccelerator('editCopyAsPlaintext'),
      click (menuItem, browserWindow) {
        actions.edit(browserWindow, 'pasteAsPlainText')
      }
    }, {
      type: 'separator'
    }, {
      label: 'Select All',
      accelerator: keybindings.getAccelerator('editSelectAll'),
      click (menuItem, browserWindow) {
        actions.edit(browserWindow, 'selectAll')
      }
    }, {
      type: 'separator'
    }, {
      label: 'Duplicate',
      accelerator: keybindings.getAccelerator('editDuplicate'),
      click (menuItem, browserWindow) {
        actions.edit(browserWindow, 'duplicate')
      }
    }, {
      label: 'Create Paragraph',
      accelerator: keybindings.getAccelerator('editCreateParagraph'),
      click (menuItem, browserWindow) {
        actions.edit(browserWindow, 'createParagraph')
      }
    }, {
      label: 'Delete Paragraph',
      accelerator: keybindings.getAccelerator('editDeleteParagraph'),
      click (menuItem, browserWindow) {
        actions.edit(browserWindow, 'deleteParagraph')
      }
    }, {
      type: 'separator'
    }, {
      label: 'Find',
      accelerator: keybindings.getAccelerator('editFind'),
      click (menuItem, browserWindow) {
        actions.edit(browserWindow, 'find')
      }
    }, {
      label: 'Find Next',
      accelerator: keybindings.getAccelerator('editFindNext'),
      click (menuItem, browserWindow) {
        actions.edit(browserWindow, 'fineNext')
      }
    }, {
      label: 'Find Previous',
      accelerator: keybindings.getAccelerator('editFindPrevious'),
      click (menuItem, browserWindow) {
        actions.edit(browserWindow, 'findPrev')
      }
    }, {
      label: 'Replace',
      accelerator: keybindings.getAccelerator('editReplace'),
      click (menuItem, browserWindow) {
        actions.edit(browserWindow, 'replace')
      }
    }, {
      type: 'separator'
    }, {
      label: 'Aidou',
      visible: aidou,
      id: 'aidou',
      accelerator: keybindings.getAccelerator('editAidou'),
      click (menuItem, browserWindow) {
        actions.edit(browserWindow, 'aidou')
      }
    }, {
      label: 'Screenshot',
      id: 'screenshot',
      visible: isOsx,
      accelerator: keybindings.getAccelerator('editScreenshot'),
      click (menuItem, browserWindow) {
        actions.screenshot(browserWindow, 'screenshot')
      }
    }, {
      type: 'separator'
    }, {
      label: 'Line Ending',
      submenu: [{
        id: 'crlfLineEndingMenuEntry',
        label: 'Carriage return and line feed (CRLF)',
        type: 'radio',
        click (menuItem, browserWindow) {
          actions.lineEnding(browserWindow, 'crlf')
        }
      }, {
        id: 'lfLineEndingMenuEntry',
        label: 'Line feed (LF)',
        type: 'radio',
        click (menuItem, browserWindow) {
          actions.lineEnding(browserWindow, 'lf')
        }
      }]
    }, {
      type: 'separator'
    }]
  }
}


marktext

Next generation markdown editor

echo 'Please run "build" or "release:{linux,mac,win}"' && exit 1

node .electron-vue/build.js && electron-builder build --linux --publish always

node .electron-vue/build.js && electron-builder build --mac --publish always

node .electron-vue/build.js && electron-builder build --win --publish always

node .electron-vue/build.js && electron-builder --dir

cross-env BUILD_TARGET=clean node .electron-vue/build.js

npm run pack && cross-env MARKTEXT_EXIT_ON_ERROR=1 mocha --timeout 10000 test/e2e

eslint --ext .js,.vue -f ./node_modules/eslint-friendly-formatter src test

eslint --ext .js,.vue -f ./node_modules/eslint-friendly-formatter --fix src test

npm run pack:main && npm run pack:renderer

cross-env NODE_ENV=production webpack --progress --colors --config .electron-vue/webpack.main.config.js

cross-env NODE_ENV=production webpack --progress --colors --config .electron-vue/webpack.renderer.config.js

node -r esm test/specs/commonMark/run.spec.js && node -r esm test/specs/gfm/run.spec.js

cross-env NODE_ENV=test ELECTRON_DISABLE_SECURITY_WARNINGS=true karma start test/unit/karma.conf.js

cd src/muya && webpack --progress --colors --config webpack.config.js

npm run build:muya && cd src/muya && npm publish

electron-rebuild -f -o keytar,keyboard-layout,vscode-windows-registry

Output: build

Markdown

Markdown document

Markdown document

Markdown document

Markdown document

Markdown document

Markdown document

Markdown document


import { ipcRenderer, shell } from 'electron'
import notice from '../services/notification'

const state = {}

const getters = {}

const mutations = {}

const actions = {
  LISTEN_FOR_NOTIFICATION ({ commit }) {
    const DEFAULT_OPTS = {
      title: 'Infomation',
      type: 'primary',
      time: 10000,
      message: 'You should never see this message'
    }

    ipcRenderer.on('AGANI::show-notification', (e, opts) => {
      const options = Object.assign(DEFAULT_OPTS, opts)

      notice.notify(options)
    })

    ipcRenderer.on('AGANI::pandoc-not-exists', async (e, opts) => {
      const options = Object.assign(DEFAULT_OPTS, opts)
      options.showConfirm = true
      await notice.notify(options)
      shell.openExternal('http://pandoc.org')
    })
  }
}

export default { state, getters, mutations, actions }


import path from 'path'

class EnvPaths {
  /**
   * @param {string} userDataPath The user data path.
   * @returns
   */
  constructor (userDataPath) {
    const currentDate = new Date()
    if (!userDataPath) {
      throw new Error('"userDataPath" is not set.')
    }

    this._electronUserDataPath = userDataPath // path.join(userDataPath, 'electronUserData')
    this._userDataPath = userDataPath
    this._logPath = path.join(this._userDataPath, 'logs', `${currentDate.getFullYear()}${currentDate.getMonth() + 1}`)
    this._preferencesPath = userDataPath // path.join(this._userDataPath, 'preferences')

    this._dataCenterPath = userDataPath

    this._preferencesFilePath = path.join(this._preferencesPath, 'preference.json')

    // TODO(sessions): enable this...
    // this._globalStorage = path.join(this._userDataPath, 'globalStorage')
    // this._preferencesPath = path.join(this._userDataPath, 'preferences')
    // this._sessionsPath = path.join(this._userDataPath, 'sessions')
  }

  get electronUserDataPath () {
    // This path is identical to app.getPath('userData') but userDataPath must not necessarily be the same path.
    return this._electronUserDataPath
  }

  get userDataPath () {
    return this._userDataPath
  }

  get logPath () {
    return this._logPath
  }

  get preferencesPath () {
    return this._preferencesPath
  }

  get dataCenterPath () {
    return this._dataCenterPath
  }

  get preferencesFilePath () {
    return this._preferencesFilePath
  }
}

export default EnvPaths


// Initialize Date and parseInt with their initial global values.

// If this file does not exist, api.addFiles will throw an exception.

// Allow the meteor package to register a weak dependency on this

// package, even though es5-shim implicitly depends on meteor.

# Meteor packages used by this project, one per line.
# Check this file (and the other files in this directory) into your repository.
#
# 'meteor add' and 'meteor remove' will edit this file for you,
# but you can also edit it by hand.

meteor-platform
standard-minifiers # JS/CSS minifiers run for production mode
autopublish # publishes all data to the clients for prototyping
insecure # allows all DB writes from clients for prototyping
ecmascript # allows ECMAScript2015+ syntax by default


{{#template name="basicCollections"}}

Collections

Meteor stores data in *collections*. JavaScript objects stored in collections
are called `documents`.  To get started, declare a collection with
`new Mongo.Collection`.

{{> autoApiBox name="Mongo.Collection" options=""}}

Calling the `Mongo.Collection` constructor creates a collection object
which acts just like a MongoDB collection. If you pass a name when you
create the collection, then you are declaring a persistent collection
— one that is stored on the server and can be published to clients.

To allow both client code and server code to access the same collection
using the same API, it's usually best to declare collections as global
variables in a JavaScript file that's present on both client and server.

Here's an example of declaring two named, persistent collections as global
variables:

```
// In a JS file that's loaded on the client and the server
Posts = new Mongo.Collection("posts");
Comments = new Mongo.Collection("comments");
```

If you pass `null` as the name, then you're creating a local
collection. Local collections are not synchronized between the client and
the server; they are just temporary collections of JavaScript objects that
support Mongo-style `find`, `insert`, `update`, and `remove` operations.

By default, Meteor automatically publishes every document in your
collection to each connected client. To disable this behavior, you must
remove the `autopublish` package:

```
$ meteor remove autopublish
```

Then, use [`Meteor.publish`](#meteor_publish) and
[`Meteor.subscribe`](#meteor_subscribe) to specify which parts of your
collection should be published to which clients.

Use `findOne` or `find` to retrieve documents from a collection.

{{> autoApiBox name="Mongo.Collection#findOne" options="sort;skip;fields"}}

This method lets you retrieve a specific document from your
collection. The `findOne` method is most commonly called with a specific
document `_id`:

```
var post = Posts.findOne(postId);
```

However, you can also call `findOne` with a Mongo selector, which is an
object that specifies a required set of attributes of the desired
document. For example, this selector

```
var post = Posts.findOne({
  createdBy: "12345",
  title: {$regex: /first/}
});
```

will match this document

```
{
  createdBy: "12345",
  title: "My first post!",
  content: "Today was a good day."
}
```

You can read about MongoDB query operators such as `$regex`, `$lt` (less than),
`$text` (text search), and more in the [MongoDB
documentation](http://docs.mongodb.org/manual/reference/operator/query/).

One useful behavior that might not be obvious is that Mongo selectors also
match items in arrays. For example, this selector

```
Post.findOne({
  tags: "meteor"
});
```

will match this document

```
{
  title: "I love Meteor",
  createdBy: "242135223",
  tags: ["meteor", "javascript", "fun"]
}
```

The `findOne` method is reactive just like [`Session.get`](#session_get),
meaning that, if you use it inside a [template helper](#template_helpers)
or a [`Tracker.autorun`](#tracker_autorun) callback, it will automatically
rerender the view or rerun the computation if the returned document
changes.

Note that `findOne` will return `null` if it fails to find a matching document,
which often happens if the document hasn't been loaded yet or has been removed
from the collection, so you should be prepared to handle `null` values.

{{> autoApiBox name="Mongo.Collection#find" options="sort;skip;limit;fields"}}

The `find` method is similar to `findOne`, but instead of returning a
single document it returns a MongoDB *cursor*. A cursor is a special
object that represents a list of documents that might be returned from a
query. You can pass a cursor into a template helper anywhere you could
pass an array:

```
Template.blog.helpers({
  posts: function () {
    // this helper returns a cursor of
    // all of the posts in the collection
    return Posts.find();
  }
});
```

```
<!-- a template that renders multiple posts -->
<template name="blog">
  {{dstache}}#each posts}}
    <h1>{{dstache}}title}}</h1>
    <p>{{dstache}}content}}</p>
  {{dstache}}/each}}
</template>
```

When you want to retrieve the current list of documents from a cursor,
call the cursor's `.fetch()` method:

```
// get an array of posts
var postsArray = Posts.find().fetch();
```

Keep in mind that while the computation in which you call `fetch` will rerun
when the data changes, the resulting array will not be reactive if it is
passed somewhere else.

You can modify the data stored in a `Mongo.Collection` by calling `insert`,
`update`, or `remove`.

{{> autoApiBox "Mongo.Collection#insert"}}

Here's how you insert a document into a collection:

```
Posts.insert({
  createdBy: Meteor.userId(),
  createdAt: new Date(),
  title: "My first post!",
  content: "Today was a good day."
});
```

Every document in every `Mongo.Collection` has an `_id` field. It must be
unique, and is automatically generated if you don't provide one. The `_id`
field can be used to retrieve a specific document using
[`collection.findOne`](#findOne).

{{> autoApiBox "Mongo.Collection#update"}}

The selector here is just like the one you would pass to `find`, and can
match multiple documents. The modifier is an object that specifies which
changes should be made to the matched documents. Watch out - unless you use
an operator like `$set`, `update` will simply replace the entire matched
document with the modifier.

Here's an example of setting the `content` field on all posts whose titles
contain the word "first":

```
Posts.update({
  title: {$regex: /first/}
}, {
  $set: {content: "Tomorrow will be a great day."}
});
```

You can read about all of the different operators that are supported in the
[MongoDB documentation](http://docs.mongodb.org/manual/reference/operator/update/).

There's one catch: when you call `update` on the client, you can only find
documents by their `_id` field. To use all of the possible selectors, you
must call `update` in server code or from a [method](#meteor_methods).

{{> autoApiBox "Mongo.Collection#remove"}}

This method uses the same selectors as `find` and `update`, and removes
any documents that match the selector from the database. Use `remove`
carefully — there's no way to get that data back.

As with `update`, client code can only remove documents by `_id`, whereas
server code and [methods](#meteor_methods) can remove documents using any
selector.

{{> autoApiBox name="Mongo.Collection#allow" options="insert, update, remove"}}

In newly created apps, Meteor allows almost any calls to `insert`, `update`, and
`remove` from any client or server code. This is because apps started with
`meteor create` include the `insecure` package by default to simplify
development. Obviously, if any user could change the database whenever they
wanted it would be bad for security, so it is important to remove the
`insecure` package and specify some permissions rules:

```
$ meteor remove insecure
```

Once you have removed the `insecure` package, use the `allow` and `deny`
methods to control who can perform which operations on the database. By
default, all operations on the client are denied, so we need to add some
`allow` rules.  Keep in mind that server code and code inside
[methods](#meteor_methods) are not affected by `allow` and `deny` —
these rules only apply when `insert`, `update`, and `remove` are called
from untrusted client code.

For example, we might say that users can only create new posts if the
`createdBy` field matches the ID of the current user, so that users can't
impersonate each other.

```
// In a file loaded on the server (ignored on the client)
Posts.allow({
  insert: function (userId, post) {
    // can only create posts where you are the author
    return post.createdBy === userId;
  },
  remove: function (userId, post) {
    // can only delete your own posts
    return post.createdBy === userId;
  }
  // since there is no update field, all updates
  // are automatically denied
});
```

The `allow` method accepts three possible callbacks: `insert`, `remove`,
and `update`. The first argument to all three callbacks is the `_id` of
the logged in user, and the remaining arguments are as follows:

1. `insert(userId, document)`

    `document` is the document that is about to be inserted into the database.
    Return `true` if the insert should be allowed, `false` otherwise.

2. `update(userId, document, fieldNames, modifier)`

    `document` is the document that is about to be modified. `fieldNames` is an
    array of top-level fields that are affected by this change. `modifier` is
    the [Mongo Modifier](#mongo_modifiers) that was passed as the second
    argument of `collection.update`. It can be difficult to achieve correct
    validation using this callback, so it is recommended to use
    [methods](#meteor_methods) instead. Return `true` if the update should be
    allowed, `false` otherwise.

3. `remove(userId, document)`

    `document` is the document that is about to be removed from the database.
    Return `true` if the document should be removed, `false` otherwise.

{{> autoApiBox name="Mongo.Collection#deny" options="insert, update, remove"}}

The `deny` method lets you selectively override your `allow` rules. While
only one of your `allow` callbacks has to return true to allow a
modification, _every one_ of your `deny` callbacks has to return false for
the database change to happen.

For example, if we wanted to override part of our `allow` rule above to exclude
certain post titles:

```
// In a file loaded on the server (ignored on the client)
Posts.deny({
  insert: function (userId, post) {
    // Don't allow posts with a certain title
    return post.title === "First!";
  }
});
```

{{/template}}


// If the es5-shim package is installed, make sure it is evaluated

// before all other packages. Note that es5-shim registers an unordered

Package.describe({
  name: "es5-shim",
  version: "0.1.0-plugins.0",
  summary: "Shims and polyfills to improve ECMAScript 5 support",
  documentation: "README.md"
});

Npm.depends({
  "es5-shim": "4.1.7"
});

Package.onUse(function(api) {
  // Initialize Date and parseInt with their initial global values.
  api.addFiles("import_globals.js");

  api.addFiles([
    // If this file does not exist, api.addFiles will throw an exception.
    ".npm/package/node_modules/es5-shim/es5-shim.js"
  ], ["client", "server"], {
    // Files in the es5-shim package are already wrapped in closures.
    bare: true
  });

  // If Date and parseInt were actually reassigned, make the global
  // environment reflect those changes.
  api.addFiles("export_globals.js");

  // Make sure code that depends on this package gets the new values of
  // Date and parseInt.
  api.export("Date");
  api.export("parseInt");
});


/**
 * @summary Boolean variable.  True if running in a Cordova mobile environment.
 * @type {Boolean}
 * @static
 * @locus Anywhere
 */
Meteor.isCordova = true;


#pragma once
//-------------------------------------------------------------------------------------------------
// <copyright file="apuputil.h" company="Outercurve Foundation">
//   Copyright (c) 2004, Outercurve Foundation.
//   This software is released under Microsoft Reciprocal License (MS-RL).
//   The license and further copyright text can be found in the file
//   LICENSE.TXT at the root directory of the distribution.
// </copyright>
//
// <summary>
//    Header for Application Update helper functions.
// </summary>
//-------------------------------------------------------------------------------------------------

#ifdef __cplusplus
extern "C" {
#endif

#define ReleaseApupChain(p) if (p) { ApupFreeChain(p); p = NULL; }
#define ReleaseNullApupChain(p) if (p) { ApupFreeChain(p); p = NULL; }

const LPCWSTR APPLICATION_SYNDICATION_NAMESPACE = L"http://appsyndication.org/2006/appsyn";

enum APUP_HASH_ALGORITHM
{
    APUP_HASH_ALGORITHM_UNKNOWN,
    APUP_HASH_ALGORITHM_MD5,
    APUP_HASH_ALGORITHM_SHA1,
    APUP_HASH_ALGORITHM_SHA256,
};

struct APPLICATION_UPDATE_ENCLOSURE
{
    LPWSTR wzUrl;
    LPWSTR wzLocalName;
    DWORD64 dw64Size;

    BYTE* rgbDigest;
    DWORD cbDigest;
    APUP_HASH_ALGORITHM digestAlgorithm;

    BOOL fInstaller;
};

struct APPLICATION_UPDATE_ENTRY
{
    LPWSTR wzApplicationId;
    LPWSTR wzApplicationType;

    LPWSTR wzUpgradeId;
    BOOL fUpgradeExclusive;
    DWORD64 dw64Version;
    DWORD64 dw64UpgradeVersion;

    DWORD64 dw64TotalSize;

    DWORD cEnclosures;
    APPLICATION_UPDATE_ENCLOSURE* rgEnclosures;
};

struct APPLICATION_UPDATE_CHAIN
{
    LPWSTR wzDefaultApplicationId;
    LPWSTR wzDefaultApplicationType;

    DWORD cEntries;
    APPLICATION_UPDATE_ENTRY* rgEntries;
};

HRESULT DAPI ApupAllocChainFromAtom(
    __in ATOM_FEED* pFeed,
    __out APPLICATION_UPDATE_CHAIN** ppChain
    );

HRESULT DAPI ApupFilterChain(
    __in APPLICATION_UPDATE_CHAIN* pChain,
    __in DWORD64 dw64Version,
    __out APPLICATION_UPDATE_CHAIN** ppFilteredChain
    );

void DAPI ApupFreeChain(
    __in APPLICATION_UPDATE_CHAIN* pChain
    );

#ifdef __cplusplus
}
#endif


// By adding this package, you get the following default policy:
// No eval or other string-to-code, and content can only be loaded from the
// same origin as the app (except for XHRs and websocket connections, which can
// go to any origin). Browsers will also be told not to sniff content types
// away from declared content types (X-Content-Type-Options: nosniff).
//
// Apps should call BrowserPolicy.content.disallowInlineScripts() if they are
// not using any inline script tags and are willing to accept an extra round
// trip on page load.
//
// BrowserPolicy.content functions for tweaking CSP:
// allowInlineScripts()
// disallowInlineScripts(): adds extra round-trip to page load time
// allowInlineStyles()
// disallowInlineStyles()
// allowEval()
// disallowEval()
//
// For each type of content (script, object, image, media, font, connect,
// style), there are the following functions:
// allow<content type>Origin(origin): allows the type of content to be loaded
// from the given origin
// allow<content type>DataUrl(): allows the content to be loaded from data: URLs
// allow<content type>SameOrigin(): allows the content to be loaded from the
// same origin
// disallow<content type>(): disallows this type of content all together (can't
// be called for script)
//
// The following functions allow you to set rules for all types of content at
// once:
// allowAllContentOrigin(origin)
// allowAllContentDataUrl()
// allowAllContentSameOrigin()
// disallowAllContent()
//
// You can allow content type sniffing by calling
// `BrowserPolicy.content.allowContentTypeSniffing()`.

var cspSrcs;
var cachedCsp; // Avoid constructing the header out of cspSrcs when possible.

// CSP keywords have to be single-quoted.
var keywords = {
  unsafeInline: "'unsafe-inline'",
  unsafeEval: "'unsafe-eval'",
  self: "'self'",
  none: "'none'"
};

// If false, we set the X-Content-Type-Options header to 'nosniff'.
var contentSniffingAllowed = false;

BrowserPolicy.content = {};

var parseCsp = function (csp) {
  var policies = csp.split("; ");
  cspSrcs = {};
  _.each(policies, function (policy) {
    if (policy[policy.length - 1] === ";")
      policy = policy.substring(0, policy.length - 1);
    var srcs = policy.split(" ");
    var directive = srcs[0];
    if (_.indexOf(srcs, keywords.none) !== -1)
      cspSrcs[directive] = null;
    else
      cspSrcs[directive] = srcs.slice(1);
  });

  if (cspSrcs["default-src"] === undefined)
    throw new Error("Content Security Policies used with " +
                    "browser-policy must specify a default-src.");

  // Copy default-src sources to other directives.
  _.each(cspSrcs, function (sources, directive) {
    cspSrcs[directive] = _.union(sources || [], cspSrcs["default-src"] || []);
  });
};

var removeCspSrc = function (directive, src) {
  cspSrcs[directive] = _.without(cspSrcs[directive] || [], src);
};

// Prepare for a change to cspSrcs. Ensure that we have a key in the dictionary
// and clear any cached CSP.
var prepareForCspDirective = function (directive) {
  cspSrcs = cspSrcs || {};
  cachedCsp = null;
  if (! _.has(cspSrcs, directive))
    cspSrcs[directive] = _.clone(cspSrcs["default-src"]);
};

// Add `src` to the list of allowed sources for `directive`, with the
// following modifications if `src` is an origin:
// - If `src` does not have a protocol specified, then add both
//   http://<src> and https://<src>. This is to mask differing
//   cross-browser behavior; some browsers interpret an origin without a
//   protocol as http://<src> and some interpret it as both http://<src>
//   and https://<src>
// - Trim trailing slashes from `src`, since some browsers interpret
//   "foo.com/" as "foo.com" and some don't.
var addSourceForDirective = function (directive, src) {
  if (_.contains(_.values(keywords), src)) {
    cspSrcs[directive].push(src);
  } else {
    src = src.toLowerCase();

    // Trim trailing slashes.
    src = src.replace(/\/+$/, '');

    var toAdd = [];
    // If there is no protocol, add both http:// and https://.
    if (! /^([a-z0-9.+-]+:)/.test(src)) {
      toAdd.push("http://" + src);
      toAdd.push("https://" + src);
    } else {
      toAdd.push(src);
    }
    _.each(toAdd, function (s) {
      cspSrcs[directive].push(s);
    });
  }
};

var setDefaultPolicy = function () {
  // By default, unsafe inline scripts and styles are allowed, since we expect
  // many apps will use them for analytics, etc. Unsafe eval is disallowed, and
  // the only allowable content source is the same origin or data, except for
  // connect which allows anything (since meteor.com apps make websocket
  // connections to a lot of different origins).
  BrowserPolicy.content.setPolicy("default-src 'self'; " +
                                  "script-src 'self' 'unsafe-inline'; " +
                                  "connect-src *; " +
                                  "img-src data: 'self'; " +
                                  "style-src 'self' 'unsafe-inline';");
  contentSniffingAllowed = false;
};

var setWebAppInlineScripts = function (value) {
  if (! BrowserPolicy._runningTest())
    WebAppInternals.setInlineScriptsAllowed(value);
};

_.extend(BrowserPolicy.content, {
  allowContentTypeSniffing: function () {
    contentSniffingAllowed = true;
  },
  // Exported for tests and browser-policy-common.
  _constructCsp: function () {
    if (! cspSrcs || _.isEmpty(cspSrcs))
      return null;

    if (cachedCsp)
      return cachedCsp;

    var header = _.map(cspSrcs, function (srcs, directive) {
      srcs = srcs || [];
      if (_.isEmpty(srcs))
        srcs = [keywords.none];
      var directiveCsp = _.uniq(srcs).join(" ");
      return directive + " " + directiveCsp + ";";
    });

    header = header.join(" ");
    cachedCsp = header;
    return header;
  },
  _reset: function () {
    cachedCsp = null;
    setDefaultPolicy();
  },

  setPolicy: function (csp) {
    cachedCsp = null;
    parseCsp(csp);
    setWebAppInlineScripts(
      BrowserPolicy.content._keywordAllowed("script-src", keywords.unsafeInline)
    );
  },

  _keywordAllowed: function (directive, keyword) {
    return (cspSrcs[directive] &&
            _.indexOf(cspSrcs[directive], keyword) !== -1);
  },

  // Helpers for creating content security policies

  allowInlineScripts: function () {
    prepareForCspDirective("script-src");
    cspSrcs["script-src"].push(keywords.unsafeInline);
    setWebAppInlineScripts(true);
  },
  disallowInlineScripts: function () {
    prepareForCspDirective("script-src");
    removeCspSrc("script-src", keywords.unsafeInline);
    setWebAppInlineScripts(false);
  },
  allowEval: function () {
    prepareForCspDirective("script-src");
    cspSrcs["script-src"].push(keywords.unsafeEval);
  },
  disallowEval: function () {
    prepareForCspDirective("script-src");
    removeCspSrc("script-src", keywords.unsafeEval);
  },
  allowInlineStyles: function () {
    prepareForCspDirective("style-src");
    cspSrcs["style-src"].push(keywords.unsafeInline);
  },
  disallowInlineStyles: function () {
    prepareForCspDirective("style-src");
    removeCspSrc("style-src", keywords.unsafeInline);
  },

  // Functions for setting defaults
  allowSameOriginForAll: function () {
    BrowserPolicy.content.allowOriginForAll(keywords.self);
  },
  allowDataUrlForAll: function () {
    BrowserPolicy.content.allowOriginForAll("data:");
  },
  allowOriginForAll: function (origin) {
    prepareForCspDirective("default-src");
    _.each(_.keys(cspSrcs), function (directive) {
      addSourceForDirective(directive, origin);
    });
  },
  disallowAll: function () {
    cachedCsp = null;
    cspSrcs = {
      "default-src": []
    };
    setWebAppInlineScripts(false);
  },

  _xContentTypeOptions: function () {
    if (! contentSniffingAllowed) {
      return "nosniff";
    }
  }
});

// allow<Resource>Origin, allow<Resource>Data, allow<Resource>self, and
// disallow<Resource> methods for each type of resource.
_.each(["script", "object", "img", "media",
        "font", "connect", "style", "frame"],
       function (resource) {
         var directive = resource + "-src";
         var methodResource;
         if (resource !== "img") {
           methodResource = resource.charAt(0).toUpperCase() +
             resource.slice(1);
         } else {
           methodResource = "Image";
         }
         var allowMethodName = "allow" + methodResource + "Origin";
         var disallowMethodName = "disallow" + methodResource;
         var allowDataMethodName = "allow" + methodResource + "DataUrl";
         var allowSelfMethodName = "allow" + methodResource + "SameOrigin";

         var disallow = function () {
           cachedCsp = null;
           cspSrcs[directive] = [];
         };

         BrowserPolicy.content[allowMethodName] = function (src) {
           prepareForCspDirective(directive);
           addSourceForDirective(directive, src);
         };
         if (resource === "script") {
           BrowserPolicy.content[disallowMethodName] = function () {
             disallow();
             setWebAppInlineScripts(false);
           };
         } else {
           BrowserPolicy.content[disallowMethodName] = disallow;
         }
         BrowserPolicy.content[allowDataMethodName] = function () {
           prepareForCspDirective(directive);
           cspSrcs[directive].push("data:");
         };
         BrowserPolicy.content[allowSelfMethodName] = function () {
           prepareForCspDirective(directive);
           cspSrcs[directive].push(keywords.self);
         };
       });

setDefaultPolicy();


standard-minifiers # JS/CSS minifiers run for production mode

autopublish # publishes all data to the clients for prototyping

insecure # allows all DB writes from clients for prototyping

ecmascript # allows ECMAScript2015+ syntax by default

es5-shim # ECMAScript 5 compatibility for older browsers.


StubStream = function () {
  var self = this;

  self.sent = [];
  self.callbacks = {};
};

_.extend(StubStream.prototype, {
  // Methods from Stream
  on: function (name, callback) {
    var self = this;

    if (!self.callbacks[name])
      self.callbacks[name] = [callback];
    else
      self.callbacks[name].push(callback);
  },

  send: function (data) {
    var self = this;
    self.sent.push(data);
  },

  status: function () {
    return {status: "connected", fake: true};
  },

  reconnect: function () {
    // no-op
  },

  _lostConnection: function () {
    // no-op
  },

  // Methods for tests
  receive: function (data) {
    var self = this;

    if (typeof data === 'object') {
      data = EJSON.stringify(data);
    }

    _.each(self.callbacks['message'], function (cb) {
      cb(data);
    });
  },

  reset: function () {
    var self = this;
    _.each(self.callbacks['reset'], function (cb) {
      cb();
    });
  },

  // Provide a tag to detect stub streams.
  // We don't log heartbeat failures on stub streams, for example.
  _isStub: true
});


// The "tree string" format is a simple format for representing syntax trees.
//
// For example, the parse of `x++;` is written as:
// "program(expressionStmnt(postfix(identifier(x) ++) ;))"
//
// A Node is written as "name(item1 item2 item3)", with additional whitespace
// allowed anywhere between the name, parentheses, and items.
//
// Tokens don't need to be escaped unless they contain '(', ')', whitespace, or
// backticks, or are empty.  If they do, they can be written enclosed in backticks.
// To escape a backtick within backticks, double it.
//
// `stringify` generates "canonical" tree strings, which have no extra escaping
// or whitespace, just one space between items in a Node.

ParseNode.prototype.stringify = function () {
  return ParseNode.stringify(this);
};

var backtickEscape = function (str) {
  if (/[\s()`]/.test(str))
    return '`' + str.replace(/`/g, '``') + '`';
  else if (! str)
    return '``';
  else
    return str;
};

var backtickUnescape = function (str) {
  if (str.charAt(0) === '`') {
    if (str.length === 1 || str.slice(-1) !== '`')
      throw new Error("Mismatched ` in " + str);
    if (str.length === 2)
      str = '';
    else
      str = str.slice(1, -1).replace(/``/g, '`');
  }
  return str;
};

ParseNode.stringify = function (tree) {
  if (tree instanceof ParseNode) {
    var str = backtickEscape(tree.name);
    str += '(';
    var escapedChildren = [];
    for(var i = 0, N = tree.children.length; i < N; i++)
      escapedChildren.push(ParseNode.stringify(tree.children[i]));
    str += escapedChildren.join(' ');
    str += ')';
    return str;
  }

  // Treat a token object or string as a token.
  if (typeof tree.text === 'function')
    tree = tree.text();
  else if (typeof tree.text === 'string')
    tree = tree.text;
  return backtickEscape(String(tree));
};

ParseNode.unstringify = function (str) {
  var lexemes = str.match(/\(|\)|`([^`]||``)*`|`|[^\s()`]+/g) || [];
  var N = lexemes.length;
  var state = {
    i: 0,
    getParseError: function (expecting) {
      throw new Error("unstringify: Expecting " + expecting +", found " +
                      (lexemes[this.i] || "end of string"));
    },
    peek: function () { return lexemes[this.i]; },
    advance: function () { this.i++; }
  };
  var paren = function (chr) {
    return new Parser(chr, function (t) {
      if (t.peek() !== chr)
        return null;
      t.advance();
      return chr;
    });
  };
  var EMPTY_STRING = [""];
  var token = new Parser('token', function (t) {
    var txt = t.peek();
    if (!txt || txt.charAt(0) === '(' || txt.charAt(0) === ')')
      return null;

    t.advance();
    // can't return falsy value from successful parser
    return backtickUnescape(txt) || EMPTY_STRING;
  });

  // Make "item" lazy so it can be recursive.
  var item = Parsers.lazy('token', function () { return item; });

  // Parse a single node or token.
  item = Parsers.mapResult(
    Parsers.seq(token,
                Parsers.opt(Parsers.seq(
                  paren('('), Parsers.opt(Parsers.list(item)), paren(')')))),
    function (v) {
      for(var i = 0, N = v.length; i < N; i++)
        if (v[i] === EMPTY_STRING)
          v[i] = "";

      if (v.length === 1)
        // token
        return v[0];
      // node. exclude parens
      return new ParseNode(v[0], v.slice(2, -1));
    });

  var endOfString = new Parser("end of string", function (t) {
    return t.i === N ? [] : null;
  });

  return Parsers.seq(item, endOfString).parseRequired(state)[0];
};


keep keep keep keep keep replace keep keep keep keep keep

var crypto = Npm.require('crypto');
var fs = Npm.require('fs');
var path = Npm.require('path');

var knownBrowsers = [
  'android',
  'chrome',
  'chromium',
  'firefox',
  'ie',
  'mobileSafari',
  'safari'
];

var browsersEnabledByDefault = [
  'android',
  'chrome',
  'chromium',
  'ie',
  'mobileSafari',
  'safari'
];

var enabledBrowsers = {};
_.each(browsersEnabledByDefault, function (browser) {
  enabledBrowsers[browser] = true;
});

Meteor.AppCache = {
  config: function(options) {
    _.each(options, function (value, option) {
      if (option === 'browsers') {
        enabledBrowsers = {};
        _.each(value, function (browser) {
          enabledBrowsers[browser] = true;
        });
      }
      else if (_.contains(knownBrowsers, option)) {
        enabledBrowsers[option] = value;
      }
      else if (option === 'onlineOnly') {
        _.each(value, function (urlPrefix) {
          RoutePolicy.declare(urlPrefix, 'static-online');
        });
      }
      else {
        throw new Error('Invalid AppCache config option: ' + option);
      }
    });
  }
};

var browserEnabled = function(request) {
  return enabledBrowsers[request.browser.name];
};

__meteor_bootstrap__.htmlAttributeHooks.push(function (request) {
  if (browserEnabled(request))
    return 'manifest="/app.manifest"';
  else
    return null;
});

WebApp.connectHandlers.use(function(req, res, next) {
  if (req.url !== '/app.manifest') {
    return next();
  }

  // Browsers will get confused if we unconditionally serve the
  // manifest and then disable the app cache for that browser.  If
  // the app cache had previously been enabled for a browser, it
  // will continue to fetch the manifest as long as it's available,
  // even if we now are not including the manifest attribute in the
  // app HTML.  (Firefox for example will continue to display "this
  // website is asking to store data on your computer for offline
  // use").  Returning a 404 gets the browser to really turn off the
  // app cache.

  if (!browserEnabled(__meteor_bootstrap__.categorizeRequest(req))) {
    res.writeHead(404);
    res.end();
    return;
  }

  // After the browser has downloaded the app files from the server and
  // has populated the browser's application cache, the browser will
  // *only* connect to the server and reload the application if the
  // *contents* of the app manifest file has changed.
  //
  // So we have to ensure that if any static client resources change,
  // something changes in the manifest file.  We compute a hash of
  // everything that gets delivered to the client during the initial
  // web page load, and include that hash as a comment in the app
  // manifest.  That way if anything changes, the comment changes, and
  // the browser will reload resources.

  var hash = crypto.createHash('sha1');
  hash.update(JSON.stringify(__meteor_runtime_config__), 'utf8');
  _.each(WebApp.clientProgram.manifest, function (resource) {
    if (resource.where === 'client' || resource.where === 'internal') {
      hash.update(resource.hash);
    }
  });
  var digest = hash.digest('hex');

  var manifest = "CACHE MANIFEST\n\n";
  manifest += '# ' + digest + "\n\n";

  manifest += "CACHE:" + "\n";
  manifest += "/" + "\n";
  _.each(WebApp.clientProgram.manifest, function (resource) {
    if (resource.where === 'client' &&
        ! RoutePolicy.classify(resource.url)) {
      manifest += resource.url;
      // If the resource is not already cacheable (has a query
      // parameter, presumably with a hash or version of some sort),
      // put a version with a hash in the cache.
      //
      // Avoid putting a non-cacheable asset into the cache, otherwise
      // the user can't modify the asset until the cache headers
      // expire.
      if (!resource.cacheable)
        manifest += "?" + resource.hash;

      manifest += "\n";
    }
  });
  manifest += "\n";

  manifest += "FALLBACK:\n";
  manifest += "/ /" + "\n";
  // Add a fallback entry for each uncacheable asset we added above.
  //
  // This means requests for the bare url (/image.png instead of
  // /image.png?hash) will work offline. Online, however, the browser
  // will send a request to the server. Users can remove this extra
  // request to the server and have the asset served from cache by
  // specifying the full URL with hash in their code (manually, with
  // some sort of URL rewriting helper)
  _.each(WebApp.clientProgram.manifest, function (resource) {
    if (resource.where === 'client' &&
        ! RoutePolicy.classify(resource.url) &&
        !resource.cacheable) {
      manifest += resource.url + " " + resource.url +
        "?" + resource.hash + "\n";
    }
  });

  manifest += "\n";

  manifest += "NETWORK:\n";
  // TODO adding the manifest file to NETWORK should be unnecessary?
  // Want more testing to be sure.
  manifest += "/app.manifest" + "\n";
  _.each(
    [].concat(
      RoutePolicy.urlPrefixesFor('network'),
      RoutePolicy.urlPrefixesFor('static-online')
    ),
    function (urlPrefix) {
      manifest += urlPrefix + "\n";
    }
  );
  manifest += "*" + "\n";

  // content length needs to be based on bytes
  var body = new Buffer(manifest);

  res.setHeader('Content-Type', 'text/cache-manifest');
  res.setHeader('Content-Length', body.length);
  return res.end(body);
});

var sizeCheck = function() {
  var totalSize = 0;
  _.each(WebApp.clientProgram.manifest, function (resource) {
    if (resource.where === 'client') {
      totalSize += resource.size;
    }
  });
  if (totalSize > 5 * 1024 * 1024) {
    Meteor._debug(
      "** You are using the appcache package but the total size of the\n" +
      "** cached resources is " +
      (totalSize / 1024 / 1024).toFixed(1) + "MB.\n" +
      "**\n" +
      "** This is over the recommended maximum of 5 MB and may break your\n" +
      "** app in some browsers! See http://docs.meteor.com/#appcache\n" +
      "** for more information and fixes.\n"
    );
  }
};

sizeCheck();


#!/bin/bash

# NOTE: by default this tests the working copy, not the installed
# meteor.  To test the installed meteor, pass in --global. To test a
# version of meteor installed in a specific directory, set the
# METEOR_TOOLS_TREE_DIR and METEOR_WAREHOUSE_DIR environment variable.

set -e -x

cd `dirname $0`/..

# METEOR_TOOLS_TREE_DIR is set in run-tools-tests.sh in order to test
# running an installed version of Meteor (though notably without
# testing springboarding, which is separately tested by
# tools-springboard-test.sh)
if [ -z "$METEOR_TOOLS_TREE_DIR" ]; then
    METEOR="`pwd`/meteor"
else
    METEOR="$METEOR_TOOLS_TREE_DIR/bin/meteor"
fi

if [ -z "$NODE" ]; then
    NODE="$(pwd)/scripts/node.sh"
fi

#If this ever takes more options, use getopt
if [ "$1" == "--global" ]; then
    if [ -z "$TEST_RELEASE" ]; then
        METEOR=/usr/local/bin/meteor
    else
        METEOR="/usr/local/bin/meteor --release=$TEST_RELEASE"
    fi
    INSTALLED_METEOR=t
elif [ "$METEOR_WAREHOUSE_DIR" ]; then
    # The point of this testing script is to test the tools, so we make sure (in
    # lib/meteor.js) to not springboard if METEOR_TEST_NO_SPRINGBOARD is
    # set. Then we specify a random release that we pass to --release on all
    # commands. This could break if this specified release is incompatible with
    # the current tools, in which case you can build and publish a new release
    # and set it here.
    INSTALLED_METEOR=t
    export METEOR_TEST_NO_SPRINGBOARD=t
    if [ -z "$TEST_RELEASE" ]; then
        TEST_RELEASE="0.6.0-alpha3"
    fi

    METEOR="$METEOR --release=$TEST_RELEASE" # some random non-official release
fi

TEST_TMPDIR=`mktemp -d -t meteor-cli-test-XXXXXXXX`
OUTPUT="$TEST_TMPDIR/output"
trap 'echo "[...]"; tail -25 $OUTPUT; echo FAILED ; rm -rf "$TEST_TMPDIR" >/dev/null 2>&1' EXIT

cd "$TEST_TMPDIR"

## Begin actual tests

if [ -n "$INSTALLED_METEOR" ]; then
    if [ -n "$TEST_RELEASE" ]; then
        $METEOR --version | grep $TEST_RELEASE >> $OUTPUT
    else
        $METEOR --version >> $OUTPUT
    fi
else
    $METEOR --version 2>&1 | grep checkout >> $OUTPUT
fi

echo "... --help"
$METEOR --help | grep "List available" >> $OUTPUT
$METEOR run --help | grep "Port to listen" >> $OUTPUT
$METEOR test-packages --help | grep "Port to listen" >> $OUTPUT
$METEOR create --help | grep "Make a subdirectory" >> $OUTPUT
$METEOR update --help | grep "Sets the version" >> $OUTPUT
$METEOR add --help | grep "Adds packages" >> $OUTPUT
$METEOR remove --help | grep "Removes a package" >> $OUTPUT
$METEOR list --help | grep "Without arguments" >> $OUTPUT
$METEOR bundle --help | grep "Package this project" >> $OUTPUT
$METEOR mongo --help | grep "Opens a Mongo" >> $OUTPUT
$METEOR deploy --help | grep "Deploys the project" >> $OUTPUT
$METEOR logs --help | grep "Retrieves the" >> $OUTPUT
$METEOR reset --help | grep "Reset the current" >> $OUTPUT
$METEOR test-packages --help | grep "Runs unit tests" >> $OUTPUT

echo "... not in dir"

$METEOR 2>&1 | grep "run: You're not in" >> $OUTPUT
$METEOR run 2>&1 | grep "run: You're not in" >> $OUTPUT
$METEOR add foo 2>&1 | grep "add: You're not in" >> $OUTPUT
$METEOR remove foo 2>&1 | grep "remove: You're not in" >> $OUTPUT
$METEOR list --using 2>&1 | grep "list --using: You're not in" >> $OUTPUT
$METEOR bundle foo.tar.gz 2>&1 | grep "bundle: You're not in" >> $OUTPUT
$METEOR mongo 2>&1 | grep "mongo: You're not in" >> $OUTPUT
$METEOR deploy automated-test 2>&1 | grep "deploy: You're not in" >> $OUTPUT
$METEOR reset 2>&1 | grep "reset: You're not in" >> $OUTPUT

echo "... create"

DIR="skel with spaces"
$METEOR create "$DIR"
test -d "$DIR"
test -f "$DIR/$DIR.js"

## Tests in a meteor project
cd "$DIR"
# run in a subdirectory, just to make sure this also works
cd .meteor

echo "... add/remove/list"

$METEOR list | grep "backbone" >> $OUTPUT
! $METEOR list --using 2>&1 | grep "backbone" >> $OUTPUT
$METEOR add backbone 2>&1 | grep "backbone:" | grep -v "no such package" | >> $OUTPUT
$METEOR list --using | grep "backbone" >> $OUTPUT
grep backbone packages >> $OUTPUT # remember, we are already in .meteor
$METEOR remove backbone 2>&1 | grep "backbone: removed" >> $OUTPUT
! $METEOR list --using 2>&1 | grep "backbone" >> $OUTPUT

echo "... bundle"

$METEOR bundle foo.tar.gz
tar tvzf foo.tar.gz >>$OUTPUT

cd .. # we're now back to $DIR
echo "... run"

MONGOMARK='--bind_ip 127.0.0.1 --smallfiles --port 9102'
# kill any old test meteor
# there is probably a better way to do this, but it is at least portable across macos and linux
# (the || true is needed on linux, whose xargs will invoke kill even with no args)
ps ax | grep -e 'meteor.js -p 9100' | grep -v grep | awk '{print $1}' | xargs kill || true

! $METEOR mongo >> $OUTPUT 2>&1
$METEOR reset >> $OUTPUT 2>&1

test ! -d .meteor/local
! ps ax | grep -e "$MONGOMARK" | grep -v grep >> $OUTPUT

PORT=9100
$METEOR -p $PORT >> $OUTPUT 2>&1 &
METEOR_PID=$!

sleep 2 # XXX XXX lame

test -d .meteor/local/db
ps ax | grep -e "$MONGOMARK" | grep -v grep >> $OUTPUT
curl -s "http://localhost:$PORT" >> $OUTPUT

echo "show collections" | $METEOR mongo

# kill meteor, see mongo is still running
kill $METEOR_PID

sleep 10 # XXX XXX lame. have to wait for inner app to die via keepalive!

! ps ax | grep "$METEOR_PID" | grep -v grep >> $OUTPUT
ps ax | grep -e "$MONGOMARK"  | grep -v grep >> $OUTPUT

echo "... rerun"

$METEOR -p $PORT >> $OUTPUT 2>&1 &
METEOR_PID=$!

sleep 2 # XXX XXX lame

ps ax | grep -e "$MONGOMARK" | grep -v grep >> $OUTPUT
curl -s "http://localhost:$PORT" >> $OUTPUT

kill $METEOR_PID
sleep 10 # XXX XXX lame. have to wait for inner app to die via keepalive!

ps ax | grep -e "$MONGOMARK" | grep -v grep | awk '{print $1}' | xargs kill || true
sleep 2 # need to make sure these kills take effect

echo "... test-packages"

mkdir -p "$TEST_TMPDIR/local-packages/die-now/"
cat > "$TEST_TMPDIR/local-packages/die-now/package.js" <<EOF
Package.on_test(function (api) {
  api.use('deps'); // try to use a core package
  api.add_files(['die-now.js'], 'server');
});
EOF
cat > "$TEST_TMPDIR/local-packages/die-now/die-now.js" <<EOF
if (Meteor.isServer) {
  console.log("Dying");
  process.exit(0);
}
EOF

$METEOR test-packages --once -p $PORT $TEST_TMPDIR/local-packages/die-now | grep Dying >> $OUTPUT 2>&1
# since the server process was killed via 'process.exit', mongo is still running.
ps ax | grep -e "$MONGOMARK" | grep -v grep | awk '{print $1}' | xargs kill || true
sleep 2 # make sure mongo is dead

$METEOR test-packages -p $PORT >> $OUTPUT 2>&1 &

METEOR_PID=$!

sleep 2 # XXX XXX lame

ps ax | grep -e "$MONGOMARK" | grep -v grep >> $OUTPUT
curl -s "http://localhost:$PORT" >> $OUTPUT

kill $METEOR_PID
sleep 10 # XXX XXX lame. have to wait for inner app to die via keepalive!

ps ax | grep -e "$MONGOMARK" | grep -v grep | awk '{print $1}' | xargs kill || true
sleep 2 # need to make sure these kills take effect

echo "... mongo message"

# Run a server on the same port as mongod, so that mongod fails to start up. Rig
# it so that a single connection will cause it to exit.
$NODE -e 'require("net").createServer(function(){process.exit(0)}).listen('$PORT'+2, "127.0.0.1")' &

sleep 1

$METEOR -p $PORT > error.txt || true

grep 'port was closed' error.txt >> $OUTPUT

# Kill the server by connecting to it.
$NODE -e 'require("net").connect({host:"127.0.0.1",port:'$PORT'+2},function(){process.exit(0);})'

echo "... settings"

cat > settings.json <<EOF
{ "foo" : "bar",
  "baz" : "quux"
}
EOF

cat > settings.js <<EOF
if (Meteor.isServer) {
  Meteor.startup(function () {
    if (!Meteor.settings) process.exit(1);
    if (Meteor.settings.foo !== "bar") process.exit(1);
    process.exit(0);
  });
}
EOF

$METEOR -p $PORT --settings='settings.json' --once >> $OUTPUT
rm settings.js

# prepare die.js so that we have a server that loads packages and dies
cat > die.js <<EOF
if (Meteor.isServer)
  process.exit(1);
EOF

echo "... local-package-sets -- new package"

mkdir -p "$TEST_TMPDIR/local-packages/a-package-named-bar/"
cat > "$TEST_TMPDIR/local-packages/a-package-named-bar/package.js" <<EOF
Npm.depends({gcd: '0.0.0'});

Package.on_use(function(api) {
  api.add_files(['call_gcd.js'], 'server');
});
EOF

cat > "$TEST_TMPDIR/local-packages/a-package-named-bar/call_gcd.js" <<EOF
console.log("loaded a-package-named-bar");

var gcd = Npm.require('gcd');
console.log("gcd(4,6)=" + gcd(4,6));
EOF

! $METEOR add a-package-named-bar >> $OUTPUT
PACKAGE_DIRS="$TEST_TMPDIR/local-packages" $METEOR add a-package-named-bar >> $OUTPUT
! $METEOR -p $PORT --once | grep "loaded a-package-named-bar" >> $OUTPUT
PACKAGE_DIRS="$TEST_TMPDIR/local-packages" $METEOR -p $PORT --once | grep "loaded a-package-named-bar" >> $OUTPUT
PACKAGE_DIRS="$TEST_TMPDIR/local-packages" $METEOR bundle $TEST_TMPDIR/bundle.tar.gz >> $OUTPUT
tar tvzf $TEST_TMPDIR/bundle.tar.gz >>$OUTPUT
PACKAGE_DIRS="$TEST_TMPDIR/local-packages" $METEOR -p $PORT --once | grep "gcd(4,6)=2" >> $OUTPUT

echo "... local-package-sets -- overridden package"

mkdir -p "$TEST_TMPDIR/local-packages/accounts-ui/"
cat > "$TEST_TMPDIR/local-packages/accounts-ui/package.js" <<EOF
Package.describe({
  summary: "accounts-ui - overridden"
});

EOF

! $METEOR add accounts-ui 2>&1 | grep "accounts-ui - overridden" >> $OUTPUT
$METEOR remove accounts-ui 2>&1 >> $OUTPUT
PACKAGE_DIRS="$TEST_TMPDIR/local-packages" $METEOR add accounts-ui 2>&1 | grep "accounts-ui - overridden" >> $OUTPUT
! $METEOR list | grep "accounts-ui - overridden" >> $OUTPUT
PACKAGE_DIRS="$TEST_TMPDIR/local-packages" $METEOR list | grep "accounts-ui - overridden" >> $OUTPUT

# remove die.js, we're done with package tests.
rm die.js

## Cleanup
trap - EXIT
rm -rf "$DIR"
echo PASSED


// A place to store request tokens pending verification
Oauth1._requestTokens = {};

// connect middleware
Oauth._requestHandlers['1'] = function (service, query, res) {

  var config = ServiceConfiguration.configurations.findOne({service: service.serviceName});
  if (!config) {
    throw new ServiceConfiguration.ConfigError("Service " + service.serviceName + " not configured");
  }

  var urls = service.urls;
  var oauthBinding = new OAuth1Binding(
    config.consumerKey, config.secret, urls);

  if (query.requestTokenAndRedirect) {
    // step 1 - get and store a request token

    // Get a request token to start auth process
    oauthBinding.prepareRequestToken(query.requestTokenAndRedirect);

    // Keep track of request token so we can verify it on the next step
    Oauth1._requestTokens[query.state] = oauthBinding.requestToken;

    // redirect to provider login, which will redirect back to "step 2" below
    var redirectUrl = urls.authenticate + '?oauth_token=' + oauthBinding.requestToken;
    res.writeHead(302, {'Location': redirectUrl});
    res.end();
  } else {
    // step 2, redirected from provider login - complete the login
    // process: if the user authorized permissions, get an access
    // token and access token secret and log in as user

    // Get the user's request token so we can verify it and clear it
    var requestToken = Oauth1._requestTokens[query.state];
    delete Oauth1._requestTokens[query.state];

    // Verify user authorized access and the oauth_token matches
    // the requestToken from previous step
    if (query.oauth_token && query.oauth_token === requestToken) {

      // Prepare the login results before returning.  This way the
      // subsequent call to the `login` method will be immediate.

      // Get the access token for signing requests
      oauthBinding.prepareAccessToken(query);

      // Run service-specific handler.
      var oauthResult = service.handleOauthRequest(oauthBinding);

      // Add the login result to the result map
      Oauth._loginResultForCredentialToken[query.state] = {
          serviceName: service.serviceName,
          serviceData: oauthResult.serviceData,
          options: oauthResult.options
        };
    }

    // Either close the window, redirect, or render nothing
    // if all else fails
    Oauth._renderOauthResults(res, query);
  }
};


Package.describe({
  summary: "Provides JSON.stringify and JSON.parse for older browsers",
  internal: true
});

// We need to figure out how to serve this file only to browsers that
// don't have JSON.stringify (eg, IE7 and earlier -- or is that IE8?)

Package.on_use(function (api, where) {
  where = where || ['client', 'server'];

  api.add_files('json2.js', where);
});


// app HTML.  (Firefox for example will continue to display "this

// website is asking to store data on your computer for offline

// use").  Returning a 404 gets the browser to really turn off the

keep keep keep keep keep replace keep keep keep keep


var path = require('path');
var files = require(path.join(__dirname, 'files.js'));
var fs = require('fs');
var _ = require('underscore');

var sha1 = function (contents) {
  var crypto = require('crypto');
  var hash = crypto.createHash('sha1');
  hash.update(contents);
  return hash.digest('hex');
};

// Builder encapsulates much of the file-handling logic need to create
// "bundles" (directory trees such as site archives, programs, or
// packages.) It can create a temporary directory in which to build
// the bundle, moving the bundle atomically into place when and if the
// build successfully completes; sanitize and generate unique
// filenames; and track dependencies (files that should be watched for
// changes when developing interactively.)
//
// Options:
//  - outputPath: Required. Path to the directory that will hold the
//    bundle when building is complete. It should not exist. Its
//    parents will be created if necessary.
//  - symlink: if true, symlink rather than copy files/directories
//    where possible. This is faster and takes up less disk space but
//    produces a bundle that can't run elsewhere, and also that will
//    reflect changes to the input files in realtime (we don't care
//    about that, at least in development mode, since we reload on
//    change anyway..)
var Builder = function (options) {
  var self = this;
  options = options || {};

  self.outputPath = options.outputPath;

  // Paths already written to. Map from canonicalized relPath (no
  // trailing slash) to true for a file, or false for a directory.
  self.usedAsFile = { '': false, '.': false };

  self.shouldSymlink = !! options.symlink;

  // foo/bar => foo/.build1234.bar
  // Should we include a random number? The advantage is that multiple
  // builds can run in parallel. The disadvantage is that stale build
  // files hang around forever. For now, go with the former.
  var nonce = Math.floor(Math.random() * 999999);
  self.buildPath = path.join(path.dirname(self.outputPath),
                             '.build' + nonce + "." +
                             path.basename(self.outputPath));
  files.rm_recursive(self.buildPath);
  files.mkdir_p(self.buildPath, 0755);

  self.dependencyInfo = { directories: {}, files: {} };

  // XXX cleaner error handling. don't make the humans read an
  // exception (and, make suitable for use in automated systems)
};

_.extend(Builder.prototype, {
  // Like mkdir_p, but records in self.usedAsFile that we have created
  // the directories, and takes a path relative to the bundle
  // root. Throws an exception on failure.
  _ensureDirectory: function (relPath) {
    var self = this;

    var parts = path.normalize(relPath).split(path.sep);
    if (parts.length > 1 && parts[parts.length - 1] === '')
      parts.pop(); // remove trailing slash

    var partsSoFar = [];
    _.each(parts, function (part) {
      partsSoFar.push(part);
      var partial = partsSoFar.join(path.sep);
      if (! (partial in self.usedAsFile)) {
        // It's new -- create it
        fs.mkdirSync(path.join(self.buildPath, partial), 0755);
        self.usedAsFile[partial] = false;
      } else if (self.usedAsFile[partial]) {
        // Already exists and is a file. Oops.
        throw new Error("tried to make " + relPath + " a directory but " +
                        partial + " is already a file");
      } else {
        // Already exists and is a directory
      }
    });
  },

  // isDirectory defaults to false
  _sanitize: function (relPath, isDirectory) {
    var self = this;

    var parts = relPath.split(path.sep);
    var partsOut = [];
    for (var i = 0; i < parts.length; i++) {
      var part = parts[i];
      var shouldBeFile = (i === parts.length - 1) && ! isDirectory;

      // Basic sanitization
      if (part.match(/^\.+$/))
        throw new Error("Path contains forbidden segment '" + part + "'");
      part = part.replace(/[^a-zA-Z0-9._-]/g, '');

      // If at last component, pull extension (if any) off of part
      var ext = '';
      if (shouldBeFile) {
        var split = part.split('.');
        if (split.length > 1)
          ext = "." + split.pop();
        part = split.join('.');
      }

      // Make sure it's sufficiently unique
      var suffix = '';
      while (true) {
        var candidate = path.join(partsOut.join(path.sep), part + suffix + ext);
        if (candidate.length &&
            ! (candidate in self.usedAsFile) ||
            (shouldBeFile === self.usedAsFile[candidate]))
          // No conflict -- either not used, or it's two paths that
          // share a common ancestor directory (as opposed to one path
          // thinking that a/b should be a file, and another thinking
          // that it should be a directory)
          break;

        suffix++; // first increment will do '' -> 1
      }

      partsOut.push(part + suffix + ext);
    }

    return partsOut.join(path.sep);
  },

  // Write either a buffer or the contents of a file to `relPath` (a
  // path to a file relative to the bundle root), creating it (and any
  // enclosing directories) if it doesn't exist yet. Exactly one of
  // `data` and or `file` must be passed.
  //
  // Options:
  // - data: a Buffer to write to relPath.
  // - file: a filename to write to relPath, as a string.
  // - sanitize: if true, then all components of the path are stripped
  //   of any potentially troubling characters, an exception is thrown
  //   if any path segments consist entirely of dots (eg, '..'), and
  //   if there is a file in the bundle with the same relPath, then
  //   the path is changed by adding a numeric suffix.
  // - append: if true, append to the file if it exists rather than
  //   throwing an exception.
  // - executable: if true, mark the file as executable.
  //
  // Returns the final canonicalize relPath that was written to.
  //
  // If `file` is used then a dependency will be added on that file.
  write: function (relPath, options) {
    var self = this;
    options = options || {};

    // Ensure no trailing slash
    if (relPath.slice(-1) === path.sep)
      relPath = relPath.slice(0, -1);

    // In sanitize mode, ensure path does not contain segments like
    // '..', does not contain forbidden characters, and is unique.
    if (options.sanitize)
      relPath = self._sanitize(relPath);

    var data;
    if (options.data) {
      if (! (options.data instanceof Buffer))
        throw new Error("data must be a Buffer");
      if (options.file)
        throw new Error("May only pass one of data and file, not both");
      data = options.data;
    } else if (options.file) {
      var sourcePath = path.resolve(options.file);
      data = fs.readFileSync(sourcePath);
      self.dependencyInfo.files[sourcePath] = sha1(data);
    }

    self._ensureDirectory(path.dirname(relPath));
    var absPath = path.join(self.buildPath, relPath);
    if (options.append)
      fs.appendFileSync(absPath, data);
    else
      fs.writeFileSync(absPath, data);
    self.usedAsFile[relPath] = true;

    if (options.executable)
      fs.chmodSync(absPath, 0755); // rwxr-xr-x

    return relPath;
  },

  // Serialize `data` as JSON and write it to `relPath` (a path to a
  // file relative to the bundle root), creating parent directories as
  // necessary. Throw an exception if the file already exists.
  writeJson: function (relPath, data) {
    var self = this;

    // Ensure no trailing slash
    if (relPath.slice(-1) === path.sep)
      relPath = relPath.slice(0, -1);

    self._ensureDirectory(path.dirname(relPath));
    fs.writeFileSync(path.join(self.buildPath, relPath),
                     new Buffer(JSON.stringify(data, null, 2), 'utf8'));

    self.usedAsFile[relPath] = true;
  },

  // Add relPath to the list of "already taken" paths in the
  // bundle. This will cause write, when in sanitize mode, to never
  // pick this filename (and will prevent files that from being
  // written that would conflict with paths that we are expecting to
  // be directories.) Calling this twice on the same relPath will
  // given an exception.
  //
  // options:
  // - directory: set to true to reserve this relPath to be a
  //   directory rather than a file.
  reserve: function (relPath, options) {
    var self = this;
    options = options || {};

    // Ensure no trailing slash
    if (relPath.slice(-1) === path.sep)
      relPath = relPath.slice(0, -1);

    var parts = relPath.split(path.sep);
    var partsSoFar = [];
    for (var i = 0; i < parts.length; i ++) {
      var part = parts[i];
      partsSoFar.push(part);
      var soFar = partsSoFar.join(path.sep);
      if (self.usedAsFile[soFar])
        throw new Error("Path reservation conflict: " + relPath);

      var shouldBeDirectory = (i < parts.length - 1) || options.directory;
      if (shouldBeDirectory) {
        if (! (soFar in self.usedAsFile)) {
          fs.mkdirSync(path.join(self.buildPath, soFar), 0755);
          self.usedAsFile[soFar] = false;
        }
      } else {
        self.usedAsFile[soFar] = true;
      }
    }
  },

  // Generate and reserve a unique name for a file based on `relPath`,
  // and return it. If `relPath` is available (there is no file with
  // that name currently existing or reserved, it doesn't contain
  // forbidden characters, a prefix of it is not already in use as a
  // file rather than a directory) then the return value will be
  // `relPath`. Otherwise relPath will be modified to get the return
  // value, say by adding a numeric suffix to some path components
  // (preserving the file extension however) and deleting forbidden
  // characters. Throws an exception if relPath contains any segments
  // that are all dots (eg, '..').
  //
  // options:
  //
  // - directory: generate (and reserve) a name for a directory,
  //   rather than a file.
  generateFilename: function (relPath, options) {
    var self = this;
    options = options || {};

    relPath = self._sanitize(relPath, options.directory);
    self.reserve(relPath, { directory: options.directory });
    return relPath;
  },

  // Recursively copy a directory and all of its contents into the
  // bundle. But if the symlink option was passed to the Builder
  // constructor, then make a symlink instead, if possible.
  //
  // Adds dependencies both on the files that were copied, and on the
  // contents of the directory tree (respecting 'ignore'.) Disable
  // this with depend: false.
  //
  // Options:
  // - from: source path on local disk to copy from
  // - to: relative path to a directory in the bundle that will
  //   receive the files
  // - ignore: array of regexps of filenames (that is, basenames) to
  //   ignore (they may still be visible in the output bundle if
  //   symlinks are being used)
  // - depend: Should dependencies be added? Defaults to true.
  copyDirectory: function (options) {
    var self = this;
    options = options || {};

    var createDependencies =
      ('depend' in options) ? options.depend : true;

    var normOptionsTo = options.to;
    if (normOptionsTo.slice(-1) === path.sep)
      normOptionsTo = normOptionsTo.slice(0, -1);

    var absPathTo = path.join(self.buildPath, normOptionsTo);
    if (self.shouldSymlink) {
      var canSymlink = true;
      if (self.usedAsFile[normOptionsTo]) {
        throw new Error("tried to copy a directory onto " + normOptionsTo +
                        " but it is is already a file");
      } else if (normOptionsTo in self.usedAsFile) {
        // It's already here and is a directory, maybe because of a call to
        // reserve with {directory: true}. If it's an empty directory, this is
        // salvageable. The directory should exist, because all code paths which
        // set usedAsFile to false create the directory.
        //
        // XXX This is somewhat broken: what if the reason we're in
        // self.usedAsFile is because an immediate child of ours was reserved as
        // a file but not actually written yet?
        var children = fs.readdirSync(absPathTo);
        if (_.isEmpty(children)) {
          fs.rmdirSync(absPathTo);
        } else {
          canSymlink = false;
        }
      }

      if (canSymlink) {
        self._ensureDirectory(path.dirname(normOptionsTo));
        fs.symlinkSync(path.resolve(options.from), absPathTo);
        return;
      }
    }

    var ignore = options.ignore || [];
    if (createDependencies) {
      self.dependencyInfo.directories[absPathTo] = {
        include: [/.?/],
        exclude: ignore
      };
    }

    var walk = function (absFrom, relTo) {
      self._ensureDirectory(relTo);

      _.each(fs.readdirSync(absFrom), function (item) {
        if (_.any(ignore, function (pattern) {
          return item.match(pattern);
        })) return; // skip excluded files

        var thisAbsFrom = path.resolve(absFrom, item);
        var thisRelTo = path.join(relTo, item);
        if (fs.statSync(thisAbsFrom).isDirectory()) {
          walk(thisAbsFrom, thisRelTo);
          return;
        }

        // XXX avoid reading whole file into memory
        var data = fs.readFileSync(thisAbsFrom);

        if (createDependencies)
          self.dependencyInfo.files[thisAbsFrom] = sha1(data);

        fs.writeFileSync(path.resolve(self.buildPath, thisRelTo), data);
        self.usedAsFile[thisRelTo] = true;
      });
    };

    walk(options.from, normOptionsTo);
  },

  // Returns a new Builder-compatible object that works just like a
  // Builder, but interprets all paths relative to 'relPath', a path
  // relative to the bundle root which should not start with a '/'.
  //
  // The sub-builder returned does not have all Builder methods (for
  // example, complete() wouldn't make sense) and you should not rely
  // on it being instanceof Builder.
  enter: function (relPath) {
    var self = this;
    var methods = ["write", "writeJson", "reserve", "generateFilename",
                   "copyDirectory", "enter"];
    var ret = {};

    _.each(methods, function (method) {
      ret[method] = function (/* arguments */) {
        var args = _.toArray(arguments);

        if (method !== "copyDirectory") {
          // Normal method (relPath as first argument)
          args = _.clone(args);
          args[0] = path.join(relPath, args[0]);
        } else {
          // with copyDirectory the path we have to fix up is inside
          // an options hash
          args[0] = _.clone(args[0]);
          args[0].to = path.join(relPath, args[0].to);
        }

        var ret = self[method].apply(self, args);

        if (method === "generateFilename") {
          // fix up the returned path to be relative to the
          // sub-bundle, not the parent bundle
          if (ret.substr(0, 1) === '/')
            ret = ret.substr(1);
          if (ret.substr(0, relPath.length) !== relPath)
            throw new Error("generateFilename returned path outside of " +
                            "sub-bundle?");
          ret = ret.substr(relPath.length);
        }

        return ret;
      };
    });

    return ret;
  },

  // Move the completed bundle into its final location (outputPath)
  complete: function () {
    var self = this;
    // XXX Alternatively, we could just keep buildPath around, and make
    // outputPath be a symlink pointing to it. This doesn't work for the NPM use
    // case of renameDirAlmostAtomically since that one is constructing files to
    // be checked in to version control, but here we could get away with it.
    files.renameDirAlmostAtomically(self.buildPath, self.outputPath);
  },

  // Delete the partially-completed bundle. Do not disturb outputPath.
  abort: function () {
    var self = this;
    files.rm_recursive(self.buildPath);
  },

  // Return all dependency info that has accumulated, in the format
  // expected by watch.Watcher.
  getDependencyInfo: function () {
    var self = this;
    return self.dependencyInfo;
  }
});

// static convenience method
Builder.sha1 = sha1;

module.exports = Builder;


#!/bin/bash
set -e
set -u
cd `dirname $0`
DIR="$(pwd)"
METEOR_DIR="$(pwd)/../.."

if [ $# -gt 2 ]; then
  echo "usage: publish-release.sh [GIT-REV [RELEASE-NAME]]"
  exit 1
fi

if [ $# -lt 1 ]; then
  GIT_SHA="$(git rev-parse HEAD)"
else
  GIT_SHA="$(git rev-parse "$1")"
fi

if [ $# -lt 2 ]; then
  RELEASE_NAME="$GIT_SHA"
else
  RELEASE_NAME="$2"
fi

# prepare settings file with git sha of last commit
PUBLISH_TMPDIR=$(mktemp -d -t meteor-publish-release-XXXXXXXX)
cat > "$PUBLISH_TMPDIR/settings.json" <<EOF
{"git-sha": "$GIT_SHA", "release-name": "$RELEASE_NAME"}
EOF

# ensure our 'awssum' smart package is up-to-date
cd $METEOR_DIR
git submodule init
git submodule update

# publish-release is a meteor app
cd $DIR/publish-release

# run it
#
# XXX when we support third-party packages use that mechanism instead
# of keeping the package in git.
#
# XXX it would be cool to be able to not listen on any port here. instead
# we use port 31337
$METEOR_DIR/meteor -p 31337 --once --settings=$PUBLISH_TMPDIR/settings.json


keep keep keep replace replace replace replace keep keep keep keep keep

// As a temporary hack there is a `categorizeRequest` function on WebApp which

// converts a connect `req` to a Meteor `request`. This can go away once smart

// packages such as appcache are being passed a `request` object directly when

////////// Requires //////////

var fs = Npm.require("fs");
var http = Npm.require("http");
var os = Npm.require("os");
var path = Npm.require("path");
var url = Npm.require("url");

var connect = Npm.require('connect');
var optimist = Npm.require('optimist');
var useragent = Npm.require('useragent');

// @export WebApp
WebApp = {};

var findGalaxy = _.once(function () {
  if (!('GALAXY' in process.env)) {
    console.log(
      "To do Meteor Galaxy operations like binding to a Galaxy " +
        "proxy, the GALAXY environment variable must be set.");
    process.exit(1);
  }

  return Meteor.connect(process.env['GALAXY']);
});

// Keepalives so that when the outer server dies unceremoniously and
// doesn't kill us, we quit ourselves. A little gross, but better than
// pidfiles.
// XXX This should really be part of the boot script, not the webapp package.
//     Or we should just get rid of it, and rely on containerization.

var initKeepalive = function () {
  var keepaliveCount = 0;

  process.stdin.on('data', function (data) {
    keepaliveCount = 0;
  });

  process.stdin.resume();

  setInterval(function () {
    keepaliveCount ++;
    if (keepaliveCount >= 3) {
      console.log("Failed to receive keepalive! Exiting.");
      process.exit(1);
    }
  }, 3000);
};

// #BrowserIdentification
//
// We have multiple places that want to identify the browser: the
// unsupported browser page, the appcache package, and, eventually
// delivering browser polyfills only as needed.
//
// To avoid detecting the browser in multiple places ad-hoc, we create a
// Meteor "browser" object. It uses but does not expose the npm
// useragent module (we could choose a different mechanism to identify
// the browser in the future if we wanted to).  The browser object
// contains
//
// * `name`: the name of the browser in camel case
// * `major`, `minor`, `patch`: integers describing the browser version
//
// Also here is an early version of a Meteor `request` object, intended
// to be a high-level description of the request without exposing
// details of connect's low-level `req`.  Currently it contains:
//
// * `browser`: browser identification object described above
// * `url`: parsed url, including parsed query params
//
// As a temporary hack there is a `categorizeRequest` function on
// __meteor_bootstrap__ which converts a connect `req` to a Meteor
// `request`. This can go away once smart packages such as appcache are
// being passed a `request` object directly when they serve content.
//
// This allows `request` to be used uniformly: it is passed to the html
// attributes hook, and the appcache package can use it when deciding
// whether to generate a 404 for the manifest.
//
// Real routing / server side rendering will probably refactor this
// heavily.

// e.g. "Mobile Safari" => "mobileSafari"
var camelCase = function (name) {
  var parts = name.split(' ');
  parts[0] = parts[0].toLowerCase();
  for (var i = 1;  i < parts.length;  ++i) {
    parts[i] = parts[i].charAt(0).toUpperCase() + parts[i].substr(1);
  }
  return parts.join('');
};

var identifyBrowser = function (req) {
  var userAgent = useragent.lookup(req.headers['user-agent']);
  return {
    name: camelCase(userAgent.family),
    major: +userAgent.major,
    minor: +userAgent.minor,
    patch: +userAgent.patch
  };
};

var categorizeRequest = function (req) {
  return {
    browser: identifyBrowser(req),
    url: url.parse(req.url, true)
  };
};

var htmlAttributes = function (template, request) {
  var attributes = '';
  _.each(__meteor_bootstrap__.htmlAttributeHooks || [], function (hook) {
    var attribute = hook(request);
    if (attribute !== null && attribute !== undefined && attribute !== '')
      attributes += ' ' + attribute;
  });
  return template.replace('##HTML_ATTRIBUTES##', attributes);
};

// Serve app HTML for this URL?
var appUrl = function (url) {
  if (url === '/favicon.ico' || url === '/robots.txt')
    return false;

  // NOTE: app.manifest is not a web standard like favicon.ico and
  // robots.txt. It is a file name we have chosen to use for HTML5
  // appcache URLs. It is included here to prevent using an appcache
  // then removing it from poisoning an app permanently. Eventually,
  // once we have server side routing, this won't be needed as
  // unknown URLs with return a 404 automatically.
  if (url === '/app.manifest')
    return false;

  // Avoid serving app HTML for declared routes such as /sockjs/.
  if (RoutePolicy.classify(url))
    return false;

  // we currently return app HTML on all URLs by default
  return true;
};

Meteor._postStartup = function (callback) {
  __meteor_bootstrap__.postStartupHooks.push(callback);
};

var runWebAppServer = function () {
  // read the control for the client we'll be serving up
  var clientJsonPath = path.join(__meteor_bootstrap__.serverDir,
                                 __meteor_bootstrap__.configJson.client);
  var clientDir = path.dirname(clientJsonPath);
  var clientJson = JSON.parse(fs.readFileSync(clientJsonPath, 'utf8'));

  if (clientJson.format !== "browser-program-pre1")
    throw new Error("Unsupported format for client assets: " +
                    JSON.stringify(clientJson.format));

  // XXX change all this config to something more reasonable
  var deployConfig =
        process.env.METEOR_DEPLOY_CONFIG
        ? JSON.parse(process.env.METEOR_DEPLOY_CONFIG) : {};
  if (!deployConfig.packages)
    deployConfig.packages = {};
  if (!deployConfig.boot)
    deployConfig.boot = {};
  if (!deployConfig.boot.bind)
    deployConfig.boot.bind = {};

  // check environment for legacy env variables.
  if (process.env.PORT && !_.has(deployConfig.boot.bind, 'localPort')) {
    deployConfig.boot.bind.localPort = parseInt(process.env.PORT);
  }
  if (process.env.MONGO_URL) {
    if (!deployConfig.packages['mongo-livedata'])
      deployConfig.packages['mongo-livedata'] = {};
    deployConfig.packages['mongo-livedata'].url = process.env.MONGO_URL;
  }

  // webserver
  var app = connect();

  // Strip off the path prefix, if it exists.
  app.use(function (request, response, next) {
    var pathPrefix = __meteor_runtime_config__.PATH_PREFIX;
    var url = Npm.require('url').parse(request.url);
    var pathname = url.pathname;
    // check if the path in the url starts with the path prefix (and the part
    // after the path prefix must start with a / if it exists.)
    if (pathPrefix && pathname.substring(0, pathPrefix.length) === pathPrefix &&
       (pathname.length == pathPrefix.length
        || pathname.substring(pathPrefix.length, pathPrefix.length + 1) === "/")) {
      request.url = request.url.substring(pathPrefix.length);
      next();
    } else if (pathname === "/favicon.ico" || pathname === "/robots.txt") {
      next();
    } else if (pathPrefix) {
      response.writeHead(404);
      response.write("Unknown path");
      response.end();
    } else {
      next();
    }
  });
  // Parse the query string into res.query. Only oauth_server cares about this,
  // but it's overkill to have that package depend on its own copy of connect
  // just for this simple processing.
  app.use(connect.query());

  // Auto-compress any json, javascript, or text.
  app.use(connect.compress());

  if (clientJson.staticCacheable) {
    // cacheable files are files that should never change. Typically
    // named by their hash (eg meteor bundled js and css files).
    // cache them ~forever (1yr)
    app.use(connect.static(path.join(clientDir, clientJson.staticCacheable),
                           {maxAge: 1000 * 60 * 60 * 24 * 365}));
  }

  // cache non-cacheable file anyway. This isn't really correct, as
  // users can change the files and changes won't propogate
  // immediately. However, if we don't cache them, browsers will
  // 'flicker' when rerendering images. Eventually we will probably want
  // to rewrite URLs of static assets to include a query parameter to
  // bust caches. That way we can both get good caching behavior and
  // allow users to change assets without delay.
  // https://github.com/meteor/meteor/issues/773
  if (clientJson.static) {
    app.use(connect.static(path.join(clientDir, clientJson.static),
                           {maxAge: 1000 * 60 * 60 * 24}));
  }

  // Packages and apps can add handlers to this via WebApp.connectHandlers.
  // They are inserted before our default handler.
  var packageAndAppHandlers = connect();
  app.use(packageAndAppHandlers);

  var suppressConnectErrors = false;
  // connect knows it is an error handler because it has 4 arguments instead of
  // 3. go figure.  (It is not smart enough to find such a thing if it's hidden
  // inside packageAndAppHandlers.)
  app.use(function (err, req, res, next) {
    if (!err || !suppressConnectErrors || !req.headers['x-suppress-error']) {
      next(err);
      return;
    }
    res.writeHead(err.status, { 'Content-Type': 'text/plain' });
    res.end("An error message");
  });

  // Will be updated by main before we listen.
  var boilerplateHtml = null;
  app.use(function (req, res, next) {
    if (! appUrl(req.url))
      return next();

    if (!boilerplateHtml)
      throw new Error("boilerplateHtml should be set before listening!");

    var request = categorizeRequest(req);

    res.writeHead(200, {'Content-Type': 'text/html; charset=utf-8'});

    var requestSpecificHtml = htmlAttributes(boilerplateHtml, request);
    res.write(requestSpecificHtml);
    res.end();
    return undefined;
  });

  // Return 404 by default, if no other handlers serve this URL.
  app.use(function (req, res) {
    res.writeHead(404);
    res.end();
  });

  var httpServer = http.createServer(app);

  // start up app
  _.extend(WebApp, {
    connectHandlers: packageAndAppHandlers,
    httpServer: httpServer,
    // metadata about the client program that we serve
    clientProgram: {
      manifest: clientJson.manifest
      // XXX do we need a "root: clientDir" field here? it used to be here but
      // was unused.
    },
    // For testing.
    suppressConnectErrors: function () {
      suppressConnectErrors = true;
    },
    // Hack: allow http tests to call connect.basicAuth without making them
    // Npm.depends on another copy of connect. (That would be fine if we could
    // have test-only NPM dependencies but is overkill here.)
    __basicAuth__: connect.basicAuth
  });
  _.extend(__meteor_bootstrap__, {
    // function that takes a connect `req` object and returns a summary
    // object with information about the request. See
    // #BrowserIdentifcation
    categorizeRequest: categorizeRequest,
    // list of functions to be called to determine any attributes to be
    // added to the '<html>' tag. Each function is passed a 'request'
    // object (see #BrowserIdentifcation) and should return a string,
    htmlAttributeHooks: [],
    deployConfig: deployConfig
  });

  // Let the rest of the packages (and Meteor.startup hooks) insert connect
  // middlewares and update __meteor_runtime_config__, then keep going to set up
  // actually serving HTML.
  // @export main
  main = function (argv) {
    argv = optimist(argv).boolean('keepalive').argv;

    var boilerplateHtmlPath = path.join(clientDir, clientJson.page);
    boilerplateHtml =
      fs.readFileSync(boilerplateHtmlPath, 'utf8')
      .replace(
        "// ##RUNTIME_CONFIG##",
        "__meteor_runtime_config__ = " +
          JSON.stringify(__meteor_runtime_config__) + ";")
      .replace(
          /##PATH_PREFIX##/g,
        __meteor_runtime_config__.PATH_PREFIX || "");

    // only start listening after all the startup code has run.
    var bind = deployConfig.boot.bind;
    httpServer.listen(bind.localPort || 0, Meteor.bindEnvironment(function() {
      if (argv.keepalive || true)
        console.log("LISTENING"); // must match run.js
      var port = httpServer.address().port;
      if (bind.viaProxy && bind.viaProxy.proxyEndpoint) {
        Meteor._bindToProxy(bind.viaProxy);
      } else if (bind.viaProxy) {
        // bind via the proxy, but we'll have to find it ourselves via
        // ultraworld.
        var galaxy = findGalaxy();
        galaxy.subscribe('servicesByName', 'proxy');
        var Proxies = new Meteor.Collection('services', {
          manager: galaxy
        });
        var doBinding = function (proxyService) {
          if (proxyService.providers.proxy) {
            Log("Attempting to bind to proxy at " + proxyService.providers.proxy);
            Meteor._bindToProxy(_.extend({
              proxyEndpoint: proxyService.providers.proxy
            }, bind.viaProxy));
          }
        };
        Proxies.find().observe({
          added: doBinding,
          changed: doBinding
        });
      }

      _.each(__meteor_bootstrap__.postStartupHooks, function (x) { x(); });

    }, function (e) {
      console.error("Error listening:", e);
      console.error(e.stack);
    }));

    if (argv.keepalive)
      initKeepalive();
    return 'DAEMON';
  };
};

Meteor._bindToProxy = function (proxyConfig) {

  var securePort = proxyConfig.securePort || 4433;
  var insecurePort = proxyConfig.insecurePort || 8080;
  var bindPathPrefix = proxyConfig.bindPathPrefix || "";
  // XXX also support galaxy-based lookup
  if (!proxyConfig.proxyEndpoint)
    throw new Error("missing proxyEndpoint");
  if (!proxyConfig.bindHost)
    throw new Error("missing bindHost");
  // XXX move these into deployConfig?
  if (!process.env.GALAXY_JOB)
    throw new Error("missing $GALAXY_JOB");
  if (!process.env.GALAXY_APP)
    throw new Error("missing $GALAXY_APP");
  if (!process.env.LAST_START)
    throw new Error("missing $LAST_START");

  // XXX rename pid argument to bindTo.
  var pid = {
    job: process.env.GALAXY_JOB,
    lastStarted: process.env.LAST_START,
    app: process.env.GALAXY_APP
  };
  var myHost = os.hostname();

  var ddpBindTo = {
    ddpUrl: 'ddp://' + proxyConfig.bindHost + ':' + securePort + bindPathPrefix + '/',
    insecurePort: insecurePort
  };

  // This is run after packages are loaded (in main) so we can use
  // Meteor.connect.
  var proxy = Meteor.connect(proxyConfig.proxyEndpoint);
  var route = process.env.ROUTE;
  var host = route.split(":")[0];
  var port = +route.split(":")[1];
  proxy.call('bindDdp', {
    pid: pid,
    bindTo: ddpBindTo,
    proxyTo: {
      host: host,
      port: port,
      pathPrefix: bindPathPrefix + '/websocket'
    }
  });
  proxy.call('bindHttp', {
    pid: pid,
    bindTo: {
      host: proxyConfig.bindHost,
      port: insecurePort,
      pathPrefix: bindPathPrefix
    },
    proxyTo: {
      host: host,
      port: port,
      pathPrefix: bindPathPrefix
    }
  });
  if (proxyConfig.securePort !== null) {
    proxy.call('bindHttp', {
      pid: pid,
      bindTo: {
        host: proxyConfig.bindHost,
        port: securePort,
        pathPrefix: bindPathPrefix,
        ssl: true
      },
      proxyTo: {
        host: host,
        port: port,
        pathPrefix: bindPathPrefix
      }
    });
  }
  Log("Bound to proxy");
};

runWebAppServer();