Skip to content

Commit

Permalink
feat: initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
JanMalch committed Oct 23, 2020
0 parents commit c999791
Show file tree
Hide file tree
Showing 7 changed files with 433 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.idea/
node_modules/
37 changes: 37 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# glob-zip <a href="https://www.github.com/JanMalch/glob-zip"><img src="https://user-images.githubusercontent.com/25508038/63974103-75242700-caac-11e9-8ca4-71cc5b905e90.png" width="90" height="90" align="right"></a>

[![npm](https://badgen.net/bpm/v/glob-zip)](https://www.npmjs.com/package/glob-zip)
[![Build](https://github.com/JanMalch/glob-zip/workflows/Build/badge.svg)](https://github.com/JanMalch/glob-zip/workflows/Build)

_Create zip files based on glob patterns._

## Installation

```
npm i -g glob-zip
```

## Usage

```
$ glob-zip --help
Usage: glob-zip [options] <outFile> [globPattern]
Options:
-V, --version output the version number
-g, --glob <pattern> Add a glob pattern
-a, --append Appends to the specified outFile if present (default: false)
-l, --lift <depth> Lift files the given amount of directories for the path in the zip (default: 0)
-w, --wrap [name] Define the root path within the zip, defaults to current directory name if flag is present without value
-F, --no-fail Do not fail when zip would be empty
-E, --no-empty Do not include empty directories
-d, --dry-run Do not write the final zip (default: false)
-v, --verbose Use verbose output (default: false)
-h, --help display help for command
Examples:
$ glob-zip out.zip *.json # easiest usage
$ glob-zip out.zip -g *.json -g *.js # multiple glob patterns
$ glob-zip out.zip src/**/*.js --wrap backup --lift 1 # effectively renames "src" to "backup" in zip
```
109 changes: 109 additions & 0 deletions cli.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
const { program } = require('commander');
const path = require('path');
const chalk = require('chalk');
const pkg = require('./package.json');
const { globZip } = require('.');

const globPatterns = new Set();
let output = null;

function addGlobPattern(pattern) {
if (pattern != null) {
globPatterns.add(pattern);
}
}

function resolveWrap(value) {
if (typeof value === 'string') {
return value;
} else {
return path.basename(process.cwd());
}
}

// FIXME: change argument usage to "glob-zip [options] <outFile> <pattern> [patterns...]"

program
.name('glob-zip')
.arguments('<outFile> [globPattern]')
.on('option:verbose', function () {
process.env.VERBOSE = this.verbose;
})
.on('option:wrap', function (value) {
this.wrap = resolveWrap(value);
})
.on('--help', () => {
console.log('');
console.log('Examples:');
console.log(' $ glob-zip out.zip *.json # easiest usage');
console.log(' $ glob-zip out.zip *.json --glob="sp ace.txt" -g *.js # three glob patterns');
console.log(' $ glob-zip out.zip src/**/*.js --wrap backup --lift 1 # effectively renames "src" to "backup" in zip');
})
.option('-g, --glob <pattern>', 'Add a glob pattern', addGlobPattern)
.option(
'-w, --wrap [name]',
'Define the root path within the zip, defaults to current directory name if flag is present without value'
)
.option(
'-l, --lift <depth>',
'Lift files the given amount of directories for the path in the zip',
(v) => parseInt(v, 10),
0
)
.option('-a, --append', 'Appends to the specified outFile if present. If not, a file with the same name would be removed.', false)
.option('-F, --no-fail', 'Do not fail when zip would be empty', false)
.option('-E, --no-empty', 'Do not include empty directories', false)
.option('-d, --dry-run', 'Do not write or delete any files', false)
.option('-v, --verbose', 'Use verbose output', false)
.version(pkg.version)
.action((outFile, globPattern) => {
output = path.resolve(outFile);
addGlobPattern(globPattern);
});

program.parse(process.argv);

if (globPatterns.size === 0) {
console.error('error: no glob patterns defined');
process.exit(1);
}

if (process.env.VERBOSE) {
console.log(`using ${chalk.blue(
globPatterns.size.toString(10)
)} glob pattern${globPatterns.size === 1 ? '' : 's'}:
${Array.from(globPatterns.values())
.map((p) => chalk.green(p))
.join('\n ')}
`);
}

const prettyPrintSrcDest = (src, dest) => {
const srcCwdPart = src.substring(0, process.cwd().length + 1);
const srcFilePart = src.substring(srcCwdPart.length);
const destCwdPart = path.dirname(output);
const destFilePart = path.basename(output);

console.log(
chalk.blue(dest.endsWith(path.sep) ? 'empty dir:' : 'file:'),
chalk.gray(srcCwdPart) + chalk.green(srcFilePart),
'->',
(
chalk.gray(destCwdPart + path.sep) +
destFilePart + path.sep +
chalk.green(dest.startsWith(path.sep) ? dest.substring(1) : dest)
)
);
};

try {
globZip({
...program,
outFile: output,
globPatterns,
fileInfoCallback: process.env.VERBOSE ? prettyPrintSrcDest : undefined,
});
} catch (e) {
console.error('error:', e.message);
process.exit(1);
}
14 changes: 14 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export interface GlobZipOptions {
outFile: string;
globPatterns: string | string[] | Set<string>;
append?: boolean;
fail?: boolean;
empty?: boolean;
wrap?: string;
dryRun?: boolean;
lift?: number;
fileInfoCallback?: (src: string, dest: string) => void;
}

export declare function globZip(options: GlobZipOptions, callback?: () => void);

83 changes: 83 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
const { glob } = require('glob');
const path = require('path');
const fs = require('fs');
const AdmZip = require('adm-zip');


module.exports.globZip = function (
{
outFile,
globPatterns: _globPatterns,
append,
fail: failIfZipEmpty,
empty: includeEmptyDirectories,
wrap,
dryRun,
lift,
fileInfoCallback = () => {
},
},
callback = () => {}
) {
let globPatterns;
if (_globPatterns instanceof Set) {
globPatterns = _globPatterns;
} else if (typeof _globPatterns === 'string') {
globPatterns = new Set([_globPatterns]);
} else if (Array.isArray(_globPatterns)) {
globPatterns = new Set(_globPatterns);
} else {
throw new TypeError(`cannot use given globPatterns`);
}

const files = Array.from(globPatterns.values()).reduce(
(acc, p) => acc.concat(glob.sync(p, { dot: true })),
[]
);

if (!dryRun && fs.existsSync(outFile) && append !== true) {
fs.unlinkSync(outFile);
}

const zip =
append === true && fs.existsSync(outFile)
? new AdmZip(outFile)
: new AdmZip();

const prefix =
wrap == null ? '' : wrap.endsWith(path.sep) ? wrap : wrap + path.sep;

files.forEach((file) => {
const segments = path.normalize(file).split(path.sep);
if (segments.length <= lift) {
throw new Error(
`cannot lift '${file}' ${lift} directories (${
lift + 1 - segments.length
} too many)`
);
}
const srcPath = path.resolve(process.cwd(), file);
const destPath = prefix + segments.slice(lift, -1).join(path.sep);
const destPathWithName =
destPath + path.sep + segments[segments.length - 1];
if (fs.lstatSync(srcPath).isDirectory()) {
if (includeEmptyDirectories && fs.readdirSync(srcPath).length === 0) {
zip.addFile(destPathWithName + path.sep, Buffer.alloc(0));
fileInfoCallback(srcPath + path.sep, destPathWithName + path.sep);
}
} else {
zip.addLocalFile(srcPath, destPath);
fileInfoCallback(srcPath, destPathWithName);
}
});

if (failIfZipEmpty && zip.getEntries().length === 0) {
throw new Error('no files found');
}

if (!dryRun) {
zip.writeZip(outFile, callback);
} else {
callback();
}
};
144 changes: 144 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit c999791

Please sign in to comment.