clever-tools 2.8.0 → 2.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # clever-tools changelog
2
2
 
3
+ ## Unrelease (????-??-??)
4
+
5
+ ...
6
+
7
+ ## 2.9.1 (2022-02-28)
8
+
9
+ * Upgrade opn to open
10
+
11
+ ## 2.9.0 (2022-01-12)
12
+
13
+ * Add NewRelic support in `clever drains`
14
+
15
+ ## 2.8.1 (2022-01-10)
16
+
17
+ * Fix `colors` to `1.4.0`
18
+
3
19
  ## 2.8.0 (2021-02-09)
4
20
 
5
21
  * Add options support for `clever addon create` (options like encryption-at-rest)
package/README.md CHANGED
@@ -6,7 +6,7 @@ Command Line Interface for Clever Cloud.
6
6
 
7
7
  ## Installation
8
8
 
9
- The clever-tools CLI can be installed through many different channels depending on your system setup.
9
+ The clever-tools CLI can be installed through many channels depending on your system setup.
10
10
 
11
11
  ### Via npm
12
12
 
@@ -26,38 +26,57 @@ npm install -g clever-tools@beta
26
26
 
27
27
  #### Debian/Ubuntu (.deb)
28
28
 
29
+ ---
30
+
31
+ ##### Warning
32
+
33
+ We recently moved from Bintray to a self-hosted Nexus repository available at [https://nexus.clever-cloud.com](https://nexus.clever-cloud.com).
34
+ If you were using Bintray to install our `.deb` packages, you will need to edit your `/etc/apt/sources.list` and remove the old entry with `https://dl.bintray.com/clevercloud/deb` before going further.
35
+
36
+ ---
37
+
29
38
  If you are using a GNU/Linux distribution that uses `.deb` packages like Debian or Ubuntu, you can run:
30
39
 
31
40
  ```sh
32
- apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys "379CE192D401AB61"
33
- echo "deb https://dl.bintray.com/clevercloud/deb stable main" | tee -a /etc/apt/sources.list
41
+ curl -fsSL https://clever-tools.clever-cloud.com/gpg/cc-nexus-deb.public.gpg.key | gpg --dearmor -o /usr/share/keyrings/cc-nexus-deb.gpg
42
+ echo "deb [signed-by=/usr/share/keyrings/cc-nexus-deb.gpg] https://nexus.clever-cloud.com/repository/deb stable main" | tee -a /etc/apt/sources.list
34
43
  apt-get update
35
44
  apt-get install clever-tools
36
45
  ```
37
46
 
38
47
  NOTES:
39
48
 
40
- * The `.deb` packages are hosted on Bintray (their GPG key is required to trust their signed packages).
49
+ * The `.deb` packages are hosted on Clever Cloud's public Nexus instance available at [https://nexus.clever-cloud.com](https://nexus.clever-cloud.com).
50
+ * Our PGP key is required to trust the repository.
41
51
  * If you want access to the beta channel, you can use this in your `sources.list`:
42
52
 
43
53
  ```sh
44
- echo "deb https://dl.bintray.com/clevercloud/deb unstable beta" | tee -a /etc/apt/sources.list
54
+ echo "deb [signed-by=/usr/share/keyrings/cc-nexus-deb.gpg] https://nexus.clever-cloud.com/repository/deb-beta beta main" | tee -a /etc/apt/sources.list
45
55
  ```
46
56
 
47
57
  #### CentOS/Fedora (.rpm)
48
58
 
59
+ ---
60
+
61
+ ##### Warning
62
+
63
+ We recently moved from Bintray to a self-hosted Nexus repository available at [https://nexus.clever-cloud.com](https://nexus.clever-cloud.com).
64
+ If you were using Bintray to install our `.rpm` packages, you will need to remove the old `/etc/yum.repos.d/bintray-clevercloud-rpm.repo` before going further.
65
+
66
+ ---
67
+
49
68
  If you are using a GNU/Linux distribution that uses `.rpm` packages like CentOS or Fedora, you can run:
50
69
 
51
70
  ```sh
52
- curl https://bintray.com/clevercloud/rpm/rpm > /etc/yum.repos.d/bintray-clevercloud-rpm.repo
53
- echo "exclude=*beta*" >> /etc/yum.repos.d/bintray-clevercloud-rpm.repo
71
+ curl -s https://clever-tools.clever-cloud.com/repos/cc-nexus-rpm.repo > /etc/yum.repos.d/cc-nexus-rpm.repo
72
+ yum update
54
73
  yum install clever-tools
55
74
  ```
56
75
 
57
76
  NOTES:
58
77
 
59
- * The `.rpm` packages are hosted on Bintray.
60
- * If you want access to the beta channel, you can omit the second line which contains an exclude option.
78
+ * The `.rpm` packages are hosted on Clever Cloud's public Nexus instance available at [https://nexus.clever-cloud.com](https://nexus.clever-cloud.com).
79
+ * If you want access to the beta channel, you will need to edit `/etc/yum.repos.d/cc-nexus-rpm.repo` and set `enabled=1` for the `[clever-tools-beta]`.
61
80
 
62
81
  #### Arch Linux
63
82
 
@@ -98,11 +117,11 @@ NOTES:
98
117
  * The packages are available on Clever Cloud's Cellar bucket: [clever-tools-latest_linux.tar.gz](https://clever-tools.clever-cloud.com/releases/latest/clever-tools-latest_linux.tar.gz).
99
118
  * You can also retrieve any release (including beta) on this Cellar bucket by replacing `latest` (path and filename) with the version number you need.
100
119
 
101
- ### On MacOS
120
+ ### On macOS
102
121
 
103
122
  #### Using homebrew
104
123
 
105
- If you are using MacOS and you have [homebrew](https://brew.sh) installed, you can run:
124
+ If you are using macOS and you have [homebrew](https://brew.sh) installed, you can run:
106
125
 
107
126
  ```sh
108
127
  brew install CleverCloud/homebrew-tap/clever-tools
@@ -114,7 +133,7 @@ NOTES:
114
133
 
115
134
  #### Using the `.tar.gz` archive
116
135
 
117
- If you are using MacOS but you don't have [homebrew](https://brew.sh) installed, you can download a `.tar.gz` archive and extract the binary in your `PATH`:
136
+ If you are using macOS, but you don't have [homebrew](https://brew.sh) installed, you can download a `.tar.gz` archive and extract the binary in your `PATH`:
118
137
 
119
138
  ```sh
120
139
  curl -O https://clever-tools.clever-cloud.com/releases/latest/clever-tools-latest_macos.tar.gz
@@ -131,20 +150,29 @@ NOTES:
131
150
 
132
151
  #### Using chocolatey
133
152
 
134
- If you are using Windows and you have [chocolatey](https://chocolatey.org) installed, you can run:
153
+ ---
154
+
155
+ ##### Warning
156
+
157
+ We recently moved from Bintray to a self-hosted Nexus repository available at [https://nexus.clever-cloud.com](https://nexus.clever-cloud.com).
158
+ If you were using Bintray to install our `.nupkg` packages, you will need to remove the old source.
159
+
160
+ ---
161
+
162
+ If you are using Windows and you have [chocolatey](https://chocolatey.org) installed, you can run:
135
163
 
136
164
  ```bash
137
- choco sources add -n=clevercloud -s='https://api.bintray.com/nuget/clevercloud/nupkg'
165
+ choco sources add -n=clevercloud -s='https://nexus.clever-cloud.com/repository/nupkg/'
138
166
  choco install clever-tools
139
167
  ```
140
168
 
141
169
  NOTES:
142
170
 
143
- * If you want access to the beta channel, you can use `choco install --pre clever-tools` instead.
171
+ * If you want access to the beta channel, you can use `choco sources add -n=clevercloud -s='https://nexus.clever-cloud.com/repository/nupkg-beta/'` instead.
144
172
 
145
173
  #### Using the `.zip` archive
146
174
 
147
- If you are using Windows but you don't have [chocolatey](https://chocolatey.org) installed, you can download a `.zip` archive and extract the binary in your `PATH`.
175
+ If you are using Windows, but you don't have [chocolatey](https://chocolatey.org) installed, you can download a `.zip` archive and extract the binary in your `PATH`.
148
176
 
149
177
  NOTES:
150
178
 
@@ -160,15 +188,23 @@ docker pull clevercloud/clever-tools
160
188
  docker run --rm clever-tools <command>
161
189
  ```
162
190
 
191
+ #### Dockerfile
192
+
193
+ In your `Dockerfile` you can copy the clever-tools CLI from the image itself with a simple one liner:
194
+
195
+ ```Dockerfile
196
+ COPY --from=clevercloud/clever-tools /bin/clever /usr/local/bin/clever
197
+ ```
198
+
163
199
  ### Via Nix
164
200
 
165
- If you are using Nix, you will find a Nix derivation on Fretlink's Github repository: https://github.com/fretlink/clever-tools-nix
201
+ If you are using Nix, you will find a Nix derivation on Fretlink's GitHub repository: https://github.com/fretlink/clever-tools-nix
166
202
 
167
203
  ## Enabling autocompletion
168
204
 
169
205
  The clever-tools CLI comes with a comprehensive auto-completion system.
170
206
  Some installation methods like `.deb` packages, `.rpm` packages or brew will try to enable it automatically.
171
- If it does not work, try this for bash:
207
+ If it does not work, try this for bash:
172
208
 
173
209
  ```bash
174
210
  clever --bash-autocomplete-script $(which clever) | sudo tee /usr/share/bash-completion/completions/clever
@@ -193,7 +229,7 @@ clever login
193
229
  It will open the Web console in your browser and reuse your existing session if you're already logged in.
194
230
 
195
231
  `clever login` tries to open a browser through `xdg-open` on GNU/Linux systems (and in bash for windows).
196
- Make sure you have `xdg-utils` available as well as a default browser set (or you can copy and paste the URL displayed in the console.
232
+ Make sure you have `xdg-utils` available as well as a default browser set, or you can copy and paste the URL displayed in the console.
197
233
 
198
234
  ### Create an application
199
235
 
@@ -288,6 +324,7 @@ Where `DRAIN-TYPE` is one of:
288
324
  - `HTTP`: for TCP syslog endpoint (note that this endpoint has optional username/passwordparameters as HTTP Basic Authentication);
289
325
  - `ElasticSearch`: for ElasticSearch endpoint (note that this endpoint requires username/password parameters as HTTP Basic Authentication);
290
326
  - `DatadogHTTP`: for Datadog endpoint (note that this endpoint needs your Datadog API Key).
327
+ - `NewRelicHTTP`: for NewRelic endpoint (note that this endpoint needs your NewRelic API Key).
291
328
 
292
329
  #### ElasticSearch logs drains
293
330
 
@@ -305,6 +342,16 @@ clever drain create DatadogHTTP "https://http-intake.logs.datadoghq.com/v1/input
305
342
 
306
343
  Please note that the `host` query parameter is not mandatory: in the Datadog pipeline configuration, you can map `@source_host` which is the host provided by Clever Cloud in logs as `host` property.
307
344
 
345
+ #### NewRelic logs drains
346
+
347
+ NewRelic has two zones, EU and US. An account on one zone is not available on the other, make sure to target the good EU or US intake endpoint.
348
+
349
+ To create a [NewRelic](https://docs.newrelic.com/docs/logs/log-api/introduction-log-api/) drain, you just need to use:
350
+
351
+ ```sh
352
+ clever drain create NewRelicHTTP "https://log-api.eu.newrelic.com/log/v1" --api-key <API_KEY>
353
+ ```
354
+
308
355
  ### Display help
309
356
 
310
357
  You can display help about each command with `clever help`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clever-tools",
3
- "version": "2.8.0",
3
+ "version": "2.9.1",
4
4
  "description": "Command Line Interface for Clever Cloud.",
5
5
  "main": "bin/clever.js",
6
6
  "keywords": [
@@ -25,45 +25,45 @@
25
25
  "scripts/*.sh"
26
26
  ],
27
27
  "dependencies": {
28
- "@clevercloud/client": "^7.2.0",
28
+ "@clevercloud/client": "^7.4.0",
29
29
  "clf-date": "^0.2.0",
30
30
  "cliparse": "^0.3.3",
31
- "colors": "^1.4.0",
31
+ "colors": "1.4.0",
32
32
  "common-env": "^6.4.0",
33
- "eventsource": "^1.0.7",
34
- "isomorphic-git": "1.7.4",
33
+ "eventsource": "^1.1.0",
34
+ "isomorphic-git": "^1.8.2",
35
35
  "linux-release-info": "^3.0.0",
36
- "lodash": "^4.17.20",
36
+ "lodash": "^4.17.21",
37
37
  "mkdirp": "^1.0.4",
38
- "moment": "^2.27.0",
39
- "opn": "^6.0.0",
40
- "slugify": "^1.4.5",
41
- "string-length": "^3.1.0",
42
- "superagent": "^6.0.0",
38
+ "moment": "^2.29.1",
39
+ "open": "^8.4.0",
40
+ "slugify": "^1.5.3",
41
+ "string-length": "^4.0.2",
42
+ "superagent": "^6.1.0",
43
43
  "text-table": "^0.2.0",
44
- "update-notifier": "^4.1.1",
45
- "ws": "^7.3.1",
44
+ "update-notifier": "^5.1.0",
45
+ "ws": "^7.4.6",
46
46
  "xdg": "^0.1.1"
47
47
  },
48
48
  "devDependencies": {
49
- "aws-sdk": "^2.735.0",
50
- "chai": "^4.2.0",
51
- "del": "^5.1.0",
52
- "eslint": "^7.7.0",
53
- "eslint-config-standard": "^14.1.1",
54
- "eslint-plugin-import": "^2.22.0",
49
+ "aws-sdk": "^2.919.0",
50
+ "chai": "^4.3.4",
51
+ "del": "^6.0.0",
52
+ "eslint": "^7.27.0",
53
+ "eslint-config-standard": "^16.0.3",
54
+ "eslint-plugin-import": "^2.23.4",
55
55
  "eslint-plugin-node": "^11.1.0",
56
- "eslint-plugin-promise": "^4.2.1",
57
- "eslint-plugin-standard": "^4.0.1",
58
- "fs-extra": "^9.0.1",
59
- "glob": "^7.1.6",
60
- "grunt": "^1.2.1",
61
- "grunt-cli": "^1.3.2",
56
+ "eslint-plugin-promise": "^5.1.0",
57
+ "eslint-plugin-standard": "^5.0.0",
58
+ "fs-extra": "^10.0.0",
59
+ "glob": "^7.1.7",
60
+ "grunt": "^1.4.1",
61
+ "grunt-cli": "^1.4.3",
62
62
  "grunt-http": "^2.3.3",
63
63
  "grunt-mocha-test": "^0.13.3",
64
- "mocha": "^8.1.1",
65
- "pkg": "^4.4.9",
66
- "semver": "^7.3.2"
64
+ "mocha": "^8.4.0",
65
+ "pkg": "^5.2.1",
66
+ "semver": "^7.3.5"
67
67
  },
68
68
  "scripts": {
69
69
  "pretest": "npm run lint",
@@ -83,5 +83,9 @@
83
83
  "scripts": [
84
84
  "src/**/*.js"
85
85
  ]
86
+ },
87
+ "volta": {
88
+ "node": "12.22.8",
89
+ "npm": "6.14.15"
86
90
  }
87
91
  }
@@ -2,7 +2,7 @@
2
2
 
3
3
  const AppConfig = require('../models/app_configuration.js');
4
4
  const Logger = require('../logger.js');
5
- const openPage = require('opn');
5
+ const openPage = require('open');
6
6
 
7
7
  async function openConsole (params) {
8
8
  const { alias } = params.options;
@@ -6,7 +6,7 @@ const util = require('util');
6
6
  const delay = util.promisify(setTimeout);
7
7
 
8
8
  const colors = require('colors/safe');
9
- const open = require('opn');
9
+ const open = require('open');
10
10
  const superagent = require('superagent');
11
11
 
12
12
  const Logger = require('../logger.js');
@@ -55,6 +55,7 @@ function getEmailNotificationTargets (notifTargets) {
55
55
  if (el.toLowerCase() === 'organisation') {
56
56
  return { type: 'organisation' };
57
57
  }
58
+ return null;
58
59
  })
59
60
  .filter((e) => e != null);
60
61
  }
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const openPage = require('opn');
3
+ const openPage = require('open');
4
4
 
5
5
  const AppConfig = require('../models/app_configuration.js');
6
6
  const Domain = require('../models/domain.js');
@@ -23,7 +23,8 @@ function formatSource (l) {
23
23
  if (l.s != null) {
24
24
  const location = l.s.ct ? `${l.s.ct}, ${l.s.co}` : l.s.co;
25
25
  return `${l.ipS} - ${location}`;
26
- } else {
26
+ }
27
+ else {
27
28
  return l.ipS;
28
29
  }
29
30
  }
@@ -1,11 +1,7 @@
1
1
  'use strict';
2
2
 
3
- const fs = require('fs');
3
+ const { promises: fs } = require('fs');
4
4
  const path = require('path');
5
- const util = require('util');
6
-
7
- const readFile = util.promisify(fs.readFile);
8
- const writeFile = util.promisify(fs.writeFile);
9
5
 
10
6
  const commonEnv = require('common-env');
11
7
  const mkdirp = require('mkdirp');
@@ -14,7 +10,7 @@ const xdg = require('xdg');
14
10
  const Logger = require('../logger.js');
15
11
  const env = commonEnv(Logger);
16
12
 
17
- function getConfigPath () {
13
+ function getConfigDir () {
18
14
  if (process.platform === 'win32') {
19
15
  return path.resolve(process.env.APPDATA, 'clever-cloud');
20
16
  }
@@ -23,6 +19,37 @@ function getConfigPath () {
23
19
  }
24
20
  }
25
21
 
22
+ function getConfigPath () {
23
+ const configDir = getConfigDir();
24
+ return path.resolve(configDir, 'clever-tools.json');
25
+ }
26
+
27
+ async function isFile (path) {
28
+ try {
29
+ const pathStat = await fs.stat(path);
30
+ return pathStat.isFile();
31
+ }
32
+ catch (e) {
33
+ return false;
34
+ }
35
+ }
36
+
37
+ async function maybeMigrateFromLegacyConfigurationPath () {
38
+ // This used to be a file
39
+ const configDir = getConfigDir();
40
+ const legacyConfig = await isFile(configDir);
41
+ // If it is still a file, we replace it with a dir and move it inside
42
+ if (legacyConfig) {
43
+ const tmpConfigFile = `${configDir}.tmp`;
44
+ const configFile = getConfigPath();
45
+
46
+ // Rename so that we can create the directory
47
+ await fs.rename(configDir, tmpConfigFile);
48
+ await mkdirp(configDir, { mode: 0o700 });
49
+ await fs.rename(tmpConfigFile, configFile);
50
+ }
51
+ }
52
+
26
53
  async function loadOAuthConf () {
27
54
  Logger.debug('Load configuration from environment variables');
28
55
  if (process.env.CLEVER_TOKEN != null && process.env.CLEVER_SECRET != null) {
@@ -32,8 +59,9 @@ async function loadOAuthConf () {
32
59
  };
33
60
  }
34
61
  Logger.debug('Load configuration from ' + conf.CONFIGURATION_FILE);
62
+ await maybeMigrateFromLegacyConfigurationPath();
35
63
  try {
36
- const rawFile = await readFile(conf.CONFIGURATION_FILE);
64
+ const rawFile = await fs.readFile(conf.CONFIGURATION_FILE);
37
65
  return JSON.parse(rawFile);
38
66
  }
39
67
  catch (error) {
@@ -47,7 +75,7 @@ async function writeOAuthConf (oauthData) {
47
75
  const configDir = path.dirname(conf.CONFIGURATION_FILE);
48
76
  try {
49
77
  await mkdirp(configDir, { mode: 0o700 });
50
- await writeFile(conf.CONFIGURATION_FILE, JSON.stringify(oauthData));
78
+ await fs.writeFile(conf.CONFIGURATION_FILE, JSON.stringify(oauthData));
51
79
  }
52
80
  catch (error) {
53
81
  throw new Error(`Cannot write configuration to ${conf.CONFIGURATION_FILE}\n${error.message}`);
@@ -30,6 +30,7 @@ async function waitForDeploymentStart ({ ownerId, appId, deploymentId, commitId,
30
30
  const isNew = knownDeployments.every(({ uuid }) => uuid !== d.uuid);
31
31
  return isNew && d.commit === commitId;
32
32
  }
33
+ return false;
33
34
  });
34
35
  if (deployment != null) {
35
36
  Logger.debug(`Deployment has started (state:${deployment.state})`);
@@ -8,12 +8,13 @@ const DRAIN_TYPES = [
8
8
  { id: 'HTTP', credentials: 'OPTIONAL' },
9
9
  { id: 'ElasticSearch', credentials: 'MANDATORY' },
10
10
  { id: 'DatadogHTTP' },
11
+ { id: 'NewRelicHTTP', apiKey: 'MANDATORY' },
11
12
  ];
12
13
 
13
14
  function createDrainBody (appId, drainTargetURL, drainTargetType, drainTargetCredentials, drainTargetConfig) {
14
15
 
15
- if (!authorizeDrainCreation(drainTargetType, drainTargetCredentials)) {
16
- throw new Error("Credentials are: optional for HTTP, mandatory for ElasticSearch and TCPSyslog/UDPSyslog don't need them.");
16
+ if (!authorizeDrainCreation(drainTargetType, drainTargetCredentials, drainTargetConfig)) {
17
+ throw new Error("Credentials are: optional for HTTP, mandatory for ElasticSearch, NewRelicHTTP and TCPSyslog/UDPSyslog don't need them.");
17
18
  }
18
19
 
19
20
  const body = {
@@ -26,20 +27,37 @@ function createDrainBody (appId, drainTargetURL, drainTargetType, drainTargetCre
26
27
  password: drainTargetCredentials.password || '',
27
28
  };
28
29
  }
30
+ if (keyExist(drainTargetConfig)) {
31
+ body.APIKey = drainTargetConfig.apiKey;
32
+ }
29
33
  return body;
30
34
  }
31
35
 
32
- function authorizeDrainCreation (drainTargetType, drainTargetCredentials) {
36
+ function authorizeDrainCreation (drainTargetType, drainTargetCredentials, drainTargetConfig) {
33
37
  if (drainTypeExists(drainTargetType)) {
34
38
  // retrieve creds for drain type ('mandatory', 'optional', undefined)
35
- const status = credentialsStatus(drainTargetType).credentials;
36
- if (status === 'MANDATORY') {
39
+ const credStatus = credentialsStatus(drainTargetType).credentials;
40
+ const keyStatus = credentialsStatus(drainTargetType).apiKey;
41
+
42
+ if (credStatus === 'MANDATORY') {
37
43
  return credentialsExist(drainTargetCredentials);
38
44
  }
39
- if (status === 'OPTIONAL') {
45
+ if (credStatus === 'OPTIONAL') {
46
+ return true;
47
+ }
48
+ if (!credStatus && !keyStatus) {
49
+ return credentialsEmpty(drainTargetCredentials);
50
+ }
51
+
52
+ if (keyStatus === 'MANDATORY') {
53
+ return keyExist(drainTargetConfig);
54
+ }
55
+ if (keyStatus === 'OPTIONAL') {
40
56
  return true;
41
57
  }
42
- return credentialsEmpty(drainTargetCredentials);
58
+ if (!keyStatus) {
59
+ return keyEmpty(drainTargetConfig);
60
+ }
43
61
  }
44
62
  }
45
63
 
@@ -59,6 +77,14 @@ function credentialsEmpty ({ username, password }) {
59
77
  return username == null && password == null;
60
78
  }
61
79
 
80
+ function keyExist ({ apiKey }) {
81
+ return apiKey != null;
82
+ }
83
+
84
+ function keyEmpty ({ apiKey }) {
85
+ return apiKey == null;
86
+ }
87
+
62
88
  function listDrainTypes () {
63
89
  return autocomplete.words(DRAIN_TYPES.map((type) => type.id));
64
90
  }
@@ -1,14 +1,11 @@
1
1
  'use strict';
2
2
 
3
3
  const { join } = require('path');
4
- const fs = require('fs');
5
- const { promisify } = require('util');
6
-
7
- const fsStat = promisify(fs.stat);
4
+ const { promises: fs } = require('fs');
8
5
 
9
6
  function findPath (dir, name) {
10
7
  const fullPath = join(dir, name);
11
- return fsStat(fullPath)
8
+ return fs.stat(fullPath)
12
9
  .then(() => dir)
13
10
  .catch((e) => {
14
11
  if (e.code === 'ENOENT' && dir !== '/') {