diff --git a/scripts/bootstrap.js b/scripts/bootstrap.js index 7ffbcb0..159c82c 100644 --- a/scripts/bootstrap.js +++ b/scripts/bootstrap.js @@ -27,11 +27,15 @@ if (!which('tsc')) { if (exec('tsc', { silent: true }).code !== 0) { console.log('tsc completed build as expected') - echo('') + console.log('') } require('../') .install({ cwd: process.cwd() }) .then(function () { - echo('Success!') + console.log('Success!') + }) + .catch(function (err) { + console.log(err.toString()) + console.log(err.stack) }) diff --git a/src/init.ts b/src/init.ts index cb6918a..542724a 100644 --- a/src/init.ts +++ b/src/init.ts @@ -1,10 +1,9 @@ import Promise = require('any-promise') import extend = require('xtend') -import { join } from 'path' +import { join, basename } from 'path' import { ConfigJson } from './interfaces' import { writeJson, isFile, readJson } from './utils/fs' import { CONFIG_FILE } from './utils/config' -import { inferDefinitionName } from './utils/path' const TSD_JSON_FILE = 'tsd.json' const DEFINITELYTYPED_REPO = 'DefinitelyTyped/DefinitelyTyped' @@ -71,7 +70,7 @@ function upgradeTsdJson (tsdJson: TsdJson, config?: ConfigJson): ConfigJson { Object.keys(tsdJson.installed).forEach(function (path) { const dependency = tsdJson.installed[path] - const name = inferDefinitionName(path) + const name = basename(path, '.d.ts') const location = `github:${repo}/${path}#${dependency.commit}` typingsJson.ambientDependencies[name] = location diff --git a/src/install.spec.ts b/src/install.spec.ts index 656e498..64a520a 100644 --- a/src/install.spec.ts +++ b/src/install.spec.ts @@ -67,7 +67,7 @@ test('install', t => { const DEPENDENCY = '@scope/test=file:custom_typings/definition.d.ts' const REGISTRY_DEPENDENCY = 'registry:dt/node@>=4.0' const PEER_DEPENDENCY = 'file:custom_typings/named/typings.json' - const AMBIENT_DEPENDENCY = 'ambient-test=file:custom_typings/ambient.d.ts' + const AMBIENT_DEPENDENCY = 'file:custom_typings/ambient.d.ts' const FIXTURE_DIR = join(__dirname, '__test__/install-dependency-fixture') const CONFIG = join(FIXTURE_DIR, CONFIG_FILE) @@ -131,24 +131,12 @@ test('install', t => { node: 'registry:dt/node#4.0.0+20160226132328' }, ambientDevDependencies: { - 'ambient-test': 'file:custom_typings/ambient.d.ts' + ambient: 'file:custom_typings/ambient.d.ts' } }) }) }) - t.test('reject install if name is missing', t => { - const DEPENDENCY = 'file:custom_typings/definition.d.ts' - const FIXTURE_DIR = join(__dirname, '__test__/install-dependency-fixture') - - t.plan(1) - - return installDependencyRaw(DEPENDENCY, { cwd: FIXTURE_DIR, emitter }) - .catch(err => { - t.ok(/^Unable to install dependency/.test(err.message)) - }) - }) - t.test('install empty', t => { const FIXTURE_DIR = join(__dirname, '__test__/install-empty') diff --git a/src/lib/compile.spec.ts b/src/lib/compile.spec.ts index 06311b3..ce4119d 100644 --- a/src/lib/compile.spec.ts +++ b/src/lib/compile.spec.ts @@ -622,7 +622,7 @@ test('compile', t => { raw: undefined, postmessage: undefined, ambient: false, - typings: 'http://example.com/index.d.ts', + main: 'http://example.com/index.d.ts?query=test', dependencies: {}, devDependencies: {}, peerDependencies: {}, @@ -633,13 +633,35 @@ test('compile', t => { const emitter = new EventEmitter() nock('http://example.com') - .get('/index.d.ts') + .get('/index.d.ts?query=test') .matchHeader('User-Agent', /^typings\/\d+\.\d+\.\d+ node\/v\d+\.\d+\.\d+.*$/) - .reply(200, 'export const helloWorld: string') + .reply(200, 'export * from "./test"') + + nock('http://example.com') + .get('/test.d.ts?query=test') + .reply(200, 'export const test: boolean') return compile(node, { name: 'test', cwd: __dirname, ambient: false, meta: false, emitter }) .then(function (result) { - t.equal(result.main, `declare module 'test' {\nexport const helloWorld: string\n}\n`) + t.equal(result.main, [ + 'declare module \'~test/test\' {', + 'export const test: boolean', + '}', + 'declare module \'test/test\' {', + 'export * from \'~test/test\';', + '}', + '', + 'declare module \'~test/index\' {', + 'export * from \'~test/test\'', + '}', + 'declare module \'test/index\' {', + 'export * from \'~test/index\';', + '}', + 'declare module \'test\' {', + 'export * from \'~test/index\';', + '}', + '' + ].join('\n')) }) }) diff --git a/src/lib/compile.ts b/src/lib/compile.ts index a288418..8fa7751 100644 --- a/src/lib/compile.ts +++ b/src/lib/compile.ts @@ -6,7 +6,7 @@ import { join, relative, basename } from 'path' import { DependencyTree, Overrides, Emitter } from '../interfaces' import { readFileFrom } from '../utils/fs' import { EOL, normalizeEOL } from '../utils/path' -import { resolveFrom, relativeTo, isHttp, isModuleName, normalizeSlashes, fromDefinition, normalizeToDefinition, toDefinition } from '../utils/path' +import { resolveFrom, relativeTo, isHttp, isModuleName, normalizeSlashes, pathFromDefinition, normalizeToDefinition, toDefinition } from '../utils/path' import { REFERENCE_REGEXP } from '../utils/references' import { PROJECT_NAME, CONFIG_FILE, DEPENDENCY_SEPARATOR } from '../utils/config' import { resolveDependency } from '../utils/parse' @@ -75,7 +75,7 @@ interface CompileOptions extends Options { /** * Resolve override paths. */ -function resolveFromWithModuleNameOverride (src: string, to: string | boolean): string { +function resolveFromOverride (src: string, to: string | boolean): string { if (typeof to === 'string') { if (isModuleName(to)) { const [moduleName, modulePath] = getModuleNameParts(to) @@ -130,8 +130,8 @@ function getStringifyOptions ( overrides[mainDefinition] = browserDefinition } else { for (const key of Object.keys(browser)) { - const from = resolveFromWithModuleNameOverride(tree.src, key) as string - const to = resolveFromWithModuleNameOverride(tree.src, browser[key]) + const from = resolveFromOverride(tree.src, key) as string + const to = resolveFromOverride(tree.src, browser[key]) overrides[from] = to } @@ -413,10 +413,10 @@ function importPath (path: string, name: string, options: StringifyOptions) { return name } - return `${prefix}${DEPENDENCY_SEPARATOR}${modulePath ? fromDefinition(resolved) : resolved}` + return `${prefix}${DEPENDENCY_SEPARATOR}${modulePath ? pathFromDefinition(resolved) : resolved}` } - const relativePath = relativeTo(tree.src, fromDefinition(resolved)) + const relativePath = relativeTo(tree.src, pathFromDefinition(resolved)) return normalizeSlashes(join(prefix, relativePath)) } @@ -545,8 +545,8 @@ function stringifyFile (path: string, rawContents: string, rawPath: string, opti return meta + declareText(parent ? moduleName : name, moduleText) } - const modulePath = importPath(path, fromDefinition(path), options) - const prettyPath = normalizeSlashes(join(name, relativeTo(tree.src, fromDefinition(path)))) + const modulePath = importPath(path, pathFromDefinition(path), options) + const prettyPath = normalizeSlashes(join(name, relativeTo(tree.src, pathFromDefinition(path)))) const declared = declareText(modulePath, moduleText) if (!isEntry) { diff --git a/src/utils/fs.ts b/src/utils/fs.ts index 001f6cb..85a219e 100644 --- a/src/utils/fs.ts +++ b/src/utils/fs.ts @@ -19,7 +19,7 @@ import { join, dirname } from 'path' import { parse as parseUrl } from 'url' import template = require('string-template') import { CONFIG_FILE, TYPINGS_DIR, DTS_MAIN_FILE, DTS_BROWSER_FILE, PRETTY_PROJECT_NAME, HOMEPAGE } from './config' -import { isHttp, toDefinition, EOL, detectEOL, normalizeEOL } from './path' +import { isHttp, EOL, detectEOL, normalizeEOL } from './path' import { parseReferences, stringifyReferences } from './references' import { ConfigJson } from '../interfaces' import { CompiledOutput } from '../lib/compile' diff --git a/src/utils/parse.spec.ts b/src/utils/parse.spec.ts index edaabaf..a41f674 100644 --- a/src/utils/parse.spec.ts +++ b/src/utils/parse.spec.ts @@ -2,15 +2,19 @@ import test = require('blue-tape') import { normalize } from 'path' import { parseDependency, resolveDependency, expandRegistry } from './parse' import { CONFIG_FILE } from './config' +import { Dependency } from '../interfaces' test('parse', t => { t.test('parse dependency', t => { t.test('parse filename', t => { const actual = parseDependency('file:./foo/bar.d.ts') - const expected = { + const expected: Dependency = { raw: 'file:./foo/bar.d.ts', location: normalize('foo/bar.d.ts'), - meta: { path: normalize('foo/bar.d.ts') }, + meta: { + name: 'bar', + path: normalize('foo/bar.d.ts') + }, type: 'file' } @@ -20,10 +24,13 @@ test('parse', t => { t.test('parse filename relative', t => { const actual = parseDependency('file:foo/bar.d.ts') - const expected = { + const expected: Dependency = { raw: 'file:foo/bar.d.ts', location: normalize('foo/bar.d.ts'), - meta: { path: normalize('foo/bar.d.ts') }, + meta: { + name: 'bar', + path: normalize('foo/bar.d.ts') + }, type: 'file' } @@ -33,7 +40,7 @@ test('parse', t => { t.test('parse npm', t => { const actual = parseDependency('npm:foobar') - const expected = { + const expected: Dependency = { raw: 'npm:foobar', type: 'npm', meta: { @@ -49,7 +56,7 @@ test('parse', t => { t.test('parse scoped npm packages', t => { const actual = parseDependency('npm:@foo/bar') - const expected = { + const expected: Dependency = { raw: 'npm:@foo/bar', type: 'npm', meta: { @@ -65,7 +72,7 @@ test('parse', t => { t.test('parse npm filename', t => { const actual = parseDependency('npm:typescript/bin/lib.es6.d.ts') - const expected = { + const expected: Dependency = { raw: 'npm:typescript/bin/lib.es6.d.ts', type: 'npm', meta: { @@ -81,7 +88,7 @@ test('parse', t => { t.test('parse bower', t => { const actual = parseDependency('bower:foobar') - const expected = { + const expected: Dependency = { raw: 'bower:foobar', type: 'bower', meta: { @@ -97,7 +104,7 @@ test('parse', t => { t.test('parse bower filename', t => { const actual = parseDependency('bower:foobar/' + CONFIG_FILE) - const expected = { + const expected: Dependency = { raw: 'bower:foobar/' + CONFIG_FILE, type: 'bower', meta: { @@ -113,10 +120,11 @@ test('parse', t => { t.test('parse github', t => { const actual = parseDependency('github:foo/bar') - const expected = { + const expected: Dependency = { raw: 'github:foo/bar', type: 'github', meta: { + name: undefined, org: 'foo', path: 'typings.json', repo: 'bar', @@ -131,10 +139,11 @@ test('parse', t => { t.test('parse github with sha and append config file', t => { const actual = parseDependency('github:foo/bar#test') - const expected = { + const expected: Dependency = { raw: 'github:foo/bar#test', type: 'github', meta: { + name: undefined, org: 'foo', path: 'typings.json', repo: 'bar', @@ -149,10 +158,11 @@ test('parse', t => { t.test('parse github paths to `.d.ts` files', t => { const actual = parseDependency('github:foo/bar/typings/file.d.ts') - const expected = { + const expected: Dependency = { raw: 'github:foo/bar/typings/file.d.ts', type: 'github', meta: { + name: 'file', org: 'foo', path: 'typings/file.d.ts', repo: 'bar', @@ -167,10 +177,11 @@ test('parse', t => { t.test('parse github paths to config file', t => { const actual = parseDependency('github:foo/bar/src/' + CONFIG_FILE) - const expected = { + const expected: Dependency = { raw: 'github:foo/bar/src/' + CONFIG_FILE, type: 'github', meta: { + name: undefined, org: 'foo', path: 'src/typings.json', repo: 'bar', @@ -185,10 +196,11 @@ test('parse', t => { t.test('parse bitbucket', t => { const actual = parseDependency('bitbucket:foo/bar') - const expected = { + const expected: Dependency = { raw: 'bitbucket:foo/bar', type: 'bitbucket', meta: { + name: undefined, org: 'foo', path: 'typings.json', repo: 'bar', @@ -203,10 +215,11 @@ test('parse', t => { t.test('parse bitbucket and append config file to path', t => { const actual = parseDependency('bitbucket:foo/bar/dir') - const expected = { + const expected: Dependency = { raw: 'bitbucket:foo/bar/dir', type: 'bitbucket', meta: { + name: undefined, org: 'foo', path: 'dir/typings.json', repo: 'bar', @@ -221,10 +234,11 @@ test('parse', t => { t.test('parse bitbucket with sha', t => { const actual = parseDependency('bitbucket:foo/bar#abc') - const expected = { + const expected: Dependency = { raw: 'bitbucket:foo/bar#abc', type: 'bitbucket', meta: { + name: undefined, org: 'foo', path: 'typings.json', repo: 'bar', @@ -239,7 +253,7 @@ test('parse', t => { t.test('parse url', t => { const actual = parseDependency('http://example.com/foo/' + CONFIG_FILE) - const expected = { + const expected: Dependency = { raw: 'http://example.com/foo/' + CONFIG_FILE, type: 'http', meta: {}, @@ -252,7 +266,7 @@ test('parse', t => { t.test('parse registry', t => { const actual = parseDependency('registry:dt/node') - const expected = { + const expected: Dependency = { raw: 'registry:dt/node', type: 'registry', meta: { name: 'node', source: 'dt', tag: undefined as string, version: undefined as string }, @@ -265,7 +279,7 @@ test('parse', t => { t.test('parse registry with scoped package', t => { const actual = parseDependency('registry:npm/@scoped/npm') - const expected = { + const expected: Dependency = { raw: 'registry:npm/@scoped/npm', type: 'registry', meta: { name: '@scoped/npm', source: 'npm', tag: undefined as string, version: undefined as string }, @@ -278,7 +292,7 @@ test('parse', t => { t.test('parse registry with tag', t => { const actual = parseDependency('registry:npm/dep#3.0.0-2016') - const expected = { + const expected: Dependency = { raw: 'registry:npm/dep#3.0.0-2016', type: 'registry', meta: { name: 'dep', source: 'npm', tag: '3.0.0-2016', version: undefined as string }, @@ -291,7 +305,7 @@ test('parse', t => { t.test('parse registry with version', t => { const actual = parseDependency('registry:npm/dep@^4.0') - const expected = { + const expected: Dependency = { raw: 'registry:npm/dep@^4.0', type: 'registry', meta: { name: 'dep', source: 'npm', tag: undefined as string, version: '^4.0' }, diff --git a/src/utils/parse.ts b/src/utils/parse.ts index 456c230..0e44d8c 100644 --- a/src/utils/parse.ts +++ b/src/utils/parse.ts @@ -3,7 +3,7 @@ import { parse, format, resolve as resolveUrl } from 'url' import { normalize, join, basename, dirname } from 'path' import { Dependency } from '../interfaces' import { CONFIG_FILE } from './config' -import { isDefinition, normalizeSlashes, inferDefinitionName, sanitizeDefinitionName } from './path' +import { isDefinition, normalizeSlashes, pathFromDefinition } from './path' import rc from './rc' /** @@ -16,15 +16,18 @@ function gitFromPath (src: string) { const org = segments.shift() const repo = segments.shift() let path = segments.join('/') + let name: string // Automatically look for the config file in the root. if (segments.length === 0) { path = CONFIG_FILE - } else if (!isDefinition(path) && segments[segments.length - 1] !== CONFIG_FILE) { + } else if (isDefinition(path)) { + name = basename(pathFromDefinition(path)) + } else if (segments[segments.length - 1] !== CONFIG_FILE) { path += `/${CONFIG_FILE}` } - return { org, repo, path, sha } + return { org, repo, path, sha, name } } /** @@ -50,6 +53,7 @@ export function parseDependency (raw: string): Dependency { if (type === 'file') { const location = normalize(src) const filename = basename(location) + const name = isDefinition(filename) ? pathFromDefinition(filename) : undefined invariant( filename === CONFIG_FILE || isDefinition(filename), @@ -60,6 +64,7 @@ export function parseDependency (raw: string): Dependency { raw, type, meta: { + name: name, path: location }, location @@ -69,7 +74,7 @@ export function parseDependency (raw: string): Dependency { // `bitbucket:org/repo/path#sha` if (type === 'github') { const meta = gitFromPath(src) - const { org, repo, path, sha } = meta + const { org, repo, path, sha, name } = meta let location = `https://raw.githubusercontent.com/${org}/${repo}/${sha}/${path}` if (rc.githubToken) { @@ -87,7 +92,7 @@ export function parseDependency (raw: string): Dependency { // `bitbucket:org/repo/path#sha` if (type === 'bitbucket') { const meta = gitFromPath(src) - const { org, repo, path, sha } = meta + const { org, repo, path, sha, name } = meta const location = `https://bitbucket.org/${org}/${repo}/raw/${sha}/${path}` return { diff --git a/src/utils/path.spec.ts b/src/utils/path.spec.ts new file mode 100644 index 0000000..992fa5c --- /dev/null +++ b/src/utils/path.spec.ts @@ -0,0 +1,16 @@ +import test = require('blue-tape') +import { pathFromDefinition } from './path' + +test('parse', t => { + t.test('path from definition', t => { + t.test('path', t => { + t.equal(pathFromDefinition('foo/bar.d.ts'), 'foo/bar') + t.end() + }) + + t.test('url', t => { + t.equal(pathFromDefinition('http://example.com/test.d.ts'), '/test') + t.end() + }) + }) +}) diff --git a/src/utils/path.ts b/src/utils/path.ts index 33983c1..4f0eb57 100644 --- a/src/utils/path.ts +++ b/src/utils/path.ts @@ -1,5 +1,5 @@ import { resolve, dirname, basename, relative, extname, join } from 'path' -import { resolve as resolveUrl, parse as parseUrl } from 'url' +import { resolve as resolveUrl, parse as parseUrl, format as formatUrl } from 'url' import { TYPINGS_DIR, DTS_MAIN_FILE, DTS_BROWSER_FILE } from './config' import isAbsolute = require('is-absolute') @@ -24,6 +24,10 @@ export function isHttp (url: string) { * Check if a path looks like a definition file. */ export function isDefinition (path: string): boolean { + if (isHttp(path)) { + return isDefinition(parseUrl(path).pathname) + } + return /\.d\.ts$/.test(path) } @@ -41,41 +45,23 @@ export function normalizeSlashes (path: string) { return path.replace(/\\/g, '/') } -/** - * Infer the definition name from a location string. - */ -export function inferDefinitionName (location: string) { - if (isDefinition(location)) { - let pathname = location - - if (isHttp(location)) { - pathname = parseUrl(location).pathname - } - - return sanitizeDefinitionName(basename(pathname, '.d.ts')) - } -} - -/** - * Attempt to sanitize the definition name (stripping "typings", etc). - */ -export function sanitizeDefinitionName (name: string) { - if (name == null) { - return name - } - - return name.replace(/^(?:typings|typed)\-|\-(?:typings|typed)$/, '') -} - /** * Resolve a path directly from another. */ export function resolveFrom (from: string, to: string) { + // Replace the entire path. if (isHttp(to)) { return to } - return isHttp(from) ? resolveUrl(from, to) : resolve(dirname(from), to) + // Resolve relative HTTP requests. + if (isHttp(from)) { + const url = parseUrl(from) + url.pathname = resolveUrl(url.pathname, to) + return formatUrl(url) + } + + return resolve(dirname(from), to) } /** @@ -83,8 +69,9 @@ export function resolveFrom (from: string, to: string) { */ export function relativeTo (from: string, to: string): string { if (isHttp(from)) { + const fromUrl = parseUrl(from) + if (isHttp(to)) { - const fromUrl = parseUrl(from) const toUrl = parseUrl(to) if (toUrl.auth !== fromUrl.auth || toUrl.host !== fromUrl.host) { @@ -104,7 +91,7 @@ export function relativeTo (from: string, to: string): string { return relativeUrl } - return to + return relativeTo(fromUrl.pathname, to) } return relative(dirname(from), to) @@ -113,15 +100,25 @@ export function relativeTo (from: string, to: string): string { /** * Append `.d.ts` to a path. */ -export function toDefinition (name: string) { - return `${name}.d.ts` +export function toDefinition (path: string) { + if (isHttp(path)) { + const url = parseUrl(path) + url.pathname = toDefinition(url.pathname) + return formatUrl(url) + } + + return `${path}.d.ts` } /** * Remove `.d.ts` from a path. */ -export function fromDefinition (name: string) { - return name.replace(/\.d\.ts$/, '') +export function pathFromDefinition (path: string): string { + if (isHttp(path)) { + return pathFromDefinition(parseUrl(path).pathname) + } + + return path.replace(/\.d\.ts$/, '') } /** @@ -132,6 +129,12 @@ export function normalizeToDefinition (path: string) { return path } + if (isHttp(path)) { + const url = parseUrl(path) + url.pathname = normalizeToDefinition(path) + return formatUrl(url) + } + const ext = extname(path) return toDefinition(ext ? path.slice(0, -ext.length) : path)