1 |
|
2 |
|
3 |
|
4 |
|
5 | async = require 'async'
|
6 | pkgcloud = require 'pkgcloud'
|
7 | fs = require 'fs'
|
8 | pathutil = require 'path'
|
9 | {BufferStream, extend} = require('./util')
|
10 | {EventEmitter} = require 'events'
|
11 |
|
12 |
|
13 | class exports.Rack extends EventEmitter
|
14 | constructor: (assets, options) ->
|
15 | super()
|
16 |
|
17 |
|
18 | options ?= {}
|
19 |
|
20 |
|
21 | @maxAge = options.maxAge
|
22 |
|
23 |
|
24 | @allowNoHashCache = options.allowNoHashCache
|
25 |
|
26 |
|
27 | @on 'complete', =>
|
28 | @completed = true
|
29 |
|
30 |
|
31 |
|
32 | @on 'newListener', (event, listener) =>
|
33 | if event is 'complete' and @completed is true
|
34 | listener()
|
35 |
|
36 |
|
37 | @on 'error', (error) =>
|
38 | throw error if @listeners('error').length is 1
|
39 |
|
40 |
|
41 | for asset in assets
|
42 | asset.rack = this
|
43 |
|
44 |
|
45 | @assets = []
|
46 |
|
47 |
|
48 | async.forEachSeries assets, (asset, next) =>
|
49 |
|
50 |
|
51 | asset.on 'error', (error) =>
|
52 | next error
|
53 |
|
54 |
|
55 | asset.on 'complete', =>
|
56 |
|
57 |
|
58 | return if @completed
|
59 |
|
60 |
|
61 | if asset.contents?
|
62 | @assets.push asset
|
63 |
|
64 |
|
65 | if asset.assets?
|
66 | @assets = @assets.concat asset.assets
|
67 | next()
|
68 |
|
69 |
|
70 | asset.emit 'start'
|
71 |
|
72 |
|
73 | , (error) =>
|
74 | return @emit 'error', error if error?
|
75 | @emit 'complete'
|
76 |
|
77 |
|
78 | handle: (request, response, next) ->
|
79 | response.locals assets: this
|
80 | handle = =>
|
81 | for asset in @assets
|
82 | check = asset.checkUrl request.path
|
83 | return asset.respond request, response if check
|
84 | next()
|
85 | if @completed
|
86 | handle()
|
87 | else @on 'complete', handle
|
88 |
|
89 |
|
90 | writeConfigFile: (filename) ->
|
91 | config = {}
|
92 | for asset in @assets
|
93 | config[asset.url] = asset.specificUrl
|
94 | fs.writeFileSync filename, JSON.stringify(config)
|
95 |
|
96 |
|
97 | deploy: (options, next) ->
|
98 | options.keyId = options.accessKey
|
99 | options.key = options.secretKey
|
100 | deploy = =>
|
101 | client = pkgcloud.storage.createClient options
|
102 | assets = @assets
|
103 |
|
104 |
|
105 |
|
106 | assets = @assets.concat @assets[0] if options.provider is 'rackspace'
|
107 | async.forEachSeries assets, (asset, next) =>
|
108 | stream = null
|
109 | headers = {}
|
110 | console.log asset.url
|
111 | if asset.gzip
|
112 | stream = new BufferStream asset.gzipContents
|
113 | headers['content-encoding'] = 'gzip'
|
114 | else
|
115 | stream = new BufferStream asset.contents
|
116 | url = asset.specificUrl.slice 1, asset.specificUrl.length
|
117 | for key, value of asset.headers
|
118 | headers[key] = value
|
119 | headers['x-amz-acl'] = 'public-read' if options.provider is 'amazon'
|
120 | clientOptions =
|
121 | container: options.container
|
122 | remote: url
|
123 | headers: headers
|
124 | stream: stream
|
125 | client.upload clientOptions, (error) ->
|
126 | return next error if error?
|
127 | next()
|
128 | , (error) =>
|
129 | if error?
|
130 | return next error if next?
|
131 | throw error
|
132 | if options.configFile?
|
133 | @writeConfigFile options.configFile
|
134 | next() if next?
|
135 | if @completed
|
136 | deploy()
|
137 | else @on 'complete', deploy
|
138 |
|
139 |
|
140 | tag: (url) ->
|
141 | for asset in @assets
|
142 | return asset.tag() if asset.url is url
|
143 | throw new Error "No asset found for url: #{url}"
|
144 |
|
145 |
|
146 | url: (url) ->
|
147 | for asset in @assets
|
148 | return asset.specificUrl if url is asset.url
|
149 |
|
150 |
|
151 | @extend: extend
|
152 |
|
153 |
|
154 |
|
155 | class ConfigRack
|
156 | constructor: (options) ->
|
157 |
|
158 | throw new Error('options.configFile is required') unless options.configFile?
|
159 | throw new Error('options.hostname is required') unless options.hostname?
|
160 |
|
161 |
|
162 | @assetMap = require options.configFile
|
163 | @hostname = options.hostname
|
164 |
|
165 |
|
166 | handle: (request, response, next) ->
|
167 | response.locals assets: this
|
168 | for url, specificUrl of @assetMap
|
169 | if request.path is url or request.path is specificUrl
|
170 |
|
171 |
|
172 | return response.redirect "//#{@hostname}#{specificUrl}"
|
173 | next()
|
174 |
|
175 |
|
176 | tag: (url) ->
|
177 | switch pathutil.extname(url)
|
178 | when '.js'
|
179 | tag = "\n<script type=\"text/javascript\" "
|
180 | return tag += "src=\"//#{@hostname}#{@assetMap[url]}\"></script>"
|
181 | when '.css'
|
182 | return "\n<link rel=\"stylesheet\" href=\"//#{@hostname}#{@assetMap[url]}\">"
|
183 |
|
184 |
|
185 | url: (url) ->
|
186 | return "//#{@hostname}#{@assetMap[url]}"
|
187 |
|
188 |
|
189 |
|
190 | exports.fromConfigFile = (options) ->
|
191 | return new ConfigRack(options)
|