1
0
mirror of https://github.com/verdaccio/verdaccio.git synced 2024-11-08 23:25:51 +01:00
Conflicts:
	lib/config.js
	lib/config_def.yaml
	lib/index.js
	lib/local-storage.js
	lib/storage.js
	package.json
This commit is contained in:
Alex Kocharin 2014-07-26 20:36:22 +04:00
commit 4f913f2468
32 changed files with 2984 additions and 28 deletions

4
.gitignore vendored

@ -1,10 +1,12 @@
node_modules
package.json
npm-debug.log
sinopia-*.tgz
.DS_Store
###
bin/storage*
bin/htpasswd
bin/*.yaml
test-storage*
example

39
Gruntfile.js Normal file

@ -0,0 +1,39 @@
module.exports = function(grunt) {
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
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'
]);
};

@ -44,6 +44,8 @@ $ 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://index.docker.io/u/keyvanfatehi/docker-sinopia/)
@ -58,20 +60,15 @@ A Sinopia puppet module [is available at puppet forge](http://forge.puppetlabs.c
## 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
@ -112,18 +109,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

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'; } /* '' */

@ -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;
}

200
lib/GUI/css/main.less Normal file

@ -0,0 +1,200 @@
@import "../../../node_modules/helpers.less/helpers.less";
@import "./markdown.less";
@import "./highlight.js.less";
@import "./fontello.less";
/*** Main Styles ***/
body {
margin: 0;
font-family: "Lucida Grande", "Helvetica Neue", Helvetica, Arial, Sans-Serif;
}
a, a:visited {
text-decoration: none;
color: #0D5AFF;
}
a:hover {
text-decoration: underline;
}
.center {
text-align: center;
}
@contentWidth: 880px;
@headerPadding: 10px;
header {
position: fixed;
width: 100%;
background: #FFF;
top: 0;
z-index: 1;
#header-inner {
max-width: @contentWidth + @headerPadding*2;
margin: 0 auto;
}
}
#content {
max-width: @contentWidth;
margin: 0 auto;
padding: 20px;
}
#logo {
margin: 20px auto 0;
width: 400px;
height: 200px;
display: block;
}
h1 {
text-align: center;
a, a:visited {
color: black;
}
}
/*** Setup ***/
#setup {
background: #DB4141;
padding: 15px 20px;
display: inline-block;
.border-radius(4px);
text-align: left;
color: #FFF;
margin-top: 20px;
code {
font-family: Consolas, monaco, monospace;
}
}
/*** Search Box ***/
#search-form {
float: right;
@media (max-width: 540px) {
float: none;
margin-top: 6px;
}
@height: 30px;
input, button {
margin: 0;
vertical-align: top;
border: 1px solid #CCC;
&:focus {
outline: none;
}
}
input {
width: 200px;
height: @height;
.border-box;
padding: 0 5px;
font-size: 16px;
border-right: 0;
}
button {
height: @height;
width: @height;
margin: 0;
border-left: 0;
background: #FFF;
cursor: pointer;
font-size: 16px;
color: #999;
}
}
/*** Heading ***/
h2 {
border-bottom: 6px solid #424242;
margin: 40px 0 0;
padding: 0 @headerPadding 10px;
}
/*** Package Entries ***/
.entry {
background: #F3F3F3;
.border-radius(4px);
padding: 12px 15px 15px;
.transition(height .3s);
overflow: hidden;
margin-bottom: 12px;
h3 {
font-size: 24px;
margin: 0 0 10px;
}
.name:hover {
text-decoration: none;
}
.name:before {
margin: 0;
margin-left: -10px;
.transformTransition(.2s);
}
&.open .name:before {
.rotate(90deg);
}
.version {
font-size: 16px;
color: #666;
}
.author {
font-size: 16px;
float: right;
color: #666;
}
p {
margin: 0;
}
.readme {
font-size: 14px;
margin-top: 10px;
background: #FFF;
padding: 10px 12px;
.border-radius(3px);
}
}
/*** Search Results ***/
.state-search #all-packages {
display: none;
}
.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;
}
}

268
lib/GUI/css/markdown.less Normal file

