Socket
Socket
Sign inDemoInstall

destiny

Package Overview
Dependencies
56
Maintainers
1
Versions
23
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 0.2.1 to 0.3.0

2

.huskyrc.json
{
"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"

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc