destiny
Advanced tools
Comparing version 0.2.1 to 0.3.0
{ | ||
"hooks": { | ||
"pre-commit": "lint-staged", | ||
"pre-push": "jest" | ||
"pre-push": "run-p check test" | ||
} | ||
} |
@@ -0,1 +1,20 @@ | ||
# [0.3.0](https://github.com/benawad/destiny/compare/v0.2.1...v0.3.0) (2020-02-24) | ||
### Bug Fixes | ||
- **printTree:** colorize tree ([9fd4706](https://github.com/benawad/destiny/commit/9fd4706deec7ef40238eaf0113e198f562584453)) | ||
- add generateTrees module ([e0366d6](https://github.com/benawad/destiny/commit/e0366d662d7051d9cdbe0e44fe98b1743c739a13)) | ||
- create restructure map ([4090fdd](https://github.com/benawad/destiny/commit/4090fdd90191c87f834bfc7f14651c1d5e5b09c4)) | ||
- use generateTrees and refactor formatFileStructure ([fb48957](https://github.com/benawad/destiny/commit/fb489571a67cd466cdcedd03787238eda9345b04)) | ||
- **printTree:** add leaf resolver ([514084a](https://github.com/benawad/destiny/commit/514084a5ffbd27badc2fd75716edc9aac974c988)) | ||
- **printTree:** add proper sorting of files and directories ([bc177c1](https://github.com/benawad/destiny/commit/bc177c10b7155634a15c990264ee505a82067d8c)) | ||
- **printTree:** position our leafs ([1b5bca1](https://github.com/benawad/destiny/commit/1b5bca10b3d29853feeb70f67e096f9b4a74ec3e)) | ||
### Features | ||
- **cli:** only restructure with write option ([65313bb](https://github.com/benawad/destiny/commit/65313bb35c042d0a9e89d832fbe74c67da5a6988)) | ||
- **printTree:** print tree ([ef01dae](https://github.com/benawad/destiny/commit/ef01dae807e7ca12ec535e2796bf69782df1e68f)) | ||
- add support for sass ([f70e96b](https://github.com/benawad/destiny/commit/f70e96bc064988c325d655a9e1c98c37fc06ae2f)) | ||
- remove detect-roots option ([7812dc6](https://github.com/benawad/destiny/commit/7812dc6310a6afbd0fcfb0cd7966b95ceee30dca)) | ||
## [0.2.1](https://github.com/benawad/destiny/compare/v0.2.0...v0.2.1) (2020-02-20) | ||
@@ -2,0 +21,0 @@ |
@@ -14,9 +14,9 @@ #!/usr/bin/env node | ||
var cosmiconfig = require('cosmiconfig'); | ||
var glob = _interopDefault(require('glob')); | ||
var path = _interopDefault(require('path')); | ||
var fs = require('fs-extra'); | ||
var fs__default = _interopDefault(fs); | ||
var path = _interopDefault(require('path')); | ||
var Git = _interopDefault(require('simple-git/promise')); | ||
var fs$1 = _interopDefault(require('fs')); | ||
var resolve = _interopDefault(require('resolve')); | ||
var Git = _interopDefault(require('simple-git/promise')); | ||
var glob = _interopDefault(require('glob')); | ||
@@ -34,67 +34,74 @@ const error = (err, code = 0) => { | ||
}; | ||
const info = msg => { | ||
console.info(chalk`{green.bold INFO:} ${msg}`); | ||
}; | ||
const log = msg => { | ||
console.log(msg); | ||
}; | ||
const warn = msg => { | ||
console.log(chalk`{yellow.bold WARN:} ${msg}`); | ||
console.warn(chalk`{yellow.bold WARN:} ${msg}`); | ||
}; | ||
const info = msg => { | ||
console.log(chalk`{green.bold INFO:} ${msg}`); | ||
}; | ||
var logger = { | ||
error, | ||
warn, | ||
info | ||
info, | ||
log, | ||
warn | ||
}; | ||
const isDirectory = filePath => fs.lstatSync(filePath).isDirectory(); | ||
const moveFiles = async (newStructure, parentFolder) => { | ||
const git = Git(parentFolder); | ||
let isRepo = false; | ||
const isFile = filePath => fs.lstatSync(filePath).isFile(); | ||
try { | ||
isRepo = await git.checkIsRepo(); | ||
} catch {} | ||
const globSearch = pattern => { | ||
const matches = glob.sync(pattern); | ||
const files = matches.filter(match => isFile(match)); | ||
for (const [k, newLocation] of Object.entries(newStructure)) { | ||
// skip globals | ||
if (k.includes("..")) { | ||
continue; | ||
} | ||
if (files.length === 0) { | ||
logger.error("Could not find any files for: " + pattern, 1); | ||
} | ||
const oldAbsLocation = path.resolve(path.join(parentFolder, k)); | ||
const newAbsLocation = path.resolve(path.join(parentFolder, newLocation)); | ||
return files; | ||
}; | ||
/** Recursively get all file paths. */ | ||
if (oldAbsLocation !== newAbsLocation) { | ||
// make folders | ||
fs__default.ensureDirSync(path.dirname(newAbsLocation)); | ||
let shouldGitMv = false; | ||
if (isRepo) { | ||
// check if file is tracked in git | ||
try { | ||
await git.silent(true).raw(["ls-files", "--error-unmatch", oldAbsLocation]); | ||
shouldGitMv = true; | ||
} catch {} | ||
} | ||
const getFilePaths = (paths, options) => { | ||
let { | ||
detectRoots | ||
} = options; | ||
const files = []; | ||
if (shouldGitMv) { | ||
await git.mv(oldAbsLocation, newAbsLocation); | ||
} else { | ||
// move | ||
fs__default.renameSync(oldAbsLocation, newAbsLocation); | ||
} | ||
} | ||
} | ||
}; | ||
while (paths.length > 0) { | ||
const filePath = paths.pop(); | ||
if (filePath == null || filePath.length === 0) continue; | ||
const isGlobPattern = glob.hasMagic(filePath); | ||
/** Recursively removes all empty folders. */ | ||
if (isGlobPattern) { | ||
files.push(globSearch(filePath)); | ||
continue; | ||
} | ||
function removeEmptyFolders(directory) { | ||
const files = fs$1.readdirSync(directory); | ||
if (!files) return fs$1.rmdirSync(directory); | ||
if (fs.existsSync(filePath)) { | ||
if (isFile(filePath)) { | ||
files.push([filePath]); | ||
} else if (isDirectory(filePath)) { | ||
if (detectRoots) { | ||
const childDirectories = fs.readdirSync(path.resolve(filePath)).map(x => path.join(filePath, x)).filter(x => isDirectory(x)); | ||
paths.push(...childDirectories); | ||
detectRoots = false; | ||
} else { | ||
paths.push(path.join(filePath, "/**/*.*")); | ||
} | ||
} | ||
} else { | ||
logger.error(`Unable to resolve the path: ${filePath}`); | ||
} | ||
for (const filePath of files) { | ||
const fullPath = path.resolve(directory, filePath); | ||
const isDirectory = fs$1.lstatSync(fullPath).isDirectory(); | ||
if (!isDirectory) continue; | ||
removeEmptyFolders(fullPath); | ||
const isEmpty = fs$1.readdirSync(fullPath).length === 0; | ||
if (isEmpty) fs$1.rmdirSync(fullPath); | ||
} | ||
} | ||
return files; | ||
}; | ||
function findEdges(filePath) { | ||
@@ -110,2 +117,4 @@ const importRegex = /(?:(?:from|import)\s+["'](\.[^'"]*)["'])|(?:(?:require|import)\s*\(["'](\.[^'"]*)["']\))/gm; | ||
while ((matches = importRegex.exec(fileContent)) !== null) { | ||
var _matches$; | ||
// This is necessary to avoid infinite loops with zero-width matches. | ||
@@ -116,3 +125,3 @@ if (matches.index === importRegex.lastIndex) { | ||
edges.push([filePath, matches[1] || matches[2]]); | ||
edges.push([filePath, (_matches$ = matches[1]) != null ? _matches$ : matches[2]]); | ||
} | ||
@@ -123,34 +132,234 @@ | ||
function addEdge([start, end], graph) { | ||
if (!(start in graph)) { | ||
graph[start] = []; | ||
} | ||
graph[start].push(end); | ||
function getExtensionFromImport(relativePath) { | ||
const ext = path.extname(relativePath); | ||
const includeExtension = [".js", ".jsx", ".ts", ".tsx"].includes(ext); | ||
return includeExtension ? ext : undefined; | ||
} | ||
const findSharedParent = paths => { | ||
if (paths.length === 1) return path.dirname(paths[0]); | ||
const fragments = paths.map(x => x.split("/")); | ||
const parentPaths = []; | ||
const makeImportPath = (fromPath, toPath, useForwardSlashes) => { | ||
const fromDirectory = path.dirname(fromPath); | ||
const relativePath = path.relative(fromDirectory, toPath); | ||
const relativeDirectory = path.dirname(relativePath); | ||
const ext = getExtensionFromImport(relativePath); | ||
const fileName = path.basename(relativePath, ext); | ||
let newImport = path.join(relativeDirectory, fileName); // Ensures relative imports. | ||
for (let i = 0; i < fragments[0].length; i++) { | ||
const fragment = fragments[0][i]; | ||
const notRelative = !newImport.startsWith("."); | ||
if (fragments.every(f => f.length > i && f[i] === fragment)) { | ||
parentPaths.push(fragment); | ||
if (notRelative) { | ||
newImport = "./" + newImport; | ||
} // Replace / and \ with \\. | ||
if (!useForwardSlashes) { | ||
newImport = newImport.replace(/\/|\\+/g, "\\\\"); | ||
} // Replace \ with /. | ||
else { | ||
newImport = newImport.replace(/\\/g, "/"); | ||
} | ||
} | ||
return parentPaths.join("/"); | ||
return newImport; | ||
}; | ||
/** Resolve with a list of predefined extensions. */ | ||
const customResolve = (id, basedir) => { | ||
return resolve.sync(id, { | ||
basedir, | ||
extensions: [".js", ".jsx", ".ts", ".tsx", ".svg"] // .svg is for create-react-app | ||
extensions: [".js", ".jsx", ".sass", ".scss", ".svg", ".ts", ".tsx"] | ||
}); | ||
}; | ||
const getNewFilePath = (file, rootOptions) => { | ||
for (const { | ||
tree, | ||
parentFolder | ||
} of rootOptions) { | ||
const key = path.relative(parentFolder, file); | ||
if (key in tree) { | ||
return path.resolve(path.join(parentFolder, tree[key])); | ||
} | ||
} | ||
return file; | ||
}; | ||
const getNewImportPath = (absImportPath, newFilePath, rootOptions) => { | ||
let lastUseForwardSlash = true; | ||
for (const { | ||
tree, | ||
parentFolder, | ||
useForwardSlash | ||
} of rootOptions) { | ||
lastUseForwardSlash = useForwardSlash; | ||
const key = path.relative(parentFolder, absImportPath); | ||
if (key in tree) { | ||
return makeImportPath(newFilePath, path.resolve(path.join(parentFolder, tree[key])), useForwardSlash); | ||
} | ||
} | ||
return makeImportPath(newFilePath, absImportPath, lastUseForwardSlash); | ||
}; | ||
const fixImports = (filePaths, rootOptions) => { | ||
for (const filePath of filePaths) { | ||
const imports = findEdges(filePath); | ||
if (!imports.length) continue; | ||
const basedir = path.dirname(filePath); | ||
const newFilePath = getNewFilePath(filePath, rootOptions); | ||
const ogText = fs.readFileSync(filePath).toString(); | ||
let newText = ogText.repeat(1); | ||
for (const imp of imports) { | ||
const absPath = customResolve(imp[1], basedir); | ||
const newImportText = getNewImportPath(absPath, newFilePath, rootOptions); | ||
if (newImportText) { | ||
newText = newText.replace(`'${imp[1]}'`, `'${newImportText}'`).replace(`"${imp[1]}"`, `"${newImportText}"`); | ||
} | ||
} | ||
if (newText !== ogText) { | ||
fs.writeFileSync(filePath, newText); | ||
} | ||
} | ||
}; | ||
const formatFileStructure = async (filePaths, rootOptions) => { | ||
logger.info("Fixing imports."); | ||
fixImports(filePaths, rootOptions); | ||
for (const { | ||
tree, | ||
parentFolder | ||
} of rootOptions) { | ||
logger.info("Moving files."); | ||
await moveFiles(tree, parentFolder); | ||
removeEmptyFolders(parentFolder); | ||
} | ||
logger.info("Restructure was successful!"); | ||
}; | ||
const createBranchFromParts = (parts, count) => parts.slice(0, count).join("/"); | ||
/** Remove path that matches `match` but save '/' to calculate position. */ | ||
const removePathDuplication = (target, match) => target.replace(new RegExp(`^(/*)${match.replace(/\//g, "")}(/+)`), "$1$2"); | ||
/** | ||
* Matches anything between '/' and '.' and prepends the highest possible char | ||
* code to it. This enables us sort files lower than directories. | ||
*/ | ||
const prependMaxCharCodeToFile = text => text.replace(/([^/]+)(?=\.)/g, String.fromCharCode(Number.MAX_SAFE_INTEGER) + "$1"); | ||
const compareLeafs = (a, b) => prependMaxCharCodeToFile(a).localeCompare(prependMaxCharCodeToFile(b)); | ||
/** Check if leaf is the last of its siblings excluding children. */ | ||
const isLeafLastSibling = (leaf, remainingLeafs) => { | ||
for (const remaningLeaf of remainingLeafs) { | ||
if (remaningLeaf.position > leaf.position) continue; | ||
if (remaningLeaf.position < leaf.position) return true; | ||
return false; | ||
} | ||
return true; | ||
}; | ||
const removeIllicitIndentGuidelines = (line, pastLine) => { | ||
const isCharEndConnectorOrWhitespace = char => char === "└" || char === " "; | ||
return Array.from(line).map((char, idx) => char === "│" && isCharEndConnectorOrWhitespace(pastLine[idx]) ? " " : char).join(""); | ||
}; | ||
/** Resolve every unique leaf from a list of paths. */ | ||
const resolveLeafs = paths => { | ||
const leafs = paths.reduce((acc, target) => { | ||
const parts = target.split("/"); | ||
parts.forEach((_, idx) => acc.add(createBranchFromParts(parts, idx + 1))); | ||
return acc; | ||
}, new Set()); | ||
return Array.from(leafs).sort(compareLeafs); | ||
}; | ||
/** | ||
* Iterates over all leafs and positions them on the correct branches, ie. | ||
* resolving their end name in relation to their branch and their position. | ||
*/ | ||
const positionLeafs = leafs => { | ||
const res = []; | ||
let queue = [...leafs]; | ||
while (queue.length > 0) { | ||
const leaf = queue.shift(); | ||
if (leaf == null) break; | ||
queue = queue.map(queuedLeaf => removePathDuplication(queuedLeaf, leaf)); | ||
res.push(leaf); | ||
} | ||
return res.map(x => { | ||
var _parts$pop; | ||
const parts = x.split("/"); | ||
return { | ||
text: (_parts$pop = parts.pop()) != null ? _parts$pop : "", | ||
position: parts.length | ||
}; | ||
}); | ||
}; | ||
/** Print a visualization of a tree of paths. */ | ||
const printTree = paths => { | ||
const leafs = resolveLeafs(paths); | ||
const positionedLeafs = positionLeafs(leafs); | ||
const treeVisualization = positionedLeafs.reduce((lines, currentLeaf, idx) => { | ||
var _lines; | ||
const pastLine = (_lines = lines[idx - 1]) != null ? _lines : ""; | ||
const remainingLeafs = positionedLeafs.slice(idx + 1); | ||
const isDirectory = remainingLeafs.length > 0 && remainingLeafs[0].position > currentLeaf.position; | ||
const indent = currentLeaf.position > 0 ? "│ ".repeat(currentLeaf.position) : ""; | ||
const connector = isLeafLastSibling(currentLeaf, remainingLeafs) ? "└──" : "├──"; | ||
const text = isDirectory ? chalk.bold.blue(currentLeaf.text) : currentLeaf.text; | ||
const line = indent + connector + text; | ||
lines.push(removeIllicitIndentGuidelines(line, pastLine)); | ||
return lines; | ||
}, []).join("\n"); | ||
logger.log(treeVisualization); | ||
return treeVisualization; | ||
}; | ||
/** Find the common parent directory between all paths. */ | ||
const findSharedParent = paths => { | ||
if (paths.length === 1) return path.dirname(paths[0]); | ||
const parentPaths = []; | ||
const parts = paths.map(x => x.split("/")); | ||
const firstParts = parts[0]; | ||
firstParts.forEach((part, idx) => { | ||
const allPartsMatch = parts.every(p => p[idx] === part); | ||
if (allPartsMatch) { | ||
parentPaths.push(part); | ||
} | ||
}); | ||
return parentPaths.join("/"); | ||
}; | ||
function addEdgeToGraph([start, end], graph) { | ||
if (!(start in graph)) { | ||
graph[start] = []; | ||
} | ||
graph[start].push(end); | ||
} | ||
function buildGraph(files) { | ||
@@ -189,3 +398,3 @@ const parentFolder = findSharedParent(files); | ||
const end = path.relative(parentFolder, pathWithExtension); | ||
addEdge([start, end], graph); | ||
addEdgeToGraph([start, end], graph); | ||
oldGraph[start].imports.push({ | ||
@@ -203,3 +412,3 @@ text: edge[1], | ||
oldGraph, | ||
useForwardSlash: numForwardSlashes >= numBackSlashes ? true : false | ||
useForwardSlash: numForwardSlashes >= numBackSlashes | ||
}; | ||
@@ -261,42 +470,2 @@ } | ||
const moveFiles = async (newStructure, parentFolder) => { | ||
const git = Git(parentFolder); | ||
let isRepo = false; | ||
try { | ||
isRepo = await git.checkIsRepo(); | ||
} catch {} | ||
for (const [k, newLocation] of Object.entries(newStructure)) { | ||
// skip globals | ||
if (k.includes("..")) { | ||
continue; | ||
} | ||
const oldAbsLocation = path.resolve(path.join(parentFolder, k)); | ||
const newAbsLocation = path.resolve(path.join(parentFolder, newLocation)); | ||
if (oldAbsLocation !== newAbsLocation) { | ||
// make folders | ||
fs__default.ensureDirSync(path.dirname(newAbsLocation)); | ||
let shouldGitMv = false; | ||
if (isRepo) { | ||
// check if file is tracked in git | ||
try { | ||
await git.silent(true).raw(["ls-files", "--error-unmatch", oldAbsLocation]); | ||
shouldGitMv = true; | ||
} catch {} | ||
} | ||
if (shouldGitMv) { | ||
await git.mv(oldAbsLocation, newAbsLocation); | ||
} else { | ||
// move | ||
fs__default.renameSync(oldAbsLocation, newAbsLocation); | ||
} | ||
} | ||
} | ||
}; | ||
const hasCycle = (node, graph, visited) => { | ||
@@ -428,152 +597,84 @@ const edges = graph[node]; | ||
/** Recursively removes all empty folders. */ | ||
function generateTrees(restructureMap) { | ||
return Object.entries(restructureMap).reduce((rootOptions, [rootPath, filePaths]) => { | ||
if (filePaths.length <= 1) return rootOptions; | ||
logger.info(`Generating tree for: ${rootPath}`); | ||
const { | ||
graph, | ||
files, | ||
useForwardSlash, | ||
parentFolder | ||
} = buildGraph(filePaths); | ||
const tree = toFractalTree(graph, findEntryPoints(graph)); | ||
const usedFilePaths = new Set(Object.entries(graph).flat(2)); | ||
const unusedFiles = files.filter(filePath => !usedFilePaths.has(filePath)); | ||
logger.log(chalk.bold.blue(rootPath)); | ||
printTree(Object.values(tree)); | ||
function removeEmptyFolders(directory) { | ||
const files = fs$1.readdirSync(directory); | ||
if (!files) return fs$1.rmdirSync(directory); | ||
if (unusedFiles.length > 0) { | ||
logger.warn(`Found ${unusedFiles.length} unused files:` + "\n" + unusedFiles.join("\n")); | ||
} | ||
for (const filePath of files) { | ||
const fullPath = path.resolve(directory, filePath); | ||
const isDirectory = fs$1.lstatSync(fullPath).isDirectory(); | ||
if (!isDirectory) continue; | ||
removeEmptyFolders(fullPath); | ||
const isEmpty = fs$1.readdirSync(fullPath).length === 0; | ||
if (isEmpty) fs$1.rmdirSync(fullPath); | ||
} | ||
rootOptions.push({ | ||
parentFolder, | ||
tree, | ||
useForwardSlash | ||
}); | ||
return rootOptions; | ||
}, []); | ||
} | ||
const makeImportPath = (p1, p2, useForwardSlashes) => { | ||
const fullPath = path.relative(path.dirname(p1), p2); | ||
const ext = path.extname(fullPath); | ||
let newImport = path.join(path.dirname(fullPath), path.basename(fullPath, [".js", ".jsx", ".ts", ".tsx"].includes(ext) ? ext : undefined)); | ||
var version = "0.3.0"; | ||
if (!newImport.startsWith(".")) { | ||
newImport = "./" + newImport; | ||
} | ||
const isDirectory = filePath => fs.lstatSync(filePath).isDirectory(); | ||
if (useForwardSlashes) { | ||
// Replace \ with /. | ||
newImport = newImport.replace(/\\/g, "/"); | ||
} else { | ||
// Replace / and \ with \\. | ||
newImport = newImport.replace(/\/|\\+/g, "\\\\"); | ||
} | ||
const isFile = filePath => fs.lstatSync(filePath).isFile(); | ||
return newImport; | ||
}; | ||
const globSearch = pattern => { | ||
const matches = glob.sync(pattern); | ||
const files = matches.filter(match => isFile(match)); | ||
const getNewFilePath = (file, rootOptions) => { | ||
for (const { | ||
tree, | ||
parentFolder | ||
} of rootOptions) { | ||
const key = path.relative(parentFolder, file); | ||
if (key in tree) { | ||
return path.resolve(path.join(parentFolder, tree[key])); | ||
} | ||
if (files.length === 0) { | ||
logger.error("Could not find any files for: " + pattern, 1); | ||
} | ||
return file; | ||
return files; | ||
}; | ||
/** Recursively get all file paths. */ | ||
const getNewImportPath = (absImportPath, newFilePath, rootOptions) => { | ||
let lastUseForwardSlash = true; | ||
for (const { | ||
tree, | ||
parentFolder, | ||
useForwardSlash | ||
} of rootOptions) { | ||
lastUseForwardSlash = useForwardSlash; | ||
const key = path.relative(parentFolder, absImportPath); | ||
const getFilePaths = rootPath => { | ||
const filePaths = []; | ||
const paths = [rootPath]; | ||
if (key in tree) { | ||
return makeImportPath(newFilePath, path.resolve(path.join(parentFolder, tree[key])), useForwardSlash); | ||
} | ||
} | ||
while (paths.length > 0) { | ||
const filePath = paths.shift(); | ||
if (filePath == null || filePath.length === 0) continue; | ||
const isGlobPattern = glob.hasMagic(filePath); | ||
return makeImportPath(newFilePath, absImportPath, lastUseForwardSlash); | ||
}; | ||
const fixImports = (files, rootOptions) => { | ||
for (const file of files) { | ||
const imports = findEdges(file); | ||
if (!imports.length) { | ||
if (isGlobPattern) { | ||
filePaths.push(...globSearch(filePath)); | ||
continue; | ||
} | ||
const basedir = path.dirname(file); | ||
const newFilePath = getNewFilePath(file, rootOptions); | ||
const ogText = fs.readFileSync(file).toString(); // deep copy | ||
let newText = ogText.repeat(1); | ||
for (const imp of imports) { | ||
const absPath = customResolve(imp[1], basedir); | ||
const newImportText = getNewImportPath(absPath, newFilePath, rootOptions); | ||
if (newImportText) { | ||
newText = newText.replace(`'${imp[1]}'`, `'${newImportText}'`).replace(`"${imp[1]}"`, `"${newImportText}"`); | ||
if (fs.existsSync(filePath)) { | ||
if (isFile(filePath)) { | ||
filePaths.push(filePath); | ||
} else if (isDirectory(filePath)) { | ||
paths.push(path.join(filePath, "/**/*.*")); | ||
} | ||
} else { | ||
logger.error(`Unable to resolve the path: ${filePath}`); | ||
} | ||
} | ||
if (newText !== ogText) { | ||
fs.writeFileSync(file, newText); | ||
} | ||
} | ||
return filePaths; | ||
}; | ||
/** Get a restructure map with rootPath keys and filePaths values. */ | ||
const formatFileStructure = async (rootDirFiles, filesToEdit) => { | ||
const unusedFiles = []; | ||
const rootOptions = []; | ||
for (const startingFiles of rootDirFiles) { | ||
if (startingFiles.length <= 1) { | ||
continue; | ||
} | ||
const getRestructureMap = rootPaths => rootPaths.reduce((acc, rootPath) => ({ ...acc, | ||
[rootPath]: getFilePaths(rootPath) | ||
}), {}); | ||
logger.info("Generating a graph and fractal tree."); | ||
const { | ||
graph, | ||
files, | ||
useForwardSlash, | ||
parentFolder | ||
} = buildGraph(startingFiles); | ||
const tree = toFractalTree(graph, findEntryPoints(graph)); | ||
const usedFiles = new Set(Object.entries(graph).flat(2)); | ||
rootOptions.push({ | ||
tree, | ||
useForwardSlash, | ||
parentFolder | ||
}); | ||
files.forEach(file => { | ||
if (!usedFiles.has(file)) { | ||
unusedFiles.push(file); | ||
} | ||
}); | ||
} | ||
logger.info("Fixing imports."); | ||
await fixImports(filesToEdit, rootOptions); | ||
for (const { | ||
tree, | ||
parentFolder | ||
} of rootOptions) { | ||
logger.info("Moving files."); | ||
await moveFiles(tree, parentFolder); | ||
removeEmptyFolders(parentFolder); | ||
} | ||
if (unusedFiles.length > 0) { | ||
logger.warn(`Found ${unusedFiles.length} unused files:` + "\n" + unusedFiles.map(file => " ".repeat(8) + file).join("\n")); | ||
} | ||
logger.info("Restructure was successful!"); | ||
}; | ||
var version = "0.2.1"; | ||
const { | ||
@@ -583,5 +684,5 @@ argv | ||
const defaultOptions = { | ||
detectRoots: false, | ||
help: false, | ||
version: false | ||
version: false, | ||
write: false | ||
}; | ||
@@ -604,3 +705,3 @@ | ||
-h, --help output usage information | ||
-dr, --detect-roots structure after the first level | ||
-w, --write restructure and edit folders and files | ||
`); | ||
@@ -622,9 +723,9 @@ return process.exit(exitCode); | ||
case "-dr": | ||
case "--detect-roots": | ||
acc.options.detectRoots = true; | ||
case "-w": | ||
case "--write": | ||
acc.options.write = true; | ||
break; | ||
default: | ||
acc.paths.push(arg); | ||
acc.rootPaths.push(arg); | ||
} | ||
@@ -635,3 +736,3 @@ | ||
options: {}, | ||
paths: [] | ||
rootPaths: [] | ||
}); | ||
@@ -645,3 +746,3 @@ | ||
options, | ||
paths | ||
rootPaths | ||
} = parseArgs(args); | ||
@@ -654,8 +755,8 @@ const mergedOptions = { ...defaultOptions, | ||
if (mergedOptions.version) return printVersion(); | ||
if (paths.length === 0) return printHelp(1); | ||
if (rootPaths.length === 0) return printHelp(1); | ||
logger.info("Resolving files."); | ||
const filesToRestructure = getFilePaths(paths, mergedOptions); | ||
const filesToEdit = filesToRestructure.flat(); | ||
const restructureMap = getRestructureMap(rootPaths); | ||
const filesToEdit = Object.values(restructureMap).flat(); | ||
if (filesToRestructure.length === 0) { | ||
if (filesToEdit.length === 0) { | ||
logger.error("Could not find any files to restructure", 1); | ||
@@ -665,3 +766,7 @@ return; | ||
await formatFileStructure(filesToRestructure, filesToEdit); | ||
const rootOptions = generateTrees(restructureMap); | ||
if (mergedOptions.write) { | ||
await formatFileStructure(filesToEdit, rootOptions); | ||
} | ||
}; | ||
@@ -668,0 +773,0 @@ |
#!/usr/bin/env node | ||
"use strict";function e(e){return e&&"object"==typeof e&&"default"in e?e.default:e}Object.defineProperty(exports,"__esModule",{value:!0}),require("core-js/modules/es.array.flat"),require("core-js/modules/es.array.unscopables.flat"),require("core-js/modules/es.promise");var t=e(require("chalk")),n=require("cosmiconfig"),r=e(require("glob")),o=e(require("path")),s=require("fs-extra"),i=e(s),c=e(require("fs")),l=e(require("resolve")),a=e(require("simple-git/promise"));var u=(e,n=0)=>{e instanceof Error?console.error(e):console.error(t`{red.bold ERROR:} ${e}`),console.log("If you think this is a bug, you can report it: https://github.com/benawad/destiny/issues"),process.exit(n)},f=e=>{console.log(t`{yellow.bold WARN:} ${e}`)},d=e=>{console.log(t`{green.bold INFO:} ${e}`)};const h=e=>s.lstatSync(e).isDirectory(),p=e=>s.lstatSync(e).isFile(),g=e=>{const t=r.sync(e).filter(e=>p(e));return 0===t.length&&u("Could not find any files for: "+e,1),t};function m(e){const t=/(?:(?:from|import)\s+["'](\.[^'"]*)["'])|(?:(?:require|import)\s*\(["'](\.[^'"]*)["']\))/gm,n=[],r=c.readFileSync(e,{encoding:"utf8"}).toString().replace(/\/\*[^]*?\*\/|^.*\/\/.*$/gm,"");let o;for(;null!==(o=t.exec(r));)o.index===t.lastIndex&&t.lastIndex++,n.push([e,o[1]||o[2]]);return n}function y([e,t],n){e in n||(n[e]=[]),n[e].push(t)}const v=e=>{if(1===e.length)return o.dirname(e[0]);const t=e.map(e=>e.split("/")),n=[];for(let e=0;e<t[0].length;e++){const r=t[0][e];t.every(t=>t.length>e&&t[e]===r)&&n.push(r)}return n.join("/")},b=(e,t)=>l.sync(e,{basedir:t,extensions:[".js",".jsx",".ts",".tsx",".svg"]});function j(e){const t=v(e),n={},r={},s=[];let i=0,c=0;for(let l of e){if(".git"===l)continue;l=o.resolve(l);const e=o.relative(t,l);e in r||(r[e]={oldLocation:l,imports:[]}),s.push(e),m(l).forEach(s=>{s[1].includes("/")?i++:s[1].includes("\\")&&c++;const l=b(s[1],o.dirname(s[0])),a=o.relative(t,l);y([e,a],n),r[e].imports.push({text:s[1],resolved:a})})}return{parentFolder:t,graph:n,files:s,oldGraph:r,useForwardSlash:i>=c}}const S=e=>/\.test\.|\.spec\.|\.stories\./.test(e);function x(e){const t=(e=>{const t={};return Object.keys(e).forEach(n=>{e[n].forEach(e=>{e in t||(t[e]=[]),t[e].push(n)})}),t})(e),n=Object.keys(e),r=n.filter(e=>{const n=t[e];return!n||!n.length||n.every(e=>S(e))});if(r.length)return r;const o={};n.forEach(e=>{const t=e.split("/").length;t in o||(o[t]=[]),o[t].push(e)});for(let e=1;e<10;e++)if(e in o)return o[e];return[]}const w=async(e,t)=>{const n=a(t);let r=!1;try{r=await n.checkIsRepo()}catch{}for(const[s,c]of Object.entries(e)){if(s.includes(".."))continue;const e=o.resolve(o.join(t,s)),l=o.resolve(o.join(t,c));if(e!==l){i.ensureDirSync(o.dirname(l));let t=!1;if(r)try{await n.silent(!0).raw(["ls-files","--error-unmatch",e]),t=!0}catch{}t?await n.mv(e,l):i.renameSync(e,l)}}},F=(e,t,n)=>{const r=t[e];if(n.has(e))return[...n,e];if(n.add(e),null==r||0===r.length)return null;for(const e of r){const r=F(e,t,new Set(n));if(r)return r}return null};function $(e,t){const n={},r={},s=new Set;let i=!1;const c=(e,t)=>{Array.isArray(r[e])||(r[e]=[]),r[e].push(t)},l=(e,t,r)=>{var s;let i;return Object.values(n).includes(e)&&(i=o.join(t,r.replace(/\//g,"-")),d(`File renamed: ${r} -> ${i}`)),null!=(s=i)?s:e},a=(e,t,r)=>{const u=o.basename(e);if(S(u))return void s.add(e);let d=o.basename(e,o.extname(e));const h=o.basename(o.dirname(e)),p=e.includes("..");let g=p?e:o.join(t,"index"===d&&h&&"."!==h?h+o.extname(e):u);g=l(g,t,e),d=o.basename(g,o.extname(g)),p||(n[e]=g);const m=r[e];if((null==m?void 0:m.length)>0){const e=o.join(t,d);for(const t of m)if(t in n){const e=F(t,r,new Set);e?(i=!0,f(`Dependency cycle detected: ${e.join(" -> ")}`)):c(t,g)}else c(t,g),a(t,e,r)}};for(const n of t)a(n,"",e);if(i||Object.entries(r).forEach(([e,t])=>{if(t.length>1&&!e.includes("..")){const r=v(t),s=o.basename(e),i=o.basename(o.dirname(e));n[e]=o.join(r,"shared","index"===o.basename(s,o.extname(s))&&i&&"."!==i?i+o.extname(s):s)}}),s.size>0)for(const t of s){const[r]=e[t];if(!r)continue;const s=n[r];if(s){let e=o.join(o.dirname(s),o.basename(t));e=l(e,o.dirname(s),t),n[t]=e}}return n}function q(e){const t=c.readdirSync(e);if(!t)return c.rmdirSync(e);for(const n of t){const t=o.resolve(e,n);c.lstatSync(t).isDirectory()&&(q(t),0===c.readdirSync(t).length&&c.rmdirSync(t))}}const O=(e,t,n)=>{const r=o.relative(o.dirname(e),t),s=o.extname(r);let i=o.join(o.dirname(r),o.basename(r,[".js",".jsx",".ts",".tsx"].includes(s)?s:void 0));return i.startsWith(".")||(i="./"+i),i=n?i.replace(/\\/g,"/"):i.replace(/\/|\\+/g,"\\\\"),i},R=(e,t)=>{for(const{tree:n,parentFolder:r}of t){const t=o.relative(r,e);if(t in n)return o.resolve(o.join(r,n[t]))}return e},E=(e,t,n)=>{let r=!0;for(const{tree:s,parentFolder:i,useForwardSlash:c}of n){r=c;const n=o.relative(i,e);if(n in s)return O(t,o.resolve(o.join(i,s[n])),c)}return O(t,e,r)},k=async(e,t)=>{const n=[],r=[];for(const t of e){if(t.length<=1)continue;d("Generating a graph and fractal tree.");const{graph:e,files:o,useForwardSlash:s,parentFolder:i}=j(t),c=$(e,x(e)),l=new Set(Object.entries(e).flat(2));r.push({tree:c,useForwardSlash:s,parentFolder:i}),o.forEach(e=>{l.has(e)||n.push(e)})}d("Fixing imports."),await((e,t)=>{for(const n of e){const e=m(n);if(!e.length)continue;const r=o.dirname(n),i=R(n,t),c=s.readFileSync(n).toString();let l=c.repeat(1);for(const n of e){const e=b(n[1],r),o=E(e,i,t);o&&(l=l.replace(`'${n[1]}'`,`'${o}'`).replace(`"${n[1]}"`,`"${o}"`))}l!==c&&s.writeFileSync(n,l)}})(t,r);for(const{tree:e,parentFolder:t}of r)d("Moving files."),await w(e,t),q(t);n.length>0&&f(`Found ${n.length} unused files:`+"\n"+n.map(e=>" ".repeat(8)+e).join("\n")),d("Restructure was successful!")};const{argv:I}=process,A={detectRoots:!1,help:!1,version:!1},D=e=>(console.log(t`{blue destiny} - Prettier for file structures. | ||
"use strict";function e(e){return e&&"object"==typeof e&&"default"in e?e.default:e}Object.defineProperty(exports,"__esModule",{value:!0}),require("core-js/modules/es.array.flat"),require("core-js/modules/es.array.unscopables.flat"),require("core-js/modules/es.promise");var n=e(require("chalk")),t=require("cosmiconfig"),r=require("fs-extra"),o=e(r),s=e(require("path")),i=e(require("simple-git/promise")),l=e(require("fs")),c=e(require("resolve")),a=e(require("glob"));var u=(e,t=0)=>{e instanceof Error?console.error(e):console.error(n`{red.bold ERROR:} ${e}`),console.log("If you think this is a bug, you can report it: https://github.com/benawad/destiny/issues"),process.exit(t)},f=e=>{console.info(n`{green.bold INFO:} ${e}`)},d=e=>{console.log(e)},p=e=>{console.warn(n`{yellow.bold WARN:} ${e}`)};const h=async(e,n)=>{const t=i(n);let r=!1;try{r=await t.checkIsRepo()}catch{}for(const[i,l]of Object.entries(e)){if(i.includes(".."))continue;const e=s.resolve(s.join(n,i)),c=s.resolve(s.join(n,l));if(e!==c){o.ensureDirSync(s.dirname(c));let n=!1;if(r)try{await t.silent(!0).raw(["ls-files","--error-unmatch",e]),n=!0}catch{}n?await t.mv(e,c):o.renameSync(e,c)}}};function m(e){const n=l.readdirSync(e);if(!n)return l.rmdirSync(e);for(const t of n){const n=s.resolve(e,t);l.lstatSync(n).isDirectory()&&(m(n),0===l.readdirSync(n).length&&l.rmdirSync(n))}}function g(e){const n=/(?:(?:from|import)\s+["'](\.[^'"]*)["'])|(?:(?:require|import)\s*\(["'](\.[^'"]*)["']\))/gm,t=[],r=l.readFileSync(e,{encoding:"utf8"}).toString().replace(/\/\*[^]*?\*\/|^.*\/\/.*$/gm,"");let o;for(;null!==(o=n.exec(r));){var s;o.index===n.lastIndex&&n.lastIndex++,t.push([e,null!=(s=o[1])?s:o[2]])}return t}const b=(e,n,t)=>{const r=s.dirname(e),o=s.relative(r,n),i=s.dirname(o),l=function(e){const n=s.extname(e);return[".js",".jsx",".ts",".tsx"].includes(n)?n:void 0}(o),c=s.basename(o,l);let a=s.join(i,c);return!a.startsWith(".")&&(a="./"+a),a=t?a.replace(/\\/g,"/"):a.replace(/\/|\\+/g,"\\\\"),a},y=(e,n)=>c.sync(e,{basedir:n,extensions:[".js",".jsx",".sass",".scss",".svg",".ts",".tsx"]}),v=(e,n)=>{for(const{tree:t,parentFolder:r}of n){const n=s.relative(r,e);if(n in t)return s.resolve(s.join(r,t[n]))}return e},j=(e,n,t)=>{let r=!0;for(const{tree:o,parentFolder:i,useForwardSlash:l}of t){r=l;const t=s.relative(i,e);if(t in o)return b(n,s.resolve(s.join(i,o[t])),l)}return b(n,e,r)},w=async(e,n)=>{f("Fixing imports."),((e,n)=>{for(const t of e){const e=g(t);if(!e.length)continue;const o=s.dirname(t),i=v(t,n),l=r.readFileSync(t).toString();let c=l.repeat(1);for(const t of e){const e=y(t[1],o),r=j(e,i,n);r&&(c=c.replace(`'${t[1]}'`,`'${r}'`).replace(`"${t[1]}"`,`"${r}"`))}c!==l&&r.writeFileSync(t,c)}})(e,n);for(const{tree:e,parentFolder:t}of n)f("Moving files."),await h(e,t),m(t);f("Restructure was successful!")},x=e=>e.replace(/([^/]+)(?=\.)/g,String.fromCharCode(Number.MAX_SAFE_INTEGER)+"$1"),S=(e,n)=>x(e).localeCompare(x(n)),F=e=>{const t=(e=>{const n=[];let t=[...e];for(;t.length>0;){const e=t.shift();if(null==e)break;t=t.map(n=>{return t=e,n.replace(new RegExp(`^(/*)${t.replace(/\//g,"")}(/+)`),"$1$2");var t}),n.push(e)}return n.map(e=>{var n;const t=e.split("/");return{text:null!=(n=t.pop())?n:"",position:t.length}})})((e=>{const n=e.reduce((e,n)=>{const t=n.split("/");return t.forEach((n,r)=>e.add(((e,n)=>e.slice(0,n).join("/"))(t,r+1))),e},new Set);return Array.from(n).sort(S)})(e)),r=t.reduce((e,r,o)=>{var s;const i=null!=(s=e[o-1])?s:"",l=t.slice(o+1),c=l.length>0&&l[0].position>r.position,a=(r.position>0?"│ ".repeat(r.position):"")+(((e,n)=>{for(const t of n)if(!(t.position>e.position))return t.position<e.position;return!0})(r,l)?"└──":"├──")+(c?n.bold.blue(r.text):r.text);return e.push(((e,n)=>Array.from(e).map((e,t)=>"│"===e&&(e=>"└"===e||" "===e)(n[t])?" ":e).join(""))(a,i)),e},[]).join("\n");return d(r),r},$=e=>{if(1===e.length)return s.dirname(e[0]);const n=[],t=e.map(e=>e.split("/"));return t[0].forEach((e,r)=>{t.every(n=>n[r]===e)&&n.push(e)}),n.join("/")};function E([e,n],t){e in t||(t[e]=[]),t[e].push(n)}const O=e=>/\.test\.|\.spec\.|\.stories\./.test(e);function q(e){const n=(e=>{const n={};return Object.keys(e).forEach(t=>{e[t].forEach(e=>{e in n||(n[e]=[]),n[e].push(t)})}),n})(e),t=Object.keys(e),r=t.filter(e=>{const t=n[e];return!t||!t.length||t.every(e=>O(e))});if(r.length)return r;const o={};t.forEach(e=>{const n=e.split("/").length;n in o||(o[n]=[]),o[n].push(e)});for(let e=1;e<10;e++)if(e in o)return o[e];return[]}const k=(e,n,t)=>{const r=n[e];if(t.has(e))return[...t,e];if(t.add(e),null==r||0===r.length)return null;for(const e of r){const r=k(e,n,new Set(t));if(r)return r}return null};function R(e){return Object.entries(e).reduce((e,[t,r])=>{if(r.length<=1)return e;f(`Generating tree for: ${t}`);const{graph:o,files:i,useForwardSlash:l,parentFolder:c}=function(e){const n=$(e),t={},r={},o=[];let i=0,l=0;for(let c of e){if(".git"===c)continue;c=s.resolve(c);const e=s.relative(n,c);e in r||(r[e]={oldLocation:c,imports:[]}),o.push(e),g(c).forEach(o=>{o[1].includes("/")?i++:o[1].includes("\\")&&l++;const c=y(o[1],s.dirname(o[0])),a=s.relative(n,c);E([e,a],t),r[e].imports.push({text:o[1],resolved:a})})}return{parentFolder:n,graph:t,files:o,oldGraph:r,useForwardSlash:i>=l}}(r),a=function(e,n){const t={},r={},o=new Set;let i=!1;const l=(e,n)=>{Array.isArray(r[e])||(r[e]=[]),r[e].push(n)},c=(e,n,r)=>{var o;let i;return Object.values(t).includes(e)&&(i=s.join(n,r.replace(/\//g,"-")),f(`File renamed: ${r} -> ${i}`)),null!=(o=i)?o:e},a=(e,n,r)=>{const u=s.basename(e);if(O(u))return void o.add(e);let f=s.basename(e,s.extname(e));const d=s.basename(s.dirname(e)),h=e.includes("..");let m=h?e:s.join(n,"index"===f&&d&&"."!==d?d+s.extname(e):u);m=c(m,n,e),f=s.basename(m,s.extname(m)),h||(t[e]=m);const g=r[e];if((null==g?void 0:g.length)>0){const e=s.join(n,f);for(const n of g)if(n in t){const e=k(n,r,new Set);e?(i=!0,p(`Dependency cycle detected: ${e.join(" -> ")}`)):l(n,m)}else l(n,m),a(n,e,r)}};for(const t of n)a(t,"",e);if(i||Object.entries(r).forEach(([e,n])=>{if(n.length>1&&!e.includes("..")){const r=$(n),o=s.basename(e),i=s.basename(s.dirname(e));t[e]=s.join(r,"shared","index"===s.basename(o,s.extname(o))&&i&&"."!==i?i+s.extname(o):o)}}),o.size>0)for(const n of o){const[r]=e[n];if(!r)continue;const o=t[r];if(o){let e=s.join(s.dirname(o),s.basename(n));e=c(e,s.dirname(o),n),t[n]=e}}return t}(o,q(o)),u=new Set(Object.entries(o).flat(2)),h=i.filter(e=>!u.has(e));return d(n.bold.blue(t)),F(Object.values(a)),h.length>0&&p(`Found ${h.length} unused files:`+"\n"+h.join("\n")),e.push({parentFolder:c,tree:a,useForwardSlash:l}),e},[])}const A=e=>r.lstatSync(e).isDirectory(),I=e=>r.lstatSync(e).isFile(),P=e=>{const n=a.sync(e).filter(e=>I(e));return 0===n.length&&u("Could not find any files for: "+e,1),n},C=e=>{const n=[],t=[e];for(;t.length>0;){const e=t.shift();null!=e&&0!==e.length&&(a.hasMagic(e)?n.push(...P(e)):r.existsSync(e)?I(e)?n.push(e):A(e)&&t.push(s.join(e,"/**/*.*")):u(`Unable to resolve the path: ${e}`))}return n},{argv:N}=process,D={help:!1,version:!1,write:!1},G=e=>(console.log(n`{blue destiny} - Prettier for file structures. | ||
@@ -14,3 +14,3 @@ {bold USAGE} | ||
-h, --help output usage information | ||
-dr, --detect-roots structure after the first level | ||
`),process.exit(e)),G=async e=>{var t,i;const c=null!=(t=null==(i=n.cosmiconfigSync("destiny").search())?void 0:i.config)?t:{},{options:l,paths:a}=(e=>e.reduce((e,t)=>{switch(t){case"-h":case"--help":e.options.help=!0;break;case"-V":case"--version":e.options.version=!0;break;case"-dr":case"--detect-roots":e.options.detectRoots=!0;break;default:e.paths.push(t)}return e},{options:{},paths:[]}))(e),f={...A,...c,...l};if(f.help)return D(0);if(f.version)return console.log("v0.2.1");if(0===a.length)return D(1);d("Resolving files.");const m=((e,t)=>{let{detectRoots:n}=t;const i=[];for(;e.length>0;){const t=e.pop();if(null!=t&&0!==t.length)if(r.hasMagic(t))i.push(g(t));else if(s.existsSync(t)){if(p(t))i.push([t]);else if(h(t))if(n){const r=s.readdirSync(o.resolve(t)).map(e=>o.join(t,e)).filter(e=>h(e));e.push(...r),n=!1}else e.push(o.join(t,"/**/*.*"))}else u(`Unable to resolve the path: ${t}`)}return i})(a,f),y=m.flat();0!==m.length?await k(m,y):u("Could not find any files to restructure",1)};G(I.slice(2,I.length)),exports.run=G; | ||
-w, --write restructure and edit folders and files | ||
`),process.exit(e)),M=async e=>{var n,r;const o=null!=(n=null==(r=t.cosmiconfigSync("destiny").search())?void 0:r.config)?n:{},{options:s,rootPaths:i}=(e=>e.reduce((e,n)=>{switch(n){case"-h":case"--help":e.options.help=!0;break;case"-V":case"--version":e.options.version=!0;break;case"-w":case"--write":e.options.write=!0;break;default:e.rootPaths.push(n)}return e},{options:{},rootPaths:[]}))(e),l={...D,...o,...s};if(l.help)return G(0);if(l.version)return console.log("v0.3.0");if(0===i.length)return G(1);f("Resolving files.");const c=(e=>e.reduce((e,n)=>({...e,[n]:C(n)}),{}))(i),a=Object.values(c).flat();if(0===a.length)return void u("Could not find any files to restructure",1);const d=R(c);l.write&&await w(a,d)};M(N.slice(2,N.length)),exports.run=M; |
{ | ||
"name": "destiny", | ||
"version": "0.2.1", | ||
"version": "0.3.0", | ||
"description": "Prettier for file structures", | ||
@@ -18,5 +18,6 @@ "license": "MIT", | ||
"scripts": { | ||
"build": "npm-run-all clean check -p 'build:* -- {@}' --", | ||
"build:cjs": "rollup -c", | ||
"check": "tsc", | ||
"clean": "rimraf lib", | ||
"build": "npm-run-all clean -p 'build:* -- {@}' --", | ||
"build:cjs": "rollup -c", | ||
"format": "run-s format:*", | ||
@@ -29,3 +30,3 @@ "format:eslint": "npm run lint -- --fix", | ||
"start": "nodemon lib/destiny.js", | ||
"test": "jest", | ||
"test": "jest --colors", | ||
"watch": "npm run build -- --watch" | ||
@@ -32,0 +33,0 @@ }, |
@@ -38,3 +38,3 @@ # destiny | ||
``` | ||
npx destiny src/**/*.* | ||
npx destiny "src/**/*.*" | ||
``` | ||
@@ -41,0 +41,0 @@ |
@@ -12,2 +12,3 @@ { | ||
"resolveJsonModule": true, | ||
"skipLibCheck": true, | ||
"strict": true, | ||
@@ -14,0 +15,0 @@ "target": "esnext" |
42274
825