Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 7ac08bb

Browse files
first commit
0 parents commit 7ac08bb

File tree

11 files changed

+237
-0
lines changed

11 files changed

+237
-0
lines changed

‎.gitignore

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
logs
2+
*.log
3+
npm-debug.log*
4+
pids
5+
*.pid
6+
*.seed
7+
*.pid.lock
8+
coverage
9+
.nyc_output
10+
node_modules/
11+
.env
12+
\.idea/
13+
14+
#*.json
15+
16+
ormconfig\.json
17+
18+
src/cache/
19+
20+
uploads/
21+
22+
dist/
23+
24+
*.pem
25+
acme/
26+
cache/

‎.npmrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
package-lock=false

‎Dockerfile

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
FROM node:10
2+
3+
LABEL version="0.0.1"
4+
5+
WORKDIR /usr/src/app
6+
7+
COPY . .
8+
9+
EXPOSE 443
10+
EXPOSE 80
11+
12+
VOLUME /usr/src/app/cache
13+
VOLUME /usr/src/app/acme
14+
15+
CMD [ "npm", "run", "start" ]

‎README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# self-hosted-unpkg

‎nodemon.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"watch": [
3+
"src/**/*.ts"
4+
],
5+
"ext": "ts",
6+
"ignore": [
7+
"./test/*.ts",
8+
"./node_modules/**/node_modules"
9+
],
10+
"exec": "node -r ts-node/register src/index.ts",
11+
"env": {
12+
"NODE_ENV": "development"
13+
}
14+
}

‎package.json

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
{
2+
"name": "@edgeworkscreative/self-hosted-unpkg",
3+
"version": "0.0.1",
4+
"private": true,
5+
"description": "",
6+
"main": "dist/index.js",
7+
"author": "Interactive Training",
8+
"license": "MIT",
9+
"scripts": {
10+
"start": "node dist/index.js",
11+
"dev": "nodemon --delay 1500ms --signal SIGTERM",
12+
"build": "tsc"
13+
},
14+
"dependencies": {
15+
"@interactivetraining/le-challenge-cloudflare": "1.3.1",
16+
"compression": "^1.7.3",
17+
"cors": "^2.8.5",
18+
"dotenv": "^5.0.1",
19+
"download-npm-package": "^3.1.12",
20+
"download-package-tarball": "^1.0.7",
21+
"express": "^4.16.4",
22+
"get-package-json-from-registry": "^2.2.1",
23+
"got": "^9.6.0",
24+
"greenlock-express": "^2.6.8",
25+
"helmet": "^3.16.0",
26+
"le-challenge-cloudflare": "^1.0.1",
27+
"mime": "^2.4.0",
28+
"redirect-https": "^1.3.0",
29+
"zlib": "^1.0.5"
30+
},
31+
"devDependencies": {
32+
"@types/compression": "0.0.36",
33+
"@types/cors": "^2.8.4",
34+
"@types/dotenv": "^6.1.0",
35+
"@types/express": "^4.16.1",
36+
"@types/helmet": "0.0.43",
37+
"@types/node": "^11.11.1",
38+
"@types/shelljs": "^0.8.3",
39+
"nodemon": "^1.18.10",
40+
"shelljs": "^0.8.3",
41+
"ts-node": "^8.0.2",
42+
"typescript": "^3.3.3333"
43+
}
44+
}

‎sample.env

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
DOMAIN=my-self-hosted-unpkg-domain.com
2+
NPM_REGISTRY=https://private.npm.registry
3+
NPM_USER=
4+
NPM_PASSWORD=
5+
NPM_TOKEN=
6+
CLOUDFLARE_EMAIL=
7+
CLOUDFLARE_API_KEY=
8+
LETS_ENCRYPT_EMAIL=
9+
LETS_ENCRYPT_AGREE_TO_TOS=true