@ -0,0 +1,268 @@
/*** Sourced from this Gist: https://gist.github.com/andyferra/2554919 ***/
.readme {
a {
color: #4183C4; }
a.absent {
color: #cc0000; }
a.anchor {
display: block;
padding-left: 30px;
margin-left: -30px;
cursor: pointer;
position: absolute;
top: 0;
left: 0;
bottom: 0; }
h1, h2, h3, h4, h5, h6 {
margin: 20px 0 10px;
padding: 0;
font-weight: bold;
-webkit-font-smoothing: antialiased;
cursor: text;
position: relative; }
h1:hover a.anchor, h2:hover a.anchor, h3:hover a.anchor, h4:hover a.anchor, h5:hover a.anchor, h6:hover a.anchor {
background: url("../../images/modules/styleguide/para.png") no-repeat 10px center;
text-decoration: none; }
h1 tt, h1 code {
font-size: inherit; }
h2 tt, h2 code {
font-size: inherit; }
h3 tt, h3 code {
font-size: inherit; }
h4 tt, h4 code {
font-size: inherit; }
h5 tt, h5 code {
font-size: inherit; }
h6 tt, h6 code {
font-size: inherit; }
h1 {
font-size: 28px;
color: black; }
h2 {
font-size: 24px;
border-bottom: 1px solid #cccccc;
color: black; }
h3 {
font-size: 18px; }
h4 {
font-size: 16px; }
h5 {
font-size: 14px; }
h6 {
color: #777777;
font-size: 14px; }
p, blockquote, ul, ol, dl, li, table, pre {
margin: 15px 0; }
hr {
background: transparent url("../../images/modules/pulls/dirty-shade.png") repeat-x 0 0;
border: 0 none;
color: #cccccc;
height: 4px;
padding: 0; }
body > h2:first-child {
margin-top: 0;
padding-top: 0; }
body > h1:first-child {
margin-top: 0;
padding-top: 0; }
body > h1:first-child + h2 {
margin-top: 0;
padding-top: 0; }
body > h3:first-child, body > h4:first-child, body > h5:first-child, body > h6:first-child {
margin-top: 0;
padding-top: 0; }
a:first-child h1, a:first-child h2, a:first-child h3, a:first-child h4, a:first-child h5, a:first-child h6 {
margin-top: 0;
padding-top: 0; }
h1 p, h2 p, h3 p, h4 p, h5 p, h6 p {
margin-top: 0; }
li p.first {
display: inline-block; }
ul, ol {
padding-left: 30px; }
ul :first-child, ol :first-child {
margin-top: 0; }
ul :last-child, ol :last-child {
margin-bottom: 0; }
dl {
padding: 0; }
dl dt {
font-size: 14px;
font-weight: bold;
font-style: italic;
padding: 0;
margin: 15px 0 5px; }
dl dt:first-child {
padding: 0; }
dl dt > :first-child {
margin-top: 0; }
dl dt > :last-child {
margin-bottom: 0; }
dl dd {
margin: 0 0 15px;
padding: 0 15px; }
dl dd > :first-child {
margin-top: 0; }
dl dd > :last-child {
margin-bottom: 0; }
blockquote {
border-left: 4px solid #dddddd;
padding: 0 15px;
color: #777777; }
blockquote > :first-child {
margin-top: 0; }
blockquote > :last-child {
margin-bottom: 0; }
table {
padding: 0; }
table tr {
border-top: 1px solid #cccccc;
background-color: white;
margin: 0;
padding: 0; }
table tr:nth-child(2n) {
background-color: #f8f8f8; }
table tr th {
font-weight: bold;
border: 1px solid #cccccc;
text-align: left;
margin: 0;
padding: 6px 13px; }
table tr td {
border: 1px solid #cccccc;
text-align: left;
margin: 0;
padding: 6px 13px; }
table tr th :first-child, table tr td :first-child {
margin-top: 0; }
table tr th :last-child, table tr td :last-child {
margin-bottom: 0; }
img {
margin: 10px 0;
max-width: 100%; }
span.frame {
display: block;
overflow: hidden; }
span.frame > span {
border: 1px solid #dddddd;
display: block;
float: left;
overflow: hidden;
margin: 13px 0 0;
padding: 7px;
width: auto; }
span.frame span img {
display: block;
float: left; }
span.frame span span {
clear: both;
color: #333333;
display: block;
padding: 5px 0 0; }
span.align-center {
display: block;
overflow: hidden;
clear: both; }
span.align-center > span {
display: block;
overflow: hidden;
margin: 13px auto 0;
text-align: center; }
span.align-center span img {
margin: 0 auto;
text-align: center; }
span.align-right {
display: block;
overflow: hidden;
clear: both; }
span.align-right > span {
display: block;
overflow: hidden;
margin: 13px 0 0;
text-align: right; }
span.align-right span img {
margin: 0;
text-align: right; }
span.float-left {
display: block;
margin-right: 13px;
overflow: hidden;
float: left; }
span.float-left span {
margin: 13px 0 0; }
span.float-right {
display: block;
margin-left: 13px;
overflow: hidden;
float: right; }
span.float-right > span {
display: block;
overflow: hidden;
margin: 13px auto 0;
text-align: right; }
code, tt {
margin: 0 2px;
padding: 0 5px;
white-space: nowrap;
border: 1px solid #eaeaea;
background-color: #f8f8f8;
border-radius: 3px; }
pre code {
margin: 0;
padding: 0;
white-space: pre;
border: none;
background: transparent; }
.highlight pre {
background-color: #f8f8f8;
border: 1px solid #cccccc;
font-size: 13px;
line-height: 19px;
overflow: auto;
padding: 6px 10px;
border-radius: 3px; }
pre {
background-color: #f8f8f8;
border: 1px solid #cccccc;
font-size: 13px;
line-height: 19px;
overflow: auto;
padding: 6px 10px;
border-radius: 3px; }
pre code, pre tt {
background-color: transparent;
border: none; }
}

8
lib/GUI/entry.hbs Normal file

@ -0,0 +1,8 @@
<article class='entry' data-name='{{ name }}' data-version='{{ version }}'>
<h3>
<a class='name icon-angle-right' href='javascript:void(0)'>{{ name }}</a>
<small class='version'>v{{ version }}</small>
<div class='author'>By: {{ _npmUser.name }}</div>
</h3>
<p>{{ description }}</p>
</article>

52
lib/GUI/index.hbs Normal file

@ -0,0 +1,52 @@
<!doctype html>
<html lang="en-us">
<head>
<meta charset="utf-8">
<title>{{ name }}</title>
<link rel="icon" type="image/ico" href="/-/static/favicon.ico"/>
<link rel="stylesheet" type="text/css" href="/-/static/main.css">
</head>
<body>
<header>
<div id='header-inner'>
<a href='/'><img id='logo' alt='{{ name }}' title='{{ name }}' src='/-/logo' /></a>
<div class='center'>
<article id='setup'>
<code>npm set registry {{ baseUrl }}</code><br>
<code>npm adduser --registry {{ baseUrl }}</code>
</article>
</div>
<h2>
Available Packages:
<form id='search-form'>
<input type='text' placeholder='Search' /><button class='clear icon-search'></button>
</form>
</h2>
</div>
</header>
<div id='content'>
<div id='search-results'></div>
<div 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}}
</div>
</div>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
<script type='text/javascript' src='/-/static/main.js'></script>
</body>
</html>

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.height())
.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.height())
.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.height();
$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');
});
}
});
}
});
});

