├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .npmignore ├── .travis.yml ├── Gruntfile.js ├── History.md ├── README.md ├── SERVER.md ├── bin └── sinopia ├── conf ├── README.md ├── default.yaml └── full.yaml ├── index.js ├── lib ├── GUI │ ├── .eslintrc │ ├── css │ │ ├── bootstrap.css │ │ ├── fontello.less │ │ ├── helpers.less │ │ ├── highlight.js.less │ │ ├── main.less │ │ ├── markdown.less │ │ ├── responsive.less │ │ └── styles.less │ ├── entry.hbs │ ├── index.hbs │ └── js │ │ ├── bootstrap-modal.js │ │ ├── entry.js │ │ ├── main.js │ │ └── search.js ├── auth.js ├── cli.js ├── config-path.js ├── config.js ├── index-api.js ├── index-web.js ├── index.js ├── local-data.js ├── local-fs.js ├── local-storage.js ├── logger.js ├── middleware.js ├── plugin-loader.js ├── search.js ├── static │ ├── ajax.gif │ ├── favicon.ico │ ├── favicon.png │ ├── fontello.eot │ ├── fontello.svg │ ├── fontello.ttf │ ├── fontello.woff │ ├── jquery.min.js │ ├── logo-sm.png │ ├── logo.png │ ├── main.css │ └── main.js ├── status-cats.js ├── storage.js ├── streams.js ├── up-storage.js └── utils.js ├── npm-shrinkwrap.json ├── package.yaml └── test ├── .eslintrc ├── README.md ├── functional ├── access.js ├── addtag.js ├── adduser.js ├── basic.js ├── config-1.yaml ├── config-2.yaml ├── fixtures │ ├── binary │ ├── newnpmreg.json │ ├── publish.json5 │ ├── scoped.json │ └── tags.json ├── gh29.js ├── gzip.js ├── incomplete.js ├── index.js ├── lib │ ├── package.js │ ├── server.js │ ├── smart_request.js │ └── startup.js ├── mirror.js ├── newnpmreg.js ├── nullstorage.js ├── plugins.js ├── plugins │ ├── authenticate.js │ └── authorize.js ├── race.js ├── racycrash.js ├── scoped.js ├── security.js └── tags.js ├── integration ├── config.yaml ├── sinopia-test-1.2.3.tgz └── test.pl ├── mocha.opts └── unit ├── config_def.js ├── listen_addr.js ├── mystreams.js ├── no_proxy.js ├── parse_interval.js ├── st_merge.js ├── tag_version.js ├── toplevel.js ├── utils.js └── validate_all.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 2 | root = true 3 | 4 | # Unix-style newlines with a newline ending every file 5 | [*] 6 | end_of_line = lf 7 | insert_final_newline = true 8 | 9 | # 2 space indentation 10 | [{.,}*.{js,yml,yaml}] 11 | indent_style = space 12 | indent_size = 2 13 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | lib/static 3 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | # vim: syntax=yaml 2 | 3 | # 4 | # List of very light restrictions designed to prevent obvious errors, 5 | # not impose our own code style upon other contributors. 6 | # 7 | # This is supposed to be used with `eslint --reset` 8 | # 9 | # Created to work with eslint@0.18.0 10 | # 11 | 12 | env: 13 | node: true 14 | 15 | rules: 16 | # useful to have in node.js, 17 | # if you're sure you don't need to handle error, rename it to "_err" 18 | handle-callback-err: 2 19 | 20 | # just to make sure we don't forget to remove them when releasing 21 | no-debugger: 2 22 | 23 | # add "falls through" for those 24 | no-fallthrough: 2 25 | 26 | # just warnings about whitespace weirdness here 27 | eol-last: 1 28 | no-irregular-whitespace: 1 29 | no-mixed-spaces-and-tabs: [1, smart-tabs] 30 | no-trailing-spaces: 1 31 | 32 | # probably always an error, tell me if it's not 33 | no-new-require: 2 34 | 35 | # single most important rule here, without it linting won't even 36 | # make any sense 37 | no-undef: 2 38 | 39 | # in practice, those are always errors 40 | no-unreachable: 2 41 | 42 | # useful for code clean-up 43 | no-unused-vars: [1, {"vars": "all", "args": "none"}] 44 | 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /package.json 2 | npm-debug.log 3 | sinopia-*.tgz 4 | .DS_Store 5 | 6 | ### 7 | bin/** 8 | !bin/sinopia 9 | test-storage* 10 | 11 | node_modules 12 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package.json 3 | npm-debug.log 4 | sinopia-*.tgz 5 | 6 | ### 7 | bin/** 8 | !bin/sinopia 9 | test-storage* 10 | 11 | /.* 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '0.10' 4 | - '0.12' 5 | - '1' 6 | - '2' 7 | - 'iojs' 8 | sudo: false 9 | matrix: 10 | allow_failures: 11 | - node_js: 'iojs' 12 | fast_finish: true 13 | script: npm install . && npm run test-travis 14 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | grunt.initConfig({ 3 | pkg: grunt.file.readYAML('package.yaml'), 4 | browserify: { 5 | dist: { 6 | files: { 7 | 'lib/static/main.js': [ 'lib/GUI/js/main.js' ] 8 | }, 9 | options: { 10 | debug: true, 11 | transform: [ 'browserify-handlebars' ] 12 | } 13 | } 14 | }, 15 | less: { 16 | dist: { 17 | files: { 18 | 'lib/static/main.css': [ 'lib/GUI/css/main.less' ] 19 | }, 20 | options: { 21 | sourceMap: false 22 | } 23 | } 24 | }, 25 | watch: { 26 | files: [ 'lib/GUI/**/*' ], 27 | tasks: [ 'default' ] 28 | } 29 | }) 30 | 31 | grunt.loadNpmTasks('grunt-browserify') 32 | grunt.loadNpmTasks('grunt-contrib-watch') 33 | grunt.loadNpmTasks('grunt-contrib-less') 34 | 35 | grunt.registerTask('default', [ 36 | 'browserify', 37 | 'less' 38 | ]) 39 | } 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | `sinopia` - a private/caching npm repository server 2 | 3 | [![npm version badge](https://img.shields.io/npm/v/sinopia.svg)](https://www.npmjs.org/package/sinopia) 4 | [![travis badge](http://img.shields.io/travis/rlidwka/sinopia.svg)](https://travis-ci.org/rlidwka/sinopia) 5 | [![downloads badge](http://img.shields.io/npm/dm/sinopia.svg)](https://www.npmjs.org/package/sinopia) 6 | 7 | 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. 8 | 9 |

