Skip to content

Commit 11bf8d0

Browse files
author
Pelle Wessman
committed
Extract path related logic from create command
1 parent ba84ba3 commit 11bf8d0

File tree

2 files changed

+118
-116
lines changed

2 files changed

+118
-116
lines changed

lib/commands/report/create.js

Lines changed: 1 addition & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,14 @@
11
/* eslint-disable no-console */
22

3-
import { stat } from 'node:fs/promises'
4-
import path from 'node:path'
5-
63
import meow from 'meow'
74
import ora from 'ora'
8-
import { ErrorWithCause } from 'pony-cause'
95

106
import { handleApiCall, handleUnsuccessfulApiResponse } from '../../utils/api-helpers.js'
117
import { ChalkOrMarkdown, logSymbols } from '../../utils/chalk-markdown.js'
12-
import { InputError } from '../../utils/errors.js'
138
import { printFlagList } from '../../utils/formatting.js'
149
import { createDebugLogger } from '../../utils/misc.js'
10+
import { resolvePackagePaths } from '../../utils/path-resolve.js'
1511
import { setupSdk } from '../../utils/sdk.js'
16-
import { isErrnoException } from '../../utils/type-helpers.js'
1712
import { fetchReportData, formatReportDataOutput } from './view.js'
1813

1914
/** @type {import('../../utils/meow-with-subcommands').CliSubcommand} */
@@ -210,113 +205,3 @@ function formatReportCreationOutput (data, { outputJson, outputMarkdown }) {
210205

211206
console.log('\nNew report: ' + format.hyperlink(data.id, data.url, { fallbackToUrl: true }))
212207
}
213-
214-
// TODO: Add globbing support with support for ignoring, as a "./**/package.json" in a project also traverses eg. node_modules
215-
/**
216-
* Takes paths to folders and/or package.json / package-lock.json files and resolves to package.json + package-lock.json pairs (where feasible)
217-
*
218-
* @param {string} cwd
219-
* @param {string[]} inputPaths
220-
* @returns {Promise<string[]>}
221-
* @throws {InputError}
222-
*/
223-
async function resolvePackagePaths (cwd, inputPaths) {
224-
const packagePathLookups = inputPaths.map(async (filePath) => {
225-
const packagePath = await resolvePackagePath(cwd, filePath)
226-
return findComplementaryPackageFile(packagePath)
227-
})
228-
229-
const packagePaths = await Promise.all(packagePathLookups)
230-
231-
const uniquePackagePaths = new Set(packagePaths.flat())
232-
233-
return [...uniquePackagePaths]
234-
}
235-
236-
/**
237-
* Resolves a package.json / package-lock.json path from a relative folder / file path
238-
*
239-
* @param {string} cwd
240-
* @param {string} inputPath
241-
* @returns {Promise<string>}
242-
* @throws {InputError}
243-
*/
244-
async function resolvePackagePath (cwd, inputPath) {
245-
const filePath = path.resolve(cwd, inputPath)
246-
/** @type {string|undefined} */
247-
let filePathAppended
248-
249-
try {
250-
const fileStat = await stat(filePath)
251-
252-
if (fileStat.isDirectory()) {
253-
filePathAppended = path.resolve(filePath, 'package.json')
254-
}
255-
} catch (err) {
256-
if (isErrnoException(err) && err.code === 'ENOENT') {
257-
throw new InputError(`Expected '${inputPath}' to point to an existing file or directory`)
258-
}
259-
throw new ErrorWithCause('Failed to resolve path to package.json', { cause: err })
260-
}
261-
262-
if (filePathAppended) {
263-
/** @type {import('node:fs').Stats} */
264-
let filePathAppendedStat
265-
266-
try {
267-
filePathAppendedStat = await stat(filePathAppended)
268-
} catch (err) {
269-
if (isErrnoException(err) && err.code === 'ENOENT') {
270-
throw new InputError(`Expected directory '${inputPath}' to contain a package.json file`)
271-
}
272-
throw new ErrorWithCause('Failed to resolve package.json in directory', { cause: err })
273-
}
274-
275-
if (!filePathAppendedStat.isFile()) {
276-
throw new InputError(`Expected '${filePathAppended}' to be a file`)
277-
}
278-
279-
return filePathAppended
280-
}
281-
282-
return filePath
283-
}
284-
285-
/**
286-
* Finds any complementary file to a package.json or package-lock.json
287-
*
288-
* @param {string} packagePath
289-
* @returns {Promise<string[]>}
290-
* @throws {InputError}
291-
*/
292-
async function findComplementaryPackageFile (packagePath) {
293-
const basename = path.basename(packagePath)
294-
const dirname = path.dirname(packagePath)
295-
296-
if (basename === 'package-lock.json') {
297-
// We need the package file as well
298-
return [
299-
packagePath,
300-
path.resolve(dirname, 'package.json')
301-
]
302-
}
303-
304-
if (basename === 'package.json') {
305-
const lockfilePath = path.resolve(dirname, 'package-lock.json')
306-
try {
307-
const lockfileStat = await stat(lockfilePath)
308-
if (lockfileStat.isFile()) {
309-
return [packagePath, lockfilePath]
310-
}
311-
} catch (err) {
312-
if (isErrnoException(err) && err.code === 'ENOENT') {
313-
return [packagePath]
314-
}
315-
throw new ErrorWithCause(`Unexpected error when finding a lockfile for '${packagePath}'`, { cause: err })
316-
}
317-
318-
throw new InputError(`Encountered a non-file at lockfile path '${lockfilePath}'`)
319-
}
320-
321-
throw new InputError(`Expected '${packagePath}' to point to a package.json or package-lock.json or to a folder containing a package.json`)
322-
}

