-
Notifications
You must be signed in to change notification settings - Fork 80
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: validate pkg@version spec (#500)
> follow #495 after supporting spec, adjust the parameter validation rules 1. ๐ Add `Spec` validation rule, validating the spec by npa 2. ๐ ๏ธ Upgrade versionOrTag to versionSpec to support semver expressions, such as `^2.x || > 3.x` --------- > follow #495 ๆฏๆ spec ๅ๏ผ่ฐๆดๅๆฐๆ ก้ช่งๅ 1. ๐ ๆฐๅข `Sepc` ๆ ก้ช่งๅ๏ผไฝฟ็จ npa ๆผๆฅๅ ๅ่ฟ่ก้ช่ฏ 2. ๐ ๏ธ versionOrTag ๅ็บงไธบ versionSpec ๆฏๆ semver ่กจ่พพๅผ๏ผไพๅฆ `^2.x || > 3.x`
- Loading branch information
Showing
7 changed files
with
94 additions
and
37 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,6 +19,7 @@ import { PackageManagerService } from '../../core/service/PackageManagerService' | |
import { PackageVersionFile } from '../../core/entity/PackageVersionFile'; | ||
import { PackageVersion } from '../../core/entity/PackageVersion'; | ||
import { DistRepository } from '../../repository/DistRepository'; | ||
import { Spec } from '../typebox'; | ||
|
||
type FileItem = { | ||
path: string, | ||
|
@@ -65,84 +66,87 @@ export class PackageVersionFileController extends AbstractController { | |
} | ||
|
||
@HTTPMethod({ | ||
// PUT /:fullname/:versionOrTag/files | ||
path: `/:fullname(${FULLNAME_REG_STRING})/:versionOrTag/files`, | ||
// PUT /:fullname/:versionSpec/files | ||
path: `/:fullname(${FULLNAME_REG_STRING})/:versionSpec/files`, | ||
method: HTTPMethodEnum.PUT, | ||
}) | ||
@Middleware(AdminAccess) | ||
async sync(@HTTPParam() fullname: string, @HTTPParam() versionOrTag: string) { | ||
async sync(@Context() ctx: EggContext, @HTTPParam() fullname: string, @HTTPParam() versionSpec: string) { | ||
ctx.tValidate(Spec, `${fullname}@${versionSpec}`); | ||
this.#requireUnpkgEnable(); | ||
const [ scope, name ] = getScopeAndName(fullname); | ||
const { packageVersion } = await this.packageManagerService.showPackageVersionByVersionOrTag( | ||
scope, name, versionOrTag); | ||
scope, name, versionSpec); | ||
if (!packageVersion) { | ||
throw new NotFoundError(`${fullname}@${versionOrTag} not found`); | ||
throw new NotFoundError(`${fullname}@${versionSpec} not found`); | ||
} | ||
const files = await this.packageVersionFileService.syncPackageVersionFiles(packageVersion); | ||
return files.map(file => formatFileItem(file)); | ||
} | ||
|
||
@HTTPMethod({ | ||
// GET /:fullname/:versionOrTag/files => /:fullname/:versionOrTag/files/${pkg.main} | ||
// GET /:fullname/:versionOrTag/files?meta | ||
// GET /:fullname/:versionOrTag/files/ | ||
path: `/:fullname(${FULLNAME_REG_STRING})/:versionOrTag/files`, | ||
// GET /:fullname/:versionSpec/files => /:fullname/:versionSpec/files/${pkg.main} | ||
// GET /:fullname/:versionSpec/files?meta | ||
// GET /:fullname/:versionSpec/files/ | ||
path: `/:fullname(${FULLNAME_REG_STRING})/:versionSpec/files`, | ||
method: HTTPMethodEnum.GET, | ||
}) | ||
async listFiles(@Context() ctx: EggContext, | ||
@HTTPParam() fullname: string, | ||
@HTTPParam() versionOrTag: string, | ||
@HTTPParam() versionSpec: string, | ||
@HTTPQuery() meta: string) { | ||
this.#requireUnpkgEnable(); | ||
ctx.tValidate(Spec, `${fullname}@${versionSpec}`); | ||
ctx.vary(this.config.cnpmcore.cdnVaryHeader); | ||
const [ scope, name ] = getScopeAndName(fullname); | ||
const packageVersion = await this.#getPackageVersion(ctx, fullname, scope, name, versionOrTag); | ||
const packageVersion = await this.#getPackageVersion(ctx, fullname, scope, name, versionSpec); | ||
ctx.set('cache-control', META_CACHE_CONTROL); | ||
const hasMeta = typeof meta === 'string' || ctx.path.endsWith('/files/'); | ||
// meta request | ||
if (hasMeta) { | ||
const files = await this.#listFilesByDirectory(packageVersion, '/'); | ||
if (!files) { | ||
throw new NotFoundError(`${fullname}@${versionOrTag}/files not found`); | ||
throw new NotFoundError(`${fullname}@${versionSpec}/files not found`); | ||
} | ||
return files; | ||
} | ||
const { manifest } = await this.packageManagerService.showPackageVersionManifest(scope, name, versionOrTag, false, true); | ||
const { manifest } = await this.packageManagerService.showPackageVersionManifest(scope, name, versionSpec, false, true); | ||
// GET /foo/1.0.0/files => /foo/1.0.0/files/{main} | ||
// ignore empty entry exp: @types/[email protected]/ | ||
const indexFile = manifest?.main || 'index.js'; | ||
ctx.redirect(join(ctx.path, indexFile)); | ||
} | ||
|
||
@HTTPMethod({ | ||
// GET /:fullname/:versionOrTag/files/:path | ||
// GET /:fullname/:versionOrTag/files/:path?meta | ||
path: `/:fullname(${FULLNAME_REG_STRING})/:versionOrTag/files/:path(.+)`, | ||
// GET /:fullname/:versionSpec/files/:path | ||
// GET /:fullname/:versionSpec/files/:path?meta | ||
path: `/:fullname(${FULLNAME_REG_STRING})/:versionSpec/files/:path(.+)`, | ||
method: HTTPMethodEnum.GET, | ||
}) | ||
async raw(@Context() ctx: EggContext, | ||
@HTTPParam() fullname: string, | ||
@HTTPParam() versionOrTag: string, | ||
@HTTPParam() versionSpec: string, | ||
@HTTPParam() path: string, | ||
@HTTPQuery() meta: string) { | ||
this.#requireUnpkgEnable(); | ||
ctx.tValidate(Spec, `${fullname}@${versionSpec}`); | ||
ctx.vary(this.config.cnpmcore.cdnVaryHeader); | ||
const [ scope, name ] = getScopeAndName(fullname); | ||
path = `/${path}`; | ||
const packageVersion = await this.#getPackageVersion(ctx, fullname, scope, name, versionOrTag); | ||
const packageVersion = await this.#getPackageVersion(ctx, fullname, scope, name, versionSpec); | ||
if (path.endsWith('/')) { | ||
const directory = path.substring(0, path.length - 1); | ||
const files = await this.#listFilesByDirectory(packageVersion, directory); | ||
if (!files) { | ||
throw new NotFoundError(`${fullname}@${versionOrTag}/files${directory} not found`); | ||
throw new NotFoundError(`${fullname}@${versionSpec}/files${directory} not found`); | ||
} | ||
ctx.set('cache-control', META_CACHE_CONTROL); | ||
return files; | ||
} | ||
|
||
const file = await this.packageVersionFileService.showPackageVersionFile(packageVersion, path); | ||
if (!file) { | ||
throw new NotFoundError(`File ${fullname}@${versionOrTag}${path} not found`); | ||
throw new NotFoundError(`File ${fullname}@${versionSpec}${path} not found`); | ||
} | ||
const hasMeta = typeof meta === 'string'; | ||
if (hasMeta) { | ||
|
@@ -157,20 +161,20 @@ export class PackageVersionFileController extends AbstractController { | |
return await this.distRepository.getDistStream(file.dist); | ||
} | ||
|
||
async #getPackageVersion(ctx: EggContext, fullname: string, scope: string, name: string, versionOrTag: string) { | ||
async #getPackageVersion(ctx: EggContext, fullname: string, scope: string, name: string, versionSpec: string) { | ||
const { blockReason, packageVersion } = await this.packageManagerService.showPackageVersionByVersionOrTag( | ||
scope, name, versionOrTag); | ||
scope, name, versionSpec); | ||
if (blockReason) { | ||
this.setCDNHeaders(ctx); | ||
throw this.createPackageBlockError(blockReason, fullname, versionOrTag); | ||
throw this.createPackageBlockError(blockReason, fullname, versionSpec); | ||
} | ||
if (!packageVersion) { | ||
throw new NotFoundError(`${fullname}@${versionOrTag} not found`); | ||
throw new NotFoundError(`${fullname}@${versionSpec} not found`); | ||
} | ||
if (packageVersion.version !== versionOrTag) { | ||
if (packageVersion.version !== versionSpec) { | ||
ctx.set('cache-control', META_CACHE_CONTROL); | ||
let location = ctx.url.replace(`/${fullname}/${versionOrTag}/files`, `/${fullname}/${packageVersion.version}/files`); | ||
location = location.replace(`/${fullname}/${encodeURIComponent(versionOrTag)}/files`, `/${fullname}/${packageVersion.version}/files`); | ||
let location = ctx.url.replace(`/${fullname}/${versionSpec}/files`, `/${fullname}/${packageVersion.version}/files`); | ||
location = location.replace(`/${fullname}/${encodeURIComponent(versionSpec)}/files`, `/${fullname}/${packageVersion.version}/files`); | ||
throw this.createControllerRedirectError(location); | ||
} | ||
return packageVersion; | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,7 +10,7 @@ describe('test/port/controller/PackageVersionFileController/listFiles.test.ts', | |
publisher = await TestUtil.createUser(); | ||
}); | ||
|
||
describe('[GET /:fullname/:versionOrTag/files] listFiles()', () => { | ||
describe('[GET /:fullname/:versionSpec/files] listFiles()', () => { | ||
it('should 404 when enableUnpkg = false', async () => { | ||
mock(app.config.cnpmcore, 'allowPublishNonScopePackage', true); | ||
mock(app.config.cnpmcore, 'enableUnpkg', false); | ||
|
@@ -68,6 +68,15 @@ describe('test/port/controller/PackageVersionFileController/listFiles.test.ts', | |
assert.equal(res.body.error, '[NOT_FOUND] File [email protected]/index.js not found'); | ||
}); | ||
|
||
it('should 422 when invalid spec', async () => { | ||
mock(app.config.cnpmcore, 'enableUnpkg', true); | ||
const res = await app.httpRequest() | ||
.get('/foo/@invalid-spec/files') | ||
.expect(422); | ||
|
||
assert.equal(res.body.error, '[INVALID_PARAM] must match format "semver-spec"'); | ||
}); | ||
|
||
it('should list one package version files', async () => { | ||
mock(app.config.cnpmcore, 'allowPublishNonScopePackage', true); | ||
const pkg = await TestUtil.getFullPackage({ | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters