apps 0.8.3 → 1.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -166
- package/dist/authoring/index.d.ts +205 -0
- package/dist/authoring/index.js +26 -0
- package/dist/chunk-B7NEUIJL.js +14 -0
- package/dist/chunk-J5AVXTHA.js +154 -0
- package/dist/chunk-LWKQDDLZ.js +98 -0
- package/dist/chunk-NFLNA2SC.js +11 -0
- package/dist/chunk-RZA7AXBK.js +107 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +26 -0
- package/dist/manifest/collect.d.ts +64 -0
- package/dist/manifest/contract.d.ts +55 -0
- package/dist/manifest/contract.js +8 -0
- package/dist/manifest/index.d.ts +18 -0
- package/dist/manifest/index.js +16 -0
- package/dist/runner/index.d.ts +4 -0
- package/dist/runner/index.js +81 -0
- package/dist/runner/protocol.d.ts +28 -0
- package/dist/runner/protocol.js +6 -0
- package/dist/workerd/index.d.ts +2 -0
- package/dist/workerd/index.js +6 -0
- package/package.json +51 -37
- package/src/authoring/index.ts +340 -0
- package/src/index.ts +9 -0
- package/src/manifest/collect.ts +198 -0
- package/src/manifest/contract.ts +55 -0
- package/src/manifest/index.ts +224 -0
- package/src/runner/index.ts +117 -0
- package/src/runner/protocol.ts +41 -0
- package/src/workerd/index.ts +2 -0
- package/.npmignore +0 -1
- package/bin/apps +0 -36
- package/bin/build +0 -14
- package/bin/doc +0 -16
- package/dist/app.js +0 -9773
- package/dist/app.min.js +0 -5
- package/dist/schema.html +0 -119
- package/dist/schema.md +0 -156
- package/index.html +0 -6
- package/index.js +0 -115
- package/lib/addons.js +0 -67
- package/schema.js +0 -100
- package/templates/app.mustache.html +0 -36
- package/templates/build.mustache.html +0 -13
- package/templates/schema.mustache.html +0 -20
- package/test/addons.js +0 -172
- package/test/cli.js +0 -53
- package/test/fixtures/app.json +0 -32
- package/test/index.js +0 -277
- package/test/mocha.opts +0 -2
package/dist/schema.md
DELETED
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
`app.json` is a manifest format for describing web apps. It declares environment
|
|
2
|
-
variables, addons, and other information required to run apps on Heroku. This document describes the schema in detail.
|
|
3
|
-
|
|
4
|
-
## Example app.json
|
|
5
|
-
|
|
6
|
-
```json
|
|
7
|
-
{
|
|
8
|
-
"name": "small-sharp-tool",
|
|
9
|
-
"description": "This app does one little thing, and does it well.",
|
|
10
|
-
"keywords": [
|
|
11
|
-
"productivity",
|
|
12
|
-
"HTML5",
|
|
13
|
-
"scalpel"
|
|
14
|
-
],
|
|
15
|
-
"website": "https://jane-doe.github.io/small-sharp-tool",
|
|
16
|
-
"repository": "https://github.com/jane-doe/small-sharp-tool",
|
|
17
|
-
"logo": "https://jane-doe.github.io/small-sharp-tool/logo.svg",
|
|
18
|
-
"success_url": "/welcome",
|
|
19
|
-
"scripts": {
|
|
20
|
-
"postdeploy": "bundle exec rake bootstrap"
|
|
21
|
-
},
|
|
22
|
-
"env": {
|
|
23
|
-
"BUILDPACK_URL": "https://github.com/stomita/heroku-buildpack-phantomjs",
|
|
24
|
-
"SECRET_TOKEN": {
|
|
25
|
-
"description": "A secret key for verifying the integrity of signed cookies.",
|
|
26
|
-
"generator": "secret"
|
|
27
|
-
},
|
|
28
|
-
"WEB_CONCURRENCY": {
|
|
29
|
-
"description": "The number of processes to run.",
|
|
30
|
-
"default": "5"
|
|
31
|
-
}
|
|
32
|
-
},
|
|
33
|
-
"addons": [
|
|
34
|
-
"openredis",
|
|
35
|
-
"mongolab:shared-single-small"
|
|
36
|
-
]
|
|
37
|
-
}
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
## The Schema
|
|
41
|
-
|
|
42
|
-
### name
|
|
43
|
-
|
|
44
|
-
A URL-friendly string that uniquely identifies the template app. *required string*
|
|
45
|
-
|
|
46
|
-
```json
|
|
47
|
-
{
|
|
48
|
-
"name": "small-sharp-tool"
|
|
49
|
-
}
|
|
50
|
-
```
|
|
51
|
-
### description
|
|
52
|
-
|
|
53
|
-
A brief summary of the app: what it does, who it's for, why it exists, etc. *optional string*
|
|
54
|
-
|
|
55
|
-
```json
|
|
56
|
-
{
|
|
57
|
-
"description": "This app does one little thing, and does it well."
|
|
58
|
-
}
|
|
59
|
-
```
|
|
60
|
-
### keywords
|
|
61
|
-
|
|
62
|
-
An array of strings describing the app. *optional array*
|
|
63
|
-
|
|
64
|
-
```json
|
|
65
|
-
{
|
|
66
|
-
"keywords": [
|
|
67
|
-
"productivity",
|
|
68
|
-
"HTML5",
|
|
69
|
-
"scalpel"
|
|
70
|
-
]
|
|
71
|
-
}
|
|
72
|
-
```
|
|
73
|
-
### website
|
|
74
|
-
|
|
75
|
-
The project's website, if there is one. *optional string*
|
|
76
|
-
|
|
77
|
-
```json
|
|
78
|
-
{
|
|
79
|
-
"website": "https://jane-doe.github.io/small-sharp-tool"
|
|
80
|
-
}
|
|
81
|
-
```
|
|
82
|
-
### repository
|
|
83
|
-
|
|
84
|
-
The location of the application's source code. Can be a git URL, a GitHub URL, or a tarball URL. *optional string*
|
|
85
|
-
|
|
86
|
-
```json
|
|
87
|
-
{
|
|
88
|
-
"repository": "https://github.com/jane-doe/small-sharp-tool"
|
|
89
|
-
}
|
|
90
|
-
```
|
|
91
|
-
### logo
|
|
92
|
-
|
|
93
|
-
The URL of the application's logo image. It's dimensions should be square. Format can be SVG or PNG. *optional string*
|
|
94
|
-
|
|
95
|
-
```json
|
|
96
|
-
{
|
|
97
|
-
"logo": "https://jane-doe.github.io/small-sharp-tool/logo.svg"
|
|
98
|
-
}
|
|
99
|
-
```
|
|
100
|
-
### success_url
|
|
101
|
-
|
|
102
|
-
A URL specifying where to redirect the user once their new app is deployed. If value is a fully-qualified URL, the user should be redirected to that URL. If value is begins with a slash `/`, the user should be redirected to that path in their newly deployed app. *optional string*
|
|
103
|
-
|
|
104
|
-
```json
|
|
105
|
-
{
|
|
106
|
-
"success_url": "/welcome"
|
|
107
|
-
}
|
|
108
|
-
```
|
|
109
|
-
### scripts
|
|
110
|
-
|
|
111
|
-
A key-value object specifying scripts or shell commands to execute at different stages in the build/release process. *optional object*
|
|
112
|
-
|
|
113
|
-
```json
|
|
114
|
-
{
|
|
115
|
-
"scripts": {
|
|
116
|
-
"postdeploy": "bundle exec rake bootstrap"
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
```
|
|
120
|
-
### env
|
|
121
|
-
|
|
122
|
-
A key-value object for environment variables, or config vars in Heroku parlance. Keys are the names of the environment variables.
|
|
123
|
-
|
|
124
|
-
Values can be strings or objects. If the value is a string, it will be used and the user will not be prompted to specify a different value. If the value is an object, it defines specific requirements for that variable:
|
|
125
|
-
|
|
126
|
-
description - a human-friendly blurb about what the value is for and how to determine what it should be
|
|
127
|
-
value - a default value to use
|
|
128
|
-
generator - a string representing a function to call to generate the value, such as cookie secret *optional object*
|
|
129
|
-
|
|
130
|
-
```json
|
|
131
|
-
{
|
|
132
|
-
"env": {
|
|
133
|
-
"BUILDPACK_URL": "https://github.com/stomita/heroku-buildpack-phantomjs",
|
|
134
|
-
"SECRET_TOKEN": {
|
|
135
|
-
"description": "A secret key for verifying the integrity of signed cookies.",
|
|
136
|
-
"generator": "secret"
|
|
137
|
-
},
|
|
138
|
-
"WEB_CONCURRENCY": {
|
|
139
|
-
"description": "The number of processes to run.",
|
|
140
|
-
"default": "5"
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
```
|
|
145
|
-
### addons
|
|
146
|
-
|
|
147
|
-
An array of strings specifying Heroku addons to provision on the app before deploying. Each addon should be in the format `addon:plan`. If plan is omitted, that addon's default plan will be provisioned. *optional array*
|
|
148
|
-
|
|
149
|
-
```json
|
|
150
|
-
{
|
|
151
|
-
"addons": [
|
|
152
|
-
"openredis",
|
|
153
|
-
"mongolab:shared-single-small"
|
|
154
|
-
]
|
|
155
|
-
}
|
|
156
|
-
```
|
package/index.html
DELETED
package/index.js
DELETED
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
var fs = require("fs")
|
|
3
|
-
var hogan = require("hogan.js")
|
|
4
|
-
var http = require('http')
|
|
5
|
-
var superagent = require('superagent')
|
|
6
|
-
var revalidator = require('revalidator')
|
|
7
|
-
var parseGithubURL = require("github-url-to-object")
|
|
8
|
-
var addons = require("./lib/addons")
|
|
9
|
-
var schema = require("./schema")
|
|
10
|
-
|
|
11
|
-
var App = module.exports = (function() {
|
|
12
|
-
|
|
13
|
-
function App(raw) {
|
|
14
|
-
var key
|
|
15
|
-
|
|
16
|
-
if (typeof(raw) === 'string' && raw.match(/\.json$/i)) {
|
|
17
|
-
raw = JSON.parse(fs.readFileSync(raw))
|
|
18
|
-
} else if (typeof(raw) === 'string') {
|
|
19
|
-
raw = JSON.parse(raw)
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
for (key in raw) {
|
|
23
|
-
if (raw.hasOwnProperty(key)) {
|
|
24
|
-
this[key] = raw[key]
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
this.__defineGetter__("valid", function(){
|
|
29
|
-
return this.validate().valid
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
this.__defineGetter__("errors", function(){
|
|
33
|
-
return this.validate().errors
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
return this
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
App.prototype.validate = function() {
|
|
40
|
-
return revalidator.validate(this, schema)
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
App.prototype.toJSON = function() {
|
|
44
|
-
var key
|
|
45
|
-
var out = {}
|
|
46
|
-
var validProps = Object.keys(schema.properties)
|
|
47
|
-
for (key in this) {
|
|
48
|
-
if (this.hasOwnProperty(key) && validProps.indexOf(key) > -1) {
|
|
49
|
-
out[key] = this[key]
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
return JSON.stringify(out, null, 2)
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
App.prototype.getAddonPrices = function(cb) {
|
|
56
|
-
var _this = this
|
|
57
|
-
App.addons.getPrices(this.addons, function(err, prices){
|
|
58
|
-
if (err) return cb(err)
|
|
59
|
-
_this.prices = prices
|
|
60
|
-
cb(null, prices)
|
|
61
|
-
})
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
App.new = function(raw) {
|
|
65
|
-
return new App(raw)
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
App.fetch = function(url, cb) {
|
|
69
|
-
if (!parseGithubURL(url))
|
|
70
|
-
return cb("Not a valid github URL: " + url)
|
|
71
|
-
|
|
72
|
-
var user = parseGithubURL(url).user
|
|
73
|
-
var repo = parseGithubURL(url).repo
|
|
74
|
-
var proxy_url = "https://github-raw-cors-proxy.herokuapp.com/" + user + "/" + repo + "/blob/master/app.json"
|
|
75
|
-
|
|
76
|
-
superagent.get(proxy_url, function(res){
|
|
77
|
-
cb(null, App.new(res.body))
|
|
78
|
-
})
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// Hogan Templates FTW
|
|
82
|
-
App.templates = {}
|
|
83
|
-
// // fs.readdirSync('./templates').forEach(function(filename){
|
|
84
|
-
// var list = ['app.mustache', 'build.mustache', 'schema.mustache']
|
|
85
|
-
// list.forEach(function(filename){
|
|
86
|
-
// var name = filename.replace(/\.\w+$/, '')
|
|
87
|
-
//
|
|
88
|
-
// // Server vs Browser
|
|
89
|
-
// if (module.parent) {
|
|
90
|
-
// console.log('server')
|
|
91
|
-
// App.templates[name] = hogan.compile(fs.readFileSync('./templates/' + filename).toString())
|
|
92
|
-
// } else {
|
|
93
|
-
// console.log('browser')
|
|
94
|
-
// App.templates[name] = require('.templates/'+filename)
|
|
95
|
-
// }
|
|
96
|
-
// })
|
|
97
|
-
|
|
98
|
-
if (module.parent) {
|
|
99
|
-
App.templates.app = hogan.compile(fs.readFileSync(__dirname + '/templates/app.mustache.html').toString())
|
|
100
|
-
App.templates.build = hogan.compile(fs.readFileSync(__dirname + '/templates/build.mustache.html').toString())
|
|
101
|
-
App.templates.schema = hogan.compile(fs.readFileSync(__dirname + '/templates/schema.mustache.html').toString())
|
|
102
|
-
} else {
|
|
103
|
-
App.templates.app = require('./templates/app.mustache.html')
|
|
104
|
-
App.templates.build = require('./templates/build.mustache.html')
|
|
105
|
-
App.templates.schema = require('./templates/schema.mustache.html')
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
App.example = new App(schema.example)
|
|
109
|
-
|
|
110
|
-
App.addons = addons
|
|
111
|
-
App.schema = schema
|
|
112
|
-
|
|
113
|
-
return App
|
|
114
|
-
|
|
115
|
-
})()
|
package/lib/addons.js
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
var async = require('async')
|
|
3
|
-
var superagent = require('superagent')
|
|
4
|
-
var addons = module.exports = {}
|
|
5
|
-
|
|
6
|
-
addons.getPrices = function(slugs, cb) {
|
|
7
|
-
|
|
8
|
-
// Assemble an empty API response
|
|
9
|
-
if (!slugs || slugs === []) {
|
|
10
|
-
return cb(null, {
|
|
11
|
-
plans: [],
|
|
12
|
-
totalPrice: "Free",
|
|
13
|
-
totalPriceInCents: 0
|
|
14
|
-
})
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
async.map(slugs, addons.getPlan, function(err, plans) {
|
|
18
|
-
if (err) return cb(err)
|
|
19
|
-
var prices = {}
|
|
20
|
-
prices.totalPriceInCents = plans.reduce(function(sum, plan) {
|
|
21
|
-
return plan.price.cents + sum
|
|
22
|
-
}, 0)
|
|
23
|
-
prices.totalPrice = formatPrice(prices.totalPriceInCents)
|
|
24
|
-
prices.plans = plans
|
|
25
|
-
cb(null, prices)
|
|
26
|
-
})
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
addons.getPlan = function(slug, cb) {
|
|
30
|
-
|
|
31
|
-
if (slug.match(/:/)) {
|
|
32
|
-
// format is 'addon:plan'
|
|
33
|
-
var addon = slug.split(":")[0]
|
|
34
|
-
var plan = slug.split(":")[1]
|
|
35
|
-
|
|
36
|
-
superagent
|
|
37
|
-
.get('https://api.heroku.com/addon-services/'+addon+'/plans/'+plan)
|
|
38
|
-
.set('Accept', 'application/vnd.heroku+json; version=3')
|
|
39
|
-
.end(function(err, res){
|
|
40
|
-
if (err) return cb(err)
|
|
41
|
-
if (res.statusCode == 404) return cb(res.body)
|
|
42
|
-
var plan = res.body
|
|
43
|
-
plan.prettyPrice = formatPrice(plan.price.cents)
|
|
44
|
-
plan.logo = "https://addons.heroku.com/addons/" + addon + "/icons/original.png"
|
|
45
|
-
cb(null, plan)
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
} else {
|
|
49
|
-
// plan not specified; find the default plan
|
|
50
|
-
superagent
|
|
51
|
-
.get('https://api.heroku.com/addon-services/'+slug+'/plans')
|
|
52
|
-
.set('Accept', 'application/vnd.heroku+json; version=3')
|
|
53
|
-
.end(function(err, res){
|
|
54
|
-
if (err) return cb(err)
|
|
55
|
-
if (res.statusCode == 404) return cb(res.body)
|
|
56
|
-
var plan = res.body.filter(function(plan) { return plan.default })[0]
|
|
57
|
-
plan.prettyPrice = formatPrice(plan.price.cents)
|
|
58
|
-
plan.logo = "https://addons.heroku.com/addons/" + slug + "/icons/original.png"
|
|
59
|
-
cb(null, plan)
|
|
60
|
-
})
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function formatPrice(price) {
|
|
66
|
-
return (price == 0) ? "Free" : "$" + price/100 + "/mo"
|
|
67
|
-
}
|
package/schema.js
DELETED
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
var schema = {
|
|
3
|
-
"properties": {
|
|
4
|
-
"name": {
|
|
5
|
-
"description": "A URL-friendly string that uniquely identifies the template app.",
|
|
6
|
-
"type": "string",
|
|
7
|
-
"required": true,
|
|
8
|
-
"allowEmpty": false,
|
|
9
|
-
"example": "small-sharp-tool"
|
|
10
|
-
},
|
|
11
|
-
"description": {
|
|
12
|
-
"description": "A brief summary of the app: what it does, who it's for, why it exists, etc.",
|
|
13
|
-
"type": "string",
|
|
14
|
-
"example": "This app does one little thing, and does it well."
|
|
15
|
-
},
|
|
16
|
-
"keywords": {
|
|
17
|
-
"description": "An array of strings describing the app.",
|
|
18
|
-
"type": "array",
|
|
19
|
-
"example": ["productivity", "HTML5", "scalpel"]
|
|
20
|
-
},
|
|
21
|
-
"website": {
|
|
22
|
-
"description": "The project's website, if there is one.",
|
|
23
|
-
"type": "string",
|
|
24
|
-
"format": "url",
|
|
25
|
-
"allowEmpty": false,
|
|
26
|
-
"example": "https://jane-doe.github.io/small-sharp-tool"
|
|
27
|
-
},
|
|
28
|
-
"repository": {
|
|
29
|
-
"description": "The location of the application's source code. Can be a git URL, a GitHub URL, or a tarball URL.",
|
|
30
|
-
"type": "string",
|
|
31
|
-
"format": "url",
|
|
32
|
-
"allowEmpty": false,
|
|
33
|
-
"example": "https://github.com/jane-doe/small-sharp-tool"
|
|
34
|
-
},
|
|
35
|
-
"logo": {
|
|
36
|
-
"description": "The URL of the application's logo image. It's dimensions should be square. Format can be SVG or PNG.",
|
|
37
|
-
"type": "string",
|
|
38
|
-
"format": "url",
|
|
39
|
-
"allowEmpty": false,
|
|
40
|
-
"example": "https://jane-doe.github.io/small-sharp-tool/logo.svg"
|
|
41
|
-
},
|
|
42
|
-
"success_url": {
|
|
43
|
-
"description": "A URL specifying where to redirect the user once their new app is deployed. If value is a fully-qualified URL, the user should be redirected to that URL. If value is begins with a slash `/`, the user should be redirected to that path in their newly deployed app.",
|
|
44
|
-
"type": "string",
|
|
45
|
-
"allowEmpty": false,
|
|
46
|
-
"example": "/welcome"
|
|
47
|
-
},
|
|
48
|
-
"scripts": {
|
|
49
|
-
"description": "A key-value object specifying scripts or shell commands to execute at different stages in the build/release process.",
|
|
50
|
-
"type": "object",
|
|
51
|
-
"example": {"postdeploy": "bundle exec rake bootstrap"}
|
|
52
|
-
},
|
|
53
|
-
"env": {
|
|
54
|
-
"description": "A key-value object for environment variables, or config vars in Heroku parlance. Keys are the names of the environment variables.\n\nValues can be strings or objects. If the value is a string, it will be used and the user will not be prompted to specify a different value. If the value is an object, it defines specific requirements for that variable:\n\ndescription - a human-friendly blurb about what the value is for and how to determine what it should be\nvalue - a default value to use\ngenerator - a string representing a function to call to generate the value, such as cookie secret",
|
|
55
|
-
"type": "object",
|
|
56
|
-
"example": {
|
|
57
|
-
"BUILDPACK_URL": "https://github.com/stomita/heroku-buildpack-phantomjs",
|
|
58
|
-
"SECRET_TOKEN": {
|
|
59
|
-
"description": "A secret key for verifying the integrity of signed cookies.",
|
|
60
|
-
"generator": "secret"
|
|
61
|
-
},
|
|
62
|
-
"WEB_CONCURRENCY": {
|
|
63
|
-
"description": "The number of processes to run.",
|
|
64
|
-
"default": "5"
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
},
|
|
68
|
-
"addons": {
|
|
69
|
-
"description": "An array of strings specifying Heroku addons to provision on the app before deploying. Each addon should be in the format `addon:plan`. If plan is omitted, that addon's default plan will be provisioned.",
|
|
70
|
-
"type": "array",
|
|
71
|
-
"example": [
|
|
72
|
-
"openredis",
|
|
73
|
-
"mongolab:shared-single-small"
|
|
74
|
-
]
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// Assemble an example schema
|
|
80
|
-
schema.example = {}
|
|
81
|
-
Object.keys(schema.properties).map(function(key){
|
|
82
|
-
schema.example[key] = schema.properties[key].example
|
|
83
|
-
})
|
|
84
|
-
|
|
85
|
-
// Assemble a template-ready stringified version of the schema
|
|
86
|
-
schema.exampleJSON = JSON.stringify(schema.example, null, 2)
|
|
87
|
-
|
|
88
|
-
// Coerce schema properties into a template-friendly format
|
|
89
|
-
schema.propertiesArray = Object.keys(schema.properties).map(function(name) {
|
|
90
|
-
var prop = schema.properties[name]
|
|
91
|
-
prop.name = name
|
|
92
|
-
prop.requiredOrOptional = prop.required ? "required" : "optional"
|
|
93
|
-
|
|
94
|
-
var jsonDoc = {}
|
|
95
|
-
jsonDoc[prop.name] = prop.example
|
|
96
|
-
prop.exampleJSON = JSON.stringify(jsonDoc, null, 2)
|
|
97
|
-
return prop
|
|
98
|
-
})
|
|
99
|
-
|
|
100
|
-
module.exports = schema
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
<li class="app">
|
|
2
|
-
|
|
3
|
-
<a class="logo activator">
|
|
4
|
-
<img src="{{logo}}">
|
|
5
|
-
</a>
|
|
6
|
-
|
|
7
|
-
<div class="meta">
|
|
8
|
-
|
|
9
|
-
<h2><a class="activator">{{name}}</a></h2>
|
|
10
|
-
|
|
11
|
-
<div class="drawer">
|
|
12
|
-
|
|
13
|
-
{{#description}}
|
|
14
|
-
<p>{{description}}</p>
|
|
15
|
-
{{/description}}
|
|
16
|
-
|
|
17
|
-
{{#repository}}
|
|
18
|
-
<a href="{{repository}}" class="repository">{{repository}}</a>
|
|
19
|
-
{{/repository}}
|
|
20
|
-
|
|
21
|
-
{{#website}}
|
|
22
|
-
<a href="{{website}}" class="website">{{website}}</a>
|
|
23
|
-
{{/website}}
|
|
24
|
-
|
|
25
|
-
<form class="deploy">
|
|
26
|
-
<input type="hidden" name="source" value="{{repository}}">
|
|
27
|
-
<input type="submit" value="Deploy for {{prices.totalPrice}}">
|
|
28
|
-
</form>
|
|
29
|
-
|
|
30
|
-
<div class="output"></div>
|
|
31
|
-
|
|
32
|
-
</div>
|
|
33
|
-
|
|
34
|
-
</div>
|
|
35
|
-
|
|
36
|
-
</li>
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
`app.json` is a manifest format for describing web apps. It declares environment
|
|
2
|
-
variables, addons, and other information required to run apps on Heroku. This document describes the schema in detail.
|
|
3
|
-
|
|
4
|
-
## Example app.json
|
|
5
|
-
|
|
6
|
-
```json
|
|
7
|
-
{{{exampleJSON}}}
|
|
8
|
-
```
|
|
9
|
-
|
|
10
|
-
## The Schema
|
|
11
|
-
|
|
12
|
-
{{#propertiesArray}}
|
|
13
|
-
### {{name}}
|
|
14
|
-
|
|
15
|
-
{{description}} *{{requiredOrOptional}} {{type}}*
|
|
16
|
-
|
|
17
|
-
```json
|
|
18
|
-
{{{exampleJSON}}}
|
|
19
|
-
```
|
|
20
|
-
{{/propertiesArray}}
|
package/test/addons.js
DELETED
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
var assert = require("assert")
|
|
2
|
-
var util = require("util")
|
|
3
|
-
var addons = require("../lib/addons")
|
|
4
|
-
|
|
5
|
-
describe('addons.getPlan()', function(){
|
|
6
|
-
|
|
7
|
-
it('accepts an addon:plan string', function(done){
|
|
8
|
-
addons.getPlan('mongohq:ssd_1g_elastic', function(err, plan) {
|
|
9
|
-
assert.equal(plan.name, 'mongohq:ssd_1g_elastic')
|
|
10
|
-
assert.equal(plan.description, 'MongoHQ 1 GB SSD')
|
|
11
|
-
done()
|
|
12
|
-
})
|
|
13
|
-
})
|
|
14
|
-
|
|
15
|
-
it('accepts an addon string with no plan', function(done){
|
|
16
|
-
addons.getPlan('mongohq', function(err, plan) {
|
|
17
|
-
assert.equal(plan.name, 'mongohq:sandbox')
|
|
18
|
-
done()
|
|
19
|
-
})
|
|
20
|
-
})
|
|
21
|
-
|
|
22
|
-
it('returns a pretty price', function(done){
|
|
23
|
-
addons.getPlan('mongohq', function(err, plan) {
|
|
24
|
-
assert.equal(plan.prettyPrice, 'Free')
|
|
25
|
-
done()
|
|
26
|
-
})
|
|
27
|
-
})
|
|
28
|
-
|
|
29
|
-
it('returns a logo URL when given an addon:plan slug', function(done){
|
|
30
|
-
addons.getPlan('mongohq:sandbox', function(err, plan) {
|
|
31
|
-
assert.equal(plan.logo, 'https://addons.heroku.com/addons/mongohq/icons/original.png')
|
|
32
|
-
done()
|
|
33
|
-
})
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
it('returns a logo URL given a plan-free slug', function(done){
|
|
37
|
-
addons.getPlan('mongohq', function(err, plan) {
|
|
38
|
-
assert.equal(plan.logo, 'https://addons.heroku.com/addons/mongohq/icons/original.png')
|
|
39
|
-
done()
|
|
40
|
-
})
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
describe('addons.getPrices()', function(){
|
|
46
|
-
|
|
47
|
-
it('accepts an array and returns a prcies object', function(done){
|
|
48
|
-
addons.getPrices(['mongohq:ssd_1g_elastic'], function(err, prices) {
|
|
49
|
-
assert(typeof(prices) === "object")
|
|
50
|
-
assert(!util.isArray(prices))
|
|
51
|
-
assert(util.isArray(prices.plans))
|
|
52
|
-
done()
|
|
53
|
-
})
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
it('returns an array of plans in the prices object', function(done){
|
|
57
|
-
addons.getPrices(['mongohq:ssd_1g_elastic'], function(err, prices) {
|
|
58
|
-
assert(util.isArray(prices.plans))
|
|
59
|
-
done()
|
|
60
|
-
})
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
it('handles addon:plan formatted slugs', function(done){
|
|
64
|
-
addons.getPrices(['mongohq:ssd_1g_elastic'], function(err, prices) {
|
|
65
|
-
assert.equal(prices.plans[0].name, 'mongohq:ssd_1g_elastic')
|
|
66
|
-
assert.equal(prices.plans[0].price.cents, 1800)
|
|
67
|
-
assert.equal(prices.plans[0].price.unit, 'month')
|
|
68
|
-
done()
|
|
69
|
-
})
|
|
70
|
-
})
|
|
71
|
-
|
|
72
|
-
it('finds the default plan, if unspecified', function(done){
|
|
73
|
-
addons.getPrices(['mongohq'], function(err, prices) {
|
|
74
|
-
assert.equal(prices.plans[0].name, 'mongohq:sandbox')
|
|
75
|
-
assert.equal(prices.plans[0].price.cents, 0)
|
|
76
|
-
assert.equal(prices.plans[0].price.unit, 'month')
|
|
77
|
-
done()
|
|
78
|
-
})
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
it('returns a totalPrice in the prices object', function(done){
|
|
82
|
-
addons.getPrices(['mongohq:ssd_1g_elastic', 'memcachedcloud:100'], function(err, prices) {
|
|
83
|
-
assert.equal(prices.plans[0].name, 'mongohq:ssd_1g_elastic')
|
|
84
|
-
assert.equal(prices.plans[0].price.cents, 1800)
|
|
85
|
-
assert.equal(prices.plans[1].price.cents, 1400)
|
|
86
|
-
assert.equal(prices.totalPriceInCents, 3200)
|
|
87
|
-
done()
|
|
88
|
-
})
|
|
89
|
-
})
|
|
90
|
-
|
|
91
|
-
it('returns a human-friendly dollar amount total', function(done){
|
|
92
|
-
addons.getPrices(['mongohq:ssd_1g_elastic', 'memcachedcloud:100'], function(err, prices) {
|
|
93
|
-
assert.equal(prices.totalPriceInCents, 3200)
|
|
94
|
-
assert.equal(prices.totalPrice, '$32/mo')
|
|
95
|
-
done()
|
|
96
|
-
})
|
|
97
|
-
})
|
|
98
|
-
|
|
99
|
-
it('returns free if total is zero', function(done){
|
|
100
|
-
addons.getPrices(['heroku-postgresql'], function(err, prices) {
|
|
101
|
-
assert.equal(prices.totalPriceInCents, 0)
|
|
102
|
-
assert.equal(prices.totalPrice, 'Free')
|
|
103
|
-
done()
|
|
104
|
-
})
|
|
105
|
-
})
|
|
106
|
-
|
|
107
|
-
it('accepts an emtpy array', function(done){
|
|
108
|
-
addons.getPrices([], function(err, prices) {
|
|
109
|
-
assert(util.isArray(prices.plans))
|
|
110
|
-
assert.equal(prices.totalPriceInCents, 0)
|
|
111
|
-
assert.equal(prices.totalPrice, 'Free')
|
|
112
|
-
done()
|
|
113
|
-
})
|
|
114
|
-
})
|
|
115
|
-
|
|
116
|
-
it('propagates errors for nonexistent addons', function(done){
|
|
117
|
-
addons.getPrices(['nonexistent-addon'], function(err, prices) {
|
|
118
|
-
assert(err)
|
|
119
|
-
done()
|
|
120
|
-
})
|
|
121
|
-
})
|
|
122
|
-
|
|
123
|
-
it('propagates errors for nonexistent plans',function(done){
|
|
124
|
-
addons.getPrices(['mongohq:bad-plan'], function(err, res) {
|
|
125
|
-
assert(err)
|
|
126
|
-
assert.equal(err.id, "not_found")
|
|
127
|
-
done()
|
|
128
|
-
})
|
|
129
|
-
})
|
|
130
|
-
|
|
131
|
-
it('handles a long list of addons',function(done){
|
|
132
|
-
var slugs = [
|
|
133
|
-
'mongohq:sandbox',
|
|
134
|
-
'redistogo',
|
|
135
|
-
'goinstant',
|
|
136
|
-
'rollbar',
|
|
137
|
-
'usersnap',
|
|
138
|
-
'bonsai:staging'
|
|
139
|
-
]
|
|
140
|
-
|
|
141
|
-
addons.getPrices(slugs, function(err, prices) {
|
|
142
|
-
assert(!err)
|
|
143
|
-
assert(prices.plans)
|
|
144
|
-
assert(prices.totalPrice)
|
|
145
|
-
assert.equal(typeof(prices.totalPriceInCents), 'number')
|
|
146
|
-
done()
|
|
147
|
-
})
|
|
148
|
-
})
|
|
149
|
-
|
|
150
|
-
it("returns a mocked response for a null slugs array", function(done) {
|
|
151
|
-
addons.getPrices(null, function(err, prices) {
|
|
152
|
-
assert(prices)
|
|
153
|
-
assert.equal(prices.totalPrice, "Free")
|
|
154
|
-
assert.equal(prices.totalPriceInCents, 0)
|
|
155
|
-
assert(util.isArray(prices.plans))
|
|
156
|
-
assert.equal(prices.plans.length, 0)
|
|
157
|
-
done()
|
|
158
|
-
})
|
|
159
|
-
})
|
|
160
|
-
|
|
161
|
-
it("returns a mocked response for an empty slugs array", function(done) {
|
|
162
|
-
addons.getPrices([], function(err, prices) {
|
|
163
|
-
assert(prices)
|
|
164
|
-
assert.equal(prices.totalPrice, "Free")
|
|
165
|
-
assert.equal(prices.totalPriceInCents, 0)
|
|
166
|
-
assert(util.isArray(prices.plans))
|
|
167
|
-
assert.equal(prices.plans.length, 0)
|
|
168
|
-
done()
|
|
169
|
-
})
|
|
170
|
-
})
|
|
171
|
-
|
|
172
|
-
})
|