Repository: Rich-Harris/degit
Branch: master
Commit: 64b80577acf3
Files: 21
Total size: 38.6 KB
Directory structure:
gitextract_a5mbvtuo/
├── .dependabot/
│ └── config.yml
├── .editorconfig
├── .eslintrc.json
├── .github/
│ └── workflows/
│ └── nodejs.yml
├── .gitignore
├── .npmrc
├── .prettierrc
├── .travis.yml
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── LICENSE.md
├── README.md
├── appveyor.yml
├── degit
├── help.md
├── package.json
├── rollup.config.js
├── src/
│ ├── bin.js
│ ├── index.js
│ └── utils.js
└── test/
└── test.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .dependabot/config.yml
================================================
version: 1
update_configs:
- package_manager: 'javascript'
directory: '/'
update_schedule: 'weekly'
target_branch: 'master'
================================================
FILE: .editorconfig
================================================
# editorconfig.org
root = true
[*]
charset = utf-8
indent_size = 2
indent_style = tab
end_of_line = lf
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
indent_style = tab
trim_trailing_whitespace = false
[{*.json,*.yml}]
indent_style = tab
================================================
FILE: .eslintrc.json
================================================
{
"root": true,
"rules": {
"no-cond-assign": 0,
"no-unused-vars": 2,
"object-shorthand": [2, "always"],
"no-console": 0,
"no-const-assign": 2,
"no-class-assign": 2,
"no-this-before-super": 2,
"no-var": 2,
"no-unreachable": 2,
"valid-typeof": 2,
"one-var": [2, "never"],
"prefer-arrow-callback": 2,
"prefer-const": [2, { "destructuring": "all" }],
"no-inner-declarations": 0
},
"env": {
"es6": true,
"node": true,
"mocha": true
},
"extends": [
"eslint:recommended",
"plugin:import/errors",
"plugin:import/warnings",
"prettier"
],
"parserOptions": {
"ecmaVersion": 8,
"sourceType": "module"
}
}
================================================
FILE: .github/workflows/nodejs.yml
================================================
name: Node.js CI
on: [push]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [8.x, 10.x, 12.x]
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
- run: npm run build
- run: npm test
- run: npm run lint
env:
CI: true
================================================
FILE: .gitignore
================================================
.DS_Store
node_modules
.tmp
dist
================================================
FILE: .npmrc
================================================
save-exact=true
================================================
FILE: .prettierrc
================================================
{
"arrowParens": "avoid",
"bracketSpacing": true,
"endOfLine": "lf",
"htmlWhitespaceSensitivity": "css",
"insertPragma": false,
"printWidth": 80,
"proseWrap": "preserve",
"requirePragma": false,
"quoteProps": "as-needed",
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": false,
"useTabs": true
}
================================================
FILE: .travis.yml
================================================
language: node_js
node_js:
- '8'
- '10'
- lts/*
- node
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- libstdc++-4.9-dev
script:
- npm run lint
- npm test
================================================
FILE: CHANGELOG.md
================================================
# degit changelog
## 2.8.4
* Whoops
## 2.8.3
* Reinstate `#!/usr/bin/env node` ([#273](https://github.com/Rich-Harris/degit/issues/273))
## 2.8.2
* Fix `bin`/`main` locations ([#273](https://github.com/Rich-Harris/degit/issues/273))
* Update dependencies
## 2.8.1
* Use `HEAD` instead of `master` ([#243](https://github.com/Rich-Harris/degit/pull/243)])
## 2.8.0
* Sort by recency in interactive mode
## 2.7.0
* Bundle for a faster install
## 2.6.0
* Add an interactive mode ([#4](https://github.com/Rich-Harris/degit/issues/4))
## 2.5.0
* Add `--mode=git` for cloning private repos ([#29](https://github.com/Rich-Harris/degit/pull/29))
## 2.4.0
* Clone subdirectories from repos (`user/repo/subdir`)
## 2.3.0
* Support HTTPS proxying where `https_proxy` env var is supplied ([#26](https://github.com/Rich-Harris/degit/issues/26))
## 2.2.2
- Improve CLI error logging ([#49](https://github.com/Rich-Harris/degit/pull/49))
## 2.2.1
- Update `help.md` for Sourcehut support
## 2.2.0
- Sourcehut support ([#85](https://github.com/Rich-Harris/degit/pull/85))
## 2.1.4
- Fix actions ([#65](https://github.com/Rich-Harris/degit/pull/65))
- Improve CLI error logging ([#46](https://github.com/Rich-Harris/degit/pull/46))
## 2.1.3
- Install `sander` ([#34](https://github.com/Rich-Harris/degit/issues/34))
## 2.1.2
- Remove `console.log`
## 2.1.1
- Oops, managed to publish 2.1.0 without building
## 2.1.0
- Add actions ([#28](https://github.com/Rich-Harris/degit/pull/28))
## 2.0.2
- Allow flags like `-v` before argument ([#25](https://github.com/Rich-Harris/degit/issues/25))
## 2.0.1
- Update node-tar for Node 9 compatibility
## 2.0.0
- Expose API for use in Node scripts ([#23](https://github.com/Rich-Harris/degit/issues/23))
## 1.2.2
- Fix `files` in package.json
## 1.2.1
- Add `engines` field ([#17](https://github.com/Rich-Harris/degit/issues/17))
## 1.2.0
- Windows support ([#1](https://github.com/Rich-Harris/degit/issues/1))
- Offline support and `--cache` flag ([#8](https://github.com/Rich-Harris/degit/issues/8))
- `degit --help` ([#5](https://github.com/Rich-Harris/degit/issues/5))
- `--verbose` flag
## 1.1.0
- Use HTTPS, not SSH ([#11](https://github.com/Rich-Harris/degit/issues/11))
## 1.0.0
- First release
================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at yogiboaron@gmail.com. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq
================================================
FILE: LICENSE.md
================================================
Copyright (c) 2019 [these people](https://github.com/Rich-Harris/degit/graphs/contributors)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
================================================
FILE: README.md
================================================
# degit — straightforward project scaffolding
[](https://travis-ci.org/Rich-Harris/degit)
[](https://ci.appveyor.com/project/Rich-Harris/degit/branch/master)
[](https://snyk.io/test/npm/degit)
[](https://packagephobia.now.sh/result?p=degit)
[](https://npm.im/degit)
[](CODE_OF_CONDUCT.md)
[](http://makeapullrequest.com)
**degit** makes copies of git repositories. When you run `degit some-user/some-repo`, it will find the latest commit on https://github.com/some-user/some-repo and download the associated tar file to `~/.degit/some-user/some-repo/commithash.tar.gz` if it doesn't already exist locally. (This is much quicker than using `git clone`, because you're not downloading the entire git history.)
_Requires Node 8 or above, because `async` and `await` are the cat's pyjamas_
## Installation
```bash
npm install -g degit
```
## Usage
### Basics
The simplest use of degit is to download the master branch of a repo from GitHub to the current working directory:
```bash
degit user/repo
# these commands are equivalent
degit github:user/repo
degit git@github.com:user/repo
degit https://github.com/user/repo
```
Or you can download from GitLab and BitBucket:
```bash
# download from GitLab
degit gitlab:user/repo
degit git@gitlab.com:user/repo
degit https://gitlab.com/user/repo
# download from BitBucket
degit bitbucket:user/repo
degit git@bitbucket.org:user/repo
degit https://bitbucket.org/user/repo
# download from Sourcehut
degit git.sr.ht/user/repo
degit git@git.sr.ht:user/repo
degit https://git.sr.ht/user/repo
```
### Specify a tag, branch or commit
The default branch is `master`.
```bash
degit user/repo#dev # branch
degit user/repo#v1.2.3 # release tag
degit user/repo#1234abcd # commit hash
````
### Create a new folder for the project
If the second argument is omitted, the repo will be cloned to the current directory.
```bash
degit user/repo my-new-project
```
### Specify a subdirectory
To clone a specific subdirectory instead of the entire repo, just add it to the argument:
```bash
degit user/repo/subdirectory
```
### HTTPS proxying
If you have an `https_proxy` environment variable, Degit will use it.
### Private repositories
Private repos can be cloned by specifying `--mode=git` (the default is `tar`). In this mode, Degit will use `git` under the hood. It's much slower than fetching a tarball, which is why it's not the default.
Note: this clones over SSH, not HTTPS.
### See all options
```bash
degit --help
```
## Not supported
- Private repositories
Pull requests are very welcome!
## Wait, isn't this just `git clone --depth 1`?
A few salient differences:
- If you `git clone`, you get a `.git` folder that pertains to the project template, rather than your project. You can easily forget to re-init the repository, and end up confusing yourself
- Caching and offline support (if you already have a `.tar.gz` file for a specific commit, you don't need to fetch it again).
- Less to type (`degit user/repo` instead of `git clone --depth 1 git@github.com:user/repo`)
- Composability via [actions](#actions)
- Future capabilities — [interactive mode](https://github.com/Rich-Harris/degit/issues/4), [friendly onboarding and postinstall scripts](https://github.com/Rich-Harris/degit/issues/6)
## JavaScript API
You can also use degit inside a Node script:
```js
const degit = require('degit');
const emitter = degit('user/repo', {
cache: true,
force: true,
verbose: true,
});
emitter.on('info', info => {
console.log(info.message);
});
emitter.clone('path/to/dest').then(() => {
console.log('done');
});
```
## Actions
You can manipulate repositories after they have been cloned with _actions_, specified in a `degit.json` file that lives at the top level of the working directory. Currently, there are two actions — `clone` and `remove`. Additional actions may be added in future.
### clone
```json
// degit.json
[
{
"action": "clone",
"src": "user/another-repo"
}
]
```
This will clone `user/another-repo`, preserving the contents of the existing working directory. This allows you to, say, add a new README.md or starter file to a repo that you do not control. The cloned repo can contain its own `degit.json` actions.
### remove
```json
// degit.json
[
{
"action": "remove",
"files": ["LICENSE"]
}
]
```
Remove a file at the specified path.
## See also
- [zel](https://github.com/vutran/zel) by [Vu Tran](https://twitter.com/tranvu)
- [gittar](https://github.com/lukeed/gittar) by [Luke Edwards](https://twitter.com/lukeed05)
## License
[MIT](LICENSE.md).
================================================
FILE: appveyor.yml
================================================
# http://www.appveyor.com/docs/appveyor-yml
version: '{build}'
clone_depth: 10
init:
- git config --global core.autocrlf false
environment:
matrix:
# node.js
- nodejs_version: 8
install:
- ps: Install-Product node $env:nodejs_version
- npm install
build: off
test_script:
- node --version && npm --version
- npm run lint
- npm test
matrix:
fast_finish: false
# cache:
# - C:\Users\appveyor\AppData\Roaming\npm-cache -> package.json # npm cache
# - node_modules -> package.json # local npm modules
================================================
FILE: degit
================================================
#!/usr/bin/env node
require('./dist/bin.js');
================================================
FILE: help.md
================================================
# _degit_
Usage:
`degit <src>[#ref] [<dest>] [options]`
Fetches the `src` repo, and extracts it to `dest` (or the current directory).
The `src` argument can be any of the following:
## GitHub repos
user/repo
github:user/repo
https://github.com/user/repo
## GitLab repos
gitlab:user/repo
https://gitlab.com/user/repo
## BitBucket repos
bitbucket:user/repo
https://bitbucket.com/user/repo
## Sourcehut repos
git.sr.ht/user/repo
git@git.sr.ht:user/repo
https://git.sr.ht/user/repo
You can append a #ref to any of the above:
## Branches (defaults to master)
user/repo#dev
## Tags
user/repo#v1.2.3
## Commit hashes
user/repo#abcd1234
The `dest` directory (or the current directory, if unspecified) must be empty
unless the `--force` option is used.
Options:
`--help`, `-h` Show this message
`--cache`, `-c` Only use local cache
`--force`, `-f` Allow non-empty destination directory
`--verbose`, `-v` Extra logging
`--mode=`, `-m=` Force the mode by which degit clones the repo
Valid options are `tar` or `git` (uses SSH)
See https://github.com/Rich-Harris/degit for more information
================================================
FILE: package.json
================================================
{
"name": "degit",
"version": "2.8.4",
"engines": {
"node": ">=8.0.0"
},
"description": "Straightforward project scaffolding",
"main": "dist/index.js",
"bin": {
"degit": "degit"
},
"scripts": {
"lint": "eslint --color --ignore-path .gitignore .",
"dev": "npm run build -- --watch",
"build": "rollup -c",
"test": "mocha",
"pretest": "npm run build",
"prepublishOnly": "npm test"
},
"repository": {
"type": "git",
"url": "git+https://github.com/Rich-Harris/degit.git"
},
"keywords": [
"scaffolding",
"template",
"git"
],
"author": "Rich Harris",
"license": "MIT",
"bugs": {
"url": "https://github.com/Rich-Harris/degit/issues"
},
"homepage": "https://github.com/Rich-Harris/degit#readme",
"devDependencies": {
"@rollup/plugin-commonjs": "18.0.0",
"@rollup/plugin-node-resolve": "11.2.1",
"chalk": "4.1.0",
"enquirer": "2.3.6",
"eslint": "7.23.0",
"eslint-config-prettier": "8.1.0",
"eslint-plugin-import": "2.22.1",
"fuzzysearch": "1.0.3",
"home-or-tmp": "3.0.0",
"https-proxy-agent": "5.0.0",
"husky": "6.0.0",
"lint-staged": "10.5.4",
"mocha": "8.3.2",
"mri": "1.1.6",
"prettier": "2.2.1",
"rimraf": "3.0.2",
"rollup": "2.44.0",
"rollup-plugin-commonjs": "10.1.0",
"sander": "0.6.0",
"source-map-support": "0.5.19",
"tar": "6.1.0",
"tiny-glob": "0.2.8"
},
"files": [
"help.md",
"dist"
],
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"*.{js}": [
"eslint --fix",
"git add"
],
"*.{js, json, yml, md}": [
"prettier --write",
"git add"
]
}
}
================================================
FILE: rollup.config.js
================================================
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import { builtinModules } from 'module';
import pkg from './package.json';
export default {
input: {
index: 'src/index.js',
bin: 'src/bin.js'
},
output: {
dir: 'dist',
entryFileNames: '[name].js',
chunkFileNames: '[name]-[hash].js',
format: 'cjs',
exports: 'auto',
sourcemap: true
},
external: Object.keys(pkg.dependencies || {}).concat(builtinModules),
plugins: [resolve(), commonjs()]
};
================================================
FILE: src/bin.js
================================================
import fs from 'fs';
import path from 'path';
import chalk from 'chalk';
import mri from 'mri';
import glob from 'tiny-glob/sync.js';
import fuzzysearch from 'fuzzysearch';
import enquirer from 'enquirer';
import degit from './index.js';
import { tryRequire, base } from './utils.js';
const args = mri(process.argv.slice(2), {
alias: {
f: 'force',
c: 'cache',
v: 'verbose',
m: 'mode'
},
boolean: ['force', 'cache', 'verbose']
});
const [src, dest = '.'] = args._;
async function main() {
if (args.help) {
const help = fs
.readFileSync(path.join(__dirname, 'help.md'), 'utf-8')
.replace(/^(\s*)#+ (.+)/gm, (m, s, _) => s + chalk.bold(_))
.replace(/_([^_]+)_/g, (m, _) => chalk.underline(_))
.replace(/`([^`]+)`/g, (m, _) => chalk.cyan(_));
process.stdout.write(`\n${help}\n`);
} else if (!src) {
// interactive mode
const accessLookup = new Map();
glob(`**/access.json`, { cwd: base }).forEach(file => {
const [host, user, repo] = file.split(path.sep);
const json = fs.readFileSync(`${base}/${file}`, 'utf-8');
const logs = JSON.parse(json);
Object.entries(logs).forEach(([ref, timestamp]) => {
const id = `${host}:${user}/${repo}#${ref}`;
accessLookup.set(id, new Date(timestamp).getTime());
});
});
const getChoice = file => {
const [host, user, repo] = file.split(path.sep);
return Object.entries(tryRequire(`${base}/${file}`)).map(
([ref, hash]) => ({
name: hash,
message: `${host}:${user}/${repo}#${ref}`,
value: `${host}:${user}/${repo}#${ref}`
})
);
};
const choices = glob(`**/map.json`, { cwd: base })
.map(getChoice)
.reduce(
(accumulator, currentValue) => accumulator.concat(currentValue),
[]
)
.sort((a, b) => {
const aTime = accessLookup.get(a.value) || 0;
const bTime = accessLookup.get(b.value) || 0;
return bTime - aTime;
});
const options = await enquirer.prompt([
{
type: 'autocomplete',
name: 'src',
message: 'Repo to clone?',
suggest: (input, choices) =>
choices.filter(({ value }) => fuzzysearch(input, value)),
choices
},
{
type: 'input',
name: 'dest',
message: 'Destination directory?',
initial: '.'
},
{
type: 'toggle',
name: 'cache',
message: 'Use cached version?'
}
]);
const empty =
!fs.existsSync(options.dest) || fs.readdirSync(options.dest).length === 0;
if (!empty) {
const { force } = await enquirer.prompt([
{
type: 'toggle',
name: 'force',
message: 'Overwrite existing files?'
}
]);
if (!force) {
console.error(chalk.magenta(`! Directory not empty — aborting`));
return;
}
}
run(options.src, options.dest, {
force: true,
cache: options.cache
});
} else {
run(src, dest, args);
}
}
function run(src, dest, args) {
const d = degit(src, args);
d.on('info', event => {
console.error(chalk.cyan(`> ${event.message.replace('options.', '--')}`));
});
d.on('warn', event => {
console.error(
chalk.magenta(`! ${event.message.replace('options.', '--')}`)
);
});
d.clone(dest).catch(err => {
console.error(chalk.red(`! ${err.message.replace('options.', '--')}`));
process.exit(1);
});
}
main();
================================================
FILE: src/index.js
================================================
import fs from 'fs';
import path from 'path';
import tar from 'tar';
import EventEmitter from 'events';
import chalk from 'chalk';
import { rimrafSync } from 'sander';
import {
DegitError,
exec,
fetch,
mkdirp,
tryRequire,
stashFiles,
unstashFiles,
degitConfigName,
base
} from './utils.js';
const validModes = new Set(['tar', 'git']);
export default function degit(src, opts) {
return new Degit(src, opts);
}
class Degit extends EventEmitter {
constructor(src, opts = {}) {
super();
this.src = src;
this.cache = opts.cache;
this.force = opts.force;
this.verbose = opts.verbose;
this.proxy = process.env.https_proxy; // TODO allow setting via --proxy
this.repo = parse(src);
this.mode = opts.mode || this.repo.mode;
if (!validModes.has(this.mode)) {
throw new Error(`Valid modes are ${Array.from(validModes).join(', ')}`);
}
this._hasStashed = false;
this.directiveActions = {
clone: async (dir, dest, action) => {
if (this._hasStashed === false) {
stashFiles(dir, dest);
this._hasStashed = true;
}
const opts = Object.assign(
{ force: true },
{ cache: action.cache, verbose: action.verbose }
);
const d = degit(action.src, opts);
d.on('info', event => {
console.error(
chalk.cyan(`> ${event.message.replace('options.', '--')}`)
);
});
d.on('warn', event => {
console.error(
chalk.magenta(`! ${event.message.replace('options.', '--')}`)
);
});
await d.clone(dest).catch(err => {
console.error(chalk.red(`! ${err.message}`));
process.exit(1);
});
},
remove: this.remove.bind(this)
};
}
_getDirectives(dest) {
const directivesPath = path.resolve(dest, degitConfigName);
const directives =
tryRequire(directivesPath, { clearCache: true }) || false;
if (directives) {
fs.unlinkSync(directivesPath);
}
return directives;
}
async clone(dest) {
this._checkDirIsEmpty(dest);
const { repo } = this;
const dir = path.join(base, repo.site, repo.user, repo.name);
if (this.mode === 'tar') {
await this._cloneWithTar(dir, dest);
} else {
await this._cloneWithGit(dir, dest);
}
this._info({
code: 'SUCCESS',
message: `cloned ${chalk.bold(repo.user + '/' + repo.name)}#${chalk.bold(
repo.ref
)}${dest !== '.' ? ` to ${dest}` : ''}`,
repo,
dest
});
const directives = this._getDirectives(dest);
if (directives) {
for (const d of directives) {
// TODO, can this be a loop with an index to pass for better error messages?
await this.directiveActions[d.action](dir, dest, d);
}
if (this._hasStashed === true) {
unstashFiles(dir, dest);
}
}
}
remove(dir, dest, action) {
let files = action.files;
if (!Array.isArray(files)) {
files = [files];
}
const removedFiles = files
.map(file => {
const filePath = path.resolve(dest, file);
if (fs.existsSync(filePath)) {
const isDir = fs.lstatSync(filePath).isDirectory();
if (isDir) {
rimrafSync(filePath);
return file + '/';
} else {
fs.unlinkSync(filePath);
return file;
}
} else {
this._warn({
code: 'FILE_DOES_NOT_EXIST',
message: `action wants to remove ${chalk.bold(
file
)} but it does not exist`
});
return null;
}
})
.filter(d => d);
if (removedFiles.length > 0) {
this._info({
code: 'REMOVED',
message: `removed: ${chalk.bold(
removedFiles.map(d => chalk.bold(d)).join(', ')
)}`
});
}
}
_checkDirIsEmpty(dir) {
try {
const files = fs.readdirSync(dir);
if (files.length > 0) {
if (this.force) {
this._info({
code: 'DEST_NOT_EMPTY',
message: `destination directory is not empty. Using options.force, continuing`
});
} else {
throw new DegitError(
`destination directory is not empty, aborting. Use options.force to override`,
{
code: 'DEST_NOT_EMPTY'
}
);
}
} else {
this._verbose({
code: 'DEST_IS_EMPTY',
message: `destination directory is empty`
});
}
} catch (err) {
if (err.code !== 'ENOENT') throw err;
}
}
_info(info) {
this.emit('info', info);
}
_warn(info) {
this.emit('warn', info);
}
_verbose(info) {
if (this.verbose) this._info(info);
}
async _getHash(repo, cached) {
try {
const refs = await fetchRefs(repo);
if (repo.ref === 'HEAD') {
return refs.find(ref => ref.type === 'HEAD').hash;
}
return this._selectRef(refs, repo.ref);
} catch (err) {
this._warn(err);
this._verbose(err.original);
return this._getHashFromCache(repo, cached);
}
}
_getHashFromCache(repo, cached) {
if (repo.ref in cached) {
const hash = cached[repo.ref];
this._info({
code: 'USING_CACHE',
message: `using cached commit hash ${hash}`
});
return hash;
}
}
_selectRef(refs, selector) {
for (const ref of refs) {
if (ref.name === selector) {
this._verbose({
code: 'FOUND_MATCH',
message: `found matching commit hash: ${ref.hash}`
});
return ref.hash;
}
}
if (selector.length < 8) return null;
for (const ref of refs) {
if (ref.hash.startsWith(selector)) return ref.hash;
}
}
async _cloneWithTar(dir, dest) {
const { repo } = this;
const cached = tryRequire(path.join(dir, 'map.json')) || {};
const hash = this.cache
? this._getHashFromCache(repo, cached)
: await this._getHash(repo, cached);
const subdir = repo.subdir ? `${repo.name}-${hash}${repo.subdir}` : null;
if (!hash) {
// TODO 'did you mean...?'
throw new DegitError(`could not find commit hash for ${repo.ref}`, {
code: 'MISSING_REF',
ref: repo.ref
});
}
const file = `${dir}/${hash}.tar.gz`;
const url =
repo.site === 'gitlab'
? `${repo.url}/repository/archive.tar.gz?ref=${hash}`
: repo.site === 'bitbucket'
? `${repo.url}/get/${hash}.tar.gz`
: `${repo.url}/archive/${hash}.tar.gz`;
try {
if (!this.cache) {
try {
fs.statSync(file);
this._verbose({
code: 'FILE_EXISTS',
message: `${file} already exists locally`
});
} catch (err) {
mkdirp(path.dirname(file));
if (this.proxy) {
this._verbose({
code: 'PROXY',
message: `using proxy ${this.proxy}`
});
}
this._verbose({
code: 'DOWNLOADING',
message: `downloading ${url} to ${file}`
});
await fetch(url, file, this.proxy);
}
}
} catch (err) {
throw new DegitError(`could not download ${url}`, {
code: 'COULD_NOT_DOWNLOAD',
url,
original: err
});
}
updateCache(dir, repo, hash, cached);
this._verbose({
code: 'EXTRACTING',
message: `extracting ${
subdir ? repo.subdir + ' from ' : ''
}${file} to ${dest}`
});
mkdirp(dest);
await untar(file, dest, subdir);
}
async _cloneWithGit(dir, dest) {
await exec(`git clone ${this.repo.ssh} ${dest}`);
await exec(`rm -rf ${path.resolve(dest, '.git')}`);
}
}
const supported = new Set(['github', 'gitlab', 'bitbucket', 'git.sr.ht']);
function parse(src) {
const match = /^(?:(?:https:\/\/)?([^:/]+\.[^:/]+)\/|git@([^:/]+)[:/]|([^/]+):)?([^/\s]+)\/([^/\s#]+)(?:((?:\/[^/\s#]+)+))?(?:\/)?(?:#(.+))?/.exec(
src
);
if (!match) {
throw new DegitError(`could not parse ${src}`, {
code: 'BAD_SRC'
});
}
const site = (match[1] || match[2] || match[3] || 'github').replace(
/\.(com|org)$/,
''
);
if (!supported.has(site)) {
throw new DegitError(
`degit supports GitHub, GitLab, Sourcehut and BitBucket`,
{
code: 'UNSUPPORTED_HOST'
}
);
}
const user = match[4];
const name = match[5].replace(/\.git$/, '');
const subdir = match[6];
const ref = match[7] || 'HEAD';
const domain = `${site}.${
site === 'bitbucket' ? 'org' : site === 'git.sr.ht' ? '' : 'com'
}`;
const url = `https://${domain}/${user}/${name}`;
const ssh = `git@${domain}:${user}/${name}`;
const mode = supported.has(site) ? 'tar' : 'git';
return { site, user, name, ref, url, ssh, subdir, mode };
}
async function untar(file, dest, subdir = null) {
return tar.extract(
{
file,
strip: subdir ? subdir.split('/').length : 1,
C: dest
},
subdir ? [subdir] : []
);
}
async function fetchRefs(repo) {
try {
const { stdout } = await exec(`git ls-remote ${repo.url}`);
return stdout
.split('\n')
.filter(Boolean)
.map(row => {
const [hash, ref] = row.split('\t');
if (ref === 'HEAD') {
return {
type: 'HEAD',
hash
};
}
const match = /refs\/(\w+)\/(.+)/.exec(ref);
if (!match)
throw new DegitError(`could not parse ${ref}`, {
code: 'BAD_REF'
});
return {
type:
match[1] === 'heads'
? 'branch'
: match[1] === 'refs'
? 'ref'
: match[1],
name: match[2],
hash
};
});
} catch (error) {
throw new DegitError(`could not fetch remote ${repo.url}`, {
code: 'COULD_NOT_FETCH',
url: repo.url,
original: error
});
}
}
function updateCache(dir, repo, hash, cached) {
// update access logs
const logs = tryRequire(path.join(dir, 'access.json')) || {};
logs[repo.ref] = new Date().toISOString();
fs.writeFileSync(
path.join(dir, 'access.json'),
JSON.stringify(logs, null, ' ')
);
if (cached[repo.ref] === hash) return;
const oldHash = cached[repo.ref];
if (oldHash) {
let used = false;
for (const key in cached) {
if (cached[key] === hash) {
used = true;
break;
}
}
if (!used) {
// we no longer need this tar file
try {
fs.unlinkSync(path.join(dir, `${oldHash}.tar.gz`));
} catch (err) {
// ignore
}
}
}
cached[repo.ref] = hash;
fs.writeFileSync(
path.join(dir, 'map.json'),
JSON.stringify(cached, null, ' ')
);
}
================================================
FILE: src/utils.js
================================================
import fs from 'fs';
import path from 'path';
import homeOrTmp from 'home-or-tmp';
import https from 'https';
import child_process from 'child_process';
import URL from 'url';
import Agent from 'https-proxy-agent';
import { rimrafSync, copydirSync } from 'sander';
const tmpDirName = 'tmp';
const degitConfigName = 'degit.json';
export { degitConfigName };
export class DegitError extends Error {
constructor(message, opts) {
super(message);
Object.assign(this, opts);
}
}
export function tryRequire(file, opts) {
try {
if (opts && opts.clearCache === true) {
delete require.cache[require.resolve(file)];
}
return require(file);
} catch (err) {
return null;
}
}
export function exec(command) {
return new Promise((fulfil, reject) => {
child_process.exec(command, (err, stdout, stderr) => {
if (err) {
reject(err);
return;
}
fulfil({ stdout, stderr });
});
});
}
export function mkdirp(dir) {
const parent = path.dirname(dir);
if (parent === dir) return;
mkdirp(parent);
try {
fs.mkdirSync(dir);
} catch (err) {
if (err.code !== 'EEXIST') throw err;
}
}
export function fetch(url, dest, proxy) {
return new Promise((fulfil, reject) => {
let options = url;
if (proxy) {
const parsedUrl = URL.parse(url);
options = {
hostname: parsedUrl.host,
path: parsedUrl.path,
agent: new Agent(proxy)
};
}
https
.get(options, response => {
const code = response.statusCode;
if (code >= 400) {
reject({ code, message: response.statusMessage });
} else if (code >= 300) {
fetch(response.headers.location, dest, proxy).then(fulfil, reject);
} else {
response
.pipe(fs.createWriteStream(dest))
.on('finish', () => fulfil())
.on('error', reject);
}
})
.on('error', reject);
});
}
export function stashFiles(dir, dest) {
const tmpDir = path.join(dir, tmpDirName);
rimrafSync(tmpDir);
mkdirp(tmpDir);
fs.readdirSync(dest).forEach(file => {
const filePath = path.join(dest, file);
const targetPath = path.join(tmpDir, file);
const isDir = fs.lstatSync(filePath).isDirectory();
if (isDir) {
copydirSync(filePath).to(targetPath);
rimrafSync(filePath);
} else {
fs.copyFileSync(filePath, targetPath);
fs.unlinkSync(filePath);
}
});
}
export function unstashFiles(dir, dest) {
const tmpDir = path.join(dir, tmpDirName);
fs.readdirSync(tmpDir).forEach(filename => {
const tmpFile = path.join(tmpDir, filename);
const targetPath = path.join(dest, filename);
const isDir = fs.lstatSync(tmpFile).isDirectory();
if (isDir) {
copydirSync(tmpFile).to(targetPath);
rimrafSync(tmpFile);
} else {
if (filename !== 'degit.json') {
fs.copyFileSync(tmpFile, targetPath);
}
fs.unlinkSync(tmpFile);
}
});
rimrafSync(tmpDir);
}
export const base = path.join(homeOrTmp, '.degit');
================================================
FILE: test/test.js
================================================
require('source-map-support').install();
const fs = require('fs');
const path = require('path');
const glob = require('tiny-glob/sync');
const rimraf = require('rimraf').sync;
const assert = require('assert');
const child_process = require('child_process');
const degit = require('../dist/index.js');
const degitPath = path.resolve('dist/bin.js');
const timeout = 30000;
function exec(cmd) {
return new Promise((fulfil, reject) => {
child_process.exec(cmd, (err, stdout, stderr) => {
if (err) return reject(err);
console.log(stdout);
console.error(stderr);
fulfil();
});
});
}
describe('degit', function() {
this.timeout(timeout);
function compare(dir, files) {
const expected = glob('**', { cwd: dir });
assert.deepEqual(Object.keys(files).sort(), expected.sort());
expected.forEach(file => {
if (!fs.lstatSync(`${dir}/${file}`).isDirectory()) {
assert.equal(files[file].trim(), read(`${dir}/${file}`).trim());
}
});
}
beforeEach(async () => await rimraf('.tmp'));
afterEach(async () => await rimraf('.tmp'));
describe('github', () => {
[
'mhkeller/degit-test-repo-compose',
'Rich-Harris/degit-test-repo',
'github:Rich-Harris/degit-test-repo',
'git@github.com:Rich-Harris/degit-test-repo',
'https://github.com/Rich-Harris/degit-test-repo.git'
].forEach(src => {
it(src, async () => {
await exec(`node ${degitPath} ${src} .tmp/test-repo -v`);
compare(`.tmp/test-repo`, {
'file.txt': 'hello from github!',
subdir: null,
'subdir/file.txt': 'hello from a subdirectory!'
});
});
});
});
describe('gitlab', () => {
[
'gitlab:Rich-Harris/degit-test-repo',
'git@gitlab.com:Rich-Harris/degit-test-repo',
'https://gitlab.com/Rich-Harris/degit-test-repo.git'
].forEach(src => {
it(src, async () => {
await exec(`node ${degitPath} ${src} .tmp/test-repo -v`);
compare(`.tmp/test-repo`, {
'file.txt': 'hello from gitlab!'
});
});
});
});
describe('bitbucket', () => {
[
'bitbucket:Rich_Harris/degit-test-repo',
'git@bitbucket.org:Rich_Harris/degit-test-repo',
'https://bitbucket.org/Rich_Harris/degit-test-repo.git'
].forEach(src => {
it(src, async () => {
await exec(`node ${degitPath} ${src} .tmp/test-repo -v`);
compare(`.tmp/test-repo`, {
'file.txt': 'hello from bitbucket'
});
});
});
});
describe('Sourcehut', () => {
[
'git.sr.ht/~satotake/degit-test-repo',
'https://git.sr.ht/~satotake/degit-test-repo',
'git@git.sr.ht:~satotake/degit-test-repo'
].forEach(src => {
it(src, async () => {
await exec(`node ${degitPath} ${src} .tmp/test-repo -v`);
compare(`.tmp/test-repo`, {
'file.txt': 'hello from sourcehut!'
});
});
});
});
describe('Subdirectories', () => {
[
'Rich-Harris/degit-test-repo/subdir',
'github:Rich-Harris/degit-test-repo/subdir',
'git@github.com:Rich-Harris/degit-test-repo/subdir',
'https://github.com/Rich-Harris/degit-test-repo.git/subdir'
].forEach(src => {
it(src, async () => {
await exec(`node ${degitPath} ${src} .tmp/test-repo -v`);
compare(`.tmp/test-repo`, {
'file.txt': 'hello from a subdirectory!'
});
});
});
});
describe('non-empty directories', () => {
it('fails without --force', async () => {
let succeeded;
try {
await exec(`mkdir -p .tmp/test-repo`);
await exec(`echo "not empty" > .tmp/test-repo/file.txt`);
await exec(
`node ${degitPath} Rich-Harris/degit-test-repo .tmp/test-repo -v`
);
succeeded = true;
} catch (err) {
assert.ok(/destination directory is not empty/.test(err.message));
}
assert.ok(!succeeded);
});
it('succeeds with --force', async () => {
await exec(
`node ${degitPath} Rich-Harris/degit-test-repo .tmp/test-repo -fv`
);
});
});
describe('command line arguments', () => {
it('allows flags wherever', async () => {
await exec(
`node ${degitPath} -v Rich-Harris/degit-test-repo .tmp/test-repo`
);
compare(`.tmp/test-repo`, {
'file.txt': 'hello from github!',
subdir: null,
'subdir/file.txt': 'hello from a subdirectory!'
});
});
});
describe('api', () => {
it('is usable from node scripts', async () => {
await degit('Rich-Harris/degit-test-repo', { force: true }).clone(
'.tmp/test-repo'
);
compare(`.tmp/test-repo`, {
'file.txt': 'hello from github!',
subdir: null,
'subdir/file.txt': 'hello from a subdirectory!'
});
});
});
describe('actions', () => {
it('removes specified file', async () => {
await exec(
`node ${degitPath} -v mhkeller/degit-test-repo-remove-only .tmp/test-repo`
);
compare(`.tmp/test-repo`, {});
});
it('clones repo and removes specified file', async () => {
await exec(
`node ${degitPath} -v mhkeller/degit-test-repo-remove .tmp/test-repo`
);
compare(`.tmp/test-repo`, {
'other.txt': 'hello from github!',
subdir: null,
'subdir/file.txt': 'hello from a subdirectory!'
});
});
it('removes and adds nested files', async () => {
await rimraf('.tmp');
await exec(
`node ${degitPath} -v mhkeller/degit-test-repo-nested-actions .tmp/test-repo`
);
compare(`.tmp/test-repo`, {
dir: null,
folder: null,
subdir: null,
'folder/file.txt': 'hello from clobber file!',
'folder/other.txt': 'hello from other file!',
'subdir/file.txt': 'hello from a subdirectory!'
});
});
});
describe('git mode', () => {
it('is able to clone correctly using git mode', async () => {
await rimraf('.tmp');
await exec(
`node ${degitPath} --mode=git https://github.com/Rich-Harris/degit-test-repo-private.git .tmp/test-repo`
);
compare('.tmp/test-repo', {
'file.txt': 'hello from a private repo!'
});
});
});
});
function read(file) {
return fs.readFileSync(file, 'utf-8');
}
gitextract_a5mbvtuo/
├── .dependabot/
│ └── config.yml
├── .editorconfig
├── .eslintrc.json
├── .github/
│ └── workflows/
│ └── nodejs.yml
├── .gitignore
├── .npmrc
├── .prettierrc
├── .travis.yml
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── LICENSE.md
├── README.md
├── appveyor.yml
├── degit
├── help.md
├── package.json
├── rollup.config.js
├── src/
│ ├── bin.js
│ ├── index.js
│ └── utils.js
└── test/
└── test.js
SYMBOL INDEX (32 symbols across 4 files)
FILE: src/bin.js
function main (line 23) | async function main() {
function run (line 123) | function run(src, dest, args) {
FILE: src/index.js
function degit (line 21) | function degit(src, opts) {
class Degit (line 25) | class Degit extends EventEmitter {
method constructor (line 26) | constructor(src, opts = {}) {
method _getDirectives (line 77) | _getDirectives(dest) {
method clone (line 88) | async clone(dest) {
method remove (line 122) | remove(dir, dest, action) {
method _checkDirIsEmpty (line 161) | _checkDirIsEmpty(dir) {
method _info (line 189) | _info(info) {
method _warn (line 193) | _warn(info) {
method _verbose (line 197) | _verbose(info) {
method _getHash (line 201) | async _getHash(repo, cached) {
method _getHashFromCache (line 216) | _getHashFromCache(repo, cached) {
method _selectRef (line 227) | _selectRef(refs, selector) {
method _cloneWithTar (line 245) | async _cloneWithTar(dir, dest) {
method _cloneWithGit (line 319) | async _cloneWithGit(dir, dest) {
function parse (line 327) | function parse(src) {
function untar (line 366) | async function untar(file, dest, subdir = null) {
function fetchRefs (line 377) | async function fetchRefs(repo) {
function updateCache (line 420) | function updateCache(dir, repo, hash, cached) {
FILE: src/utils.js
class DegitError (line 15) | class DegitError extends Error {
method constructor (line 16) | constructor(message, opts) {
function tryRequire (line 22) | function tryRequire(file, opts) {
function exec (line 33) | function exec(command) {
function mkdirp (line 46) | function mkdirp(dir) {
function fetch (line 59) | function fetch(url, dest, proxy) {
function stashFiles (line 90) | function stashFiles(dir, dest) {
function unstashFiles (line 108) | function unstashFiles(dir, dest) {
FILE: test/test.js
function exec (line 15) | function exec(cmd) {
function compare (line 29) | function compare(dir, files) {
function read (line 225) | function read(file) {
Condensed preview — 21 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (45K chars).
[
{
"path": ".dependabot/config.yml",
"chars": 138,
"preview": "version: 1\nupdate_configs:\n - package_manager: 'javascript'\n directory: '/'\n update_schedule: 'weekly'\n target"
},
{
"path": ".editorconfig",
"chars": 261,
"preview": "# editorconfig.org\nroot = true\n\n[*]\ncharset = utf-8\nindent_size = 2\nindent_style = tab\nend_of_line = lf\ntrim_trailing_wh"
},
{
"path": ".eslintrc.json",
"chars": 648,
"preview": "{\n\t\"root\": true,\n\t\"rules\": {\n\t\t\"no-cond-assign\": 0,\n\t\t\"no-unused-vars\": 2,\n\t\t\"object-shorthand\": [2, \"always\"],\n\t\t\"no-co"
},
{
"path": ".github/workflows/nodejs.yml",
"chars": 447,
"preview": "name: Node.js CI\n\non: [push]\n\njobs:\n build:\n\n runs-on: ubuntu-latest\n\n strategy:\n matrix:\n node-versi"
},
{
"path": ".gitignore",
"chars": 32,
"preview": ".DS_Store\nnode_modules\n.tmp\ndist"
},
{
"path": ".npmrc",
"chars": 16,
"preview": "save-exact=true\n"
},
{
"path": ".prettierrc",
"chars": 328,
"preview": "{\n\t\"arrowParens\": \"avoid\",\n\t\"bracketSpacing\": true,\n\t\"endOfLine\": \"lf\",\n\t\"htmlWhitespaceSensitivity\": \"css\",\n\t\"insertPra"
},
{
"path": ".travis.yml",
"chars": 202,
"preview": "language: node_js\nnode_js:\n - '8'\n - '10'\n - lts/*\n - node\n\naddons:\n apt:\n sources:\n - ubuntu-toolchain-r-t"
},
{
"path": "CHANGELOG.md",
"chars": 2276,
"preview": "# degit changelog\n\n## 2.8.4\n\n* Whoops\n\n## 2.8.3\n\n* Reinstate `#!/usr/bin/env node` ([#273](https://github.com/Rich-Harri"
},
{
"path": "CODE_OF_CONDUCT.md",
"chars": 3352,
"preview": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, w"
},
{
"path": "LICENSE.md",
"chars": 1116,
"preview": "Copyright (c) 2019 [these people](https://github.com/Rich-Harris/degit/graphs/contributors)\n\nPermission is hereby grante"
},
{
"path": "README.md",
"chars": 5090,
"preview": "# degit — straightforward project scaffolding\n\n[;\n"
},
{
"path": "help.md",
"chars": 1145,
"preview": "# _degit_\n\nUsage:\n\n`degit <src>[#ref] [<dest>] [options]`\n\nFetches the `src` repo, and extracts it to `dest` (or the cur"
},
{
"path": "package.json",
"chars": 1594,
"preview": "{\n\t\"name\": \"degit\",\n\t\"version\": \"2.8.4\",\n\t\"engines\": {\n\t\t\"node\": \">=8.0.0\"\n\t},\n\t\"description\": \"Straightforward project "
},
{
"path": "rollup.config.js",
"chars": 512,
"preview": "import resolve from '@rollup/plugin-node-resolve';\nimport commonjs from '@rollup/plugin-commonjs';\nimport { builtinModul"
},
{
"path": "src/bin.js",
"chars": 3233,
"preview": "import fs from 'fs';\nimport path from 'path';\nimport chalk from 'chalk';\nimport mri from 'mri';\nimport glob from 'tiny-g"
},
{
"path": "src/index.js",
"chars": 9794,
"preview": "import fs from 'fs';\nimport path from 'path';\nimport tar from 'tar';\nimport EventEmitter from 'events';\nimport chalk fro"
},
{
"path": "src/utils.js",
"chars": 2862,
"preview": "import fs from 'fs';\nimport path from 'path';\nimport homeOrTmp from 'home-or-tmp';\nimport https from 'https';\nimport chi"
},
{
"path": "test/test.js",
"chars": 5865,
"preview": "require('source-map-support').install();\n\nconst fs = require('fs');\nconst path = require('path');\nconst glob = require('"
}
]
About this extraction
This page contains the full source code of the Rich-Harris/degit GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 21 files (38.6 KB), approximately 12.0k tokens, and a symbol index with 32 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.