Skip to content

Commit 9d4e158

Browse files
committed
initial commit
0 parents  commit 9d4e158

30 files changed

+13582
-0
lines changed

.github/workflows/ci.yml

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
name: CI
2+
3+
on:
4+
pull_request:
5+
push:
6+
branches:
7+
- main
8+
- latest
9+
10+
jobs:
11+
build:
12+
strategy:
13+
fail-fast: false
14+
matrix:
15+
node-version: [10.0.x, 10.x, 12.0.x, 12.x, 14.0.x, 14.x, 15.x, 16.x]
16+
platform:
17+
- os: ubuntu-latest
18+
shell: bash
19+
- os: macos-latest
20+
shell: bash
21+
- os: windows-latest
22+
shell: bash
23+
- os: windows-latest
24+
shell: cmd
25+
- os: windows-latest
26+
shell: powershell
27+
28+
runs-on: ${{ matrix.platform.os }}
29+
defaults:
30+
run:
31+
shell: ${{ matrix.platform.shell }}
32+
33+
steps:
34+
- name: Checkout Repository
35+
uses: actions/[email protected]
36+
37+
- name: Use Nodejs ${{ matrix.node-version }}
38+
uses: actions/setup-node@v2
39+
with:
40+
node-version: ${{ matrix.node-version }}
41+
cache: npm
42+
43+
- name: Update npm
44+
run: npm i --prefer-online -g npm@latest
45+
46+
- name: Install dependencies
47+
run: npm ci
48+
49+
- name: Run Tap Tests
50+
run: npm test
51+
52+
- name: List dependencies
53+
run: npm ls -a

.gitignore

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# ignore everything in the root
2+
/*
3+
4+
# keep these
5+
!/.github
6+
!**/.gitignore
7+
!/package.json
8+
!/package-lock.json
9+
!/bin
10+
!/lib
11+
!/map.js
12+
!/tap-snapshots
13+
!/test
14+
!/README*
15+
!/LICENSE*

LICENSE.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
ISC License
2+
3+
Copyright npm, Inc.
4+
5+
Permission to use, copy, modify, and/or distribute this
6+
software for any purpose with or without fee is hereby
7+
granted, provided that the above copyright notice and this
8+
permission notice appear in all copies.
9+
10+
THE SOFTWARE IS PROVIDED "AS IS" AND NPM DISCLAIMS ALL
11+
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
12+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO
13+
EVENT SHALL NPM BE LIABLE FOR ANY SPECIAL, DIRECT,
14+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
16+
WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
17+
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE
18+
USE OR PERFORMANCE OF THIS SOFTWARE.

lib/common/get-options.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// given an input that may or may not be an object, return an object that has
2+
// a copy of every defined property listed in 'copy'. if the input is not an
3+
// object, assign it to the property named by 'wrap'
4+
const getOptions = (input, { copy, wrap }) => {
5+
const result = {}
6+
7+
if (input && typeof input === 'object') {
8+
for (const prop of copy) {
9+
if (input[prop] !== undefined) {
10+
result[prop] = input[prop]
11+
}
12+
}
13+
} else {
14+
result[wrap] = input
15+
}
16+
17+
return result
18+
}
19+
20+
module.exports = getOptions

lib/common/node.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
const semver = require('semver')
2+
3+
const satisfies = (range) => {
4+
return semver.satisfies(process.version, range, { includePrerelease: true })
5+
}
6+
7+
module.exports = {
8+
satisfies,
9+
}

