Comparing version 0.3.1 to 0.4.0
@@ -1,61 +0,55 @@ | ||
module.exports = { | ||
root: true, | ||
const defaultSettings = { | ||
env: { es6: true, node: true }, | ||
extends: ["standard", "prettier"], | ||
parser: "babel-eslint", | ||
rules: { | ||
indent: "off", | ||
"no-unused-vars": "off", | ||
semi: [2, "always"], | ||
"keyword-spacing": [2, { before: true, after: true }], | ||
"space-before-blocks": [2, "always"], | ||
"no-mixed-spaces-and-tabs": [2, "smart-tabs"], | ||
"no-cond-assign": 0, | ||
"no-empty": 0, | ||
"object-shorthand": [2, "always"], | ||
"no-const-assign": 2, | ||
"no-class-assign": 2, | ||
"no-this-before-super": 2, | ||
"no-var": 2, | ||
"no-unreachable": 2, | ||
"valid-typeof": 2, | ||
"quote-props": [2, "as-needed"], | ||
"one-var": [2, "never"], | ||
"prefer-arrow-callback": 2, | ||
"prefer-const": [2, { destructuring: "all" }], | ||
"arrow-spacing": 2, | ||
"no-inner-declarations": 0, | ||
"require-atomic-updates": "off", | ||
quotes: "off", | ||
"@typescript-eslint/quotes": ["error", "double"], | ||
"@typescript-eslint/no-use-before-define": "off", | ||
"@typescript-eslint/explicit-function-return-type": "off", | ||
"@typescript-eslint/no-explicit-any": "off", | ||
"@typescript-eslint/explicit-member-accessibility": "off", | ||
"@typescript-eslint/no-object-literal-type-assertion": "off", | ||
"@typescript-eslint/no-unused-vars": "off", | ||
"@typescript-eslint/prefer-interface": "off", | ||
"@typescript-eslint/no-non-null-assertion": "off", | ||
"no-unused-vars": [ | ||
"error", | ||
{ | ||
args: "none", | ||
ignoreRestSiblings: true, | ||
vars: "all", | ||
varsIgnorePattern: "^_+$", | ||
}, | ||
], | ||
}, | ||
env: { | ||
es6: true, | ||
browser: true, | ||
node: true, | ||
jest: true, | ||
}, | ||
}; | ||
const testSettings = { | ||
env: { ...defaultSettings.env, jest: true }, | ||
files: ["tests/**/*.{js,jsx,mjs,ts,tsx}", "**/*.test.{js,jsx,mjs,ts,tsx}"], | ||
rules: { ...defaultSettings.rules, "import/first": "off" }, | ||
}; | ||
const typescriptSettings = { | ||
files: ["src/**/*.{ts,tsx}"], | ||
extends: [ | ||
"eslint:recommended", | ||
"plugin:@typescript-eslint/recommended", | ||
// keep at the end | ||
"plugin:prettier/recommended", | ||
"prettier/@typescript-eslint", | ||
], | ||
parser: "@typescript-eslint/parser", | ||
parserOptions: { | ||
ecmaVersion: 9, | ||
sourceType: "module", | ||
ecmaFeatures: { jsx: true }, | ||
ecmaVersion: 2020, | ||
project: "./tsconfig.json", | ||
warnOnUnsupportedTypeScriptVersion: false, | ||
}, | ||
overrides: [ | ||
{ | ||
files: ["*.js"], | ||
rules: { | ||
"@typescript-eslint/no-var-requires": "off", | ||
plugins: ["@typescript-eslint"], | ||
rules: { | ||
...defaultSettings.rules, | ||
"@typescript-eslint/member-delimiter-style": [ | ||
"error", | ||
{ | ||
multiline: { delimiter: "comma", requireLast: true }, | ||
singleline: { delimiter: "comma", requireLast: false }, | ||
}, | ||
}, | ||
], | ||
], | ||
"@typescript-eslint/explicit-function-return-type": "off", | ||
"@typescript-eslint/no-unused-vars": "error", | ||
}, | ||
}; | ||
module.exports = { | ||
...defaultSettings, | ||
overrides: [testSettings, typescriptSettings], | ||
}; |
@@ -0,1 +1,16 @@ | ||
# [0.4.0](https://github.com/benawad/destiny/compare/v0.3.1...v0.4.0) (2020-03-04) | ||
### Bug Fixes | ||
- ensure path correctness ([f22f07f](https://github.com/benawad/destiny/commit/f22f07f962c130a821009b43c0e568d819533b96)), closes [#81](https://github.com/benawad/destiny/issues/81) | ||
- extend linting ([039cadf](https://github.com/benawad/destiny/commit/039cadf98fb5e08fcf469faa95154b763b64c3bd)) | ||
- fix new linting issues ([c19f2e5](https://github.com/benawad/destiny/commit/c19f2e53a93c3859c7c02fd206c87c2b367efe36)) | ||
- Fix time complexity when searching for common parent dir ([ce9915e](https://github.com/benawad/destiny/commit/ce9915eac72870135e30b9b895a4ad265a004266)) | ||
- improve regex path matching ([ff9b19b](https://github.com/benawad/destiny/commit/ff9b19b43870a6fd0d43364ce9e342c4f2c6b30c)), closes [#87](https://github.com/benawad/destiny/issues/87) | ||
- support paths in config via include ([c049ceb](https://github.com/benawad/destiny/commit/c049cebf336da6a01f3b931a2ce0b62d0f482b06)) | ||
### Features | ||
- Add CLI flag to rename single files ([2de9ff1](https://github.com/benawad/destiny/commit/2de9ff1acde8c040fe324b8d9a5fc37b6413e935)) | ||
## [0.3.1](https://github.com/benawad/destiny/compare/v0.3.0...v0.3.1) (2020-02-26) | ||
@@ -2,0 +17,0 @@ |
module.exports = { | ||
testPathIgnorePatterns: ["/tests/fixtures/", "/test/tmp/"], | ||
testPathIgnorePatterns: ["<rootDir>/tests/fixtures/", "<rootDir>/tests/tmp/"], | ||
coveragePathIgnorePatterns: ["/tests/tmp/"], | ||
}; |
@@ -13,10 +13,10 @@ #!/usr/bin/env node | ||
var chalk = _interopDefault(require('chalk')); | ||
var fs = _interopDefault(require('fs')); | ||
var glob = _interopDefault(require('glob')); | ||
var cosmiconfig = require('cosmiconfig'); | ||
var fs = require('fs-extra'); | ||
var fs__default = _interopDefault(fs); | ||
var path = _interopDefault(require('path')); | ||
var fs$1 = require('fs-extra'); | ||
var fs$1__default = _interopDefault(fs$1); | ||
var Git = _interopDefault(require('simple-git/promise')); | ||
var fs$1 = _interopDefault(require('fs')); | ||
var resolve = _interopDefault(require('resolve')); | ||
var glob = _interopDefault(require('glob')); | ||
@@ -50,79 +50,119 @@ const error = (err, code = 0) => { | ||
const moveFiles = async (newStructure, parentFolder) => { | ||
const git = Git(parentFolder); | ||
let isRepo = false; | ||
const isDirectory = filePath => fs$1.lstatSync(filePath).isDirectory(); | ||
try { | ||
isRepo = await git.checkIsRepo(); | ||
} catch {} | ||
const isFile = filePath => fs$1.lstatSync(filePath).isFile(); | ||
for (const [k, newLocation] of Object.entries(newStructure)) { | ||
// skip globals | ||
if (k.includes("..")) { | ||
continue; | ||
} | ||
const globSearch = pattern => { | ||
const matches = glob.sync(pattern); | ||
const files = matches.filter(match => isFile(match)); | ||
const oldAbsLocation = path.resolve(path.join(parentFolder, k)); | ||
const newAbsLocation = path.resolve(path.join(parentFolder, newLocation)); | ||
if (files.length === 0) { | ||
logger.error("Could not find any files for: " + pattern, 1); | ||
} | ||
if (oldAbsLocation !== newAbsLocation) { | ||
// make folders | ||
fs__default.ensureDirSync(path.dirname(newAbsLocation)); | ||
let shouldGitMv = false; | ||
return files; | ||
}; | ||
/** Recursively get all file paths. */ | ||
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 getFilePaths = rootPath => { | ||
const filePaths = []; | ||
const paths = [rootPath]; | ||
while (paths.length > 0) { | ||
const filePath = paths.shift(); | ||
if (filePath == null || filePath.length === 0) continue; | ||
const isGlobPattern = glob.hasMagic(filePath); | ||
if (isGlobPattern) { | ||
filePaths.push(...globSearch(filePath)); | ||
continue; | ||
} | ||
if (fs$1.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}`); | ||
} | ||
} | ||
return filePaths; | ||
}; | ||
/** Get a restructure map with rootPath keys and filePaths values. */ | ||
const getRestructureMap = rootPaths => rootPaths.reduce((acc, rootPath) => ({ ...acc, | ||
[rootPath]: getFilePaths(rootPath) | ||
}), {}); | ||
async function isFileGitTracked(git, location) { | ||
return git.silent(true).raw(["ls-files", "--error-unmatch", location]).then(() => true).catch(() => false); | ||
} | ||
/** Moves each file in the tree from old path to new path. */ | ||
async function moveFiles(tree, parentFolder) { | ||
const git = Git(parentFolder); | ||
const isFolderGitTracked = await git.checkIsRepo(); | ||
for (const [oldPath, newPath] of Object.entries(tree)) { | ||
// skip globals | ||
if (oldPath.includes("..")) continue; | ||
const oldAbsolutePath = path.resolve(parentFolder, oldPath); | ||
const newAbsolutePath = path.resolve(parentFolder, newPath); | ||
if (oldAbsolutePath === newAbsolutePath) continue; // Create folder for files | ||
const newDirname = path.dirname(newAbsolutePath); | ||
fs$1__default.ensureDirSync(newDirname); | ||
const shouldGitMv = isFolderGitTracked && (await isFileGitTracked(git, oldAbsolutePath)); | ||
if (shouldGitMv) { | ||
await git.mv(oldAbsolutePath, newAbsolutePath); | ||
} else { | ||
fs$1__default.renameSync(oldAbsolutePath, newAbsolutePath); | ||
} | ||
} | ||
} | ||
/** Recursively removes all empty folders. */ | ||
function removeEmptyFolders(directory) { | ||
const files = fs$1.readdirSync(directory); | ||
if (!files) return fs$1.rmdirSync(directory); | ||
const files = fs.readdirSync(directory); | ||
if (!files) return fs.rmdirSync(directory); | ||
for (const filePath of files) { | ||
const fullPath = path.resolve(directory, filePath); | ||
const isDirectory = fs$1.lstatSync(fullPath).isDirectory(); | ||
const isDirectory = fs.lstatSync(fullPath).isDirectory(); | ||
if (!isDirectory) continue; | ||
removeEmptyFolders(fullPath); | ||
const isEmpty = fs$1.readdirSync(fullPath).length === 0; | ||
if (isEmpty) fs$1.rmdirSync(fullPath); | ||
const isEmpty = fs.readdirSync(fullPath).length === 0; | ||
if (isEmpty) fs.rmdirSync(fullPath); | ||
} | ||
} | ||
function findEdges(filePath) { | ||
const importRegex = /(?:(?:from|import)\s+["'](\.[^'"]*)["'])|(?:(?:require|import)\s*\(["'](\.[^'"]*)["']\))/gm; | ||
const commentRegex = /\/\*[^]*?\*\/|^.*\/\/.*$/gm; | ||
const edges = []; | ||
const fileContent = fs$1.readFileSync(filePath, { | ||
/** Find all imports for file path. */ | ||
function findImports(filePath) { | ||
const reImport = /(?:(?:import|from)\s+|(?:import|require)\s*\()['"]((?:\.{1,2})(?:\/.+)?)['"]/gm; | ||
const reComment = /\/\*[^]*?\*\/|^.*\/\/.*$/gm; | ||
const importPaths = []; | ||
const fileContent = fs.readFileSync(filePath, { | ||
encoding: "utf8" | ||
}).toString().replace(commentRegex, ""); | ||
}).replace(reComment, ""); | ||
let matches; | ||
while ((matches = importRegex.exec(fileContent)) !== null) { | ||
var _matches$; | ||
while ((matches = reImport.exec(fileContent)) !== null) { | ||
// This is necessary to avoid infinite loops with zero-width matches. | ||
if (matches.index === importRegex.lastIndex) { | ||
importRegex.lastIndex++; | ||
if (matches.index === reImport.lastIndex) { | ||
reImport.lastIndex++; | ||
} | ||
edges.push([filePath, (_matches$ = matches[1]) != null ? _matches$ : matches[2]]); | ||
importPaths.push(matches[1]); | ||
} | ||
return edges; | ||
return importPaths; | ||
} | ||
@@ -211,21 +251,21 @@ | ||
for (const filePath of filePaths) { | ||
const imports = findEdges(filePath); | ||
if (!imports.length) continue; | ||
const importPaths = findImports(filePath); | ||
if (!importPaths.length) continue; | ||
const basedir = path.dirname(filePath); | ||
const newFilePath = getNewFilePath(filePath, rootOptions); | ||
const ogText = fs.readFileSync(filePath).toString(); | ||
const ogText = fs$1.readFileSync(filePath).toString(); | ||
let newText = ogText.repeat(1); | ||
for (const imp of imports) { | ||
const absPath = customResolve(imp[1], basedir); | ||
for (const importPath of importPaths) { | ||
const absPath = customResolve(importPath, basedir); | ||
if (absPath == null) { | ||
logger.error(`Cannot find import ${imp[1]}`); | ||
logger.error(`Cannot find import ${importPath}`); | ||
continue; | ||
} | ||
const newImportText = getNewImportPath(absPath, newFilePath, rootOptions); | ||
const newImportPath = getNewImportPath(absPath, newFilePath, rootOptions); | ||
if (newImportText) { | ||
newText = newText.replace(`'${imp[1]}'`, `'${newImportText}'`).replace(`"${imp[1]}"`, `"${newImportText}"`); | ||
if (newImportPath != null) { | ||
newText = newText.replace(`'${importPath}'`, `'${newImportPath}'`).replace(`"${importPath}"`, `"${newImportPath}"`); | ||
} | ||
@@ -235,3 +275,3 @@ } | ||
if (newText !== ogText) { | ||
fs.writeFileSync(filePath, newText); | ||
fs$1.writeFileSync(filePath, newText); | ||
} | ||
@@ -257,3 +297,3 @@ } | ||
const createBranchFromParts = (parts, count) => parts.slice(0, count).join("/"); | ||
const createBranchFromParts = (parts, count) => path.join(...parts.slice(0, count)); | ||
/** Remove path that matches `match` but save '/' to calculate position. */ | ||
@@ -355,59 +395,29 @@ | ||
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); | ||
const [shortest, secondShortest] = paths.length > 2 ? paths.sort((a, b) => a.length - b.length) : paths; | ||
const secondShortestParts = secondShortest.split(path.sep); | ||
return shortest.split(path.sep).filter((part, idx) => part === secondShortestParts[idx]).join(path.sep); | ||
}; | ||
if (allPartsMatch) { | ||
parentPaths.push(part); | ||
} | ||
}); | ||
return parentPaths.join("/"); | ||
const isFilePathIgnored = filePath => { | ||
const ignoreList = [/^\.git/]; | ||
return ignoreList.some(re => re.test(filePath)); | ||
}; | ||
/** Build graph of all file paths and their own imports. */ | ||
function addEdgeToGraph([start, end], graph) { | ||
if (!(start in graph)) { | ||
graph[start] = []; | ||
} | ||
if (graph[start].includes(end)) return; | ||
graph[start].push(end); | ||
} | ||
function buildGraph(files) { | ||
const parentFolder = findSharedParent(files); | ||
function buildGraph(filePaths) { | ||
const parentFolder = findSharedParent(filePaths); | ||
const graph = {}; | ||
const oldGraph = {}; | ||
const totalFiles = []; | ||
let numForwardSlashes = 0; | ||
let numBackSlashes = 0; | ||
for (let file of files) { | ||
if (file === ".git") { | ||
continue; | ||
} | ||
file = path.resolve(file); | ||
const start = path.relative(parentFolder, file); | ||
if (!(start in oldGraph)) { | ||
oldGraph[start] = { | ||
oldLocation: file, | ||
imports: [] | ||
}; | ||
} | ||
for (let filePath of filePaths) { | ||
if (isFilePathIgnored(filePath)) continue; | ||
filePath = path.resolve(filePath); | ||
const start = path.relative(parentFolder, filePath); | ||
totalFiles.push(start); | ||
findEdges(file).forEach(edge => { | ||
if (edge[1].includes("/")) { | ||
numForwardSlashes++; | ||
} else if (edge[1].includes("\\")) { | ||
numBackSlashes++; | ||
} | ||
findImports(filePath).forEach(importPath => { | ||
const pathWithExtension = customResolve(importPath, path.dirname(filePath)); | ||
const pathWithExtension = customResolve(edge[1], path.dirname(edge[0])); | ||
if (pathWithExtension == null) { | ||
logger.error(`Cannot find import ${edge[1]}`); | ||
logger.error(`Cannot find import ${importPath}`); | ||
return; | ||
@@ -417,7 +427,10 @@ } | ||
const end = path.relative(parentFolder, pathWithExtension); | ||
addEdgeToGraph([start, end], graph); | ||
oldGraph[start].imports.push({ | ||
text: edge[1], | ||
resolved: end | ||
}); | ||
if (!Array.isArray(graph[start])) { | ||
graph[start] = []; | ||
} | ||
if (!graph[start].includes(end)) { | ||
graph[start].push(end); | ||
} | ||
}); | ||
@@ -427,7 +440,6 @@ } | ||
return { | ||
files: totalFiles, | ||
graph, | ||
parentFolder, | ||
graph, | ||
files: totalFiles, | ||
oldGraph, | ||
useForwardSlash: numForwardSlashes >= numBackSlashes | ||
useForwardSlash: path.sep === "/" | ||
}; | ||
@@ -508,3 +520,4 @@ } | ||
function toFractalTree(graph, entryPoints) { | ||
const res = {}; | ||
const tree = {}; | ||
const treeSet = new Set(); | ||
const dependencies = {}; | ||
@@ -523,18 +536,17 @@ const testFiles = new Set(); | ||
const checkDuplicates = (location, dirname, filePath) => { | ||
var _newLocation; | ||
const hasLocation = treeSet.has(location); | ||
let newLocation; | ||
if (Object.values(res).includes(location)) { | ||
newLocation = path.join(dirname, filePath.replace(/\//g, "-")); | ||
if (hasLocation) { | ||
const newLocation = path.join(dirname, filePath.replace(/\//g, "-")); | ||
logger.info(`File renamed: ${filePath} -> ${newLocation}`); | ||
return newLocation; | ||
} | ||
return (_newLocation = newLocation) != null ? _newLocation : location; | ||
return location; | ||
}; | ||
const fn = (filePath, folderPath, graph) => { | ||
const basenameWithExt = path.basename(filePath); | ||
const basename = path.basename(filePath); | ||
if (isTestFile(basenameWithExt)) { | ||
if (isTestFile(basename)) { | ||
testFiles.add(filePath); | ||
@@ -544,11 +556,12 @@ return; | ||
let folderName = path.basename(filePath, path.extname(filePath)); | ||
const upperFolder = path.basename(path.dirname(filePath)); | ||
let directoryName = path.basename(filePath, path.extname(filePath)); | ||
const currentFolder = path.basename(path.dirname(filePath)); | ||
const isGlobal = filePath.includes(".."); | ||
let location = isGlobal ? filePath : path.join(folderPath, folderName === "index" && upperFolder && upperFolder !== "." ? upperFolder + path.extname(filePath) : basenameWithExt); | ||
location = checkDuplicates(location, folderPath, filePath); | ||
folderName = path.basename(location, path.extname(location)); | ||
const tempLocation = isGlobal ? filePath : path.join(folderPath, directoryName === "index" && currentFolder && currentFolder !== "." ? currentFolder + path.extname(filePath) : basename); | ||
const location = checkDuplicates(tempLocation, folderPath, filePath); | ||
directoryName = path.basename(location, path.extname(location)); | ||
if (!isGlobal) { | ||
res[filePath] = location; | ||
tree[filePath] = location; | ||
treeSet.add(location); | ||
} | ||
@@ -559,6 +572,6 @@ | ||
if ((imports == null ? void 0 : imports.length) > 0) { | ||
const newDestination = path.join(folderPath, folderName); | ||
const newDestination = path.join(folderPath, directoryName); | ||
for (const importFilePath of imports) { | ||
if (importFilePath in res) { | ||
if (importFilePath in tree) { | ||
const cycle = hasCycle(importFilePath, graph, new Set()); | ||
@@ -587,9 +600,13 @@ | ||
if (!containsCycle) { | ||
Object.entries(dependencies).forEach(([k, v]) => { | ||
if (v.length > 1 && !k.includes("..")) { | ||
const parent = findSharedParent(v); | ||
const filename = path.basename(k); | ||
const upperFolder = path.basename(path.dirname(k)); | ||
res[k] = path.join(parent, "shared", path.basename(filename, path.extname(filename)) === "index" && upperFolder && upperFolder !== "." ? upperFolder + path.extname(filename) : filename); | ||
Object.entries(dependencies).forEach(([currentPath, dependencies]) => { | ||
if (dependencies.length <= 1 || currentPath.includes("..")) { | ||
return; | ||
} | ||
const parent = findSharedParent(dependencies); | ||
const filename = path.basename(currentPath); | ||
const currentDir = path.dirname(currentPath); | ||
const newFilePath = path.join(parent, "shared", path.basename(filename, path.extname(filename)) === "index" && currentDir && currentDir !== "." ? path.join(currentDir + path.extname(filename)) : filename); | ||
tree[currentPath] = newFilePath; | ||
treeSet.add(newFilePath); | ||
}); | ||
@@ -607,16 +624,102 @@ } | ||
const testFilePath = res[firstRelativeImport]; | ||
const testFilePath = tree[firstRelativeImport]; | ||
if (!testFilePath) continue; | ||
const location = checkDuplicates(path.join(path.dirname(testFilePath), path.basename(testFile)), path.dirname(testFilePath), testFile); | ||
tree[testFile] = location; | ||
treeSet.add(location); | ||
} | ||
} | ||
if (testFilePath) { | ||
let location = path.join(path.dirname(testFilePath), path.basename(testFile)); | ||
location = checkDuplicates(location, path.dirname(testFilePath), testFile); | ||
res[testFile] = location; | ||
return tree; | ||
} | ||
const extractParentDirectory = destination => { | ||
const parts = destination.split(path.sep); | ||
if (parts.length === 1) { | ||
return; | ||
} | ||
parts.pop(); | ||
return parts.join(path.sep); | ||
}; | ||
const moveUp = destinationPath => { | ||
const parts = destinationPath.split(path.sep); | ||
return [...parts.slice(0, parts.length - 2), parts[parts.length - 1]].join(path.sep); | ||
}; | ||
const detectLonelyFiles = tree => { | ||
const fractalTree = { ...tree | ||
}; // Reverse lookup destination -> current location | ||
const reversedFractalTree = {}; | ||
for (const [currentFilePath, destinationFilePath] of Object.entries(fractalTree)) { | ||
reversedFractalTree[destinationFilePath] = currentFilePath; | ||
} | ||
const dirCounter = {}; // Sort is important here since we want to go from deep in the file structure to top | ||
const currentDestinations = Object.values(fractalTree).sort((a, b) => b.length - a.length); // Count all occurencies of the parent dirs of the current destinations | ||
for (const currentDestination of currentDestinations) { | ||
const parentDir = extractParentDirectory(currentDestination); | ||
if (!parentDir) { | ||
continue; | ||
} | ||
if (parentDir in dirCounter) { | ||
dirCounter[parentDir] = dirCounter[parentDir] + 1; | ||
continue; | ||
} | ||
dirCounter[parentDir] = 1; | ||
} | ||
/** | ||
* Loop over all the destinations again and move them up if they are lonely files | ||
*/ | ||
for (const currentDestination of currentDestinations) { | ||
const startParentDir = extractParentDirectory(currentDestination); | ||
if (!startParentDir) { | ||
continue; | ||
} | ||
let counter = dirCounter[startParentDir]; | ||
let newDestination = currentDestination; | ||
let parentDir = startParentDir; | ||
while (counter === 1) { | ||
parentDir = extractParentDirectory(newDestination); | ||
if (!parentDir) { | ||
break; | ||
} | ||
if (startParentDir === parentDir) { | ||
counter = 1; | ||
} else if (parentDir in dirCounter) { | ||
counter = dirCounter[parentDir] + 1; | ||
} | ||
dirCounter[parentDir] = counter; | ||
if (counter === 1) { | ||
newDestination = moveUp(newDestination); | ||
} | ||
} | ||
fractalTree[reversedFractalTree[currentDestination]] = newDestination; | ||
} | ||
return res; | ||
} | ||
return fractalTree; | ||
}; | ||
function generateTrees(restructureMap) { | ||
function generateTrees(restructureMap, { | ||
avoidSingleFile | ||
}) { | ||
return Object.entries(restructureMap).reduce((rootOptions, [rootPath, filePaths]) => { | ||
@@ -631,3 +734,8 @@ if (filePaths.length <= 1) return rootOptions; | ||
} = buildGraph(filePaths); | ||
const tree = toFractalTree(graph, findEntryPoints(graph)); | ||
let tree = toFractalTree(graph, findEntryPoints(graph)); | ||
if (avoidSingleFile) { | ||
tree = detectLonelyFiles(tree); | ||
} | ||
const usedFilePaths = new Set(Object.entries(graph).flat(2)); | ||
@@ -651,62 +759,13 @@ const unusedFiles = files.filter(filePath => !usedFilePaths.has(filePath)); | ||
var version = "0.3.1"; | ||
var version = "0.4.0"; | ||
const isDirectory = filePath => fs.lstatSync(filePath).isDirectory(); | ||
const isFile = filePath => fs.lstatSync(filePath).isFile(); | ||
const globSearch = pattern => { | ||
const matches = glob.sync(pattern); | ||
const files = matches.filter(match => isFile(match)); | ||
if (files.length === 0) { | ||
logger.error("Could not find any files for: " + pattern, 1); | ||
} | ||
return files; | ||
}; | ||
/** Recursively get all file paths. */ | ||
const getFilePaths = rootPath => { | ||
const filePaths = []; | ||
const paths = [rootPath]; | ||
while (paths.length > 0) { | ||
const filePath = paths.shift(); | ||
if (filePath == null || filePath.length === 0) continue; | ||
const isGlobPattern = glob.hasMagic(filePath); | ||
if (isGlobPattern) { | ||
filePaths.push(...globSearch(filePath)); | ||
continue; | ||
} | ||
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}`); | ||
} | ||
} | ||
return filePaths; | ||
}; | ||
/** Get a restructure map with rootPath keys and filePaths values. */ | ||
const getRestructureMap = rootPaths => rootPaths.reduce((acc, rootPath) => ({ ...acc, | ||
[rootPath]: getFilePaths(rootPath) | ||
}), {}); | ||
const { | ||
argv | ||
} = process; | ||
const defaultOptions = { | ||
const defaultConfig = { | ||
help: false, | ||
include: [], | ||
version: false, | ||
write: false | ||
write: false, | ||
avoidSingleFile: false | ||
}; | ||
@@ -727,5 +786,6 @@ | ||
-V, --version output version number | ||
-h, --help output usage information | ||
-w, --write restructure and edit folders and files | ||
-V, --version Output version number | ||
-h, --help Output usage information | ||
-w, --write Restructure and edit folders and files | ||
-S, --avoid-single-file Flag to indicate if single files in folders should be avoided | ||
`); | ||
@@ -735,46 +795,61 @@ return process.exit(exitCode); | ||
const parseArgs = args => args.reduce((acc, arg) => { | ||
switch (arg) { | ||
case "-h": | ||
case "--help": | ||
acc.options.help = true; | ||
break; | ||
const parseArgs = args => { | ||
const cliConfig = {}; | ||
case "-V": | ||
case "--version": | ||
acc.options.version = true; | ||
break; | ||
while (args.length > 0) { | ||
const arg = args.shift(); | ||
if (arg == null) break; | ||
case "-w": | ||
case "--write": | ||
acc.options.write = true; | ||
break; | ||
switch (arg) { | ||
case "-h": | ||
case "--help": | ||
cliConfig.help = true; | ||
break; | ||
default: | ||
acc.rootPaths.push(arg); | ||
case "-V": | ||
case "--version": | ||
cliConfig.version = true; | ||
break; | ||
case "-w": | ||
case "--write": | ||
cliConfig.write = true; | ||
break; | ||
case "-S": | ||
case "--avoid-single-file": | ||
cliConfig.avoidSingleFile = true; | ||
case "-G": | ||
default: | ||
{ | ||
if (fs.existsSync(arg) || glob.hasMagic(arg)) { | ||
var _cliConfig$include; | ||
cliConfig.include = [...((_cliConfig$include = cliConfig.include) != null ? _cliConfig$include : []), arg]; | ||
} | ||
} | ||
} | ||
} | ||
return acc; | ||
}, { | ||
options: {}, | ||
rootPaths: [] | ||
}); | ||
return cliConfig; | ||
}; | ||
const run = async args => { | ||
const getMergedConfig = cliConfig => { | ||
var _ref, _cosmiconfigSync$sear; | ||
const config = (_ref = (_cosmiconfigSync$sear = cosmiconfig.cosmiconfigSync("destiny").search()) == null ? void 0 : _cosmiconfigSync$sear.config) != null ? _ref : {}; | ||
const { | ||
options, | ||
rootPaths | ||
} = parseArgs(args); | ||
const mergedOptions = { ...defaultOptions, | ||
...config, | ||
...options | ||
const externalConfig = (_ref = (_cosmiconfigSync$sear = cosmiconfig.cosmiconfigSync("destiny").search()) == null ? void 0 : _cosmiconfigSync$sear.config) != null ? _ref : {}; | ||
return { ...defaultConfig, | ||
...externalConfig, | ||
...cliConfig | ||
}; | ||
if (mergedOptions.help) return printHelp(0); | ||
if (mergedOptions.version) return printVersion(); | ||
if (rootPaths.length === 0) return printHelp(1); | ||
logger.info("Resolving files."); | ||
const restructureMap = getRestructureMap(rootPaths); | ||
}; | ||
const run = async args => { | ||
const cliConfig = parseArgs(args); | ||
const mergedConfig = getMergedConfig(cliConfig); | ||
if (mergedConfig.help) return printHelp(0); | ||
if (mergedConfig.version) return printVersion(); | ||
if (mergedConfig.include.length === 0) return printHelp(1); | ||
const restructureMap = getRestructureMap(mergedConfig.include); | ||
const filesToEdit = Object.values(restructureMap).flat(); | ||
@@ -787,5 +862,5 @@ | ||
const rootOptions = generateTrees(restructureMap); | ||
const rootOptions = generateTrees(restructureMap, mergedConfig); | ||
if (mergedOptions.write) { | ||
if (mergedConfig.write) { | ||
await formatFileStructure(filesToEdit, rootOptions); | ||
@@ -792,0 +867,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 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=[".js",".json",".jsx",".sass",".scss",".svg",".ts",".tsx"],v=(e,n)=>{try{return c.sync(e,{basedir:n,extensions:y})}catch(e){return null}},j=(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},w=(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)},x=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=j(t,n),l=r.readFileSync(t).toString();let c=l.repeat(1);for(const t of e){const e=v(t[1],o);if(null==e){u(`Cannot find import ${t[1]}`);continue}const r=w(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!")},S=e=>e.replace(/([^/]+)(?=\.)/g,String.fromCharCode(Number.MAX_SAFE_INTEGER)+"$1"),$=(e,n)=>S(e).localeCompare(S(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($)})(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=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 O([e,n],t){e in t||(t[e]=[]),t[e].includes(n)||t[e].push(n)}const q=e=>/\.test\.|\.spec\.|\.stories\./.test(e);function k(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=>q(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 R=(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=R(e,n,new Set(t));if(r)return r}return null};function A(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(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=v(o[1],s.dirname(o[0]));if(null==c)return void u(`Cannot find import ${o[1]}`);const a=s.relative(n,c);O([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(q(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=R(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=E(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,k(o)),h=new Set(Object.entries(o).flat(2)),m=i.filter(e=>!h.has(e));return d(n.bold.blue(t)),F(Object.values(a)),m.length>0&&p(`Found ${m.length} unused files:`+"\n"+m.join("\n")),e.push({parentFolder:c,tree:a,useForwardSlash:l}),e},[])}const C=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},N=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):C(e)&&t.push(s.join(e,"/**/*.*")):u(`Unable to resolve the path: ${e}`))}return n},{argv:D}=process,G={help:!1,version:!1,write:!1},M=e=>(console.log(n`{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=e(require("fs")),r=e(require("glob")),o=require("cosmiconfig"),s=e(require("path")),i=require("fs-extra"),c=e(i),l=e(require("simple-git/promise")),a=e(require("resolve"));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=e=>i.lstatSync(e).isDirectory(),g=e=>i.lstatSync(e).isFile(),m=e=>{const n=r.sync(e).filter(e=>g(e));return 0===n.length&&u("Could not find any files for: "+e,1),n},b=e=>{const n=[],t=[e];for(;t.length>0;){const e=t.shift();null!=e&&0!==e.length&&(r.hasMagic(e)?n.push(...m(e)):i.existsSync(e)?g(e)?n.push(e):h(e)&&t.push(s.join(e,"/**/*.*")):u(`Unable to resolve the path: ${e}`))}return n};async function v(e,n){return e.silent(!0).raw(["ls-files","--error-unmatch",n]).then(()=>!0).catch(()=>!1)}async function y(e,n){const t=l(n),r=await t.checkIsRepo();for(const[o,i]of Object.entries(e)){if(o.includes(".."))continue;const e=s.resolve(n,o),l=s.resolve(n,i);if(e===l)continue;const a=s.dirname(l);c.ensureDirSync(a),r&&await v(t,e)?await t.mv(e,l):c.renameSync(e,l)}}function j(e){const n=t.readdirSync(e);if(!n)return t.rmdirSync(e);for(const r of n){const n=s.resolve(e,r);t.lstatSync(n).isDirectory()&&(j(n),0===t.readdirSync(n).length&&t.rmdirSync(n))}}function S(e){const n=/(?:(?:import|from)\s+|(?:import|require)\s*\()['"]((?:\.{1,2})(?:\/.+)?)['"]/gm,r=[],o=t.readFileSync(e,{encoding:"utf8"}).replace(/\/\*[^]*?\*\/|^.*\/\/.*$/gm,"");let s;for(;null!==(s=n.exec(o));)s.index===n.lastIndex&&n.lastIndex++,r.push(s[1]);return r}const w=(e,n,t)=>{const r=s.dirname(e),o=s.relative(r,n),i=s.dirname(o),c=function(e){const n=s.extname(e);return[".js",".jsx",".ts",".tsx"].includes(n)?n:void 0}(o),l=s.basename(o,c);let a=s.join(i,l);return!a.startsWith(".")&&(a="./"+a),a=t?a.replace(/\\/g,"/"):a.replace(/\/|\\+/g,"\\\\"),a},x=[".js",".json",".jsx",".sass",".scss",".svg",".ts",".tsx"],F=(e,n)=>{try{return a.sync(e,{basedir:n,extensions:x})}catch(e){return null}},$=(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},O=(e,n,t)=>{let r=!0;for(const{tree:o,parentFolder:i,useForwardSlash:c}of t){r=c;const t=s.relative(i,e);if(t in o)return w(n,s.resolve(s.join(i,o[t])),c)}return w(n,e,r)},E=async(e,n)=>{f("Fixing imports."),((e,n)=>{for(const t of e){const e=S(t);if(!e.length)continue;const r=s.dirname(t),o=$(t,n),c=i.readFileSync(t).toString();let l=c.repeat(1);for(const t of e){const e=F(t,r);if(null==e){u(`Cannot find import ${t}`);continue}const s=O(e,o,n);null!=s&&(l=l.replace(`'${t}'`,`'${s}'`).replace(`"${t}"`,`"${s}"`))}l!==c&&i.writeFileSync(t,l)}})(e,n);for(const{tree:e,parentFolder:t}of n)f("Moving files."),await y(e,t),j(t);f("Restructure was successful!")},q=e=>e.replace(/([^/]+)(?=\.)/g,String.fromCharCode(Number.MAX_SAFE_INTEGER)+"$1"),k=(e,n)=>q(e).localeCompare(q(n)),A=e=>{const n=e.reduce((e,n)=>{const t=n.split("/");return t.forEach((n,r)=>e.add(((e,n)=>s.join(...e.slice(0,n)))(t,r+1))),e},new Set);return Array.from(n).sort(k)},R=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}})})(A(e)),r=t.reduce((e,r,o)=>{var s;const i=null!=(s=e[o-1])?s:"",c=t.slice(o+1),l=c.length>0&&c[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,c)?"└──":"├──")+(l?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},C=e=>{if(1===e.length)return s.dirname(e[0]);const[n,t]=e.length>2?e.sort((e,n)=>e.length-n.length):e,r=t.split(s.sep);return n.split(s.sep).filter((e,n)=>e===r[n]).join(s.sep)},I=e=>[/^\.git/].some(n=>n.test(e));const M=e=>/\.test\.|\.spec\.|\.stories\./.test(e);function N(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=>M(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 D=(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=D(e,n,new Set(t));if(r)return r}return null};const G=e=>{const n=e.split(s.sep);if(1!==n.length)return n.pop(),n.join(s.sep)},_=e=>{const n=e.split(s.sep);return[...n.slice(0,n.length-2),n[n.length-1]].join(s.sep)};function P(e,{avoidSingleFile:t}){return Object.entries(e).reduce((e,[r,o])=>{if(o.length<=1)return e;f(`Generating tree for: ${r}`);const{graph:i,files:c,useForwardSlash:l,parentFolder:a}=function(e){const n=C(e),t={},r=[];for(let o of e){if(I(o))continue;o=s.resolve(o);const e=s.relative(n,o);r.push(e),S(o).forEach(r=>{const i=F(r,s.dirname(o));if(null==i)return void u(`Cannot find import ${r}`);const c=s.relative(n,i);Array.isArray(t[e])||(t[e]=[]),t[e].includes(c)||t[e].push(c)})}return{files:r,graph:t,parentFolder:n,useForwardSlash:"/"===s.sep}}(o);let h=function(e,n){const t={},r=new Set,o={},i=new Set;let c=!1;const l=(e,n)=>{Array.isArray(o[e])||(o[e]=[]),o[e].push(n)},a=(e,n,t)=>{if(r.has(e)){const e=s.join(n,t.replace(/\//g,"-"));return f(`File renamed: ${t} -> ${e}`),e}return e},u=(e,n,o)=>{const f=s.basename(e);if(M(f))return void i.add(e);let d=s.basename(e,s.extname(e));const h=s.basename(s.dirname(e)),g=e.includes(".."),m=g?e:s.join(n,"index"===d&&h&&"."!==h?h+s.extname(e):f),b=a(m,n,e);d=s.basename(b,s.extname(b)),g||(t[e]=b,r.add(b));const v=o[e];if((null==v?void 0:v.length)>0){const e=s.join(n,d);for(const n of v)if(n in t){const e=D(n,o,new Set);e?(c=!0,p(`Dependency cycle detected: ${e.join(" -> ")}`)):l(n,b)}else l(n,b),u(n,e,o)}};for(const t of n)u(t,"",e);if(c||Object.entries(o).forEach(([e,n])=>{if(n.length<=1||e.includes(".."))return;const o=C(n),i=s.basename(e),c=s.dirname(e),l=s.join(o,"shared","index"===s.basename(i,s.extname(i))&&c&&"."!==c?s.join(c+s.extname(i)):i);t[e]=l,r.add(l)}),i.size>0)for(const n of i){const[o]=e[n];if(!o)continue;const i=t[o];if(!i)continue;const c=a(s.join(s.dirname(i),s.basename(n)),s.dirname(i),n);t[n]=c,r.add(c)}return t}(i,N(i));t&&(h=(e=>{const n={...e},t={};for(const[e,r]of Object.entries(n))t[r]=e;const r={},o=Object.values(n).sort((e,n)=>n.length-e.length);for(const e of o){const n=G(e);n&&(r[n]=n in r?r[n]+1:1)}for(const e of o){const o=G(e);if(!o)continue;let s=r[o],i=e,c=o;for(;1===s&&(c=G(i),c);)o===c?s=1:c in r&&(s=r[c]+1),r[c]=s,1===s&&(i=_(i));n[t[e]]=i}return n})(h));const g=new Set(Object.entries(i).flat(2)),m=c.filter(e=>!g.has(e));return d(n.bold.blue(r)),R(Object.values(h)),m.length>0&&p(`Found ${m.length} unused files:`+"\n"+m.join("\n")),e.push({parentFolder:a,tree:h,useForwardSlash:l}),e},[])}const{argv:T}=process,U={help:!1,include:[],version:!1,write:!1,avoidSingleFile:!1},V=e=>(console.log(n`{blue destiny} - Prettier for file structures. | ||
@@ -12,5 +12,6 @@ {bold USAGE} | ||
-V, --version output version number | ||
-h, --help output usage information | ||
-w, --write restructure and edit folders and files | ||
`),process.exit(e)),_=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={...G,...o,...s};if(l.help)return M(0);if(l.version)return console.log("v0.3.1");if(0===i.length)return M(1);f("Resolving files.");const c=(e=>e.reduce((e,n)=>({...e,[n]:N(n)}),{}))(i),a=Object.values(c).flat();if(0===a.length)return void u("Could not find any files to restructure",1);const d=A(c);l.write&&await x(a,d)};_(D.slice(2,D.length)),exports.run=_; | ||
-V, --version Output version number | ||
-h, --help Output usage information | ||
-w, --write Restructure and edit folders and files | ||
-S, --avoid-single-file Flag to indicate if single files in folders should be avoided | ||
`),process.exit(e)),W=async e=>{const n=(e=>{var n,t;const r=null!=(n=null==(t=o.cosmiconfigSync("destiny").search())?void 0:t.config)?n:{};return{...U,...r,...e}})((e=>{const n={};for(;e.length>0;){const s=e.shift();if(null==s)break;switch(s){case"-h":case"--help":n.help=!0;break;case"-V":case"--version":n.version=!0;break;case"-w":case"--write":n.write=!0;break;case"-S":case"--avoid-single-file":n.avoidSingleFile=!0;case"-G":default:var o;if(t.existsSync(s)||r.hasMagic(s))n.include=[...null!=(o=n.include)?o:[],s]}}return n})(e));if(n.help)return V(0);if(n.version)return console.log("v0.4.0");if(0===n.include.length)return V(1);const s=n.include.reduce((e,n)=>({...e,[n]:b(n)}),{});const i=Object.values(s).flat();if(0===i.length)return void u("Could not find any files to restructure",1);const c=P(s,n);n.write&&await E(i,c)};W(T.slice(2,T.length)),exports.run=W; |
{ | ||
"name": "destiny", | ||
"version": "0.3.1", | ||
"version": "0.4.0", | ||
"description": "Prettier for file structures", | ||
@@ -21,3 +21,3 @@ "license": "MIT", | ||
"check": "tsc", | ||
"clean": "rimraf lib", | ||
"clean": "rimraf coverage lib", | ||
"format": "run-s format:*", | ||
@@ -31,2 +31,3 @@ "format:eslint": "npm run lint -- --fix", | ||
"test": "jest --colors", | ||
"test:coverage": "npm run test -- --coverage", | ||
"watch": "npm run build -- --watch" | ||
@@ -61,6 +62,12 @@ }, | ||
"@typescript-eslint/parser": "^2.19.2", | ||
"babel-eslint": "^10.1.0", | ||
"babel-jest": "^25.1.0", | ||
"eslint": "^6.8.0", | ||
"eslint-config-prettier": "^6.10.0", | ||
"eslint-config-standard": "^14.1.0", | ||
"eslint-plugin-import": "^2.20.1", | ||
"eslint-plugin-node": "^11.0.0", | ||
"eslint-plugin-prettier": "^3.1.2", | ||
"eslint-plugin-promise": "^4.2.1", | ||
"eslint-plugin-standard": "^4.0.1", | ||
"husky": ">=4", | ||
@@ -67,0 +74,0 @@ "jest": "^25.1.0", |
@@ -1,9 +0,29 @@ | ||
# destiny | ||
# Destiny | ||
Prettier for File Structures | ||
<img align="center" alt="example transformation" src="https://raw.githubusercontent.com/benawad/destiny/master/assets/example.png" /> | ||
[![npm version](https://badge.fury.io/js/destiny.svg)](https://badge.fury.io/js/destiny) | ||
[![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat)](https://github.com/benawad/destiny/issues) | ||
[![](https://github.com/benawad/destiny/workflows/ci/badge.svg)](https://github.com/benawad/destiny/actions?query=workflow%3Aci) [![Join the chat at https://gitter.im/destiny-dev/community](https://badges.gitter.im/destiny-dev/community.svg)](https://gitter.im/destiny-dev/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) | ||
<h2 align="center">Prettier for File Structures</h2> | ||
<p align="center"> | ||
<a href="https://www.npmjs.com/package/destiny"> | ||
<img alt="npm version" src="https://badge.fury.io/js/destiny.svg"> | ||
</a> | ||
<a href="https://github.com/benawad/destiny/issues"> | ||
<img alt="contributions welcome" src="https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat"> | ||
</a> | ||
<a href="https://github.com/benawad/destiny/actions?query=workflow%3Aci"> | ||
<img alt="ci workflow" src="https://github.com/benawad/destiny/workflows/ci/badge.svg"> | ||
</a> | ||
<a href="https://gitter.im/destiny-dev/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge"> | ||
<img alt="Join the chat at https://gitter.im/destiny-dev/community" src="https://badges.gitter.im/destiny-dev/community.svg"> | ||
</a> | ||
<a href="https://github.com/benawad/destiny"> | ||
<img alt="file structure: destiny" src="https://img.shields.io/badge/file%20structure-destiny-7a49ff?style=flat"> | ||
</a> | ||
</p> | ||
--- | ||
@@ -13,4 +33,2 @@ | ||
![example transformation](https://github.com/benawad/destiny/blob/master/assets/example.png) | ||
## What does this do? | ||
@@ -38,5 +56,10 @@ | ||
Dry run which will output what the resulting file structure will look like: | ||
``` | ||
npx destiny "src/**/*.*" | ||
``` | ||
This will actually move files around and fix imports: | ||
``` | ||
npx destiny -w "src/**/*.*" | ||
``` | ||
@@ -53,2 +76,10 @@ ## This tool might be useless | ||
pull requests are welcome :) | ||
Pull requests are welcome :) | ||
## Badge | ||
[![file structure: destiny](https://img.shields.io/badge/file%20structure-destiny-7a49ff?style=flat)](https://github.com/benawad/destiny) | ||
``` | ||
[![file structure: destiny](https://img.shields.io/badge/file%20structure-destiny-7a49ff?style=flat)](https://github.com/benawad/destiny) | ||
``` |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
49334
22
890
83
43