Compare commits
343 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4fb5acad48 | ||
|
|
2e396e4d65 | ||
|
|
55ddeb7aaa | ||
|
|
907e1a8c43 | ||
|
|
8628c34b25 | ||
|
|
9938a6bf4b | ||
|
|
f308bfb4bf | ||
|
|
c0793c575d | ||
|
|
3c00c96b6a | ||
|
|
a1010be87a | ||
|
|
4b20afbd15 | ||
|
|
dc9859cd83 | ||
|
|
08f218724b | ||
|
|
daeb1eb2a3 | ||
|
|
326c7355ed | ||
|
|
d38606f409 | ||
|
|
29c45e3ebc | ||
|
|
20f748be12 | ||
|
|
1cb1e91ca1 | ||
|
|
7fb119c9f3 | ||
|
|
eda8dfe9ca | ||
|
|
4b3939ef4e | ||
|
|
cb0c79e79f | ||
|
|
e36638973f | ||
|
|
aef982d6e6 | ||
|
|
4a588f6512 | ||
|
|
b34cfb1fe0 | ||
|
|
4f028a107b | ||
|
|
91ee928f06 | ||
|
|
862b1eeed4 | ||
|
|
0786ec6108 | ||
|
|
2dca3114e9 | ||
|
|
3bf26bd89e | ||
|
|
ef074f8d6c | ||
|
|
4b46bff72f | ||
|
|
765d3b47a7 | ||
|
|
20c97527fb | ||
|
|
35b3efb058 | ||
|
|
8d1781489b | ||
|
|
92a33acf45 | ||
|
|
48a35b6a01 | ||
|
|
8db7c4355e | ||
|
|
d0a1013131 | ||
|
|
936c0b607b | ||
|
|
1123a440bc | ||
|
|
8f0039de92 | ||
|
|
f8b0ae6c6f | ||
|
|
188ab10871 | ||
|
|
a016077bf3 | ||
|
|
834c155da4 | ||
|
|
19b1d76e98 | ||
|
|
c43846f14e | ||
|
|
fd8d481be4 | ||
|
|
8576201cc1 | ||
|
|
0837025a74 | ||
|
|
5468c4e767 | ||
|
|
34c12de81c | ||
|
|
a5cd498f92 | ||
|
|
dbb588f031 | ||
|
|
720b98b715 | ||
|
|
efd921d876 | ||
|
|
1e28c2d949 | ||
|
|
a0c861ec5a | ||
|
|
8d841ea8e3 | ||
|
|
504430dcdd | ||
|
|
435ff1beeb | ||
|
|
6e247829a3 | ||
|
|
99795b747c | ||
|
|
3a325a1e04 | ||
|
|
734565dc05 | ||
|
|
697d5f0892 | ||
|
|
409d25739e | ||
|
|
65a792ba53 | ||
|
|
4239107ed2 | ||
|
|
f512925fd4 | ||
|
|
b740dd11cf | ||
|
|
430a479113 | ||
|
|
86394b25ee | ||
|
|
8086c6f0bf | ||
|
|
7742e11cde | ||
|
|
2597fcae80 | ||
|
|
5dd720cc21 | ||
|
|
fdbde9eb87 | ||
|
|
7e5227e4a1 | ||
|
|
60ae815c09 | ||
|
|
adecd99866 | ||
|
|
932fa34edc | ||
|
|
75fc0ae472 | ||
|
|
10433438d0 | ||
|
|
c84bc05d10 | ||
|
|
33412c4613 | ||
|
|
4bc965b45b | ||
|
|
6704abf988 | ||
|
|
d9f84677f8 | ||
|
|
e1880ce19e | ||
|
|
478d75fec4 | ||
|
|
77700e72b1 | ||
|
|
5d4068d539 | ||
|
|
57d6fe7150 | ||
|
|
a8dc1b2f7b | ||
|
|
060dcb49b4 | ||
|
|
7981d55860 | ||
|
|
8aa6754875 | ||
|
|
4660dd3b7f | ||
|
|
58046a2ec7 | ||
|
|
008423d49b | ||
|
|
3eb09b8b07 | ||
|
|
888cfe3f1e | ||
|
|
4545076b0f | ||
|
|
854fd796ae | ||
|
|
77af6d4c17 | ||
|
|
21704c9f84 | ||
|
|
4f913f2468 | ||
|
|
859f1bb959 | ||
|
|
8d8963d37a | ||
|
|
c2d3fe9929 | ||
|
|
3809d6eb32 | ||
|
|
490340fbb0 | ||
|
|
a528811e35 | ||
|
|
9275b2cc85 | ||
|
|
48b7031074 | ||
|
|
41d4997ea7 | ||
|
|
ff8a5e99ec | ||
|
|
9a14a6e022 | ||
|
|
404acfae49 | ||
|
|
1e438d7dae | ||
|
|
e929e089d0 | ||
|
|
81486f412f | ||
|
|
5cc0187b67 | ||
|
|
d9accbb6a7 | ||
|
|
17d8ab7dcd | ||
|
|
3f746eb7c8 | ||
|
|
9816059485 | ||
|
|
fe058b716b | ||
|
|
dc7d7dfd53 | ||
|
|
3e514c2c3b | ||
|
|
703adeaffc | ||
|
|
c4e5cb71b4 | ||
|
|
73572b0839 | ||
|
|
88f3944f5a | ||
|
|
20524118ef | ||
|
|
68bf469ad4 | ||
|
|
21f7f03cf7 | ||
|
|
96126e5a2f | ||
|
|
490b04f298 | ||
|
|
abacb64479 | ||
|
|
2a299c9201 | ||
|
|
39083b819f | ||
|
|
228f1512d7 | ||
|
|
f5b9e0e0d5 | ||
|
|
daaee2b971 | ||
|
|
a38cf312b2 | ||
|
|
ab44907dac | ||
|
|
1246f12f5a | ||
|
|
99b8c31d3a | ||
|
|
dbf3301ff9 | ||
|
|
a6fe4a1516 | ||
|
|
277f1023c9 | ||
|
|
820ae3f27c | ||
|
|
d52cfe8bac | ||
|
|
f74d7cbd3e | ||
|
|
7e9b5944fd | ||
|
|
6fb0be9cc0 | ||
|
|
1ddc1c68dc | ||
|
|
8b3dc9072f | ||
|
|
112e551240 | ||
|
|
7b4bac1874 | ||
|
|
baa7f78e83 | ||
|
|
45e4208218 | ||
|
|
1e48eacbf8 | ||
|
|
9e062994fe | ||
|
|
4f155f5004 | ||
|
|
c0ee2db8a4 | ||
|
|
5464b449ce | ||
|
|
f2e433d52b | ||
|
|
11fdc9340e | ||
|
|
1e71e2faa0 | ||
|
|
2806de2a5d | ||
|
|
27c032d53f | ||
|
|
c47f73f799 | ||
|
|
2901e619cb | ||
|
|
5a98db9589 | ||
|
|
34b631fa12 | ||
|
|
bb129c1151 | ||
|
|
37afd429ec | ||
|
|
edee08a778 | ||
|
|
0395d958cf | ||
|
|
9e51798295 | ||
|
|
cb6e2cd7f6 | ||
|
|
a79fa68deb | ||
|
|
fabf3eff4c | ||
|
|
6a60650073 | ||
|
|
6eada0f22e | ||
|
|
5241ddfe84 | ||
|
|
bea03619d7 | ||
|
|
8f03805697 | ||
|
|
d0c2ac0f9d | ||
|
|
d820a030a1 | ||
|
|
b4c0b89365 | ||
|
|
752d0f62f0 | ||
|
|
9404e811a5 | ||
|
|
2102e71c10 | ||
|
|
f023826a59 | ||
|
|
f03512d32b | ||
|
|
7f56593ee3 | ||
|
|
2995858bb0 | ||
|
|
a8cdfcd7cd | ||
|
|
4470cb7d55 | ||
|
|
4b06026d2e | ||
|
|
7967d5857d | ||
|
|
4ea84e8b74 | ||
|
|
bf63b9f738 | ||
|
|
8af77df0e0 | ||
|
|
df49fb84c1 | ||
|
|
d657e180d5 | ||
|
|
0afd9d1bb3 | ||
|
|
f839837f34 | ||
|
|
a030e1110d | ||
|
|
dabf5e1c9a | ||
|
|
bb9612441c | ||
|
|
48825a2e46 | ||
|
|
6a2a463b76 | ||
|
|
f1ec18dc4b | ||
|
|
6b9001ef6c | ||
|
|
9ff1203688 | ||
|
|
a891354a32 | ||
|
|
9c4c93695b | ||
|
|
3b510437a8 | ||
|
|
ed1ec0c27a | ||
|
|
526f61b136 | ||
|
|
568058d7d9 | ||
|
|
b77da91094 | ||
|
|
e2457e9e5c | ||
|
|
c6714cd591 | ||
|
|
58e98c7263 | ||
|
|
234deb4e7e | ||
|
|
4466cf6be1 | ||
|
|
cf5dddb174 | ||
|
|
70506d54ee | ||
|
|
09781df60a | ||
|
|
c730670711 | ||
|
|
691d62f403 | ||
|
|
a5d0094669 | ||
|
|
b65cf7afe3 | ||
|
|
e2e2c39148 | ||
|
|
de4dbd234a | ||
|
|
440f42415c | ||
|
|
8840ac4c1f | ||
|
|
9f662a69e1 | ||
|
|
c098eb4661 | ||
|
|
2c28f3549e | ||
|
|
616ec7626a | ||
|
|
9a915511b3 | ||
|
|
0d37933259 | ||
|
|
2add883370 | ||
|
|
e522347667 | ||
|
|
8987ee0b2a | ||
|
|
ceb9b5be1d | ||
|
|
6c258758c8 | ||
|
|
dadbf3a18b | ||
|
|
c18ac8015a | ||
|
|
68d3cc7295 | ||
|
|
8ebe73d1f1 | ||
|
|
75ea41a55b | ||
|
|
d519e8e763 | ||
|
|
bb6b31e7c0 | ||
|
|
5d19b66290 | ||
|
|
5422de642e | ||
|
|
02f8143097 | ||
|
|
3c7ae2f37d | ||
|
|
6075034521 | ||
|
|
d1a1a8f4cd | ||
|
|
54535893ab | ||
|
|
8c05cfe6a2 | ||
|
|
6c838c7947 | ||
|
|
f3f4fdc4ac | ||
|
|
985d705ad2 | ||
|
|
6234f8cba9 | ||
|
|
c0e34890e4 | ||
|
|
b42687d230 | ||
|
|
4ce0142651 | ||
|
|
b44255de3c | ||
|
|
b0fa7ee2d1 | ||
|
|
1c17291654 | ||
|
|
3b2bd161b7 | ||
|
|
d29e22ed4b | ||
|
|
1a0f577689 | ||
|
|
a3a59aa8f3 | ||
|
|
3a31064ded | ||
|
|
151136381e | ||
|
|
503b60a45b | ||
|
|
225c2bb65f | ||
|
|
8ea985ff6b | ||
|
|
0cec69ee1d | ||
|
|
d5d2a8bdcc | ||
|
|
693aa576b4 | ||
|
|
3abce5e2b6 | ||
|
|
5c6d6301ef | ||
|
|
f43ec71ccb | ||
|
|
ef61515f28 | ||
|
|
9094d0742f | ||
|
|
ede09b5c77 | ||
|
|
19e4d78197 | ||
|
|
380ec1e917 | ||
|
|
ee16b06b3a | ||
|
|
8149f883b0 | ||
|
|
c75249b5b0 | ||
|
|
942a7747ac | ||
|
|
96b336acc3 | ||
|
|
520a3b0220 | ||
|
|
230750ff88 | ||
|
|
84a6e36d84 | ||
|
|
203f0300e3 | ||
|
|
1cb12fec35 | ||
|
|
e5195ecfb5 | ||
|
|
7ee2361700 | ||
|
|
47a92ff273 | ||
|
|
fc99692219 | ||
|
|
bd7dde229a | ||
|
|
21a5c788e1 | ||
|
|
0b286d1fe3 | ||
|
|
987f540cde | ||
|
|
a11da1545a | ||
|
|
d149252a3b | ||
|
|
ed3386f044 | ||
|
|
36fa1ba655 | ||
|
|
77182a755d | ||
|
|
f05a4784fd | ||
|
|
7f7c056ecd | ||
|
|
bbb402f762 | ||
|
|
4d70d8065e | ||
|
|
3617a91f87 | ||
|
|
03445c4c49 | ||
|
|
2f45649a2c | ||
|
|
08d1011433 | ||
|
|
6e71913c46 | ||
|
|
cc7165dd3c | ||
|
|
a257fc3962 | ||
|
|
6a295ac196 | ||
|
|
ff52b00a7c | ||
|
|
9ee8d484db | ||
|
|
7b0ab14d4c | ||
|
|
1d109f8071 |
21
.editorconfig
Normal file
21
.editorconfig
Normal file
@@ -0,0 +1,21 @@
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
# Unix-style newlines with a newline ending every file
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
|
||||
# Tab indentation (no size specified)
|
||||
[*.js]
|
||||
indent_style = tab
|
||||
|
||||
# 2 space indentation
|
||||
[{.,}*.y{a,}ml]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
# Indentation override for Gruntfile
|
||||
[Gruntfile.js]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
275
.eslint.yaml
Normal file
275
.eslint.yaml
Normal file
@@ -0,0 +1,275 @@
|
||||
env:
|
||||
node: true
|
||||
|
||||
#
|
||||
# 0 - disable
|
||||
# Rules that more harmful than useful, or just buggy.
|
||||
#
|
||||
# 1 - warning
|
||||
# Rules that we didn't encounter yet. You can safely ignore them,
|
||||
# but I'd like to know any interesting use-cases they forbid.
|
||||
#
|
||||
# 2 - error
|
||||
# Rules that have proven to be useful, please follow them.
|
||||
#
|
||||
|
||||
rules:
|
||||
# didn't understand what it does, but it fails a good code
|
||||
block-scoped-var: 0
|
||||
|
||||
# fails where newlines are used to format pretty big "if":
|
||||
# if (
|
||||
# name.charAt(0) === "." ||
|
||||
# name.match(/[\/@\s\+%:]/) ||
|
||||
# name !== encodeURIComponent(name) ||
|
||||
# name.toLowerCase() === "node_modules"
|
||||
# ) {
|
||||
brace-style: 1
|
||||
|
||||
# snake_case is more readable, what's up with you guys?
|
||||
camelcase: 0
|
||||
|
||||
# if some functions are complex, they are for a good reason,
|
||||
# ain't worth it
|
||||
complexity: [0, 10]
|
||||
|
||||
# never saw it, but self is preferred
|
||||
consistent-this: [1, self]
|
||||
|
||||
# fails good code
|
||||
curly: [0, multi]
|
||||
|
||||
# fails good code, where this notation is used for consistency:
|
||||
# something['foo-bar'] = 123
|
||||
# something['blahblah'] = 234
|
||||
dot-notation: 0
|
||||
|
||||
# pointless in many cases (like indexOf() == -1), harmful in a few
|
||||
# cases (when you do want to ignore types), fails good code
|
||||
eqeqeq: 0
|
||||
|
||||
# if someone is changing prototype and makes properties enumerable,
|
||||
# it's their own fault
|
||||
guard-for-in: 0
|
||||
|
||||
# if some functions are complex, they are for a good reason,
|
||||
# ain't worth it
|
||||
max-depth: [0, 4]
|
||||
max-nested-callbacks: [0, 2]
|
||||
|
||||
# should it really throw for every long URL?
|
||||
max-len: [0, 80, 4]
|
||||
|
||||
# that's obvious by just looking at the code, you don't need lint for that
|
||||
max-params: [0, 3]
|
||||
|
||||
# if some functions are complex, they are for a good reason,
|
||||
# ain't worth it
|
||||
max-statements: [0, 10]
|
||||
|
||||
# that one makes sense
|
||||
new-cap: 2
|
||||
|
||||
# I'm writing javascript, not some weird reduced version of it
|
||||
no-bitwise: 0
|
||||
|
||||
# not working around IE bugs, sorry
|
||||
no-catch-shadow: 0
|
||||
|
||||
# see above, IE is useful for downloading other browsers only
|
||||
no-comma-dangle: 0
|
||||
|
||||
# good for removing debugging code
|
||||
no-console: 2
|
||||
|
||||
# good for removing debugging code
|
||||
no-debugger: 2
|
||||
|
||||
# why would anyone need to check against that?
|
||||
no-else-return: 0
|
||||
|
||||
# sometimes empty statement contains useful comment
|
||||
no-empty: 0
|
||||
|
||||
# stupid rule
|
||||
# "x == null" is "x === null || x === undefined"
|
||||
no-eq-null: 0
|
||||
|
||||
# fails good code, when parens are used for grouping:
|
||||
# (req && req.headers['via']) ? req.headers['via'] + ', ' : ''
|
||||
# not everyone remembers priority tables, you know
|
||||
no-extra-parens: 0
|
||||
|
||||
# fails defensive semicolons:
|
||||
# ;['foo', 'bar'].forEach(function(x) {})
|
||||
no-extra-semi: 0
|
||||
|
||||
# fails good code:
|
||||
# var fs = require('fs'),
|
||||
# , open = fs.open
|
||||
no-mixed-requires: [0, false]
|
||||
|
||||
# new Array(12) is used to pre-allocate arrays
|
||||
no-new-array: 0
|
||||
|
||||
# fails good code:
|
||||
# fs.open('/file', 0666, function(){})
|
||||
no-octal: 0
|
||||
|
||||
# fails good code:
|
||||
# console.log('\033[31m' + str + '\033[39m')
|
||||
# also fails \0 which is not octal escape
|
||||
no-octal-escape: 0
|
||||
|
||||
# I'm writing javascript, not some weird reduced version of it
|
||||
no-plusplus: 0
|
||||
|
||||
# fails good code:
|
||||
# if (a) {
|
||||
# var x = 'foo'
|
||||
# } else {
|
||||
# var x = bar
|
||||
# }
|
||||
no-redeclare: 0
|
||||
|
||||
# sometimes useful, often isn't
|
||||
# probably worth enforcing
|
||||
no-shadow: 2
|
||||
|
||||
no-sync: 2
|
||||
|
||||
# I'm writing javascript, not some weird reduced version of it
|
||||
no-ternary: 0
|
||||
|
||||
# the single most important rule in the entire ruleset
|
||||
no-undef: 2
|
||||
|
||||
# it is failing our own underscores
|
||||
no-underscore-dangle: 0
|
||||
|
||||
# fails function hoisting
|
||||
no-unreachable: 0
|
||||
|
||||
# fails npm-style code, it's good once you get used to it:
|
||||
# if (typeof(options) === 'function') callback = options, options = {}
|
||||
no-unused-expressions: 0
|
||||
|
||||
# fails (function(_err) {}) where named argument is used to show what
|
||||
# nth function argument means
|
||||
no-unused-vars: [0, local]
|
||||
|
||||
# fails function hoisting
|
||||
no-use-before-define: 0
|
||||
|
||||
# fails foobar( (function(){}).bind(this) )
|
||||
# parens are added for readability
|
||||
no-wrap-func: 0
|
||||
|
||||
# fails good code:
|
||||
# var x
|
||||
# if (something) {
|
||||
# var y
|
||||
one-var: 0
|
||||
|
||||
quote-props: 0
|
||||
|
||||
# fails situation when different quotes are used to avoid escaping
|
||||
quotes: [2, single, avoid-escape]
|
||||
|
||||
# http:#blog.izs.me/post/2353458699/an-open-letter-to-javascript-leaders-regarding
|
||||
semi: [2, never]
|
||||
|
||||
# fails good code where spaces are used for grouping:
|
||||
# (x+y * y+z)
|
||||
space-infix-ops: 0
|
||||
|
||||
# typeof(something) should have braces to look like a function
|
||||
# a matter of taste I suppose
|
||||
space-unary-word-ops: 0
|
||||
|
||||
# strict mode is just harmful,
|
||||
# can I have a check to enforce not using it?
|
||||
strict: 0
|
||||
|
||||
sort-vars: 0
|
||||
no-path-concat: 0
|
||||
func-names: 0
|
||||
|
||||
# how can you set a return code without process.exit?
|
||||
no-process-exit: 0
|
||||
|
||||
# both styles are useful
|
||||
func-style: [0, declaration]
|
||||
|
||||
# fails while(1) {...}
|
||||
no-constant-condition: 0
|
||||
|
||||
# fails good code:
|
||||
# https://github.com/rlidwka/jju/blob/eb52ee72e5f21d48963798f9bda8ac8d68082148/lib/parse.js#L732
|
||||
no-ex-assign: 0
|
||||
|
||||
wrap-iife: [2, inside]
|
||||
|
||||
# doesn't always make sense
|
||||
consistent-return: 0
|
||||
no-sequences: 0
|
||||
|
||||
# fails defensive semicolons
|
||||
no-space-before-semi: 0
|
||||
|
||||
new-parens: 1
|
||||
no-alert: 1
|
||||
no-array-constructor: 1
|
||||
no-caller: 1
|
||||
no-cond-assign: 1
|
||||
no-control-regex: 1
|
||||
no-delete-var: 1
|
||||
no-div-regex: 1
|
||||
no-dupe-keys: 1
|
||||
no-empty-class: 1
|
||||
no-empty-label: 1
|
||||
no-eval: 1
|
||||
no-extend-native: 1
|
||||
no-extra-boolean-cast: 1
|
||||
no-extra-strict: 1
|
||||
no-fallthrough: 1
|
||||
no-floating-decimal: 1
|
||||
no-func-assign: 1
|
||||
no-global-strict: 1
|
||||
no-implied-eval: 1
|
||||
no-invalid-regexp: 1
|
||||
no-iterator: 1
|
||||
no-labels: 1
|
||||
no-label-var: 1
|
||||
no-lone-blocks: 1
|
||||
no-loop-func: 1
|
||||
no-multi-str: 1
|
||||
no-native-reassign: 1
|
||||
no-negated-in-lhs: 1
|
||||
no-nested-ternary: 1
|
||||
no-new: 1
|
||||
no-new-func: 1
|
||||
no-new-object: 1
|
||||
no-new-wrappers: 1
|
||||
no-obj-calls: 1
|
||||
no-octal: 1
|
||||
no-proto: 1
|
||||
no-regex-spaces: 1
|
||||
no-return-assign: 1
|
||||
no-script-url: 1
|
||||
no-self-compare: 1
|
||||
no-shadow: 1
|
||||
no-shadow-restricted-names: 1
|
||||
no-spaced-func: 1
|
||||
no-sparse-arrays: 1
|
||||
no-sync: 1
|
||||
no-undef: 1
|
||||
no-undef-init: 1
|
||||
no-unreachable: 1
|
||||
no-with: 1
|
||||
no-yoda: 1
|
||||
radix: 1
|
||||
space-return-throw-case: 1
|
||||
use-isnan: 1
|
||||
valid-jsdoc: 1
|
||||
wrap-regex: 1
|
||||
24
.gitignore
vendored
24
.gitignore
vendored
@@ -1,9 +1,23 @@
|
||||
node_modules
|
||||
package.json
|
||||
/package.json
|
||||
npm-debug.log
|
||||
sinopia-*.tgz
|
||||
.DS_Store
|
||||
|
||||
###
|
||||
bin/storage*
|
||||
bin/*.yaml
|
||||
test/test-storage*
|
||||
bin/**
|
||||
!bin/sinopia
|
||||
test-storage*
|
||||
|
||||
# ignoring everything except bundled deps
|
||||
/node_modules/*
|
||||
!/node_modules/mkdirp
|
||||
!/node_modules/unopinionate
|
||||
!/node_modules/onclick
|
||||
!/node_modules/onscroll
|
||||
!/node_modules/transition-complete
|
||||
!/node_modules/helpers.less
|
||||
!/node_modules/tar.gz
|
||||
!/node_modules/sinopia-htpasswd
|
||||
!/node_modules/crypt3
|
||||
!/node_modules/http-errors
|
||||
|
||||
|
||||
11
.npmignore
Normal file
11
.npmignore
Normal file
@@ -0,0 +1,11 @@
|
||||
node_modules
|
||||
package.json
|
||||
npm-debug.log
|
||||
sinopia-*.tgz
|
||||
|
||||
###
|
||||
bin/**
|
||||
!bin/sinopia
|
||||
test-storage*
|
||||
|
||||
/.eslint*
|
||||
9
.travis.yml
Normal file
9
.travis.yml
Normal file
@@ -0,0 +1,9 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- '0.10'
|
||||
- '0.11'
|
||||
matrix:
|
||||
allow_failures:
|
||||
- node_js: "0.11"
|
||||
fast_finish: true
|
||||
script: npm install . && npm run test-travis
|
||||
73
ChangeLog.md
73
ChangeLog.md
@@ -1,73 +0,0 @@
|
||||
|
||||
24 Nov 2013, version 0.5.3
|
||||
|
||||
- added proxy support for requests to uplinks (issue #13)
|
||||
- changed license from default BSD to WTFPL
|
||||
|
||||
26 Oct 2013, version 0.5.2
|
||||
|
||||
- server now supports unpublishing local packages
|
||||
- added fs-ext dependency (flock)
|
||||
- fixed a few face conditions
|
||||
|
||||
20 Oct 2013, version 0.5.1
|
||||
|
||||
- fixed a few errors related to logging
|
||||
|
||||
12 Oct 2013, version 0.5.0
|
||||
|
||||
- using bunyan as a log engine
|
||||
- pretty-formatting colored logs to stdout by default
|
||||
- ask user before creating any config files
|
||||
|
||||
5 Oct 2013, version 0.4.3
|
||||
|
||||
- basic tags support for npm (read-only)
|
||||
- npm star/unstar calls now return proper error
|
||||
|
||||
29 Sep 2013, version 0.4.2
|
||||
|
||||
28 Sep 2013, version 0.4.1
|
||||
|
||||
- using mocha for tests now
|
||||
- making use of streams2 api, doesn't work on 0.8 anymore
|
||||
- basic support for uploading packages to other registries
|
||||
|
||||
27 Sep 2013, version 0.4.0
|
||||
|
||||
- basic test suite
|
||||
- storage path in config is now relative to config file location, not cwd
|
||||
- proper cleanup for temporary files
|
||||
|
||||
12 Jul 2013, version 0.3.2
|
||||
|
||||
4 Jul 2013, version 0.3.1
|
||||
|
||||
- using ETag header for all json output, based on md5
|
||||
|
||||
20 Jun 2013, version 0.3.0
|
||||
|
||||
- compression for http responses
|
||||
- requests for files to uplinks are now streams (no buffering)
|
||||
- tarballs are now cached locally
|
||||
|
||||
19 Jun 2013, version 0.2.0
|
||||
|
||||
- config file changed, packages is now specified with minimatch
|
||||
- ability to retrieve all packages from another registry (i.e. npmjs)
|
||||
|
||||
14 Jun 2013, version 0.1.1
|
||||
|
||||
- config is now autogenerated
|
||||
- tarballs are now read/written from fs using streams (no buffering)
|
||||
|
||||
9 Jun 2013, version 0.1.0
|
||||
|
||||
- first npm version
|
||||
- ability to publish packages and retrieve them locally
|
||||
- basic authentication/access control
|
||||
|
||||
22 May 2013, version 0.0.0
|
||||
|
||||
- first commits
|
||||
|
||||
39
Gruntfile.js
Normal file
39
Gruntfile.js
Normal file
@@ -0,0 +1,39 @@
|
||||
module.exports = function(grunt) {
|
||||
grunt.initConfig({
|
||||
pkg: grunt.file.readYAML('package.yaml'),
|
||||
browserify: {
|
||||
dist: {
|
||||
files: {
|
||||
'lib/static/main.js': ['lib/GUI/js/main.js']
|
||||
},
|
||||
options: {
|
||||
debug: true,
|
||||
transform: ['browserify-handlebars']
|
||||
}
|
||||
}
|
||||
},
|
||||
less: {
|
||||
dist: {
|
||||
files: {
|
||||
'lib/static/main.css': ['lib/GUI/css/main.less']
|
||||
},
|
||||
options: {
|
||||
sourceMap: true
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
files: [ "lib/GUI/**/*"],
|
||||
tasks: [ 'default' ]
|
||||
}
|
||||
});
|
||||
|
||||
grunt.loadNpmTasks('grunt-browserify');
|
||||
grunt.loadNpmTasks('grunt-contrib-watch');
|
||||
grunt.loadNpmTasks('grunt-contrib-less');
|
||||
|
||||
grunt.registerTask('default', [
|
||||
'browserify',
|
||||
'less'
|
||||
]);
|
||||
};
|
||||
239
History.md
Normal file
239
History.md
Normal file
@@ -0,0 +1,239 @@
|
||||
|
||||
22 Oct 2014, version 0.13.0
|
||||
|
||||
- web interface:
|
||||
- web page layout improved (issue [#141](https://github.com/rlidwka/sinopia/pull/141))
|
||||
- latest version is now displayed correctly (issues [#120](https://github.com/rlidwka/sinopia/issues/120), [#123](https://github.com/rlidwka/sinopia/issues/123), [#143](https://github.com/rlidwka/sinopia/pull/143))
|
||||
- fixed web interface working behind reverse proxy (issues [#145](https://github.com/rlidwka/sinopia/issues/145), [#147](https://github.com/rlidwka/sinopia/issues/147))
|
||||
|
||||
2 Oct 2014, version 0.12.1
|
||||
|
||||
- web interface:
|
||||
- update markdown CSS (issue [#137](https://github.com/rlidwka/sinopia/pull/137))
|
||||
- jquery is now served locally (issue [#133](https://github.com/rlidwka/sinopia/pull/133))
|
||||
|
||||
- bugfixes:
|
||||
- fix "offset out of bounds" issues (issue [sinopia-htpasswd/#2](https://github.com/rlidwka/sinopia-htpasswd/issues/2))
|
||||
- "max_users" in htpasswd plugin now work correctly (issue [sinopia-htpasswd/#3](https://github.com/rlidwka/sinopia-htpasswd/issues/3))
|
||||
- fix `ENOTDIR, open '.sinopia-db.json'` error in npm search (issue [#122](https://github.com/rlidwka/sinopia/issues/122))
|
||||
|
||||
25 Sep 2014, version 0.12.0
|
||||
|
||||
- set process title to `sinopia`
|
||||
|
||||
- web interface bugfixes:
|
||||
- save README data for each package (issue [#100](https://github.com/rlidwka/sinopia/issues/100))
|
||||
- fix crashes related to READMEs (issue [#128](https://github.com/rlidwka/sinopia/issues/128))
|
||||
|
||||
18 Sep 2014, version 0.11.3
|
||||
|
||||
- fix 500 error in adduser function in sinopia-htpasswd (issue [#121](https://github.com/rlidwka/sinopia/issues/121))
|
||||
- fix fd leak in authenticate function in sinopia-htpasswd (issue [#116](https://github.com/rlidwka/sinopia/issues/116))
|
||||
|
||||
15 Sep 2014, version 0.11.1
|
||||
|
||||
- mark crypt3 as optional (issue [#119](https://github.com/rlidwka/sinopia/issues/119))
|
||||
|
||||
15 Sep 2014, version 0.11.0
|
||||
|
||||
- Added auth plugins (issue [#99](https://github.com/rlidwka/sinopia/pull/99))
|
||||
|
||||
Now you can create your own auth plugin based on [sinopia-htpasswd](https://github.com/rlidwka/sinopia-htpasswd) package.
|
||||
|
||||
- WIP: web interface (issue [#73](https://github.com/rlidwka/sinopia/pull/73))
|
||||
|
||||
It is disabled by default, and not ready for production yet. Use at your own risk. We will enable it in the next major release.
|
||||
|
||||
- Some modules are now bundled by default, so users won't have to install stuff from git. We'll see what issues it causes, maybe all modules will be bundled in the future like in npm.
|
||||
|
||||
14 Sep 2014, version 0.10.x
|
||||
|
||||
*A bunch of development releases that are broken in various ways. Please use 0.11.x instead.*
|
||||
|
||||
7 Sep 2014, version 0.9.3
|
||||
|
||||
- fix several bugs that could cause "can't set headers" exception
|
||||
|
||||
3 Sep 2014, version 0.9.2
|
||||
|
||||
- allow "pretty" format for logging into files (issue [#88](https://github.com/rlidwka/sinopia/pull/88))
|
||||
- remove outdated user existence check (issue [#115](https://github.com/rlidwka/sinopia/pull/115))
|
||||
|
||||
11 Aug 2014, version 0.9.1
|
||||
|
||||
- filter falsey _npmUser values (issue [#95](https://github.com/rlidwka/sinopia/pull/95))
|
||||
- option not to cache third-party files (issue [#85](https://github.com/rlidwka/sinopia/issues/85))
|
||||
|
||||
26 Jul 2014, version 0.9.0
|
||||
|
||||
- new features:
|
||||
- add search functionality (issue [#65](https://github.com/rlidwka/sinopia/pull/65))
|
||||
- allow users to authenticate using .htpasswd (issue [#44](https://github.com/rlidwka/sinopia/issues/44))
|
||||
- allow user registration with "npm adduser" (issue [#44](https://github.com/rlidwka/sinopia/issues/44))
|
||||
|
||||
- bugfixes:
|
||||
- avoid crashing when res.socket is null (issue [#89](https://github.com/rlidwka/sinopia/issues/89))
|
||||
|
||||
20 Jun 2014, version 0.8.2
|
||||
|
||||
- allow '@' in package/tarball names (issue [#75](https://github.com/rlidwka/sinopia/issues/75))
|
||||
- other minor fixes (issues [#77](https://github.com/rlidwka/sinopia/issues/77), [#80](https://github.com/rlidwka/sinopia/issues/80))
|
||||
|
||||
14 Apr 2014, version 0.8.1
|
||||
|
||||
- "latest" tag is now always present in any package (issue [#63](https://github.com/rlidwka/sinopia/issues/63))
|
||||
- tags created with new npm versions (>= 1.3.19) can now be published correctly
|
||||
|
||||
1 Apr 2014, version 0.8.0
|
||||
|
||||
- use gzip compression whenever possible (issue [#54](https://github.com/rlidwka/sinopia/issues/54))
|
||||
- set `ignore_latest_tag` to false, it should now be more compatible with npm registry
|
||||
- make `fs-ext` optional (issue [#61](https://github.com/rlidwka/sinopia/issues/61))
|
||||
|
||||
29 Mar 2014, version 0.7.1
|
||||
|
||||
- added `ignore_latest_tag` config param (issues [#55](https://github.com/rlidwka/sinopia/issues/55), [#59](https://github.com/rlidwka/sinopia/issues/59))
|
||||
- reverted PR [#56](https://github.com/rlidwka/sinopia/issues/56) (see discussion in [#57](https://github.com/rlidwka/sinopia/issues/57))
|
||||
|
||||
13 Mar 2014, version 0.7.0
|
||||
|
||||
- config changes:
|
||||
- breaking change: all time intervals are now specified in *seconds* instead of *milliseconds* for the sake of consistency. Change `timeout` if you have one!
|
||||
- all time intervals now can be specified in [nginx notation](http://wiki.nginx.org/ConfigNotation), for example `1m 30s` will specify a 90 seconds timeout
|
||||
- added `maxage` option to avoid asking public registry for the same data too often (issue [#47](https://github.com/rlidwka/sinopia/issues/47))
|
||||
- added `max_fails` and `fail_timeout` options to reduce amount of requests to public registry when it's down (issue [#7](https://github.com/rlidwka/sinopia/issues/7))
|
||||
|
||||
- bug fixes:
|
||||
- fix crash when headers are sent twice (issue [#52](https://github.com/rlidwka/sinopia/issues/52))
|
||||
- all tarballs are returned with `Content-Length`, which allows [yapm](https://github.com/rlidwka/yapm) to estimate download time
|
||||
- when connection to public registry is interrupted when downloading a tarball, we no longer save incomplete tarball to the disk
|
||||
|
||||
- other changes:
|
||||
- 404 errors are returned in couchdb-like manner (issue [#56](https://github.com/rlidwka/sinopia/issues/56))
|
||||
|
||||
5 Mar 2014, version 0.6.7
|
||||
|
||||
- pin down express@3 version, since sinopia doesn't yet work with express@4
|
||||
|
||||
28 Feb 2014, version 0.6.5
|
||||
|
||||
- old SSL keys for npm are removed, solves `SELF_SIGNED_CERT_IN_CHAIN` error
|
||||
|
||||
3 Feb 2014, version 0.6.3
|
||||
|
||||
- validate tags and versions (issue [#40](https://github.com/rlidwka/sinopia/issues/40))
|
||||
- don't crash when process.getuid doesn't exist (issue [#41](https://github.com/rlidwka/sinopia/issues/41))
|
||||
|
||||
18 Jan 2014, version 0.6.2
|
||||
|
||||
- adding config param to specify upload limits (issue [#39](https://github.com/rlidwka/sinopia/issues/39))
|
||||
- making loose semver versions work (issue [#38](https://github.com/rlidwka/sinopia/issues/38))
|
||||
|
||||
13 Jan 2014, version 0.6.1
|
||||
|
||||
- support setting different storage paths for different packages (issue [#35](https://github.com/rlidwka/sinopia/issues/35))
|
||||
|
||||
30 Dec 2013, version 0.6.0
|
||||
|
||||
- tag support (issue [#8](https://github.com/rlidwka/sinopia/issues/8))
|
||||
- adding support for npm 1.3.19+ behaviour (issue [#31](https://github.com/rlidwka/sinopia/issues/31))
|
||||
- removing all support for proxying publish requests to uplink (too complex)
|
||||
|
||||
26 Dec 2013, version 0.5.9
|
||||
|
||||
- fixing bug with bad Accept header (issue [#32](https://github.com/rlidwka/sinopia/issues/32))
|
||||
|
||||
20 Dec 2013, version 0.5.8
|
||||
|
||||
- fixed a warning from js-yaml
|
||||
- don't color multiline strings in logs output
|
||||
- better error messages in various cases
|
||||
- test format changed
|
||||
|
||||
15 Dec 2013, version 0.5.7
|
||||
|
||||
- try to fetch package from uplinks if user requested a tarball we don't know about (issue [#29](https://github.com/rlidwka/sinopia/issues/29))
|
||||
- security fix: set express.js to production mode so we won't return stack traces to the user in case of errors
|
||||
|
||||
11 Dec 2013, version 0.5.6
|
||||
|
||||
- fixing a few crashes related to tags
|
||||
|
||||
8 Dec 2013, version 0.5.4
|
||||
|
||||
- latest tag always shows highest version available (issue [#8](https://github.com/rlidwka/sinopia/issues/8))
|
||||
- added a configurable timeout for requests to uplinks (issue [#18](https://github.com/rlidwka/sinopia/issues/18))
|
||||
- users with bad authentication header are considered not logged in (issue [#17](https://github.com/rlidwka/sinopia/issues/17))
|
||||
|
||||
24 Nov 2013, version 0.5.3
|
||||
|
||||
- added proxy support for requests to uplinks (issue [#13](https://github.com/rlidwka/sinopia/issues/13))
|
||||
- changed license from default BSD to WTFPL
|
||||
|
||||
26 Oct 2013, version 0.5.2
|
||||
|
||||
- server now supports unpublishing local packages
|
||||
- added fs-ext dependency (flock)
|
||||
- fixed a few face conditions
|
||||
|
||||
20 Oct 2013, version 0.5.1
|
||||
|
||||
- fixed a few errors related to logging
|
||||
|
||||
12 Oct 2013, version 0.5.0
|
||||
|
||||
- using bunyan as a log engine
|
||||
- pretty-formatting colored logs to stdout by default
|
||||
- ask user before creating any config files
|
||||
|
||||
5 Oct 2013, version 0.4.3
|
||||
|
||||
- basic tags support for npm (read-only)
|
||||
- npm star/unstar calls now return proper error
|
||||
|
||||
29 Sep 2013, version 0.4.2
|
||||
|
||||
28 Sep 2013, version 0.4.1
|
||||
|
||||
- using mocha for tests now
|
||||
- making use of streams2 api, doesn't work on 0.8 anymore
|
||||
- basic support for uploading packages to other registries
|
||||
|
||||
27 Sep 2013, version 0.4.0
|
||||
|
||||
- basic test suite
|
||||
- storage path in config is now relative to config file location, not cwd
|
||||
- proper cleanup for temporary files
|
||||
|
||||
12 Jul 2013, version 0.3.2
|
||||
|
||||
4 Jul 2013, version 0.3.1
|
||||
|
||||
- using ETag header for all json output, based on md5
|
||||
|
||||
20 Jun 2013, version 0.3.0
|
||||
|
||||
- compression for http responses
|
||||
- requests for files to uplinks are now streams (no buffering)
|
||||
- tarballs are now cached locally
|
||||
|
||||
19 Jun 2013, version 0.2.0
|
||||
|
||||
- config file changed, packages is now specified with minimatch
|
||||
- ability to retrieve all packages from another registry (i.e. npmjs)
|
||||
|
||||
14 Jun 2013, version 0.1.1
|
||||
|
||||
- config is now autogenerated
|
||||
- tarballs are now read/written from fs using streams (no buffering)
|
||||
|
||||
9 Jun 2013, version 0.1.0
|
||||
|
||||
- first npm version
|
||||
- ability to publish packages and retrieve them locally
|
||||
- basic authentication/access control
|
||||
|
||||
22 May 2013, version 0.0.0
|
||||
|
||||
- first commits
|
||||
|
||||
40
README.md
40
README.md
@@ -1,7 +1,13 @@
|
||||
Sinopia is a private/caching npm repository server.
|
||||
`sinopia` - a private/caching npm repository server
|
||||
|
||||
[](https://www.npmjs.org/package/sinopia)
|
||||
[](https://travis-ci.org/rlidwka/sinopia)
|
||||
[](https://www.npmjs.org/package/sinopia)
|
||||
|
||||
It allows you to have a local npm registry with zero configuration. You don't have to install and replicate an entire CouchDB database. Sinopia keeps its own small database and, if a package doesn't exist there, it asks npmjs.org for it keeping only those packages you use.
|
||||
|
||||
<p align="center"><img src="https://f.cloud.github.com/assets/999113/1795553/680177b2-6a1d-11e3-82e1-02193aa4e32e.png"></p>
|
||||
|
||||
## Use cases
|
||||
|
||||
1. Use private packages.
|
||||
@@ -19,7 +25,7 @@ It allows you to have a local npm registry with zero configuration. You don't ha
|
||||
|
||||
3. Override public packages.
|
||||
|
||||
If you want to use a modified version of some 3rd-party package (for example, you found a bug, but maintainer didn't accepted pull request yet), you can publish your version locally under the same name.
|
||||
If you want to use a modified version of some 3rd-party package (for example, you found a bug, but maintainer didn't accept pull request yet), you can publish your version locally under the same name.
|
||||
|
||||
See [override public packages](#override-public-packages) section for details.
|
||||
|
||||
@@ -42,23 +48,31 @@ $ npm set always-auth true
|
||||
$ npm set ca null
|
||||
```
|
||||
|
||||
Now you can navigate to [http://localhost:4873/](http://localhost:4873/) where your local packages will be listed and can be searched.
|
||||
|
||||
### Docker
|
||||
|
||||
A Sinopia docker image [is available](https://registry.hub.docker.com/u/keyvanfatehi/sinopia/)
|
||||
|
||||
### Chef
|
||||
|
||||
A Sinopia Chef cookbook [is available at Opscode community](http://community.opscode.com/cookbooks/sinopia) source: https://github.com/BarthV/sinopia-cookbook
|
||||
|
||||
### Puppet
|
||||
|
||||
A Sinopia puppet module [is available at puppet forge](http://forge.puppetlabs.com/saheba/sinopia) source: https://github.com/saheba/puppet-sinopia
|
||||
|
||||
## Configuration
|
||||
|
||||
When you start a server, it auto-creates a config file that adds one user (password is printed to stdout only once).
|
||||
When you start a server, it auto-creates a config file.
|
||||
|
||||
## Adding a new user
|
||||
|
||||
There is no utility to add a new user but you can at least use node on the command-line to generate a password. You will need to edit the config and add the user manually.
|
||||
|
||||
Start node and enter the following code replacing 'newpass' with the password you want to get the hash for.
|
||||
```bash
|
||||
$ node
|
||||
> crypto.createHash('sha1').update('newpass').digest('hex')
|
||||
'6c55803d6f1d7a177a0db3eb4b343b0d50f9c111'
|
||||
> [CTRL-D]
|
||||
npm adduser --registry http://localhost:4873/
|
||||
```
|
||||
|
||||
This will prompt you for user credentials which will be saved on the Sinopia server.
|
||||
|
||||
## Using private packages
|
||||
|
||||
@@ -99,18 +113,18 @@ Basic features:
|
||||
|
||||
Advanced package control:
|
||||
|
||||
- Unpublishing packages (npm unpublish) - not yet supported, should be soon
|
||||
- Unpublishing packages (npm unpublish) - supported
|
||||
- Tagging (npm tag) - not yet supported, should be soon
|
||||
- Deprecation (npm deprecate) - not supported
|
||||
|
||||
User management:
|
||||
|
||||
- Registering new users (npm adduser {newuser}) - not supported, sinopia uses its own acl management system
|
||||
- Registering new users (npm adduser {newuser}) - supported
|
||||
- Transferring ownership (npm owner add {user} {pkg}) - not supported, sinopia uses its own acl management system
|
||||
|
||||
Misc stuff:
|
||||
|
||||
- Searching (npm search) - not supported
|
||||
- Searching (npm search) - supported in the browser client but not command line
|
||||
- Starring (npm star, npm unstar) - not supported, doesn't make sense in private registry
|
||||
|
||||
## Storage
|
||||
|
||||
9
index.js
Normal file
9
index.js
Normal file
@@ -0,0 +1,9 @@
|
||||
module.exports = require('./lib')
|
||||
|
||||
/**package
|
||||
{ "name": "sinopia",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {"js-yaml": "*"},
|
||||
"scripts": {"postinstall": "js-yaml package.yaml > package.json ; npm install"}
|
||||
}
|
||||
**/
|
||||
6203
lib/GUI/css/bootstrap.css
vendored
Normal file
6203
lib/GUI/css/bootstrap.css
vendored
Normal file
File diff suppressed because it is too large
Load Diff
56
lib/GUI/css/fontello.less
Normal file
56
lib/GUI/css/fontello.less
Normal file
@@ -0,0 +1,56 @@
|
||||
@font-face {
|
||||
font-family: 'fontello';
|
||||
src: url('../static/fontello.eot?10872183');
|
||||
src: url('../static/fontello.eot?10872183#iefix') format('embedded-opentype'),
|
||||
url('../static/fontello.woff?10872183') format('woff'),
|
||||
url('../static/fontello.ttf?10872183') format('truetype'),
|
||||
url('../static/fontello.svg?10872183#fontello') format('svg');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
/* Chrome hack: SVG is rendered more smooth in Windozze. 100% magic, uncomment if you need it. */
|
||||
/* Note, that will break hinting! In other OS-es font will be not as sharp as it could be */
|
||||
/*
|
||||
@media screen and (-webkit-min-device-pixel-ratio:0) {
|
||||
@font-face {
|
||||
font-family: 'fontello';
|
||||
src: url('../font/fontello.svg?10872183#fontello') format('svg');
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
[class^="icon-"]:before, [class*=" icon-"]:before {
|
||||
font-family: "fontello";
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
speak: none;
|
||||
|
||||
display: inline-block;
|
||||
text-decoration: inherit;
|
||||
width: 1em;
|
||||
margin-right: .2em;
|
||||
text-align: center;
|
||||
/* opacity: .8; */
|
||||
|
||||
/* For safety - reset parent styles, that can break glyph codes*/
|
||||
font-variant: normal;
|
||||
text-transform: none;
|
||||
|
||||
/* fix buttons height, for twitter bootstrap */
|
||||
line-height: 1em;
|
||||
|
||||
/* Animation center compensation - margins should be symmetric */
|
||||
/* remove if not needed */
|
||||
margin-left: .2em;
|
||||
|
||||
/* you can be more comfortable with increased icons size */
|
||||
/* font-size: 120%; */
|
||||
|
||||
/* Uncomment for 3D effect */
|
||||
/* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */
|
||||
}
|
||||
|
||||
.icon-search:before { content: '\e801'; } /* '' */
|
||||
.icon-cancel:before { content: '\e803'; } /* '' */
|
||||
.icon-right-open:before { content: '\e802'; } /* '' */
|
||||
.icon-angle-right:before { content: '\e800'; } /* '' */
|
||||
153
lib/GUI/css/highlight.js.less
Normal file
153
lib/GUI/css/highlight.js.less
Normal file
@@ -0,0 +1,153 @@
|
||||
/*
|
||||
|
||||
Original style from softwaremaniacs.org (c) Ivan Sagalaev <Maniac@SoftwareManiacs.Org>
|
||||
|
||||
*/
|
||||
|
||||
.hljs {
|
||||
display: block; padding: 0.5em;
|
||||
background: #F0F0F0;
|
||||
}
|
||||
|
||||
.hljs,
|
||||
.hljs-subst,
|
||||
.hljs-tag .hljs-title,
|
||||
.lisp .hljs-title,
|
||||
.clojure .hljs-built_in,
|
||||
.nginx .hljs-title {
|
||||
color: black;
|
||||
}
|
||||
|
||||
.hljs-string,
|
||||
.hljs-title,
|
||||
.hljs-constant,
|
||||
.hljs-parent,
|
||||
.hljs-tag .hljs-value,
|
||||
.hljs-rules .hljs-value,
|
||||
.hljs-rules .hljs-value .hljs-number,
|
||||
.hljs-preprocessor,
|
||||
.hljs-pragma,
|
||||
.haml .hljs-symbol,
|
||||
.ruby .hljs-symbol,
|
||||
.ruby .hljs-symbol .hljs-string,
|
||||
.hljs-aggregate,
|
||||
.hljs-template_tag,
|
||||
.django .hljs-variable,
|
||||
.smalltalk .hljs-class,
|
||||
.hljs-addition,
|
||||
.hljs-flow,
|
||||
.hljs-stream,
|
||||
.bash .hljs-variable,
|
||||
.apache .hljs-tag,
|
||||
.apache .hljs-cbracket,
|
||||
.tex .hljs-command,
|
||||
.tex .hljs-special,
|
||||
.erlang_repl .hljs-function_or_atom,
|
||||
.asciidoc .hljs-header,
|
||||
.markdown .hljs-header,
|
||||
.coffeescript .hljs-attribute {
|
||||
color: #800;
|
||||
}
|
||||
|
||||
.smartquote,
|
||||
.hljs-comment,
|
||||
.hljs-annotation,
|
||||
.hljs-template_comment,
|
||||
.diff .hljs-header,
|
||||
.hljs-chunk,
|
||||
.asciidoc .hljs-blockquote,
|
||||
.markdown .hljs-blockquote {
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.hljs-number,
|
||||
.hljs-date,
|
||||
.hljs-regexp,
|
||||
.hljs-literal,
|
||||
.hljs-hexcolor,
|
||||
.smalltalk .hljs-symbol,
|
||||
.smalltalk .hljs-char,
|
||||
.go .hljs-constant,
|
||||
.hljs-change,
|
||||
.lasso .hljs-variable,
|
||||
.makefile .hljs-variable,
|
||||
.asciidoc .hljs-bullet,
|
||||
.markdown .hljs-bullet,
|
||||
.asciidoc .hljs-link_url,
|
||||
.markdown .hljs-link_url {
|
||||
color: #080;
|
||||
}
|
||||
|
||||
.hljs-label,
|
||||
.hljs-javadoc,
|
||||
.ruby .hljs-string,
|
||||
.hljs-decorator,
|
||||
.hljs-filter .hljs-argument,
|
||||
.hljs-localvars,
|
||||
.hljs-array,
|
||||
.hljs-attr_selector,
|
||||
.hljs-important,
|
||||
.hljs-pseudo,
|
||||
.hljs-pi,
|
||||
.haml .hljs-bullet,
|
||||
.hljs-doctype,
|
||||
.hljs-deletion,
|
||||
.hljs-envvar,
|
||||
.hljs-shebang,
|
||||
.apache .hljs-sqbracket,
|
||||
.nginx .hljs-built_in,
|
||||
.tex .hljs-formula,
|
||||
.erlang_repl .hljs-reserved,
|
||||
.hljs-prompt,
|
||||
.asciidoc .hljs-link_label,
|
||||
.markdown .hljs-link_label,
|
||||
.vhdl .hljs-attribute,
|
||||
.clojure .hljs-attribute,
|
||||
.asciidoc .hljs-attribute,
|
||||
.lasso .hljs-attribute,
|
||||
.coffeescript .hljs-property,
|
||||
.hljs-phony {
|
||||
color: #88F
|
||||
}
|
||||
|
||||
.hljs-keyword,
|
||||
.hljs-id,
|
||||
.hljs-title,
|
||||
.hljs-built_in,
|
||||
.hljs-aggregate,
|
||||
.css .hljs-tag,
|
||||
.hljs-javadoctag,
|
||||
.hljs-phpdoc,
|
||||
.hljs-yardoctag,
|
||||
.smalltalk .hljs-class,
|
||||
.hljs-winutils,
|
||||
.bash .hljs-variable,
|
||||
.apache .hljs-tag,
|
||||
.go .hljs-typename,
|
||||
.tex .hljs-command,
|
||||
.asciidoc .hljs-strong,
|
||||
.markdown .hljs-strong,
|
||||
.hljs-request,
|
||||
.hljs-status {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.asciidoc .hljs-emphasis,
|
||||
.markdown .hljs-emphasis {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.nginx .hljs-built_in {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.coffeescript .javascript,
|
||||
.javascript .xml,
|
||||
.lasso .markup,
|
||||
.tex .hljs-formula,
|
||||
.xml .javascript,
|
||||
.xml .vbscript,
|
||||
.xml .css,
|
||||
.xml .hljs-cdata {
|
||||
opacity: 0.5;
|
||||
}
|
||||
7
lib/GUI/css/main.less
Normal file
7
lib/GUI/css/main.less
Normal file
@@ -0,0 +1,7 @@
|
||||
@import "../../../node_modules/helpers.less/helpers.less";
|
||||
@import (less) "bootstrap.css";
|
||||
@import "markdown.less";
|
||||
@import "highlight.js.less";
|
||||
@import "fontello.less";
|
||||
@import "styles.less";
|
||||
@import "responsive.less";
|
||||
700
lib/GUI/css/markdown.less
Normal file
700
lib/GUI/css/markdown.less
Normal file
@@ -0,0 +1,700 @@
|
||||
/*** Sourced from this Gist: https://github.com/sindresorhus/github-markdown-css ***/
|
||||
@font-face {
|
||||
font-family: octicons-anchor;
|
||||
src: url(data:font/woff;charset=utf-8;base64,d09GRgABAAAAAAYcAA0AAAAACjQAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABGRlRNAAABMAAAABwAAAAca8vGTk9TLzIAAAFMAAAARAAAAFZG1VHVY21hcAAAAZAAAAA+AAABQgAP9AdjdnQgAAAB0AAAAAQAAAAEACICiGdhc3AAAAHUAAAACAAAAAj//wADZ2x5ZgAAAdwAAADRAAABEKyikaNoZWFkAAACsAAAAC0AAAA2AtXoA2hoZWEAAALgAAAAHAAAACQHngNFaG10eAAAAvwAAAAQAAAAEAwAACJsb2NhAAADDAAAAAoAAAAKALIAVG1heHAAAAMYAAAAHwAAACABEAB2bmFtZQAAAzgAAALBAAAFu3I9x/Nwb3N0AAAF/AAAAB0AAAAvaoFvbwAAAAEAAAAAzBdyYwAAAADP2IQvAAAAAM/bz7t4nGNgZGFgnMDAysDB1Ml0hoGBoR9CM75mMGLkYGBgYmBlZsAKAtJcUxgcPsR8iGF2+O/AEMPsznAYKMwIkgMA5REMOXicY2BgYGaAYBkGRgYQsAHyGMF8FgYFIM0ChED+h5j//yEk/3KoSgZGNgYYk4GRCUgwMaACRoZhDwCs7QgGAAAAIgKIAAAAAf//AAJ4nHWMMQrCQBBF/0zWrCCIKUQsTDCL2EXMohYGSSmorScInsRGL2DOYJe0Ntp7BK+gJ1BxF1stZvjz/v8DRghQzEc4kIgKwiAppcA9LtzKLSkdNhKFY3HF4lK69ExKslx7Xa+vPRVS43G98vG1DnkDMIBUgFN0MDXflU8tbaZOUkXUH0+U27RoRpOIyCKjbMCVejwypzJJG4jIwb43rfl6wbwanocrJm9XFYfskuVC5K/TPyczNU7b84CXcbxks1Un6H6tLH9vf2LRnn8Ax7A5WQAAAHicY2BkYGAA4teL1+yI57f5ysDNwgAC529f0kOmWRiYVgEpDgYmEA8AUzEKsQAAAHicY2BkYGB2+O/AEMPCAAJAkpEBFbAAADgKAe0EAAAiAAAAAAQAAAAEAAAAAAAAKgAqACoAiAAAeJxjYGRgYGBhsGFgYgABEMkFhAwM/xn0QAIAD6YBhwB4nI1Ty07cMBS9QwKlQapQW3VXySvEqDCZGbGaHULiIQ1FKgjWMxknMfLEke2A+IJu+wntrt/QbVf9gG75jK577Lg8K1qQPCfnnnt8fX1NRC/pmjrk/zprC+8D7tBy9DHgBXoWfQ44Av8t4Bj4Z8CLtBL9CniJluPXASf0Lm4CXqFX8Q84dOLnMB17N4c7tBo1AS/Qi+hTwBH4rwHHwN8DXqQ30XXAS7QaLwSc0Gn8NuAVWou/gFmnjLrEaEh9GmDdDGgL3B4JsrRPDU2hTOiMSuJUIdKQQayiAth69r6akSSFqIJuA19TrzCIaY8sIoxyrNIrL//pw7A2iMygkX5vDj+G+kuoLdX4GlGK/8Lnlz6/h9MpmoO9rafrz7ILXEHHaAx95s9lsI7AHNMBWEZHULnfAXwG9/ZqdzLI08iuwRloXE8kfhXYAvE23+23DU3t626rbs8/8adv+9DWknsHp3E17oCf+Z48rvEQNZ78paYM38qfk3v/u3l3u3GXN2Dmvmvpf1Srwk3pB/VSsp512bA/GG5i2WJ7wu430yQ5K3nFGiOqgtmSB5pJVSizwaacmUZzZhXLlZTq8qGGFY2YcSkqbth6aW1tRmlaCFs2016m5qn36SbJrqosG4uMV4aP2PHBmB3tjtmgN2izkGQyLWprekbIntJFing32a5rKWCN/SdSoga45EJykyQ7asZvHQ8PTm6cslIpwyeyjbVltNikc2HTR7YKh9LBl9DADC0U/jLcBZDKrMhUBfQBvXRzLtFtjU9eNHKin0x5InTqb8lNpfKv1s1xHzTXRqgKzek/mb7nB8RZTCDhGEX3kK/8Q75AmUM/eLkfA+0Hi908Kx4eNsMgudg5GLdRD7a84npi+YxNr5i5KIbW5izXas7cHXIMAau1OueZhfj+cOcP3P8MNIWLyYOBuxL6DRylJ4cAAAB4nGNgYoAALjDJyIAOWMCiTIxMLDmZedkABtIBygAAAA==) format('woff');
|
||||
}
|
||||
|
||||
.readme {
|
||||
-ms-text-size-adjust: 100%;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
color: #333;
|
||||
overflow: hidden;
|
||||
font-family: "Helvetica Neue", Helvetica, "Segoe UI", Arial, freesans, sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
word-wrap: break-word;
|
||||
|
||||
|
||||
a {
|
||||
background: transparent;
|
||||
|
||||
&:active,
|
||||
&:hover {
|
||||
outline: 0;
|
||||
}
|
||||
}
|
||||
|
||||
strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-align: left;
|
||||
font-size: 2em;
|
||||
margin: 0.67em 0;
|
||||
}
|
||||
|
||||
img {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
hr {
|
||||
-moz-box-sizing: content-box;
|
||||
box-sizing: content-box;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
pre {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
code,
|
||||
kbd,
|
||||
pre {
|
||||
font-family: monospace, monospace;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
input {
|
||||
color: inherit;
|
||||
font: inherit;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
html input[disabled] {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
input {
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
td,
|
||||
th {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
* {
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
input {
|
||||
font: 13px/1.4 Helvetica, arial, freesans, clean, sans-serif, "Segoe UI Emoji", "Segoe UI Symbol";
|
||||
}
|
||||
|
||||
a {
|
||||
color: #4183c4;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
hr {
|
||||
height: 0;
|
||||
margin: 15px 0;
|
||||
overflow: hidden;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
border-bottom: 1px solid #ddd;
|
||||
|
||||
&:before {
|
||||
display: table;
|
||||
content: "";
|
||||
}
|
||||
|
||||
&:after {
|
||||
display: table;
|
||||
clear: both;
|
||||
content: "";
|
||||
}
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
margin-top: 15px;
|
||||
margin-bottom: 15px;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 30px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 21px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
h6 {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
ul,
|
||||
ol {
|
||||
padding: 0;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
|
||||
ol {
|
||||
list-style-type: lower-roman;
|
||||
|
||||
ol {
|
||||
list-style-type: lower-alpha;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ul {
|
||||
ul {
|
||||
ol {
|
||||
list-style-type: lower-alpha;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ol {
|
||||
ul {
|
||||
ol {
|
||||
list-style-type: lower-alpha;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
dd {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
code {
|
||||
font: 12px Consolas, "Liberation Mono", Menlo, Courier, monospace;
|
||||
}
|
||||
|
||||
pre {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
font: 12px Consolas, "Liberation Mono", Menlo, Courier, monospace;
|
||||
}
|
||||
|
||||
kbd {
|
||||
background-color: #e7e7e7;
|
||||
background-image: -webkit-linear-gradient(#fefefe, #e7e7e7);
|
||||
background-image: linear-gradient(#fefefe, #e7e7e7);
|
||||
background-repeat: repeat-x;
|
||||
border-radius: 2px;
|
||||
border: 1px solid #cfcfcf;
|
||||
color: #000;
|
||||
padding: 3px 5px;
|
||||
line-height: 10px;
|
||||
font: 11px Consolas, "Liberation Mono", Menlo, Courier, monospace;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
>*:first-child {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
|
||||
>*:last-child {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
.anchor {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
display: block;
|
||||
padding-right: 6px;
|
||||
padding-left: 30px;
|
||||
margin-left: -30px;
|
||||
}
|
||||
|
||||
.anchor:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
position: relative;
|
||||
margin-top: 1em;
|
||||
margin-bottom: 16px;
|
||||
font-weight: bold;
|
||||
line-height: 1.4;
|
||||
|
||||
.octicon-link {
|
||||
display: none;
|
||||
color: #000;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.anchor {
|
||||
height: 1em;
|
||||
padding-left: 8px;
|
||||
margin-left: -30px;
|
||||
line-height: 1;
|
||||
text-decoration: none;
|
||||
|
||||
.octicon-link {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
h1 {
|
||||
padding-bottom: 0.3em;
|
||||
font-size: 2.25em;
|
||||
line-height: 1.2;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
h2 {
|
||||
padding-bottom: 0.3em;
|
||||
font-size: 1.75em;
|
||||
line-height: 1.225;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.5em;
|
||||
line-height: 1.43;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 1.25em;
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
h6 {
|
||||
font-size: 1em;
|
||||
color: #777;
|
||||
}
|
||||
|
||||
p,
|
||||
blockquote,
|
||||
ul,
|
||||
ol,
|
||||
dl,
|
||||
table,
|
||||
pre {
|
||||
margin-top: 0;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
hr {
|
||||
height: 4px;
|
||||
padding: 0;
|
||||
margin: 16px 0;
|
||||
background-color: #e7e7e7;
|
||||
border: 0 none;
|
||||
}
|
||||
|
||||
ul,
|
||||
ol {
|
||||
padding-left: 2em;
|
||||
|
||||
ul, ol {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
li {
|
||||
>p {
|
||||
margin-top: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
dl {
|
||||
padding: 0;
|
||||
|
||||
dt {
|
||||
padding: 0;
|
||||
margin-top: 16px;
|
||||
font-size: 1em;
|
||||
font-style: italic;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
dd {
|
||||
padding: 0 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
blockquote {
|
||||
padding: 0 15px;
|
||||
color: #777;
|
||||
border-left: 4px solid #ddd;
|
||||
|
||||
>:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
>:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
table {
|
||||
display: block;
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
word-break: normal;
|
||||
word-break: keep-all;
|
||||
|
||||
th {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
th,
|
||||
td {
|
||||
padding: 6px 13px;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
tr {
|
||||
background-color: #fff;
|
||||
border-top: 1px solid #ccc;
|
||||
|
||||
&:nth-child(2n) {
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
code {
|
||||
padding: 0;
|
||||
padding-top: 0.2em;
|
||||
padding-bottom: 0.2em;
|
||||
margin: 0;
|
||||
font-size: 85%;
|
||||
background-color: rgba(0,0,0,0.04);
|
||||
border-radius: 3px;
|
||||
|
||||
&:before,
|
||||
&:after {
|
||||
letter-spacing: -0.2em;
|
||||
content: "\00a0";
|
||||
}
|
||||
}
|
||||
|
||||
pre {
|
||||
padding: 16px;
|
||||
overflow: auto;
|
||||
font-size: 85%;
|
||||
line-height: 1.45;
|
||||
background-color: #f7f7f7;
|
||||
border-radius: 3px;
|
||||
word-wrap: normal;
|
||||
|
||||
>code {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-size: 100%;
|
||||
word-break: normal;
|
||||
white-space: pre;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
display: inline;
|
||||
max-width: initial;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
overflow: initial;
|
||||
line-height: inherit;
|
||||
word-wrap: normal;
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
|
||||
&:before,
|
||||
&:after {
|
||||
content: normal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.highlight {
|
||||
margin-bottom: 16px;
|
||||
background: #fff;
|
||||
|
||||
pre {
|
||||
padding: 16px;
|
||||
overflow: auto;
|
||||
font-size: 85%;
|
||||
line-height: 1.45;
|
||||
background-color: #f7f7f7;
|
||||
border-radius: 3px;
|
||||
margin-bottom: 0;
|
||||
word-break: normal;
|
||||
}
|
||||
}
|
||||
|
||||
.highlight{
|
||||
.mf,
|
||||
.mh,
|
||||
.mi,
|
||||
.mo,
|
||||
.il,
|
||||
.m {
|
||||
color: #945277;
|
||||
}
|
||||
|
||||
.s,
|
||||
.sb,
|
||||
.sc,
|
||||
.sd,
|
||||
.s2,
|
||||
.se,
|
||||
.sh,
|
||||
.si,
|
||||
.sx,
|
||||
.s1 {
|
||||
color: #df5000;
|
||||
}
|
||||
|
||||
.kc,
|
||||
.kd,
|
||||
.kn,
|
||||
.kp,
|
||||
.kr,
|
||||
.kt,
|
||||
.k,
|
||||
.o {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.kt {
|
||||
color: #458;
|
||||
}
|
||||
|
||||
.c,
|
||||
.cm,
|
||||
.c1 {
|
||||
color: #998;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.cp,
|
||||
.cs {
|
||||
color: #999;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.cs {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.n {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.na,
|
||||
.nv,
|
||||
.vc,
|
||||
.vg,
|
||||
.vi {
|
||||
color: #008080;
|
||||
}
|
||||
|
||||
.nb {
|
||||
color: #0086B3;
|
||||
}
|
||||
|
||||
.nc {
|
||||
color: #458;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.no {
|
||||
color: #094e99;
|
||||
}
|
||||
|
||||
.ni {
|
||||
color: #800080;
|
||||
}
|
||||
|
||||
.ne {
|
||||
color: #990000;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.nf {
|
||||
color: #945277;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.nn {
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.nt {
|
||||
color: #000080;
|
||||
}
|
||||
|
||||
.err {
|
||||
color: #a61717;
|
||||
background-color: #e3d2d2;
|
||||
}
|
||||
|
||||
.gd {
|
||||
color: #000;
|
||||
background-color: #fdd;
|
||||
|
||||
.x {
|
||||
color: #000;
|
||||
background-color: #faa;
|
||||
}
|
||||
}
|
||||
|
||||
.ge {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.gr {
|
||||
color: #aa0000;
|
||||
}
|
||||
|
||||
.gh {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.gi {
|
||||
color: #000;
|
||||
background-color: #dfd;
|
||||
|
||||
.x {
|
||||
color: #000;
|
||||
background-color: #afa;
|
||||
}
|
||||
}
|
||||
|
||||
.go {
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.gp {
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.gs {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.gu {
|
||||
color: #800080;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.gt {
|
||||
color: #aa0000;
|
||||
}
|
||||
|
||||
.ow {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.w {
|
||||
color: #bbb;
|
||||
}
|
||||
|
||||
.sr {
|
||||
color: #017936;
|
||||
}
|
||||
|
||||
.ss {
|
||||
color: #8b467f;
|
||||
}
|
||||
|
||||
.bp {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.gc {
|
||||
color: #999;
|
||||
background-color: #EAF2F5;
|
||||
}
|
||||
}
|
||||
|
||||
.octicon {
|
||||
font: normal normal 16px octicons-anchor;
|
||||
line-height: 1;
|
||||
display: inline-block;
|
||||
text-decoration: none;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.octicon-link {
|
||||
&:before {
|
||||
content: '\f05c';
|
||||
}
|
||||
}
|
||||
|
||||
.task-list-item {
|
||||
list-style-type: none;
|
||||
|
||||
+.task-list-item {
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
input {
|
||||
float: left;
|
||||
margin: 0.3em 0 0.25em -1.6em;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
}
|
||||
33
lib/GUI/css/responsive.less
Normal file
33
lib/GUI/css/responsive.less
Normal file
@@ -0,0 +1,33 @@
|
||||
@media (max-width: 992px) {
|
||||
.body {
|
||||
.main-header {
|
||||
.packages-header {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.body {
|
||||
.content {
|
||||
padding-top: @mainHeaderHeight + @packagesHeaderHeight + @smRegistryInfoHeight + 10;
|
||||
|
||||
.entry {
|
||||
.title {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.author {
|
||||
float: none !important;
|
||||
clear: both;
|
||||
padding: 0 0 5px 18px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.no-results {
|
||||
margin: 10px 0 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
189
lib/GUI/css/styles.less
Normal file
189
lib/GUI/css/styles.less
Normal file
@@ -0,0 +1,189 @@
|
||||
//vars
|
||||
@npmRed: #cc3d33;
|
||||
@white: #fff;
|
||||
@entryBg: #F3F3F3;
|
||||
@mainHeaderHeight: 50px;
|
||||
@packagesHeaderHeight: 60px;
|
||||
@headerBorderWidth: 2px;
|
||||
@smRegistryInfoHeight: 25px;
|
||||
|
||||
/*** Main Styles ***/
|
||||
.body {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left:0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
.main-header {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: @white;
|
||||
z-index: 1;
|
||||
|
||||
.navbar {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.npm-logo {
|
||||
width: 79px;
|
||||
height: @mainHeaderHeight;
|
||||
background-image: url( -/logo-sm );
|
||||
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAE8AAAAoCAYAAAC/xadkAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoTWFjaW50b3NoKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDozMDA4QUE4NzQ1QkMxMUU0QTVCMUYwQjdEREYxNENGMiIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDozMDA4QUE4ODQ1QkMxMUU0QTVCMUYwQjdEREYxNENGMiI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjMwMDhBQTg1NDVCQzExRTRBNUIxRjBCN0RERjE0Q0YyIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjMwMDhBQTg2NDVCQzExRTRBNUIxRjBCN0RERjE0Q0YyIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+ypkYZgAACelJREFUeNrsWmtsHFcZPXd2Zh9+r+31+5W4tSPSoKZUKgpIgdAWqorSULVFqJREKpVAAqRKhfIPKfCDtP2TKilpRRACUpBC+UlbQRG0RUmo0gBtWoyT2HHstR0/Yq/X9j5mh/PdmfX7te42tZyMdO2dmTt3vnvu+Z53lHOm9VcwQntRuvMq0qMKmTSgDNw4Fh4OG3Gxyh3E/l2GZOxVE0UtDShqLUbCLkb9o0D6KmAnbgA4D7cMcbMIXAUw+BoQ2QXEu5pMAjqB2q8Cl34BXDwC1D3IzkkgNUEAfTeAE+B8IWJhAl0vAKECoOa7wIVDEwbsaQUMA03fAMJbgJ5fAxlS1Crjg/Z1DhznbxbzB1l3+SWguBpo2cdzamd6wjBcdqXYeoHa+8m8T/PncSBJ5gUiHCB9nQLHefvLCRK1sPe3QNUtQAO1ElE2MWumcJHEg881iE43ULGbQIdJ0RNA5V1EuxWY7r++VFiAC5JlcRLqyp+olV8CSm938RGn4foDwyR2xowzcQy3Q8l24KYi6vVviHyc6ryTAPa5OLt/Nq9HddiCddTM94GxvwFbHwIKb6YpE+CEa9n5K2HewoOXMj1AAQfY9h2g8xi1mjpevYdATnoob+JQxEeHcOUNauYHQNtjVN0KFw8sgEppFyJQLmQTDWRmgP3LXQDPU+ejpK+/lNeTm5R9jlZEpEgQRbJse5z//ZxvdDFw7mHM5eHCe2wxtnqglfHfMEOZcJt7eVOyjzA4hGP0TaD8EZ7TWeKC5w+WfsBc1o7xVvpqCom+Dhi+KWCSIMbb+QLxzJnNCZ6iyo50w4l2kHRjCDYTHstw7eDi/ssxj51VGMOvnsK5b55Awc0RbwXObn6HQVW1Y2/DoMbu/PN+BBoreTm21LxJL0MtO5AiroKvMg0XY8fa/GGKIZ7AdCMzzatltEwJMkl4DsNZDJ7PoPOxYIQs/vZhuqsL9tRkbuwj5YNNTfCVlOhUJzM1hamLXbnPyW8huGWr+9uykIj2ITU8nD9NoJxmaSlVtYmWyeY71GpDk3mJtLGMN5kTM6ZJ5Rgan3gCwYZGYrB2m6eYxPQfP474++f072BjI5p/8EPK6qzZ78hzqZER9L34ol7U6YEBRO67D6W7djGDyo/9lXfE33sX/S8d50IVr7IoyrN5RSZ/rZLD2jbsiQlUP/wwAnX1OQs2+vpfMPbWmxTKD5MMrHrwodyJkUrj8pHDWuzk4ABKP/NZVD3wQF41dowL2/PcIQRqi9eGNxy1Ou/F9lFtk/396xIqk0jQa1nalgiL13Mk+nq9kJQpkeVnJHA17+ZOzIDi2Gu0BBKq5KdwlxocRCaVdGPNUAhWRcU6ihhpjnNF2x+HbDc5hq+gIOdx7PExpMfHKYihF91fVT0TVNjxONI0AbKQ8h4rEtELm7tXltGh8mJxz+3fh7fvuAOnb7sVnU8+uT52RaN454t348wX9uDUJ3dg+JVX1jVOz+HDOLnjFpzZvRv/2bsX9uTkzL0rf3wZJ7d/Au/cfRfO7Pk8ndfFnB2LDttsnZ7lx12Jatr0pPI/k0yuz2hnHGSmp8nglDthe30qLqYhMzkFm2NJmxvkZsT5UU5p2oQ4OWZLKpvKubltXmpNvqIi7eoFPF9haJ0xlqEdioCXIXi5q5M3TCCgZZGxfMXF85RLO62yMn1PwBMvmzt6oraGtnf5CZQcZ07L0zj4iMbJ0zuMDZdtCROyplht5FRQ6eh4w5SIxcMm6TREbRP0umK3Nmbdb149b4OsJW1cQVubdjhmRaW2WxuYefnztvk4AnV12Pn6X+es8Mau4Bj5CpI/kvragixnxfNVMqS8jLPYOq++vI4X8ftr61bIVdzUy6G9MoLzQxUjGOT15IciUqC+fsZDarWWKs1yYZOEIZRDZJay2twsxSoLu3GoNydhe/bwM9tw1hSjuhMx4RXWV07iTJjFJeh9/oiehBQJsgNI+qMsE+mxMZjhsBZoqqsLvUd/rstPkqolenrgr65m2hRb8T2Sr/a+cJTBcQYq4PdsswODk0+PjupSlDAl2FCvs4/U8NA8Wdx4sxDjb/2DC12r3y0LeumZpxn7BaH8FibOnkWQgIl9NcidS88+C7OyQsd7kx0d8NdUr4GNTtbmqZULV1IUMH0wykrR98tjFDYOwzTnsVLqdKGtrTr4FCESPd3ofOop9vNpMIPNzbAqIwR4fOX8mAB1HTigmSHsUToY5d+0rQPfUOtWLbevphZjb/wdg384MU+WbHYhwAkI8pycdx88qOWQUpq/shKBhgaXYYbCZRIi42UhIn9wSzMXZWIVAGeZt+wGUCbBRH1oElZVmGy4CrOonG3phN+OJXTLGvpATZM3G7LICripltQBVzCxFoUvaG/XoYrJxbLH4pxYcuaZ1FDMW3epcgdm37EoV3QY8swWSq2K2nkBciI6JJGGC0BpBCidZVQyOoJ03A+fOe3VLdVypFKzm96LeQCjKIRASx38RYNAMAJd2HcyOdt8J5FhTikfzJCJUk2WVRcm2PbMhBSZJQVPPb7YowQBLAkC4dA13KwTKNLwDfXAKG+nxgX1+fIlKdub4jwBZVIZVH55B8q3n2KHTwF1j7J7LDfwZD/AF8EHj/+MNuo0gi31SA0M6IqGZuHc9Mhwd6mUPwAf29T5KNqf/x4iX7mTxj96bcIWzXA6m77fQzlnYNSUecDEMb/a7ngZhiPfWBhz0CPSRg3/jUJ1H4WvSr5X2SfFHLaCHCchfUvd2hnBEhsorEsP9Hs2Zc5Y+hs4n1ZXfZ2q5yukkTfl+YlrFPM5btv6feL1O+B/zwHNj9DV1y7e/NYZhiXbZxmvWCDANQOxd4Gel4EIQSu+nzc6PPoa61CDKUxdGMDEQI/+YkvbkRWMcbJ/Qv+Pj/QgOTCqnweuFXhZAOnYCr8GlNO+d9L7N95DQt7GW5e8OSlPbXUZ3puQ2gIMvwZET/OBH5M0O9jnPS/9XW8KPIb6b9+J8J52WOFi1zuvpTI9EkPJ7S3Q38LhWsbx2Z1Ezrvyc7T1jUD3AaCK2hK5l7f6kN2OVE7nPf9F67fa9Nnl41xk2rUtBC4gmyBdEjp+SGGEsYydUAj3O8C1MkhqeYMuC/Bx7RdLwEzwJMC/8BMgRNmbvi4hO6E51mXCX8Yr9CqdT7sdt/3UEzgfwGWjoSGvrYcFH+dGu8z/snzEDbQ/QwAP0g4eAm76ERUxrJRz/t4hFLVUwLqVYcF+dpbv0CaxeC93qUTdWUD1jVQ2Ujn0XS2/TrtsA4P0UWpn4qR8rzhuYvRfHUhNtaG8OoZLjyk3FDFmH1ZzQpn5HwWpZZP42WtqWaHUjOxqaQeiVkv21Rr7L86gFoumlh5j7nnGRSLY5iDeX4jRf3b9X4ABADgctRMdEJWtAAAAAElFTkSuQmCC);
|
||||
background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
|
||||
>a {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.setup {
|
||||
line-height: 1.3em;
|
||||
padding-top: 5px;
|
||||
}
|
||||
|
||||
.packages-header {
|
||||
border-bottom: @headerBorderWidth solid #e6e6e6;
|
||||
|
||||
.search-container {
|
||||
top: 9px;
|
||||
|
||||
.search-icon {
|
||||
background: #e6e6e6;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sm-registry-info {
|
||||
height: @smRegistryInfoHeight;
|
||||
line-height: 1.7em;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
padding-top: @mainHeaderHeight + @packagesHeaderHeight + 10;
|
||||
|
||||
.entry {
|
||||
.transition(height .3s);
|
||||
padding: 9px 10px;
|
||||
overflow: hidden;
|
||||
border-bottom: 1px solid #E7E7E7;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
&:nth-child( even ) {
|
||||
background: @entryBg;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin: 0 0 5px 10px;
|
||||
}
|
||||
|
||||
.description {
|
||||
margin: 0 0 0 18px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.name:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.name:before {
|
||||
margin: 0;
|
||||
margin-left: -10px;
|
||||
.transformTransition(.2s);
|
||||
}
|
||||
|
||||
&.open .name:before {
|
||||
.rotate(90deg);
|
||||
}
|
||||
|
||||
.version {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.author {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.readme {
|
||||
font-size: 14px;
|
||||
margin-top: 10px;
|
||||
background: @white;
|
||||
padding: 10px 12px;
|
||||
.border-radius(3px);
|
||||
border: 1px solid darken( @entryBg, 10% );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.pkg-search-container {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.packages-container {
|
||||
.search-ajax {
|
||||
display: block;
|
||||
margin: 50px auto;
|
||||
}
|
||||
}
|
||||
|
||||
.no-results {
|
||||
text-align: center;
|
||||
margin: 50px 0;
|
||||
color: #888;
|
||||
|
||||
big {
|
||||
font-size: 38px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
code {
|
||||
font-size: 1.2em;
|
||||
}
|
||||
}
|
||||
|
||||
.red {
|
||||
color: @npmRed;
|
||||
}
|
||||
|
||||
.light-red {
|
||||
color: lighten( @npmRed, 10% );
|
||||
}
|
||||
|
||||
.white {
|
||||
color: @white;
|
||||
}
|
||||
|
||||
.red-bg {
|
||||
background: @npmRed;
|
||||
}
|
||||
|
||||
.light-red-bg {
|
||||
background: lighten( @npmRed, 10% );
|
||||
}
|
||||
|
||||
.no-bg {
|
||||
background: none;
|
||||
}
|
||||
|
||||
.no-border {
|
||||
border: none;
|
||||
}
|
||||
.no-rnd-cnr {
|
||||
.border-radius( 0 );
|
||||
}
|
||||
|
||||
.center {
|
||||
text-align: center;
|
||||
}
|
||||
20
lib/GUI/entry.hbs
Normal file
20
lib/GUI/entry.hbs
Normal file
@@ -0,0 +1,20 @@
|
||||
<div class="entry" data-name="{{ name }}" data-version="{{ version }}">
|
||||
<div class="row">
|
||||
<div class="col-md-8 col-sm-8">
|
||||
<h4 class="title">
|
||||
<a class='name icon-angle-right red' href='javascript:void(0)'>{{ name }}</a>
|
||||
<small class='version'>v{{ version }}</small>
|
||||
</h4>
|
||||
</div>
|
||||
<div class="col-md-4 col-sm-4">
|
||||
<div class="author pull-right">
|
||||
<small>By: {{ _npmUser.name }}</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<p class="description">{{ description }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
65
lib/GUI/index.hbs
Normal file
65
lib/GUI/index.hbs
Normal file
@@ -0,0 +1,65 @@
|
||||
<!doctype html>
|
||||
<html lang="en-us">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>{{ name }}</title>
|
||||
|
||||
<link rel="icon" type="image/ico" href="{{ baseUrl }}/-/static/favicon.ico"/>
|
||||
<link rel="stylesheet" type="text/css" href="{{ baseUrl }}/-/static/main.css">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
</head>
|
||||
<body class="body">
|
||||
<header class="main-header">
|
||||
<nav class="navbar navbar-default red-bg white no-border no-rnd-cnr" role="navigation">
|
||||
<div class="container">
|
||||
<div class="npm-logo navbar-left">
|
||||
<a href="{{ baseUrl }}"></a>
|
||||
</div>
|
||||
|
||||
<div class="navbar-right setup hidden-xs">
|
||||
<code class="white no-bg">npm set registry {{ baseUrl }}</code><br>
|
||||
<code class="white no-bg">npm adduser --registry {{ baseUrl }}</code>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<header class="sm-registry-info light-red-bg center hidden-sm hidden-lg hidden-md">
|
||||
<code class="white no-bg">{{ baseUrl }}</code><br>
|
||||
</header>
|
||||
<header class="packages-header container">
|
||||
<div class="row">
|
||||
<div class="col-md-5 hidden-xs hidden-sm">
|
||||
<h2 class="title">Available Packages</h2>
|
||||
</div>
|
||||
<div class="col-md-4 col-md-offset-3 col-sm-12">
|
||||
<form id='search-form'>
|
||||
<div class="input-group input-group-lg search-container">
|
||||
<input type="text" class="form-control" placeholder="Search for packages">
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-default search-icon js-search-btn"><i class="icon-search"></i></button>
|
||||
</span>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
</header>
|
||||
|
||||
<section class="content container packages-container" id="all-packages">
|
||||
{{#each packages}}
|
||||
{{> entry}}
|
||||
{{/each}}
|
||||
|
||||
{{#unless packages.length}}
|
||||
<div class='no-results'>
|
||||
<big>No Packages</big><br>
|
||||
Use <code>npm publish</code>
|
||||
</div>
|
||||
{{/unless}}
|
||||
</section>
|
||||
|
||||
<section class="content container pkg-search-container" id="search-results"></section>
|
||||
|
||||
<script src="{{ baseUrl }}/-/static/jquery.min.js"></script>
|
||||
<script type='text/javascript' src='{{ baseUrl }}/-/static/main.js'></script>
|
||||
</body>
|
||||
</html>
|
||||
71
lib/GUI/js/entry.js
Normal file
71
lib/GUI/js/entry.js
Normal file
@@ -0,0 +1,71 @@
|
||||
var $ = require('unopinionate').selector,
|
||||
onClick = require('onclick'),
|
||||
transitionComplete = require('transition-complete');
|
||||
|
||||
$(function() {
|
||||
onClick('.entry .name', function() {
|
||||
var $this = $(this),
|
||||
$entry = $this.closest('.entry');
|
||||
|
||||
//Close entry
|
||||
if($entry.hasClass('open')) {
|
||||
$entry
|
||||
.height($entry.outerHeight())
|
||||
.removeClass('open');
|
||||
|
||||
setTimeout(function() {
|
||||
$entry.css('height', $entry.attr('data-height') + 'px');
|
||||
}, 0);
|
||||
|
||||
transitionComplete(function() {
|
||||
$entry.find('.readme').remove();
|
||||
$entry.css('height', 'auto');
|
||||
});
|
||||
}
|
||||
//Open entry
|
||||
else {
|
||||
//Close open entries
|
||||
$('.entry.open').each(function() {
|
||||
var $entry = $(this);
|
||||
$entry
|
||||
.height($entry.outerHeight())
|
||||
.removeClass('open');
|
||||
|
||||
setTimeout(function() {
|
||||
$entry.css('height', $entry.attr('data-height') + 'px');
|
||||
}, 0);
|
||||
|
||||
transitionComplete(function() {
|
||||
$entry.find('.readme').remove();
|
||||
$entry.css('height', 'auto');
|
||||
});
|
||||
});
|
||||
|
||||
//Add the open class
|
||||
$entry.addClass('open');
|
||||
|
||||
//Explicitly set heights for transitions
|
||||
var height = $entry.outerHeight();
|
||||
$entry
|
||||
.attr('data-height', height)
|
||||
.css('height', height);
|
||||
|
||||
//Get the data
|
||||
$.ajax({
|
||||
url: '-/readme/'+$entry.attr('data-name')+'/'+$entry.attr('data-version'),
|
||||
dataType: 'text',
|
||||
success: function(html) {
|
||||
var $readme = $("<div class='readme'>")
|
||||
.html(html)
|
||||
.appendTo($entry);
|
||||
|
||||
$entry.height(height + $readme.outerHeight());
|
||||
|
||||
transitionComplete(function() {
|
||||
$entry.css('height', 'auto');
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
2
lib/GUI/js/main.js
Normal file
2
lib/GUI/js/main.js
Normal file
@@ -0,0 +1,2 @@
|
||||
require('./search');
|
||||
require('./entry');
|
||||
81
lib/GUI/js/search.js
Normal file
81
lib/GUI/js/search.js
Normal file
@@ -0,0 +1,81 @@
|
||||
var $ = require('unopinionate').selector,
|
||||
template = require('../entry.hbs'),
|
||||
onScroll = require('onscroll');
|
||||
|
||||
$(function() {
|
||||
'use strict';
|
||||
|
||||
(function( window, document ) {
|
||||
|
||||
var $form = $('#search-form');
|
||||
var $input = $form.find('input');
|
||||
var $body = $('body');
|
||||
var $clear = $form.find('.clear');
|
||||
var $searchResults = $("#search-results");
|
||||
var $pkgListing = $("#all-packages");
|
||||
var $searchBtn = $('.js-search-btn');
|
||||
var request;
|
||||
var currentResults;
|
||||
|
||||
var toggle = function( validQuery ) {
|
||||
$searchResults.toggleClass( 'show', validQuery );
|
||||
$pkgListing.toggleClass( 'hide', validQuery );
|
||||
|
||||
|
||||
$searchBtn.find('i').toggleClass( 'icon-cancel', validQuery );
|
||||
$searchBtn.find('i').toggleClass( 'icon-search', !validQuery );
|
||||
};
|
||||
|
||||
$form.bind('submit keyup', function(e) {
|
||||
var q, qBool;
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
q = $input.val();
|
||||
qBool = q !== '';
|
||||
|
||||
toggle( qBool );
|
||||
|
||||
if( !qBool ) {
|
||||
if( request && typeof request.abort === 'function' ) {
|
||||
request.abort();
|
||||
}
|
||||
|
||||
currentResults = null;
|
||||
$searchResults.html('');
|
||||
return;
|
||||
}
|
||||
|
||||
if( request && typeof request.abort === 'function' ) {
|
||||
request.abort();
|
||||
}
|
||||
|
||||
if( !currentResults ) {
|
||||
$searchResults.html( "<img class='search-ajax' src='-/static/ajax.gif' alt='Spinner'/>" );
|
||||
}
|
||||
|
||||
request = $.getJSON('-/search/' + q, function( results ) {
|
||||
currentResults = results;
|
||||
|
||||
if( results.length > 0 ) {
|
||||
var html = '';
|
||||
|
||||
$.each(results, function( i, entry ) {
|
||||
html += template( entry );
|
||||
});
|
||||
|
||||
$searchResults.html(html);
|
||||
} else {
|
||||
$searchResults.html("<div class='no-results'><big>No Results</big></div>");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$( document ).on( 'click', '.icon-cancel', function( e ) {
|
||||
e.preventDefault();
|
||||
$input.val('');
|
||||
$form.keyup();
|
||||
});
|
||||
|
||||
})( window, window.document );
|
||||
});
|
||||
185
lib/auth.js
Normal file
185
lib/auth.js
Normal file
@@ -0,0 +1,185 @@
|
||||
var Path = require('path')
|
||||
, crypto = require('crypto')
|
||||
, Error = require('http-errors')
|
||||
, Logger = require('./logger')
|
||||
, assert = require('assert')
|
||||
|
||||
module.exports = Auth
|
||||
|
||||
function Auth(config) {
|
||||
if (!(this instanceof Auth)) return new Auth(config)
|
||||
this.config = config
|
||||
this.logger = Logger.logger.child({sub: 'auth'})
|
||||
var stuff = {
|
||||
config: config,
|
||||
logger: this.logger,
|
||||
}
|
||||
|
||||
if (config.users_file) {
|
||||
if (!config.auth || !config.auth.htpasswd) {
|
||||
// b/w compat
|
||||
config.auth = config.auth || {}
|
||||
config.auth.htpasswd = {file: config.users_file}
|
||||
}
|
||||
}
|
||||
|
||||
this.plugins = Object.keys(config.auth || {}).map(function(p) {
|
||||
var plugin, name
|
||||
try {
|
||||
name = 'sinopia-' + p
|
||||
plugin = require(name)
|
||||
} catch(x) {
|
||||
try {
|
||||
name = p
|
||||
plugin = require(name)
|
||||
} catch(x) {}
|
||||
}
|
||||
|
||||
if (plugin == null) {
|
||||
throw Error('"' + p + '" auth plugin not found\n'
|
||||
+ 'try "npm install sinopia-' + p + '"')
|
||||
}
|
||||
|
||||
if (typeof(plugin) !== 'function')
|
||||
throw Error('"' + name + '" doesn\'t look like a valid auth plugin')
|
||||
|
||||
plugin = plugin(config.auth[p], stuff)
|
||||
|
||||
if (plugin == null || typeof(plugin.authenticate) !== 'function')
|
||||
throw Error('"' + name + '" doesn\'t look like a valid auth plugin')
|
||||
|
||||
return plugin
|
||||
})
|
||||
|
||||
this.plugins.unshift({
|
||||
authenticate: function(user, password, cb) {
|
||||
if (config.users != null
|
||||
&& config.users[user] != null
|
||||
&& (crypto.createHash('sha1').update(password).digest('hex')
|
||||
=== config.users[user].password)
|
||||
) {
|
||||
return cb(null, [ user ])
|
||||
}
|
||||
|
||||
return cb()
|
||||
},
|
||||
|
||||
adduser: function(user, password, cb) {
|
||||
if (config.users && config.users[user])
|
||||
return cb(Error[403]('this user already exists'))
|
||||
|
||||
return cb()
|
||||
},
|
||||
})
|
||||
|
||||
this.plugins.push({
|
||||
authenticate: function(user, password, cb) {
|
||||
return cb(Error[403]('bad username/password, access denied'))
|
||||
},
|
||||
|
||||
adduser: function(user, password, cb) {
|
||||
return cb(Error[409]('registration is disabled'))
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
Auth.prototype.authenticate = function(user, password, cb) {
|
||||
var plugins = this.plugins.slice(0)
|
||||
|
||||
!function next() {
|
||||
var p = plugins.shift()
|
||||
p.authenticate(user, password, function(err, groups) {
|
||||
if (err || groups) return cb(err, groups)
|
||||
next()
|
||||
})
|
||||
}()
|
||||
}
|
||||
|
||||
Auth.prototype.add_user = function(user, password, cb) {
|
||||
var plugins = this.plugins.slice(0)
|
||||
|
||||
!function next() {
|
||||
var p = plugins.shift()
|
||||
var n = 'adduser'
|
||||
if (typeof(p[n]) !== 'function') {
|
||||
n = 'add_user'
|
||||
}
|
||||
if (typeof(p[n]) !== 'function') {
|
||||
next()
|
||||
} else {
|
||||
p[n](user, password, function(err, ok) {
|
||||
if (err || ok) return cb(err, ok)
|
||||
next()
|
||||
})
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
Auth.prototype.middleware = function() {
|
||||
var self = this
|
||||
return function(req, res, _next) {
|
||||
req.pause()
|
||||
function next(err) {
|
||||
req.resume()
|
||||
// uncomment this to reject users with bad auth headers
|
||||
//return _next.apply(null, arguments)
|
||||
|
||||
// swallow error, user remains unauthorized
|
||||
// set remoteUserError to indicate that user was attempting authentication
|
||||
if (err) req.remote_user.error = err.message
|
||||
return _next()
|
||||
}
|
||||
|
||||
if (req.remote_user != null) return next()
|
||||
req.remote_user = AnonymousUser()
|
||||
|
||||
var authorization = req.headers.authorization
|
||||
if (authorization == null) return next()
|
||||
|
||||
var parts = authorization.split(' ')
|
||||
|
||||
if (parts.length !== 2) return next({
|
||||
status: 400,
|
||||
message: 'bad authorization header',
|
||||
})
|
||||
|
||||
var scheme = parts[0]
|
||||
, credentials = new Buffer(parts[1], 'base64').toString()
|
||||
, index = credentials.indexOf(':')
|
||||
|
||||
if (scheme !== 'Basic' || index < 0) return next({
|
||||
status: 400,
|
||||
message: 'bad authorization header',
|
||||
})
|
||||
|
||||
var user = credentials.slice(0, index)
|
||||
, pass = credentials.slice(index + 1)
|
||||
|
||||
self.authenticate(user, pass, function(err, groups) {
|
||||
if (!err && groups != null && groups != false) {
|
||||
req.remote_user = AuthenticatedUser(user, groups)
|
||||
next()
|
||||
} else {
|
||||
req.remote_user = AnonymousUser()
|
||||
next(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function AnonymousUser() {
|
||||
return {
|
||||
name: undefined,
|
||||
// groups without '@' are going to be deprecated eventually
|
||||
groups: ['$all', '$anonymous', '@all', '@anonymous', 'all', 'undefined', 'anonymous'],
|
||||
}
|
||||
}
|
||||
|
||||
function AuthenticatedUser(name, groups) {
|
||||
groups = groups.concat(['$all', '$authenticated', '@all', '@authenticated', 'all'])
|
||||
return {
|
||||
name: name,
|
||||
groups: groups,
|
||||
}
|
||||
}
|
||||
|
||||
64
lib/cli.js
64
lib/cli.js
@@ -1,5 +1,19 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/*eslint no-sync:0*/
|
||||
|
||||
if (process.getuid && process.getuid() === 0) {
|
||||
global.console.error("Sinopia doesn't need superuser privileges. Don't run it under root.")
|
||||
}
|
||||
|
||||
process.title = 'sinopia'
|
||||
|
||||
try {
|
||||
// for debugging memory leaks
|
||||
// totally optional
|
||||
require('heapdump')
|
||||
} catch(err){}
|
||||
|
||||
var logger = require('./logger')
|
||||
logger.setup() // default setup
|
||||
|
||||
@@ -9,7 +23,8 @@ var pkg_file = '../package.yaml'
|
||||
, commander = require('commander')
|
||||
, server = require('./index')
|
||||
, crypto = require('crypto')
|
||||
, pkg = require(pkg_file)
|
||||
, Path = require('path')
|
||||
, pkg = yaml.safeLoad(fs.readFileSync(__dirname + '/' + pkg_file, 'utf8'))
|
||||
|
||||
commander
|
||||
.option('-l, --listen <[host:]port>', 'host:port number to listen on (default: localhost:4873)')
|
||||
@@ -39,7 +54,7 @@ try {
|
||||
var readline = require('readline')
|
||||
var rl = readline.createInterface(process.stdin, process.stdout)
|
||||
var timeout = setTimeout(function() {
|
||||
console.log('I got tired waiting for an answer. Exitting...')
|
||||
global.console.log('I got tired waiting for an answer. Exitting...')
|
||||
process.exit(1)
|
||||
}, 20000)
|
||||
|
||||
@@ -57,7 +72,7 @@ try {
|
||||
afterConfigLoad()
|
||||
} else if (x[0] == 'N' || x[0] == 'n') {
|
||||
rl.close()
|
||||
console.log('So, you just accidentally run me in a wrong folder. Exitting...')
|
||||
global.console.log('So, you just accidentally run me in a wrong folder. Exitting...')
|
||||
process.exit(1)
|
||||
} else {
|
||||
askUser()
|
||||
@@ -89,13 +104,19 @@ function get_hostport() {
|
||||
|
||||
function afterConfigLoad() {
|
||||
if (!config.user_agent) config.user_agent = 'Sinopia/'+pkg.version
|
||||
if (!config.self_path) config.self_path = config_path
|
||||
if (!config.self_path) config.self_path = Path.resolve(config_path)
|
||||
|
||||
logger.setup(config.logs)
|
||||
|
||||
var hostport = get_hostport()
|
||||
server(config).listen(hostport[1], hostport[0])
|
||||
logger.logger.warn({addr: 'http://'+hostport[0]+':'+hostport[1]+'/'}, 'Server is listening on @{addr}')
|
||||
server(config)
|
||||
.listen(hostport[1], hostport[0])
|
||||
.on('error', function(err) {
|
||||
logger.logger.fatal({err: err}, 'cannot create server: @{err.message}')
|
||||
process.exit(2)
|
||||
})
|
||||
|
||||
logger.logger.warn({addr: 'http://'+hostport[0]+':'+hostport[1]+'/', version: 'Sinopia/'+pkg.version}, 'Server is listening on @{addr}')
|
||||
|
||||
// undocumented stuff for tests
|
||||
if (typeof(process.send) === 'function') {
|
||||
@@ -105,17 +126,24 @@ function afterConfigLoad() {
|
||||
|
||||
function write_config_banner(def, config) {
|
||||
var hostport = get_hostport()
|
||||
console.log('===========================================================')
|
||||
console.log(' Creating a new configuration file: "%s"', config_path)
|
||||
console.log(' ')
|
||||
console.log(' If you want to setup npm to work with this registry,')
|
||||
console.log(' run following commands:')
|
||||
console.log(' ')
|
||||
console.log(' $ npm set registry http://%s:%s/', hostport[0], hostport[1])
|
||||
console.log(' $ npm set always-auth true')
|
||||
console.log(' $ npm adduser')
|
||||
console.log(' Username: %s', def.user)
|
||||
console.log(' Password: %s', def.pass)
|
||||
console.log('===========================================================')
|
||||
var log = global.console.log
|
||||
|
||||
log('===========================================================')
|
||||
log(' Creating a new configuration file: "%s"', config_path)
|
||||
log(' ')
|
||||
log(' If you want to setup npm to work with this registry,')
|
||||
log(' run following commands:')
|
||||
log(' ')
|
||||
log(' $ npm set registry http://%s:%s/', hostport[0], hostport[1])
|
||||
log(' $ npm set always-auth true')
|
||||
log(' $ npm adduser')
|
||||
log(' Username: %s', def.user)
|
||||
log(' Password: %s', def.pass)
|
||||
log('===========================================================')
|
||||
}
|
||||
|
||||
process.on('uncaughtException', function(err) {
|
||||
logger.logger.fatal({err: err}, 'uncaught exception, please report this\n@{err.stack}')
|
||||
process.exit(255)
|
||||
})
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
var assert = require('assert')
|
||||
, crypto = require('crypto')
|
||||
, Path = require('path')
|
||||
, minimatch = require('minimatch')
|
||||
, Error = require('http-errors')
|
||||
, LocalList = require('./local-list')
|
||||
, utils = require('./utils')
|
||||
|
||||
// [[a, [b, c]], d] -> [a, b, c, d]
|
||||
@@ -26,11 +29,17 @@ function Config(config) {
|
||||
assert.equal(typeof(config), 'object', 'CONFIG: this doesn\'t look like a valid config file')
|
||||
|
||||
assert(this.storage, 'CONFIG: storage path not defined')
|
||||
this.localList = LocalList(
|
||||
Path.join(
|
||||
Path.resolve(Path.dirname(this.self_path), this.storage),
|
||||
'.sinopia-db.json'
|
||||
)
|
||||
)
|
||||
|
||||
var users = {all:true, anonymous:true, 'undefined':true, owner:true, none:true}
|
||||
|
||||
var check_user_or_uplink = function(arg) {
|
||||
assert(arg !== 'all' || arg !== 'owner' || arg !== 'anonymous' || arg !== 'undefined' || arg !== 'none', 'CONFIG: reserved user/uplink name: ' + arg)
|
||||
assert(arg !== 'all' && arg !== 'owner' && arg !== 'anonymous' && arg !== 'undefined' && arg !== 'none', 'CONFIG: reserved user/uplink name: ' + arg)
|
||||
assert(!arg.match(/\s/), 'CONFIG: invalid user name: ' + arg)
|
||||
assert(users[arg] == null, 'CONFIG: duplicate user/uplink name: ' + arg)
|
||||
users[arg] = true
|
||||
@@ -47,7 +56,7 @@ function Config(config) {
|
||||
for (var i in this.users) {
|
||||
assert(this.users[i].password, 'CONFIG: no password for user: ' + i)
|
||||
assert(
|
||||
typeof(this.users[i].password) === 'string' &&
|
||||
typeof(this.users[i].password) === 'string' &&
|
||||
this.users[i].password.match(/^[a-f0-9]{40}$/)
|
||||
, 'CONFIG: wrong password format for user: ' + i + ', sha1 expected')
|
||||
}
|
||||
@@ -73,11 +82,6 @@ function Config(config) {
|
||||
Array.isArray(hash[action])
|
||||
, 'CONFIG: bad "'+i+'" package '+action+' description (array or string expected)')
|
||||
hash[action] = flatten(hash[action])
|
||||
hash[action].forEach(function(user) {
|
||||
assert(
|
||||
users[user] != null
|
||||
, 'CONFIG: "'+i+'" package: user "'+user+'" doesn\'t exist')
|
||||
})
|
||||
}
|
||||
|
||||
for (var i in this.packages) {
|
||||
@@ -104,27 +108,30 @@ function Config(config) {
|
||||
}
|
||||
}).bind(this))
|
||||
|
||||
// unique identifier of this server (or a cluster), used to avoid loops
|
||||
if (!this.server_id) {
|
||||
this.server_id = crypto.pseudoRandomBytes(6).toString('hex')
|
||||
}
|
||||
|
||||
if (this.ignore_latest_tag == null) this.ignore_latest_tag = false
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
function allow_action(package, who, action) {
|
||||
for (var i in this.packages) {
|
||||
if (minimatch.makeRe(i).exec(package)) {
|
||||
return this.packages[i][action].reduce(function(prev, curr) {
|
||||
if (curr === String(who) || curr === 'all') return true
|
||||
return prev
|
||||
}, false)
|
||||
}
|
||||
}
|
||||
return false
|
||||
return (this.get_package_setting(package, action) || []).reduce(function(prev, curr) {
|
||||
if (typeof(who) === 'string' && curr === who) return true
|
||||
if (Array.isArray(who) && who.indexOf(curr) !== -1) return true
|
||||
return prev
|
||||
}, false)
|
||||
}
|
||||
|
||||
Config.prototype.allow_access = function(package, user) {
|
||||
return allow_action.call(this, package, user, 'allow_access') || allow_action.call(this, package, user, 'access')
|
||||
return allow_action.call(this, package, user.groups, 'allow_access') || allow_action.call(this, package, user.groups, 'access')
|
||||
}
|
||||
|
||||
Config.prototype.allow_publish = function(package, user) {
|
||||
return allow_action.call(this, package, user, 'allow_publish') || allow_action.call(this, package, user, 'publish')
|
||||
return allow_action.call(this, package, user.groups, 'allow_publish') || allow_action.call(this, package, user.groups, 'publish')
|
||||
}
|
||||
|
||||
Config.prototype.proxy_access = function(package, uplink) {
|
||||
@@ -132,13 +139,49 @@ Config.prototype.proxy_access = function(package, uplink) {
|
||||
}
|
||||
|
||||
Config.prototype.proxy_publish = function(package, uplink) {
|
||||
return allow_action.call(this, package, uplink, 'proxy_publish')
|
||||
throw new Error('deprecated')
|
||||
//return allow_action.call(this, package, uplink, 'proxy_publish')
|
||||
}
|
||||
|
||||
Config.prototype.authenticate = function(user, password) {
|
||||
if (this.users[user] == null) return false
|
||||
return crypto.createHash('sha1').update(password).digest('hex') === this.users[user].password
|
||||
Config.prototype.get_package_setting = function(package, setting) {
|
||||
for (var i in this.packages) {
|
||||
if (minimatch.makeRe(i).exec(package)) {
|
||||
return this.packages[i][setting]
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
module.exports = Config
|
||||
|
||||
var parse_interval_table = {
|
||||
'': 1000,
|
||||
ms: 1,
|
||||
s: 1000,
|
||||
m: 60*1000,
|
||||
h: 60*60*1000,
|
||||
d: 86400000,
|
||||
w: 7*86400000,
|
||||
M: 30*86400000,
|
||||
y: 365*86400000,
|
||||
}
|
||||
|
||||
module.exports.parse_interval = function(interval) {
|
||||
if (typeof(interval) === 'number') return interval * 1000
|
||||
|
||||
var result = 0
|
||||
var last_suffix = Infinity
|
||||
interval.split(/\s+/).forEach(function(x) {
|
||||
if (!x) return
|
||||
var m = x.match(/^((0|[1-9][0-9]*)(\.[0-9]+)?)(ms|s|m|h|d|w|M|y|)$/)
|
||||
if (!m
|
||||
|| parse_interval_table[m[4]] >= last_suffix
|
||||
|| (m[4] === '' && last_suffix !== Infinity)) {
|
||||
throw new Error('invalid interval: ' + interval)
|
||||
}
|
||||
last_suffix = parse_interval_table[m[4]]
|
||||
result += Number(m[1]) * parse_interval_table[m[4]]
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
@@ -2,42 +2,75 @@
|
||||
storage: ./storage
|
||||
|
||||
# a list of users
|
||||
#
|
||||
# This could be deprecated soon, use auth plugins instead (see htpasswd below).
|
||||
users:
|
||||
admin:
|
||||
# crypto.createHash('sha1').update(pass).digest('hex')
|
||||
password: __PASSWORD__
|
||||
|
||||
web:
|
||||
# web interface is disabled by default in 0.x, will be enabled soon in 1.x
|
||||
# when all its issues will be fixed
|
||||
#
|
||||
# set this to `true` if you want to experiment with web ui now;
|
||||
# this has a lot of issues, e.g. no auth yet, so use at your own risk
|
||||
#enable: true
|
||||
|
||||
title: Sinopia
|
||||
# logo: logo.png
|
||||
|
||||
auth:
|
||||
htpasswd:
|
||||
file: ./htpasswd
|
||||
# Maximum amount of users allowed to register, defaults to "+inf".
|
||||
# You can set this to -1 to disable registration.
|
||||
#max_users: 1000
|
||||
|
||||
# a list of other known repositories we can talk to
|
||||
uplinks:
|
||||
npmjs:
|
||||
url: https://registry.npmjs.org/
|
||||
|
||||
# maximum time (in seconds) in which data is considered up to date
|
||||
# amount of time to wait for repository to respond
|
||||
# before giving up and use the local cached copy
|
||||
#timeout: 30s
|
||||
|
||||
# maximum time in which data is considered up to date
|
||||
#
|
||||
# default is 2 minutes, so server won't request the same data from
|
||||
# uplink if a similar request was made less than 2 minutes ago
|
||||
#maxage: 120
|
||||
#maxage: 2m
|
||||
|
||||
# if two subsequent requests fail, no further requests will be sent to
|
||||
# this uplink for five minutes
|
||||
#max_fails: 2
|
||||
#fail_timeout: 5m
|
||||
|
||||
# timeouts are defined in the same way as nginx, see:
|
||||
# http://wiki.nginx.org/ConfigNotation
|
||||
|
||||
packages:
|
||||
# uncomment this for packages with "local-" prefix to be available
|
||||
# uncomment this for packages with "local-" prefix to be available
|
||||
# for admin only, it's a recommended way of handling private packages
|
||||
#'local-*':
|
||||
# allow_access: admin
|
||||
# allow_publish: admin
|
||||
# # you can override storage directory for a group of packages this way:
|
||||
# storage: 'local_storage'
|
||||
|
||||
'*':
|
||||
# allow all users to read packages ('all' is a keyword)
|
||||
# this includes non-authenticated users
|
||||
allow_access: all
|
||||
# allow all users to read packages (including non-authenticated users)
|
||||
#
|
||||
# you can specify usernames/groupnames (depending on your auth plugin)
|
||||
# and three keywords: "$all", "$anonymous", "$authenticated"
|
||||
allow_access: $all
|
||||
|
||||
# allow 'admin' to publish packages
|
||||
allow_publish: admin
|
||||
|
||||
# if package is not available locally, proxy requests to 'npmjs' registry
|
||||
proxy_access: npmjs
|
||||
|
||||
# when package is published locally, also push it to remote registry
|
||||
#proxy_publish: none
|
||||
proxy: npmjs
|
||||
|
||||
#####################################################################
|
||||
# Advanced settings
|
||||
@@ -67,3 +100,12 @@ logs:
|
||||
#https_proxy: https://something.local/
|
||||
#no_proxy: localhost,127.0.0.1
|
||||
|
||||
# maximum size of uploaded json document
|
||||
# increase it if you have "request entity too large" errors
|
||||
#max_body_size: 1mb
|
||||
|
||||
# Workaround for countless npm bugs. Must have for npm <1.14.x, but expect
|
||||
# it to be turned off in future versions. If `true`, latest tag is ignored,
|
||||
# and the highest semver is placed instead.
|
||||
#ignore_latest_tag: false
|
||||
|
||||
|
||||
@@ -4,7 +4,10 @@ var fs = require('fs')
|
||||
module.exports = function create_config() {
|
||||
var pass = crypto.randomBytes(8).toString('base64').replace(/[=+\/]/g, '')
|
||||
, pass_digest = crypto.createHash('sha1').update(pass).digest('hex')
|
||||
, config = fs.readFileSync(require.resolve('./config_def.yaml'), 'utf8')
|
||||
|
||||
/*eslint no-sync:0*/
|
||||
var config = fs.readFileSync(require.resolve('./config_def.yaml'), 'utf8')
|
||||
|
||||
config = config.replace('__PASSWORD__', pass_digest)
|
||||
|
||||
return {
|
||||
|
||||
62
lib/error.js
62
lib/error.js
@@ -1,62 +0,0 @@
|
||||
var util = require('util')
|
||||
, utils = require('./utils')
|
||||
|
||||
function parse_error_params(params, status, msg) {
|
||||
if (typeof(params) === 'string') {
|
||||
return {
|
||||
msg: params,
|
||||
status: status,
|
||||
}
|
||||
} else if (typeof(params) === 'number') {
|
||||
return {
|
||||
msg: msg,
|
||||
status: params,
|
||||
}
|
||||
} else if (utils.is_object(params)) {
|
||||
if (params.msg == null) params.msg = msg
|
||||
if (params.status == null) params.status = status
|
||||
return params
|
||||
} else {
|
||||
return {
|
||||
msg: msg,
|
||||
status: status,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Errors caused by malfunctioned code
|
||||
*/
|
||||
var AppError = function(params, constr) {
|
||||
Error.captureStackTrace(this, constr || this)
|
||||
params = parse_error_params(params, 500, 'Internal server error')
|
||||
this.msg = params.msg
|
||||
this.status = params.status
|
||||
}
|
||||
util.inherits(AppError, Error)
|
||||
AppError.prototype.name = 'Application Error'
|
||||
|
||||
/*
|
||||
* Errors caused by wrong request
|
||||
*/
|
||||
var UserError = function(params, constr) {
|
||||
params = parse_error_params(params, 404, 'The requested resource was not found')
|
||||
this.msg = params.msg
|
||||
this.status = params.status
|
||||
}
|
||||
util.inherits(UserError, Error)
|
||||
UserError.prototype.name = 'User Error'
|
||||
|
||||
/*
|
||||
* Mimic filesystem errors
|
||||
*/
|
||||
var FSError = function(code) {
|
||||
this.code = code
|
||||
}
|
||||
util.inherits(UserError, Error)
|
||||
UserError.prototype.name = 'FS Error'
|
||||
|
||||
module.exports.AppError = AppError
|
||||
module.exports.UserError = UserError
|
||||
module.exports.FSError = FSError
|
||||
|
||||
85
lib/index-web.js
Normal file
85
lib/index-web.js
Normal file
@@ -0,0 +1,85 @@
|
||||
var fs = require('fs')
|
||||
var marked = require('marked')
|
||||
var search = require('./search')
|
||||
var Handlebars = require('handlebars')
|
||||
var Error = require('http-errors')
|
||||
|
||||
module.exports = function(app, config, storage) {
|
||||
search.configureStorage(storage)
|
||||
|
||||
Handlebars.registerPartial('entry', fs.readFileSync(require.resolve('./GUI/entry.hbs'), 'utf8'));
|
||||
var template = Handlebars.compile(fs.readFileSync(require.resolve('./GUI/index.hbs'), 'utf8'));
|
||||
|
||||
app.get('/', function(req, res, next) {
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
|
||||
storage.get_local(function(err, packages) {
|
||||
res.send(template({
|
||||
name: config.web.title || "Sinopia",
|
||||
packages: packages,
|
||||
baseUrl: config.url_prefix || req.protocol + '://' + req.get('host')
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
// Static
|
||||
app.get('/-/static/:filename', function(req, res, next) {
|
||||
var file = __dirname + '/static/' + req.params.filename
|
||||
fs.exists(file, function(exists) {
|
||||
if (exists) {
|
||||
res.sendfile(file)
|
||||
} else {
|
||||
res.status(404);
|
||||
res.send("File Not Found")
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
app.get('/-/logo', function(req, res, next) {
|
||||
res.sendfile(config.web.logo ? config.web.logo : __dirname + "/static/logo.png")
|
||||
})
|
||||
|
||||
app.get('/-/logo-sm', function(req, res, next) {
|
||||
res.sendfile(config.web.logosm ? config.web.logosm : __dirname + "/static/logo-sm.png")
|
||||
})
|
||||
|
||||
// Search
|
||||
app.get('/-/search/:anything', function(req, res, next) {
|
||||
var results = search.query(req.params.anything),
|
||||
packages = []
|
||||
|
||||
var getData = function(i) {
|
||||
storage.get_package(results[i].ref, function(err, entry) {
|
||||
if (entry) {
|
||||
packages.push(entry.versions[entry['dist-tags'].latest])
|
||||
}
|
||||
|
||||
if (i >= results.length - 1) {
|
||||
res.send(packages)
|
||||
} else {
|
||||
getData(i + 1)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (results.length) {
|
||||
getData(0);
|
||||
} else {
|
||||
res.send([])
|
||||
}
|
||||
})
|
||||
|
||||
// Readme
|
||||
marked.setOptions({
|
||||
highlight: function (code) {
|
||||
return require('highlight.js').highlightAuto(code).value
|
||||
}
|
||||
})
|
||||
|
||||
app.get('/-/readme/:package/:version?', function(req, res, next) {
|
||||
storage.get_package(req.params.package, {req: req}, function(err, info) {
|
||||
if (err) return next(err)
|
||||
res.send(marked(info.readme || 'ERROR: No README data found!'))
|
||||
})
|
||||
})
|
||||
}
|
||||
300
lib/index.js
300
lib/index.js
@@ -3,34 +3,46 @@ var express = require('express')
|
||||
, utils = require('./utils')
|
||||
, Storage = require('./storage')
|
||||
, Config = require('./config')
|
||||
, UError = require('./error').UserError
|
||||
, Error = require('http-errors')
|
||||
, Middleware = require('./middleware')
|
||||
, Logger = require('./logger')
|
||||
, Cats = require('./status-cats')
|
||||
, basic_auth = Middleware.basic_auth
|
||||
, validate_name = Middleware.validate_name
|
||||
, media = Middleware.media
|
||||
, expect_json = Middleware.expect_json
|
||||
, fs = require('fs')
|
||||
, Auth = require('./auth')
|
||||
|
||||
function match(regexp) {
|
||||
return function(req, res, next, value, name) {
|
||||
if (regexp.exec(value)) {
|
||||
next()
|
||||
} else {
|
||||
next('route')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = function(config_hash) {
|
||||
var config = new Config(config_hash)
|
||||
, storage = new Storage(config)
|
||||
, auth = new Auth(config)
|
||||
|
||||
var can = function(action) {
|
||||
return function(req, res, next) {
|
||||
if (config['allow_'+action](req.params.package, req.remoteUser)) {
|
||||
if (config['allow_'+action](req.params.package, req.remote_user)) {
|
||||
next()
|
||||
} else {
|
||||
if (!req.remoteUser) {
|
||||
next(new UError({
|
||||
status: 403,
|
||||
msg: "can't "+action+" restricted package without auth, did you forget 'npm set always-auth true'?",
|
||||
}))
|
||||
if (!req.remote_user.name) {
|
||||
if (req.remote_user.error) {
|
||||
var message = "can't "+action+' restricted package, ' + req.remote_user.error
|
||||
} else {
|
||||
var message = "can't "+action+" restricted package without auth, did you forget 'npm set always-auth true'?"
|
||||
}
|
||||
next(Error[403](message))
|
||||
} else {
|
||||
next(new UError({
|
||||
status: 403,
|
||||
msg: 'user '+req.remoteUser+' not allowed to '+action+' it'
|
||||
}))
|
||||
next(Error[403]('user ' + req.remote_user.name
|
||||
+ ' not allowed to ' + action + ' it'))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -38,55 +50,63 @@ module.exports = function(config_hash) {
|
||||
|
||||
var app = express()
|
||||
|
||||
app.use(function(req, res, next) {
|
||||
var calls = 0
|
||||
res.report_error = function(err) {
|
||||
calls++
|
||||
// run in production mode by default, just in case
|
||||
// it shouldn't make any difference anyway
|
||||
app.set('env', process.env.NODE_ENV || 'production')
|
||||
|
||||
function error_reporting_middleware(req, res, next) {
|
||||
res.report_error = res.report_error || function(err) {
|
||||
if (err.status && err.status >= 400 && err.status < 600) {
|
||||
if (calls == 1) {
|
||||
if (!res.headersSent) {
|
||||
res.status(err.status)
|
||||
res.send({error: err.msg || err.message || 'unknown error'})
|
||||
res.send({error: err.message || 'unknown error'})
|
||||
}
|
||||
} else {
|
||||
Logger.logger.error({err: err}, 'unexpected error: @{!err.message}\n@{err.stack}')
|
||||
if (calls == 1) {
|
||||
if (!res.status || !res.send) {
|
||||
Logger.logger.error('this is an error in express.js, please report this')
|
||||
res.destroy()
|
||||
} else if (!res.headersSent) {
|
||||
res.status(500)
|
||||
res.send({error: 'internal server error'})
|
||||
} else {
|
||||
// socket should be already closed
|
||||
}
|
||||
}
|
||||
}
|
||||
next()
|
||||
})
|
||||
}
|
||||
|
||||
app.use(error_reporting_middleware)
|
||||
app.use(Middleware.log_and_etagify)
|
||||
app.use(function(req, res, next) {
|
||||
res.setHeader('X-Powered-By', 'Sinopia')
|
||||
res.setHeader('X-Powered-By', config.user_agent)
|
||||
next()
|
||||
})
|
||||
app.use(Cats.middleware)
|
||||
app.use(basic_auth(function(user, pass) {
|
||||
return config.authenticate(user, pass)
|
||||
}))
|
||||
app.use(express.json({strict: false}))
|
||||
|
||||
// TODO: npm DO NOT support compression :(
|
||||
app.use(auth.middleware())
|
||||
app.use(express.json({strict: false, limit: config.max_body_size || '10mb'}))
|
||||
app.use(express.compress())
|
||||
app.use(Middleware.anti_loop(config))
|
||||
|
||||
// validate all of these params as a package name
|
||||
// this might be too harsh, so ask if it causes trouble
|
||||
app.param('package', validate_name)
|
||||
app.param('filename', validate_name)
|
||||
app.param('tag', validate_name)
|
||||
app.param('version', validate_name)
|
||||
app.param('revision', validate_name)
|
||||
|
||||
/* app.get('/', function(req, res) {
|
||||
res.send({
|
||||
error: 'unimplemented'
|
||||
})
|
||||
})*/
|
||||
// these can't be safely put into express url for some reason
|
||||
app.param('_rev', match(/^-rev$/))
|
||||
app.param('org_couchdb_user', match(/^org\.couchdb\.user:/))
|
||||
app.param('anything', match(/.*/))
|
||||
|
||||
/* app.get('/-/all', function(req, res) {
|
||||
/* app.get('/-/all', function(req, res) {
|
||||
var https = require('https')
|
||||
var JSONStream = require('JSONStream')
|
||||
var request = require('request')({
|
||||
url: 'https://registry.npmjs.org/-/all',
|
||||
ca: require('./npmsslkeys'),
|
||||
})
|
||||
.pipe(JSONStream.parse('*'))
|
||||
.on('data', function(d) {
|
||||
@@ -96,46 +116,60 @@ module.exports = function(config_hash) {
|
||||
|
||||
// TODO: anonymous user?
|
||||
app.get('/:package/:version?', can('access'), function(req, res, next) {
|
||||
storage.get_package(req.params.package, function(err, info) {
|
||||
storage.get_package(req.params.package, {req: req}, function(err, info) {
|
||||
if (err) return next(err)
|
||||
info = utils.filter_tarball_urls(info, req, config)
|
||||
|
||||
var version = req.params.version
|
||||
, t
|
||||
if (!version) {
|
||||
return res.send(info)
|
||||
}
|
||||
|
||||
if (info.versions[version] != null) {
|
||||
return res.send(info.versions[version])
|
||||
if ((t = utils.get_version(info, version)) != null) {
|
||||
return res.send(t)
|
||||
}
|
||||
|
||||
if (info['dist-tags'] != null) {
|
||||
if (info['dist-tags'][version] != null) {
|
||||
version = info['dist-tags'][version]
|
||||
if (info.versions[version] != null) {
|
||||
return res.send(info.versions[version])
|
||||
if ((t = utils.get_version(info, version)) != null) {
|
||||
return res.send(t)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return next(new UError({
|
||||
status: 404,
|
||||
msg: 'version not found: ' + req.params.version
|
||||
}))
|
||||
return next(Error[404]('version not found: ' + req.params.version))
|
||||
})
|
||||
})
|
||||
|
||||
app.get('/:package/-/:filename', can('access'), function(req, res, next) {
|
||||
var stream = storage.get_tarball(req.params.package, req.params.filename)
|
||||
stream.on('content-length', function(v) {
|
||||
res.header('Content-Length', v)
|
||||
})
|
||||
stream.on('error', function(err) {
|
||||
return res.report_error(err)
|
||||
})
|
||||
res.header('content-type', 'application/octet-stream')
|
||||
res.header('Content-Type', 'application/octet-stream')
|
||||
stream.pipe(res)
|
||||
})
|
||||
|
||||
// searching packages
|
||||
app.get('/-/all/:anything?', function(req, res, next) {
|
||||
storage.search(req.param.startkey || 0, {req: req}, function(err, result) {
|
||||
if (err) return next(err)
|
||||
for (var pkg in result) {
|
||||
if (!config.allow_access(pkg, req.remote_user)) {
|
||||
delete result[pkg]
|
||||
}
|
||||
}
|
||||
return res.send(result)
|
||||
})
|
||||
})
|
||||
|
||||
//app.get('/*', function(req, res) {
|
||||
// proxy.request(req, res)
|
||||
// proxy.request(req, res)
|
||||
//})
|
||||
|
||||
// placeholder 'cause npm require to be authenticated to publish
|
||||
@@ -145,75 +179,153 @@ module.exports = function(config_hash) {
|
||||
// npmjs.org sets 10h expire
|
||||
expires: new Date(Date.now() + 10*60*60*1000)
|
||||
})
|
||||
res.send({"ok":true,"name":"somebody","roles":[]})
|
||||
res.send({'ok':true,'name':'somebody','roles':[]})
|
||||
})
|
||||
|
||||
app.get('/-/user/:argument', function(req, res, next) {
|
||||
// can't put 'org.couchdb.user' in route address for some reason
|
||||
if (req.params.argument.split(':')[0] !== 'org.couchdb.user') return next('route')
|
||||
app.get('/-/user/:org_couchdb_user', function(req, res, next) {
|
||||
res.status(200)
|
||||
return res.send({
|
||||
ok: 'you are authenticated as "' + req.user + '"',
|
||||
ok: 'you are authenticated as "' + req.remote_user.name + '"',
|
||||
})
|
||||
})
|
||||
|
||||
app.put('/-/user/:argument', function(req, res, next) {
|
||||
// can't put 'org.couchdb.user' in route address for some reason
|
||||
if (req.params.argument.split(':')[0] !== 'org.couchdb.user') return next('route')
|
||||
res.status(409)
|
||||
return res.send({
|
||||
error: 'registration is not implemented',
|
||||
})
|
||||
app.put('/-/user/:org_couchdb_user/:_rev?/:revision?', function(req, res, next) {
|
||||
if (req.remote_user.name != null) {
|
||||
res.status(201)
|
||||
return res.send({
|
||||
ok: 'you are authenticated as "' + req.remote_user.name + '"',
|
||||
})
|
||||
} else {
|
||||
if (typeof(req.body.name) !== 'string' || typeof(req.body.password) !== 'string') {
|
||||
return next(Error[400]('user/password is not found in request (npm issue?)'))
|
||||
}
|
||||
auth.add_user(req.body.name, req.body.password, function(err) {
|
||||
if (err) {
|
||||
if (err.status < 500 && err.message === 'this user already exists') {
|
||||
// with npm registering is the same as logging in
|
||||
// so we replace message in case of conflict
|
||||
return next(Error[409]('bad username/password, access denied'))
|
||||
}
|
||||
return next(err)
|
||||
}
|
||||
|
||||
res.status(201)
|
||||
return res.send({
|
||||
ok: 'user "' + req.body.name + '" created',
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
app.put('/-/user/:argument/-rev/*', function(req, res, next) {
|
||||
// can't put 'org.couchdb.user' in route address for some reason
|
||||
if (req.params.argument.split(':')[0] !== 'org.couchdb.user') return next('route')
|
||||
res.status(201)
|
||||
return res.send({
|
||||
ok: 'you are authenticated as "' + req.user + '"',
|
||||
// tagging a package
|
||||
app.put('/:package/:tag', can('publish'), media('application/json'), function(req, res, next) {
|
||||
if (typeof(req.body) !== 'string') return next('route')
|
||||
|
||||
var tags = {}
|
||||
tags[req.params.tag] = req.body
|
||||
storage.add_tags(req.params.package, tags, function(err) {
|
||||
if (err) return next(err)
|
||||
res.status(201)
|
||||
return res.send({
|
||||
ok: 'package tagged'
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// publishing a package
|
||||
app.put('/:package/:_rev?/:revision?', can('publish'), media('application/json'), expect_json, function(req, res, next) {
|
||||
if (req.params._rev != null && req.params._rev != '-rev') return next('route')
|
||||
var name = req.params.package
|
||||
|
||||
if (Object.keys(req.body).length == 1 && utils.is_object(req.body.users)) {
|
||||
return next(new UError({
|
||||
// 501 status is more meaningful, but npm doesn't show error message for 5xx
|
||||
status: 404,
|
||||
msg: 'npm star|unstar calls are not implemented',
|
||||
}))
|
||||
// 501 status is more meaningful, but npm doesn't show error message for 5xx
|
||||
return next(Error[404]('npm star|unstar calls are not implemented'))
|
||||
}
|
||||
|
||||
try {
|
||||
var metadata = utils.validate_metadata(req.body, name)
|
||||
} catch(err) {
|
||||
return next(new UError({
|
||||
status: 422,
|
||||
msg: 'bad incoming package data',
|
||||
}))
|
||||
return next(Error[422]('bad incoming package data'))
|
||||
}
|
||||
|
||||
if (req.params._rev) {
|
||||
storage.change_package(name, metadata, req.params.revision, function(err) {
|
||||
if (err) return next(err)
|
||||
res.status(201)
|
||||
return res.send({
|
||||
ok: 'package changed'
|
||||
})
|
||||
after_change(err, 'package changed')
|
||||
})
|
||||
} else {
|
||||
storage.add_package(name, metadata, function(err) {
|
||||
after_change(err, 'created new package')
|
||||
})
|
||||
}
|
||||
|
||||
function after_change(err, ok_message) {
|
||||
// old npm behaviour
|
||||
if (metadata._attachments == null) {
|
||||
if (err) return next(err)
|
||||
res.status(201)
|
||||
return res.send({
|
||||
ok: 'created new package'
|
||||
ok: ok_message
|
||||
})
|
||||
}
|
||||
|
||||
// npm-registry-client 0.3+ embeds tarball into the json upload
|
||||
// https://github.com/isaacs/npm-registry-client/commit/e9fbeb8b67f249394f735c74ef11fe4720d46ca0
|
||||
// issue #31, dealing with it here:
|
||||
|
||||
if (typeof(metadata._attachments) != 'object'
|
||||
|| Object.keys(metadata._attachments).length != 1
|
||||
|| typeof(metadata.versions) != 'object'
|
||||
|| Object.keys(metadata.versions).length != 1) {
|
||||
|
||||
// npm is doing something strange again
|
||||
// if this happens in normal circumstances, report it as a bug
|
||||
return next(Error[400]('unsupported registry call'))
|
||||
}
|
||||
|
||||
if (err && err.status != 409) return next(err)
|
||||
|
||||
// at this point document is either created or existed before
|
||||
var t1 = Object.keys(metadata._attachments)[0]
|
||||
create_tarball(t1, metadata._attachments[t1], function(err) {
|
||||
if (err) return next(err)
|
||||
|
||||
var t2 = Object.keys(metadata.versions)[0]
|
||||
metadata.versions[t2].readme = metadata.readme != null ? String(metadata.readme) : ''
|
||||
create_version(t2, metadata.versions[t2], function(err) {
|
||||
if (err) return next(err)
|
||||
|
||||
add_tags(metadata['dist-tags'], function(err) {
|
||||
if (err) return next(err)
|
||||
|
||||
res.status(201)
|
||||
return res.send({
|
||||
ok: ok_message
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function create_tarball(filename, data, cb) {
|
||||
var stream = storage.add_tarball(name, filename)
|
||||
stream.on('error', function(err) {
|
||||
cb(err)
|
||||
})
|
||||
stream.on('success', function() {
|
||||
cb()
|
||||
})
|
||||
|
||||
// this is dumb and memory-consuming, but what choices do we have?
|
||||
stream.end(new Buffer(data.data, 'base64'))
|
||||
stream.done()
|
||||
}
|
||||
|
||||
function create_version(version, data, cb) {
|
||||
storage.add_version(name, version, data, null, cb)
|
||||
}
|
||||
|
||||
function add_tags(tags, cb) {
|
||||
storage.add_tags(name, tags, cb)
|
||||
}
|
||||
})
|
||||
|
||||
// unpublishing an entire package
|
||||
@@ -283,11 +395,41 @@ module.exports = function(config_hash) {
|
||||
})
|
||||
})
|
||||
|
||||
// hook for tests only
|
||||
if (config._debug) {
|
||||
app.get('/-/_debug', function(req, res) {
|
||||
var do_gc = typeof(global.gc) !== 'undefined'
|
||||
if (do_gc) global.gc()
|
||||
res.send({
|
||||
pid: process.pid,
|
||||
main: process.mainModule.filename,
|
||||
conf: config.self_path,
|
||||
mem: process.memoryUsage(),
|
||||
gc: do_gc,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
app.use(app.router)
|
||||
app.use(function(err, req, res, next) {
|
||||
if (typeof(res.report_error) !== 'function') {
|
||||
// in case of very early error this middleware may not be loaded before error is generated
|
||||
// fixing that
|
||||
error_reporting_middleware(req, res, function(){})
|
||||
}
|
||||
res.report_error(err)
|
||||
})
|
||||
|
||||
if (config.web && config.web.enable) {
|
||||
require('./index-web')(app, config, storage)
|
||||
} else {
|
||||
app.get('/', function(req, res) {
|
||||
res.send('Web interface is a work-in-progress right now, '
|
||||
+ 'so it is disabled by default. If you want to play with it, '
|
||||
+ 'you can enable it in the config file.')
|
||||
})
|
||||
}
|
||||
|
||||
return app
|
||||
}
|
||||
|
||||
|
||||
165
lib/local-fs.js
165
lib/local-fs.js
@@ -1,22 +1,23 @@
|
||||
var fs = require('fs')
|
||||
, fsExt = require('fs-ext')
|
||||
, Path = require('path')
|
||||
, mkdirp = require('mkdirp')
|
||||
, mystreams = require('./streams')
|
||||
, Logger = require('./logger')
|
||||
, FSError = require('./error').FSError
|
||||
, Error = require('http-errors')
|
||||
|
||||
function make_directories(dest, cb) {
|
||||
var dir = Path.dirname(dest)
|
||||
if (dir === '.' || dir === '..') return cb()
|
||||
fs.mkdir(dir, function(err) {
|
||||
if (err && err.code === 'ENOENT') {
|
||||
make_directories(dir, function() {
|
||||
fs.mkdir(dir, cb)
|
||||
})
|
||||
} else {
|
||||
cb()
|
||||
function FSError(code) {
|
||||
var err = Error(code)
|
||||
err.code = code
|
||||
return err
|
||||
}
|
||||
|
||||
try {
|
||||
var fsExt = require('fs-ext')
|
||||
} catch(e) {
|
||||
fsExt = {
|
||||
flock: function() {
|
||||
arguments[arguments.length-1]()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function write(dest, data, cb) {
|
||||
@@ -30,7 +31,8 @@ function write(dest, data, cb) {
|
||||
|
||||
safe_write(function(err) {
|
||||
if (err && err.code === 'ENOENT') {
|
||||
make_directories(dest, function() {
|
||||
mkdirp(Path.dirname(dest), function(err) {
|
||||
if (err) return cb(err)
|
||||
safe_write(cb)
|
||||
})
|
||||
} else {
|
||||
@@ -48,7 +50,7 @@ function write_stream(name) {
|
||||
})
|
||||
|
||||
fs.exists(name, function(exists) {
|
||||
if (exists) return stream.emit('error', new FSError('EEXISTS'))
|
||||
if (exists) return stream.emit('error', FSError('EEXISTS'))
|
||||
|
||||
var tmpname = name + '.tmp-'+String(Math.random()).replace(/^0\./, '')
|
||||
, file = fs.createWriteStream(tmpname)
|
||||
@@ -59,8 +61,11 @@ function write_stream(name) {
|
||||
function onend() {
|
||||
file.on('close', function() {
|
||||
fs.rename(tmpname, name, function(err) {
|
||||
if (err) stream.emit('error', err)
|
||||
stream.emit('success')
|
||||
if (err) {
|
||||
stream.emit('error', err)
|
||||
} else {
|
||||
stream.emit('success')
|
||||
}
|
||||
})
|
||||
})
|
||||
file.destroySoon()
|
||||
@@ -93,19 +98,36 @@ function write_stream(name) {
|
||||
}
|
||||
|
||||
function read_stream(name, stream, callback) {
|
||||
return fs.createReadStream(name)
|
||||
var rstream = fs.createReadStream(name)
|
||||
rstream.on('error', function(err) {
|
||||
stream.emit('error', err)
|
||||
})
|
||||
rstream.on('open', function(fd) {
|
||||
fs.fstat(fd, function(err, stats) {
|
||||
if (err) return stream.emit('error', err)
|
||||
stream.emit('content-length', stats.size)
|
||||
stream.emit('open')
|
||||
rstream.pipe(stream)
|
||||
})
|
||||
})
|
||||
|
||||
var stream = new mystreams.ReadTarballStream()
|
||||
stream.abort = function() {
|
||||
rstream.close()
|
||||
}
|
||||
return stream
|
||||
}
|
||||
|
||||
function create(name, contents, callback) {
|
||||
fs.exists(name, function(exists) {
|
||||
if (exists) return callback(new FSError('EEXISTS'))
|
||||
if (exists) return callback(FSError('EEXISTS'))
|
||||
write(name, contents, callback)
|
||||
})
|
||||
}
|
||||
|
||||
function update(name, contents, callback) {
|
||||
fs.exists(name, function(exists) {
|
||||
if (!exists) return callback(new FSError('ENOENT'))
|
||||
if (!exists) return callback(FSError('ENOENT'))
|
||||
write(name, contents, callback)
|
||||
})
|
||||
}
|
||||
@@ -137,44 +159,45 @@ function open_flock(name, opmod, flmod, tries, backoff, cb) {
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// this function neither unlocks file nor closes it
|
||||
// it'll have to be done manually later
|
||||
function lock_and_read(name, callback) {
|
||||
function lock_and_read(name, _callback) {
|
||||
open_flock(name, 'r', 'exnb', 4, 10, function(err, fd) {
|
||||
function callback(err) {
|
||||
if (err && fd) {
|
||||
fs.close(fd, function(err2) {
|
||||
_callback(err)
|
||||
})
|
||||
} else {
|
||||
_callback.apply(null, arguments)
|
||||
}
|
||||
}
|
||||
|
||||
if (err) return callback(err, fd)
|
||||
|
||||
fs.fstat(fd, function(err, st) {
|
||||
if (err) return callback(err, fd)
|
||||
|
||||
var buffer = new Buffer(st.size)
|
||||
fs.read(fd, buffer, 0, st.size, null, function(err, bytesRead, buffer) {
|
||||
if (st.size === 0) return onRead(null, 0, buffer)
|
||||
fs.read(fd, buffer, 0, st.size, null, onRead)
|
||||
|
||||
function onRead(err, bytesRead, buffer) {
|
||||
if (err) return callback(err, fd)
|
||||
if (bytesRead != st.size) return callback(new Error('st.size != bytesRead'), fd)
|
||||
|
||||
callback(null, fd, buffer)
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function Storage(path) {
|
||||
this.path = path
|
||||
this.logger = Logger.logger.child({sub: 'fs'})
|
||||
try {
|
||||
fs.mkdirSync(path)
|
||||
this.logger.warn({path: path}, 'created new packages directory: @{path}')
|
||||
} catch(err) {
|
||||
if (err.code !== 'EEXIST') throw new Error(err)
|
||||
}
|
||||
}
|
||||
module.exports.read = read
|
||||
|
||||
Storage.prototype.read = function(name, cb) {
|
||||
read(this.path + '/' + name, cb)
|
||||
}
|
||||
|
||||
Storage.prototype.read_json = function(name, cb) {
|
||||
read(this.path + '/' + name, function(err, res) {
|
||||
module.exports.read_json = function(name, cb) {
|
||||
read(name, function(err, res) {
|
||||
if (err) return cb(err)
|
||||
|
||||
var args = []
|
||||
@@ -187,12 +210,10 @@ Storage.prototype.read_json = function(name, cb) {
|
||||
})
|
||||
}
|
||||
|
||||
Storage.prototype.lock_and_read = function(name, cb) {
|
||||
lock_and_read(this.path + '/' + name, cb)
|
||||
}
|
||||
module.exports.lock_and_read = lock_and_read
|
||||
|
||||
Storage.prototype.lock_and_read_json = function(name, cb) {
|
||||
lock_and_read(this.path + '/' + name, function(err, fd, res) {
|
||||
module.exports.lock_and_read_json = function(name, cb) {
|
||||
lock_and_read(name, function(err, fd, res) {
|
||||
if (err) return cb(err, fd)
|
||||
|
||||
var args = []
|
||||
@@ -205,49 +226,29 @@ Storage.prototype.lock_and_read_json = function(name, cb) {
|
||||
})
|
||||
}
|
||||
|
||||
Storage.prototype.path_to = function(file) {
|
||||
return this.path + '/' + file
|
||||
module.exports.create = create
|
||||
|
||||
module.exports.create_json = function(name, value, cb) {
|
||||
create(name, JSON.stringify(value, null, '\t'), cb)
|
||||
}
|
||||
|
||||
Storage.prototype.create = function(name, value, cb) {
|
||||
create(this.path + '/' + name, value, cb)
|
||||
module.exports.update = update
|
||||
|
||||
module.exports.update_json = function(name, value, cb) {
|
||||
update(name, JSON.stringify(value, null, '\t'), cb)
|
||||
}
|
||||
|
||||
Storage.prototype.create_json = function(name, value, cb) {
|
||||
create(this.path + '/' + name, JSON.stringify(value, null, '\t'), cb)
|
||||
module.exports.write = write
|
||||
|
||||
module.exports.write_json = function(name, value, cb) {
|
||||
write(name, JSON.stringify(value, null, '\t'), cb)
|
||||
}
|
||||
|
||||
Storage.prototype.update = function(name, value, cb) {
|
||||
update(this.path + '/' + name, value, cb)
|
||||
}
|
||||
module.exports.write_stream = write_stream
|
||||
|
||||
Storage.prototype.update_json = function(name, value, cb) {
|
||||
update(this.path + '/' + name, JSON.stringify(value, null, '\t'), cb)
|
||||
}
|
||||
module.exports.read_stream = read_stream
|
||||
|
||||
Storage.prototype.write = function(name, value, cb) {
|
||||
write(this.path + '/' + name, value, cb)
|
||||
}
|
||||
module.exports.unlink = fs.unlink
|
||||
|
||||
Storage.prototype.write_json = function(name, value, cb) {
|
||||
write(this.path + '/' + name, JSON.stringify(value, null, '\t'), cb)
|
||||
}
|
||||
|
||||
Storage.prototype.write_stream = function(name, value, cb) {
|
||||
return write_stream(this.path + '/' + name, value, cb)
|
||||
}
|
||||
|
||||
Storage.prototype.read_stream = function(name, cb) {
|
||||
return read_stream(this.path + '/' + name, cb)
|
||||
}
|
||||
|
||||
Storage.prototype.unlink = function(name, cb) {
|
||||
fs.unlink(this.path + '/' + name, cb)
|
||||
}
|
||||
|
||||
Storage.prototype.rmdir = function(name, cb) {
|
||||
fs.rmdir(this.path + '/' + name, cb)
|
||||
}
|
||||
|
||||
module.exports = Storage
|
||||
module.exports.rmdir = fs.rmdir
|
||||
|
||||
|
||||
38
lib/local-list.js
Normal file
38
lib/local-list.js
Normal file
@@ -0,0 +1,38 @@
|
||||
var fs = require('fs')
|
||||
|
||||
function LocalList(path) {
|
||||
var self = Object.create(LocalList.prototype)
|
||||
self.path = path
|
||||
try {
|
||||
self.list = JSON.parse(fs.readFileSync(self.path, 'utf8')).list
|
||||
} catch(_) {
|
||||
self.list = []
|
||||
}
|
||||
return self
|
||||
}
|
||||
|
||||
LocalList.prototype = {
|
||||
add: function(name) {
|
||||
if (this.list.indexOf(name) === -1) {
|
||||
this.list.push(name)
|
||||
this.sync()
|
||||
}
|
||||
},
|
||||
remove: function(name) {
|
||||
var i = this.list.indexOf(name)
|
||||
if (i !== -1) {
|
||||
this.list.splice(i, 1)
|
||||
}
|
||||
|
||||
this.sync()
|
||||
},
|
||||
get: function() {
|
||||
return this.list
|
||||
},
|
||||
sync: function() {
|
||||
fs.writeFileSync(this.path, JSON.stringify({list: this.list})); //Uses sync to prevent ugly race condition
|
||||
},
|
||||
}
|
||||
|
||||
module.exports = LocalList
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
var fs = require('fs')
|
||||
, semver = require('semver')
|
||||
, Path = require('path')
|
||||
, crypto = require('crypto')
|
||||
, assert = require('assert')
|
||||
, fs_storage = require('./local-fs')
|
||||
, UError = require('./error').UserError
|
||||
, Error = require('http-errors')
|
||||
, utils = require('./utils')
|
||||
, mystreams = require('./streams')
|
||||
, Logger = require('./logger')
|
||||
, info_file = 'package.json'
|
||||
, search = require('./search');
|
||||
|
||||
//
|
||||
// Implements Storage interface
|
||||
@@ -16,8 +17,6 @@ var fs = require('fs')
|
||||
function Storage(config) {
|
||||
if (!(this instanceof Storage)) return new Storage(config)
|
||||
this.config = config
|
||||
var path = Path.resolve(Path.dirname(this.config.self_path), this.config.storage)
|
||||
this.storage = new fs_storage(path)
|
||||
this.logger = Logger.logger.child({sub: 'fs'})
|
||||
return this
|
||||
}
|
||||
@@ -37,23 +36,26 @@ function get_boilerplate(name) {
|
||||
}
|
||||
}
|
||||
|
||||
Storage.prototype._internal_error = function(err, file, msg) {
|
||||
this.logger.error( {err: err, file: this.storage.path_to(file)}
|
||||
, msg + ' @{file}: @{!err.message}'
|
||||
Storage.prototype._internal_error = function(err, file, message) {
|
||||
this.logger.error( {err: err, file: file}
|
||||
, message + ' @{file}: @{!err.message}'
|
||||
)
|
||||
return new UError({
|
||||
status: 500,
|
||||
msg: 'internal server error'
|
||||
})
|
||||
return Error[500]()
|
||||
}
|
||||
|
||||
Storage.prototype.add_package = function(name, metadata, callback) {
|
||||
this.storage.create_json(name + '/' + info_file, get_boilerplate(name), function(err) {
|
||||
Storage.prototype.add_package = function(name, info, callback) {
|
||||
var self = this
|
||||
var storage = this.storage(name)
|
||||
if (!storage) return callback(Error[404]('this package cannot be added'))
|
||||
|
||||
storage.create_json(info_file, get_boilerplate(name), function(err) {
|
||||
if (err && err.code === 'EEXISTS') {
|
||||
return callback(new UError({
|
||||
status: 409,
|
||||
msg: 'this package is already present'
|
||||
}))
|
||||
return callback(Error[409]('this package is already present'))
|
||||
}
|
||||
|
||||
var latest = info['dist-tags'].latest
|
||||
if (latest && info.versions[latest]) {
|
||||
search.add(info.versions[latest])
|
||||
}
|
||||
callback()
|
||||
})
|
||||
@@ -62,20 +64,21 @@ Storage.prototype.add_package = function(name, metadata, callback) {
|
||||
Storage.prototype.remove_package = function(name, callback) {
|
||||
var self = this
|
||||
self.logger.info({name: name}, 'unpublishing @{name} (all)')
|
||||
self.storage.read_json(name + '/' + info_file, function(err, data) {
|
||||
|
||||
var storage = self.storage(name)
|
||||
if (!storage) return callback(Error[404]('no such package available'))
|
||||
|
||||
storage.read_json(info_file, function(err, data) {
|
||||
if (err) {
|
||||
if (err.code === 'ENOENT') {
|
||||
return callback(new UError({
|
||||
status: 404,
|
||||
msg: 'no such package available',
|
||||
}))
|
||||
return callback(Error[404]('no such package available'))
|
||||
} else {
|
||||
return callback(err)
|
||||
}
|
||||
}
|
||||
self._normalize_package(data)
|
||||
|
||||
self.storage.unlink(name + '/' + info_file, function(err) {
|
||||
storage.unlink(info_file, function(err) {
|
||||
if (err) return callback(err)
|
||||
|
||||
var files = Object.keys(data._attachments)
|
||||
@@ -84,32 +87,40 @@ Storage.prototype.remove_package = function(name, callback) {
|
||||
if (files.length === 0) return cb()
|
||||
|
||||
var file = files.shift()
|
||||
self.storage.unlink(name + '/' + file, function() {
|
||||
storage.unlink(file, function() {
|
||||
unlinkNext(cb)
|
||||
})
|
||||
}
|
||||
|
||||
unlinkNext(function() {
|
||||
// try to unlink the directory, but ignore errors because it can fail
|
||||
self.storage.rmdir(name, function(err) {
|
||||
callback()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
storage.rmdir('.', function(err) {
|
||||
callback(err)
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
search.remove(name);
|
||||
this.config.localList.remove(name);
|
||||
}
|
||||
|
||||
Storage.prototype._read_create_package = function(name, callback) {
|
||||
var self = this
|
||||
, file = name + '/' + info_file
|
||||
self.storage.read_json(file, function(err, data) {
|
||||
var storage = self.storage(name)
|
||||
if (!storage) {
|
||||
var data = get_boilerplate(name)
|
||||
self._normalize_package(data)
|
||||
return callback(null, data)
|
||||
}
|
||||
storage.read_json(info_file, function(err, data) {
|
||||
// TODO: race condition
|
||||
if (err) {
|
||||
if (err.code === 'ENOENT') {
|
||||
// if package doesn't exist, we create it here
|
||||
data = get_boilerplate(name)
|
||||
} else {
|
||||
return callback(self._internal_error(err, file, 'error reading'))
|
||||
return callback(self._internal_error(err, info_file, 'error reading'))
|
||||
}
|
||||
}
|
||||
self._normalize_package(data)
|
||||
@@ -129,7 +140,8 @@ Storage.prototype.update_versions = function(name, newdata, callback) {
|
||||
if (data.versions[ver] == null) {
|
||||
var verdata = newdata.versions[ver]
|
||||
|
||||
// why does anyone need to keep that in database?
|
||||
// we don't keep readmes for package versions,
|
||||
// only one readme per package
|
||||
delete verdata.readme
|
||||
|
||||
change = true
|
||||
@@ -137,13 +149,13 @@ Storage.prototype.update_versions = function(name, newdata, callback) {
|
||||
|
||||
if (verdata.dist && verdata.dist.tarball) {
|
||||
var url = utils.parse_tarball_url(
|
||||
verdata.dist.__sinopia_orig_tarball || verdata.dist.tarball
|
||||
/*verdata.dist.__sinopia_orig_tarball ||*/ verdata.dist.tarball
|
||||
)
|
||||
|
||||
// we do NOT overwrite any existing records
|
||||
if (url != null && data._distfiles[url.filename] == null) {
|
||||
data._distfiles[url.filename] = {
|
||||
url: verdata.dist.__sinopia_orig_tarball || verdata.dist.tarball,
|
||||
url: /*verdata.dist.__sinopia_orig_tarball ||*/ verdata.dist.tarball,
|
||||
sha: verdata.dist.shasum,
|
||||
}
|
||||
}
|
||||
@@ -151,10 +163,17 @@ Storage.prototype.update_versions = function(name, newdata, callback) {
|
||||
}
|
||||
}
|
||||
for (var tag in newdata['dist-tags']) {
|
||||
// if tag is updated to reference latter version, that's fine
|
||||
var need_change =
|
||||
(data['dist-tags'][tag] == null) ||
|
||||
(!semver.gte(newdata['dist-tags'][tag], data['dist-tags'][tag]))
|
||||
if (!Array.isArray(data['dist-tags'][tag]) || data['dist-tags'][tag].length != newdata['dist-tags'][tag].length) {
|
||||
// backward compat
|
||||
var need_change = true
|
||||
} else {
|
||||
for (var i=0; i<data['dist-tags'][tag].length; i++) {
|
||||
if (data['dist-tags'][tag][i] != newdata['dist-tags'][tag][i]) {
|
||||
var need_change = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (need_change) {
|
||||
change = true
|
||||
@@ -162,20 +181,26 @@ Storage.prototype.update_versions = function(name, newdata, callback) {
|
||||
}
|
||||
}
|
||||
for (var up in newdata._uplinks) {
|
||||
var need_change =
|
||||
!utils.is_object(data._uplinks[up]) || (newdata._uplinks[up].etag !== data._uplinks[up].etag)
|
||||
var need_change =
|
||||
!utils.is_object(data._uplinks[up]) || (newdata._uplinks[up].etag !== data._uplinks[up].etag || (newdata._uplinks[up].fetched !== data._uplinks[up].fetched))
|
||||
|
||||
if (need_change) {
|
||||
change = true
|
||||
data._uplinks[up] = newdata._uplinks[up]
|
||||
}
|
||||
}
|
||||
if (newdata.readme !== data.readme) {
|
||||
data.readme = newdata.readme
|
||||
change = true
|
||||
}
|
||||
|
||||
if (change) {
|
||||
self.logger.debug('updating package info')
|
||||
self._write_package(name, data, callback)
|
||||
self._write_package(name, data, function(err) {
|
||||
callback(err, data)
|
||||
})
|
||||
} else {
|
||||
callback()
|
||||
callback(null, data)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -183,14 +208,12 @@ Storage.prototype.update_versions = function(name, newdata, callback) {
|
||||
Storage.prototype.add_version = function(name, version, metadata, tag, callback) {
|
||||
var self = this
|
||||
self.update_package(name, function updater(data, cb) {
|
||||
// why does anyone need to keep that in database?
|
||||
// keep only one readme per package
|
||||
data.readme = metadata.readme
|
||||
delete metadata.readme
|
||||
|
||||
if (data.versions[version] != null) {
|
||||
return cb(new UError({
|
||||
status: 409,
|
||||
msg: 'this version already present'
|
||||
}))
|
||||
return cb(Error[409]('this version already present'))
|
||||
}
|
||||
|
||||
// if uploaded tarball has a different shasum, it's very likely that we have some kind of error
|
||||
@@ -199,10 +222,9 @@ Storage.prototype.add_version = function(name, version, metadata, tag, callback)
|
||||
if (utils.is_object(data._attachments[tarball])) {
|
||||
if (data._attachments[tarball].shasum != null && metadata.dist.shasum != null) {
|
||||
if (data._attachments[tarball].shasum != metadata.dist.shasum) {
|
||||
return cb(new UError({
|
||||
status: 400,
|
||||
msg: 'shasum error, ' + data._attachments[tarball].shasum + ' != ' + metadata.dist.shasum,
|
||||
}))
|
||||
return cb(Error[400]('shasum error, '
|
||||
+ data._attachments[tarball].shasum
|
||||
+ ' != ' + metadata.dist.shasum))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,20 +233,38 @@ Storage.prototype.add_version = function(name, version, metadata, tag, callback)
|
||||
}
|
||||
|
||||
data.versions[version] = metadata
|
||||
data['dist-tags'][tag] = version
|
||||
utils.tag_version(data, version, tag, self.config)
|
||||
self.config.localList.add(name)
|
||||
cb()
|
||||
}, callback)
|
||||
}
|
||||
|
||||
Storage.prototype.add_tags = function(name, tags, callback) {
|
||||
var self = this
|
||||
|
||||
self.update_package(name, function updater(data, cb) {
|
||||
for (var t in tags) {
|
||||
if (data.versions[tags[t]] == null) {
|
||||
return cb(Error[404]("this version doesn't exist"))
|
||||
}
|
||||
|
||||
utils.tag_version(data, tags[t], t, self.config)
|
||||
}
|
||||
cb()
|
||||
}, callback)
|
||||
}
|
||||
|
||||
// change package info to tag a specific version
|
||||
function _add_tag(data, version, tag) {
|
||||
data['dist-tags'][tag] = version
|
||||
}
|
||||
|
||||
// currently supports unpublishing only
|
||||
Storage.prototype.change_package = function(name, metadata, revision, callback) {
|
||||
var self = this
|
||||
|
||||
if (!utils.is_object(metadata.versions) || !utils.is_object(metadata['dist-tags'])) {
|
||||
return callback(new UError({
|
||||
status: 422,
|
||||
msg: 'bad data',
|
||||
}))
|
||||
return callback(Error[422]('bad data'))
|
||||
}
|
||||
|
||||
self.update_package(name, function updater(data, cb) {
|
||||
@@ -250,29 +290,32 @@ Storage.prototype.change_package = function(name, metadata, revision, callback)
|
||||
|
||||
Storage.prototype.remove_tarball = function(name, filename, revision, callback) {
|
||||
var self = this
|
||||
assert(utils.validate_name(filename))
|
||||
|
||||
self.update_package(name, function updater(data, cb) {
|
||||
if (data._attachments[filename]) {
|
||||
delete data._attachments[filename]
|
||||
cb()
|
||||
} else {
|
||||
cb(new UError({
|
||||
status: 404,
|
||||
msg: 'no such file available',
|
||||
}))
|
||||
cb(Error[404]('no such file available'))
|
||||
}
|
||||
}, function(err) {
|
||||
if (err) return callback(err)
|
||||
self.storage.unlink(name + '/' + filename, callback)
|
||||
var storage = self.storage(name)
|
||||
if (storage) storage.unlink(filename, callback)
|
||||
})
|
||||
}
|
||||
|
||||
Storage.prototype.add_tarball = function(name, filename) {
|
||||
assert(utils.validate_name(filename))
|
||||
|
||||
var stream = new mystreams.UploadTarballStream()
|
||||
, _transform = stream._transform
|
||||
, length = 0
|
||||
, shasum = crypto.createHash('sha1')
|
||||
|
||||
stream.abort = stream.done = function(){}
|
||||
|
||||
stream._transform = function(data) {
|
||||
shasum.update(data)
|
||||
length += data.length
|
||||
@@ -281,20 +324,25 @@ Storage.prototype.add_tarball = function(name, filename) {
|
||||
|
||||
var self = this
|
||||
if (name === info_file || name === '__proto__') {
|
||||
stream.emit('error', new UError({
|
||||
status: 403,
|
||||
msg: 'can\'t use this filename'
|
||||
}))
|
||||
process.nextTick(function() {
|
||||
stream.emit('error', Error[403]("can't use this filename"))
|
||||
})
|
||||
return stream
|
||||
}
|
||||
|
||||
var wstream = this.storage.write_stream(name + '/' + filename)
|
||||
var storage = self.storage(name)
|
||||
if (!storage) {
|
||||
process.nextTick(function() {
|
||||
stream.emit('error', Error[404]("can't upload this package"))
|
||||
})
|
||||
return stream
|
||||
}
|
||||
|
||||
var wstream = storage.write_stream(filename)
|
||||
|
||||
wstream.on('error', function(err) {
|
||||
if (err.code === 'EEXISTS') {
|
||||
stream.emit('error', new UError({
|
||||
status: 409,
|
||||
msg: 'this tarball is already present'
|
||||
}))
|
||||
stream.emit('error', Error[409]('this tarball is already present'))
|
||||
} else if (err.code === 'ENOENT') {
|
||||
// check if package exists to throw an appropriate message
|
||||
self.get_package(name, function(_err, res) {
|
||||
@@ -332,10 +380,7 @@ Storage.prototype.add_tarball = function(name, filename) {
|
||||
}
|
||||
stream.done = function() {
|
||||
if (!length) {
|
||||
stream.emit('error', new UError({
|
||||
status: 422,
|
||||
msg: 'refusing to accept zero-length file'
|
||||
}))
|
||||
stream.emit('error', Error[422]('refusing to accept zero-length file'))
|
||||
wstream.abort()
|
||||
} else {
|
||||
wstream.done()
|
||||
@@ -347,22 +392,33 @@ Storage.prototype.add_tarball = function(name, filename) {
|
||||
}
|
||||
|
||||
Storage.prototype.get_tarball = function(name, filename, callback) {
|
||||
assert(utils.validate_name(filename))
|
||||
var self = this
|
||||
|
||||
var stream = new mystreams.ReadTarballStream()
|
||||
stream.abort = function() {
|
||||
rstream.close()
|
||||
if (rstream) rstream.abort()
|
||||
}
|
||||
|
||||
var rstream = this.storage.read_stream(name + '/' + filename)
|
||||
var storage = self.storage(name)
|
||||
if (!storage) {
|
||||
process.nextTick(function() {
|
||||
stream.emit('error', Error[404]('no such file available'))
|
||||
})
|
||||
return stream
|
||||
}
|
||||
|
||||
var rstream = storage.read_stream(filename)
|
||||
rstream.on('error', function(err) {
|
||||
if (err && err.code === 'ENOENT') {
|
||||
stream.emit('error', new UError({
|
||||
status: 404,
|
||||
msg: 'no such file available',
|
||||
}))
|
||||
stream.emit('error', Error(404, 'no such file available'))
|
||||
} else {
|
||||
stream.emit('error', err)
|
||||
}
|
||||
})
|
||||
rstream.on('content-length', function(v) {
|
||||
stream.emit('content-length', v)
|
||||
})
|
||||
rstream.on('open', function() {
|
||||
// re-emitting open because it's handled in storage.js
|
||||
stream.emit('open')
|
||||
@@ -371,19 +427,19 @@ Storage.prototype.get_tarball = function(name, filename, callback) {
|
||||
return stream
|
||||
}
|
||||
|
||||
Storage.prototype.get_package = function(name, callback) {
|
||||
var self = this
|
||||
, file = name + '/' + info_file
|
||||
Storage.prototype.get_package = function(name, options, callback) {
|
||||
if (typeof(options) === 'function') callback = options, options = {}
|
||||
|
||||
self.storage.read_json(file, function(err, result) {
|
||||
var self = this
|
||||
var storage = self.storage(name)
|
||||
if (!storage) return callback(Error[404]('no such package available'))
|
||||
|
||||
storage.read_json(info_file, function(err, result) {
|
||||
if (err) {
|
||||
if (err.code === 'ENOENT') {
|
||||
return callback(new UError({
|
||||
status: 404,
|
||||
msg: 'no such package available'
|
||||
}))
|
||||
return callback(Error[404]('no such package available'))
|
||||
} else {
|
||||
return callback(self._internal_error(err, file, 'error reading'))
|
||||
return callback(self._internal_error(err, info_file, 'error reading'))
|
||||
}
|
||||
}
|
||||
self._normalize_package(result)
|
||||
@@ -391,6 +447,37 @@ Storage.prototype.get_package = function(name, callback) {
|
||||
})
|
||||
}
|
||||
|
||||
Storage.prototype.get_recent_packages = function(startkey, callback) {
|
||||
var self = this
|
||||
var i = 0
|
||||
var list = []
|
||||
|
||||
var storage = self.storage('')
|
||||
if (!storage) return callback(null, [])
|
||||
|
||||
fs.readdir(storage.path, function(err, files) {
|
||||
if (err) return callback(null, [])
|
||||
|
||||
var filesL = files.length
|
||||
|
||||
files.forEach(function(file) {
|
||||
fs.stat(storage.path, function(err, stats) {
|
||||
if (err) return callback(err)
|
||||
if (stats.mtime > startkey && utils.validate_name(file)) {
|
||||
list.push({
|
||||
time: stats.mtime,
|
||||
name: file
|
||||
})
|
||||
}
|
||||
if (++i !== filesL) {
|
||||
return false
|
||||
}
|
||||
return callback(null, list)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
//
|
||||
// This function allows to update the package thread-safely
|
||||
//
|
||||
@@ -409,12 +496,10 @@ Storage.prototype.get_package = function(name, callback) {
|
||||
//
|
||||
Storage.prototype.update_package = function(name, updateFn, _callback) {
|
||||
var self = this
|
||||
, file = name + '/' + info_file
|
||||
self.storage.lock_and_read_json(file, function(err, fd, json) {
|
||||
self.logger.debug({file: file}, 'locking @{file}')
|
||||
|
||||
var storage = self.storage(name)
|
||||
if (!storage) return _callback(Error[404]('no such package available'))
|
||||
storage.lock_and_read_json(info_file, function(err, fd, json) {
|
||||
function callback() {
|
||||
self.logger.debug({file: file}, 'unlocking @{file}')
|
||||
var _args = arguments
|
||||
if (fd) {
|
||||
fs.close(fd, function(err) {
|
||||
@@ -428,15 +513,9 @@ Storage.prototype.update_package = function(name, updateFn, _callback) {
|
||||
|
||||
if (err) {
|
||||
if (err.code === 'EAGAIN') {
|
||||
return callback(new UError({
|
||||
status: 503,
|
||||
msg: 'resource temporarily unavailable'
|
||||
}))
|
||||
return callback(Error[503]('resource temporarily unavailable'))
|
||||
} else if (err.code === 'ENOENT') {
|
||||
return callback(new UError({
|
||||
status: 404,
|
||||
msg: 'no such package available',
|
||||
}))
|
||||
return callback(Error[404]('no such package available'))
|
||||
} else {
|
||||
return callback(err)
|
||||
}
|
||||
@@ -465,8 +544,48 @@ Storage.prototype._write_package = function(name, json, callback) {
|
||||
var rev = json._rev.split('-')
|
||||
json._rev = ((+rev[0] || 0) + 1) + '-' + crypto.pseudoRandomBytes(8).toString('hex')
|
||||
|
||||
this.storage.write_json(name + '/' + info_file, json, callback)
|
||||
var storage = this.storage(name)
|
||||
if (!storage) return callback()
|
||||
storage.write_json(info_file, json, callback)
|
||||
}
|
||||
|
||||
Storage.prototype.storage = function(package) {
|
||||
var path = this.config.get_package_setting(package, 'storage')
|
||||
if (path == null) path = this.config.storage
|
||||
if (path == null || path === false) {
|
||||
this.logger.debug({name: package}, 'this package has no storage defined: @{name}')
|
||||
return null
|
||||
}
|
||||
return new Path_Wrapper(
|
||||
Path.join(
|
||||
Path.resolve(Path.dirname(this.config.self_path), path),
|
||||
package
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
var Path_Wrapper = (function() {
|
||||
// a wrapper adding paths to fs_storage methods
|
||||
function Wrapper(path) {
|
||||
this.path = path
|
||||
}
|
||||
|
||||
for (var i in fs_storage) {
|
||||
if (fs_storage.hasOwnProperty(i)) {
|
||||
Wrapper.prototype[i] = wrapper(i)
|
||||
}
|
||||
}
|
||||
|
||||
function wrapper(method) {
|
||||
return function(/*...*/) {
|
||||
var args = Array.prototype.slice.apply(arguments)
|
||||
args[0] = Path.join(this.path, args[0] || '')
|
||||
return fs_storage[method].apply(null, args)
|
||||
}
|
||||
}
|
||||
|
||||
return Wrapper
|
||||
})()
|
||||
|
||||
module.exports = Storage
|
||||
|
||||
|
||||
@@ -1,28 +1,23 @@
|
||||
var Logger = require('bunyan')
|
||||
, Error = require('http-errors')
|
||||
, Stream = require('stream')
|
||||
, utils = require('./utils')
|
||||
|
||||
function getlvl(x) {
|
||||
if (x < 15) {
|
||||
return 'trace'
|
||||
} else if (x < 25) {
|
||||
return 'debug'
|
||||
} else if (x < 35) {
|
||||
return 'info'
|
||||
} else if (x == 35) {
|
||||
return 'http'
|
||||
} else if (x < 45) {
|
||||
return 'warn'
|
||||
} else if (x < 55) {
|
||||
return 'error'
|
||||
} else {
|
||||
return 'fatal'
|
||||
switch(true) {
|
||||
case x < 15: return 'trace'
|
||||
case x < 25: return 'debug'
|
||||
case x < 35: return 'info'
|
||||
case x == 35: return 'http'
|
||||
case x < 45: return 'warn'
|
||||
case x < 55: return 'error'
|
||||
default: return 'fatal'
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.setup = function(logs) {
|
||||
var streams = []
|
||||
if (!logs) logs = [{ type: 'stdout', format: 'pretty', level: 'http' }]
|
||||
if (logs == null) logs = [{ type: 'stdout', format: 'pretty', level: 'http' }]
|
||||
|
||||
logs.forEach(function(target) {
|
||||
var stream = new Stream()
|
||||
@@ -35,11 +30,11 @@ module.exports.setup = function(logs) {
|
||||
if (target.format === 'pretty') {
|
||||
// making fake stream for prettypritting
|
||||
stream.write = function(obj) {
|
||||
dest.write(print(obj.level, obj.msg, obj, dest.isTTY) + "\n")
|
||||
dest.write(print(obj.level, obj.msg, obj, dest.isTTY) + '\n')
|
||||
}
|
||||
} else {
|
||||
stream.write = function(obj) {
|
||||
dest.write(JSON.stringify(obj, Logger.safeCycles()) + "\n")
|
||||
dest.write(JSON.stringify(obj, Logger.safeCycles()) + '\n')
|
||||
}
|
||||
}
|
||||
} else if (target.type === 'file') {
|
||||
@@ -48,15 +43,19 @@ module.exports.setup = function(logs) {
|
||||
Logger.emit('error', err)
|
||||
})
|
||||
stream.write = function(obj) {
|
||||
dest.write(JSON.stringify(obj, Logger.safeCycles()) + "\n")
|
||||
if (target.format === 'pretty') {
|
||||
dest.write(print(obj.level, obj.msg, obj, false) + '\n')
|
||||
} else {
|
||||
dest.write(JSON.stringify(obj, Logger.safeCycles()) + '\n')
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new Error('wrong target type for a log')
|
||||
throw Error('wrong target type for a log')
|
||||
}
|
||||
|
||||
if (target.level === 'http') target.level = 35
|
||||
streams.push({
|
||||
type: "raw",
|
||||
type: 'raw',
|
||||
level: target.level || 35,
|
||||
stream: stream,
|
||||
})
|
||||
@@ -101,10 +100,10 @@ function pad(str) {
|
||||
}
|
||||
|
||||
var subsystems = [{
|
||||
in: '\x1b[32m<--\x1b[39m',
|
||||
out: '\x1b[33m-->\x1b[39m',
|
||||
fs: '\x1b[90m-=-\x1b[39m',
|
||||
default: '\x1b[34m---\x1b[39m',
|
||||
in: '\033[32m<--\033[39m',
|
||||
out: '\033[33m-->\033[39m',
|
||||
fs: '\033[90m-=-\033[39m',
|
||||
default: '\033[34m---\033[39m',
|
||||
}, {
|
||||
in: '<--',
|
||||
out: '-->',
|
||||
@@ -132,12 +131,12 @@ function print(type, msg, obj, colors) {
|
||||
}
|
||||
|
||||
if (typeof(str) === 'string') {
|
||||
if (!colors) {
|
||||
if (!colors || ~str.indexOf('\n')) {
|
||||
return str
|
||||
} else if (is_error) {
|
||||
return '\x1b[31m' + str + '\x1b[39m'
|
||||
return '\033[31m' + str + '\033[39m'
|
||||
} else {
|
||||
return '\x1b[32m' + str + '\x1b[39m'
|
||||
return '\033[32m' + str + '\033[39m'
|
||||
}
|
||||
} else {
|
||||
return require('util').inspect(str, void 0, void 0, colors)
|
||||
@@ -147,9 +146,9 @@ function print(type, msg, obj, colors) {
|
||||
// ^^--- black magic... kidding, just "colors ? 0 : 1"
|
||||
|
||||
if (colors) {
|
||||
return " \x1b[" + levels[type] + "m" + (pad(type)) + "\x1b[39m " + sub + " " + finalmsg
|
||||
return ' \033[' + levels[type] + 'm' + (pad(type)) + '\033[39m ' + sub + ' ' + finalmsg
|
||||
} else {
|
||||
return " " + (pad(type)) + " " + sub + " " + finalmsg
|
||||
return ' ' + (pad(type)) + ' ' + sub + ' ' + finalmsg
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,27 +1,24 @@
|
||||
var crypto = require('crypto')
|
||||
, Error = require('http-errors')
|
||||
, utils = require('./utils')
|
||||
, UError = require('./error').UserError
|
||||
, Logger = require('./logger')
|
||||
|
||||
module.exports.validate_name = function validate_name(req, res, next, value, name) {
|
||||
if (utils.validate_name(req.params.package)) {
|
||||
req.params.package = String(req.params.package)
|
||||
if (value.charAt(0) === '-') {
|
||||
// special case in couchdb usually
|
||||
next('route')
|
||||
} else if (utils.validate_name(value)) {
|
||||
next()
|
||||
} else {
|
||||
next(new UError({
|
||||
status: 403,
|
||||
msg: 'invalid package name',
|
||||
}))
|
||||
next(Error[403]('invalid ' + name))
|
||||
}
|
||||
}
|
||||
|
||||
module.exports.media = function media(expect) {
|
||||
return function(req, res, next) {
|
||||
if (req.headers['content-type'] !== expect) {
|
||||
next(new UError({
|
||||
status: 415,
|
||||
msg: 'wrong content-type, expect: '+expect+', got: '+req.headers['content-type'],
|
||||
}))
|
||||
next(Error[415]('wrong content-type, expect: ' + expect
|
||||
+ ', got: '+req.headers['content-type']))
|
||||
} else {
|
||||
next()
|
||||
}
|
||||
@@ -32,50 +29,24 @@ module.exports.expect_json = function expect_json(req, res, next) {
|
||||
if (!utils.is_object(req.body)) {
|
||||
return next({
|
||||
status: 400,
|
||||
msg: 'can\'t parse incoming json',
|
||||
message: 'can\'t parse incoming json',
|
||||
})
|
||||
}
|
||||
next()
|
||||
}
|
||||
|
||||
module.exports.basic_auth = function basic_auth(callback) {
|
||||
module.exports.anti_loop = function(config) {
|
||||
return function(req, res, next) {
|
||||
var authorization = req.headers.authorization
|
||||
|
||||
if (req.user) return next()
|
||||
if (authorization == null) {
|
||||
req.user = req.remoteUser = undefined
|
||||
return next()
|
||||
}
|
||||
|
||||
var parts = authorization.split(' ')
|
||||
|
||||
if (parts.length !== 2) return next({
|
||||
status: 400,
|
||||
msg: 'bad authorization header',
|
||||
})
|
||||
|
||||
var scheme = parts[0]
|
||||
, credentials = new Buffer(parts[1], 'base64').toString()
|
||||
, index = credentials.indexOf(':')
|
||||
|
||||
if ('Basic' != scheme || index < 0) return next({
|
||||
status: 400,
|
||||
msg: 'bad authorization header',
|
||||
})
|
||||
|
||||
var user = credentials.slice(0, index)
|
||||
, pass = credentials.slice(index + 1)
|
||||
|
||||
if (callback(user, pass)) {
|
||||
req.user = req.remoteUser = user
|
||||
next()
|
||||
} else {
|
||||
next({
|
||||
status: 403,
|
||||
msg: 'bad username/password, access denied',
|
||||
})
|
||||
if (req.headers.via != null) {
|
||||
var arr = req.headers.via.split(',')
|
||||
for (var i=0; i<arr.length; i++) {
|
||||
var m = arr[i].match(/\s*(\S+)\s+(\S+)/)
|
||||
if (m && m[2] === config.server_id) {
|
||||
return next(Error[508]('loop detected'))
|
||||
}
|
||||
}
|
||||
}
|
||||
next()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,23 +67,42 @@ module.exports.log_and_etagify = function(req, res, next) {
|
||||
if (_auth) req.headers.authorization = _auth
|
||||
|
||||
var bytesin = 0
|
||||
req.on('data', function(chunk){ bytesin += chunk.length })
|
||||
req.on('data', function(chunk) {
|
||||
bytesin += chunk.length
|
||||
})
|
||||
|
||||
var _send = res.send
|
||||
res.send = function(body) {
|
||||
if (typeof(body) === 'string' || typeof(body) === 'object') {
|
||||
res.header('Content-type', 'application/json')
|
||||
|
||||
if (typeof(body) === 'object' && body != null) {
|
||||
if (body.error) {
|
||||
res._sinopia_error = body.error
|
||||
try {
|
||||
if (typeof(body) === 'string' || typeof(body) === 'object') {
|
||||
if (!res.getHeader('Content-type')) {
|
||||
res.header('Content-type', 'application/json')
|
||||
}
|
||||
body = JSON.stringify(body, undefined, '\t')
|
||||
}
|
||||
|
||||
res.header('ETag', '"' + md5sum(body) + '"')
|
||||
} else {
|
||||
// send(null), send(204), etc.
|
||||
if (typeof(body) === 'object' && body != null) {
|
||||
if (typeof(body.error) === 'string') {
|
||||
res._sinopia_error = body.error
|
||||
}
|
||||
body = JSON.stringify(body, undefined, '\t') + '\n'
|
||||
}
|
||||
|
||||
// don't send etags with errors
|
||||
if (!res.statusCode || (res.statusCode >= 200 && res.statusCode < 300)) {
|
||||
res.header('ETag', '"' + md5sum(body) + '"')
|
||||
}
|
||||
} else {
|
||||
// send(null), send(204), etc.
|
||||
}
|
||||
} catch(err) {
|
||||
// if sinopia sends headers first, and then calls res.send()
|
||||
// as an error handler, we can't report error properly,
|
||||
// and should just close socket
|
||||
if (err.message.match(/set headers after they are sent/)) {
|
||||
if (res.socket != null) res.socket.destroy()
|
||||
return
|
||||
} else {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
res.send = _send
|
||||
@@ -127,23 +117,23 @@ module.exports.log_and_etagify = function(req, res, next) {
|
||||
}
|
||||
|
||||
function log() {
|
||||
var msg = '@{status}, user: @{user}, req: \'@{request.method} @{request.url}\''
|
||||
var message = '@{status}, user: @{user}, req: \'@{request.method} @{request.url}\''
|
||||
if (res._sinopia_error) {
|
||||
msg += ', error: @{!error}'
|
||||
message += ', error: @{!error}'
|
||||
} else {
|
||||
msg += ', bytes: @{bytes.in}/@{bytes.out}'
|
||||
message += ', bytes: @{bytes.in}/@{bytes.out}'
|
||||
}
|
||||
req.log.warn({
|
||||
request: {method: req.method, url: req.url},
|
||||
level: 35, // http
|
||||
user: req.user,
|
||||
user: req.remote_user.name,
|
||||
status: res.statusCode,
|
||||
error: res._sinopia_error,
|
||||
bytes: {
|
||||
in: bytesin,
|
||||
out: bytesout,
|
||||
}
|
||||
}, msg)
|
||||
}, message)
|
||||
}
|
||||
|
||||
req.on('close', function() {
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
//
|
||||
// Get this thingy from `npmconf` package if it ever changes...
|
||||
//
|
||||
|
||||
module.exports = // the npm CA certificate.
|
||||
[ "-----BEGIN CERTIFICATE-----\n"+
|
||||
"MIIChzCCAfACCQDauvz/KHp8ejANBgkqhkiG9w0BAQUFADCBhzELMAkGA1UEBhMC\n"+
|
||||
"VVMxCzAJBgNVBAgTAkNBMRAwDgYDVQQHEwdPYWtsYW5kMQwwCgYDVQQKEwNucG0x\n"+
|
||||
"IjAgBgNVBAsTGW5wbSBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxDjAMBgNVBAMTBW5w\n"+
|
||||
"bUNBMRcwFQYJKoZIhvcNAQkBFghpQGl6cy5tZTAeFw0xMTA5MDUwMTQ3MTdaFw0y\n"+
|
||||
"MTA5MDIwMTQ3MTdaMIGHMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEDAOBgNV\n"+
|
||||
"BAcTB09ha2xhbmQxDDAKBgNVBAoTA25wbTEiMCAGA1UECxMZbnBtIENlcnRpZmlj\n"+
|
||||
"YXRlIEF1dGhvcml0eTEOMAwGA1UEAxMFbnBtQ0ExFzAVBgkqhkiG9w0BCQEWCGlA\n"+
|
||||
"aXpzLm1lMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDLI4tIqPpRW+ACw9GE\n"+
|
||||
"OgBlJZwK5f8nnKCLK629Pv5yJpQKs3DENExAyOgDcyaF0HD0zk8zTp+ZsLaNdKOz\n"+
|
||||
"Gn2U181KGprGKAXP6DU6ByOJDWmTlY6+Ad1laYT0m64fERSpHw/hjD3D+iX4aMOl\n"+
|
||||
"y0HdbT5m1ZGh6SJz3ZqxavhHLQIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAC4ySDbC\n"+
|
||||
"l7W1WpLmtLGEQ/yuMLUf6Jy/vr+CRp4h+UzL+IQpCv8FfxsYE7dhf/bmWTEupBkv\n"+
|
||||
"yNL18lipt2jSvR3v6oAHAReotvdjqhxddpe5Holns6EQd1/xEZ7sB1YhQKJtvUrl\n"+
|
||||
"ZNufy1Jf1r0ldEGeA+0ISck7s+xSh9rQD2Op\n"+
|
||||
"-----END CERTIFICATE-----\n",
|
||||
|
||||
// "GlobalSign Root CA"
|
||||
"-----BEGIN CERTIFICATE-----\n"+
|
||||
"MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkGA1UEBhMCQkUx\n"+
|
||||
"GTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jvb3QgQ0ExGzAZBgNVBAMTEkds\n"+
|
||||
"b2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAwMDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNV\n"+
|
||||
"BAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYD\n"+
|
||||
"VQQDExJHbG9iYWxTaWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDa\n"+
|
||||
"DuaZjc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavpxy0Sy6sc\n"+
|
||||
"THAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp1Wrjsok6Vjk4bwY8iGlb\n"+
|
||||
"Kk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdGsnUOhugZitVtbNV4FpWi6cgKOOvyJBNP\n"+
|
||||
"c1STE4U6G7weNLWLBYy5d4ux2x8gkasJU26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrX\n"+
|
||||
"gzT/LCrBbBlDSgeF59N89iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV\n"+
|
||||
"HRMBAf8EBTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0BAQUF\n"+
|
||||
"AAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOzyj1hTdNGCbM+w6Dj\n"+
|
||||
"Y1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE38NflNUVyRRBnMRddWQVDf9VMOyG\n"+
|
||||
"j/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymPAbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhH\n"+
|
||||
"hm4qxFYxldBniYUr+WymXUadDKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveC\n"+
|
||||
"X4XSQRjbgbMEHMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A==\n"+
|
||||
"-----END CERTIFICATE-----\n",
|
||||
|
||||
// "GlobalSign Root CA - R2"
|
||||
"-----BEGIN CERTIFICATE-----\n"+
|
||||
"MIIDujCCAqKgAwIBAgILBAAAAAABD4Ym5g0wDQYJKoZIhvcNAQEFBQAwTDEgMB4GA1UECxMXR2xv\n"+
|
||||
"YmFsU2lnbiBSb290IENBIC0gUjIxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkdsb2Jh\n"+
|
||||
"bFNpZ24wHhcNMDYxMjE1MDgwMDAwWhcNMjExMjE1MDgwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxT\n"+
|
||||
"aWduIFJvb3QgQ0EgLSBSMjETMBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2ln\n"+
|
||||
"bjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKbPJA6+Lm8omUVCxKs+IVSbC9N/hHD6\n"+
|
||||
"ErPLv4dfxn+G07IwXNb9rfF73OX4YJYJkhD10FPe+3t+c4isUoh7SqbKSaZeqKeMWhG8eoLrvozp\n"+
|
||||
"s6yWJQeXSpkqBy+0Hne/ig+1AnwblrjFuTosvNYSuetZfeLQBoZfXklqtTleiDTsvHgMCJiEbKjN\n"+
|
||||
"S7SgfQx5TfC4LcshytVsW33hoCmEofnTlEnLJGKRILzdC9XZzPnqJworc5HGnRusyMvo4KD0L5CL\n"+
|
||||
"TfuwNhv2GXqF4G3yYROIXJ/gkwpRl4pazq+r1feqCapgvdzZX99yqWATXgAByUr6P6TqBwMhAo6C\n"+
|
||||
"ygPCm48CAwEAAaOBnDCBmTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E\n"+
|
||||
"FgQUm+IHV2ccHsBqBt5ZtJot39wZhi4wNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL2NybC5nbG9i\n"+
|
||||
"YWxzaWduLm5ldC9yb290LXIyLmNybDAfBgNVHSMEGDAWgBSb4gdXZxwewGoG3lm0mi3f3BmGLjAN\n"+
|
||||
"BgkqhkiG9w0BAQUFAAOCAQEAmYFThxxol4aR7OBKuEQLq4GsJ0/WwbgcQ3izDJr86iw8bmEbTUsp\n"+
|
||||
"9Z8FHSbBuOmDAGJFtqkIk7mpM0sYmsL4h4hO291xNBrBVNpGP+DTKqttVCL1OmLNIG+6KYnX3ZHu\n"+
|
||||
"01yiPqFbQfXf5WRDLenVOavSot+3i9DAgBkcRcAtjOj4LaR0VknFBbVPFd5uRHg5h6h+u/N5GJG7\n"+
|
||||
"9G+dwfCMNYxdAfvDbbnvRG15RjF+Cv6pgsH/76tuIMRQyV+dTZsXjAzlAcmgQWpzU/qlULRuJQ/7\n"+
|
||||
"TBj0/VLZjmmx6BEP3ojY+x1J96relc8geMJgEtslQIxq/H5COEBkEveegeGTLg==\n"+
|
||||
"-----END CERTIFICATE-----\n" ]
|
||||
45
lib/search.js
Normal file
45
lib/search.js
Normal file
@@ -0,0 +1,45 @@
|
||||
var lunr = require('lunr')
|
||||
|
||||
var Search = function() {
|
||||
this.index = lunr(function () {
|
||||
this.field('name', {boost: 10});
|
||||
this.field('description', {boost: 4});
|
||||
this.field('author', {boost: 6});
|
||||
this.field('readme');
|
||||
});
|
||||
};
|
||||
|
||||
Search.prototype = {
|
||||
query: function(q) {
|
||||
return this.index.search(q);
|
||||
},
|
||||
add: function(package) {
|
||||
this.index.add({
|
||||
id: package.name,
|
||||
name: package.name,
|
||||
description: package.description,
|
||||
author: package._npmUser ? package._npmUser.name : '???'
|
||||
});
|
||||
},
|
||||
remove: function(name) {
|
||||
this.index.remove({
|
||||
id: name
|
||||
});
|
||||
},
|
||||
reindex: function() {
|
||||
var self = this;
|
||||
this.storage.get_local(function(err, packages) {
|
||||
var i = packages.length;
|
||||
|
||||
while(i--) {
|
||||
self.add(packages[i]);
|
||||
}
|
||||
});
|
||||
},
|
||||
configureStorage: function(storage) {
|
||||
this.storage = storage;
|
||||
this.reindex();
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = new Search();
|
||||
BIN
lib/static/ajax.gif
Normal file
BIN
lib/static/ajax.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.9 KiB |
BIN
lib/static/favicon.ico
Normal file
BIN
lib/static/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.9 KiB |
BIN
lib/static/fontello.eot
Normal file
BIN
lib/static/fontello.eot
Normal file
Binary file not shown.
15
lib/static/fontello.svg
Normal file
15
lib/static/fontello.svg
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg xmlns="http://www.w3.org/2000/svg">
|
||||
<metadata>Copyright (C) 2014 by original authors @ fontello.com</metadata>
|
||||
<defs>
|
||||
<font id="fontello" horiz-adv-x="1000" >
|
||||
<font-face font-family="fontello" font-weight="400" font-stretch="normal" units-per-em="1000" ascent="850" descent="-150" />
|
||||
<missing-glyph horiz-adv-x="1000" />
|
||||
<glyph glyph-name="search" unicode="" d="m643 386q0 103-74 176t-176 74-177-74-73-176 73-177 177-73 176 73 74 177z m286-465q0-29-22-50t-50-21q-30 0-50 21l-191 191q-100-69-223-69-80 0-153 31t-125 84-84 125-31 153 31 152 84 126 125 84 153 31 152-31 126-84 84-126 31-152q0-123-69-223l191-191q21-21 21-51z" horiz-adv-x="928.6" />
|
||||
<glyph glyph-name="cancel" unicode="" d="m724 112q0-22-15-38l-76-76q-16-15-38-15t-38 15l-164 165-164-165q-16-15-38-15t-38 15l-76 76q-16 16-16 38t16 38l164 164-164 164q-16 16-16 38t16 38l76 76q16 16 38 16t38-16l164-164 164 164q16 16 38 16t38-16l76-76q15-15 15-38t-15-38l-164-164 164-164q15-15 15-38z" horiz-adv-x="785.7" />
|
||||
<glyph glyph-name="right-open" unicode="" d="m613 386q0-29-20-51l-364-363q-21-21-50-21t-51 21l-42 42q-21 21-21 50 0 30 21 51l271 271-271 270q-21 22-21 51 0 30 21 50l42 42q20 21 51 21t50-21l364-363q20-21 20-50z" horiz-adv-x="642.9" />
|
||||
<glyph glyph-name="angle-right" unicode="" d="m332 314q0-7-6-13l-260-260q-5-5-12-5t-13 5l-28 28q-6 6-6 13t6 13l219 219-219 220q-6 5-6 12t6 13l28 28q5 6 13 6t12-6l260-260q6-5 6-13z" horiz-adv-x="357.1" />
|
||||
</font>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
BIN
lib/static/fontello.ttf
Normal file
BIN
lib/static/fontello.ttf
Normal file
Binary file not shown.
BIN
lib/static/fontello.woff
Normal file
BIN
lib/static/fontello.woff
Normal file
Binary file not shown.
4
lib/static/jquery.min.js
vendored
Normal file
4
lib/static/jquery.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
lib/static/logo-sm.png
Normal file
BIN
lib/static/logo-sm.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.4 KiB |
BIN
lib/static/logo.png
Normal file
BIN
lib/static/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
7252
lib/static/main.css
Normal file
7252
lib/static/main.css
Normal file
File diff suppressed because one or more lines are too long
947
lib/static/main.js
Normal file
947
lib/static/main.js
Normal file
@@ -0,0 +1,947 @@
|
||||
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
|
||||
var templater = require("handlebars/runtime").default.template;module.exports = templater(function (Handlebars,depth0,helpers,partials,data) {
|
||||
this.compilerInfo = [4,'>= 1.0.0'];
|
||||
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
|
||||
var buffer = "", stack1, helper, functionType="function", escapeExpression=this.escapeExpression;
|
||||
|
||||
|
||||
buffer += "<div class=\"entry\" data-name=\"";
|
||||
if (helper = helpers.name) { stack1 = helper.call(depth0, {hash:{},data:data}); }
|
||||
else { helper = (depth0 && depth0.name); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
|
||||
buffer += escapeExpression(stack1)
|
||||
+ "\" data-version=\"";
|
||||
if (helper = helpers.version) { stack1 = helper.call(depth0, {hash:{},data:data}); }
|
||||
else { helper = (depth0 && depth0.version); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
|
||||
buffer += escapeExpression(stack1)
|
||||
+ "\">\n <div class=\"row\">\n <div class=\"col-md-8 col-sm-8\">\n <h4 class=\"title\">\n <a class='name icon-angle-right red' href='javascript:void(0)'>";
|
||||
if (helper = helpers.name) { stack1 = helper.call(depth0, {hash:{},data:data}); }
|
||||
else { helper = (depth0 && depth0.name); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
|
||||
buffer += escapeExpression(stack1)
|
||||
+ "</a>\n <small class='version'>v";
|
||||
if (helper = helpers.version) { stack1 = helper.call(depth0, {hash:{},data:data}); }
|
||||
else { helper = (depth0 && depth0.version); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
|
||||
buffer += escapeExpression(stack1)
|
||||
+ "</small>\n </h4>\n </div>\n <div class=\"col-md-4 col-sm-4\">\n <div class=\"author pull-right\">\n <small>By: "
|
||||
+ escapeExpression(((stack1 = ((stack1 = (depth0 && depth0._npmUser)),stack1 == null || stack1 === false ? stack1 : stack1.name)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
|
||||
+ "</small>\n </div>\n </div>\n </div>\n <div class=\"row\">\n <div class=\"col-md-12\">\n <p class=\"description\">";
|
||||
if (helper = helpers.description) { stack1 = helper.call(depth0, {hash:{},data:data}); }
|
||||
else { helper = (depth0 && depth0.description); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
|
||||
buffer += escapeExpression(stack1)
|
||||
+ "</p>\n </div>\n </div>\n</div>\n";
|
||||
return buffer;
|
||||
});
|
||||
},{"handlebars/runtime":11}],2:[function(require,module,exports){
|
||||
var $ = require('unopinionate').selector,
|
||||
onClick = require('onclick'),
|
||||
transitionComplete = require('transition-complete');
|
||||
|
||||
$(function() {
|
||||
onClick('.entry .name', function() {
|
||||
var $this = $(this),
|
||||
$entry = $this.closest('.entry');
|
||||
|
||||
//Close entry
|
||||
if($entry.hasClass('open')) {
|
||||
$entry
|
||||
.height($entry.outerHeight())
|
||||
.removeClass('open');
|
||||
|
||||
setTimeout(function() {
|
||||
$entry.css('height', $entry.attr('data-height') + 'px');
|
||||
}, 0);
|
||||
|
||||
transitionComplete(function() {
|
||||
$entry.find('.readme').remove();
|
||||
$entry.css('height', 'auto');
|
||||
});
|
||||
}
|
||||
//Open entry
|
||||
else {
|
||||
//Close open entries
|
||||
$('.entry.open').each(function() {
|
||||
var $entry = $(this);
|
||||
$entry
|
||||
.height($entry.outerHeight())
|
||||
.removeClass('open');
|
||||
|
||||
setTimeout(function() {
|
||||
$entry.css('height', $entry.attr('data-height') + 'px');
|
||||
}, 0);
|
||||
|
||||
transitionComplete(function() {
|
||||
$entry.find('.readme').remove();
|
||||
$entry.css('height', 'auto');
|
||||
});
|
||||
});
|
||||
|
||||
//Add the open class
|
||||
$entry.addClass('open');
|
||||
|
||||
//Explicitly set heights for transitions
|
||||
var height = $entry.outerHeight();
|
||||
$entry
|
||||
.attr('data-height', height)
|
||||
.css('height', height);
|
||||
|
||||
//Get the data
|
||||
$.ajax({
|
||||
url: '-/readme/'+$entry.attr('data-name')+'/'+$entry.attr('data-version'),
|
||||
dataType: 'text',
|
||||
success: function(html) {
|
||||
var $readme = $("<div class='readme'>")
|
||||
.html(html)
|
||||
.appendTo($entry);
|
||||
|
||||
$entry.height(height + $readme.outerHeight());
|
||||
|
||||
transitionComplete(function() {
|
||||
$entry.css('height', 'auto');
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
},{"onclick":12,"transition-complete":14,"unopinionate":15}],3:[function(require,module,exports){
|
||||
require('./search');
|
||||
require('./entry');
|
||||
|
||||
},{"./entry":2,"./search":4}],4:[function(require,module,exports){
|
||||
var $ = require('unopinionate').selector,
|
||||
template = require('../entry.hbs'),
|
||||
onScroll = require('onscroll');
|
||||
|
||||
$(function() {
|
||||
'use strict';
|
||||
|
||||
(function( window, document ) {
|
||||
|
||||
var $form = $('#search-form');
|
||||
var $input = $form.find('input');
|
||||
var $body = $('body');
|
||||
var $clear = $form.find('.clear');
|
||||
var $searchResults = $("#search-results");
|
||||
var $pkgListing = $("#all-packages");
|
||||
var $searchBtn = $('.js-search-btn');
|
||||
var request;
|
||||
var currentResults;
|
||||
|
||||
var toggle = function( validQuery ) {
|
||||
$searchResults.toggleClass( 'show', validQuery );
|
||||
$pkgListing.toggleClass( 'hide', validQuery );
|
||||
|
||||
|
||||
$searchBtn.find('i').toggleClass( 'icon-cancel', validQuery );
|
||||
$searchBtn.find('i').toggleClass( 'icon-search', !validQuery );
|
||||
};
|
||||
|
||||
$form.bind('submit keyup', function(e) {
|
||||
var q, qBool;
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
q = $input.val();
|
||||
qBool = q !== '';
|
||||
|
||||
toggle( qBool );
|
||||
|
||||
if( !qBool ) {
|
||||
if( request && typeof request.abort === 'function' ) {
|
||||
request.abort();
|
||||
}
|
||||
|
||||
currentResults = null;
|
||||
$searchResults.html('');
|
||||
return;
|
||||
}
|
||||
|
||||
if( request && typeof request.abort === 'function' ) {
|
||||
request.abort();
|
||||
}
|
||||
|
||||
if( !currentResults ) {
|
||||
$searchResults.html( "<img class='search-ajax' src='-/static/ajax.gif' alt='Spinner'/>" );
|
||||
}
|
||||
|
||||
request = $.getJSON('-/search/' + q, function( results ) {
|
||||
currentResults = results;
|
||||
|
||||
if( results.length > 0 ) {
|
||||
var html = '';
|
||||
|
||||
$.each(results, function( i, entry ) {
|
||||
html += template( entry );
|
||||
});
|
||||
|
||||
$searchResults.html(html);
|
||||
} else {
|
||||
$searchResults.html("<div class='no-results'><big>No Results</big></div>");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$( document ).on( 'click', '.icon-cancel', function( e ) {
|
||||
e.preventDefault();
|
||||
$input.val('');
|
||||
$form.keyup();
|
||||
});
|
||||
|
||||
})( window, window.document );
|
||||
});
|
||||
|
||||
},{"../entry.hbs":1,"onscroll":13,"unopinionate":15}],5:[function(require,module,exports){
|
||||
"use strict";
|
||||
/*globals Handlebars: true */
|
||||
var base = require("./handlebars/base");
|
||||
|
||||
// Each of these augment the Handlebars object. No need to setup here.
|
||||
// (This is done to easily share code between commonjs and browse envs)
|
||||
var SafeString = require("./handlebars/safe-string")["default"];
|
||||
var Exception = require("./handlebars/exception")["default"];
|
||||
var Utils = require("./handlebars/utils");
|
||||
var runtime = require("./handlebars/runtime");
|
||||
|
||||
// For compatibility and usage outside of module systems, make the Handlebars object a namespace
|
||||
var create = function() {
|
||||
var hb = new base.HandlebarsEnvironment();
|
||||
|
||||
Utils.extend(hb, base);
|
||||
hb.SafeString = SafeString;
|
||||
hb.Exception = Exception;
|
||||
hb.Utils = Utils;
|
||||
|
||||
hb.VM = runtime;
|
||||
hb.template = function(spec) {
|
||||
return runtime.template(spec, hb);
|
||||
};
|
||||
|
||||
return hb;
|
||||
};
|
||||
|
||||
var Handlebars = create();
|
||||
Handlebars.create = create;
|
||||
|
||||
exports["default"] = Handlebars;
|
||||
},{"./handlebars/base":6,"./handlebars/exception":7,"./handlebars/runtime":8,"./handlebars/safe-string":9,"./handlebars/utils":10}],6:[function(require,module,exports){
|
||||
"use strict";
|
||||
var Utils = require("./utils");
|
||||
var Exception = require("./exception")["default"];
|
||||
|
||||
var VERSION = "1.3.0";
|
||||
exports.VERSION = VERSION;var COMPILER_REVISION = 4;
|
||||
exports.COMPILER_REVISION = COMPILER_REVISION;
|
||||
var REVISION_CHANGES = {
|
||||
1: '<= 1.0.rc.2', // 1.0.rc.2 is actually rev2 but doesn't report it
|
||||
2: '== 1.0.0-rc.3',
|
||||
3: '== 1.0.0-rc.4',
|
||||
4: '>= 1.0.0'
|
||||
};
|
||||
exports.REVISION_CHANGES = REVISION_CHANGES;
|
||||
var isArray = Utils.isArray,
|
||||
isFunction = Utils.isFunction,
|
||||
toString = Utils.toString,
|
||||
objectType = '[object Object]';
|
||||
|
||||
function HandlebarsEnvironment(helpers, partials) {
|
||||
this.helpers = helpers || {};
|
||||
this.partials = partials || {};
|
||||
|
||||
registerDefaultHelpers(this);
|
||||
}
|
||||
|
||||
exports.HandlebarsEnvironment = HandlebarsEnvironment;HandlebarsEnvironment.prototype = {
|
||||
constructor: HandlebarsEnvironment,
|
||||
|
||||
logger: logger,
|
||||
log: log,
|
||||
|
||||
registerHelper: function(name, fn, inverse) {
|
||||
if (toString.call(name) === objectType) {
|
||||
if (inverse || fn) { throw new Exception('Arg not supported with multiple helpers'); }
|
||||
Utils.extend(this.helpers, name);
|
||||
} else {
|
||||
if (inverse) { fn.not = inverse; }
|
||||
this.helpers[name] = fn;
|
||||
}
|
||||
},
|
||||
|
||||
registerPartial: function(name, str) {
|
||||
if (toString.call(name) === objectType) {
|
||||
Utils.extend(this.partials, name);
|
||||
} else {
|
||||
this.partials[name] = str;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function registerDefaultHelpers(instance) {
|
||||
instance.registerHelper('helperMissing', function(arg) {
|
||||
if(arguments.length === 2) {
|
||||
return undefined;
|
||||
} else {
|
||||
throw new Exception("Missing helper: '" + arg + "'");
|
||||
}
|
||||
});
|
||||
|
||||
instance.registerHelper('blockHelperMissing', function(context, options) {
|
||||
var inverse = options.inverse || function() {}, fn = options.fn;
|
||||
|
||||
if (isFunction(context)) { context = context.call(this); }
|
||||
|
||||
if(context === true) {
|
||||
return fn(this);
|
||||
} else if(context === false || context == null) {
|
||||
return inverse(this);
|
||||
} else if (isArray(context)) {
|
||||
if(context.length > 0) {
|
||||
return instance.helpers.each(context, options);
|
||||
} else {
|
||||
return inverse(this);
|
||||
}
|
||||
} else {
|
||||
return fn(context);
|
||||
}
|
||||
});
|
||||
|
||||
instance.registerHelper('each', function(context, options) {
|
||||
var fn = options.fn, inverse = options.inverse;
|
||||
var i = 0, ret = "", data;
|
||||
|
||||
if (isFunction(context)) { context = context.call(this); }
|
||||
|
||||
if (options.data) {
|
||||
data = createFrame(options.data);
|
||||
}
|
||||
|
||||
if(context && typeof context === 'object') {
|
||||
if (isArray(context)) {
|
||||
for(var j = context.length; i<j; i++) {
|
||||
if (data) {
|
||||
data.index = i;
|
||||
data.first = (i === 0);
|
||||
data.last = (i === (context.length-1));
|
||||
}
|
||||
ret = ret + fn(context[i], { data: data });
|
||||
}
|
||||
} else {
|
||||
for(var key in context) {
|
||||
if(context.hasOwnProperty(key)) {
|
||||
if(data) {
|
||||
data.key = key;
|
||||
data.index = i;
|
||||
data.first = (i === 0);
|
||||
}
|
||||
ret = ret + fn(context[key], {data: data});
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(i === 0){
|
||||
ret = inverse(this);
|
||||
}
|
||||
|
||||
return ret;
|
||||
});
|
||||
|
||||
instance.registerHelper('if', function(conditional, options) {
|
||||
if (isFunction(conditional)) { conditional = conditional.call(this); }
|
||||
|
||||
// Default behavior is to render the positive path if the value is truthy and not empty.
|
||||
// The `includeZero` option may be set to treat the condtional as purely not empty based on the
|
||||
// behavior of isEmpty. Effectively this determines if 0 is handled by the positive path or negative.
|
||||
if ((!options.hash.includeZero && !conditional) || Utils.isEmpty(conditional)) {
|
||||
return options.inverse(this);
|
||||
} else {
|
||||
return options.fn(this);
|
||||
}
|
||||
});
|
||||
|
||||
instance.registerHelper('unless', function(conditional, options) {
|
||||
return instance.helpers['if'].call(this, conditional, {fn: options.inverse, inverse: options.fn, hash: options.hash});
|
||||
});
|
||||
|
||||
instance.registerHelper('with', function(context, options) {
|
||||
if (isFunction(context)) { context = context.call(this); }
|
||||
|
||||
if (!Utils.isEmpty(context)) return options.fn(context);
|
||||
});
|
||||
|
||||
instance.registerHelper('log', function(context, options) {
|
||||
var level = options.data && options.data.level != null ? parseInt(options.data.level, 10) : 1;
|
||||
instance.log(level, context);
|
||||
});
|
||||
}
|
||||
|
||||
var logger = {
|
||||
methodMap: { 0: 'debug', 1: 'info', 2: 'warn', 3: 'error' },
|
||||
|
||||
// State enum
|
||||
DEBUG: 0,
|
||||
INFO: 1,
|
||||
WARN: 2,
|
||||
ERROR: 3,
|
||||
level: 3,
|
||||
|
||||
// can be overridden in the host environment
|
||||
log: function(level, obj) {
|
||||
if (logger.level <= level) {
|
||||
var method = logger.methodMap[level];
|
||||
if (typeof console !== 'undefined' && console[method]) {
|
||||
console[method].call(console, obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
exports.logger = logger;
|
||||
function log(level, obj) { logger.log(level, obj); }
|
||||
|
||||
exports.log = log;var createFrame = function(object) {
|
||||
var obj = {};
|
||||
Utils.extend(obj, object);
|
||||
return obj;
|
||||
};
|
||||
exports.createFrame = createFrame;
|
||||
},{"./exception":7,"./utils":10}],7:[function(require,module,exports){
|
||||
"use strict";
|
||||
|
||||
var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack'];
|
||||
|
||||
function Exception(message, node) {
|
||||
var line;
|
||||
if (node && node.firstLine) {
|
||||
line = node.firstLine;
|
||||
|
||||
message += ' - ' + line + ':' + node.firstColumn;
|
||||
}
|
||||
|
||||
var tmp = Error.prototype.constructor.call(this, message);
|
||||
|
||||
// Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work.
|
||||
for (var idx = 0; idx < errorProps.length; idx++) {
|
||||
this[errorProps[idx]] = tmp[errorProps[idx]];
|
||||
}
|
||||
|
||||
if (line) {
|
||||
this.lineNumber = line;
|
||||
this.column = node.firstColumn;
|
||||
}
|
||||
}
|
||||
|
||||
Exception.prototype = new Error();
|
||||
|
||||
exports["default"] = Exception;
|
||||
},{}],8:[function(require,module,exports){
|
||||
"use strict";
|
||||
var Utils = require("./utils");
|
||||
var Exception = require("./exception")["default"];
|
||||
var COMPILER_REVISION = require("./base").COMPILER_REVISION;
|
||||
var REVISION_CHANGES = require("./base").REVISION_CHANGES;
|
||||
|
||||
function checkRevision(compilerInfo) {
|
||||
var compilerRevision = compilerInfo && compilerInfo[0] || 1,
|
||||
currentRevision = COMPILER_REVISION;
|
||||
|
||||
if (compilerRevision !== currentRevision) {
|
||||
if (compilerRevision < currentRevision) {
|
||||
var runtimeVersions = REVISION_CHANGES[currentRevision],
|
||||
compilerVersions = REVISION_CHANGES[compilerRevision];
|
||||
throw new Exception("Template was precompiled with an older version of Handlebars than the current runtime. "+
|
||||
"Please update your precompiler to a newer version ("+runtimeVersions+") or downgrade your runtime to an older version ("+compilerVersions+").");
|
||||
} else {
|
||||
// Use the embedded version info since the runtime doesn't know about this revision yet
|
||||
throw new Exception("Template was precompiled with a newer version of Handlebars than the current runtime. "+
|
||||
"Please update your runtime to a newer version ("+compilerInfo[1]+").");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exports.checkRevision = checkRevision;// TODO: Remove this line and break up compilePartial
|
||||
|
||||
function template(templateSpec, env) {
|
||||
if (!env) {
|
||||
throw new Exception("No environment passed to template");
|
||||
}
|
||||
|
||||
// Note: Using env.VM references rather than local var references throughout this section to allow
|
||||
// for external users to override these as psuedo-supported APIs.
|
||||
var invokePartialWrapper = function(partial, name, context, helpers, partials, data) {
|
||||
var result = env.VM.invokePartial.apply(this, arguments);
|
||||
if (result != null) { return result; }
|
||||
|
||||
if (env.compile) {
|
||||
var options = { helpers: helpers, partials: partials, data: data };
|
||||
partials[name] = env.compile(partial, { data: data !== undefined }, env);
|
||||
return partials[name](context, options);
|
||||
} else {
|
||||
throw new Exception("The partial " + name + " could not be compiled when running in runtime-only mode");
|
||||
}
|
||||
};
|
||||
|
||||
// Just add water
|
||||
var container = {
|
||||
escapeExpression: Utils.escapeExpression,
|
||||
invokePartial: invokePartialWrapper,
|
||||
programs: [],
|
||||
program: function(i, fn, data) {
|
||||
var programWrapper = this.programs[i];
|
||||
if(data) {
|
||||
programWrapper = program(i, fn, data);
|
||||
} else if (!programWrapper) {
|
||||
programWrapper = this.programs[i] = program(i, fn);
|
||||
}
|
||||
return programWrapper;
|
||||
},
|
||||
merge: function(param, common) {
|
||||
var ret = param || common;
|
||||
|
||||
if (param && common && (param !== common)) {
|
||||
ret = {};
|
||||
Utils.extend(ret, common);
|
||||
Utils.extend(ret, param);
|
||||
}
|
||||
return ret;
|
||||
},
|
||||
programWithDepth: env.VM.programWithDepth,
|
||||
noop: env.VM.noop,
|
||||
compilerInfo: null
|
||||
};
|
||||
|
||||
return function(context, options) {
|
||||
options = options || {};
|
||||
var namespace = options.partial ? options : env,
|
||||
helpers,
|
||||
partials;
|
||||
|
||||
if (!options.partial) {
|
||||
helpers = options.helpers;
|
||||
partials = options.partials;
|
||||
}
|
||||
var result = templateSpec.call(
|
||||
container,
|
||||
namespace, context,
|
||||
helpers,
|
||||
partials,
|
||||
options.data);
|
||||
|
||||
if (!options.partial) {
|
||||
env.VM.checkRevision(container.compilerInfo);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
exports.template = template;function programWithDepth(i, fn, data /*, $depth */) {
|
||||
var args = Array.prototype.slice.call(arguments, 3);
|
||||
|
||||
var prog = function(context, options) {
|
||||
options = options || {};
|
||||
|
||||
return fn.apply(this, [context, options.data || data].concat(args));
|
||||
};
|
||||
prog.program = i;
|
||||
prog.depth = args.length;
|
||||
return prog;
|
||||
}
|
||||
|
||||
exports.programWithDepth = programWithDepth;function program(i, fn, data) {
|
||||
var prog = function(context, options) {
|
||||
options = options || {};
|
||||
|
||||
return fn(context, options.data || data);
|
||||
};
|
||||
prog.program = i;
|
||||
prog.depth = 0;
|
||||
return prog;
|
||||
}
|
||||
|
||||
exports.program = program;function invokePartial(partial, name, context, helpers, partials, data) {
|
||||
var options = { partial: true, helpers: helpers, partials: partials, data: data };
|
||||
|
||||
if(partial === undefined) {
|
||||
throw new Exception("The partial " + name + " could not be found");
|
||||
} else if(partial instanceof Function) {
|
||||
return partial(context, options);
|
||||
}
|
||||
}
|
||||
|
||||
exports.invokePartial = invokePartial;function noop() { return ""; }
|
||||
|
||||
exports.noop = noop;
|
||||
},{"./base":6,"./exception":7,"./utils":10}],9:[function(require,module,exports){
|
||||
"use strict";
|
||||
// Build out our basic SafeString type
|
||||
function SafeString(string) {
|
||||
this.string = string;
|
||||
}
|
||||
|
||||
SafeString.prototype.toString = function() {
|
||||
return "" + this.string;
|
||||
};
|
||||
|
||||
exports["default"] = SafeString;
|
||||
},{}],10:[function(require,module,exports){
|
||||
"use strict";
|
||||
/*jshint -W004 */
|
||||
var SafeString = require("./safe-string")["default"];
|
||||
|
||||
var escape = {
|
||||
"&": "&",
|
||||
"<": "<",
|
||||
">": ">",
|
||||
'"': """,
|
||||
"'": "'",
|
||||
"`": "`"
|
||||
};
|
||||
|
||||
var badChars = /[&<>"'`]/g;
|
||||
var possible = /[&<>"'`]/;
|
||||
|
||||
function escapeChar(chr) {
|
||||
return escape[chr] || "&";
|
||||
}
|
||||
|
||||
function extend(obj, value) {
|
||||
for(var key in value) {
|
||||
if(Object.prototype.hasOwnProperty.call(value, key)) {
|
||||
obj[key] = value[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exports.extend = extend;var toString = Object.prototype.toString;
|
||||
exports.toString = toString;
|
||||
// Sourced from lodash
|
||||
// https://github.com/bestiejs/lodash/blob/master/LICENSE.txt
|
||||
var isFunction = function(value) {
|
||||
return typeof value === 'function';
|
||||
};
|
||||
// fallback for older versions of Chrome and Safari
|
||||
if (isFunction(/x/)) {
|
||||
isFunction = function(value) {
|
||||
return typeof value === 'function' && toString.call(value) === '[object Function]';
|
||||
};
|
||||
}
|
||||
var isFunction;
|
||||
exports.isFunction = isFunction;
|
||||
var isArray = Array.isArray || function(value) {
|
||||
return (value && typeof value === 'object') ? toString.call(value) === '[object Array]' : false;
|
||||
};
|
||||
exports.isArray = isArray;
|
||||
|
||||
function escapeExpression(string) {
|
||||
// don't escape SafeStrings, since they're already safe
|
||||
if (string instanceof SafeString) {
|
||||
return string.toString();
|
||||
} else if (!string && string !== 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// Force a string conversion as this will be done by the append regardless and
|
||||
// the regex test will do this transparently behind the scenes, causing issues if
|
||||
// an object's to string has escaped characters in it.
|
||||
string = "" + string;
|
||||
|
||||
if(!possible.test(string)) { return string; }
|
||||
return string.replace(badChars, escapeChar);
|
||||
}
|
||||
|
||||
exports.escapeExpression = escapeExpression;function isEmpty(value) {
|
||||
if (!value && value !== 0) {
|
||||
return true;
|
||||
} else if (isArray(value) && value.length === 0) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
exports.isEmpty = isEmpty;
|
||||
},{"./safe-string":9}],11:[function(require,module,exports){
|
||||
// Create a simple path alias to allow browserify to resolve
|
||||
// the runtime on a supported path.
|
||||
module.exports = require('./dist/cjs/handlebars.runtime');
|
||||
|
||||
},{"./dist/cjs/handlebars.runtime":5}],12:[function(require,module,exports){
|
||||
var $ = require('unopinionate').selector;
|
||||
|
||||
var $document = $(document),
|
||||
bindings = {};
|
||||
|
||||
var click = function(events) {
|
||||
click.bind.apply(click, arguments);
|
||||
return click;
|
||||
};
|
||||
|
||||
/*** Configuration Options ***/
|
||||
click.distanceLimit = 10;
|
||||
click.timeLimit = 140;
|
||||
|
||||
/*** Useful Properties ***/
|
||||
click.isTouch = ('ontouchstart' in window) ||
|
||||
window.DocumentTouch &&
|
||||
document instanceof DocumentTouch;
|
||||
|
||||
/*** Cached Functions ***/
|
||||
var onTouchstart = function(e) {
|
||||
e.stopPropagation(); //Prevents multiple click events from happening
|
||||
|
||||
click._doAnywheres(e);
|
||||
|
||||
var $this = $(this),
|
||||
startTime = new Date().getTime(),
|
||||
startPos = click._getPos(e);
|
||||
|
||||
$this.one('touchend', function(e) {
|
||||
e.preventDefault(); //Prevents click event from firing
|
||||
|
||||
var time = new Date().getTime() - startTime,
|
||||
endPos = click._getPos(e),
|
||||
distance = Math.sqrt(
|
||||
Math.pow(endPos.x - startPos.x, 2) +
|
||||
Math.pow(endPos.y - startPos.y, 2)
|
||||
);
|
||||
|
||||
if(time < click.timeLimit && distance < click.distanceLimit) {
|
||||
//Find the correct callback
|
||||
$.each(bindings, function(selector, callback) {
|
||||
if($this.is(selector)) {
|
||||
callback.apply(e.target, [e]);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/*** API ***/
|
||||
click.bind = function(events) {
|
||||
|
||||
//Argument Surgery
|
||||
if(!$.isPlainObject(events)) {
|
||||
newEvents = {};
|
||||
newEvents[arguments[0]] = arguments[1];
|
||||
events = newEvents;
|
||||
}
|
||||
|
||||
$.each(events, function(selector, callback) {
|
||||
|
||||
/*** Register Binding ***/
|
||||
if(typeof bindings[selector] != 'undefined') {
|
||||
click.unbind(selector); //Ensure no duplicates
|
||||
}
|
||||
|
||||
bindings[selector] = callback;
|
||||
|
||||
/*** Touch Support ***/
|
||||
if(click.isTouch) {
|
||||
$document.delegate(selector, 'touchstart', onTouchstart);
|
||||
}
|
||||
|
||||
/*** Mouse Support ***/
|
||||
$document.delegate(selector, 'click', function(e) {
|
||||
e.stopPropagation(); //Prevents multiple click events from happening
|
||||
//click._doAnywheres(e); //Do anywheres first to be consistent with touch order
|
||||
callback.apply(this, [e]);
|
||||
});
|
||||
});
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
click.unbind = function(selector) {
|
||||
$document
|
||||
.undelegate(selector, 'touchstart')
|
||||
.undelegate(selector, 'click');
|
||||
|
||||
delete bindings[selector];
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
click.unbindAll = function() {
|
||||
$.each(bindings, function(selector, callback) {
|
||||
$document
|
||||
.undelegate(selector, 'touchstart')
|
||||
.undelegate(selector, 'click');
|
||||
});
|
||||
|
||||
bindings = {};
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
click.trigger = function(selector, e) {
|
||||
e = e || $.Event('click');
|
||||
|
||||
if(typeof bindings[selector] != 'undefined') {
|
||||
bindings[selector](e);
|
||||
}
|
||||
else {
|
||||
console.error("No click events bound for selector '"+selector+"'.");
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
click.anywhere = function(callback) {
|
||||
click._anywheres.push(callback);
|
||||
return this;
|
||||
};
|
||||
|
||||
/*** Internal (but useful) Methods ***/
|
||||
click._getPos = function(e) {
|
||||
e = e.originalEvent;
|
||||
|
||||
if(e.pageX || e.pageY) {
|
||||
return {
|
||||
x: e.pageX,
|
||||
y: e.pageY
|
||||
};
|
||||
}
|
||||
else if(e.changedTouches) {
|
||||
return {
|
||||
x: e.changedTouches[0].clientX,
|
||||
y: e.changedTouches[0].clientY
|
||||
};
|
||||
}
|
||||
else {
|
||||
return {
|
||||
x: e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft,
|
||||
y: e.clientY + document.body.scrollTop + document.documentElement.scrollTop
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
click._anywheres = [];
|
||||
|
||||
click._doAnywheres = function(e) {
|
||||
var i = click._anywheres.length;
|
||||
while(i--) {
|
||||
click._anywheres[i](e);
|
||||
}
|
||||
};
|
||||
|
||||
$(document).bind('mousedown', click._doAnywheres);
|
||||
|
||||
module.exports = click;
|
||||
|
||||
|
||||
},{"unopinionate":15}],13:[function(require,module,exports){
|
||||
var $ = require('unopinionate').selector;
|
||||
|
||||
var bodyScrollers = [];
|
||||
|
||||
$(function() {
|
||||
var $html = $('html'),
|
||||
$body = $('body');
|
||||
|
||||
$(window, document, 'body').bind('scroll touchmove', function() {
|
||||
var top = $html[0].scrollTop || $body[0].scrollTop;
|
||||
|
||||
for(var i=0; i<bodyScrollers.length; i++) {
|
||||
bodyScrollers[i](top);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
var onScroll = function(callback) {
|
||||
bodyScrollers.push(callback);
|
||||
};
|
||||
|
||||
module.exports = onScroll;
|
||||
},{"unopinionate":15}],14:[function(require,module,exports){
|
||||
(function(root) {
|
||||
var callbacks = [];
|
||||
|
||||
var transitionComplete = function(callback) {
|
||||
if(callbacks.length === 0) {
|
||||
setEvent();
|
||||
}
|
||||
|
||||
callbacks.push(callback);
|
||||
};
|
||||
|
||||
function setEvent() {
|
||||
document.addEventListener(eventName(), function() {
|
||||
var i = callbacks.length;
|
||||
while(i--) {
|
||||
callbacks[i]();
|
||||
}
|
||||
|
||||
callbacks = [];
|
||||
});
|
||||
}
|
||||
|
||||
var _eventName;
|
||||
|
||||
function eventName() {
|
||||
if(!_eventName) {
|
||||
// Sourced from: http://stackoverflow.com/questions/5023514/how-do-i-normalize-css3-transition-functions-across-browsers
|
||||
var el = document.createElement('fakeelement');
|
||||
transitions = {
|
||||
transition: 'transitionend',
|
||||
OTransition: 'oTransitionEnd',
|
||||
MozTransition: 'transitionend',
|
||||
WebkitTransition: 'webkitTransitionEnd'
|
||||
};
|
||||
|
||||
for(var t in transitions) {
|
||||
if(el.style[t] !== undefined) {
|
||||
_eventName = transitions[t];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return _eventName;
|
||||
}
|
||||
|
||||
/*** Export ***/
|
||||
|
||||
// AMD
|
||||
if(typeof define === 'function' && define.amd) {
|
||||
define([], function() {
|
||||
return transitionComplete;
|
||||
});
|
||||
}
|
||||
// CommonJS
|
||||
else if(typeof exports !== 'undefined') {
|
||||
module.exports = transitionComplete;
|
||||
}
|
||||
// Browser Global
|
||||
else {
|
||||
root.transitionComplete = transitionComplete;
|
||||
}
|
||||
})(this);
|
||||
|
||||
},{}],15:[function(require,module,exports){
|
||||
(function (global){
|
||||
(function(root) {
|
||||
var unopinionate = {
|
||||
selector: root.jQuery || root.Zepto || root.ender || root.$,
|
||||
template: root.Handlebars || root.Mustache
|
||||
};
|
||||
|
||||
/*** Export ***/
|
||||
|
||||
//AMD
|
||||
if(typeof define === 'function' && define.amd) {
|
||||
define([], function() {
|
||||
return unopinionate;
|
||||
});
|
||||
}
|
||||
//CommonJS
|
||||
else if(typeof module.exports !== 'undefined') {
|
||||
module.exports = unopinionate;
|
||||
}
|
||||
//Global
|
||||
else {
|
||||
root.unopinionate = unopinionate;
|
||||
}
|
||||
})(typeof window != 'undefined' ? window : global);
|
||||
|
||||
}).call(this,typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
|
||||
},{}]},{},[3]);
|
||||
@@ -64,7 +64,7 @@ module.exports.middleware = function(req, res, next) {
|
||||
var _writeHead = res.writeHead
|
||||
res.writeHead = function(status) {
|
||||
if (status in images) {
|
||||
res.setHeader("X-Status-Cat", module.exports.get_image(status))
|
||||
res.setHeader('X-Status-Cat', module.exports.get_image(status))
|
||||
}
|
||||
_writeHead.apply(res, arguments)
|
||||
}
|
||||
|
||||
639
lib/storage.js
639
lib/storage.js
@@ -1,11 +1,11 @@
|
||||
var async = require('async')
|
||||
, semver = require('semver')
|
||||
, UError = require('./error').UserError
|
||||
, assert = require('assert')
|
||||
, Error = require('http-errors')
|
||||
, Local = require('./local-storage')
|
||||
, Proxy = require('./up-storage')
|
||||
, mystreams = require('./streams')
|
||||
, utils = require('./utils')
|
||||
, transaction = require('./transaction')
|
||||
, Logger = require('./logger')
|
||||
|
||||
//
|
||||
// Implements Storage interface
|
||||
@@ -24,6 +24,7 @@ function Storage(config) {
|
||||
this.uplinks[p].upname = p
|
||||
}
|
||||
this.local = new Local(config)
|
||||
this.logger = Logger.logger.child()
|
||||
|
||||
return this
|
||||
}
|
||||
@@ -32,16 +33,9 @@ function Storage(config) {
|
||||
// Add a {name} package to a system
|
||||
//
|
||||
// Function checks if package with the same name is available from uplinks.
|
||||
// If it isn't, we create package metadata locally and send requests to do
|
||||
// the same to all uplinks with write access. If all actions succeeded, we
|
||||
// report success, if just one uplink fails, we abort.
|
||||
// If it isn't, we create package locally
|
||||
//
|
||||
// TODO: if a package is uploaded to uplink1, but upload to uplink2 fails,
|
||||
// we report failure, but package is not removed from uplink1. This might
|
||||
// require manual intervention.
|
||||
//
|
||||
// Used storages: local (write) && uplinks (proxy_access, r/o) &&
|
||||
// uplinks (proxy_publish, write)
|
||||
// Used storages: local (write) && uplinks
|
||||
//
|
||||
Storage.prototype.add_package = function(name, metadata, callback) {
|
||||
var self = this
|
||||
@@ -51,118 +45,76 @@ Storage.prototype.add_package = function(name, metadata, callback) {
|
||||
// - when we publishing package, we only publish it to some of them
|
||||
// so all requests are necessary
|
||||
|
||||
check_package(function(err) {
|
||||
check_package_local(function(err) {
|
||||
if (err) return callback(err)
|
||||
|
||||
publish_package(function(err) {
|
||||
check_package_remote(function(err) {
|
||||
if (err) return callback(err)
|
||||
callback()
|
||||
|
||||
publish_package(function(err) {
|
||||
if (err) return callback(err)
|
||||
callback()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
function check_package(cb) {
|
||||
self.get_package(name, function(err, results, err_results) {
|
||||
function check_package_local(cb) {
|
||||
self.local.get_package(name, {}, function(err, results) {
|
||||
if (err && err.status !== 404) return cb(err)
|
||||
|
||||
if (results) {
|
||||
return cb(Error[409]('this package is already present'))
|
||||
}
|
||||
|
||||
cb()
|
||||
})
|
||||
}
|
||||
|
||||
function check_package_remote(cb) {
|
||||
self._sync_package_with_uplinks(name, null, {}, function(err, results, err_results) {
|
||||
// something weird
|
||||
if (err && err.status !== 404) return cb(err)
|
||||
|
||||
// checking package
|
||||
if (results) {
|
||||
return cb(Error[409]('this package is already present'))
|
||||
}
|
||||
|
||||
for (var i=0; i<err_results.length; i++) {
|
||||
// checking error
|
||||
// if uplink fails with a status other than 404, we report failure
|
||||
if (err_results[i][0] != null) {
|
||||
if (err_results[i][0].status !== 404) {
|
||||
return cb(new UError({
|
||||
status: 503,
|
||||
msg: 'one of the uplinks is down, refuse to publish'
|
||||
}))
|
||||
return cb(Error[503]('one of the uplinks is down, refuse to publish'))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// checking package
|
||||
if (results) {
|
||||
return cb(new UError({
|
||||
status: 409,
|
||||
msg: 'this package is already present'
|
||||
}))
|
||||
}
|
||||
|
||||
return cb()
|
||||
})
|
||||
}
|
||||
|
||||
function publish_package(cb) {
|
||||
var fw_uplinks = []
|
||||
for (var i in self.uplinks) {
|
||||
if (self.config.proxy_publish(name, i)) {
|
||||
fw_uplinks.push(self.uplinks[i])
|
||||
}
|
||||
}
|
||||
|
||||
transaction(
|
||||
fw_uplinks,
|
||||
function localAction(cb) {
|
||||
self.local.add_package(name, metadata, cb)
|
||||
},
|
||||
function localRollback(cb) {
|
||||
self.local.remove_package(name, cb)
|
||||
},
|
||||
function remoteAction(remote, cb) {
|
||||
remote.add_package(name, metadata, cb)
|
||||
},
|
||||
function remoteRollback(remote, cb) {
|
||||
remote.remove_package(name, cb)
|
||||
},
|
||||
function(err) {
|
||||
if (!err) {
|
||||
callback()
|
||||
|
||||
} else if (err.uplink === 'local') {
|
||||
return callback(err)
|
||||
|
||||
} else {
|
||||
// hide uplink error with general message
|
||||
return callback(new UError({
|
||||
status: 503,
|
||||
msg: 'can\'t upload to one of the uplinks, refuse to publish'
|
||||
}))
|
||||
}
|
||||
}
|
||||
)
|
||||
self.local.add_package(name, metadata, callback)
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Add a new version of package {name} to a system
|
||||
//
|
||||
// Function uploads a new package version to all uplinks with write access
|
||||
// and if everything succeeded it adds it locally.
|
||||
//
|
||||
// TODO: if a package is uploaded to uplink1, but upload to uplink2 fails,
|
||||
// we report failure, but package is not removed from uplink1. This might
|
||||
// require manual intervention.
|
||||
//
|
||||
// Used storages: local (write) && uplinks (proxy_publish, write)
|
||||
// Used storages: local (write)
|
||||
//
|
||||
Storage.prototype.add_version = function(name, version, metadata, tag, callback) {
|
||||
var self = this
|
||||
return this.local.add_version(name, version, metadata, tag, callback)
|
||||
}
|
||||
|
||||
var uplinks = []
|
||||
for (var i in self.uplinks) {
|
||||
if (self.config.proxy_publish(name, i)) {
|
||||
uplinks.push(self.uplinks[i])
|
||||
}
|
||||
}
|
||||
async.map(uplinks, function(up, cb) {
|
||||
up.add_version(name, version, metadata, tag, cb)
|
||||
}, function(err, results) {
|
||||
if (err) {
|
||||
return callback(new UError({
|
||||
status: 503,
|
||||
msg: 'can\'t upload to one of the uplinks, refuse to publish'
|
||||
}))
|
||||
}
|
||||
self.local.add_version(name, version, metadata, tag, callback)
|
||||
})
|
||||
//
|
||||
// Tags a package version with a provided tag
|
||||
//
|
||||
// Used storages: local (write)
|
||||
//
|
||||
Storage.prototype.add_tags = function(name, tag_hash, callback) {
|
||||
return this.local.add_tags(name, tag_hash, callback)
|
||||
}
|
||||
|
||||
//
|
||||
@@ -171,13 +123,7 @@ Storage.prototype.add_version = function(name, version, metadata, tag, callback)
|
||||
// Function changes a package info from local storage and all uplinks with
|
||||
// write access.
|
||||
//
|
||||
// TODO: currently it works only locally
|
||||
//
|
||||
// TODO: if a package is uploaded to uplink1, but upload to uplink2 fails,
|
||||
// we report failure, but package is not removed from uplink1. This might
|
||||
// require manual intervention.
|
||||
//
|
||||
// Used storages: local (write) && uplinks (proxy_publish, write)
|
||||
// Used storages: local (write)
|
||||
//
|
||||
Storage.prototype.change_package = function(name, metadata, revision, callback) {
|
||||
return this.local.change_package(name, metadata, revision, callback)
|
||||
@@ -186,16 +132,9 @@ Storage.prototype.change_package = function(name, metadata, revision, callback)
|
||||
//
|
||||
// Remove a package from a system
|
||||
//
|
||||
// Function removes a package from local storage and all uplinks with
|
||||
// write access.
|
||||
// Function removes a package from local storage
|
||||
//
|
||||
// TODO: currently it works only locally
|
||||
//
|
||||
// TODO: if a package is uploaded to uplink1, but upload to uplink2 fails,
|
||||
// we report failure, but package is not removed from uplink1. This might
|
||||
// require manual intervention.
|
||||
//
|
||||
// Used storages: local (write) && uplinks (proxy_publish, write)
|
||||
// Used storages: local (write)
|
||||
//
|
||||
Storage.prototype.remove_package = function(name, callback) {
|
||||
return this.local.remove_package(name, callback)
|
||||
@@ -204,17 +143,11 @@ Storage.prototype.remove_package = function(name, callback) {
|
||||
//
|
||||
// Remove a tarball from a system
|
||||
//
|
||||
// Function removes a tarball from local storage and all uplinks with
|
||||
// write access. Tarball in question should not be linked to in any existing
|
||||
// Function removes a tarball from local storage.
|
||||
// Tarball in question should not be linked to in any existing
|
||||
// versions, i.e. package version should be unpublished first.
|
||||
//
|
||||
// TODO: currently it works only locally
|
||||
//
|
||||
// TODO: if a package is uploaded to uplink1, but upload to uplink2 fails,
|
||||
// we report failure, but package is not removed from uplink1. This might
|
||||
// require manual intervention.
|
||||
//
|
||||
// Used storages: local (write) && uplinks (proxy_publish, write)
|
||||
// Used storages: local (write)
|
||||
//
|
||||
Storage.prototype.remove_tarball = function(name, filename, revision, callback) {
|
||||
return this.local.remove_tarball(name, filename, revision, callback)
|
||||
@@ -225,71 +158,10 @@ Storage.prototype.remove_tarball = function(name, filename, revision, callback)
|
||||
//
|
||||
// Function is syncronous and returns a WritableStream
|
||||
//
|
||||
// Function uploads a tarball to all uplinks with write access and to
|
||||
// local storage in parallel with a speed of a slowest pipe. It reports
|
||||
// success if all uploads succeed.
|
||||
//
|
||||
// Used storages: local (write) && uplinks (proxy_publish, write)
|
||||
// Used storages: local (write)
|
||||
//
|
||||
Storage.prototype.add_tarball = function(name, filename) {
|
||||
var stream = new mystreams.UploadTarballStream()
|
||||
|
||||
var self = this
|
||||
var upstreams = []
|
||||
var localstream = self.local.add_tarball(name, filename)
|
||||
|
||||
upstreams.push(localstream)
|
||||
for (var i in self.uplinks) {
|
||||
if (self.config.proxy_publish(name, i)) {
|
||||
upstreams.push(self.uplinks[i].add_tarball(name, filename))
|
||||
}
|
||||
}
|
||||
|
||||
function bail(err) {
|
||||
upstreams.forEach(function(upstream) {
|
||||
upstream.abort()
|
||||
})
|
||||
}
|
||||
|
||||
upstreams.forEach(function(upstream) {
|
||||
stream.pipe(upstream)
|
||||
|
||||
upstream.on('error', function(err) {
|
||||
if (err.code === 'EEXISTS') {
|
||||
stream.emit('error', new UError({
|
||||
status: 409,
|
||||
msg: 'this tarball is already present'
|
||||
}))
|
||||
} else if (!stream.status && upstream !== localstream) {
|
||||
stream.emit('error', new UError({
|
||||
status: 503,
|
||||
msg: 'one or more uplinks are unreachable'
|
||||
}))
|
||||
} else {
|
||||
stream.emit('error', err)
|
||||
}
|
||||
bail(err)
|
||||
})
|
||||
upstream.on('success', function() {
|
||||
upstream._sinopia_success = true
|
||||
if (upstreams.filter(function(upstream) {
|
||||
return !upstream._sinopia_success
|
||||
}).length === 0) {
|
||||
stream.emit('success')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
stream.abort = function() {
|
||||
bail()
|
||||
}
|
||||
stream.done = function() {
|
||||
upstreams.forEach(function(upstream) {
|
||||
upstream.done()
|
||||
})
|
||||
}
|
||||
|
||||
return stream
|
||||
return this.local.add_tarball(name, filename)
|
||||
}
|
||||
|
||||
//
|
||||
@@ -326,52 +198,77 @@ Storage.prototype.get_tarball = function(name, filename) {
|
||||
rstream = null // gc
|
||||
|
||||
self.local.get_package(name, function(err, info) {
|
||||
if (err) return stream.emit('error', err)
|
||||
if (!err && info._distfiles && info._distfiles[filename] != null) {
|
||||
// information about this file exists locally
|
||||
serve_file(info._distfiles[filename])
|
||||
|
||||
if (info._distfiles[filename] == null) {
|
||||
return stream.emit('error', err404)
|
||||
}
|
||||
} else {
|
||||
// we know nothing about this file, trying to get information elsewhere
|
||||
|
||||
var file = info._distfiles[filename]
|
||||
var uplink = null
|
||||
for (var p in self.uplinks) {
|
||||
if (self.uplinks[p].can_fetch_url(file.url)) {
|
||||
uplink = self.uplinks[p]
|
||||
}
|
||||
}
|
||||
if (uplink == null) {
|
||||
uplink = new Proxy({
|
||||
url: file.url,
|
||||
_autogenerated: true,
|
||||
}, self.config)
|
||||
}
|
||||
self._sync_package_with_uplinks(name, info, {}, function(err, info) {
|
||||
if (err) return stream.emit('error', err)
|
||||
|
||||
var savestream = self.local.add_tarball(name, filename)
|
||||
savestream.on('error', function(err) {
|
||||
savestream.abort()
|
||||
stream.emit('error', err)
|
||||
})
|
||||
savestream.on('open', function() {
|
||||
var rstream2 = uplink.get_url(file.url)
|
||||
rstream2.on('error', function(err) {
|
||||
savestream.abort()
|
||||
stream.emit('error', err)
|
||||
if (!info._distfiles || info._distfiles[filename] == null) {
|
||||
return stream.emit('error', err404)
|
||||
}
|
||||
|
||||
serve_file(info._distfiles[filename])
|
||||
})
|
||||
rstream2.on('end', function() {
|
||||
savestream.done()
|
||||
})
|
||||
|
||||
// XXX: check, what would happen if client disconnects?
|
||||
rstream2.pipe(stream)
|
||||
rstream2.pipe(savestream)
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
rstream.on('content-length', function(v) {
|
||||
stream.emit('content-length', v)
|
||||
})
|
||||
rstream.on('open', function() {
|
||||
is_open = true
|
||||
rstream.pipe(stream)
|
||||
})
|
||||
return stream
|
||||
|
||||
function serve_file(file) {
|
||||
var uplink = null
|
||||
for (var p in self.uplinks) {
|
||||
if (self.uplinks[p].can_fetch_url(file.url)) {
|
||||
uplink = self.uplinks[p]
|
||||
}
|
||||
}
|
||||
if (uplink == null) {
|
||||
uplink = new Proxy({
|
||||
url: file.url,
|
||||
_autogenerated: true,
|
||||
}, self.config)
|
||||
}
|
||||
|
||||
var savestream = self.local.add_tarball(name, filename)
|
||||
function on_open() {
|
||||
var rstream2 = uplink.get_url(file.url)
|
||||
rstream2.on('error', function(err) {
|
||||
if (savestream) savestream.abort()
|
||||
savestream = null
|
||||
stream.emit('error', err)
|
||||
})
|
||||
rstream2.on('end', function() {
|
||||
if (savestream) savestream.done()
|
||||
})
|
||||
|
||||
rstream2.on('content-length', function(v) {
|
||||
stream.emit('content-length', v)
|
||||
if (savestream) savestream.emit('content-length', v)
|
||||
})
|
||||
rstream2.pipe(stream)
|
||||
if (savestream) rstream2.pipe(savestream)
|
||||
}
|
||||
|
||||
savestream.on('open', function() {
|
||||
on_open()
|
||||
})
|
||||
savestream.on('error', function() {
|
||||
if (savestream) savestream.abort()
|
||||
savestream = null
|
||||
on_open()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
@@ -383,93 +280,271 @@ Storage.prototype.get_tarball = function(name, filename) {
|
||||
//
|
||||
// Used storages: local && uplink (proxy_access)
|
||||
//
|
||||
Storage.prototype.get_package = function(name, callback) {
|
||||
// NOTE: callback(err, result, _uplink_errors)
|
||||
// _uplink_errors is an array of errors used internally
|
||||
// XXX: move it to another function maybe?
|
||||
Storage.prototype.get_package = function(name, options, callback) {
|
||||
if (typeof(options) === 'function') callback = options, options = {}
|
||||
|
||||
var self = this
|
||||
|
||||
self.local.get_package(name, function(err, data) {
|
||||
self.local.get_package(name, options, function(err, data) {
|
||||
if (err && (!err.status || err.status >= 500)) {
|
||||
// report internal errors right away
|
||||
return cb(err)
|
||||
return callback(err)
|
||||
}
|
||||
|
||||
var uplinks = []
|
||||
for (var i in self.uplinks) {
|
||||
if (self.config.proxy_access(name, i)) {
|
||||
uplinks.push(self.uplinks[i])
|
||||
self._sync_package_with_uplinks(name, data, options, function(err, result, uplink_errors) {
|
||||
if (err) return callback(err)
|
||||
var whitelist = ['_rev', 'name', 'versions', 'dist-tags', 'readme']
|
||||
for (var i in result) {
|
||||
if (whitelist.indexOf(i) === -1) delete result[i]
|
||||
}
|
||||
}
|
||||
|
||||
var result = data || {
|
||||
if (self.config.ignore_latest_tag || !result['dist-tags'].latest) {
|
||||
result['dist-tags'].latest = utils.semver_sort(Object.keys(result.versions))
|
||||
}
|
||||
|
||||
for (var i in result['dist-tags']) {
|
||||
if (Array.isArray(result['dist-tags'][i])) {
|
||||
result['dist-tags'][i] = result['dist-tags'][i][result['dist-tags'][i].length-1]
|
||||
if (result['dist-tags'][i] == null) delete result['dist-tags'][i]
|
||||
}
|
||||
}
|
||||
|
||||
// npm can throw if this field doesn't exist
|
||||
result._attachments = {}
|
||||
|
||||
callback(null, result, uplink_errors)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
//
|
||||
// Retrieve remote and local packages more recent than {startkey}
|
||||
//
|
||||
// Function invokes uplink.request for npm and local.get_recent_packages for
|
||||
// local ones then sum up the result in a json object
|
||||
//
|
||||
// Used storages: local && uplink (proxy_access)
|
||||
//
|
||||
Storage.prototype.search = function(startkey, options, callback) {
|
||||
var self = this
|
||||
var uplinks = []
|
||||
var i = 0
|
||||
|
||||
var uplinks
|
||||
for (var p in self.uplinks) {
|
||||
uplinks.push(p)
|
||||
}
|
||||
|
||||
function merge_with_local_packages(err, res, body) {
|
||||
if (err) return callback(err)
|
||||
var j = 0
|
||||
|
||||
self.local.get_recent_packages(startkey, function(err, list) {
|
||||
if (err) return callback(err)
|
||||
|
||||
var listL = list.length
|
||||
|
||||
if (!listL) return callback(null, body)
|
||||
|
||||
list.forEach(function(item) {
|
||||
self.local.get_package(item.name, options, function(err, data) {
|
||||
if (err) return callback(err)
|
||||
|
||||
var versions = utils.semver_sort(Object.keys(data.versions))
|
||||
var latest = versions[versions.length - 1]
|
||||
|
||||
if (data.versions[latest]) {
|
||||
body[item.name] = {
|
||||
name : data.versions[latest].name,
|
||||
description : data.versions[latest].description,
|
||||
'dist-tags' : {
|
||||
latest: latest
|
||||
},
|
||||
maintainers : data.versions[latest].maintainers || [data.versions[latest]._npmUser].filter(Boolean),
|
||||
readmeFilename: data.versions[latest].readmeFilename || '',
|
||||
time : {
|
||||
modified: new Date(item.time).toISOString()
|
||||
},
|
||||
versions : {},
|
||||
repository : data.versions[latest].repository,
|
||||
keywords : data.versions[latest].keywords
|
||||
}
|
||||
body[item.name].versions[latest] = 'latest'
|
||||
}
|
||||
|
||||
if (++j !== listL) {
|
||||
return false
|
||||
}
|
||||
|
||||
return callback(null, body)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function remote_search() {
|
||||
var uplink = self.uplinks[uplinks[i]]
|
||||
if (!uplink) {
|
||||
return merge_with_local_packages(null, null, {})
|
||||
}
|
||||
self.uplinks[uplinks[i]].request({
|
||||
uri: options.req.url,
|
||||
timeout: self.uplinks[p].timeout,
|
||||
json: true
|
||||
}, function(err, res, body) {
|
||||
if (err || Math.floor(res.statusCode / 100) > 3) {
|
||||
i++
|
||||
return remote_search()
|
||||
}
|
||||
return merge_with_local_packages(err, res, body)
|
||||
})
|
||||
}
|
||||
|
||||
remote_search()
|
||||
}
|
||||
|
||||
Storage.prototype.get_local = function(callback) {
|
||||
var self = this
|
||||
, locals = this.config.localList.get()
|
||||
, packages = [];
|
||||
|
||||
var getPackage = function(i) {
|
||||
self.local.get_package(locals[i], function(err, info) {
|
||||
if (!err) {
|
||||
var latest = Array.isArray(info['dist-tags'].latest)
|
||||
? utils.semver_sort(info['dist-tags'].latest).pop()
|
||||
: info['dist-tags'].latest
|
||||
if (info.versions[latest]) {
|
||||
packages.push(info.versions[latest])
|
||||
} else {
|
||||
self.logger.warn({package: locals[i]}, 'package @{package} does not have a "latest" tag?')
|
||||
}
|
||||
}
|
||||
|
||||
if (i >= locals.length - 1) {
|
||||
callback(null, packages);
|
||||
} else {
|
||||
getPackage(i + 1);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
if(locals.length) {
|
||||
getPackage(0);
|
||||
}
|
||||
else {
|
||||
callback(null, []);
|
||||
}
|
||||
};
|
||||
|
||||
// function fetches package information from uplinks and synchronizes it with local data
|
||||
// if package is available locally, it MUST be provided in pkginfo
|
||||
// returns callback(err, result, uplink_errors)
|
||||
Storage.prototype._sync_package_with_uplinks = function(name, pkginfo, options, callback) {
|
||||
var self = this
|
||||
|
||||
if (!pkginfo) {
|
||||
var exists = false
|
||||
|
||||
pkginfo = {
|
||||
name: name,
|
||||
versions: {},
|
||||
'dist-tags': {},
|
||||
_uplinks: {},
|
||||
}
|
||||
var exists = !err
|
||||
var latest = result['dist-tags'].latest
|
||||
} else {
|
||||
var exists = true
|
||||
}
|
||||
|
||||
async.map(uplinks, function(up, cb) {
|
||||
var oldetag = null
|
||||
if (utils.is_object(result._uplinks[up.upname]))
|
||||
oldetag = result._uplinks[up.upname].etag
|
||||
var uplinks = []
|
||||
for (var i in self.uplinks) {
|
||||
if (self.config.proxy_access(name, i)) {
|
||||
uplinks.push(self.uplinks[i])
|
||||
}
|
||||
}
|
||||
|
||||
up.get_package(name, oldetag, function(err, up_res, etag) {
|
||||
if (err || !up_res) return cb(null, [err || new Error('no data')])
|
||||
|
||||
try {
|
||||
utils.validate_metadata(up_res, name)
|
||||
} catch(err) {
|
||||
return cb(null, [err])
|
||||
}
|
||||
|
||||
result._uplinks[up.upname] = {
|
||||
etag: etag
|
||||
}
|
||||
|
||||
var this_version = up_res['dist-tags'].latest
|
||||
if (latest == null
|
||||
|| (!semver.gt(latest, this_version) && this_version)) {
|
||||
latest = this_version
|
||||
var is_latest = true
|
||||
}
|
||||
|
||||
;['versions', 'dist-tags'].forEach(function(key) {
|
||||
for (var i in up_res[key]) {
|
||||
if (!result[key][i] || is_latest) {
|
||||
result[key][i] = up_res[key][i]
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// if we got to this point, assume that the correct package exists
|
||||
// on the uplink
|
||||
exists = true
|
||||
cb()
|
||||
})
|
||||
}, function(err, uplink_errors) {
|
||||
if (err) return callback(err)
|
||||
if (!exists) {
|
||||
return callback(new UError({
|
||||
status: 404,
|
||||
msg: 'no such package available'
|
||||
}), null, uplink_errors)
|
||||
async.map(uplinks, function(up, cb) {
|
||||
var _options = Object.create(options)
|
||||
if (utils.is_object(pkginfo._uplinks[up.upname])) {
|
||||
var fetched = pkginfo._uplinks[up.upname].fetched
|
||||
if (fetched && fetched > (Date.now() - up.maxage)) {
|
||||
return cb()
|
||||
}
|
||||
|
||||
self.local.update_versions(name, result, function(err) {
|
||||
if (err) return callback(err)
|
||||
_options.etag = pkginfo._uplinks[up.upname].etag
|
||||
}
|
||||
|
||||
var whitelist = ['_rev', 'name', 'versions', 'dist-tags']
|
||||
for (var i in result) {
|
||||
if (!~whitelist.indexOf(i)) delete result[i]
|
||||
}
|
||||
callback(null, result, uplink_errors)
|
||||
})
|
||||
up.get_package(name, _options, function(err, up_res, etag) {
|
||||
if (err && err.status === 304)
|
||||
pkginfo._uplinks[up.upname].fetched = Date.now()
|
||||
|
||||
if (err || !up_res) return cb(null, [err || Error('no data')])
|
||||
|
||||
try {
|
||||
utils.validate_metadata(up_res, name)
|
||||
} catch(err) {
|
||||
self.logger.error({
|
||||
sub: 'out',
|
||||
err: err,
|
||||
}, 'package.json validating error @{!err.message}\n@{err.stack}')
|
||||
return cb(null, [err])
|
||||
}
|
||||
|
||||
pkginfo._uplinks[up.upname] = {
|
||||
etag: etag,
|
||||
fetched: Date.now()
|
||||
}
|
||||
|
||||
try {
|
||||
Storage._merge_versions(pkginfo, up_res, self.config)
|
||||
} catch(err) {
|
||||
self.logger.error({
|
||||
sub: 'out',
|
||||
err: err,
|
||||
}, 'package.json parsing error @{!err.message}\n@{err.stack}')
|
||||
return cb(null, [err])
|
||||
}
|
||||
|
||||
// if we got to this point, assume that the correct package exists
|
||||
// on the uplink
|
||||
exists = true
|
||||
cb()
|
||||
})
|
||||
}, function(err, uplink_errors) {
|
||||
assert(!err && Array.isArray(uplink_errors))
|
||||
|
||||
if (!exists) {
|
||||
return callback( Error[404]('no such package available')
|
||||
, null
|
||||
, uplink_errors )
|
||||
}
|
||||
|
||||
self.local.update_versions(name, pkginfo, function(err, pkginfo) {
|
||||
if (err) return callback(err)
|
||||
return callback(null, pkginfo, uplink_errors)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = Storage
|
||||
// function gets a local info and an info from uplinks and tries to merge it
|
||||
// exported for unit tests only
|
||||
Storage._merge_versions = function(local, up, config) {
|
||||
// copy new versions to a cache
|
||||
// NOTE: if a certain version was updated, we can't refresh it reliably
|
||||
for (var i in up.versions) {
|
||||
if (local.versions[i] == null) {
|
||||
local.versions[i] = up.versions[i]
|
||||
}
|
||||
}
|
||||
|
||||
// refresh dist-tags
|
||||
for (var i in up['dist-tags']) {
|
||||
var added = utils.tag_version(local, up['dist-tags'][i], i, config || {})
|
||||
if (i === 'latest' && added) {
|
||||
// if remote has more fresh package, we should borrow its readme
|
||||
local.readme = up.readme
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Storage
|
||||
|
||||
@@ -51,16 +51,3 @@ function add_abstract_method(self, name) {
|
||||
})
|
||||
}
|
||||
|
||||
function __test() {
|
||||
var test = new ReadTarball()
|
||||
test.abort()
|
||||
setTimeout(function() {
|
||||
test.abort = function() {
|
||||
console.log('ok')
|
||||
}
|
||||
test.abort = function() {
|
||||
throw 'fail'
|
||||
}
|
||||
}, 100)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
var async = require('async')
|
||||
|
||||
//
|
||||
// Function performs a certain task on a multiple uplinks
|
||||
// and reverts changes if something fails
|
||||
//
|
||||
// uplinks - list of uplinks not counting local
|
||||
// localAction, localRollback - function(cb)
|
||||
// remoteAction, remoteRollback - function(uplink, cb)
|
||||
//
|
||||
module.exports = function(uplinks, localAction, localRollback, remoteAction, remoteRollback, callback) {
|
||||
var uplink_ids = uplinks.map(function(_, i) {
|
||||
return i
|
||||
})
|
||||
|
||||
localAction(function(err) {
|
||||
if (err) return callback(err)
|
||||
async.map(uplink_ids, function(i, cb) {
|
||||
remoteAction(uplinks[i], function(err) {
|
||||
cb(null, err)
|
||||
})
|
||||
}, function(err, res) {
|
||||
var return_err = err
|
||||
|
||||
// let err be first non-null element in the array
|
||||
for (var i=0; i<res.length; i++) {
|
||||
if (return_err) break
|
||||
return_err = res[i]
|
||||
}
|
||||
|
||||
if (!return_err) return callback()
|
||||
|
||||
async.map(uplink_ids, function(i, cb) {
|
||||
if (res[i]) return cb()
|
||||
remoteRollback(uplinks[i], function() {
|
||||
cb()
|
||||
})
|
||||
}, function(err) {
|
||||
localRollback(function() {
|
||||
callback(return_err)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
var URL = require('url')
|
||||
, request = require('request')
|
||||
, UError = require('./error').UserError
|
||||
, Stream = require('stream')
|
||||
, zlib = require('zlib')
|
||||
, Error = require('http-errors')
|
||||
, mystreams = require('./streams')
|
||||
, Logger = require('./logger')
|
||||
, utils = require('./utils')
|
||||
, parse_interval = require('./config').parse_interval
|
||||
, encode = encodeURIComponent
|
||||
|
||||
//
|
||||
// Implements Storage interface
|
||||
@@ -12,15 +16,14 @@ var URL = require('url')
|
||||
function Storage(config, mainconfig) {
|
||||
if (!(this instanceof Storage)) return new Storage(config)
|
||||
this.config = config
|
||||
this.is_alive = false
|
||||
this.failed_requests = 0
|
||||
this.userAgent = mainconfig.user_agent
|
||||
this.ca = config.ca
|
||||
this.logger = Logger.logger.child({sub: 'out'})
|
||||
this.server_id = mainconfig.server_id
|
||||
|
||||
this.url = URL.parse(this.config.url)
|
||||
if (this.url.hostname === 'registry.npmjs.org') {
|
||||
this.ca = this.ca || require('./npmsslkeys')
|
||||
|
||||
// npm registry is too slow working with ssl :(
|
||||
/*if (this.config._autogenerated) {
|
||||
// encrypt all the things!
|
||||
@@ -32,11 +35,24 @@ function Storage(config, mainconfig) {
|
||||
_setupProxy.call(this, this.url.hostname, config, mainconfig, this.url.protocol === 'https:')
|
||||
|
||||
this.config.url = this.config.url.replace(/\/$/, '')
|
||||
if (Number(this.config.timeout) >= 1000) {
|
||||
this.logger.warn('Too big timeout value: ' + this.config.timeout + '\nWe changed time format to nginx-like one\n(see http://wiki.nginx.org/ConfigNotation)\nso please update your config accordingly')
|
||||
}
|
||||
|
||||
// a bunch of different configurable timers
|
||||
this.maxage = parse_interval(config_get('maxage' , '2m' ))
|
||||
this.timeout = parse_interval(config_get('timeout' , '30s'))
|
||||
this.max_fails = Number(config_get('max_fails' , 2 ))
|
||||
this.fail_timeout = parse_interval(config_get('fail_timeout', '5m' ))
|
||||
return this
|
||||
|
||||
// just a helper (`config[key] || default` doesn't work because of zeroes)
|
||||
function config_get(key, def) {
|
||||
return config[key] != null ? config[key] : def
|
||||
}
|
||||
}
|
||||
|
||||
function _setupProxy(hostname, config, mainconfig, isHTTPS) {
|
||||
debugger;
|
||||
var no_proxy
|
||||
var proxy_key = isHTTPS ? 'https_proxy' : 'http_proxy'
|
||||
|
||||
@@ -63,7 +79,8 @@ debugger;
|
||||
if (no_proxy_item[0] !== '.') no_proxy_item = '.' + no_proxy_item
|
||||
if (hostname.lastIndexOf(no_proxy_item) === hostname.length - no_proxy_item.length) {
|
||||
if (this.proxy) {
|
||||
this.logger.debug({url: this.url.href, rule: no_proxy_item}, 'not using proxy for @{url}, excluded by @{rule} rule')
|
||||
this.logger.debug({url: this.url.href, rule: no_proxy_item},
|
||||
'not using proxy for @{url}, excluded by @{rule} rule')
|
||||
this.proxy = false
|
||||
}
|
||||
break
|
||||
@@ -75,15 +92,28 @@ debugger;
|
||||
if (typeof(this.proxy) !== 'string') {
|
||||
delete this.proxy
|
||||
} else {
|
||||
this.logger.debug({url: this.url.href, proxy: this.proxy}, 'using proxy @{proxy} for @{url}')
|
||||
this.logger.debug({url: this.url.href, proxy: this.proxy},
|
||||
'using proxy @{proxy} for @{url}')
|
||||
}
|
||||
}
|
||||
|
||||
Storage.prototype.request = function(options, cb) {
|
||||
if (!this.status_check()) {
|
||||
var req = new Stream.Readable()
|
||||
process.nextTick(function() {
|
||||
if (typeof(cb) === 'function') cb(Error('uplink is offline'))
|
||||
req.emit('error', Error('uplink is offline'))
|
||||
})
|
||||
// preventing 'Uncaught, unspecified "error" event'
|
||||
req.on('error', function(){})
|
||||
return req
|
||||
}
|
||||
|
||||
var self = this
|
||||
, headers = options.headers || {}
|
||||
headers.accept = headers.accept || 'application/json'
|
||||
headers['user-agent'] = headers['user-agent'] || this.userAgent
|
||||
headers['Accept'] = headers['Accept'] || 'application/json'
|
||||
headers['Accept-Encoding'] = headers['Accept-Encoding'] || 'gzip'
|
||||
headers['User-Agent'] = headers['User-Agent'] || this.userAgent
|
||||
|
||||
var method = options.method || 'GET'
|
||||
, uri = options.uri_full || (this.config.url + options.uri)
|
||||
@@ -95,7 +125,7 @@ Storage.prototype.request = function(options, cb) {
|
||||
|
||||
if (utils.is_object(options.json)) {
|
||||
var json = JSON.stringify(options.json)
|
||||
headers['content-type'] = headers['content-type'] || 'application/json'
|
||||
headers['Content-Type'] = headers['Content-Type'] || 'application/json'
|
||||
}
|
||||
|
||||
var req = request({
|
||||
@@ -105,14 +135,37 @@ Storage.prototype.request = function(options, cb) {
|
||||
body: json,
|
||||
ca: this.ca,
|
||||
proxy: this.proxy,
|
||||
encoding: null,
|
||||
timeout: this.timeout,
|
||||
}, function(err, res, body) {
|
||||
var error
|
||||
if (!err) {
|
||||
var res_length = body.length
|
||||
var res_length = err ? 0 : body.length
|
||||
|
||||
do_gunzip(function() {
|
||||
do_decode()
|
||||
do_log()
|
||||
if (cb) cb(err, res, body)
|
||||
})
|
||||
|
||||
function do_gunzip(cb) {
|
||||
if (err) return cb()
|
||||
if (res.headers['content-encoding'] !== 'gzip') return cb()
|
||||
zlib.gunzip(body, function(er, buf) {
|
||||
if (er) err = er
|
||||
body = buf
|
||||
return cb()
|
||||
})
|
||||
}
|
||||
|
||||
function do_decode() {
|
||||
if (err) {
|
||||
error = err.message
|
||||
return
|
||||
}
|
||||
|
||||
if (options.json && res.statusCode < 300) {
|
||||
try {
|
||||
body = JSON.parse(body)
|
||||
body = JSON.parse(body.toString('utf8'))
|
||||
} catch(_err) {
|
||||
body = {}
|
||||
err = _err
|
||||
@@ -121,52 +174,69 @@ Storage.prototype.request = function(options, cb) {
|
||||
}
|
||||
|
||||
if (!err && utils.is_object(body)) {
|
||||
if (body.error) {
|
||||
if (typeof(body.error) === 'string') {
|
||||
error = body.error
|
||||
}
|
||||
}
|
||||
} else {
|
||||
error = err.message
|
||||
}
|
||||
|
||||
var msg = '@{!status}, req: \'@{request.method} @{request.url}\''
|
||||
if (error) {
|
||||
msg += ', error: @{!error}'
|
||||
} else {
|
||||
msg += ', bytes: @{bytes.in}/@{bytes.out}'
|
||||
}
|
||||
self.logger.warn({
|
||||
err: err,
|
||||
request: {method: method, url: uri},
|
||||
level: 35, // http
|
||||
status: res != null ? res.statusCode : 'ERR',
|
||||
error: error,
|
||||
bytes: {
|
||||
in: json ? json.length : 0,
|
||||
out: res_length || 0,
|
||||
function do_log() {
|
||||
var message = '@{!status}, req: \'@{request.method} @{request.url}\''
|
||||
if (error) {
|
||||
message += ', error: @{!error}'
|
||||
} else {
|
||||
message += ', bytes: @{bytes.in}/@{bytes.out}'
|
||||
}
|
||||
}, msg)
|
||||
if (cb) cb.apply(self, arguments)
|
||||
self.logger.warn({
|
||||
err: err,
|
||||
request: {method: method, url: uri},
|
||||
level: 35, // http
|
||||
status: res != null ? res.statusCode : 'ERR',
|
||||
error: error,
|
||||
bytes: {
|
||||
in: json ? json.length : 0,
|
||||
out: res_length || 0,
|
||||
}
|
||||
}, message)
|
||||
}
|
||||
})
|
||||
|
||||
var status_called = false
|
||||
req.on('response', function(res) {
|
||||
self.status_check(true)
|
||||
if (!req._sinopia_aborted && !status_called) {
|
||||
status_called = true
|
||||
self.status_check(true)
|
||||
}
|
||||
})
|
||||
req.on('error', function() {
|
||||
self.status_check(false)
|
||||
req.on('error', function(_err) {
|
||||
if (!req._sinopia_aborted && !status_called) {
|
||||
status_called = true
|
||||
self.status_check(false)
|
||||
}
|
||||
})
|
||||
return req
|
||||
}
|
||||
|
||||
Storage.prototype.status_check = function(alive) {
|
||||
if (arguments.length === 0) {
|
||||
if (!this.is_alive && Math.abs(Date.now() - this.is_alive_time()) > 60*1000) {
|
||||
if (this.failed_requests >= this.max_fails && Math.abs(Date.now() - this.last_request_time) < this.fail_timeout) {
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
this.is_alive = alive
|
||||
this.is_alive_time = Date.now()
|
||||
if (alive) {
|
||||
if (this.failed_requests >= this.max_fails) {
|
||||
this.logger.warn({host: this.url.host}, 'host @{host} is back online')
|
||||
}
|
||||
this.failed_requests = 0
|
||||
} else {
|
||||
this.failed_requests++
|
||||
if (this.failed_requests === this.max_fails) {
|
||||
this.logger.warn({host: this.url.host}, 'host @{host} is now offline')
|
||||
}
|
||||
}
|
||||
this.last_request_time = Date.now()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,123 +248,62 @@ Storage.prototype.can_fetch_url = function(url) {
|
||||
&& url.path.indexOf(this.url.path) === 0
|
||||
}
|
||||
|
||||
Storage.prototype.add_package = function(name, metadata, callback) {
|
||||
this.request({
|
||||
uri: '/' + escape(name),
|
||||
method: 'PUT',
|
||||
json: metadata,
|
||||
}, function(err, res, body) {
|
||||
if (err) return callback(err)
|
||||
if (!(res.statusCode >= 200 && res.statusCode < 300)) {
|
||||
return callback(new Error('bad status code: ' + res.statusCode))
|
||||
}
|
||||
callback(null, body)
|
||||
})
|
||||
}
|
||||
Storage.prototype.get_package = function(name, options, callback) {
|
||||
if (typeof(options) === 'function') callback = options, options = {}
|
||||
|
||||
Storage.prototype.add_version = function(name, version, metadata, tag, callback) {
|
||||
this.request({
|
||||
uri: '/' + escape(name) + '/' + escape(version) + '/-tag/' + escape(tag),
|
||||
method: 'PUT',
|
||||
json: metadata,
|
||||
}, function(err, res, body) {
|
||||
if (err) return callback(err)
|
||||
if (!(res.statusCode >= 200 && res.statusCode < 300)) {
|
||||
return callback(new Error('bad status code: ' + res.statusCode))
|
||||
}
|
||||
callback(null, body)
|
||||
})
|
||||
}
|
||||
|
||||
Storage.prototype.add_tarball = function(name, filename) {
|
||||
var stream = new mystreams.UploadTarballStream()
|
||||
, self = this
|
||||
|
||||
var wstream = this.request({
|
||||
uri: '/' + escape(name) + '/-/' + escape(filename) + '/whatever',
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'content-type': 'application/octet-stream'
|
||||
},
|
||||
})
|
||||
|
||||
wstream.on('response', function(res) {
|
||||
if (!(res.statusCode >= 200 && res.statusCode < 300)) {
|
||||
return stream.emit('error', new UError({
|
||||
msg: 'bad uplink status code: ' + res.statusCode,
|
||||
status: 500,
|
||||
}))
|
||||
}
|
||||
stream.emit('success')
|
||||
})
|
||||
|
||||
wstream.on('error', function(err) {
|
||||
stream.emit('error', err)
|
||||
})
|
||||
|
||||
stream.abort = function() {
|
||||
process.nextTick(function() {
|
||||
if (wstream.req) {
|
||||
wstream.req.abort()
|
||||
}
|
||||
})
|
||||
var headers = {}
|
||||
if (options.etag) {
|
||||
headers['If-None-Match'] = options.etag
|
||||
headers['Accept'] = 'application/octet-stream'
|
||||
}
|
||||
stream.done = function() {}
|
||||
stream.pipe(wstream)
|
||||
this._add_proxy_headers(options.req, headers)
|
||||
|
||||
return stream
|
||||
}
|
||||
|
||||
Storage.prototype.get_package = function(name, etag, callback) {
|
||||
if (etag) {
|
||||
var headers = {
|
||||
'if-none-match': etag
|
||||
}
|
||||
}
|
||||
this.request({
|
||||
uri: '/' + escape(name),
|
||||
uri: '/' + encode(name),
|
||||
json: true,
|
||||
headers: headers,
|
||||
}, function(err, res, body) {
|
||||
if (err) return callback(err)
|
||||
if (res.statusCode === 404) {
|
||||
return callback(new UError({
|
||||
msg: 'package doesn\'t exist on uplink',
|
||||
status: 404,
|
||||
}))
|
||||
return callback(Error[404]("package doesn't exist on uplink"))
|
||||
}
|
||||
if (!(res.statusCode >= 200 && res.statusCode < 300)) {
|
||||
return callback(new Error('bad status code: ' + res.statusCode))
|
||||
var error = Error('bad status code: ' + res.statusCode)
|
||||
error.remoteStatus = res.statusCode
|
||||
return callback(error)
|
||||
}
|
||||
callback(null, body, res.headers.etag)
|
||||
})
|
||||
}
|
||||
|
||||
Storage.prototype.get_tarball = function(name, filename) {
|
||||
Storage.prototype.get_tarball = function(name, options, filename) {
|
||||
if (!options) options = {}
|
||||
return this.get_url(this.config.url + '/' + name + '/-/' + filename)
|
||||
}
|
||||
|
||||
Storage.prototype.get_url = function(url) {
|
||||
var stream = new mystreams.ReadTarballStream()
|
||||
stream.abort = function() {}
|
||||
var current_length = 0, expected_length
|
||||
|
||||
var rstream = this.request({
|
||||
uri_full: url,
|
||||
encoding: null,
|
||||
headers: {
|
||||
Accept: 'application/octet-stream',
|
||||
},
|
||||
})
|
||||
|
||||
rstream.on('response', function(res) {
|
||||
if (res.statusCode === 404) {
|
||||
return stream.emit('error', new UError({
|
||||
msg: 'file doesn\'t exist on uplink',
|
||||
status: 404,
|
||||
}))
|
||||
return stream.emit('error', Error[404]("file doesn't exist on uplink"))
|
||||
}
|
||||
if (!(res.statusCode >= 200 && res.statusCode < 300)) {
|
||||
return stream.emit('error', new UError({
|
||||
msg: 'bad uplink status code: ' + res.statusCode,
|
||||
status: 500,
|
||||
}))
|
||||
return stream.emit('error', Error('bad uplink status code: ' + res.statusCode))
|
||||
}
|
||||
if (res.headers['content-length']) {
|
||||
expected_length = res.headers['content-length']
|
||||
stream.emit('content-length', res.headers['content-length'])
|
||||
}
|
||||
|
||||
rstream.pipe(stream)
|
||||
@@ -303,8 +312,34 @@ Storage.prototype.get_url = function(url) {
|
||||
rstream.on('error', function(err) {
|
||||
stream.emit('error', err)
|
||||
})
|
||||
rstream.on('data', function(d) {
|
||||
current_length += d.length
|
||||
})
|
||||
rstream.on('end', function(d) {
|
||||
if (d) current_length += d.length
|
||||
if (expected_length && current_length != expected_length)
|
||||
stream.emit('error', Error('content length mismatch'))
|
||||
})
|
||||
return stream
|
||||
}
|
||||
|
||||
Storage.prototype._add_proxy_headers = function(req, headers) {
|
||||
if (req) {
|
||||
headers['X-Forwarded-For'] = (
|
||||
(req && req.headers['x-forwarded-for']) ?
|
||||
req.headers['x-forwarded-for'] + ', ' :
|
||||
''
|
||||
) + req.connection.remoteAddress
|
||||
}
|
||||
|
||||
// always attach Via header to avoid loops, even if we're not proxying
|
||||
headers['Via'] =
|
||||
(req && req.headers['via']) ?
|
||||
req.headers['via'] + ', ' :
|
||||
''
|
||||
|
||||
headers['Via'] += '1.1 ' + this.server_id + ' (Sinopia)'
|
||||
}
|
||||
|
||||
module.exports = Storage
|
||||
|
||||
|
||||
84
lib/utils.js
84
lib/utils.js
@@ -1,17 +1,21 @@
|
||||
var assert = require('assert')
|
||||
, semver = require('semver')
|
||||
, Logger = require('./logger')
|
||||
, URL = require('url')
|
||||
|
||||
// from normalize-package-data/lib/fixer.js
|
||||
module.exports.validate_name = function(name) {
|
||||
if (typeof(name) !== 'string') return false
|
||||
name = name.toLowerCase()
|
||||
if (
|
||||
name.charAt(0) === "." || // ".bin", etc.
|
||||
name.match(/[\/@\s\+%:]/) ||
|
||||
name !== encodeURIComponent(name) ||
|
||||
name.toLowerCase() === "node_modules" ||
|
||||
name.toLowerCase() === "__proto__" ||
|
||||
name.toLowerCase() === "package.json" ||
|
||||
name.toLowerCase() === "favicon.ico"
|
||||
// all URL-safe characters and "@" for issue #75
|
||||
!name.match(/^[-a-zA-Z0-9_.!~*'()@]+$/) ||
|
||||
name.charAt(0) === '.' || // ".bin", etc.
|
||||
name.charAt(0) === '-' || // "-" is reserved by couchdb
|
||||
name === 'node_modules' ||
|
||||
name === '__proto__' ||
|
||||
name === 'package.json' ||
|
||||
name === 'favicon.ico'
|
||||
) {
|
||||
return false
|
||||
} else {
|
||||
@@ -24,7 +28,7 @@ module.exports.is_object = function(obj) {
|
||||
}
|
||||
|
||||
module.exports.validate_metadata = function(object, name) {
|
||||
assert(module.exports.is_object(object))
|
||||
assert(module.exports.is_object(object), 'not a json object')
|
||||
assert.equal(object.name, name)
|
||||
|
||||
if (!module.exports.is_object(object['dist-tags'])) {
|
||||
@@ -78,12 +82,68 @@ module.exports.filter_tarball_urls = function(pkg, req, config) {
|
||||
}
|
||||
|
||||
for (var ver in pkg.versions) {
|
||||
if (pkg.versions[ver].dist != null
|
||||
&& pkg.versions[ver].dist.tarball != null) {
|
||||
pkg.versions[ver].dist.__sinopia_orig_tarball = pkg.versions[ver].dist.tarball
|
||||
pkg.versions[ver].dist.tarball = filter(pkg.versions[ver].dist.tarball)
|
||||
var dist = pkg.versions[ver].dist
|
||||
if (dist != null && dist.tarball != null) {
|
||||
//dist.__sinopia_orig_tarball = dist.tarball
|
||||
dist.tarball = filter(dist.tarball)
|
||||
}
|
||||
}
|
||||
return pkg
|
||||
}
|
||||
|
||||
function can_add_tag(tag, config) {
|
||||
if (!tag) return false
|
||||
if (tag === 'latest' && config.ignore_latest_tag) return false
|
||||
return true
|
||||
}
|
||||
|
||||
module.exports.tag_version = function(data, version, tag, config) {
|
||||
if (!can_add_tag(tag, config)) return false
|
||||
|
||||
switch(typeof(data['dist-tags'][tag])) {
|
||||
case 'string':
|
||||
data['dist-tags'][tag] = [data['dist-tags'][tag]]
|
||||
break
|
||||
case 'object': // array
|
||||
break
|
||||
default:
|
||||
data['dist-tags'][tag] = []
|
||||
}
|
||||
if (data['dist-tags'][tag].indexOf(version) === -1) {
|
||||
data['dist-tags'][tag].push(version)
|
||||
data['dist-tags'][tag] = module.exports.semver_sort(data['dist-tags'][tag])
|
||||
return data['dist-tags'][tag][data['dist-tags'][tag].length - 1] === version
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// gets version from a package object taking into account semver weirdness
|
||||
module.exports.get_version = function(object, version) {
|
||||
if (object.versions[version] != null) return object.versions[version]
|
||||
|
||||
try {
|
||||
version = semver.parse(version, true)
|
||||
for (var k in object.versions) {
|
||||
if (version.compare(semver.parse(k, true)) === 0) {
|
||||
return object.versions[k]
|
||||
}
|
||||
}
|
||||
} catch(err) {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
// function filters out bad semver versions and sorts the array
|
||||
module.exports.semver_sort = function semver_sort(array) {
|
||||
return array
|
||||
.filter(function(x) {
|
||||
if (!semver.parse(x, true)) {
|
||||
Logger.logger.warn({ver: x}, 'ignoring bad version @{ver}')
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
.sort(semver.compareLoose)
|
||||
.map(String)
|
||||
}
|
||||
|
||||
|
||||
16
node_modules/crypt3/.gitignore
generated
vendored
Normal file
16
node_modules/crypt3/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
lib-cov
|
||||
*.seed
|
||||
*.log
|
||||
*.csv
|
||||
*.dat
|
||||
*.out
|
||||
*.pid
|
||||
*.gz
|
||||
|
||||
pids
|
||||
logs
|
||||
results
|
||||
|
||||
npm-debug.log
|
||||
node_modules
|
||||
build/
|
||||
20
node_modules/crypt3/LICENSE
generated
vendored
Normal file
20
node_modules/crypt3/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 Sendanor
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
43
node_modules/crypt3/README.md
generated
vendored
Normal file
43
node_modules/crypt3/README.md
generated
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
node-crypt3
|
||||
===========
|
||||
|
||||
[crypt3link]: https://en.wikipedia.org/wiki/Crypt_(C) "crypt() in C"
|
||||
|
||||
[crypt(3)][crypt3link] for Node.js
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
Install using `npm install crypt3` and use:
|
||||
|
||||
```javascript
|
||||
var crypt = require('crypt3');
|
||||
```
|
||||
|
||||
Example password check
|
||||
----------------------
|
||||
|
||||
```javascript
|
||||
if( crypt('6Xz7sS6fEmnWScMb6Ayf363e5cdqF4Kh', '$1$SrkubyRm$DEQU3KupUxt4yfhbK1HyV/') !== '$1$SrkubyRm$DEQU3KupUxt4yfhbK1HyV/' ) {
|
||||
console.error('Access denied!');
|
||||
return;
|
||||
}
|
||||
```
|
||||
|
||||
Example password encoding
|
||||
-------------------------
|
||||
|
||||
Use `crypt(key[, salt])`:
|
||||
|
||||
```javascript
|
||||
console.log( crypt('6Xz7sS6fEmnWScMb6Ayf363e5cdqF4Kh') ); // Salt generated automatically using default SHA512
|
||||
console.log( crypt('6Xz7sS6fEmnWScMb6Ayf363e5cdqF4Kh', crypt.createSalt('md5') ) ); // MD5 salt
|
||||
console.log( crypt('6Xz7sS6fEmnWScMb6Ayf363e5cdqF4Kh', crypt.createSalt('blowfish') ) ); // Blowfish salt (only some Linux distros)
|
||||
console.log( crypt('6Xz7sS6fEmnWScMb6Ayf363e5cdqF4Kh', crypt.createSalt('sha256') ) ); // SHA-256
|
||||
console.log( crypt('6Xz7sS6fEmnWScMb6Ayf363e5cdqF4Kh', crypt.createSalt('sha512') ) ); // SHA-512
|
||||
```
|
||||
|
||||
Create hashes
|
||||
-------------
|
||||
|
||||
Use `crypt.createSalt([type=sha512])` where type is one of `md5`, `blowfish`, `sha256` or `sha512` (default).
|
||||
14
node_modules/crypt3/binding.gyp
generated
vendored
Normal file
14
node_modules/crypt3/binding.gyp
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"targets": [
|
||||
{
|
||||
"target_name": "crypt3",
|
||||
"sources": [ "crypt3.cc" ],
|
||||
"include_dirs" : [ "<!(node -e \"require('nan')\")" ],
|
||||
"conditions": [
|
||||
['OS!="mac"', {
|
||||
'link_settings': { "libraries": [ "-lcrypt" ] }
|
||||
}]
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
39
node_modules/crypt3/crypt3.cc
generated
vendored
Normal file
39
node_modules/crypt3/crypt3.cc
generated
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
/* Node.js Crypt(3) implementation */
|
||||
|
||||
#include <node.h>
|
||||
#include <v8.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h> // for crypt if _XOPEN_SOURCE exists
|
||||
#include <nan.h>
|
||||
|
||||
using namespace v8;
|
||||
|
||||
NAN_METHOD(Method) {
|
||||
NanScope();
|
||||
|
||||
if (args.Length() < 2) {
|
||||
return NanThrowTypeError("Wrong number of arguments");
|
||||
}
|
||||
|
||||
if (!args[0]->IsString() || !args[1]->IsString()) {
|
||||
return NanThrowTypeError("Wrong arguments");
|
||||
}
|
||||
|
||||
v8::String::Utf8Value key(args[0]->ToString());
|
||||
v8::String::Utf8Value salt(args[1]->ToString());
|
||||
|
||||
char* res = crypt(*key, *salt);
|
||||
if (res != NULL) {
|
||||
NanReturnValue(NanNew<String>(res));
|
||||
} else {
|
||||
return NanThrowError(node::ErrnoException(errno, "crypt"));
|
||||
}
|
||||
}
|
||||
|
||||
void init(Handle<Object> exports) {
|
||||
exports->Set(NanNew<String>("crypt"), NanNew<FunctionTemplate>(Method)->GetFunction());
|
||||
}
|
||||
|
||||
NODE_MODULE(crypt3, init)
|
||||
|
||||
/* EOF */
|
||||
33
node_modules/crypt3/index.js
generated
vendored
Normal file
33
node_modules/crypt3/index.js
generated
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
/** Node.js Crypt(3) Library */
|
||||
|
||||
var salters = {
|
||||
'md5': function() { return '$1$'+require('crypto').randomBytes(10).toString('base64'); },
|
||||
'blowfish': function() { return '$2a$'+require('crypto').randomBytes(10).toString('base64'); },
|
||||
'sha256': function() { return '$5$'+require('crypto').randomBytes(10).toString('base64'); },
|
||||
'sha512': function() { return '$6$'+require('crypto').randomBytes(10).toString('base64'); }
|
||||
};
|
||||
|
||||
function createSalt(type) {
|
||||
type = type || 'sha512';
|
||||
if(!salters[type]) throw new TypeError('Unknown salt type at crypt3.createSalt: ' + type);
|
||||
return salters[type]();
|
||||
};
|
||||
|
||||
/** Crypt(3) password and data encryption.
|
||||
* @param {string} key user's typed password
|
||||
* @param {string} salt Optional salt, for example SHA-512 use "$6$salt$".
|
||||
* @returns {string} A generated hash in format $id$salt$encrypted
|
||||
* @see https://en.wikipedia.org/wiki/Crypt_(C)
|
||||
*/
|
||||
var crypt3 = module.exports = function(key, salt) {
|
||||
salt = salt || createSalt();
|
||||
return require('./build/Release/crypt3').crypt(key, salt);
|
||||
};
|
||||
|
||||
/** Create salt
|
||||
* @param {string} type The type of salt: md5, blowfish (only some linux distros), sha256 or sha512. Default is sha512.
|
||||
* @returns {string} Generated salt string
|
||||
*/
|
||||
crypt3.createSalt = createSalt;
|
||||
|
||||
/* EOF */
|
||||
30
node_modules/crypt3/node_modules/nan/.dntrc
generated
vendored
Normal file
30
node_modules/crypt3/node_modules/nan/.dntrc
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
## DNT config file
|
||||
## see https://github.com/rvagg/dnt
|
||||
|
||||
NODE_VERSIONS="\
|
||||
master \
|
||||
v0.11.13 \
|
||||
v0.10.30 \
|
||||
v0.10.29 \
|
||||
v0.10.28 \
|
||||
v0.10.26 \
|
||||
v0.10.25 \
|
||||
v0.10.24 \
|
||||
v0.10.23 \
|
||||
v0.10.22 \
|
||||
v0.10.21 \
|
||||
v0.10.20 \
|
||||
v0.10.19 \
|
||||
v0.8.28 \
|
||||
v0.8.27 \
|
||||
v0.8.26 \
|
||||
v0.8.24 \
|
||||
"
|
||||
OUTPUT_PREFIX="nan-"
|
||||
TEST_CMD=" \
|
||||
cd /dnt/ && \
|
||||
npm install && \
|
||||
node_modules/.bin/node-gyp --nodedir /usr/src/node/ rebuild --directory test && \
|
||||
node_modules/.bin/tap --gc test/js/*-test.js \
|
||||
"
|
||||
|
||||
200
node_modules/crypt3/node_modules/nan/CHANGELOG.md
generated
vendored
Normal file
200
node_modules/crypt3/node_modules/nan/CHANGELOG.md
generated
vendored
Normal file
@@ -0,0 +1,200 @@
|
||||
# NAN ChangeLog
|
||||
|
||||
### Version 1.3.0: current Node unstable: 0.11.13, Node stable: 0.10.30
|
||||
|
||||
|
||||
**1.3.0 Aug 2 2014**
|
||||
|
||||
- Added NanNew<v8::String, std::string>(std::string)
|
||||
- Added NanNew<v8::String, std::string&>(std::string&)
|
||||
- Added NanAsciiString helper class
|
||||
- Added NanUtf8String helper class
|
||||
- Added NanUcs2String helper class
|
||||
- Deprecated NanRawString()
|
||||
- Deprecated NanCString()
|
||||
- Added NanGetIsolateData(v8::Isolate *isolate)
|
||||
- Added NanMakeCallback(v8::Handle<v8::Object> target, v8::Handle<v8::Function> func, int argc, v8::Handle<v8::Value>* argv)
|
||||
- Added NanMakeCallback(v8::Handle<v8::Object> target, v8::Handle<v8::String> symbol, int argc, v8::Handle<v8::Value>* argv)
|
||||
- Added NanMakeCallback(v8::Handle<v8::Object> target, const char* method, int argc, v8::Handle<v8::Value>* argv)
|
||||
- Added NanSetTemplate(v8::Handle<v8::Template> templ, v8::Handle<v8::String> name , v8::Handle<v8::Data> value, v8::PropertyAttribute attributes)
|
||||
- Added NanSetPrototypeTemplate(v8::Local<v8::FunctionTemplate> templ, v8::Handle<v8::String> name, v8::Handle<v8::Data> value, v8::PropertyAttribute attributes)
|
||||
- Added NanSetInstanceTemplate(v8::Local<v8::FunctionTemplate> templ, const char *name, v8::Handle<v8::Data> value)
|
||||
- Added NanSetInstanceTemplate(v8::Local<v8::FunctionTemplate> templ, v8::Handle<v8::String> name, v8::Handle<v8::Data> value, v8::PropertyAttribute attributes)
|
||||
|
||||
**1.2.0 Jun 5 2014**
|
||||
|
||||
- Add NanSetPrototypeTemplate
|
||||
- Changed NAN_WEAK_CALLBACK internals, switched _NanWeakCallbackData to class,
|
||||
introduced _NanWeakCallbackDispatcher
|
||||
- Removed -Wno-unused-local-typedefs from test builds
|
||||
- Made test builds Windows compatible ('Sleep()')
|
||||
|
||||
**1.1.2 May 28 2014**
|
||||
|
||||
- Release to fix more stuff-ups in 1.1.1
|
||||
|
||||
**1.1.1 May 28 2014**
|
||||
|
||||
- Release to fix version mismatch in nan.h and lack of changelog entry for 1.1.0
|
||||
|
||||
**1.1.0 May 25 2014**
|
||||
|
||||
- Remove nan_isolate, use v8::Isolate::GetCurrent() internally instead
|
||||
- Additional explicit overloads for NanNew(): (char*,int), (uint8_t*[,int]),
|
||||
(uint16_t*[,int), double, int, unsigned int, bool, v8::String::ExternalStringResource*,
|
||||
v8::String::ExternalAsciiStringResource*
|
||||
- Deprecate NanSymbol()
|
||||
- Added SetErrorMessage() and ErrorMessage() to NanAsyncWorker
|
||||
|
||||
**1.0.0 May 4 2014**
|
||||
|
||||
- Heavy API changes for V8 3.25 / Node 0.11.13
|
||||
- Use cpplint.py
|
||||
- Removed NanInitPersistent
|
||||
- Removed NanPersistentToLocal
|
||||
- Removed NanFromV8String
|
||||
- Removed NanMakeWeak
|
||||
- Removed NanNewLocal
|
||||
- Removed NAN_WEAK_CALLBACK_OBJECT
|
||||
- Removed NAN_WEAK_CALLBACK_DATA
|
||||
- Introduce NanNew, replaces NanNewLocal, NanPersistentToLocal, adds many overloaded typed versions
|
||||
- Introduce NanUndefined, NanNull, NanTrue and NanFalse
|
||||
- Introduce NanEscapableScope and NanEscapeScope
|
||||
- Introduce NanMakeWeakPersistent (requires a special callback to work on both old and new node)
|
||||
- Introduce NanMakeCallback for node::MakeCallback
|
||||
- Introduce NanSetTemplate
|
||||
- Introduce NanGetCurrentContext
|
||||
- Introduce NanCompileScript and NanRunScript
|
||||
- Introduce NanAdjustExternalMemory
|
||||
- Introduce NanAddGCEpilogueCallback, NanAddGCPrologueCallback, NanRemoveGCEpilogueCallback, NanRemoveGCPrologueCallback
|
||||
- Introduce NanGetHeapStatistics
|
||||
- Rename NanAsyncWorker#SavePersistent() to SaveToPersistent()
|
||||
|
||||
**0.8.0 Jan 9 2014**
|
||||
|
||||
- NanDispose -> NanDisposePersistent, deprecate NanDispose
|
||||
- Extract _NAN_*_RETURN_TYPE, pull up NAN_*()
|
||||
|
||||
**0.7.1 Jan 9 2014**
|
||||
|
||||
- Fixes to work against debug builds of Node
|
||||
- Safer NanPersistentToLocal (avoid reinterpret_cast)
|
||||
- Speed up common NanRawString case by only extracting flattened string when necessary
|
||||
|
||||
**0.7.0 Dec 17 2013**
|
||||
|
||||
- New no-arg form of NanCallback() constructor.
|
||||
- NanCallback#Call takes Handle rather than Local
|
||||
- Removed deprecated NanCallback#Run method, use NanCallback#Call instead
|
||||
- Split off _NAN_*_ARGS_TYPE from _NAN_*_ARGS
|
||||
- Restore (unofficial) Node 0.6 compatibility at NanCallback#Call()
|
||||
- Introduce NanRawString() for char* (or appropriate void*) from v8::String
|
||||
(replacement for NanFromV8String)
|
||||
- Introduce NanCString() for null-terminated char* from v8::String
|
||||
|
||||
**0.6.0 Nov 21 2013**
|
||||
|
||||
- Introduce NanNewLocal<T>(v8::Handle<T> value) for use in place of
|
||||
v8::Local<T>::New(...) since v8 started requiring isolate in Node 0.11.9
|
||||
|
||||
**0.5.2 Nov 16 2013**
|
||||
|
||||
- Convert SavePersistent and GetFromPersistent in NanAsyncWorker from protected and public
|
||||
|
||||
**0.5.1 Nov 12 2013**
|
||||
|
||||
- Use node::MakeCallback() instead of direct v8::Function::Call()
|
||||
|
||||
**0.5.0 Nov 11 2013**
|
||||
|
||||
- Added @TooTallNate as collaborator
|
||||
- New, much simpler, "include_dirs" for binding.gyp
|
||||
- Added full range of NAN_INDEX_* macros to match NAN_PROPERTY_* macros
|
||||
|
||||
**0.4.4 Nov 2 2013**
|
||||
|
||||
- Isolate argument from v8::Persistent::MakeWeak removed for 0.11.8+
|
||||
|
||||
**0.4.3 Nov 2 2013**
|
||||
|
||||
- Include node_object_wrap.h, removed from node.h for Node 0.11.8.
|
||||
|
||||
**0.4.2 Nov 2 2013**
|
||||
|
||||
- Handle deprecation of v8::Persistent::Dispose(v8::Isolate* isolate)) for
|
||||
Node 0.11.8 release.
|
||||
|
||||
**0.4.1 Sep 16 2013**
|
||||
|
||||
- Added explicit `#include <uv.h>` as it was removed from node.h for v0.11.8
|
||||
|
||||
**0.4.0 Sep 2 2013**
|
||||
|
||||
- Added NAN_INLINE and NAN_DEPRECATED and made use of them
|
||||
- Added NanError, NanTypeError and NanRangeError
|
||||
- Cleaned up code
|
||||
|
||||
**0.3.2 Aug 30 2013**
|
||||
|
||||
- Fix missing scope declaration in GetFromPersistent() and SaveToPersistent
|
||||
in NanAsyncWorker
|
||||
|
||||
**0.3.1 Aug 20 2013**
|
||||
|
||||
- fix "not all control paths return a value" compile warning on some platforms
|
||||
|
||||
**0.3.0 Aug 19 2013**
|
||||
|
||||
- Made NAN work with NPM
|
||||
- Lots of fixes to NanFromV8String, pulling in features from new Node core
|
||||
- Changed node::encoding to Nan::Encoding in NanFromV8String to unify the API
|
||||
- Added optional error number argument for NanThrowError()
|
||||
- Added NanInitPersistent()
|
||||
- Added NanReturnNull() and NanReturnEmptyString()
|
||||
- Added NanLocker and NanUnlocker
|
||||
- Added missing scopes
|
||||
- Made sure to clear disposed Persistent handles
|
||||
- Changed NanAsyncWorker to allocate error messages on the heap
|
||||
- Changed NanThrowError(Local<Value>) to NanThrowError(Handle<Value>)
|
||||
- Fixed leak in NanAsyncWorker when errmsg is used
|
||||
|
||||
**0.2.2 Aug 5 2013**
|
||||
|
||||
- Fixed usage of undefined variable with node::BASE64 in NanFromV8String()
|
||||
|
||||
**0.2.1 Aug 5 2013**
|
||||
|
||||
- Fixed 0.8 breakage, node::BUFFER encoding type not available in 0.8 for
|
||||
NanFromV8String()
|
||||
|
||||
**0.2.0 Aug 5 2013**
|
||||
|
||||
- Added NAN_PROPERTY_GETTER, NAN_PROPERTY_SETTER, NAN_PROPERTY_ENUMERATOR,
|
||||
NAN_PROPERTY_DELETER, NAN_PROPERTY_QUERY
|
||||
- Extracted _NAN_METHOD_ARGS, _NAN_GETTER_ARGS, _NAN_SETTER_ARGS,
|
||||
_NAN_PROPERTY_GETTER_ARGS, _NAN_PROPERTY_SETTER_ARGS,
|
||||
_NAN_PROPERTY_ENUMERATOR_ARGS, _NAN_PROPERTY_DELETER_ARGS,
|
||||
_NAN_PROPERTY_QUERY_ARGS
|
||||
- Added NanGetInternalFieldPointer, NanSetInternalFieldPointer
|
||||
- Added NAN_WEAK_CALLBACK, NAN_WEAK_CALLBACK_OBJECT,
|
||||
NAN_WEAK_CALLBACK_DATA, NanMakeWeak
|
||||
- Renamed THROW_ERROR to _NAN_THROW_ERROR
|
||||
- Added NanNewBufferHandle(char*, size_t, node::smalloc::FreeCallback, void*)
|
||||
- Added NanBufferUse(char*, uint32_t)
|
||||
- Added NanNewContextHandle(v8::ExtensionConfiguration*,
|
||||
v8::Handle<v8::ObjectTemplate>, v8::Handle<v8::Value>)
|
||||
- Fixed broken NanCallback#GetFunction()
|
||||
- Added optional encoding and size arguments to NanFromV8String()
|
||||
- Added NanGetPointerSafe() and NanSetPointerSafe()
|
||||
- Added initial test suite (to be expanded)
|
||||
- Allow NanUInt32OptionValue to convert any Number object
|
||||
|
||||
**0.1.0 Jul 21 2013**
|
||||
|
||||
- Added `NAN_GETTER`, `NAN_SETTER`
|
||||
- Added `NanThrowError` with single Local<Value> argument
|
||||
- Added `NanNewBufferHandle` with single uint32_t argument
|
||||
- Added `NanHasInstance(Persistent<FunctionTemplate>&, Handle<Value>)`
|
||||
- Added `Local<Function> NanCallback#GetFunction()`
|
||||
- Added `NanCallback#Call(int, Local<Value>[])`
|
||||
- Deprecated `NanCallback#Run(int, Local<Value>[])` in favour of Call
|
||||
46
node_modules/crypt3/node_modules/nan/LICENSE
generated
vendored
Normal file
46
node_modules/crypt3/node_modules/nan/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
Copyright 2013, NAN contributors:
|
||||
- Rod Vagg <https://github.com/rvagg>
|
||||
- Benjamin Byholm <https://github.com/kkoopa>
|
||||
- Trevor Norris <https://github.com/trevnorris>
|
||||
- Nathan Rajlich <https://github.com/TooTallNate>
|
||||
- Brett Lawson <https://github.com/brett19>
|
||||
- Ben Noordhuis <https://github.com/bnoordhuis>
|
||||
(the "Original Author")
|
||||
All rights reserved.
|
||||
|
||||
MIT +no-false-attribs License
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation
|
||||
files (the "Software"), to deal in the Software without
|
||||
restriction, including without limitation the rights to use,
|
||||
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
Distributions of all or part of the Software intended to be used
|
||||
by the recipients as they would use the unmodified Software,
|
||||
containing modifications that substantially alter, remove, or
|
||||
disable functionality of the Software, outside of the documented
|
||||
configuration mechanisms provided by the Software, shall be
|
||||
modified such that the Original Author's bug reporting email
|
||||
addresses and urls are either replaced with the contact information
|
||||
of the parties responsible for the changes, or removed entirely.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
Except where noted, this license applies to any and all software
|
||||
programs and associated documentation files created by the
|
||||
Original Author, when distributed with the Software.
|
||||
1054
node_modules/crypt3/node_modules/nan/README.md
generated
vendored
Normal file
1054
node_modules/crypt3/node_modules/nan/README.md
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
32
node_modules/crypt3/node_modules/nan/appveyor.yml
generated
vendored
Normal file
32
node_modules/crypt3/node_modules/nan/appveyor.yml
generated
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
# http://www.appveyor.com/docs/appveyor-yml
|
||||
|
||||
# Test against these versions of Node.js.
|
||||
environment:
|
||||
matrix:
|
||||
- nodejs_version: "0.8"
|
||||
- nodejs_version: "0.10"
|
||||
- nodejs_version: "0.11"
|
||||
|
||||
# Install scripts. (runs after repo cloning)
|
||||
install:
|
||||
# Get the latest stable version of Node 0.STABLE.latest
|
||||
- npm install npm
|
||||
- move node_modules npm
|
||||
- ps: Update-NodeJsInstallation (Get-NodeJsLatestBuild $env:nodejs_version)
|
||||
# Typical npm stuff.
|
||||
- npm/.bin/npm install
|
||||
- npm/.bin/npm run rebuild-tests
|
||||
|
||||
# Post-install test scripts.
|
||||
test_script:
|
||||
# Output useful info for debugging.
|
||||
- node --version
|
||||
- npm --version
|
||||
- cmd: npm test
|
||||
|
||||
# Don't actually build.
|
||||
build: off
|
||||
|
||||
# Set build version format here instead of in the admin panel.
|
||||
version: "{build}"
|
||||
|
||||
1
node_modules/crypt3/node_modules/nan/include_dirs.js
generated
vendored
Normal file
1
node_modules/crypt3/node_modules/nan/include_dirs.js
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
console.log(require('path').relative('.', __dirname));
|
||||
2331
node_modules/crypt3/node_modules/nan/nan.h
generated
vendored
Normal file
2331
node_modules/crypt3/node_modules/nan/nan.h
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
63
node_modules/crypt3/node_modules/nan/package.json
generated
vendored
Normal file
63
node_modules/crypt3/node_modules/nan/package.json
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
40
node_modules/crypt3/package.json
generated
vendored
Normal file
40
node_modules/crypt3/package.json
generated
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"name": "crypt3",
|
||||
"version": "0.1.5",
|
||||
"description": "Node.js crypt(3) bindings",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "node test/test.js",
|
||||
"install": "node-gyp rebuild"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sendanor/node-crypt3.git"
|
||||
},
|
||||
"keywords": [
|
||||
"crypt",
|
||||
"password",
|
||||
"md5",
|
||||
"sha256",
|
||||
"sha512",
|
||||
"blowfish",
|
||||
"hash"
|
||||
],
|
||||
"dependencies": { "nan": "~1.3.0" },
|
||||
"author": {
|
||||
"name": "Jaakko-Heikki Heusala",
|
||||
"email": "jheusala@iki.fi"
|
||||
},
|
||||
"license": "MIT",
|
||||
"gypfile": true,
|
||||
"readme": "node-crypt3\n===========\n\n[crypt3link]: https://en.wikipedia.org/wiki/Crypt_(C) \"crypt() in C\"\n\n[crypt(3)][crypt3link] for Node.js\n\nInstallation\n------------\n\nInstall using `npm install crypt3` and use:\n\n```javascript\nvar crypt = require('crypt3');\n```\n\nExample password check\n----------------------\n\n```javascript\nif( crypt('6Xz7sS6fEmnWScMb6Ayf363e5cdqF4Kh', '$1$SrkubyRm$DEQU3KupUxt4yfhbK1HyV/') !== '$1$SrkubyRm$DEQU3KupUxt4yfhbK1HyV/' ) {\n\tconsole.error('Access denied!');\n\treturn;\n}\n```\n\nExample password encoding\n-------------------------\n\nUse `crypt(key[, salt])`:\n\n```javascript\nconsole.log( crypt('6Xz7sS6fEmnWScMb6Ayf363e5cdqF4Kh') ); // Salt generated automatically using default SHA512\nconsole.log( crypt('6Xz7sS6fEmnWScMb6Ayf363e5cdqF4Kh', crypt.createSalt('md5') ) ); // MD5 salt\nconsole.log( crypt('6Xz7sS6fEmnWScMb6Ayf363e5cdqF4Kh', crypt.createSalt('blowfish') ) ); // Blowfish salt (only some Linux distros)\nconsole.log( crypt('6Xz7sS6fEmnWScMb6Ayf363e5cdqF4Kh', crypt.createSalt('sha256') ) ); // SHA-256\nconsole.log( crypt('6Xz7sS6fEmnWScMb6Ayf363e5cdqF4Kh', crypt.createSalt('sha512') ) ); // SHA-512\n```\n\nCreate hashes\n-------------\n\nUse `crypt.createSalt([type=sha512])` where type is one of `md5`, `blowfish`, `sha256` or `sha512` (default). \n",
|
||||
"readmeFilename": "README.md",
|
||||
"bugs": {
|
||||
"url": "https://github.com/sendanor/node-crypt3/issues"
|
||||
},
|
||||
"homepage": "https://github.com/sendanor/node-crypt3",
|
||||
"_id": "crypt3@0.1.5",
|
||||
"_shasum": "f21e9ba7a57736f47e0654ad27f8668966de02db",
|
||||
"_resolved": "git://github.com/sendanor/node-crypt3.git#9b893c95ed956adc3da681b125c371112b1ad31d",
|
||||
"_from": "crypt3@git://github.com/sendanor/node-crypt3.git"
|
||||
}
|
||||
14
node_modules/crypt3/test/test.js
generated
vendored
Normal file
14
node_modules/crypt3/test/test.js
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
var assert = require('assert')
|
||||
var crypt3 = require('../')
|
||||
|
||||
assert.throws(function() {
|
||||
crypt3()
|
||||
}, /Wrong arguments/)
|
||||
|
||||
assert.equal(crypt3('pass', 'salt'), 'sa5JEXtYx/rm6')
|
||||
assert.equal(crypt3('pass', 'sa5JEXtYx/rm6'), 'sa5JEXtYx/rm6')
|
||||
|
||||
var hash = crypt3('password')
|
||||
assert.equal(crypt3('password', hash), hash)
|
||||
assert.notEqual(crypt3('bad-pass', hash), hash)
|
||||
|
||||
43
node_modules/helpers.less/.gitignore
generated
vendored
Executable file
43
node_modules/helpers.less/.gitignore
generated
vendored
Executable file
@@ -0,0 +1,43 @@
|
||||
# Compiled source #
|
||||
###################
|
||||
*.com
|
||||
*.class
|
||||
*.dll
|
||||
*.exe
|
||||
*.o
|
||||
*.so
|
||||
|
||||
# Packages #
|
||||
############
|
||||
# it's better to unpack these files and commit the raw source
|
||||
# git has its own built in compression methods
|
||||
*.7z
|
||||
*.dmg
|
||||
*.gz
|
||||
*.iso
|
||||
*.jar
|
||||
*.rar
|
||||
*.tar
|
||||
*.zip
|
||||
|
||||
# Logs and databases #
|
||||
######################
|
||||
*.log
|
||||
*.sql
|
||||
*.sqlite
|
||||
|
||||
# OS generated files #
|
||||
######################
|
||||
.DS_Store
|
||||
.DS_Store?
|
||||
._*
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
ehthumbs.db
|
||||
Thumbs.db
|
||||
|
||||
# Node Stuff #
|
||||
##############
|
||||
node_modules/
|
||||
|
||||
|
||||
17
node_modules/helpers.less/Gruntfile.js
generated
vendored
Normal file
17
node_modules/helpers.less/Gruntfile.js
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
module.exports = function(grunt) {
|
||||
grunt.initConfig({
|
||||
pkg: grunt.file.readJSON('package.json'),
|
||||
browserify: {
|
||||
'dist/subview.js': ['src/main.js'],
|
||||
'examples/build.js': ['examples/example.js']
|
||||
},
|
||||
watch: {
|
||||
files: [ "src/*.js", "examples/example.js"],
|
||||
tasks: [ 'browserify' ]
|
||||
}
|
||||
});
|
||||
|
||||
grunt.loadNpmTasks('grunt-browserify');
|
||||
grunt.loadNpmTasks('grunt-contrib-watch');
|
||||
};
|
||||
|
||||
11
node_modules/helpers.less/README.md
generated
vendored
Normal file
11
node_modules/helpers.less/README.md
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
To Build:
|
||||
|
||||
```bash
|
||||
grunt browserify
|
||||
```
|
||||
|
||||
To Develop:
|
||||
|
||||
```bash
|
||||
grunt watch
|
||||
```
|
||||
123
node_modules/helpers.less/helpers.less
generated
vendored
Normal file
123
node_modules/helpers.less/helpers.less
generated
vendored
Normal file
@@ -0,0 +1,123 @@
|
||||
.backface-visibility(@style) {
|
||||
-webkit-backface-visibility: @style;
|
||||
-moz-backface-visibility: @style;
|
||||
-ms-backface-visibility: @style;
|
||||
-o-backface-visibility: @style;
|
||||
backface-visibility: @style;
|
||||
}
|
||||
|
||||
.perspective(@style) {
|
||||
-webkit-perspective: @style;
|
||||
-moz-perspective: @style;
|
||||
-ms-perspective: @style;
|
||||
-o-perspective: @style;
|
||||
perspective: @style;
|
||||
}
|
||||
|
||||
.border-radius(@radius) {
|
||||
-webkit-border-radius: @radius;
|
||||
-moz-border-radius: @radius;
|
||||
border-radius: @radius;
|
||||
}
|
||||
|
||||
.border-radius-topleft(@radius) {
|
||||
-moz-border-radius-topleft: @radius;
|
||||
border-top-left-radius: @radius;
|
||||
}
|
||||
|
||||
.border-radius-topright(@radius) {
|
||||
-moz-border-radius-topright: @radius;
|
||||
border-top-right-radius: @radius;
|
||||
}
|
||||
|
||||
.border-radius-bottomleft(@radius) {
|
||||
-moz-border-radius-bottomleft: @radius;
|
||||
border-bottom-left-radius: @radius;
|
||||
}
|
||||
|
||||
.border-radius-bottomright(@radius) {
|
||||
-moz-border-radius-bottomright: @radius;
|
||||
border-bottom-right-radius: @radius;
|
||||
}
|
||||
|
||||
.no-select() {
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-o-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.do-select() {
|
||||
-moz-user-select: text;
|
||||
-ms-user-select: text;
|
||||
-khtml-user-select: text;
|
||||
-webkit-user-select: text;
|
||||
-o-user-select: text;
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
.border-box() {
|
||||
-moz-box-sizing: border-box;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.box-shadow(@value1, @value2:X, ...) {
|
||||
@value: ~`"@{arguments}".replace(/[\[\]]|\,\sX/g, '')`;
|
||||
-moz-box-shadow: @value;
|
||||
-webkit-box-shadow: @value;
|
||||
box-shadow: @value;
|
||||
}
|
||||
|
||||
.transition(@value1, @value2:X, ...) {
|
||||
@value: ~`"@{arguments}".replace(/[\[\]]|\,\sX/g, '')`;
|
||||
|
||||
-webkit-transition: @value;
|
||||
-moz-transition: @value;
|
||||
-ms-transition: @value;
|
||||
-o-transition: @value;
|
||||
transition: @value;
|
||||
}
|
||||
|
||||
.transformTransition(@value1, @value2:X, ...) {
|
||||
@value: ~`"@{arguments}".replace(/[\[\]]|\,\sX/g, '')`;
|
||||
|
||||
-webkit-transition: -webkit-transform @value;
|
||||
-moz-transition: -moz-transform @value;
|
||||
-ms-transition: -ms-transform @value;
|
||||
-o-transition: -o-transform @value;
|
||||
transition: transform @value;
|
||||
}
|
||||
|
||||
.animation(@value1, @value2:X, ...) {
|
||||
@value: ~`"@{arguments}".replace(/[\[\]]|\,\sX/g, '')`;
|
||||
|
||||
-webkit-animation: @value;
|
||||
-moz-animation: @value;
|
||||
-o-animation: @value;
|
||||
animation: @value;
|
||||
}
|
||||
|
||||
.transform(@value1, @value2:X, ...) {
|
||||
@value: ~`"@{arguments}".replace(/[\[\]]|\,\sX/g, '')`;
|
||||
|
||||
-webkit-transform: @value;
|
||||
-moz-transform: @value;
|
||||
-o-transform: @value;
|
||||
-ms-transform: @value;
|
||||
transform: @value;
|
||||
}
|
||||
|
||||
.rotate(@deg) {
|
||||
.transform(rotate(@deg));
|
||||
}
|
||||
|
||||
.scale(@ratio) {
|
||||
.transform(scale(@ratio, @ratio));
|
||||
}
|
||||
|
||||
.translate(@x, @y) {
|
||||
.transform(translate(@x, @y));
|
||||
}
|
||||
27
node_modules/helpers.less/package.json
generated
vendored
Normal file
27
node_modules/helpers.less/package.json
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "helpers.less",
|
||||
"description": "A set of very convenient less helpers.",
|
||||
"version": "0.1.0",
|
||||
"author": {
|
||||
"name": "Brian Peacock"
|
||||
},
|
||||
"main": "helpers.less",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/bpeacock/helpers.less"
|
||||
},
|
||||
"dependencies": {},
|
||||
"devDependencies": {},
|
||||
"license": "MIT",
|
||||
"keywords": [],
|
||||
"readme": "To Build:\n\n```bash\ngrunt browserify\n```\n\nTo Develop:\n\n```bash\ngrunt watch\n```",
|
||||
"readmeFilename": "README.md",
|
||||
"bugs": {
|
||||
"url": "https://github.com/bpeacock/helpers.less/issues"
|
||||
},
|
||||
"homepage": "https://github.com/bpeacock/helpers.less",
|
||||
"_id": "helpers.less@0.1.0",
|
||||
"_shasum": "d77d92fb3f710e0807f00bd2de920a9e4374799a",
|
||||
"_resolved": "git://github.com/bpeacock/helpers.less.git#4184c4abac632b4b14129ec9ef6e1173370adc22",
|
||||
"_from": "helpers.less@git://github.com/bpeacock/helpers.less.git"
|
||||
}
|
||||
22
node_modules/http-errors/LICENSE
generated
vendored
Normal file
22
node_modules/http-errors/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Jonathan Ong me@jongleberry.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
63
node_modules/http-errors/README.md
generated
vendored
Normal file
63
node_modules/http-errors/README.md
generated
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
# http-errors
|
||||
|
||||
[![NPM Version][npm-image]][npm-url]
|
||||
[![NPM Downloads][downloads-image]][downloads-url]
|
||||
[![Node.js Version][node-version-image]][node-version-url]
|
||||
[![Build Status][travis-image]][travis-url]
|
||||
[![Test Coverage][coveralls-image]][coveralls-url]
|
||||
|
||||
Create HTTP errors for Express, Koa, Connect, etc. with ease.
|
||||
|
||||
## Example
|
||||
|
||||
```js
|
||||
var createError = require('http-errors');
|
||||
|
||||
app.use(function (req, res, next) {
|
||||
if (!req.user) return next(createError(401, 'Please login to view this page.'));
|
||||
next();
|
||||
})
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
This is the current API, currently extracted from Koa and subject to change.
|
||||
|
||||
### Error Properties
|
||||
|
||||
- `message`
|
||||
- `status` and `statusCode` - the status code of the error, defaulting to `500`
|
||||
|
||||
### createError([status], [message], [properties])
|
||||
|
||||
```js
|
||||
var err = createError(404, 'This video does not exist!');
|
||||
```
|
||||
|
||||
- `status: 500` - the status code as a number
|
||||
- `message` - the message of the error, defaulting to node's text for that status code.
|
||||
- `properties` - custom properties to attach to the object
|
||||
|
||||
### new createError\[code || name\](\[msg]\))
|
||||
|
||||
```js
|
||||
var err = new createError.NotFound();
|
||||
```
|
||||
|
||||
- `code` - the status code as a number
|
||||
- `name` - the name of the error as a "bumpy case", i.e. `NotFound` or `InternalServerError`.
|
||||
|
||||
## License
|
||||
|
||||
[MIT](LICENSE)
|
||||
|
||||
[npm-image]: https://img.shields.io/npm/v/http-errors.svg?style=flat
|
||||
[npm-url]: https://npmjs.org/package/http-errors
|
||||
[node-version-image]: https://img.shields.io/node/v/http-errors.svg?style=flat
|
||||
[node-version-url]: http://nodejs.org/download/
|
||||
[travis-image]: https://img.shields.io/travis/jshttp/http-errors.svg?style=flat
|
||||
[travis-url]: https://travis-ci.org/jshttp/http-errors
|
||||
[coveralls-image]: https://img.shields.io/coveralls/jshttp/http-errors.svg?style=flat
|
||||
[coveralls-url]: https://coveralls.io/r/jshttp/http-errors
|
||||
[downloads-image]: https://img.shields.io/npm/dm/http-errors.svg?style=flat
|
||||
[downloads-url]: https://npmjs.org/package/http-errors
|
||||
75
node_modules/http-errors/index.js
generated
vendored
Normal file
75
node_modules/http-errors/index.js
generated
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
|
||||
var statuses = require('statuses');
|
||||
var inherits = require('inherits');
|
||||
|
||||
exports = module.exports = function () {
|
||||
// so much arity going on ~_~
|
||||
var err;
|
||||
var msg;
|
||||
var status = 500;
|
||||
var props = {};
|
||||
for (var i = 0; i < arguments.length; i++) {
|
||||
var arg = arguments[i];
|
||||
if (arg instanceof Error) {
|
||||
err = arg;
|
||||
status = err.status || err.statusCode || status;
|
||||
continue;
|
||||
}
|
||||
switch (typeof arg) {
|
||||
case 'string':
|
||||
msg = arg;
|
||||
break;
|
||||
case 'number':
|
||||
status = arg;
|
||||
break;
|
||||
case 'object':
|
||||
props = arg;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof status !== 'number' || !statuses[status]) status = 500;
|
||||
err = err || new Error(msg || statuses[status]);
|
||||
err = err || new Error(msg || statuses[status]);
|
||||
err.expose = status < 500;
|
||||
for (var key in props) err[key] = props[key];
|
||||
err.status = err.statusCode = status;
|
||||
return err;
|
||||
};
|
||||
|
||||
// create generic error objects
|
||||
var codes = statuses.codes.filter(function (num) {
|
||||
return num >= 400;
|
||||
});
|
||||
|
||||
codes.forEach(function (code) {
|
||||
if (code >= 500) {
|
||||
var ServerError = function ServerError(msg) {
|
||||
var self = new Error(msg != null ? msg : statuses[code])
|
||||
Error.captureStackTrace(self, arguments.callee)
|
||||
self.__proto__ = ServerError.prototype
|
||||
return self
|
||||
}
|
||||
inherits(ServerError, Error);
|
||||
ServerError.prototype.status =
|
||||
ServerError.prototype.statusCode = code;
|
||||
ServerError.prototype.expose = false;
|
||||
exports[code] =
|
||||
exports[statuses[code].replace(/\s+/g, '')] = ServerError;
|
||||
return;
|
||||
}
|
||||
|
||||
var ClientError = function ClientError(msg) {
|
||||
var self = new Error(msg != null ? msg : statuses[code])
|
||||
Error.captureStackTrace(self, arguments.callee)
|
||||
self.__proto__ = ClientError.prototype
|
||||
return self
|
||||
}
|
||||
inherits(ClientError, Error);
|
||||
ClientError.prototype.status =
|
||||
ClientError.prototype.statusCode = code;
|
||||
ClientError.prototype.expose = false;
|
||||
exports[code] =
|
||||
exports[statuses[code].replace(/\s+/g, '')] = ClientError;
|
||||
return;
|
||||
});
|
||||
16
node_modules/http-errors/node_modules/inherits/LICENSE
generated
vendored
Normal file
16
node_modules/http-errors/node_modules/inherits/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
The ISC License
|
||||
|
||||
Copyright (c) Isaac Z. Schlueter
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
42
node_modules/http-errors/node_modules/inherits/README.md
generated
vendored
Normal file
42
node_modules/http-errors/node_modules/inherits/README.md
generated
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
Browser-friendly inheritance fully compatible with standard node.js
|
||||
[inherits](http://nodejs.org/api/util.html#util_util_inherits_constructor_superconstructor).
|
||||
|
||||
This package exports standard `inherits` from node.js `util` module in
|
||||
node environment, but also provides alternative browser-friendly
|
||||
implementation through [browser
|
||||
field](https://gist.github.com/shtylman/4339901). Alternative
|
||||
implementation is a literal copy of standard one located in standalone
|
||||
module to avoid requiring of `util`. It also has a shim for old
|
||||
browsers with no `Object.create` support.
|
||||
|
||||
While keeping you sure you are using standard `inherits`
|
||||
implementation in node.js environment, it allows bundlers such as
|
||||
[browserify](https://github.com/substack/node-browserify) to not
|
||||
include full `util` package to your client code if all you need is
|
||||
just `inherits` function. It worth, because browser shim for `util`
|
||||
package is large and `inherits` is often the single function you need
|
||||
from it.
|
||||
|
||||
It's recommended to use this package instead of
|
||||
`require('util').inherits` for any code that has chances to be used
|
||||
not only in node.js but in browser too.
|
||||
|
||||
## usage
|
||||
|
||||
```js
|
||||
var inherits = require('inherits');
|
||||
// then use exactly as the standard one
|
||||
```
|
||||
|
||||
## note on version ~1.0
|
||||
|
||||
Version ~1.0 had completely different motivation and is not compatible
|
||||
neither with 2.0 nor with standard node.js `inherits`.
|
||||
|
||||
If you are using version ~1.0 and planning to switch to ~2.0, be
|
||||
careful:
|
||||
|
||||
* new version uses `super_` instead of `super` for referencing
|
||||
superclass
|
||||
* new version overwrites current prototype while old one preserves any
|
||||
existing fields on it
|
||||
1
node_modules/http-errors/node_modules/inherits/inherits.js
generated
vendored
Normal file
1
node_modules/http-errors/node_modules/inherits/inherits.js
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
module.exports = require('util').inherits
|
||||
23
node_modules/http-errors/node_modules/inherits/inherits_browser.js
generated
vendored
Normal file
23
node_modules/http-errors/node_modules/inherits/inherits_browser.js
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
if (typeof Object.create === 'function') {
|
||||
// implementation from standard node.js 'util' module
|
||||
module.exports = function inherits(ctor, superCtor) {
|
||||
ctor.super_ = superCtor
|
||||
ctor.prototype = Object.create(superCtor.prototype, {
|
||||
constructor: {
|
||||
value: ctor,
|
||||
enumerable: false,
|
||||
writable: true,
|
||||
configurable: true
|
||||
}
|
||||
});
|
||||
};
|
||||
} else {
|
||||
// old school shim for old browsers
|
||||
module.exports = function inherits(ctor, superCtor) {
|
||||
ctor.super_ = superCtor
|
||||
var TempCtor = function () {}
|
||||
TempCtor.prototype = superCtor.prototype
|
||||
ctor.prototype = new TempCtor()
|
||||
ctor.prototype.constructor = ctor
|
||||
}
|
||||
}
|
||||
35
node_modules/http-errors/node_modules/inherits/package.json
generated
vendored
Normal file
35
node_modules/http-errors/node_modules/inherits/package.json
generated
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"name": "inherits",
|
||||
"description": "Browser-friendly inheritance fully compatible with standard node.js inherits()",
|
||||
"version": "2.0.1",
|
||||
"keywords": [
|
||||
"inheritance",
|
||||
"class",
|
||||
"klass",
|
||||
"oop",
|
||||
"object-oriented",
|
||||
"inherits",
|
||||
"browser",
|
||||
"browserify"
|
||||
],
|
||||
"main": "./inherits.js",
|
||||
"browser": "./inherits_browser.js",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/isaacs/inherits"
|
||||
},
|
||||
"license": "ISC",
|
||||
"scripts": {
|
||||
"test": "node test"
|
||||
},
|
||||
"readme": "Browser-friendly inheritance fully compatible with standard node.js\n[inherits](http://nodejs.org/api/util.html#util_util_inherits_constructor_superconstructor).\n\nThis package exports standard `inherits` from node.js `util` module in\nnode environment, but also provides alternative browser-friendly\nimplementation through [browser\nfield](https://gist.github.com/shtylman/4339901). Alternative\nimplementation is a literal copy of standard one located in standalone\nmodule to avoid requiring of `util`. It also has a shim for old\nbrowsers with no `Object.create` support.\n\nWhile keeping you sure you are using standard `inherits`\nimplementation in node.js environment, it allows bundlers such as\n[browserify](https://github.com/substack/node-browserify) to not\ninclude full `util` package to your client code if all you need is\njust `inherits` function. It worth, because browser shim for `util`\npackage is large and `inherits` is often the single function you need\nfrom it.\n\nIt's recommended to use this package instead of\n`require('util').inherits` for any code that has chances to be used\nnot only in node.js but in browser too.\n\n## usage\n\n```js\nvar inherits = require('inherits');\n// then use exactly as the standard one\n```\n\n## note on version ~1.0\n\nVersion ~1.0 had completely different motivation and is not compatible\nneither with 2.0 nor with standard node.js `inherits`.\n\nIf you are using version ~1.0 and planning to switch to ~2.0, be\ncareful:\n\n* new version uses `super_` instead of `super` for referencing\n superclass\n* new version overwrites current prototype while old one preserves any\n existing fields on it\n",
|
||||
"readmeFilename": "README.md",
|
||||
"bugs": {
|
||||
"url": "https://github.com/isaacs/inherits/issues"
|
||||
},
|
||||
"homepage": "https://github.com/isaacs/inherits",
|
||||
"_id": "inherits@2.0.1",
|
||||
"_shasum": "b17d08d326b4423e568eff719f91b0b1cbdf69f1",
|
||||
"_from": "inherits@~2.0.1",
|
||||
"_resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
|
||||
}
|
||||
25
node_modules/http-errors/node_modules/inherits/test.js
generated
vendored
Normal file
25
node_modules/http-errors/node_modules/inherits/test.js
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
var inherits = require('./inherits.js')
|
||||
var assert = require('assert')
|
||||
|
||||
function test(c) {
|
||||
assert(c.constructor === Child)
|
||||
assert(c.constructor.super_ === Parent)
|
||||
assert(Object.getPrototypeOf(c) === Child.prototype)
|
||||
assert(Object.getPrototypeOf(Object.getPrototypeOf(c)) === Parent.prototype)
|
||||
assert(c instanceof Child)
|
||||
assert(c instanceof Parent)
|
||||
}
|
||||
|
||||
function Child() {
|
||||
Parent.call(this)
|
||||
test(this)
|
||||
}
|
||||
|
||||
function Parent() {}
|
||||
|
||||
inherits(Child, Parent)
|
||||
|
||||
var c = new Child
|
||||
test(c)
|
||||
|
||||
console.log('ok')
|
||||
22
node_modules/http-errors/node_modules/statuses/LICENSE
generated
vendored
Normal file
22
node_modules/http-errors/node_modules/statuses/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Jonathan Ong me@jongleberry.com
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
114
node_modules/http-errors/node_modules/statuses/README.md
generated
vendored
Normal file
114
node_modules/http-errors/node_modules/statuses/README.md
generated
vendored
Normal file
@@ -0,0 +1,114 @@
|
||||
# Statuses
|
||||
|
||||
[![NPM Version][npm-image]][npm-url]
|
||||
[![NPM Downloads][downloads-image]][downloads-url]
|
||||
[![Node.js Version][node-version-image]][node-version-url]
|
||||
[![Build Status][travis-image]][travis-url]
|
||||
[![Test Coverage][coveralls-image]][coveralls-url]
|
||||
|
||||
HTTP status utility for node.
|
||||
|
||||
## API
|
||||
|
||||
```js
|
||||
var status = require('statuses');
|
||||
```
|
||||
|
||||
### var code = status(Integer || String)
|
||||
|
||||
If `Integer` or `String` is a valid HTTP code or status message, then the appropriate `code` will be returned. Otherwise, an error will be thrown.
|
||||
|
||||
```js
|
||||
status(403) // => 403
|
||||
status('403') // => 403
|
||||
status('forbidden') // => 403
|
||||
status('Forbidden') // => 403
|
||||
status(306) // throws, as it's not supported by node.js
|
||||
```
|
||||
|
||||
### status.codes
|
||||
|
||||
Returns an array of all the status codes as `Integer`s.
|
||||
|
||||
### var msg = status[code]
|
||||
|
||||
Map of `code` to `status message`. `undefined` for invalid `code`s.
|
||||
|
||||
```js
|
||||
status[404] // => 'Not Found'
|
||||
```
|
||||
|
||||
### var code = status[msg]
|
||||
|
||||
Map of `status message` to `code`. `msg` can either be title-cased or lower-cased. `undefined` for invalid `status message`s.
|
||||
|
||||
```js
|
||||
status['not found'] // => 404
|
||||
status['Not Found'] // => 404
|
||||
```
|
||||
|
||||
### status.redirect[code]
|
||||
|
||||
Returns `true` if a status code is a valid redirect status.
|
||||
|
||||
```js
|
||||
status.redirect[200] // => undefined
|
||||
status.redirect[301] // => true
|
||||
```
|
||||
|
||||
### status.empty[code]
|
||||
|
||||
Returns `true` if a status code expects an empty body.
|
||||
|
||||
```js
|
||||
status.empty[200] // => undefined
|
||||
status.empty[204] // => true
|
||||
status.empty[304] // => true
|
||||
```
|
||||
|
||||
### status.retry[code]
|
||||
|
||||
Returns `true` if you should retry the rest.
|
||||
|
||||
```js
|
||||
status.retry[501] // => undefined
|
||||
status.retry[503] // => true
|
||||
```
|
||||
|
||||
### statuses/codes.json
|
||||
|
||||
```js
|
||||
var codes = require('statuses/codes.json');
|
||||
```
|
||||
|
||||
This is a JSON file of the status codes
|
||||
taken from `require('http').STATUS_CODES`.
|
||||
This is saved so that codes are consistent even in older node.js versions.
|
||||
For example, `308` will be added in v0.12.
|
||||
|
||||
## Adding Status Codes
|
||||
|
||||
The status codes are primarily sourced from http://www.iana.org/assignments/http-status-codes/http-status-codes-1.csv.
|
||||
Additionally, custom codes are added from http://en.wikipedia.org/wiki/List_of_HTTP_status_codes.
|
||||
These are added manually in the `lib/*.json` files.
|
||||
If you would like to add a status code, add it to the appropriate JSON file.
|
||||
|
||||
To rebuild `codes.json`, run the following:
|
||||
|
||||
```bash
|
||||
# update src/iana.json
|
||||
npm run update
|
||||
# build codes.json
|
||||
npm run build
|
||||
```
|
||||
|
||||
[npm-image]: https://img.shields.io/npm/v/statuses.svg?style=flat
|
||||
[npm-url]: https://npmjs.org/package/statuses
|
||||
[node-version-image]: http://img.shields.io/badge/node.js-%3E%3D_0.6-brightgreen.svg?style=flat
|
||||
[node-version-url]: http://nodejs.org/download/
|
||||
[travis-image]: https://img.shields.io/travis/jshttp/statuses.svg?style=flat
|
||||
[travis-url]: https://travis-ci.org/jshttp/statuses
|
||||
[coveralls-image]: https://img.shields.io/coveralls/jshttp/statuses.svg?style=flat
|
||||
[coveralls-url]: https://coveralls.io/r/jshttp/statuses?branch=master
|
||||
[downloads-image]: http://img.shields.io/npm/dm/statuses.svg?style=flat
|
||||
[downloads-url]: https://npmjs.org/package/statuses
|
||||
64
node_modules/http-errors/node_modules/statuses/codes.json
generated
vendored
Normal file
64
node_modules/http-errors/node_modules/statuses/codes.json
generated
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
{
|
||||
"100": "Continue",
|
||||
"101": "Switching Protocols",
|
||||
"102": "Processing",
|
||||
"200": "OK",
|
||||
"201": "Created",
|
||||
"202": "Accepted",
|
||||
"203": "Non-Authoritative Information",
|
||||
"204": "No Content",
|
||||
"205": "Reset Content",
|
||||
"206": "Partial Content",
|
||||
"207": "Multi-Status",
|
||||
"208": "Already Reported",
|
||||
"226": "IM Used",
|
||||
"300": "Multiple Choices",
|
||||
"301": "Moved Permanently",
|
||||
"302": "Found",
|
||||
"303": "See Other",
|
||||
"304": "Not Modified",
|
||||
"305": "Use Proxy",
|
||||
"306": "(Unused)",
|
||||
"307": "Temporary Redirect",
|
||||
"308": "Permanent Redirect",
|
||||
"400": "Bad Request",
|
||||
"401": "Unauthorized",
|
||||
"402": "Payment Required",
|
||||
"403": "Forbidden",
|
||||
"404": "Not Found",
|
||||
"405": "Method Not Allowed",
|
||||
"406": "Not Acceptable",
|
||||
"407": "Proxy Authentication Required",
|
||||
"408": "Request Timeout",
|
||||
"409": "Conflict",
|
||||
"410": "Gone",
|
||||
"411": "Length Required",
|
||||
"412": "Precondition Failed",
|
||||
"413": "Payload Too Large",
|
||||
"414": "URI Too Long",
|
||||
"415": "Unsupported Media Type",
|
||||
"416": "Range Not Satisfiable",
|
||||
"417": "Expectation Failed",
|
||||
"418": "I'm a teapot",
|
||||
"422": "Unprocessable Entity",
|
||||
"423": "Locked",
|
||||
"424": "Failed Dependency",
|
||||
"425": "Unordered Collection",
|
||||
"426": "Upgrade Required",
|
||||
"428": "Precondition Required",
|
||||
"429": "Too Many Requests",
|
||||
"431": "Request Header Fields Too Large",
|
||||
"451": "Unable For Legal Reasons",
|
||||
"500": "Internal Server Error",
|
||||
"501": "Not Implemented",
|
||||
"502": "Bad Gateway",
|
||||
"503": "Service Unavailable",
|
||||
"504": "Gateway Timeout",
|
||||
"505": "HTTP Version Not Supported",
|
||||
"506": "Variant Also Negotiates",
|
||||
"507": "Insufficient Storage",
|
||||
"508": "Loop Detected",
|
||||
"509": "Bandwidth Limit Exceeded",
|
||||
"510": "Not Extended",
|
||||
"511": "Network Authentication Required"
|
||||
}
|
||||
60
node_modules/http-errors/node_modules/statuses/index.js
generated
vendored
Normal file
60
node_modules/http-errors/node_modules/statuses/index.js
generated
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
|
||||
var codes = require('./codes.json');
|
||||
|
||||
module.exports = status;
|
||||
|
||||
// [Integer...]
|
||||
status.codes = Object.keys(codes).map(function (code) {
|
||||
code = ~~code;
|
||||
var msg = codes[code];
|
||||
status[code] = msg;
|
||||
status[msg] = status[msg.toLowerCase()] = code;
|
||||
return code;
|
||||
});
|
||||
|
||||
// status codes for redirects
|
||||
status.redirect = {
|
||||
300: true,
|
||||
301: true,
|
||||
302: true,
|
||||
303: true,
|
||||
305: true,
|
||||
307: true,
|
||||
308: true,
|
||||
};
|
||||
|
||||
// status codes for empty bodies
|
||||
status.empty = {
|
||||
204: true,
|
||||
205: true,
|
||||
304: true,
|
||||
};
|
||||
|
||||
// status codes for when you should retry the request
|
||||
status.retry = {
|
||||
502: true,
|
||||
503: true,
|
||||
504: true,
|
||||
};
|
||||
|
||||
function status(code) {
|
||||
if (typeof code === 'number') {
|
||||
if (!status[code]) throw new Error('invalid status code: ' + code);
|
||||
return code;
|
||||
}
|
||||
|
||||
if (typeof code !== 'string') {
|
||||
throw new TypeError('code must be a number or string');
|
||||
}
|
||||
|
||||
// '403'
|
||||
var n = parseInt(code, 10)
|
||||
if (!isNaN(n)) {
|
||||
if (!status[n]) throw new Error('invalid status code: ' + n);
|
||||
return n;
|
||||
}
|
||||
|
||||
n = status[code.toLowerCase()];
|
||||
if (!n) throw new Error('invalid status message: "' + code + '"');
|
||||
return n;
|
||||
}
|
||||
49
node_modules/http-errors/node_modules/statuses/package.json
generated
vendored
Normal file
49
node_modules/http-errors/node_modules/statuses/package.json
generated
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"name": "statuses",
|
||||
"description": "HTTP status utility",
|
||||
"version": "1.2.0",
|
||||
"author": {
|
||||
"name": "Jonathan Ong",
|
||||
"email": "me@jongleberry.com",
|
||||
"url": "http://jongleberry.com"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/jshttp/statuses"
|
||||
},
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
"http",
|
||||
"status",
|
||||
"code"
|
||||
],
|
||||
"files": [
|
||||
"index.js",
|
||||
"codes.json",
|
||||
"LICENSE"
|
||||
],
|
||||
"devDependencies": {
|
||||
"csv-parse": "0.0.6",
|
||||
"istanbul": "0",
|
||||
"mocha": "1",
|
||||
"request": "^2.44.0",
|
||||
"stream-to-array": "^2.0.2"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "node scripts/build.js",
|
||||
"update": "node scripts/update.js",
|
||||
"test": "mocha --reporter spec --bail --check-leaks",
|
||||
"test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks",
|
||||
"test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks"
|
||||
},
|
||||
"readme": "# Statuses\n\n[![NPM Version][npm-image]][npm-url]\n[![NPM Downloads][downloads-image]][downloads-url]\n[![Node.js Version][node-version-image]][node-version-url]\n[![Build Status][travis-image]][travis-url]\n[![Test Coverage][coveralls-image]][coveralls-url]\n\nHTTP status utility for node.\n\n## API\n\n```js\nvar status = require('statuses');\n```\n\n### var code = status(Integer || String)\n\nIf `Integer` or `String` is a valid HTTP code or status message, then the appropriate `code` will be returned. Otherwise, an error will be thrown.\n\n```js\nstatus(403) // => 403\nstatus('403') // => 403\nstatus('forbidden') // => 403\nstatus('Forbidden') // => 403\nstatus(306) // throws, as it's not supported by node.js\n```\n\n### status.codes\n\nReturns an array of all the status codes as `Integer`s.\n\n### var msg = status[code]\n\nMap of `code` to `status message`. `undefined` for invalid `code`s.\n\n```js\nstatus[404] // => 'Not Found'\n```\n\n### var code = status[msg]\n\nMap of `status message` to `code`. `msg` can either be title-cased or lower-cased. `undefined` for invalid `status message`s.\n\n```js\nstatus['not found'] // => 404\nstatus['Not Found'] // => 404\n```\n\n### status.redirect[code]\n\nReturns `true` if a status code is a valid redirect status.\n\n```js\nstatus.redirect[200] // => undefined\nstatus.redirect[301] // => true\n```\n\n### status.empty[code]\n\nReturns `true` if a status code expects an empty body.\n\n```js\nstatus.empty[200] // => undefined\nstatus.empty[204] // => true\nstatus.empty[304] // => true\n```\n\n### status.retry[code]\n\nReturns `true` if you should retry the rest.\n\n```js\nstatus.retry[501] // => undefined\nstatus.retry[503] // => true\n```\n\n### statuses/codes.json\n\n```js\nvar codes = require('statuses/codes.json');\n```\n\nThis is a JSON file of the status codes\ntaken from `require('http').STATUS_CODES`.\nThis is saved so that codes are consistent even in older node.js versions.\nFor example, `308` will be added in v0.12.\n\n## Adding Status Codes\n\nThe status codes are primarily sourced from http://www.iana.org/assignments/http-status-codes/http-status-codes-1.csv.\nAdditionally, custom codes are added from http://en.wikipedia.org/wiki/List_of_HTTP_status_codes.\nThese are added manually in the `lib/*.json` files.\nIf you would like to add a status code, add it to the appropriate JSON file.\n\nTo rebuild `codes.json`, run the following:\n\n```bash\n# update src/iana.json\nnpm run update\n# build codes.json\nnpm run build\n```\n\n[npm-image]: https://img.shields.io/npm/v/statuses.svg?style=flat\n[npm-url]: https://npmjs.org/package/statuses\n[node-version-image]: http://img.shields.io/badge/node.js-%3E%3D_0.6-brightgreen.svg?style=flat\n[node-version-url]: http://nodejs.org/download/\n[travis-image]: https://img.shields.io/travis/jshttp/statuses.svg?style=flat\n[travis-url]: https://travis-ci.org/jshttp/statuses\n[coveralls-image]: https://img.shields.io/coveralls/jshttp/statuses.svg?style=flat\n[coveralls-url]: https://coveralls.io/r/jshttp/statuses?branch=master\n[downloads-image]: http://img.shields.io/npm/dm/statuses.svg?style=flat\n[downloads-url]: https://npmjs.org/package/statuses\n",
|
||||
"readmeFilename": "README.md",
|
||||
"bugs": {
|
||||
"url": "https://github.com/jshttp/statuses/issues"
|
||||
},
|
||||
"homepage": "https://github.com/jshttp/statuses",
|
||||
"_id": "statuses@1.2.0",
|
||||
"_shasum": "4445790d65bec29184f50d54810f67e290c1679e",
|
||||
"_from": "statuses@1",
|
||||
"_resolved": "https://registry.npmjs.org/statuses/-/statuses-1.2.0.tgz"
|
||||
}
|
||||
49
node_modules/http-errors/package.json
generated
vendored
Normal file
49
node_modules/http-errors/package.json
generated
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"name": "http-errors",
|
||||
"description": "Create HTTP error objects",
|
||||
"version": "1.2.5",
|
||||
"author": {
|
||||
"name": "Jonathan Ong",
|
||||
"email": "me@jongleberry.com",
|
||||
"url": "http://jongleberry.com"
|
||||
},
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/jshttp/http-errors"
|
||||
},
|
||||
"dependencies": {
|
||||
"inherits": "~2.0.1",
|
||||
"statuses": "1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"istanbul": "0",
|
||||
"mocha": "1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "mocha --reporter spec --bail",
|
||||
"test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot",
|
||||
"test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter dot"
|
||||
},
|
||||
"keywords": [
|
||||
"http",
|
||||
"error"
|
||||
],
|
||||
"files": [
|
||||
"index.js",
|
||||
"LICENSE"
|
||||
],
|
||||
"readme": "# http-errors\n\n[![NPM Version][npm-image]][npm-url]\n[![NPM Downloads][downloads-image]][downloads-url]\n[![Node.js Version][node-version-image]][node-version-url]\n[![Build Status][travis-image]][travis-url]\n[![Test Coverage][coveralls-image]][coveralls-url]\n\nCreate HTTP errors for Express, Koa, Connect, etc. with ease.\n\n## Example\n\n```js\nvar createError = require('http-errors');\n\napp.use(function (req, res, next) {\n if (!req.user) return next(createError(401, 'Please login to view this page.'));\n next();\n})\n```\n\n## API\n\nThis is the current API, currently extracted from Koa and subject to change.\n\n### Error Properties\n\n- `message`\n- `status` and `statusCode` - the status code of the error, defaulting to `500`\n\n### createError([status], [message], [properties])\n\n```js\nvar err = createError(404, 'This video does not exist!');\n```\n\n- `status: 500` - the status code as a number\n- `message` - the message of the error, defaulting to node's text for that status code.\n- `properties` - custom properties to attach to the object\n\n### new createError\\[code || name\\](\\[msg]\\))\n\n```js\nvar err = new createError.NotFound();\n```\n\n- `code` - the status code as a number\n- `name` - the name of the error as a \"bumpy case\", i.e. `NotFound` or `InternalServerError`.\n\n## License\n\n[MIT](LICENSE)\n\n[npm-image]: https://img.shields.io/npm/v/http-errors.svg?style=flat\n[npm-url]: https://npmjs.org/package/http-errors\n[node-version-image]: https://img.shields.io/node/v/http-errors.svg?style=flat\n[node-version-url]: http://nodejs.org/download/\n[travis-image]: https://img.shields.io/travis/jshttp/http-errors.svg?style=flat\n[travis-url]: https://travis-ci.org/jshttp/http-errors\n[coveralls-image]: https://img.shields.io/coveralls/jshttp/http-errors.svg?style=flat\n[coveralls-url]: https://coveralls.io/r/jshttp/http-errors\n[downloads-image]: https://img.shields.io/npm/dm/http-errors.svg?style=flat\n[downloads-url]: https://npmjs.org/package/http-errors\n",
|
||||
"readmeFilename": "README.md",
|
||||
"bugs": {
|
||||
"url": "https://github.com/jshttp/http-errors/issues"
|
||||
},
|
||||
"homepage": "https://github.com/jshttp/http-errors",
|
||||
"_id": "http-errors@1.2.5",
|
||||
"_shasum": "61da92170b47c12bd11083653e9ed44a9b7abe92",
|
||||
"_from": "http-errors@~1.2.0",
|
||||
"_resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.2.5.tgz"
|
||||
}
|
||||
2
node_modules/mkdirp/.npmignore
generated
vendored
Normal file
2
node_modules/mkdirp/.npmignore
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
node_modules/
|
||||
npm-debug.log
|
||||
5
node_modules/mkdirp/.travis.yml
generated
vendored
Normal file
5
node_modules/mkdirp/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- 0.6
|
||||
- 0.8
|
||||
- "0.10"
|
||||
21
node_modules/mkdirp/LICENSE
generated
vendored
Normal file
21
node_modules/mkdirp/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
Copyright 2010 James Halliday (mail@substack.net)
|
||||
|
||||
This project is free software released under the MIT/X11 license:
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
33
node_modules/mkdirp/bin/cmd.js
generated
vendored
Executable file
33
node_modules/mkdirp/bin/cmd.js
generated
vendored
Executable file
@@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
var mkdirp = require('../');
|
||||
var minimist = require('minimist');
|
||||
var fs = require('fs');
|
||||
|
||||
var argv = minimist(process.argv.slice(2), {
|
||||
alias: { m: 'mode', h: 'help' },
|
||||
string: [ 'mode' ]
|
||||
});
|
||||
if (argv.help) {
|
||||
fs.createReadStream(__dirname + '/usage.txt').pipe(process.stdout);
|
||||
return;
|
||||
}
|
||||
|
||||
var paths = argv._.slice();
|
||||
var mode = argv.mode ? parseInt(argv.mode, 8) : undefined;
|
||||
|
||||
(function next () {
|
||||
if (paths.length === 0) return;
|
||||
var p = paths.shift();
|
||||
|
||||
if (mode === undefined) mkdirp(p, cb)
|
||||
else mkdirp(p, mode, cb)
|
||||
|
||||
function cb (err) {
|
||||
if (err) {
|
||||
console.error(err.message);
|
||||
process.exit(1);
|
||||
}
|
||||
else next();
|
||||
}
|
||||
})();
|
||||
12
node_modules/mkdirp/bin/usage.txt
generated
vendored
Normal file
12
node_modules/mkdirp/bin/usage.txt
generated
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
usage: mkdirp [DIR1,DIR2..] {OPTIONS}
|
||||
|
||||
Create each supplied directory including any necessary parent directories that
|
||||
don't yet exist.
|
||||
|
||||
If the directory already exists, do nothing.
|
||||
|
||||
OPTIONS are:
|
||||
|
||||
-m, --mode If a directory needs to be created, set the mode as an octal
|
||||
permission string.
|
||||
|
||||
6
node_modules/mkdirp/examples/pow.js
generated
vendored
Normal file
6
node_modules/mkdirp/examples/pow.js
generated
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
var mkdirp = require('mkdirp');
|
||||
|
||||
mkdirp('/tmp/foo/bar/baz', function (err) {
|
||||
if (err) console.error(err)
|
||||
else console.log('pow!')
|
||||
});
|
||||
97
node_modules/mkdirp/index.js
generated
vendored
Normal file
97
node_modules/mkdirp/index.js
generated
vendored
Normal file
@@ -0,0 +1,97 @@
|
||||
var path = require('path');
|
||||
var fs = require('fs');
|
||||
|
||||
module.exports = mkdirP.mkdirp = mkdirP.mkdirP = mkdirP;
|
||||
|
||||
function mkdirP (p, opts, f, made) {
|
||||
if (typeof opts === 'function') {
|
||||
f = opts;
|
||||
opts = {};
|
||||
}
|
||||
else if (!opts || typeof opts !== 'object') {
|
||||
opts = { mode: opts };
|
||||
}
|
||||
|
||||
var mode = opts.mode;
|
||||
var xfs = opts.fs || fs;
|
||||
|
||||
if (mode === undefined) {
|
||||
mode = 0777 & (~process.umask());
|
||||
}
|
||||
if (!made) made = null;
|
||||
|
||||
var cb = f || function () {};
|
||||
p = path.resolve(p);
|
||||
|
||||
xfs.mkdir(p, mode, function (er) {
|
||||
if (!er) {
|
||||
made = made || p;
|
||||
return cb(null, made);
|
||||
}
|
||||
switch (er.code) {
|
||||
case 'ENOENT':
|
||||
mkdirP(path.dirname(p), opts, function (er, made) {
|
||||
if (er) cb(er, made);
|
||||
else mkdirP(p, opts, cb, made);
|
||||
});
|
||||
break;
|
||||
|
||||
// In the case of any other error, just see if there's a dir
|
||||
// there already. If so, then hooray! If not, then something
|
||||
// is borked.
|
||||
default:
|
||||
xfs.stat(p, function (er2, stat) {
|
||||
// if the stat fails, then that's super weird.
|
||||
// let the original error be the failure reason.
|
||||
if (er2 || !stat.isDirectory()) cb(er, made)
|
||||
else cb(null, made);
|
||||
});
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
mkdirP.sync = function sync (p, opts, made) {
|
||||
if (!opts || typeof opts !== 'object') {
|
||||
opts = { mode: opts };
|
||||
}
|
||||
|
||||
var mode = opts.mode;
|
||||
var xfs = opts.fs || fs;
|
||||
|
||||
if (mode === undefined) {
|
||||
mode = 0777 & (~process.umask());
|
||||
}
|
||||
if (!made) made = null;
|
||||
|
||||
p = path.resolve(p);
|
||||
|
||||
try {
|
||||
xfs.mkdirSync(p, mode);
|
||||
made = made || p;
|
||||
}
|
||||
catch (err0) {
|
||||
switch (err0.code) {
|
||||
case 'ENOENT' :
|
||||
made = sync(path.dirname(p), opts, made);
|
||||
sync(p, opts, made);
|
||||
break;
|
||||
|
||||
// In the case of any other error, just see if there's a dir
|
||||
// there already. If so, then hooray! If not, then something
|
||||
// is borked.
|
||||
default:
|
||||
var stat;
|
||||
try {
|
||||
stat = xfs.statSync(p);
|
||||
}
|
||||
catch (err1) {
|
||||
throw err0;
|
||||
}
|
||||
if (!stat.isDirectory()) throw err0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return made;
|
||||
};
|
||||
4
node_modules/mkdirp/node_modules/minimist/.travis.yml
generated
vendored
Normal file
4
node_modules/mkdirp/node_modules/minimist/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- "0.8"
|
||||
- "0.10"
|
||||
18
node_modules/mkdirp/node_modules/minimist/LICENSE
generated
vendored
Normal file
18
node_modules/mkdirp/node_modules/minimist/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
This software is released under the MIT license:
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
2
node_modules/mkdirp/node_modules/minimist/example/parse.js
generated
vendored
Normal file
2
node_modules/mkdirp/node_modules/minimist/example/parse.js
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
var argv = require('../')(process.argv.slice(2));
|
||||
console.dir(argv);
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user