Merge pull request #284 from SAP-samples/registry
NPM registry: more robust, work on Windows
This commit is contained in:
@@ -1,17 +1,22 @@
|
||||
const { exec } = require ('child_process')
|
||||
const isWin = process.platform === 'win32'
|
||||
const express = require ('express')
|
||||
const fs = require ('fs')
|
||||
const app = express()
|
||||
|
||||
const { PORT=4444 } = process.env
|
||||
const [,,port=PORT] = process.argv
|
||||
const [,,port=PORT,scope='@capire'] = process.argv
|
||||
const cwd = __dirname
|
||||
|
||||
// clean up on start (exit handler might not complete on Windows)
|
||||
exec(isWin ? 'del *.tgz' : 'rm *.tgz', {cwd})
|
||||
|
||||
|
||||
app.use('/-/:tarball', (req,res,next) => {
|
||||
console.debug ('GET', req.params)
|
||||
try {
|
||||
const { tarball } = req.params
|
||||
const [, pkg ] = /^capire-(\w+)/.exec(tarball)
|
||||
const [, pkg ] = /^\w+-(\w+)/.exec(tarball)
|
||||
fs.lstat(tarball,(err => {
|
||||
if (err) exec(`npm pack ../${pkg}`,{cwd},next)
|
||||
else next()
|
||||
@@ -25,12 +30,14 @@ app.use('/-/:tarball', (req,res,next) => {
|
||||
app.use('/-', express.static(__dirname))
|
||||
|
||||
app.get('/*', (req,res)=>{
|
||||
const urlRegex = /^\/(@\w+)\/(\w+)/
|
||||
const url = decodeURIComponent(req.url)
|
||||
console.debug ('GET',url)
|
||||
try {
|
||||
const [, capire, pkg ] = /^\/(@capire)\/(\w+)/.exec(url)
|
||||
const package = require (`${capire}/${pkg}/package.json`)
|
||||
const tarball = `capire-${pkg}-${package.version}.tgz`
|
||||
if (!urlRegex.test(url)) return res.sendStatus(404)
|
||||
const [, scpe, pkg ] = urlRegex.exec(url)
|
||||
const package = require (`${scpe}/${pkg}/package.json`)
|
||||
const tarball = `${scpe.slice(1)}-${pkg}-${package.version}.tgz`
|
||||
// https://github.com/npm/registry/blob/master/docs/responses/package-metadata.md
|
||||
res.json({
|
||||
"name": package.name,
|
||||
@@ -42,29 +49,30 @@ app.get('/*', (req,res)=>{
|
||||
"name": package.name,
|
||||
"version": package.version,
|
||||
"dist": {
|
||||
"tarball": `http://localhost:${port}/-/${tarball}`
|
||||
"tarball": `/-/${tarball}`
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
res.sendStatus(404)
|
||||
if (e.code === 'MODULE_NOT_FOUND') return res.sendStatus(404)
|
||||
console.error(e); throw e
|
||||
}
|
||||
})
|
||||
|
||||
app.listen(port, ()=>{
|
||||
console.log (`npm set @capire:registry=http://localhost:${port}`)
|
||||
console.log (`@capire registry listening on http://localhost:${port}`)
|
||||
exec(`npm set @capire:registry=http://localhost:${port}`)
|
||||
const server = app.listen(port, ()=>{
|
||||
const url = `http://localhost:${server.address().port}`
|
||||
console.log (`npm set ${scope}:registry=${url}`)
|
||||
exec(`npm set ${scope}:registry=${url}`)
|
||||
console.log (`${scope} registry listening on ${url}`)
|
||||
})
|
||||
|
||||
|
||||
const _exit = ()=>{
|
||||
console.log ('\nnpm conf rm @capire:registry')
|
||||
exec('npm conf rm @capire:registry')
|
||||
exec('rm *.tgz')
|
||||
process.exit()
|
||||
server.close()
|
||||
exec(`npm conf rm "${scope}:registry"`, ()=> { process.exit() })
|
||||
}
|
||||
|
||||
process.on ('SIGTERM',_exit)
|
||||
process.on ('SIGHUP',_exit)
|
||||
process.on ('SIGINT',_exit)
|
||||
|
||||
54
test/registry.test.js
Normal file
54
test/registry.test.js
Normal file
@@ -0,0 +1,54 @@
|
||||
|
||||
const { fork } = require('child_process')
|
||||
const { resolve } = require('path')
|
||||
const Axios = require('axios')
|
||||
const verbose = process.env.CDS_TEST_VERBOSE
|
||||
// ||true
|
||||
|
||||
describe('Local NPM registry', () => {
|
||||
let registry
|
||||
let axios
|
||||
const cwd = resolve(__dirname, '..')
|
||||
|
||||
beforeAll(async ()=> {
|
||||
const env = Object.assign(process.env, {PORT:'0'})
|
||||
const res = await exec (resolve(cwd, '.registry/server.js'), {cwd, stdio: 'pipe', env})
|
||||
registry = res.cp
|
||||
axios = Axios.default.create ({ baseURL: res.url, validateStatus: (status)=>status<500 })
|
||||
})
|
||||
|
||||
afterAll(() => { registry.kill() })
|
||||
|
||||
for (const mod of ['bookshop','fiori','orders','reviews']) {
|
||||
it(`should serve ${mod}`, async () => {
|
||||
const resp = await axios.get(`/@capire/${mod}`)
|
||||
expect(resp.data).toMatchObject({name: `@capire/${mod}`, versions:{}})
|
||||
const versions = Object.values(resp.data.versions)
|
||||
await axios.get(versions[0].dist.tarball)
|
||||
})
|
||||
}
|
||||
it(`should return 404 for unknown packages`, async () => {
|
||||
let resp = await axios.get(`/@capire/foo`)
|
||||
expect(resp.status).toEqual(404)
|
||||
resp = await axios.get(`/foo`)
|
||||
expect(resp.status).toEqual(404)
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
function exec (script, opts) {
|
||||
return new Promise((resolve, reject)=> {
|
||||
const cp = fork (script, [], opts)
|
||||
.on('error', err => reject(new Error(err)))
|
||||
cp.stdout.on('data', chunk => {
|
||||
if (verbose) console.log(chunk.toString())
|
||||
if (chunk.toString().match(/listening.*(http:.*:\d+)/i)) {
|
||||
resolve({cp, url:RegExp.$1})
|
||||
}
|
||||
})
|
||||
cp.stderr.on('data', chunk => {
|
||||
if (verbose) console.error(chunk.toString())
|
||||
})
|
||||
})
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user