Skip to content

Commit 173e4bf

Browse files
achingbrainhugomrdias
authored andcommitted
feat: support unixfs metadata and formatting it (#14)
Adds mode and mtime properties to normalised .add inputs, also adds functions for turning metadata into strings for CLI use. BREAKING CHANGE: In order to support metadata on intermediate directories, globSource in this module will now emit directories and files where previously it only emitted files.
1 parent 251eff0 commit 173e4bf

11 files changed

+368
-52
lines changed

package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,14 @@
3030
"err-code": "^2.0.0",
3131
"fs-extra": "^8.1.0",
3232
"is-electron": "^2.2.0",
33-
"it-glob": "0.0.6",
33+
"it-glob": "0.0.7",
3434
"ky": "^0.15.0",
3535
"ky-universal": "^0.3.0",
3636
"stream-to-it": "^0.2.0"
3737
},
3838
"devDependencies": {
3939
"aegir": "^20.3.0",
40-
"async-iterator-all": "^1.0.0",
40+
"it-all": "^1.0.1",
4141
"chai": "^4.2.0",
4242
"chai-as-promised": "^7.1.1",
4343
"dirty-chai": "^2.0.1"

src/files/format-mode.js

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
'use strict'
2+
3+
const S_ISUID = parseInt('4000', 8) // set UID bit
4+
const S_ISGID = parseInt('2000', 8) // set-group-ID bit (see below)
5+
const S_ISVTX = parseInt('1000', 8) // sticky bit (see below)
6+
// const S_IRWXU = parseInt('700', 8) // mask for file owner permissions
7+
const S_IRUSR = parseInt('400', 8) // owner has read permission
8+
const S_IWUSR = parseInt('200', 8) // owner has write permission
9+
const S_IXUSR = parseInt('100', 8) // owner has execute permission
10+
// const S_IRWXG = parseInt('70', 8) // mask for group permissions
11+
const S_IRGRP = parseInt('40', 8) // group has read permission
12+
const S_IWGRP = parseInt('20', 8) // group has write permission
13+
const S_IXGRP = parseInt('10', 8) // group has execute permission
14+
// const S_IRWXO = parseInt('7', 8) // mask for permissions for others (not in group)
15+
const S_IROTH = parseInt('4', 8) // others have read permission
16+
const S_IWOTH = parseInt('2', 8) // others have write permission
17+
const S_IXOTH = parseInt('1', 8) // others have execute permission
18+
19+
function checkPermission (mode, perm, type, output) {
20+
if ((mode & perm) === perm) {
21+
output.push(type)
22+
} else {
23+
output.push('-')
24+
}
25+
}
26+
27+
function formatMode (mode, isDirectory) {
28+
const output = []
29+
30+
if (isDirectory) {
31+
output.push('d')
32+
} else {
33+
output.push('-')
34+
}
35+
36+
checkPermission(mode, S_IRUSR, 'r', output)
37+
checkPermission(mode, S_IWUSR, 'w', output)
38+
39+
if ((mode & S_ISUID) === S_ISUID) {
40+
output.push('s')
41+
} else {
42+
checkPermission(mode, S_IXUSR, 'x', output)
43+
}
44+
45+
checkPermission(mode, S_IRGRP, 'r', output)
46+
checkPermission(mode, S_IWGRP, 'w', output)
47+
48+
if ((mode & S_ISGID) === S_ISGID) {
49+
output.push('s')
50+
} else {
51+
checkPermission(mode, S_IXGRP, 'x', output)
52+
}
53+
54+
checkPermission(mode, S_IROTH, 'r', output)
55+
checkPermission(mode, S_IWOTH, 'w', output)
56+
57+
if ((mode & S_ISVTX) === S_ISVTX) {
58+
output.push('t')
59+
} else {
60+
checkPermission(mode, S_IXOTH, 'x', output)
61+
}
62+
63+
return output.join('')
64+
}
65+
66+
module.exports = formatMode

src/files/format-mtime.js

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
'use strict'
2+
3+
function formatMtime (mtime) {
4+
if (mtime === undefined) {
5+
return '-'
6+
}
7+
8+
return new Date(mtime * 1000).toLocaleDateString(Intl.DateTimeFormat().resolvedOptions().locale, {
9+
year: 'numeric',
10+
month: 'short',
11+
day: 'numeric',
12+
hour: '2-digit',
13+
minute: '2-digit',
14+
second: '2-digit',
15+
timeZoneName: 'short'
16+
})
17+
}
18+
19+
module.exports = formatMtime

src/files/glob-source.js

+53-7
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ const errCode = require('err-code')
1414
* @param {Boolean} [options.hidden] Include .dot files in matched paths
1515
* @param {Array<String>} [options.ignore] Glob paths to ignore
1616
* @param {Boolean} [options.followSymlinks] follow symlinks
17+
* @param {Boolean} [options.preserveMode] preserve mode
18+
* @param {Boolean} [options.preserveMtime] preserve mtime
19+
* @param {Boolean} [options.mode] mode to use - if preserveMode is true this will be ignored
20+
* @param {Boolean} [options.mtime] mtime to use - if preserveMtime is true this will be ignored
1721
* @yields {Object} File objects in the form `{ path: String, content: AsyncIterator<Buffer> }`
1822
*/
1923
module.exports = async function * globSource (paths, options) {
@@ -46,21 +50,49 @@ module.exports = async function * globSource (paths, options) {
4650
const stat = await fs.stat(absolutePath)
4751
const prefix = Path.dirname(absolutePath)
4852

49-
for await (const entry of toGlobSource({ path, type: stat.isDirectory() ? 'dir' : 'file', prefix }, globSourceOptions)) {
50-
yield entry
53+
let mode = options.mode
54+
55+
if (options.preserveMode) {
56+
mode = stat.mode
57+
}
58+
59+
let mtime = options.mtime
60+
61+
if (options.preserveMtime) {
62+
mtime = parseInt(stat.mtimeMs / 1000)
63+
}
64+
65+
if (stat.isDirectory()) {
66+
yield {
67+
path: `/${Path.basename(path)}`,
68+
mode,
69+
mtime
70+
}
5171
}
72+
73+
yield * toGlobSource({
74+
path,
75+
type: stat.isDirectory() ? 'dir' : 'file',
76+
prefix,
77+
mode,
78+
mtime,
79+
preserveMode: options.preserveMode,
80+
preserveMtime: options.preserveMtime
81+
}, globSourceOptions)
5282
}
5383
}
5484

55-
async function * toGlobSource ({ path, type, prefix }, options) {
85+
async function * toGlobSource ({ path, type, prefix, mode, mtime, preserveMode, preserveMtime }, options) {
5686
options = options || {}
5787

5888
const baseName = Path.basename(path)
5989

6090
if (type === 'file') {
6191
yield {
62-
path: baseName.replace(prefix, ''),
63-
content: fs.createReadStream(Path.isAbsolute(path) ? path : Path.join(process.cwd(), path))
92+
path: `/${baseName.replace(prefix, '')}`,
93+
content: fs.createReadStream(Path.isAbsolute(path) ? path : Path.join(process.cwd(), path)),
94+
mode,
95+
mtime
6496
}
6597

6698
return
@@ -76,15 +108,29 @@ async function * toGlobSource ({ path, type, prefix }, options) {
76108

77109
const globOptions = Object.assign({}, options.glob, {
78110
cwd: path,
79-
nodir: true,
111+
nodir: false,
80112
realpath: false,
81113
absolute: true
82114
})
83115

84116
for await (const p of glob(path, '**/*', globOptions)) {
117+
const stat = await fs.stat(p)
118+
119+
if (preserveMode || preserveMtime) {
120+
if (preserveMode) {
121+
mode = stat.mode
122+
}
123+
124+
if (preserveMtime) {
125+
mtime = parseInt(stat.mtimeMs / 1000)
126+
}
127+
}
128+
85129
yield {
86130
path: toPosix(p.replace(prefix, '')),
87-
content: fs.createReadStream(p)
131+
content: stat.isFile() ? fs.createReadStream(p) : undefined,
132+
mode,
133+
mtime
88134
}
89135
}
90136
}

src/files/normalise-input.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,11 @@ module.exports = function normaliseInput (input) {
143143
}
144144

145145
function toFileObject (input) {
146-
const obj = { path: input.path || '' }
146+
const obj = {
147+
path: input.path || '',
148+
mode: input.mode,
149+
mtime: input.mtime
150+
}
147151

148152
if (input.content) {
149153
obj.content = toAsyncIterable(input.content)

test/files/format-mode.spec.js

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
'use strict'
2+
3+
/* eslint-env mocha */
4+
const chai = require('chai')
5+
const dirtyChai = require('dirty-chai')
6+
const formatMode = require('../../src/files/format-mode')
7+
8+
chai.use(dirtyChai)
9+
const expect = chai.expect
10+
11+
describe('format-mode', function () {
12+
it('formats mode for directories', function () {
13+
expect(formatMode(parseInt('0777', 8), true)).to.equal('drwxrwxrwx')
14+
})
15+
16+
it('formats mode for files', function () {
17+
expect(formatMode(parseInt('0777', 8), false)).to.equal('-rwxrwxrwx')
18+
})
19+
20+
it('setgid, setuid and stick bit', function () {
21+
expect(formatMode(parseInt('1777', 8), false)).to.equal('-rwxrwxrwt')
22+
expect(formatMode(parseInt('2777', 8), false)).to.equal('-rwxrwsrwx')
23+
expect(formatMode(parseInt('4777', 8), false)).to.equal('-rwsrwxrwx')
24+
expect(formatMode(parseInt('5777', 8), false)).to.equal('-rwsrwxrwt')
25+
expect(formatMode(parseInt('6777', 8), false)).to.equal('-rwsrwsrwx')
26+
expect(formatMode(parseInt('7777', 8), false)).to.equal('-rwsrwsrwt')
27+
})
28+
29+
it('formats user', function () {
30+
expect(formatMode(parseInt('0100', 8), false)).to.equal('---x------')
31+
expect(formatMode(parseInt('0200', 8), false)).to.equal('--w-------')
32+
expect(formatMode(parseInt('0300', 8), false)).to.equal('--wx------')
33+
expect(formatMode(parseInt('0400', 8), false)).to.equal('-r--------')
34+
expect(formatMode(parseInt('0500', 8), false)).to.equal('-r-x------')
35+
expect(formatMode(parseInt('0600', 8), false)).to.equal('-rw-------')
36+
expect(formatMode(parseInt('0700', 8), false)).to.equal('-rwx------')
37+
})
38+
39+
it('formats group', function () {
40+
expect(formatMode(parseInt('0010', 8), false)).to.equal('------x---')
41+
expect(formatMode(parseInt('0020', 8), false)).to.equal('-----w----')
42+
expect(formatMode(parseInt('0030', 8), false)).to.equal('-----wx---')
43+
expect(formatMode(parseInt('0040', 8), false)).to.equal('----r-----')
44+
expect(formatMode(parseInt('0050', 8), false)).to.equal('----r-x---')
45+
expect(formatMode(parseInt('0060', 8), false)).to.equal('----rw----')
46+
expect(formatMode(parseInt('0070', 8), false)).to.equal('----rwx---')
47+
})
48+
49+
it('formats other', function () {
50+
expect(formatMode(parseInt('0001', 8), false)).to.equal('---------x')
51+
expect(formatMode(parseInt('0002', 8), false)).to.equal('--------w-')
52+
expect(formatMode(parseInt('0003', 8), false)).to.equal('--------wx')
53+
expect(formatMode(parseInt('0004', 8), false)).to.equal('-------r--')
54+
expect(formatMode(parseInt('0005', 8), false)).to.equal('-------r-x')
55+
expect(formatMode(parseInt('0006', 8), false)).to.equal('-------rw-')
56+
expect(formatMode(parseInt('0007', 8), false)).to.equal('-------rwx')
57+
})
58+
})

test/files/format-mtime.spec.js

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
'use strict'
2+
3+
/* eslint-env mocha */
4+
const chai = require('chai')
5+
const dirtyChai = require('dirty-chai')
6+
const formatMtime = require('../../src/files/format-mtime')
7+
8+
chai.use(dirtyChai)
9+
const expect = chai.expect
10+
11+
describe('format-mtime', function () {
12+
it('formats mtime', function () {
13+
expect((new Date(formatMtime(0))).getTime()).to.equal(0)
14+
})
15+
})

0 commit comments

Comments
 (0)