29
lib/GUI/js/header.js Normal file

@ -0,0 +1,29 @@
var $ = require('unopinionate').selector,
onScroll = require('onscroll');
$(function() {
var $header = $('header'),
$content = $('#content'),
bottomOffset = 52;
var scrollFunc = function(top) {
var limit = $header.outerHeight() - bottomOffset;
if(top < 0) {
$header.css('top', 0);
}
else if(top > limit) {
$header.css('top', -limit + 'px');
}
else {
$header.css('top', -top + 'px');
}
};
onScroll(scrollFunc);
scrollFunc();
$(window).resize(function() {
$content.css('margin-top', $header.outerHeight());
}).resize();
});

3
lib/GUI/js/main.js Normal file

@ -0,0 +1,3 @@
require('./search');
require('./entry');
require('./header');

70
lib/GUI/js/search.js Normal file

@ -0,0 +1,70 @@
var $ = require('unopinionate').selector,
template = require('../entry.hbs'),
onScroll = require('onscroll');
$(function() {
'use strict';
var $form = $('#search-form'),
$input = $form.find('input'),
$searchResults = $("#search-results"),
$body = $('body'),
$clear = $form.find('.clear'),
request,
currentResults;
$form.bind('submit keyup', function(e) {
e.preventDefault();
var q = $input.val();
$body.addClass('state-search');
//Switch the icons
$clear
[q ? 'addClass' : 'removeClass']('icon-cancel')
[!q ? 'addClass' : 'removeClass']('icon-search');
if(q) {
if(request) {
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) {
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>");
}
});
}
else {
request.abort();
currentResults = null;
$searchResults.html('');
$body.removeClass('state-search');
}
});
$clear.click(function(e) {
e.preventDefault();
$input.val('');
$form.keyup();
});
});

