diff --git a/.registry/server.js b/.registry/server.js index 2696d948..90fe17d9 100644 --- a/.registry/server.js +++ b/.registry/server.js @@ -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) diff --git a/test/registry.test.js b/test/registry.test.js new file mode 100644 index 00000000..beb98f87 --- /dev/null +++ b/test/registry.test.js @@ -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()) + }) + }) + +}