10 | 11 | ## Use cases 12 | 13 | 1. Use private packages. 14 | 15 | If you want to use all benefits of npm package system in your company without sending all code to the public, and use your private packages just as easy as public ones. 16 | 17 | See [using private packages](#using-private-packages) section for details. 18 | 19 | 2. Cache npmjs.org registry. 20 | 21 | If you have more than one server you want to install packages on, you might want to use this to decrease latency 22 | (presumably "slow" npmjs.org will be connected to only once per package/version) and provide limited failover (if npmjs.org is down, we might still find something useful in the cache). 23 | 24 | See [using public packages](#using-public-packages-from-npmjsorg) section for details. 25 | 26 | 3. Override public packages. 27 | 28 | 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. 29 | 30 | See [override public packages](#override-public-packages) section for details. 31 | 32 | ## Installation 33 | 34 | ```bash 35 | # installation and starting (application will create default 36 | # config in config.yaml you can edit later) 37 | $ npm install -g sinopia 38 | $ sinopia 39 | 40 | # npm configuration 41 | $ npm set registry http://localhost:4873/ 42 | 43 | # if you use HTTPS, add an appropriate CA information 44 | # ("null" means get CA list from OS) 45 | $ npm set ca null 46 | ``` 47 | 48 | Now you can navigate to [http://localhost:4873/](http://localhost:4873/) where your local packages will be listed and can be searched. 49 | 50 | ### Docker 51 | 52 | A Sinopia docker image [is available](https://registry.hub.docker.com/u/keyvanfatehi/sinopia/) 53 | 54 | ### Chef 55 | 56 | A Sinopia Chef cookbook [is available at Opscode community](http://community.opscode.com/cookbooks/sinopia) source: https://github.com/BarthV/sinopia-cookbook 57 | 58 | ### Puppet 59 | 60 | A Sinopia puppet module [is available at puppet forge](http://forge.puppetlabs.com/saheba/sinopia) source: https://github.com/saheba/puppet-sinopia 61 | 62 | ## Configuration 63 | 64 | When you start a server, it auto-creates a config file. 65 | 66 | ## Adding a new user 67 | 68 | ```bash 69 | npm adduser --registry http://localhost:4873/ 70 | ``` 71 | 72 | This will prompt you for user credentials which will be saved on the Sinopia server. 73 | 74 | ## Using private packages 75 | 76 | You can add users and manage which users can access which packages. 77 | 78 | It is recommended that you define a prefix for your private packages, for example "local", so all your private things will look like this: `local-foo`. This way you can clearly separate public packages from private ones. 79 | 80 | ## Using public packages from npmjs.org 81 | 82 | If some package doesn't exist in the storage, server will try to fetch it from npmjs.org. If npmjs.org is down, it serves packages from cache pretending that no other packages exist. Sinopia will download only what's needed (= requested by clients), and this information will be cached, so if client will ask the same thing second time, it can be served without asking npmjs.org for it. 83 | 84 | Example: if you successfully request express@3.0.1 from this server once, you'll able to do that again (with all it's dependencies) anytime even if npmjs.org is down. But say express@3.0.0 will not be downloaded until it's actually needed by somebody. And if npmjs.org is offline, this server would say that only express@3.0.1 (= only what's in the cache) is published, but nothing else. 85 | 86 | ## Override public packages 87 | 88 | If you want to use a modified version of some public package `foo`, you can just publish it to your local server, so when your type `npm install foo`, it'll consider installing your version. 89 | 90 | There's two options here: 91 | 92 | 1. You want to create a separate fork and stop synchronizing with public version. 93 | 94 | If you want to do that, you should modify your configuration file so sinopia won't make requests regarding this package to npmjs anymore. Add a separate entry for this package to *config.yaml* and remove `npmjs` from `proxy_access` list and restart the server. 95 | 96 | When you publish your package locally, you should probably start with version string higher than existing one, so it won't conflict with existing package in the cache. 97 | 98 | 2. You want to temporarily use your version, but return to public one as soon as it's updated. 99 | 100 | In order to avoid version conflicts, you should use a custom pre-release suffix of the next patch version. For example, if a public package has version 0.1.2, you can upload 0.1.3-my-temp-fix. This way your package will be used until its original maintainer updates his public package to 0.1.3. 101 | 102 | ## Compatibility 103 | 104 | Sinopia aims to support all features of a standard npm client that make sense to support in private repository. Unfortunately, it isn't always possible. 105 | 106 | Basic features: 107 | 108 | - Installing packages (npm install, npm upgrade, etc.) - supported 109 | - Publishing packages (npm publish) - supported 110 | 111 | Advanced package control: 112 | 113 | - Unpublishing packages (npm unpublish) - supported 114 | - Tagging (npm tag) - not yet supported, should be soon 115 | - Deprecation (npm deprecate) - not supported 116 | 117 | User management: 118 | 119 | - Registering new users (npm adduser {newuser}) - supported 120 | - Transferring ownership (npm owner add {user} {pkg}) - not supported, sinopia uses its own acl management system 121 | 122 | Misc stuff: 123 | 124 | - Searching (npm search) - supported in the browser client but not command line 125 | - Starring (npm star, npm unstar) - not supported, doesn't make sense in private registry 126 | 127 | ## Storage 128 | 129 | No CouchDB here. This application is supposed to work with zero configuration, so filesystem is used as a storage. 130 | 131 | If you want to use a database instead, ask for it, we'll come up with some kind of a plugin system. 132 | 133 | ## Similar existing things 134 | 135 | - npm + git (I mean, using git+ssh:// dependencies) - most people seem to use this, but it's a terrible idea... *npm update* doesn't work, can't use git subdirectories this way, etc. 136 | - [reggie](https://github.com/mbrevoort/node-reggie) - this looks very interesting indeed... I might borrow some code there. 137 | - [shadow-npm](https://github.com/dominictarr/shadow-npm), [public service](http://shadow-npm.net/) - it uses the same code as npmjs.org + service is dead 138 | - [gemfury](http://www.gemfury.com/l/npm-registry) and others - those are closed-source cloud services, and I'm not in a mood to trust my private code to somebody (security through obscurity yeah!) 139 | - npm-registry-proxy, npm-delegate, npm-proxy - those are just proxies... 140 | - Is there something else? 141 | 142 | -------------------------------------------------------------------------------- /SERVER.md: -------------------------------------------------------------------------------- 1 | This is mostly basic linux server configuration stuff but I felt it important to document and share the steps I took to get sinopia running permanently on my server. You will need root (or sudo) permissions for the following. 2 | 3 | ## Running as a separate user 4 | First create the sinopia user: 5 | ```bash 6 | $ sudo adduser --disabled-login --gecos 'Sinopia NPM mirror' sinopia 7 | ``` 8 | 9 | You create a shell as the sinopia user using the following command: 10 | ```bash 11 | $ sudo su sinopia 12 | $ cd ~ 13 | ``` 14 | 15 | The 'cd ~' command send you to the home directory of the sinopia user. Make sure you run sinopia at least once to generate the config file. Edit it according to your needs. 16 | 17 | ## Listening on all addresses 18 | If you want to listen to every external address set the listen directive in the config to: 19 | ``` 20 | # you can specify listen address (or simply a port) 21 | listen: 0.0.0.0:4873 22 | ``` 23 | 24 | ## Keeping sinopia running forever 25 | We can use the node package called 'forever' to keep sinopia running all the time. 26 | https://github.com/nodejitsu/forever 27 | 28 | First install forever globally: 29 | ```bash 30 | $ sudo npm install -g forever 31 | ``` 32 | 33 | Make sure you've started sinopia at least once to generate the config file and write down the created admin user. You can then use the following command to start sinopia: 34 | ```bash 35 | $ forever start `which sinopia` 36 | ``` 37 | 38 | You can check the documentation for more information on how to use forever. 39 | 40 | ## Surviving server restarts 41 | We can use crontab and forever together to restart sinopia after a server reboot. 42 | When you're logged in as the sinopia user do the following: 43 | 44 | ```bash 45 | $ crontab -e 46 | ``` 47 | 48 | This might ask you to choose an editor. Pick your favorite and proceed. 49 | Add the following entry to the file: 50 | ``` 51 | @reboot /usr/bin/forever start /usr/lib/node_modules/sinopia/bin/sinopia 52 | ``` 53 | 54 | The locations may vary depending on your server setup. If you want to know where your files are you can use the 'which' command: 55 | ```bash 56 | $ which forever 57 | $ which sinopia 58 | ``` -------------------------------------------------------------------------------- /bin/sinopia: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | require('../lib/cli') 4 | 5 | -------------------------------------------------------------------------------- /conf/README.md: -------------------------------------------------------------------------------- 1 | This directory is for config examples. 2 | -------------------------------------------------------------------------------- /conf/default.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # This is the default config file. It allows all users to do anything, 3 | # so don't use it on production systems. 4 | # 5 | # Look here for more config file examples: 6 | # https://github.com/rlidwka/sinopia/tree/master/conf 7 | # 8 | 9 | # path to a directory with all packages 10 | storage: ./storage 11 | 12 | auth: 13 | htpasswd: 14 | file: ./htpasswd 15 | # Maximum amount of users allowed to register, defaults to "+inf". 16 | # You can set this to -1 to disable registration. 17 | #max_users: 1000 18 | 19 | # a list of other known repositories we can talk to 20 | uplinks: 21 | npmjs: 22 | url: https://registry.npmjs.org/ 23 | 24 | packages: 25 | '@*/*': 26 | # scoped packages 27 | access: $all 28 | publish: $authenticated 29 | 30 | '*': 31 | # allow all users (including non-authenticated users) to read and 32 | # publish all packages 33 | # 34 | # you can specify usernames/groupnames (depending on your auth plugin) 35 | # and three keywords: "$all", "$anonymous", "$authenticated" 36 | access: $all 37 | 38 | # allow all known users to publish packages 39 | # (anyone can register by default, remember?) 40 | publish: $authenticated 41 | 42 | # if package is not available locally, proxy requests to 'npmjs' registry 43 | proxy: npmjs 44 | 45 | # log settings 46 | logs: 47 | - {type: stdout, format: pretty, level: http} 48 | #- {type: file, path: sinopia.log, level: info} 49 | 50 | -------------------------------------------------------------------------------- /conf/full.yaml: -------------------------------------------------------------------------------- 1 | # path to a directory with all packages 2 | storage: ./storage 3 | 4 | # a list of users 5 | # 6 | # This could be deprecated soon, use auth plugins instead (see htpasswd below). 7 | users: 8 | admin: 9 | # crypto.createHash('sha1').update(pass).digest('hex') 10 | password: a94a8fe5ccb19ba61c4c0873d391e987982fbbd3 11 | 12 | web: 13 | # web interface is disabled by default in 0.x, will be enabled soon in 1.x 14 | # when all its issues will be fixed 15 | # 16 | # set this to `true` if you want to experiment with web ui now; 17 | # this has a lot of issues, e.g. no auth yet, so use at your own risk 18 | #enable: true 19 | 20 | title: Sinopia 21 | # logo: logo.png 22 | # template: custom.hbs 23 | 24 | auth: 25 | htpasswd: 26 | file: ./htpasswd 27 | # Maximum amount of users allowed to register, defaults to "+inf". 28 | # You can set this to -1 to disable registration. 29 | #max_users: 1000 30 | 31 | # a list of other known repositories we can talk to 32 | uplinks: 33 | npmjs: 34 | url: https://registry.npmjs.org/ 35 | 36 | # amount of time to wait for repository to respond 37 | # before giving up and use the local cached copy 38 | #timeout: 30s 39 | 40 | # maximum time in which data is considered up to date 41 | # 42 | # default is 2 minutes, so server won't request the same data from 43 | # uplink if a similar request was made less than 2 minutes ago 44 | #maxage: 2m 45 | 46 | # if two subsequent requests fail, no further requests will be sent to 47 | # this uplink for five minutes 48 | #max_fails: 2 49 | #fail_timeout: 5m 50 | 51 | # timeouts are defined in the same way as nginx, see: 52 | # http://wiki.nginx.org/ConfigNotation 53 | 54 | packages: 55 | # uncomment this for packages with "local-" prefix to be available 56 | # for admin only, it's a recommended way of handling private packages 57 | #'local-*': 58 | # access: admin 59 | # publish: admin 60 | # # you can override storage directory for a group of packages this way: 61 | # storage: 'local_storage' 62 | 63 | '*': 64 | # allow all users to read packages (including non-authenticated users) 65 | # 66 | # you can specify usernames/groupnames (depending on your auth plugin) 67 | # and three keywords: "$all", "$anonymous", "$authenticated" 68 | access: $all 69 | 70 | # allow 'admin' to publish packages 71 | publish: admin 72 | 73 | # if package is not available locally, proxy requests to 'npmjs' registry 74 | proxy: npmjs 75 | 76 | ##################################################################### 77 | # Advanced settings 78 | ##################################################################### 79 | 80 | # if you use nginx with custom path, use this to override links 81 | #url_prefix: https://dev.company.local/sinopia/ 82 | 83 | # You can specify listen address (or simply a port). 84 | # If you add multiple values, sinopia will listen on all of them. 85 | # 86 | # Examples: 87 | # 88 | #listen: 89 | # - localhost:4873 # default value 90 | # - http://localhost:4873 # same thing 91 | # - 0.0.0.0:4873 # listen on all addresses (INADDR_ANY) 92 | # - https://example.org:4873 # if you want to use https 93 | # - [::1]:4873 # ipv6 94 | # - unix:/tmp/sinopia.sock # unix socket 95 | 96 | # Configure HTTPS, it is required if you use "https" protocol above. 97 | #https: 98 | # key: path/to/server.key 99 | # cert: path/to/server.crt 100 | 101 | # type: file | stdout | stderr 102 | # level: trace | debug | info | http (default) | warn | error | fatal 103 | # 104 | # parameters for file: name is filename 105 | # {type: 'file', path: 'sinopia.log', level: 'debug'}, 106 | # 107 | # parameters for stdout and stderr: format: json | pretty 108 | # {type: 'stdout', format: 'pretty', level: 'debug'}, 109 | logs: 110 | - {type: stdout, format: pretty, level: http} 111 | #- {type: file, path: sinopia.log, level: info} 112 | 113 | # you can specify proxy used with all requests in wget-like manner here 114 | # (or set up ENV variables with the same name) 115 | #http_proxy: http://something.local/ 116 | #https_proxy: https://something.local/ 117 | #no_proxy: localhost,127.0.0.1 118 | 119 | # maximum size of uploaded json document 120 | # increase it if you have "request entity too large" errors 121 | #max_body_size: 1mb 122 | 123 | # Workaround for countless npm bugs. Must have for npm <1.14.x, but expect 124 | # it to be turned off in future versions. If `true`, latest tag is ignored, 125 | # and the highest semver is placed instead. 126 | #ignore_latest_tag: false 127 | 128 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | require('es6-shim') 2 | module.exports = require('./lib') 3 | 4 | /**package 5 | { "name": "sinopia", 6 | "version": "0.0.0", 7 | "dependencies": {"js-yaml": "*"}, 8 | "scripts": {"postinstall": "js-yaml package.yaml > package.json ; npm install"} 9 | } 10 | **/ 11 | -------------------------------------------------------------------------------- /lib/GUI/.eslintrc: -------------------------------------------------------------------------------- 1 | 2 | env: 3 | node: true 4 | browser: true 5 | 6 | globals: 7 | jQuery: true 8 | 9 | -------------------------------------------------------------------------------- /lib/GUI/css/fontello.less: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'fontello'; 3 | src: url('../static/fontello.eot?10872183'); 4 | src: url('../static/fontello.eot?10872183#iefix') format('embedded-opentype'), 5 | url('../static/fontello.woff?10872183') format('woff'), 6 | url('../static/fontello.ttf?10872183') format('truetype'), 7 | url('../static/fontello.svg?10872183#fontello') format('svg'); 8 | font-weight: normal; 9 | font-style: normal; 10 | } 11 | /* Chrome hack: SVG is rendered more smooth in Windozze. 100% magic, uncomment if you need it. */ 12 | /* Note, that will break hinting! In other OS-es font will be not as sharp as it could be */ 13 | /* 14 | @media screen and (-webkit-min-device-pixel-ratio:0) { 15 | @font-face { 16 | font-family: 'fontello'; 17 | src: url('../font/fontello.svg?10872183#fontello') format('svg'); 18 | } 19 | } 20 | */ 21 | 22 | [class^="icon-"]:before, [class*=" icon-"]:before { 23 | font-family: "fontello"; 24 | font-style: normal; 25 | font-weight: normal; 26 | speak: none; 27 | 28 | display: inline-block; 29 | text-decoration: inherit; 30 | width: 1em; 31 | margin-right: .2em; 32 | text-align: center; 33 | /* opacity: .8; */ 34 | 35 | /* For safety - reset parent styles, that can break glyph codes*/ 36 | font-variant: normal; 37 | text-transform: none; 38 | 39 | /* fix buttons height, for twitter bootstrap */ 40 | line-height: 1em; 41 | 42 | /* Animation center compensation - margins should be symmetric */ 43 | /* remove if not needed */ 44 | margin-left: .2em; 45 | 46 | /* you can be more comfortable with increased icons size */ 47 | /* font-size: 120%; */ 48 | 49 | /* Uncomment for 3D effect */ 50 | /* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */ 51 | } 52 | 53 | .icon-search:before { content: '\e801'; } /* '' */ 54 | .icon-cancel:before { content: '\e803'; } /* '' */ 55 | .icon-right-open:before { content: '\e802'; } /* '' */ 56 | .icon-angle-right:before { content: '\e800'; } /* '' */ -------------------------------------------------------------------------------- /lib/GUI/css/helpers.less: -------------------------------------------------------------------------------- 1 | // 2 | // copied from https://github.com/bpeacock/helpers.less 3 | // 4 | // author: Brian Peacock 5 | // license: MIT 6 | // 7 | 8 | .backface-visibility(@style) { 9 | -webkit-backface-visibility: @style; 10 | -moz-backface-visibility: @style; 11 | -ms-backface-visibility: @style; 12 | -o-backface-visibility: @style; 13 | backface-visibility: @style; 14 | } 15 | 16 | .perspective(@style) { 17 | -webkit-perspective: @style; 18 | -moz-perspective: @style; 19 | -ms-perspective: @style; 20 | -o-perspective: @style; 21 | perspective: @style; 22 | } 23 | 24 | .border-radius(@radius) { 25 | -webkit-border-radius: @radius; 26 | -moz-border-radius: @radius; 27 | border-radius: @radius; 28 | } 29 | 30 | .border-radius-topleft(@radius) { 31 | -moz-border-radius-topleft: @radius; 32 | border-top-left-radius: @radius; 33 | } 34 | 35 | .border-radius-topright(@radius) { 36 | -moz-border-radius-topright: @radius; 37 | border-top-right-radius: @radius; 38 | } 39 | 40 | .border-radius-bottomleft(@radius) { 41 | -moz-border-radius-bottomleft: @radius; 42 | border-bottom-left-radius: @radius; 43 | } 44 | 45 | .border-radius-bottomright(@radius) { 46 | -moz-border-radius-bottomright: @radius; 47 | border-bottom-right-radius: @radius; 48 | } 49 | 50 | .circle(@diameter) { 51 | width: @diameter; 52 | height: @diameter; 53 | .border-radius(@diameter/2); 54 | } 55 | 56 | .no-select() { 57 | -moz-user-select: none; 58 | -ms-user-select: none; 59 | -khtml-user-select: none; 60 | -webkit-user-select: none; 61 | -o-user-select: none; 62 | user-select: none; 63 | } 64 | 65 | .do-select() { 66 | -moz-user-select: text; 67 | -ms-user-select: text; 68 | -khtml-user-select: text; 69 | -webkit-user-select: text; 70 | -o-user-select: text; 71 | user-select: text; 72 | } 73 | 74 | .border-box() { 75 | -moz-box-sizing: border-box; 76 | -webkit-box-sizing: border-box; 77 | box-sizing: border-box; 78 | } 79 | 80 | .box-shadow(@value1, @value2:X, ...) { 81 | @value: ~`"@{arguments}".replace(/[\[\]]|\,\sX/g, '')`; 82 | -moz-box-shadow: @value; 83 | -webkit-box-shadow: @value; 84 | box-shadow: @value; 85 | } 86 | 87 | .transition(@value1, @value2:X, ...) { 88 | @value: ~`"@{arguments}".replace(/[\[\]]|\,\sX/g, '')`; 89 | 90 | -webkit-transition: @value; 91 | -moz-transition: @value; 92 | -ms-transition: @value; 93 | -o-transition: @value; 94 | transition: @value; 95 | } 96 | 97 | .transformTransition(@value1, @value2:X, ...) { 98 | @value: ~`"@{arguments}".replace(/[\[\]]|\,\sX/g, '')`; 99 | 100 | -webkit-transition: -webkit-transform @value; 101 | -moz-transition: -moz-transform @value; 102 | -ms-transition: -ms-transform @value; 103 | -o-transition: -o-transform @value; 104 | transition: transform @value; 105 | } 106 | 107 | .animation(@value1, @value2:X, ...) { 108 | @value: ~`"@{arguments}".replace(/[\[\]]|\,\sX/g, '')`; 109 | 110 | -webkit-animation: @value; 111 | -moz-animation: @value; 112 | -o-animation: @value; 113 | animation: @value; 114 | } 115 | 116 | .transform(@value1, @value2:X, ...) { 117 | @value: ~`"@{arguments}".replace(/[\[\]]|\,\sX/g, '')`; 118 | 119 | -webkit-transform: @value; 120 | -moz-transform: @value; 121 | -o-transform: @value; 122 | -ms-transform: @value; 123 | transform: @value; 124 | } 125 | 126 | .rotate(@deg) { 127 | .transform(rotate(@deg)); 128 | } 129 | 130 | .scale(@ratio) { 131 | .transform(scale(@ratio, @ratio)); 132 | } 133 | 134 | .translate(@x, @y) { 135 | .transform(translate(@x, @y)); 136 | } 137 | -------------------------------------------------------------------------------- /lib/GUI/css/highlight.js.less: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Original style from softwaremaniacs.org (c) Ivan Sagalaev 4 | 5 | */ 6 | 7 | .hljs { 8 | display: block; padding: 0.5em; 9 | background: #F0F0F0; 10 | } 11 | 12 | .hljs, 13 | .hljs-subst, 14 | .hljs-tag .hljs-title, 15 | .lisp .hljs-title, 16 | .clojure .hljs-built_in, 17 | .nginx .hljs-title { 18 | color: black; 19 | } 20 | 21 | .hljs-string, 22 | .hljs-title, 23 | .hljs-constant, 24 | .hljs-parent, 25 | .hljs-tag .hljs-value, 26 | .hljs-rules .hljs-value, 27 | .hljs-rules .hljs-value .hljs-number, 28 | .hljs-preprocessor, 29 | .hljs-pragma, 30 | .haml .hljs-symbol, 31 | .ruby .hljs-symbol, 32 | .ruby .hljs-symbol .hljs-string, 33 | .hljs-aggregate, 34 | .hljs-template_tag, 35 | .django .hljs-variable, 36 | .smalltalk .hljs-class, 37 | .hljs-addition, 38 | .hljs-flow, 39 | .hljs-stream, 40 | .bash .hljs-variable, 41 | .apache .hljs-tag, 42 | .apache .hljs-cbracket, 43 | .tex .hljs-command, 44 | .tex .hljs-special, 45 | .erlang_repl .hljs-function_or_atom, 46 | .asciidoc .hljs-header, 47 | .markdown .hljs-header, 48 | .coffeescript .hljs-attribute { 49 | color: #800; 50 | } 51 | 52 | .smartquote, 53 | .hljs-comment, 54 | .hljs-annotation, 55 | .hljs-template_comment, 56 | .diff .hljs-header, 57 | .hljs-chunk, 58 | .asciidoc .hljs-blockquote, 59 | .markdown .hljs-blockquote { 60 | color: #888; 61 | } 62 | 63 | .hljs-number, 64 | .hljs-date, 65 | .hljs-regexp, 66 | .hljs-literal, 67 | .hljs-hexcolor, 68 | .smalltalk .hljs-symbol, 69 | .smalltalk .hljs-char, 70 | .go .hljs-constant, 71 | .hljs-change, 72 | .lasso .hljs-variable, 73 | .makefile .hljs-variable, 74 | .asciidoc .hljs-bullet, 75 | .markdown .hljs-bullet, 76 | .asciidoc .hljs-link_url, 77 | .markdown .hljs-link_url { 78 | color: #080; 79 | } 80 | 81 | .hljs-label, 82 | .hljs-javadoc, 83 | .ruby .hljs-string, 84 | .hljs-decorator, 85 | .hljs-filter .hljs-argument, 86 | .hljs-localvars, 87 | .hljs-array, 88 | .hljs-attr_selector, 89 | .hljs-important, 90 | .hljs-pseudo, 91 | .hljs-pi, 92 | .haml .hljs-bullet, 93 | .hljs-doctype, 94 | .hljs-deletion, 95 | .hljs-envvar, 96 | .hljs-shebang, 97 | .apache .hljs-sqbracket, 98 | .nginx .hljs-built_in, 99 | .tex .hljs-formula, 100 | .erlang_repl .hljs-reserved, 101 | .hljs-prompt, 102 | .asciidoc .hljs-link_label, 103 | .markdown .hljs-link_label, 104 | .vhdl .hljs-attribute, 105 | .clojure .hljs-attribute, 106 | .asciidoc .hljs-attribute, 107 | .lasso .hljs-attribute, 108 | .coffeescript .hljs-property, 109 | .hljs-phony { 110 | color: #88F 111 | } 112 | 113 | .hljs-keyword, 114 | .hljs-id, 115 | .hljs-title, 116 | .hljs-built_in, 117 | .hljs-aggregate, 118 | .css .hljs-tag, 119 | .hljs-javadoctag, 120 | .hljs-phpdoc, 121 | .hljs-yardoctag, 122 | .smalltalk .hljs-class, 123 | .hljs-winutils, 124 | .bash .hljs-variable, 125 | .apache .hljs-tag, 126 | .go .hljs-typename, 127 | .tex .hljs-command, 128 | .asciidoc .hljs-strong, 129 | .markdown .hljs-strong, 130 | .hljs-request, 131 | .hljs-status { 132 | font-weight: bold; 133 | } 134 | 135 | .asciidoc .hljs-emphasis, 136 | .markdown .hljs-emphasis { 137 | font-style: italic; 138 | } 139 | 140 | .nginx .hljs-built_in { 141 | font-weight: normal; 142 | } 143 | 144 | .coffeescript .javascript, 145 | .javascript .xml, 146 | .lasso .markup, 147 | .tex .hljs-formula, 148 | .xml .javascript, 149 | .xml .vbscript, 150 | .xml .css, 151 | .xml .hljs-cdata { 152 | opacity: 0.5; 153 | } 154 | -------------------------------------------------------------------------------- /lib/GUI/css/main.less: -------------------------------------------------------------------------------- 1 | @import "helpers.less"; 2 | @import (less) "bootstrap.css"; 3 | @import "markdown.less"; 4 | @import "highlight.js.less"; 5 | @import "fontello.less"; 6 | @import "styles.less"; 7 | @import "responsive.less"; 8 | -------------------------------------------------------------------------------- /lib/GUI/css/responsive.less: -------------------------------------------------------------------------------- 1 | @media (max-width: 992px) { 2 | .body { 3 | .main-header { 4 | .npm-logo { 5 | width: 100px; 6 | float: left; 7 | } 8 | 9 | .packages-header { 10 | border-bottom: none; 11 | } 12 | } 13 | } 14 | } 15 | 16 | @media (max-width: 768px) { 17 | .body { 18 | .content { 19 | padding-top: @mainHeaderHeight + @packagesHeaderHeight + @smRegistryInfoHeight + 10; 20 | 21 | .entry { 22 | .title { 23 | margin-bottom: 0; 24 | } 25 | 26 | .author { 27 | float: none !important; 28 | clear: both; 29 | padding: 0 0 5px 18px; 30 | } 31 | } 32 | } 33 | 34 | .no-results { 35 | margin: 10px 0 0; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lib/GUI/css/styles.less: -------------------------------------------------------------------------------- 1 | //vars 2 | @npmRed: #cc3d33; 3 | @white: #fff; 4 | @entryBg: #F3F3F3; 5 | @mainHeaderHeight: 50px; 6 | @packagesHeaderHeight: 60px; 7 | @headerBorderWidth: 2px; 8 | @smRegistryInfoHeight: 25px; 9 | 10 | /*** Main Styles ***/ 11 | .body { 12 | position: absolute; 13 | top: 0; 14 | right: 0; 15 | bottom: 0; 16 | left:0; 17 | margin: 0; 18 | padding: 0; 19 | 20 | .main-header { 21 | background: @white; 22 | 23 | .navbar { 24 | margin-bottom: 0; 25 | } 26 | 27 | .npm-logo { 28 | width: 79px; 29 | height: @mainHeaderHeight; 30 | // https://example.org/sinopia/-/static/../../-/logo 31 | background-image: url( ../../-/logo ); 32 | background-repeat: no-repeat; 33 | background-position: center center; 34 | 35 | >a { 36 | display: block; 37 | width: 100%; 38 | height: 100%; 39 | } 40 | } 41 | 42 | .setup { 43 | line-height: 1.3em; 44 | padding-top: 5px; 45 | } 46 | 47 | .packages-header { 48 | border-bottom: @headerBorderWidth solid #e6e6e6; 49 | 50 | .search-container { 51 | top: 9px; 52 | 53 | .search-icon { 54 | background: #e6e6e6; 55 | } 56 | } 57 | } 58 | 59 | .sm-registry-info { 60 | height: @smRegistryInfoHeight; 61 | line-height: 1.7em; 62 | } 63 | } 64 | 65 | .content { 66 | padding-top: 10px; 67 | 68 | .entry { 69 | .transition(height .3s); 70 | padding: 9px 10px; 71 | overflow: hidden; 72 | border-bottom: 1px solid #E7E7E7; 73 | 74 | &:last-child { 75 | border-bottom: none; 76 | } 77 | 78 | &:nth-child( even ) { 79 | background: @entryBg; 80 | } 81 | 82 | .title { 83 | margin: 0 0 5px 10px; 84 | } 85 | 86 | .description { 87 | margin: 0 0 0 18px; 88 | font-size: 13px; 89 | } 90 | 91 | .name:hover { 92 | text-decoration: none; 93 | } 94 | 95 | .name:before { 96 | margin: 0; 97 | margin-left: -10px; 98 | .transformTransition(.2s); 99 | } 100 | 101 | &.open .name:before { 102 | .rotate(90deg); 103 | } 104 | 105 | .version { 106 | color: #666; 107 | } 108 | 109 | .author { 110 | color: #666; 111 | } 112 | 113 | .readme { 114 | font-size: 14px; 115 | margin-top: 10px; 116 | background: @white; 117 | padding: 10px 12px; 118 | .border-radius(3px); 119 | border: 1px solid darken( @entryBg, 10% ); 120 | } 121 | } 122 | } 123 | } 124 | 125 | .pkg-search-container { 126 | display: none; 127 | } 128 | 129 | .packages-container { 130 | .search-ajax { 131 | display: block; 132 | margin: 50px auto; 133 | } 134 | } 135 | 136 | .no-results { 137 | text-align: center; 138 | margin: 50px 0; 139 | color: #888; 140 | 141 | big { 142 | font-size: 38px; 143 | margin-bottom: 8px; 144 | } 145 | 146 | code { 147 | font-size: 1.2em; 148 | } 149 | } 150 | 151 | .red { 152 | color: @npmRed; 153 | } 154 | 155 | .light-red { 156 | color: lighten( @npmRed, 10% ); 157 | } 158 | 159 | .white { 160 | color: @white !important; 161 | } 162 | 163 | .red-bg { 164 | background: @npmRed; 165 | } 166 | 167 | .light-red-bg { 168 | background: lighten( @npmRed, 10% ); 169 | } 170 | 171 | .no-bg { 172 | background: none !important; 173 | } 174 | 175 | .no-border { 176 | border: none !important; 177 | } 178 | .no-rnd-cnr { 179 | .border-radius( 0 ); 180 | } 181 | 182 | .center { 183 | text-align: center; 184 | } 185 | 186 | .login-btn { 187 | margin-left: 10px; 188 | margin-top: 5px; 189 | } 190 | 191 | .pad-right-10 { 192 | padding-right: 10px; 193 | } 194 | 195 | .inline-block { 196 | display: inline-block; 197 | } 198 | -------------------------------------------------------------------------------- /lib/GUI/entry.hbs: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

