Skip to content
This repository was archived by the owner on Oct 9, 2023. It is now read-only.

Commit 4d84043

Browse files
committed
add ignore globs to zip functionality
1 parent d17fc36 commit 4d84043

File tree

10 files changed

+225
-99
lines changed

10 files changed

+225
-99
lines changed

index.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "pgb-api",
3-
"version": "1.0.5",
3+
"version": "1.1.0",
44
"description": "nodeJS API to PhoneGap Build",
55
"keywords": [
66
"PhoneGap",

src/api.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ const merge = require('./misc').merge
44
const getPath = require('./misc').getPath
55
const mkdirp = require('./misc').mkdirp
66
const rest = require('./rest-client')
7-
const zipper = require('./zipper')
7+
const zip = require('./zip')
88
const fs = require('fs')
99
const path = require('path')
1010
const os = require('os')
@@ -171,9 +171,13 @@ class PGBApi {
171171
addAppFromDir(id, dir, data) {
172172
return new Promise((resolve, reject) => {
173173
let cleanup = false
174+
174175
let filePath = data.zip
175176
delete data.zip
176177

178+
let ignore = data.ignore || []
179+
delete data.ignore
180+
177181
if (!filePath) {
178182
filePath = path.join(os.tmpdir(), 'pgb-' + Math.random().toString(32).slice(2) + '.zip')
179183
cleanup = true
@@ -199,7 +203,7 @@ class PGBApi {
199203
}
200204

201205
emit('debug', `archiving ${dir} to ${filePath}`)
202-
zipper.zipDir(dir, filePath, this.defaults.events)
206+
zip(dir, filePath, this.defaults.events, ignore)
203207
.then(() => this.addAppFromFile(id, filePath, data))
204208
.then((app) => {
205209
deleteZip()

src/glob.js

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
const fs = require('fs')
2+
const path = require('path')
3+
4+
const glob = (root, ignoreGlobs) => {
5+
let list = []
6+
let skipped = []
7+
let globRegexes = (ignoreGlobs || []).map(toGlobRegex)
8+
9+
let walkSync = dir => {
10+
let files = fs.readdirSync(dir)
11+
12+
files.forEach(file => {
13+
let fullPath = path.join(dir, file)
14+
let globPath = path.resolve(fullPath).replace(path.resolve(root) + '/', '')
15+
16+
if (file.startsWith('.') && !file.match(/^\.pgb/)) {
17+
skipped.push(`${fullPath} [HIDDEN]`)
18+
return
19+
}
20+
21+
try {
22+
let stat = fs.statSync(fullPath)
23+
fs.closeSync(fs.openSync(fullPath, 'r'))
24+
25+
let ignored = filter(globPath, stat.isDirectory(), globRegexes)
26+
27+
if (stat.isDirectory()) {
28+
if (ignored) {
29+
skipped.push(`${fullPath}/ [IGNORED]`)
30+
} else {
31+
list.push({ path: fullPath, size: 0 })
32+
walkSync(fullPath)
33+
}
34+
} else {
35+
if (ignored) {
36+
skipped.push(`${fullPath} [IGNORED]`)
37+
} else {
38+
list.push({ path: fullPath, size: stat.size })
39+
}
40+
list.push()
41+
}
42+
} catch (e) {
43+
skipped.push(`${fullPath} [${e.code}]`)
44+
}
45+
})
46+
}
47+
48+
walkSync(root)
49+
return { list, skipped }
50+
}
51+
52+
const toGlobRegex = (glob) => {
53+
if (glob == null || glob[0] === '#' || glob.trim() === '') return null
54+
55+
let negation = glob.indexOf('!') === 0
56+
let dir = false
57+
58+
if (glob.endsWith('/')) {
59+
glob = glob.slice(0, -1)
60+
dir = true
61+
}
62+
63+
if (negation || glob[0] === '/') {
64+
glob = glob.slice(1)
65+
}
66+
67+
glob = glob
68+
.replace(/[.+^${}()|[\]\\]/g, '\\$&')
69+
.replace(/\?/g, '.')
70+
.replace(/\*\*\//g, '^e^')
71+
.replace(/\*\*/g, '^e^')
72+
.replace(/\*/g, `[^/]+`)
73+
.replace(/\^e\^/g, '.*')
74+
75+
if (glob.indexOf('/') === -1) {
76+
glob = glob + '/?'
77+
}
78+
79+
return { dir, negation, regex: new RegExp(`^${glob}$`) }
80+
}
81+
82+
const filter = (filePath, isDir, globRegexes) => {
83+
let result = false
84+
85+
for (let globRegex of globRegexes) {
86+
if (globRegex == null) continue
87+
88+
if (globRegex.dir && !isDir) continue
89+
90+
if (globRegex.regex.test(filePath)) {
91+
if (globRegex.dir && isDir) {
92+
return !globRegex.negation
93+
}
94+
result = !globRegex.negation
95+
}
96+
}
97+
return result
98+
}
99+
100+
module.exports = { glob, toGlobRegex, filter }

src/rest-client.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,11 @@ const urlParse = require('url')
66
const fs = require('fs')
77
const path = require('path')
88
const Stream = require('stream').Stream
9-
const version = require('../package.json').version
109
const https = require('https')
1110

1211
const defaultOpts = {
1312
headers: {
14-
'User-Agent': `pgb-api/${version} node/${process.version} (${process.platform})`
13+
'User-Agent': `pgb-api/1.1.0 node/${process.version} (${process.platform})`
1514
}
1615
}
1716

src/zipper.js renamed to src/zip.js

Lines changed: 6 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,11 @@
11
const fs = require('fs')
22
const yazl = require('yazl')
33
const path = require('path')
4+
const glob = require('./glob').glob
45

5-
const getFileList = (dir) => {
6-
let list = []
7-
let skipped = []
8-
9-
let walkSync = dir => {
10-
let files = fs.readdirSync(dir)
11-
12-
files.forEach(file => {
13-
let fullPath = path.join(dir, file)
14-
if (file.startsWith('.') && !file.match(/^\.pgb/)) {
15-
skipped.push(`${fullPath} [HIDDEN]`)
16-
return
17-
}
18-
19-
try {
20-
let stat = fs.statSync(fullPath)
21-
fs.closeSync(fs.openSync(fullPath, 'r'))
22-
23-
if (stat.isDirectory()) {
24-
list.push({ path: fullPath, size: 0 })
25-
walkSync(fullPath)
26-
} else {
27-
list.push({ path: fullPath, size: stat.size })
28-
}
29-
} catch (e) {
30-
skipped.push(`${fullPath} [${e.code}]`)
31-
}
32-
})
33-
}
34-
35-
walkSync(dir)
36-
return { list, skipped }
37-
}
38-
39-
const zipDir = (dir, dest, eventEmitter) => {
6+
const zipDir = (dir, dest, eventEmitter, ignore) => {
407
return new Promise((resolve, reject) => {
41-
let fileList = getFileList(dir)
8+
let files = glob(dir, ignore)
429
let stream = fs.createWriteStream(dest)
4310
let zip = new yazl.ZipFile()
4411
let file = ''
@@ -51,9 +18,9 @@ const zipDir = (dir, dest, eventEmitter) => {
5118
if (eventEmitter) eventEmitter.emit(evt, data)
5219
}
5320

54-
emit('zip/files', fileList)
21+
emit('zip/files', files)
5522

56-
for (let f of fileList.list) {
23+
for (let f of files.list) {
5724
let pathInArchive = path.relative(dir, f.path)
5825
if (fs.statSync(f.path).isDirectory()) {
5926
zip.addEmptyDirectory(pathInArchive)
@@ -102,4 +69,4 @@ const zipDir = (dir, dest, eventEmitter) => {
10269
})
10370
}
10471

105-
module.exports = { getFileList, zipDir }
72+
module.exports = zipDir

test/api.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
const restClient = require('../src/rest-client')
2-
const zipper = require('../src/zipper')
2+
const zipper = require('../src/zip')
33
jest.mock('../src/rest-client')
4-
jest.mock('../src/zipper')
4+
jest.mock('../src/zip')
55
const apiClient = require('../src/api')
66
const fs = require('fs')
77
const os = require('os')
@@ -261,7 +261,7 @@ describe('api', () => {
261261
os.tmpdir = jest.fn().mockImplementation(() => '/tmp')
262262
fs.mkdirSync('/tmp')
263263
fs.mkdirSync('/app_to_zip')
264-
zipper.zipDir.mockImplementation((src, dest) => {
264+
zipper.mockImplementation((src, dest) => {
265265
fs.writeFileSync(dest)
266266
return Promise.resolve()
267267
})
@@ -326,7 +326,7 @@ describe('api', () => {
326326
})
327327

328328
test('zip dir and add app with bad files', (done) => {
329-
zipper.zipDir.mockImplementation((src, dest) => Promise.reject(new Error('zip failed')))
329+
zipper.mockImplementation((src, dest) => Promise.reject(new Error('zip failed')))
330330

331331
return api.addApp('/app_to_zip', { hydrates: true }).catch((val) => {
332332
expect(val).toEqual(new Error('zip failed'))

test/glob.js

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
const glob = require('../src/glob')
2+
const os = require('os')
3+
const fs = require('fs')
4+
5+
beforeEach(() => {
6+
os.tmpdir = jest.fn().mockImplementation(() => '/tmp')
7+
fs.mkdirSync('/tmp')
8+
fs.mkdirSync('/app_to_zip')
9+
fs.writeFileSync('/app_to_zip/.delete_me', '1')
10+
fs.writeFileSync('/app_to_zip/.pgb_dont_delete_me', '22')
11+
fs.writeFileSync('/app_to_zip/index.html', '333')
12+
fs.writeFileSync('/app_to_zip/cordova.js', '4444')
13+
fs.mkdirSync('/app_to_zip/res')
14+
})
15+
16+
afterEach(() => {
17+
fs.rmdirSync('/tmp')
18+
fs.unlinkSync('/app_to_zip/.delete_me')
19+
fs.unlinkSync('/app_to_zip/.pgb_dont_delete_me')
20+
fs.unlinkSync('/app_to_zip/index.html')
21+
fs.unlinkSync('/app_to_zip/cordova.js')
22+
fs.rmdirSync('/app_to_zip/res')
23+
fs.rmdirSync('/app_to_zip')
24+
})
25+
26+
describe('glob', () => {
27+
test('should return files', () => {
28+
let result = {
29+
list: [
30+
{ 'path': '/app_to_zip/.pgb_dont_delete_me', 'size': 2 },
31+
{ 'path': '/app_to_zip/cordova.js', 'size': 4 },
32+
{ 'path': '/app_to_zip/index.html', 'size': 3 },
33+
{ 'path': '/app_to_zip/res', 'size': 0 }
34+
],
35+
skipped: [ '/app_to_zip/.delete_me [HIDDEN]' ]
36+
}
37+
expect(glob.glob('/app_to_zip')).toEqual(result)
38+
})
39+
40+
test('should skip if file cant be read', () => {
41+
let error = new Error('bad file')
42+
error.code = 'ENOENT'
43+
let oldOpenSync = fs.openSync
44+
fs.openSync = jest.fn().mockImplementation(() => { throw error })
45+
let result = {
46+
skipped: [
47+
'/app_to_zip/.delete_me [HIDDEN]',
48+
'/app_to_zip/.pgb_dont_delete_me [ENOENT]',
49+
'/app_to_zip/cordova.js [ENOENT]',
50+
'/app_to_zip/index.html [ENOENT]',
51+
'/app_to_zip/res [ENOENT]'
52+
],
53+
list: [ ]
54+
}
55+
expect(glob.glob('/app_to_zip')).toEqual(result)
56+
fs.openSync = oldOpenSync
57+
})
58+
59+
test('should skip ignored files', () => {
60+
let result = {
61+
list: [
62+
{ 'path': '/app_to_zip/.pgb_dont_delete_me', 'size': 2 },
63+
{ 'path': '/app_to_zip/cordova.js', 'size': 4 }
64+
],
65+
skipped: [ '/app_to_zip/.delete_me [HIDDEN]', '/app_to_zip/index.html [IGNORED]', '/app_to_zip/res/ [IGNORED]' ]
66+
}
67+
expect(glob.glob('/app_to_zip', [ '**/*.html', 'res/' ])).toEqual(result)
68+
})
69+
})
70+
71+
describe('toGlobRegex', () => {
72+
test('should skip ignored files', () => {
73+
expect(glob.toGlobRegex('')).toEqual(null)
74+
expect(glob.toGlobRegex(null)).toEqual(null)
75+
expect(glob.toGlobRegex('# comment')).toEqual(null)
76+
expect(glob.toGlobRegex('shell')).toEqual({ 'dir': false, 'negation': false, 'regex': /^shell\/?$/ })
77+
expect(glob.toGlobRegex('dir/')).toEqual({ 'dir': true, 'negation': false, 'regex': /^dir\/?$/ })
78+
expect(glob.toGlobRegex('dir/**')).toEqual({ 'dir': false, 'negation': false, 'regex': /^dir\/.*$/ })
79+
expect(glob.toGlobRegex('/dir')).toEqual({ 'dir': false, 'negation': false, 'regex': /^dir\/?$/ })
80+
expect(glob.toGlobRegex('!not_me')).toEqual({ 'dir': false, 'negation': true, 'regex': /^not_me\/?$/ })
81+
expect(glob.toGlobRegex('(.moot?[]+)')).toEqual({ 'dir': false, 'negation': false, 'regex': /^\(\.moot.\[\]\+\)\/?$/ })
82+
})
83+
})
84+
85+
describe('filter', () => {
86+
test('should skip ignored files', () => {
87+
expect(glob.filter('', true, [])).toEqual(false)
88+
expect(glob.filter('', true, [null])).toEqual(false)
89+
expect(glob.filter('rabbit', true, [{ dir: true, negation: false, regex: /^rabbit\/?$/ }])).toEqual(true)
90+
expect(glob.filter('rabbit', true, [{ dir: false, negation: false, regex: /^rabbit\/?$/ }])).toEqual(true)
91+
expect(glob.filter('rabbit', true, [{ dir: true, negation: true, regex: /^rabbit$/ }])).toEqual(false)
92+
expect(glob.filter('rabbit', false, [{ dir: true, negation: false, regex: /^rabbit\/?$/ }])).toEqual(false)
93+
})
94+
})

test/rest-client.js

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ const fs = require('fs')
33
const server = require('./_helpers/fake-server')
44
const app = server.listen(3000, '0.0.0.0')
55
const reqs = jest.spyOn(server, 'requestLogger')
6+
const version = require('../package.json').version
67
const lastReq = () => reqs.mock.calls[reqs.mock.calls.length - 1][0]
78

89
jest.mock('https', () => {
@@ -27,18 +28,13 @@ describe('rest-client', () => {
2728
})
2829

2930
describe('#get', () => {
30-
test('use http for http url and https for https url', () => {
31-
let spy1 = jest.spyOn(require('http'), 'request')
32-
let spy2 = jest.spyOn(require('https'), 'request')
33-
34-
return restClient.get('http://localhost:3000/page1')
35-
.then((response) => expect(response).toBe('A page'))
36-
.then(() => expect(spy1).toBeCalled())
37-
.then(() => restClient.get('https://localhost:3000/page1'))
38-
.then((response) => expect(response).toBe('A page'))
39-
.catch(() => { /* it will fail the get */ })
40-
.then(() => expect(spy2).toBeCalled())
41-
})
31+
test('should populate user-agent (remember to manually update the user-agent!!!)', () =>
32+
restClient.get('http://localhost:3000/page1')
33+
.then((response) => {
34+
let userAgent = `pgb-api/${version} node/${process.version} (${process.platform})`
35+
expect(reqs.mock.calls[0][0].headers['user-agent']).toEqual(userAgent)
36+
})
37+
)
4238

4339
test('should get simple html', () =>
4440
restClient.get('http://localhost:3000/page1')

0 commit comments

Comments
 (0)