lib/utils/path-resolve.js

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import { stat } from 'node:fs/promises'
2+
import path from 'node:path'
3+
4+
import { ErrorWithCause } from 'pony-cause'
5+
6+
import { InputError } from './errors.js'
7+
import { isErrnoException } from './type-helpers.js'
8+
9+
// TODO: Add globbing support with support for ignoring, as a "./**/package.json" in a project also traverses eg. node_modules
10+
/**
11+
* Takes paths to folders and/or package.json / package-lock.json files and resolves to package.json + package-lock.json pairs (where feasible)
12+
*
13+
* @param {string} cwd
14+
* @param {string[]} inputPaths
15+
* @returns {Promise<string[]>}
16+
* @throws {InputError}
17+
*/
18+
export async function resolvePackagePaths (cwd, inputPaths) {
19+
const packagePathLookups = inputPaths.map(async (filePath) => {
20+
const packagePath = await resolvePackagePath(cwd, filePath)
21+
return findComplementaryPackageFile(packagePath)
22+
})
23+
24+
const packagePaths = await Promise.all(packagePathLookups)
25+
26+
const uniquePackagePaths = new Set(packagePaths.flat())
27+
28+
return [...uniquePackagePaths]
29+
}
30+
31+
/**
32+
* Resolves a package.json / package-lock.json path from a relative folder / file path
33+
*
34+
* @param {string} cwd
35+
* @param {string} inputPath
36+
* @returns {Promise<string>}
37+
* @throws {InputError}
38+
*/
39+
async function resolvePackagePath (cwd, inputPath) {
40+
const filePath = path.resolve(cwd, inputPath)
41+
/** @type {string|undefined} */
42+
let filePathAppended
43+
44+
try {
45+
const fileStat = await stat(filePath)
46+
47+
if (fileStat.isDirectory()) {
48+
filePathAppended = path.resolve(filePath, 'package.json')
49+
}
50+
} catch (err) {
51+
if (isErrnoException(err) && err.code === 'ENOENT') {
52+
throw new InputError(`Expected '${inputPath}' to point to an existing file or directory`)
53+
}
54+
throw new ErrorWithCause('Failed to resolve path to package.json', { cause: err })
55+
}
56+
57+
if (filePathAppended) {
58+
/** @type {import('node:fs').Stats} */
59+
let filePathAppendedStat
60+
61+
try {
62+
filePathAppendedStat = await stat(filePathAppended)
63+
} catch (err) {
64+
if (isErrnoException(err) && err.code === 'ENOENT') {
65+
throw new InputError(`Expected directory '${inputPath}' to contain a package.json file`)
66+
}
67+
throw new ErrorWithCause('Failed to resolve package.json in directory', { cause: err })
68+
}
69+
70+
if (!filePathAppendedStat.isFile()) {
71+
throw new InputError(`Expected '${filePathAppended}' to be a file`)
72+
}
73+
74+
return filePathAppended
75+
}
76+
77+
return filePath
78+
}
79+
80+
/**
81+
* Finds any complementary file to a package.json or package-lock.json
82+
*
83+
* @param {string} packagePath
84+
* @returns {Promise<string[]>}
85+
* @throws {InputError}
86+
*/
87+
async function findComplementaryPackageFile (packagePath) {
88+
const basename = path.basename(packagePath)
89+
const dirname = path.dirname(packagePath)
90+
91+
if (basename === 'package-lock.json') {
92+
// We need the package file as well
93+
return [
94+
packagePath,
95+
path.resolve(dirname, 'package.json')
96+
]
97+
}
98+
99+
if (basename === 'package.json') {
100+
const lockfilePath = path.resolve(dirname, 'package-lock.json')
101+
try {
102+
const lockfileStat = await stat(lockfilePath)
103+
if (lockfileStat.isFile()) {
104+
return [packagePath, lockfilePath]
105+
}
106+
} catch (err) {
107+
if (isErrnoException(err) && err.code === 'ENOENT') {
108+
return [packagePath]
109+
}
110+
throw new ErrorWithCause(`Unexpected error when finding a lockfile for '${packagePath}'`, { cause: err })
111+
}
112+
113+
throw new InputError(`Encountered a non-file at lockfile path '${lockfilePath}'`)
114+
}
115+
116+
throw new InputError(`Expected '${packagePath}' to point to a package.json or package-lock.json or to a folder containing a package.json`)
117+
}

0 commit comments

Comments
 (0)