@ -4,6 +4,7 @@ var assert = require('assert')
, minimatch = require('minimatch')
, UError = require('./error').UserError
, utils = require('./utils')
, users = require('./users')
// [[a, [b, c]], d] -> [a, b, c, d]
function flatten(array) {

@ -9,6 +9,9 @@ users:
# crypto.createHash('sha1').update(pass).digest('hex')
password: __PASSWORD__
title: Sinopia
# logo: logo.png
users_file: ./htpasswd
# Maximum amount of users allowed to register, defaults to "+inf".

@ -11,6 +11,13 @@ var express = require('express')
, validate_name = Middleware.validate_name
, media = Middleware.media
, expect_json = Middleware.expect_json
, Handlebars = require('handlebars')
, fs = require('fs')
, localList = require('./local-list')
, search = require('./search')
, _ = require('underscore')
, users = require('./users')
, marked = require('marked');
function match(regexp) {
return function(req, res, next, value, name) {
@ -24,7 +31,9 @@ function match(regexp) {
module.exports = function(config_hash) {
var config = new Config(config_hash)
, storage = new Storage(config)
, storage = new Storage(config);
search.configureStorage(storage);
var can = function(action) {
return function(req, res, next) {
@ -109,13 +118,7 @@ module.exports = function(config_hash) {
app.param('org_couchdb_user', match(/^org\.couchdb\.user:/))
app.param('anything', match(/.*/))
/* app.get('/', function(req, res) {
res.send({
error: 'unimplemented'
})
})*/
/* app.get('/-/all', function(req, res) {
/* app.get('/-/all', function(req, res) {
var https = require('https')
var JSONStream = require('JSONStream')
var request = require('request')({
@ -126,6 +129,21 @@ module.exports = function(config_hash) {
console.log(d)
})
})*/
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('/', can('access'), function(req, res, next) {
res.setHeader('Content-Type', 'text/html');
storage.get_local(function(err, packages) {
res.send(template({
name: config.title || "Sinopia",
packages: packages,
baseUrl: config.url_prefix || req.protocol + '://' + req.get('host') + '/'
}));
});
});
// TODO: anonymous user?
app.get('/:package/:version?', can('access'), function(req, res, next) {
@ -185,7 +203,7 @@ module.exports = function(config_hash) {
})
//app.get('/*', function(req, res) {
// proxy.request(req, res)
// proxy.request(req, res)
//})
// placeholder 'cause npm require to be authenticated to publish
@ -239,6 +257,65 @@ module.exports = function(config_hash) {
}
})
// Static
app.get('/-/static/:file', function(req, res, next) {
var file = __dirname + '/static/' + req.params.file;
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.logo ? config.logo : __dirname + "/static/logo.png");
});
// Search
app.get('/-/search/:query', function(req, res, next) {
var results = search.query(req.params.query),
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/:name/:version', function(req, res, next) {
storage.get_readme(req.params.name, req.params.version, function(readme) {
res.send(marked(readme));
});
});
// tagging a package
app.put('/:package/:tag', can('publish'), media('application/json'), function(req, res, next) {
if (typeof(req.body) !== 'string') return next('route')

36
lib/local-list.js Normal file

@ -0,0 +1,36 @@
var fs = require('fs')
, listFilePath = './local-list.json';
var LocalList = function() {
if(fs.existsSync(listFilePath)) {
this.list = JSON.parse(fs.readFileSync(listFilePath, 'utf8'));
}
else {
this.list = [];
}
};
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(listFilePath, JSON.stringify(this.list)); //Uses sync to prevent ugly race condition
}
};
module.exports = new LocalList();

@ -8,6 +8,9 @@ var fs = require('fs')
, mystreams = require('./streams')
, Logger = require('./logger')
, info_file = 'package.json'
, localList = require('./local-list')
, targz = require('tar.gz')
, search = require('./search');
//
// Implements Storage interface
@ -45,7 +48,7 @@ Storage.prototype._internal_error = function(err, file, message) {
})
}
Storage.prototype.add_package = function(name, metadata, callback) {
Storage.prototype.add_package = function(name, info, callback) {
this.storage(name).create_json(info_file, get_boilerplate(name), function(err) {
if (err && err.code === 'EEXISTS') {
return callback(new UError({
@ -53,6 +56,10 @@ Storage.prototype.add_package = function(name, metadata, callback) {
message: 'this package is already present'
}))
}
search.add(info.versions[info['dist-tags'].latest]);
localList.add(name);
callback()
})
}
@ -91,10 +98,13 @@ Storage.prototype.remove_package = function(name, callback) {
// try to unlink the directory, but ignore errors because it can fail
self.storage(name).rmdir('.', function(err) {
callback(err)
})
})
})
})
});
});
});
});
search.remove(name);
localList.remove(name);
}
Storage.prototype._read_create_package = function(name, callback) {
@ -378,6 +388,41 @@ Storage.prototype.add_tarball = function(name, filename) {
return stream
}
Storage.prototype.unpack_tarball = function(file, callback) {
new targz().extract(file + '.tgz', file, callback);
};
Storage.prototype.get_readme = function(name, version, callback) {
var self = this,
fileName = this.storage(name).path + '/' + name + '-' + version;
fs.exists(fileName, function(exists) {
if(exists) {
returnReadme();
}
else {
self.unpack_tarball(fileName, function(err) {
returnReadme();
});
}
});
function returnReadme() {
var readmeFileName = fileName + '/package/README.md';
fs.exists(readmeFileName, function(exists) {
if(exists) {
fs.readFile(readmeFileName, {encoding: "UTF-8"}, function(err, file) {
callback(file);
});
}
else {
callback('');
}
});
}
};
Storage.prototype.get_tarball = function(name, filename, callback) {
assert(utils.validate_name(filename))

@ -136,7 +136,9 @@ module.exports.log_and_etagify = function(req, res, next) {
res.send = function(body) {
try {
if (typeof(body) === 'string' || typeof(body) === 'object') {
res.header('Content-type', 'application/json')
if (!res.getHeader('Content-type')) {
res.header('Content-type', 'application/json')
}
if (typeof(body) === 'object' && body != null) {
if (typeof(body.error) === 'string') {

46
lib/search.js Normal file

@ -0,0 +1,46 @@
var lunr = require('lunr')
, localList = require('./local-list');
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.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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

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

Binary file not shown.

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="&#xe801;" 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="&#xe803;" 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="&#xe802;" 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="&#xe800;" 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

Binary file not shown.

BIN
lib/static/fontello.woff Normal file

Binary file not shown.

BIN
lib/static/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

731
lib/static/main.css Normal file

File diff suppressed because one or more lines are too long

965
lib/static/main.js Normal file

@ -0,0 +1,965 @@
(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 += "<article 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 <h3>\n <a class='name icon-angle-right' 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 <div class='author'>By: "
+ escapeExpression(((stack1 = ((stack1 = (depth0 && depth0._npmUser)),stack1 == null || stack1 === false ? stack1 : stack1.name)),typeof stack1 === functionType ? stack1.apply(depth0) : stack1))
+ "</div>\n </h3>\n <p>";
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</article>";
return buffer;
});
},{"handlebars/runtime":12}],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.height())
.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.height())
.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.height();
$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":13,"transition-complete":15,"unopinionate":16}],3:[function(require,module,exports){
var $ = require('unopinionate').selector,
onScroll = require('onscroll');
$(function() {
var $header = $('header'),
$content = $('#content'),
bottomOffset = 52;
var scrollFunc = function(top) {
var limit = $header.outerHeight() - bottomOffset;
if(top < 0) {
$header.css('top', 0);
}
else if(top > limit) {
$header.css('top', -limit + 'px');
}
else {
$header.css('top', -top + 'px');
}
};
onScroll(scrollFunc);
scrollFunc();
$(window).resize(function() {
$content.css('margin-top', $header.outerHeight());
}).resize();
});
},{"onscroll":14,"unopinionate":16}],4:[function(require,module,exports){
require('./search');
require('./entry');
require('./header');
},{"./entry":2,"./header":3,"./search":5}],5:[function(require,module,exports){
var $ = require('unopinionate').selector,
template = require('../entry.hbs'),
onScroll = require('onscroll');
$(function() {
'use strict';
var $form = $('#search-form'),
$input = $form.find('input'),
$searchResults = $("#search-results"),
$body = $('body'),
$clear = $form.find('.clear'),
request,
currentResults;
$form.bind('submit keyup', function(e) {
e.preventDefault();
var q = $input.val();
$body.addClass('state-search');
//Switch the icons
$clear
[q ? 'addClass' : 'removeClass']('icon-cancel')
[!q ? 'addClass' : 'removeClass']('icon-search');
if(q) {
if(request) {
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) {
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>");
}
});
}
else {
request.abort();
currentResults = null;
$searchResults.html('');
$body.removeClass('state-search');
}
});
$clear.click(function(e) {
e.preventDefault();
$input.val('');
$form.keyup();
});
});
},{"../entry.hbs":1,"onscroll":14,"unopinionate":16}],6:[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":7,"./handlebars/exception":8,"./handlebars/runtime":9,"./handlebars/safe-string":10,"./handlebars/utils":11}],7:[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":8,"./utils":11}],8:[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;
},{}],9:[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":7,"./exception":8,"./utils":11}],10:[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;
},{}],11:[function(require,module,exports){
"use strict";
/*jshint -W004 */
var SafeString = require("./safe-string")["default"];
var escape = {
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
'"': "&quot;",
"'": "&#x27;",
"`": "&#x60;"
};
var badChars = /[&<>"'`]/g;
var possible = /[&<>"'`]/;
function escapeChar(chr) {
return escape[chr] || "&amp;";
}
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":10}],12:[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":6}],13:[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":16}],14:[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":16}],15:[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);
},{}],16:[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 : {})
},{}]},{},[4])