‎src/helpers.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import * as downloadTarball from 'download-package-tarball';
2+
import * as got from 'got';
3+
import {IPackageParams} from './interfaces';
4+
5+
export async function downloadPackage(pkg: IPackageParams, directory: string) {
6+
let gotOpts: any = {};
7+
8+
if (process.env.NPM_TOKEN.trim() && process.env.NPM_TOKEN.trim().length > 0) {
9+
gotOpts.headers = {authorization: `Bearer ${process.env.NPM_TOKEN.trim()}`};
10+
} else {
11+
gotOpts.auth = `${process.env.NPM_USER}:${process.env.NPM_PASSWORD}`;
12+
}
13+
14+
if (!pkg.version.includes('.')) {
15+
const {body} = await got(`${process.env.NPM_REGISTRY}/${(pkg.scope) ? `${pkg.scope}/` : ''}${pkg.package}`, {
16+
...gotOpts,
17+
json: true
18+
});
19+
pkg.version = body['dist-tags'][pkg.version];
20+
}
21+
22+
await downloadTarball({
23+
url: `${process.env.NPM_REGISTRY}/${(pkg.scope) ? `${pkg.scope}/` : ''}${pkg.package}/-/${pkg.package}-${pkg.version}.tgz`,
24+
gotOpts: gotOpts,
25+
dir: directory
26+
});
27+
}

‎src/index.ts

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import * as express from 'express';
2+
import * as fs from 'fs';
3+
import * as cors from 'cors';
4+
import * as compression from 'compression';
5+
import * as helmet from 'helmet';
6+
import {CloudflareChallenge} from '@interactivetraining/le-challenge-cloudflare';
7+
import {IPackageParams} from './interfaces';
8+
import {downloadPackage} from './helpers';
9+
10+
require('dotenv').config();
11+
12+
if (!fs.existsSync('cache')) fs.mkdirSync('cache');
13+
if (!fs.existsSync('acme')) fs.mkdirSync('acme');
14+
15+
const app = express();
16+
17+
app.use(helmet());
18+
app.use(cors());
19+
app.use(compression());
20+
21+
app.get(['/:scope?/:package@:version/*', '/:scope?/:package/*'], async (req, res) => {
22+
try {
23+
let params: IPackageParams = req.params;
24+
25+
// correct the params.package value when there isn't a scope or version provided
26+
if (!params.version && params.scope && !req.url.split('/')[1].includes('@')) {
27+
params.package = params.scope;
28+
params.scope = undefined;
29+
}
30+
31+
if (!params.version) params.version = 'latest';
32+
33+
const packagePath = `cache/${params.version}/${(params.scope) ? `${params.scope}/` : ``}${params.package}`;
34+
const filePath = `${packagePath}/${params['0']}`;
35+
36+
if (!fs.existsSync(packagePath) || params.version === 'latest') {
37+
await downloadPackage({
38+
scope: params.scope,
39+
package: params.package,
40+
version: params.version
41+
}, `cache/${params.version}`);
42+
}
43+
44+
console.log(`${(params.scope) ? `${params.scope}/` : ``}${params.package}@${params.version}: ${params['0']}`);
45+
46+
res.setHeader('Cache-Control', (!params.version.includes('.')) ? 'no-cache' : 'public, max-age=31536000');
47+
res.sendFile(filePath, {root: `./`});
48+
} catch (e) {
49+
console.log(e);
50+
const status = (e.hasOwnProperty('statusCode')) ? e.statusCode : 500;
51+
const message = (e.hasOwnProperty('statusMessage')) ? e.statusMessage : e.message;
52+
res.status(status).send(message);
53+
}
54+
});
55+
56+
require('greenlock-express').create({
57+
version: 'draft-11',
58+
server: 'https://acme-v02.api.letsencrypt.org/directory',
59+
email: process.env.LETS_ENCRYPT_EMAIL,
60+
agreeTos: (process.env.LETS_ENCRYPT_AGREE_TO_TOS.trim() === 'true'),
61+
approveDomains: [
62+
process.env.DOMAIN
63+
],
64+
configDir: 'acme/',
65+
app: app,
66+
challengeType: 'dns-01',
67+
challenge: new CloudflareChallenge({
68+
cloudflare: {
69+
email: process.env.CLOUDFLARE_EMAIL,
70+
key: process.env.CLOUDFLARE_API_KEY
71+
},
72+
acmePrefix: '_acme-challenge',
73+
verifyPropagation: {waitFor: 5000, retries: 50},
74+
useDNSOverHTTPS: false
75+
})
76+
}).listen(80, 443, () => console.log(`Listening...`));

‎src/interfaces.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export interface IPackageParams {
2+
scope?: string;
3+
package: string;
4+
version?: string;
5+
0?: string;
6+
}

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /