diff --git a/.circleci/config.yml b/.circleci/config.yml index e1846f2..0b759a0 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -3,8 +3,11 @@ jobs: test: working_directory: ~/dems docker: - - image: node:12-stretch-slim + - image: node:12-alpine steps: + - run: + name: Install certificate handling for artifact uploading + command: apk --update add ca-certificates git - checkout - run: name: Install fixed dependencies from lockfile @@ -22,11 +25,11 @@ jobs: release: working_directory: ~/dems docker: - - image: node:12-stretch-slim + - image: node:12-alpine steps: - checkout - run: - command: apt update && apt install -y git + command: apk --update add git - run: yarn --frozen-lockfile - run: yarn build - run: npx semantic-release diff --git a/LICENSE.md b/LICENSE.md deleted file mode 100644 index f49a4e1..0000000 --- a/LICENSE.md +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. \ No newline at end of file diff --git a/README.md b/README.md deleted file mode 100644 index 8b3bb39..0000000 --- a/README.md +++ /dev/null @@ -1,64 +0,0 @@ -# dems - -![License: Apache-2.0](https://img.shields.io/npm/l/dems.svg?style=for-the-badge) -![npm download count](https://img.shields.io/npm/dt/dems.svg?style=for-the-badge) -![Dependency status for latest release](https://img.shields.io/librariesio/release/npm/dems.svg?style=for-the-badge) -![Vulnerability count from Snyk](https://img.shields.io/snyk/vulnerabilities/npm/dems.svg?style=for-the-badge) - -> dems is currently at a 0.x release and it may break at any point. Feel free to -> use or test it for one-off bootstrapping, but do not rely on it as part of -> scripts until 1.x has been released. - -A combination of `degit` and `ms` (for `mustache`). This is a scaffolding tool -that downloads a given repository from GitHub / GitLab / BitBucket and -optionally replaces any designated variables in it. Without variables, this acts -much the same as [degit](https://github.com/Rich-Harris/degit). - -## Getting Started - -```console -npx dems user/repo -``` - -That's it! This will copy the latest commit on the default branch into a local -directory that shares the name of the `repo`. As with `degit`, all git history -is removed during the process, so it appears as a clean repo. - -You can also supply an alternate target with an optional second argument: - -```console -npx dems user/repo new-dir -``` - -This will copy the repo to a local `new-dir` directory. - -## Templating - -During the cloning process, `dems` will parse each file looking for mustache -templating. If any variables are found, it will then launch a wizard asking you -to enter a value for each variable. Finally, the templated files are rendered -with mustache. - -To see an example of this, take a look at -[the example repo](https://github.com/robcresswell/dems-example). - -## Why should I use this? - -This is an experiment for me; I really like the idea of `degit`, but it has some -under-explored concepts around "actions" and I thought I'd take it in a -different direction via templating. I usually find, when using `degit`, that I -have to replace several simple strings; package name and author fields for -example, so `dems` provides a solution. If you also find yourself experiencing -this, give `dems` a go. - -## This code is really bad! It's missing \$FEATURE! I found a bug! - -[Open an issue](https://github.com/robcresswell/dems/issues/new). If I agree -with you, I'll probably fix the bug / add the feature, but it'll be in my own -time. If you need it sooner, open a Pull Request or fork for your own needs. - -## Credits - -Big thanks to [Rich Harris](https://github.com/Rich-Harris) for -[degit](https://github.com/Rich-Harris/degit), which provided a bunch of code -and inspiration for this. diff --git a/src/cli.ts b/src/cli.ts index d4e882f..d69bd34 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -20,10 +20,7 @@ export async function cli( try { const { resolvedDest, archiveUrl } = await getValidConfig(...args); - - // TODO: Clean up this side-effect nightmare - const templateVariables: Set = new Set(); - await downloadRepo(archiveUrl, resolvedDest, templateVariables); + const templateVariables = await downloadRepo(archiveUrl, resolvedDest); if (templateVariables.size > 0) { const variables: { [key: string]: string } = {}; diff --git a/src/download-repo.ts b/src/download-repo.ts index 0453609..d285c13 100644 --- a/src/download-repo.ts +++ b/src/download-repo.ts @@ -37,9 +37,11 @@ function getMapStream(templateVariables: Set) { export async function downloadRepo( archiveUrl: string, dest: string, - templateVariables: Set, -) { +): Promise> { return new Promise((resolve, reject) => { + // TODO: Clean up this side-effect nightmare + const templateVariables: Set = new Set(); + // eslint-disable-next-line consistent-return get(archiveUrl, (response) => { if (!response.statusCode || response.statusCode >= 400) { @@ -57,10 +59,7 @@ export async function downloadRepo( ); } - downloadRepo(redirectUrl, dest, templateVariables).then( - resolve, - reject, - ); + downloadRepo(redirectUrl, dest).then(resolve, reject); } else { const mapStream = getMapStream(templateVariables); response @@ -71,7 +70,7 @@ export async function downloadRepo( mapStream, }), ) - .on('finish', resolve) + .on('finish', () => resolve(templateVariables)) .on('error', reject); } }); diff --git a/src/get-scm-info.ts b/src/get-scm-info.ts index 9177598..644cda8 100644 --- a/src/get-scm-info.ts +++ b/src/get-scm-info.ts @@ -1,16 +1,22 @@ import { InvalidSourceError } from './errors'; import { SCMType } from './types'; -export function getScmInfo(url: string): { type: SCMType; repo: string } { +const repoUrlMap = { + github: (repo: string) => `https://github.com/${repo}`, + gitlab: (repo: string) => `https://gitlab.com/${repo}`, + bitbucket: (repo: string) => `https://bitbucket.org/${repo}`, +}; + +export function getScmInfo(descriptor: string): { type: SCMType; url: string } { const pattern = /(?:https:\/\/|git@)?(github|bitbucket|gitlab)?(?::)?(?:\.org|\.com)?(?:\/|:)?([\w-]+\/[\w-]+)(?:\.git)?/; - const match = url.match(pattern); + const match = descriptor.match(pattern); if (!match) { throw new InvalidSourceError(); } - return { - type: (match[1] || 'github') as SCMType, - repo: match[2], - }; + const type = (match[1] || 'github') as SCMType; + const url = repoUrlMap[type](match[2]); + + return { type, url }; } diff --git a/src/validate.ts b/src/validate.ts index a538f0a..ef5d929 100644 --- a/src/validate.ts +++ b/src/validate.ts @@ -7,12 +7,10 @@ import { debug } from './log'; import { getScmInfo } from './get-scm-info'; const scmArchiveUrls = { - gitlab: (repo: string, sha: string) => - `https://gitlab.com/${repo}/repository/archive.tar.gz?ref=${sha}`, - bitbucket: (repo: string, sha: string) => - `https://bitbucket.org/${repo}/get/${sha}.tar.gz`, - github: (repo: string, sha: string) => - `https://github.com/${repo}/archive/${sha}.tar.gz`, + gitlab: (url: string, sha: string) => + `${url}/repository/archive.tar.gz?ref=${sha}`, + bitbucket: (url: string, sha: string) => `${url}/get/${sha}.tar.gz`, + github: (url: string, sha: string) => `${url}/archive/${sha}.tar.gz`, }; /** @@ -27,16 +25,16 @@ const scmArchiveUrls = { * @param dest A user input target directory */ export async function getValidConfig( - url?: string, + descriptor?: string, dest?: string, ): Promise { debug('Checking source is supplied'); - if (typeof url === 'undefined' || url.length === 0) { + if (typeof descriptor === 'undefined' || descriptor.length === 0) { throw new MissingSourceArgError(); } debug('Checking source is valid'); - const { repo, type } = getScmInfo(url); + const { url, type } = getScmInfo(descriptor); debug(`Valid source: ${url}`); const { stdout } = await pExec(`git ls-remote ${url}`); @@ -56,7 +54,7 @@ export async function getValidConfig( const sha = shaRefMap.HEAD; debug(`SHA: ${sha}`); - const archiveUrl = scmArchiveUrls[type](repo, sha); + const archiveUrl = scmArchiveUrls[type](url, sha); debug(`Archive URL: ${archiveUrl}`); debug('Checking dest is valid'); diff --git a/test/README.md b/test/README.md new file mode 100644 index 0000000..32a973c --- /dev/null +++ b/test/README.md @@ -0,0 +1,5 @@ +Create a new fixture tarball with: + +```console +tar -cvzf test/fixtures/test-dir.tar.gz test/fixtures/test-dir +``` diff --git a/test/cleanup.ts b/test/cleanup.ts new file mode 100644 index 0000000..09dc65d --- /dev/null +++ b/test/cleanup.ts @@ -0,0 +1,22 @@ +import { existsSync, rmdirSync, readdirSync, unlinkSync } from 'fs'; +import { resolve } from 'path'; + +/** + * Not an ideal solution, but relatively safe if the tests are not executed in + * parallel, and provides an easier solution than mocking write streams. Note + * that due to performance issues with Jest and parallelism in CircleCI, + * --runInBand is recommended anyway. + */ +export function removeTestDir() { + const dir = resolve('dems-example'); + if (existsSync(dir)) { + const dirents = readdirSync(dir, { withFileTypes: true }); + dirents.forEach((ent) => { + if (ent.isFile()) { + unlinkSync(resolve(dir, ent.name)); + } + }); + + rmdirSync(dir); + } +} diff --git a/test/cli.test.ts b/test/cli.test.ts index 141d9ef..4c2e116 100644 --- a/test/cli.test.ts +++ b/test/cli.test.ts @@ -1,16 +1,25 @@ import { mocked } from 'ts-jest/utils'; +import { promises as fsp } from 'fs'; +import { resolve } from 'path'; import { cli } from '../src/cli'; -import { getValidConfig } from '../src/validate'; -import { InvalidSourceError } from '../src/errors'; +import { prompt } from '../src/prompt'; +import { removeTestDir } from './cleanup'; -// mock the parts of the CLI that have side effects on the file system -jest.mock('../src/validate'); -jest.mock('../src/download-repo'); +jest.mock('../src/prompt'); describe('cli', () => { - const validateMock = mocked(getValidConfig); + fsp.writeFile = jest.fn(); + const promptMock = mocked(prompt); + const writeMock = mocked(fsp.writeFile); + + beforeEach(() => { + writeMock.mockResolvedValue(); + }); + afterEach(() => { - validateMock.mockReset(); + writeMock.mockRestore(); + promptMock.mockReset(); + removeTestDir(); }); it('prints help text when passed "-h"', async () => { @@ -30,9 +39,6 @@ describe('cli', () => { }); it('propagates error codes and messages', async () => { - validateMock.mockImplementationOnce(async () => { - throw new InvalidSourceError(); - }); const args = ['not-a-valid-source']; const { code, message } = await cli(args); @@ -41,16 +47,36 @@ describe('cli', () => { }); it('propagates success codes and messages', async () => { - validateMock.mockImplementationOnce(async () => { - return { - resolvedDest: 'dest', - archiveUrl: 'https://foo', - }; - }); - const args = ['github:valid/source']; + promptMock.mockResolvedValueOnce('foo'); + promptMock.mockResolvedValueOnce('bar'); + + const args = ['github:robcresswell/dems-example']; const { code, message } = await cli(args); expect(code).toBe(0); expect(message.toLowerCase()).toEqual(expect.stringContaining('success')); }); + + describe('e2e scenarios', () => { + it('downloads a repo and renders files with user input variables', async () => { + promptMock.mockResolvedValueOnce('foo'); + promptMock.mockResolvedValueOnce('bar'); + + const args = ['github:robcresswell/dems-example']; + const { code } = await cli(args); + + expect(writeMock).toHaveBeenCalledTimes(4); + expect(writeMock).toHaveBeenNthCalledWith( + 1, + resolve('dems-example', 'LICENSE.md'), + expect.any(String), + ); + expect(writeMock).toHaveBeenNthCalledWith( + 2, + resolve('dems-example', 'README.md'), + expect.any(String), + ); + expect(code).toBe(0); + }); + }); }); diff --git a/test/fixtures/test-dir.tar.gz b/test/fixtures/test-dir.tar.gz new file mode 100644 index 0000000..b6fbc2d Binary files /dev/null and b/test/fixtures/test-dir.tar.gz differ diff --git a/test/get-scm-info.test.ts b/test/get-scm-info.test.ts index 9c5237e..22b4c4d 100644 --- a/test/get-scm-info.test.ts +++ b/test/get-scm-info.test.ts @@ -6,44 +6,44 @@ jest.mock('../src/exec-promise'); const validSourceFixtures: { [key: string]: { type: SCMType; - repo: string; + url: string; }; } = { 'robcresswell/dems-example-1': { type: 'github', - repo: 'robcresswell/dems-example-1', + url: 'https://github.com/robcresswell/dems-example-1', }, 'github:robcresswell/dems-example-2': { type: 'github', - repo: 'robcresswell/dems-example-2', + url: 'https://github.com/robcresswell/dems-example-2', }, 'https://github.com/robcresswell/dems-example-3': { type: 'github', - repo: 'robcresswell/dems-example-3', + url: 'https://github.com/robcresswell/dems-example-3', }, 'git@github.com:robcresswell/dems-example-4.git': { type: 'github', - repo: 'robcresswell/dems-example-4', + url: 'https://github.com/robcresswell/dems-example-4', }, 'gitlab:robcresswell/dems-example-5': { type: 'gitlab', - repo: 'robcresswell/dems-example-5', + url: 'https://gitlab.com/robcresswell/dems-example-5', }, 'https://gitlab.com/robcresswell/dems-example-6': { type: 'gitlab', - repo: 'robcresswell/dems-example-6', + url: 'https://gitlab.com/robcresswell/dems-example-6', }, 'git@gitlab.com:robcresswell/dems-example-7.git': { type: 'gitlab', - repo: 'robcresswell/dems-example-7', + url: 'https://gitlab.com/robcresswell/dems-example-7', }, 'bitbucket:robcresswell/dems-example-8': { type: 'bitbucket', - repo: 'robcresswell/dems-example-8', + url: 'https://bitbucket.org/robcresswell/dems-example-8', }, 'https://bitbucket.org/robcresswell/dems-example-9': { type: 'bitbucket', - repo: 'robcresswell/dems-example-9', + url: 'https://bitbucket.org/robcresswell/dems-example-9', }, }; @@ -51,10 +51,10 @@ describe('get-scm-info', () => { describe('parses SCM info correctly', () => { Object.entries(validSourceFixtures).forEach(([sourceUrl, sourceInfo]) => { it(sourceUrl, () => { - const { type, repo } = getScmInfo(sourceUrl); + const { type, url } = getScmInfo(sourceUrl); expect(type).toEqual(sourceInfo.type); - expect(repo).toEqual(sourceInfo.repo); + expect(url).toEqual(sourceInfo.url); }); }); }); diff --git a/test/validate.test.ts b/test/validate.test.ts index 5c8c688..68f92f8 100644 --- a/test/validate.test.ts +++ b/test/validate.test.ts @@ -18,8 +18,21 @@ describe('validate', () => { }); it('retrieves an archive URL for GitHub', async () => { - const url = 'https://github.com/robcresswell/dems-example'; - const { archiveUrl } = await getValidConfig(url); + const descriptor = 'https://github.com/robcresswell/dems-example'; + const { archiveUrl } = await getValidConfig(descriptor); + + expect(pExecMock).toHaveBeenCalledTimes(1); + expect(pExecMock).toHaveBeenCalledWith( + 'git ls-remote https://github.com/robcresswell/dems-example', + ); + expect(archiveUrl).toBe( + 'https://github.com/robcresswell/dems-example/archive/0efcb30d0a12d6f00ff476aec0642cb6253d7a90.tar.gz', + ); + }); + + it('retrieves an archive URL for GitHub (shorthand)', async () => { + const descriptor = 'github:robcresswell/dems-example'; + const { archiveUrl } = await getValidConfig(descriptor); expect(pExecMock).toHaveBeenCalledTimes(1); expect(pExecMock).toHaveBeenCalledWith( @@ -31,8 +44,21 @@ describe('validate', () => { }); it('retrieves an archive URL for GitLab', async () => { - const url = 'https://gitlab.com/robcresswell/dems-example'; - const { archiveUrl } = await getValidConfig(url); + const descriptor = 'https://gitlab.com/robcresswell/dems-example'; + const { archiveUrl } = await getValidConfig(descriptor); + + expect(pExecMock).toHaveBeenCalledTimes(1); + expect(pExecMock).toHaveBeenCalledWith( + 'git ls-remote https://gitlab.com/robcresswell/dems-example', + ); + expect(archiveUrl).toBe( + 'https://gitlab.com/robcresswell/dems-example/repository/archive.tar.gz?ref=0efcb30d0a12d6f00ff476aec0642cb6253d7a90', + ); + }); + + it('retrieves an archive URL for GitLab (shorthand)', async () => { + const descriptor = 'gitlab:robcresswell/dems-example'; + const { archiveUrl } = await getValidConfig(descriptor); expect(pExecMock).toHaveBeenCalledTimes(1); expect(pExecMock).toHaveBeenCalledWith( @@ -44,8 +70,21 @@ describe('validate', () => { }); it('retrieves an archive URL for Bitbucket', async () => { - const url = 'https://bitbucket.org/robcresswell/dems-example'; - const { archiveUrl } = await getValidConfig(url); + const descriptor = 'https://bitbucket.org/robcresswell/dems-example'; + const { archiveUrl } = await getValidConfig(descriptor); + + expect(pExecMock).toHaveBeenCalledTimes(1); + expect(pExecMock).toHaveBeenCalledWith( + 'git ls-remote https://bitbucket.org/robcresswell/dems-example', + ); + expect(archiveUrl).toBe( + 'https://bitbucket.org/robcresswell/dems-example/get/0efcb30d0a12d6f00ff476aec0642cb6253d7a90.tar.gz', + ); + }); + + it('retrieves an archive URL for Bitbucket (shorthand)', async () => { + const descriptor = 'bitbucket:robcresswell/dems-example'; + const { archiveUrl } = await getValidConfig(descriptor); expect(pExecMock).toHaveBeenCalledTimes(1); expect(pExecMock).toHaveBeenCalledWith( diff --git a/yarn.lock b/yarn.lock index b5fa719..f2cf68c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -312,9 +312,9 @@ "@babel/types" "^7.0.0" "@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": - version "7.0.6" - resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.0.6.tgz#328dd1a8fc4cfe3c8458be9477b219ea158fd7b2" - integrity sha512-XYVgHF2sQ0YblLRMLNPB3CkFMewzFmlDsH/TneZFHUXDlABQgh88uOxuez7ZcXxayLFrqLwtDH1t+FmlFwNZxw== + version "7.0.7" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.0.7.tgz#2496e9ff56196cc1429c72034e07eab6121b6f3f" + integrity sha512-CeBpmX1J8kWLcDEnI3Cl2Eo6RfbGvzUctA+CjZUhOKDFbLfcr7fc4usEqLNWetrlJd7RhAkyYe2czXop4fICpw== dependencies: "@babel/types" "^7.3.0" @@ -360,12 +360,7 @@ resolved "https://registry.yarnpkg.com/@types/mustache/-/mustache-0.8.32.tgz#7db3b81f2bf450bd38805f596d20eca97c4ed595" integrity sha512-RTVWV485OOf4+nO2+feurk0chzHkSjkjALiejpHltyuMf/13fGymbbNNFrSKdSSUg1TIwzszXdWsVirxgqYiFA== -"@types/node@*": - version "12.0.7" - resolved "https://registry.yarnpkg.com/@types/node/-/node-12.0.7.tgz#4f2563bad652b2acb1722d7e7aae2b0ff62d192c" - integrity sha512-1YKeT4JitGgE4SOzyB9eMwO0nGVNkNEsm9qlIt1Lqm/tG2QEiSMTD4kS3aO6L+w5SClLVxALmIBESK6Mk5wX0A== - -"@types/node@^12.0.10": +"@types/node@*", "@types/node@^12.0.10": version "12.0.10" resolved "https://registry.yarnpkg.com/@types/node/-/node-12.0.10.tgz#51babf9c7deadd5343620055fc8aff7995c8b031" integrity sha512-LcsGbPomWsad6wmMNv7nBLw7YYYyfdYcz6xryKYQhx89c3XXan+8Q6AJ43G5XDIaklaVkK3mE4fCb0SBvMiPSQ== @@ -725,9 +720,9 @@ bs-logger@0.x: fast-json-stable-stringify "2.x" bser@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/bser/-/bser-2.0.0.tgz#9ac78d3ed5d915804fd87acb158bc797147a1719" - integrity sha1-mseNPtXZFYBP2HrLFYvHlxR6Fxk= + version "2.1.0" + resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.0.tgz#65fc784bf7f87c009b973c12db6546902fa9c7b5" + integrity sha512-8zsjWrQkkBoLK6uxASk1nJ2SKv97ltiGDo6A3wA0/yRPz+CwmEyDo0hUrhIuukG2JHpAl3bvFIixw2/3Hi0DOg== dependencies: node-int64 "^0.4.0" @@ -1466,9 +1461,9 @@ flat-cache@^2.0.1: write "1.0.3" flatted@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.0.tgz#55122b6536ea496b4b44893ee2608141d10d9916" - integrity sha512-R+H8IZclI8AAkSBRQJLVOsxwAoHd6WC40b4QTNWIjzAa6BXOBfQcM587MXDTVPeYaopFNWHUFLx7eNmHDSxMWg== + version "2.0.1" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.1.tgz#69e57caa8f0eacbc281d2e2cb458d46fdb449e08" + integrity sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg== for-in@^1.0.2: version "1.0.2" @@ -1756,9 +1751,9 @@ inflight@^1.0.4: wrappy "1" inherits@2, inherits@^2.0.3, inherits@~2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" - integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== ini@~1.3.0: version "1.3.5" @@ -1766,9 +1761,9 @@ ini@~1.3.0: integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== inquirer@^6.2.2: - version "6.4.0" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.4.0.tgz#6f9284047c4e48b76b169e46b3ae3b2171ce30a2" - integrity sha512-O3qJQ+fU/AI1K2y5/RjqefMEQTdJQf6sPTvyRA1bx6D634ADxcu97u6YOUciIeU2OWIuvpUsQs6Wx3Fdi3eFaQ== + version "6.4.1" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.4.1.tgz#7bd9e5ab0567cd23b41b0180b68e0cfa82fc3c0b" + integrity sha512-/Jw+qPZx4EDYsaT6uz7F4GJRNFMRdKNeUZw3ZnKV8lyuUgz/YWRCSUAJMZSVhSq4Ec0R2oYnyi6b3d4JXcL5Nw== dependencies: ansi-escapes "^3.2.0" chalk "^2.4.2" @@ -1924,7 +1919,7 @@ is-number@^3.0.0: dependencies: kind-of "^3.0.2" -is-plain-object@^2.0.1, is-plain-object@^2.0.3, is-plain-object@^2.0.4: +is-plain-object@^2.0.3, is-plain-object@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== @@ -2150,9 +2145,9 @@ jest-get-type@^24.8.0: integrity sha512-RR4fo8jEmMD9zSz2nLbs2j0zvPpk/KCEz3a62jJWbd2ayNo0cb+KFRxPHVhE4ZmgGJEQp0fosmNz84IfqM8cMQ== jest-haste-map@^24.8.0: - version "24.8.0" - resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-24.8.0.tgz#51794182d877b3ddfd6e6d23920e3fe72f305800" - integrity sha512-ZBPRGHdPt1rHajWelXdqygIDpJx8u3xOoLyUBWRW28r3tagrgoepPrzAozW7kW9HrQfhvmiv1tncsxqHJO1onQ== + version "24.8.1" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-24.8.1.tgz#f39cc1d2b1d907e014165b4bd5a957afcb992982" + integrity sha512-SwaxMGVdAZk3ernAx2Uv2sorA7jm3Kx+lR0grp6rMmnY06Kn/urtKx1LPN2mGTea4fCT38impYT28FfcLUhX0g== dependencies: "@jest/types" "^24.8.0" anymatch "^2.0.0" @@ -2747,9 +2742,9 @@ minizlib@^1.2.1: minipass "^2.2.1" mixin-deep@^1.2.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.1.tgz#a49e7268dce1a0d9698e45326c5626df3543d0fe" - integrity sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ== + version "1.3.2" + resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566" + integrity sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA== dependencies: for-in "^1.0.2" is-extendable "^1.0.1" @@ -3264,9 +3259,9 @@ pretty-format@^24.8.0: react-is "^16.8.4" process-nextick-args@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa" - integrity sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw== + version "2.0.1" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== progress@^2.0.0: version "2.0.3" @@ -3282,9 +3277,9 @@ prompts@^2.0.1: sisteransi "^1.0.0" psl@^1.1.24, psl@^1.1.28: - version "1.1.32" - resolved "https://registry.yarnpkg.com/psl/-/psl-1.1.32.tgz#3f132717cf2f9c169724b2b6caf373cf694198db" - integrity sha512-MHACAkHpihU/REGGPLj4sEfc/XKW2bheigvHO1dUqjaKigMp1C8+WLQYRGgeKFMsw5PMfegZcaN8IDXK/cD0+g== + version "1.1.33" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.1.33.tgz#5533d9384ca7aab86425198e10e8053ebfeab661" + integrity sha512-LTDP2uSrsc7XCb5lO7A8BI1qYxRe/8EqlRvMeEl6rsnYAqDOl8xHR+8lSAIVfrNaSAlTPTNOCgNjWcoUL3AZsw== pump@^3.0.0: version "3.0.0" @@ -3593,29 +3588,19 @@ semver@5.5.0: integrity sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA== semver@^6.0.0: - version "6.1.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.1.1.tgz#53f53da9b30b2103cd4f15eab3a18ecbcb210c9b" - integrity sha512-rWYq2e5iYW+fFe/oPPtYJxYgjBm8sC4rmoGdUOgBB7VnwKt6HrL793l2voH1UlsyYZpJ4g0wfjnTEO1s1NP2eQ== + version "6.1.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.1.2.tgz#079960381376a3db62eb2edc8a3bfb10c7cfe318" + integrity sha512-z4PqiCpomGtWj8633oeAdXm1Kn1W++3T8epkZYnwiVgIYIJ0QHszhInYSJTYxebByQH7KVCEAn8R9duzZW2PhQ== set-blocking@^2.0.0, set-blocking@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= -set-value@^0.4.3: - version "0.4.3" - resolved "https://registry.yarnpkg.com/set-value/-/set-value-0.4.3.tgz#7db08f9d3d22dc7f78e53af3c3bf4666ecdfccf1" - integrity sha1-fbCPnT0i3H945Trzw79GZuzfzPE= - dependencies: - extend-shallow "^2.0.1" - is-extendable "^0.1.1" - is-plain-object "^2.0.1" - to-object-path "^0.3.0" - -set-value@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.0.tgz#71ae4a88f0feefbbf52d1ea604f3fb315ebb6274" - integrity sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg== +set-value@^2.0.0, set-value@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" + integrity sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw== dependencies: extend-shallow "^2.0.1" is-extendable "^0.1.1" @@ -3897,9 +3882,9 @@ supports-color@^6.1.0: has-flag "^3.0.0" symbol-tree@^3.2.2: - version "3.2.2" - resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.2.tgz#ae27db38f660a7ae2e1c3b7d1bc290819b8519e6" - integrity sha1-rifbOPZgp64uHDt9G8KQgZuFGeY= + version "3.2.4" + resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" + integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== table@^5.2.3: version "5.4.1" @@ -4100,14 +4085,14 @@ uglify-js@^3.1.4: source-map "~0.6.1" union-value@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.0.tgz#5c71c34cb5bad5dcebe3ea0cd08207ba5aa1aea4" - integrity sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ= + version "1.0.1" + resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" + integrity sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg== dependencies: arr-union "^3.1.0" get-value "^2.0.6" is-extendable "^0.1.1" - set-value "^0.4.3" + set-value "^2.0.1" unset-value@^1.0.0: version "1.0.0"