diff --git a/README.md b/README.md index 82968a4..4d9ed0a 100644 --- a/README.md +++ b/README.md @@ -22,12 +22,13 @@ var parsed = npa("@bar/foo@1.2") // Returns an object like: { - raw: '@bar/foo@1.2', // what was passed in - name: "@bar/foo", // the name of the package - scope: "@bar", // the private scope of the package, or null - type: "range", // the type of specifier this is - spec: ">=1.2.0 <1.3.0" // the expanded specifier - rawSpec: "1.2" // the specifier as passed in + raw: '@bar/foo@1.2', // what was passed in + name: '@bar/foo', // the name of the package + escapedName: '@bar%2ffoo', // the escaped name, for registry internals + scope: '@bar', // the scope of the package, or null + type: 'range', // the type of specifier this is + spec: '>=1.2.0 <1.3.0', // the expanded specifier + rawSpec: '1.2' // the specifier as passed in } // Parsing urls pointing at hosted git services produces a variation: @@ -38,6 +39,7 @@ var parsed = npa("git+https://github.com/user/foo") raw: 'git+https://github.com/user/foo', scope: null, name: null, + escapedName: null, rawSpec: 'git+https://github.com/user/foo', spec: 'user/foo', type: 'hosted', @@ -97,8 +99,11 @@ keys: * `rawSpec` - The part after the `name@...`, as it was originally provided. * `scope` - If a name is something like `@org/module` then the `scope` - field will be set to `org`. If it doesn't have a scoped name, then + field will be set to `@org`. If it doesn't have a scoped name, then scope is `null`. +* `escapedName` - The escaped version of `name`, which may be needed when + interacting with registry internals like CouchDB. When `name` is `null`, + `escapedName` will also be `null`. If you only include a name and no specifier part, eg, `foo` or `foo@` then a default of `latest` will be used (as of 4.1.0). This is contrast with diff --git a/npa.js b/npa.js index 1e6deb1..1d884e3 100644 --- a/npa.js +++ b/npa.js @@ -43,6 +43,7 @@ function npa (arg) { var res = new Result res.raw = arg res.scope = null + res.escapedName = null // See if it's something like foo@... var nameparse = arg.match(nameAt) @@ -50,6 +51,7 @@ function npa (arg) { if (nameparse && validName(nameparse[3]) && (!nameparse[2] || validName(nameparse[2]))) { res.name = (nameparse[1] || "") + nameparse[3] + res.escapedName = escapeName(res.name) if (nameparse[2]) res.scope = "@" + nameparse[2] arg = arg.substr(nameparse[0].length) @@ -106,6 +108,7 @@ function npa (arg) { res.spec = "latest" res.rawSpec = "" res.name = arg + res.escapedName = escapeName(res.name) if (p[1]) res.scope = "@" + p[1] } else { @@ -116,6 +119,11 @@ function npa (arg) { return res } +function escapeName (name) { + // scoped packages in couch must have slash url-encoded, e.g. @foo%2Fbar + return name && name.replace('/', '%2f') +} + function parseLocal (res, arg) { // turns out nearly every character is allowed in fs paths if (/\0/.test(arg)) { diff --git a/test/basic.js b/test/basic.js index 7e4112b..bce4c91 100644 --- a/test/basic.js +++ b/test/basic.js @@ -7,6 +7,7 @@ require("tap").test("basic", function (t) { var tests = { "foo@1.2": { name: "foo", + escapedName: "foo", type: "range", spec: ">=1.2.0 <1.3.0", raw: "foo@1.2", @@ -16,6 +17,7 @@ require("tap").test("basic", function (t) { "@foo/bar": { raw: "@foo/bar", name: "@foo/bar", + escapedName: "@foo%2fbar", scope: "@foo", rawSpec: "", spec: "latest", @@ -25,6 +27,7 @@ require("tap").test("basic", function (t) { "@foo/bar@": { raw: "@foo/bar@", name: "@foo/bar", + escapedName: "@foo%2fbar", scope: "@foo", rawSpec: "", spec: "latest", @@ -34,6 +37,7 @@ require("tap").test("basic", function (t) { "@foo/bar@baz": { raw: "@foo/bar@baz", name: "@foo/bar", + escapedName: "@foo%2fbar", scope: "@foo", rawSpec: "baz", spec: "baz", @@ -43,6 +47,7 @@ require("tap").test("basic", function (t) { "@f fo o al/ a d s ;f ": { raw: "@f fo o al/ a d s ;f", name: null, + escapedName: null, rawSpec: "@f fo o al/ a d s ;f", spec: "@f fo o al/ a d s ;f", type: "local" @@ -50,6 +55,7 @@ require("tap").test("basic", function (t) { "foo@1.2.3": { name: "foo", + escapedName: "foo", type: "version", spec: "1.2.3", raw: "foo@1.2.3" @@ -57,6 +63,7 @@ require("tap").test("basic", function (t) { "foo@=v1.2.3": { name: "foo", + escapedName: "foo", type: "version", spec: "1.2.3", raw: "foo@=v1.2.3", @@ -65,6 +72,7 @@ require("tap").test("basic", function (t) { "git+ssh://git@notgithub.com/user/foo#1.2.3": { name: null, + escapedName: null, type: "git", spec: "ssh://git@notgithub.com/user/foo#1.2.3", raw: "git+ssh://git@notgithub.com/user/foo#1.2.3" @@ -72,6 +80,7 @@ require("tap").test("basic", function (t) { "git+file://path/to/repo#1.2.3": { name: null, + escapedName: null, type: "git", spec: "file://path/to/repo#1.2.3", raw: "git+file://path/to/repo#1.2.3" @@ -79,6 +88,7 @@ require("tap").test("basic", function (t) { "git://notgithub.com/user/foo": { name: null, + escapedName: null, type: "git", spec: "git://notgithub.com/user/foo", raw: "git://notgithub.com/user/foo" @@ -86,6 +96,7 @@ require("tap").test("basic", function (t) { "@foo/bar@git+ssh://notgithub.com/user/foo": { name: "@foo/bar", + escapedName: "@foo%2fbar", scope: "@foo", spec: "ssh://notgithub.com/user/foo", rawSpec: "git+ssh://notgithub.com/user/foo", @@ -94,6 +105,7 @@ require("tap").test("basic", function (t) { "/path/to/foo": { name: null, + escapedName: null, type: "local", spec: path.resolve(__dirname, "/path/to/foo"), raw: "/path/to/foo" @@ -101,6 +113,7 @@ require("tap").test("basic", function (t) { "file:path/to/foo": { name: null, + escapedName: null, type: "local", spec: "path/to/foo", raw: "file:path/to/foo" @@ -108,6 +121,7 @@ require("tap").test("basic", function (t) { "file:~/path/to/foo": { name: null, + escapedName: null, type: "local", spec: "~/path/to/foo", raw: "file:~/path/to/foo" @@ -115,6 +129,7 @@ require("tap").test("basic", function (t) { "file:../path/to/foo": { name: null, + escapedName: null, type: "local", spec: "../path/to/foo", raw: "file:../path/to/foo" @@ -122,6 +137,7 @@ require("tap").test("basic", function (t) { "file:///path/to/foo": { name: null, + escapedName: null, type: "local", spec: "/path/to/foo", raw: "file:///path/to/foo" @@ -129,6 +145,7 @@ require("tap").test("basic", function (t) { "https://server.com/foo.tgz": { name: null, + escapedName: null, type: "remote", spec: "https://server.com/foo.tgz", raw: "https://server.com/foo.tgz" @@ -136,6 +153,7 @@ require("tap").test("basic", function (t) { "foo@latest": { name: "foo", + escapedName: "foo", type: "tag", spec: "latest", raw: "foo@latest" @@ -143,6 +161,7 @@ require("tap").test("basic", function (t) { "foo": { name: "foo", + escapedName: "foo", type: "tag", spec: "latest", raw: "foo" diff --git a/test/windows.js b/test/windows.js index b91416e..344cc6d 100644 --- a/test/windows.js +++ b/test/windows.js @@ -9,6 +9,7 @@ var cases = { raw: "C:\\x\\y\\z", scope: null, name: null, + escapedName: null, rawSpec: "C:\\x\\y\\z", spec: "C:\\x\\y\\z", type: "local" @@ -17,6 +18,7 @@ var cases = { raw: "foo@C:\\x\\y\\z", scope: null, name: "foo", + escapedName: "foo", rawSpec: "C:\\x\\y\\z", spec: "C:\\x\\y\\z", type: "local" @@ -25,6 +27,7 @@ var cases = { raw: "foo@file:///C:\\x\\y\\z", scope: null, name: "foo", + escapedName: "foo", rawSpec: "file:///C:\\x\\y\\z", spec: "C:\\x\\y\\z", type: "local" @@ -33,6 +36,7 @@ var cases = { raw: "foo@file://C:\\x\\y\\z", scope: null, name: "foo", + escapedName: "foo", rawSpec: "file://C:\\x\\y\\z", spec: "C:\\x\\y\\z", type: "local" @@ -41,6 +45,7 @@ var cases = { raw: "file:///C:\\x\\y\\z", scope: null, name: null, + escapedName: null, rawSpec: "file:///C:\\x\\y\\z", spec: "C:\\x\\y\\z", type: "local" @@ -49,6 +54,7 @@ var cases = { raw: "file://C:\\x\\y\\z", scope: null, name: null, + escapedName: null, rawSpec: "file://C:\\x\\y\\z", spec: "C:\\x\\y\\z", type: "local" @@ -57,6 +63,7 @@ var cases = { raw: "foo@/foo/bar/baz", scope: null, name: "foo", + escapedName: "foo", rawSpec: "/foo/bar/baz", spec: "/foo/bar/baz", type: "local"