5 | {{ name }} 6 | v{{ version }} 7 |

8 |
9 |
10 |
11 | By: {{ _npmUser.name }} 12 |
13 |
14 |
15 |
16 |
17 |

{{ description }}

18 |
19 |
20 |
21 | -------------------------------------------------------------------------------- /lib/GUI/index.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{ name }} 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 58 | 61 |
62 |
63 | 66 |
67 |
68 |
69 | 70 | 71 | 72 | 73 |
74 |
75 |
76 |
77 |
78 |
79 | 80 |
81 | {{#each packages}} 82 | {{> entry}} 83 | {{/each}} 84 | 85 | {{#unless packages.length}} 86 |
87 | No Packages
88 | Use npm publish 89 |
90 | {{/unless}} 91 |
92 | 93 |
94 | 95 | 121 | 122 |
123 | 124 | 125 | 126 | 127 | 128 | -------------------------------------------------------------------------------- /lib/GUI/js/bootstrap-modal.js: -------------------------------------------------------------------------------- 1 | /* ======================================================================== 2 | * Bootstrap: modal.js v3.3.0 3 | * http://getbootstrap.com/javascript/#modals 4 | * ======================================================================== 5 | * Copyright 2011-2014 Twitter, Inc. 6 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 7 | * ======================================================================== */ 8 | 9 | 10 | +function ($) { 11 | 'use strict'; 12 | 13 | // MODAL CLASS DEFINITION 14 | // ====================== 15 | 16 | var Modal = function (element, options) { 17 | this.options = options 18 | this.$body = $(document.body) 19 | this.$element = $(element) 20 | this.$backdrop = 21 | this.isShown = null 22 | this.scrollbarWidth = 0 23 | 24 | if (this.options.remote) { 25 | this.$element 26 | .find('.modal-content') 27 | .load(this.options.remote, $.proxy(function () { 28 | this.$element.trigger('loaded.bs.modal') 29 | }, this)) 30 | } 31 | } 32 | 33 | Modal.VERSION = '3.3.0' 34 | 35 | Modal.TRANSITION_DURATION = 300 36 | Modal.BACKDROP_TRANSITION_DURATION = 150 37 | 38 | Modal.DEFAULTS = { 39 | backdrop: true, 40 | keyboard: true, 41 | show: true 42 | } 43 | 44 | Modal.prototype.toggle = function (_relatedTarget) { 45 | return this.isShown ? this.hide() : this.show(_relatedTarget) 46 | } 47 | 48 | Modal.prototype.show = function (_relatedTarget) { 49 | var that = this 50 | var e = $.Event('show.bs.modal', { relatedTarget: _relatedTarget }) 51 | 52 | this.$element.trigger(e) 53 | 54 | if (this.isShown || e.isDefaultPrevented()) return 55 | 56 | this.isShown = true 57 | 58 | this.checkScrollbar() 59 | this.$body.addClass('modal-open') 60 | 61 | this.setScrollbar() 62 | this.escape() 63 | 64 | this.$element.on('click.dismiss.bs.modal', '[data-dismiss="modal"]', $.proxy(this.hide, this)) 65 | 66 | this.backdrop(function () { 67 | var transition = $.support.transition && that.$element.hasClass('fade') 68 | 69 | if (!that.$element.parent().length) { 70 | that.$element.appendTo(that.$body) // don't move modals dom position 71 | } 72 | 73 | that.$element 74 | .show() 75 | .scrollTop(0) 76 | 77 | if (transition) { 78 | that.$element[0].offsetWidth // force reflow 79 | } 80 | 81 | that.$element 82 | .addClass('in') 83 | .attr('aria-hidden', false) 84 | 85 | that.enforceFocus() 86 | 87 | var e = $.Event('shown.bs.modal', { relatedTarget: _relatedTarget }) 88 | 89 | transition ? 90 | that.$element.find('.modal-dialog') // wait for modal to slide in 91 | .one('bsTransitionEnd', function () { 92 | that.$element.trigger('focus').trigger(e) 93 | }) 94 | .emulateTransitionEnd(Modal.TRANSITION_DURATION) : 95 | that.$element.trigger('focus').trigger(e) 96 | }) 97 | } 98 | 99 | Modal.prototype.hide = function (e) { 100 | if (e) e.preventDefault() 101 | 102 | e = $.Event('hide.bs.modal') 103 | 104 | this.$element.trigger(e) 105 | 106 | if (!this.isShown || e.isDefaultPrevented()) return 107 | 108 | this.isShown = false 109 | 110 | this.escape() 111 | 112 | $(document).off('focusin.bs.modal') 113 | 114 | this.$element 115 | .removeClass('in') 116 | .attr('aria-hidden', true) 117 | .off('click.dismiss.bs.modal') 118 | 119 | $.support.transition && this.$element.hasClass('fade') ? 120 | this.$element 121 | .one('bsTransitionEnd', $.proxy(this.hideModal, this)) 122 | .emulateTransitionEnd(Modal.TRANSITION_DURATION) : 123 | this.hideModal() 124 | } 125 | 126 | Modal.prototype.enforceFocus = function () { 127 | $(document) 128 | .off('focusin.bs.modal') // guard against infinite focus loop 129 | .on('focusin.bs.modal', $.proxy(function (e) { 130 | if (this.$element[0] !== e.target && !this.$element.has(e.target).length) { 131 | this.$element.trigger('focus') 132 | } 133 | }, this)) 134 | } 135 | 136 | Modal.prototype.escape = function () { 137 | if (this.isShown && this.options.keyboard) { 138 | this.$element.on('keydown.dismiss.bs.modal', $.proxy(function (e) { 139 | e.which == 27 && this.hide() 140 | }, this)) 141 | } else if (!this.isShown) { 142 | this.$element.off('keydown.dismiss.bs.modal') 143 | } 144 | } 145 | 146 | Modal.prototype.hideModal = function () { 147 | var that = this 148 | this.$element.hide() 149 | this.backdrop(function () { 150 | that.$body.removeClass('modal-open') 151 | that.resetScrollbar() 152 | that.$element.trigger('hidden.bs.modal') 153 | }) 154 | } 155 | 156 | Modal.prototype.removeBackdrop = function () { 157 | this.$backdrop && this.$backdrop.remove() 158 | this.$backdrop = null 159 | } 160 | 161 | Modal.prototype.backdrop = function (callback) { 162 | var that = this 163 | var animate = this.$element.hasClass('fade') ? 'fade' : '' 164 | 165 | if (this.isShown && this.options.backdrop) { 166 | var doAnimate = $.support.transition && animate 167 | 168 | this.$backdrop = $('