@ -6,6 +6,7 @@ var async = require('async')
, mystreams = require('./streams')
, utils = require('./utils')
, Logger = require('./logger')
, localList = require('./local-list');
//
// Implements Storage interface
@ -173,6 +174,10 @@ Storage.prototype.add_tarball = function(name, filename) {
return this.local.add_tarball(name, filename)
}
Storage.prototype.get_readme = function(name, version, callback) {
return this.local.get_readme(name, version, callback);
};
//
// Get a tarball from a storage for {name} package
//
@ -406,6 +411,34 @@ Storage.prototype.search = function(startkey, options, callback) {
remote_search()
}
Storage.prototype.get_local = function(callback) {
var self = this
, locals = localList.get()
, packages = [];
var getPackage = function(i) {
self.get_package(locals[i], function(err, info) {
var latest = info['dist-tags'].latest;
packages.push(info.versions[latest]);
if(err || i >= locals.length - 1) {
callback(err, 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)

37
lib/users.js Normal file

@ -0,0 +1,37 @@
var fs = require('fs')
, crypto = require('crypto')
, usersPath = './users.json';
var Users = function() {
if(fs.existsSync(usersPath)) {
this.users = JSON.parse(fs.readFileSync(usersPath, 'utf8'));
}
else {
this.users = {};
}
};
Users.prototype = {
add: function(params, callback) {
//Hash the Password
if(params.password) {
params.password = crypto.createHash('sha1').update(params.password).digest('hex');
}
else if(params.password_sha) {
params.password = params.password_sha;
}
//Save
this.users[params.name] = params;
this.sync(callback);
},
remove: function(name, callback) {
delete this.users[name];
this.sync(callback);
},
sync: function(callback) {
fs.writeFile(usersPath, JSON.stringify(this.users), callback);
}
};
module.exports = new Users();

@ -28,6 +28,17 @@ dependencies:
minimatch: '>= 0.2.14'
bunyan: '>= 0.22.1'
mkdirp: '>= 0.3.5'
handlebars: '1.x.x'
helpers.less: 'git://github.com/bpeacock/helpers.less.git'
highlight.js: '^8.0.0'
lunr: '^0.5.2'
marked: '^0.3.2'
onclick: '^0.1.0'
onscroll: '0.0.3'
tar.gz: '^0.1.1'
transition-complete: '0.0.2'
underscore: '^1.6.0'
unopinionate: '0.0.4'
optionalDependencies:
fs-ext: '>= 0.3.2'
@ -44,6 +55,13 @@ devDependencies:
# installed, but I don't want it to be installed everytime
#heapdump: '*'
browserify: '^3.46.0'
browserify-handlebars: '~0.2.0'
grunt: '^0.4.4'
grunt-browserify: '^2.0.8'
grunt-contrib-less: '^0.11.0'
grunt-contrib-watch: '^0.6.1'
keywords:
- private
- package
@ -71,4 +89,3 @@ publishConfig:
license:
type: WTFPL
url: http://www.wtfpl.net/txt/copying/