lib/common/owner.js

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
const { dirname, resolve } = require('path')
2+
const { fileURLToPath } = require('url')
3+
4+
const fs = require('../fs.js')
5+
6+
// given a path, find the owner of the nearest parent
7+
const find = async (path) => {
8+
// if we have no getuid, permissions are irrelevant on this platform
9+
if (!process.getuid) {
10+
return {}
11+
}
12+
13+
// fs methods accept URL objects with a scheme of file: so we need to unwrap
14+
// those into an actual path string before we can resolve it
15+
const resolved = path != null && path.href && path.origin
16+
? resolve(fileURLToPath(path))
17+
: resolve(path)
18+
19+
let stat
20+
21+
try {
22+
stat = await fs.lstat(resolved)
23+
} finally {
24+
// if we got a stat, return its contents
25+
if (stat) {
26+
return { uid: stat.uid, gid: stat.gid }
27+
}
28+
29+
// try the parent directory
30+
if (resolved !== dirname(resolved)) {
31+
return find(dirname(resolved))
32+
}
33+
34+
// no more parents, never got a stat, just return an empty object
35+
return {}
36+
}
37+
}
38+
39+
// given a path, uid, and gid update the ownership of the path if necessary
40+
const update = async (path, uid, gid) => {
41+
// nothing to update, just exit
42+
if (uid === undefined && gid === undefined) {
43+
return
44+
}
45+
46+
try {
47+
// see if the permissions are already the same, if they are we don't
48+
// need to do anything, so return early
49+
const stat = await fs.stat(path)
50+
if (uid === stat.uid && gid === stat.gid) {
51+
return
52+
}
53+
} catch (err) {}
54+
55+
try {
56+
await fs.chown(path, uid, gid)
57+
} catch (err) {}
58+
}
59+
60+
// accepts a `path` and the `owner` property of an options object and normalizes
61+
// it into an object with numerical `uid` and `gid`
62+
const validate = async (path, input) => {
63+
let uid
64+
let gid
65+
66+
if (typeof input === 'string' || typeof input === 'number') {
67+
uid = input
68+
gid = input
69+
} else if (input && typeof input === 'object') {
70+
uid = input.uid
71+
gid = input.gid
72+
}
73+
74+
if (uid === 'inherit' || gid === 'inherit') {
75+
const owner = await find(path)
76+
if (uid === 'inherit') {
77+
uid = owner.uid
78+
}
79+
80+
if (gid === 'inherit') {
81+
gid = owner.gid
82+
}
83+
}
84+
85+
return { uid, gid }
86+
}
87+
88+
module.exports = {
89+
find,
90+
update,
91+
validate,
92+
}

lib/copy-file.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
const fs = require('./fs.js')
2+
const getOptions = require('./common/get-options.js')
3+
const owner = require('./common/owner.js')
4+
5+
const copyFile = async (src, dest, opts) => {
6+
const options = getOptions(opts, {
7+
copy: ['mode', 'owner'],
8+
wrap: 'mode',
9+
})
10+
11+
const { uid, gid } = await owner.validate(dest, options.owner)
12+
13+
// the node core method as of 16.5.0 does not support the mode being in an
14+
// object, so we have to pass the mode value directly
15+
const result = await fs.copyFile(src, dest, options.mode)
16+
17+
await owner.update(dest, uid, gid)
18+
19+
return result
20+
}
21+
22+
module.exports = copyFile

lib/fs.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
const fs = require('fs')
2+
const promisify = require('@gar/promisify')
3+
4+
// this module returns the core fs module wrapped in a proxy that promisifies
5+
// method calls within the getter. we keep it in a separate module so that the
6+
// overridden methods have a consistent way to get to promisified fs methods
7+
// without creating a circular dependency
8+
module.exports = promisify(fs)

lib/index.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
module.exports = {
2+
...require('./fs.js'),
3+
copyFile: require('./copy-file.js'),
4+
mkdir: require('./mkdir/index.js'),
5+
mkdtemp: require('./mkdtemp.js'),
6+
rm: require('./rm/index.js'),
7+
withTempDir: require('./with-temp-dir.js'),
8+
writeFile: require('./write-file.js'),
9+
}

lib/mkdir/index.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
const fs = require('../fs.js')
2+
const getOptions = require('../common/get-options.js')
3+
const node = require('../common/node.js')
4+
const owner = require('../common/owner.js')
5+
6+
const polyfill = require('./polyfill.js')
7+
8+
// node 10.12.0 added the options parameter, which allows recursive and mode
9+
// properties to be passed
10+
const useNative = node.satisfies('>=10.12.0')
11+
12+
// extends mkdir with the ability to specify an owner of the new dir
13+
const mkdir = async (path, opts) => {
14+
const options = getOptions(opts, {
15+
copy: ['mode', 'recursive', 'owner'],
16+
wrap: 'mode',
17+
})
18+
const { uid, gid } = await owner.validate(path, options.owner)
19+
20+
// the polyfill is tested separately from this module, no need to hack
21+
// process.version to try to trigger it just for coverage
22+
// istanbul ignore next
23+
const result = useNative
24+
? await fs.mkdir(path, options)
25+
: await polyfill(path, options)
26+
27+
await owner.update(path, uid, gid)
28+
29+
return result
30+
}
31+
32+
module.exports = mkdir

0 commit comments

Comments
 (0)