Comparing version 0.4.0 to 0.5.0
@@ -18,12 +18,7 @@ const defaultSettings = { | ||
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}"], | ||
files: ["src/**/*.ts"], | ||
extends: [ | ||
"plugin:@typescript-eslint/recommended", | ||
"plugin:@typescript-eslint/recommended-requiring-type-checking", | ||
"prettier/@typescript-eslint", | ||
@@ -44,4 +39,4 @@ ], | ||
{ | ||
multiline: { delimiter: "comma", requireLast: true }, | ||
singleline: { delimiter: "comma", requireLast: false }, | ||
multiline: { delimiter: "semi", requireLast: true }, | ||
singleline: { delimiter: "semi", requireLast: false }, | ||
}, | ||
@@ -54,2 +49,24 @@ ], | ||
const testSettings = { | ||
...typescriptSettings, | ||
env: { ...defaultSettings.env, jest: true }, | ||
extends: [ | ||
"plugin:@typescript-eslint/recommended", | ||
"prettier/@typescript-eslint", | ||
], | ||
parserOptions: { | ||
ecmaFeatures: { jsx: true }, | ||
ecmaVersion: 2020, | ||
warnOnUnsupportedTypeScriptVersion: false, | ||
}, | ||
files: ["tests/**/*.ts", "**/*.test.ts"], | ||
rules: { | ||
...typescriptSettings.rules, | ||
"@typescript-eslint/ban-ts-ignore": "off", | ||
"@typescript-eslint/no-empty-function": "off", | ||
"@typescript-eslint/no-explicit-any": "off", | ||
"import/first": "off", | ||
}, | ||
}; | ||
module.exports = { | ||
@@ -56,0 +73,0 @@ ...defaultSettings, |
@@ -0,1 +1,11 @@ | ||
# [0.5.0](https://github.com/benawad/destiny/compare/v0.4.0...v0.5.0) (2020-03-26) | ||
### Bug Fixes | ||
- Glob stays in the tree's root path - [#88](https://github.com/benawad/destiny/issues/88) ([3795d40](https://github.com/benawad/destiny/commit/3795d40b9b20e04215eb57fa8bb919a8ad774195)) | ||
### Features | ||
- **debugger:** add debugger ([10d6a8b](https://github.com/benawad/destiny/commit/10d6a8b095b74f0e34419941d64468e8ffa1bd74)) | ||
# [0.4.0](https://github.com/benawad/destiny/compare/v0.3.1...v0.4.0) (2020-03-04) | ||
@@ -2,0 +12,0 @@ |
@@ -12,20 +12,18 @@ #!/usr/bin/env node | ||
require('core-js/modules/es.promise'); | ||
var chalk = _interopDefault(require('chalk')); | ||
var fs = _interopDefault(require('fs')); | ||
var glob = _interopDefault(require('glob')); | ||
var cosmiconfig = require('cosmiconfig'); | ||
var path = _interopDefault(require('path')); | ||
var fs$1 = require('fs-extra'); | ||
var fs$1__default = _interopDefault(fs$1); | ||
var path = _interopDefault(require('path')); | ||
var chalk = _interopDefault(require('chalk')); | ||
var Git = _interopDefault(require('simple-git/promise')); | ||
var resolve = _interopDefault(require('resolve')); | ||
require('core-js/modules/es.symbol.description'); | ||
let loggerStdout = ""; | ||
const error = (err, code = 0) => { | ||
if (err instanceof Error) { | ||
console.error(err); | ||
} else { | ||
console.error(chalk`{red.bold ERROR:} ${err}`); | ||
} | ||
const text = err instanceof Error ? err : chalk.red.bold("ERROR: ") + err; | ||
loggerStdout += `${err}\n`; | ||
console.error(text); | ||
console.log("If you think this is a bug, you can report it: https://github.com/benawad/destiny/issues"); | ||
@@ -35,10 +33,44 @@ process.exit(code); | ||
const info = msg => { | ||
console.info(chalk`{green.bold INFO:} ${msg}`); | ||
const text = chalk.green.bold("INFO: ") + msg; | ||
loggerStdout += `${msg}\n`; | ||
console.info(text); | ||
}; | ||
const log = msg => { | ||
loggerStdout += `${msg}\n`; | ||
console.log(msg); | ||
}; | ||
const warn = msg => { | ||
console.warn(chalk`{yellow.bold WARN:} ${msg}`); | ||
const text = chalk.yellow.bold("WARN: ") + msg; | ||
loggerStdout += `${msg}\n`; | ||
console.warn(text); | ||
}; | ||
let isDebuggerEnabled = false; | ||
let lastDebugTimestamp = null; | ||
const enableDebugger = () => { | ||
isDebuggerEnabled = true; | ||
}; // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
const debug = (msg, ...data) => { | ||
if ( !isDebuggerEnabled) return; | ||
const currentDebugTimestamp = Date.now(); | ||
const text = chalk.magenta.bold("DEBUG: ") + chalk.yellow.bold(`+${lastDebugTimestamp ? currentDebugTimestamp - lastDebugTimestamp : 0}ms `) + msg; | ||
loggerStdout += `${msg}\n`; | ||
console.info(text); | ||
lastDebugTimestamp = currentDebugTimestamp; | ||
if (data.length > 0) { | ||
console.group(); | ||
data.forEach(d => { | ||
console.log(d, "\n"); | ||
}); | ||
console.groupEnd(); | ||
} | ||
}; | ||
const writeDebugStdout = filePath => { | ||
if ( !isDebuggerEnabled) return; | ||
const resolvedFilePath = path.resolve(filePath); | ||
if (fs.existsSync(resolvedFilePath)) error(`The debug file output already exist "${resolvedFilePath}".\nPlease give a path to a non existing file.`); | ||
fs.writeFileSync(resolvedFilePath, loggerStdout, "utf8"); | ||
debug(`stdout written in "${resolvedFilePath}"`); | ||
}; | ||
var logger = { | ||
@@ -48,8 +80,11 @@ error, | ||
log, | ||
warn | ||
warn, | ||
enableDebugger, | ||
debug, | ||
writeDebugStdout | ||
}; | ||
const isDirectory = filePath => fs$1.lstatSync(filePath).isDirectory(); | ||
const isDirectory = filePath => fs$1__default.lstatSync(filePath).isDirectory(); | ||
const isFile = filePath => fs$1.lstatSync(filePath).isFile(); | ||
const isFile = filePath => fs$1__default.lstatSync(filePath).isFile(); | ||
@@ -59,2 +94,3 @@ const globSearch = pattern => { | ||
const files = matches.filter(match => isFile(match)); | ||
logger.debug(`glob matches for "${pattern}":`, matches); | ||
@@ -67,5 +103,2 @@ if (files.length === 0) { | ||
}; | ||
/** Recursively get all file paths. */ | ||
const getFilePaths = rootPath => { | ||
@@ -77,3 +110,3 @@ const filePaths = []; | ||
const filePath = paths.shift(); | ||
if (filePath == null || filePath.length === 0) continue; | ||
if (filePath == null || filePath.length === 0) break; | ||
const isGlobPattern = glob.hasMagic(filePath); | ||
@@ -86,11 +119,15 @@ | ||
if (fs$1.existsSync(filePath)) { | ||
if (isFile(filePath)) { | ||
filePaths.push(filePath); | ||
} else if (isDirectory(filePath)) { | ||
paths.push(path.join(filePath, "/**/*.*")); | ||
} | ||
} else { | ||
if (!fs$1__default.existsSync(filePath)) { | ||
logger.error(`Unable to resolve the path: ${filePath}`); | ||
break; | ||
} | ||
if (isDirectory(filePath)) { | ||
paths.push(path.resolve(filePath, "./**/*.*")); | ||
continue; | ||
} | ||
if (isFile(filePath)) { | ||
filePaths.push(filePath); | ||
} | ||
} | ||
@@ -102,3 +139,2 @@ | ||
const getRestructureMap = rootPaths => rootPaths.reduce((acc, rootPath) => ({ ...acc, | ||
@@ -128,2 +164,3 @@ [rootPath]: getFilePaths(rootPath) | ||
const shouldGitMv = isFolderGitTracked && (await isFileGitTracked(git, oldAbsolutePath)); | ||
logger.debug(`moving "${oldAbsolutePath}" to "${newAbsolutePath}"`); | ||
@@ -150,3 +187,7 @@ if (shouldGitMv) { | ||
const isEmpty = fs.readdirSync(fullPath).length === 0; | ||
if (isEmpty) fs.rmdirSync(fullPath); | ||
if (isEmpty) { | ||
fs.rmdirSync(fullPath); | ||
logger.debug(`removing "${fullPath}" as empty folder`); | ||
} | ||
} | ||
@@ -259,4 +300,10 @@ } | ||
for (const filePath of filePaths) { | ||
logger.debug(`checking imports of "${filePath}"`); | ||
const importPaths = findImports(filePath); | ||
if (!importPaths.length) continue; | ||
if (importPaths.length === 0) { | ||
logger.debug(`no import found in "${filePath}"`); | ||
continue; | ||
} | ||
const basedir = path.dirname(filePath); | ||
@@ -277,3 +324,4 @@ const newFilePath = getNewFilePath(filePath, rootOptions); | ||
if (newImportPath != null) { | ||
if (newImportPath != null && importPath !== newImportPath) { | ||
logger.debug(`replacing import of "${importPath}" by "${newImportPath}" in "${filePath}"`); | ||
newText = newText.replace(`'${importPath}'`, `'${newImportPath}'`).replace(`"${importPath}"`, `"${newImportPath}"`); | ||
@@ -284,2 +332,3 @@ } | ||
if (newText !== ogText) { | ||
logger.debug(`writing new imports of "${filePath}"`); | ||
fs$1.writeFileSync(filePath, newText); | ||
@@ -721,2 +770,4 @@ } | ||
const getRootFolder = parentDir => parentDir.split(path.sep).pop(); | ||
function generateTrees(restructureMap, { | ||
@@ -742,3 +793,3 @@ avoidSingleFile | ||
const unusedFiles = files.filter(filePath => !usedFilePaths.has(filePath)); | ||
logger.log(chalk.bold.blue(rootPath)); | ||
logger.log(chalk.bold.blue(getRootFolder(parentFolder))); | ||
printTree(Object.values(tree)); | ||
@@ -759,4 +810,33 @@ | ||
var version = "0.4.0"; | ||
var version = "0.5.0"; | ||
/** Format the given options and print the help message */ | ||
const printHelpMessage = options => { | ||
const indent = " "; | ||
console.log(chalk`{blue destiny} - Prettier for file structures. | ||
{bold USAGE} | ||
${indent}{blue destiny} [option...] [{underline path}] | ||
${indent}The {underline path} argument can consist of either a {bold file path} or a {bold glob}. | ||
{bold OPTIONS} | ||
`); // options | ||
const optionsWithJoinedFlags = options.map(opt => ({ ...opt, | ||
flags: opt.flags.join(", ") | ||
})); | ||
const [longestFlag] = [...optionsWithJoinedFlags].sort((a, b) => b.flags.length - a.flags.length); | ||
const descriptionsX = `${longestFlag.flags}${indent}${indent}`.length; | ||
const parsedOptionsMessage = optionsWithJoinedFlags.map(({ | ||
flags, | ||
description | ||
}) => { | ||
const numOfSpacesToAdd = descriptionsX - flags.length; | ||
return `${indent}${flags}${Array(numOfSpacesToAdd).fill(" ").join("")}${description}`; | ||
}).join("\n"); | ||
console.log(parsedOptionsMessage, "\n"); | ||
}; | ||
const { | ||
@@ -770,3 +850,4 @@ argv | ||
write: false, | ||
avoidSingleFile: false | ||
avoidSingleFile: false, | ||
debug: false | ||
}; | ||
@@ -777,17 +858,18 @@ | ||
const printHelp = exitCode => { | ||
console.log(chalk`{blue destiny} - Prettier for file structures. | ||
{bold USAGE} | ||
{blue destiny} [option...] [{underline path}] | ||
The {underline path} argument can consist of either a {bold file path} or a {bold glob}. | ||
{bold OPTIONS} | ||
-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 | ||
`); | ||
printHelpMessage([{ | ||
flags: ["-V", "--version"], | ||
description: "Output version number" | ||
}, { | ||
flags: ["-h", "--help"], | ||
description: "Output usage information" | ||
}, { | ||
flags: ["-w", "--write"], | ||
description: "Restructure and edit folders and files" | ||
}, { | ||
flags: ["-S", "--avoid-single-file"], | ||
description: "Flag to indicate if single files in folders should be avoided" | ||
}, { | ||
flags: ["--debug [?output file]"], | ||
description: "Print debugging info" | ||
}]); | ||
return process.exit(exitCode); | ||
@@ -822,4 +904,8 @@ }; | ||
cliConfig.avoidSingleFile = true; | ||
break; | ||
case "-G": | ||
case "--debug": | ||
cliConfig.debug = !args[0] || args[0].startsWith("-") ? true : args.shift(); | ||
break; | ||
default: | ||
@@ -855,4 +941,14 @@ { | ||
if (mergedConfig.include.length === 0) return printHelp(1); | ||
if (mergedConfig.debug) logger.enableDebugger(); | ||
process.on("exit", () => { | ||
if (typeof mergedConfig.debug === "string") { | ||
logger.writeDebugStdout(mergedConfig.debug); | ||
} | ||
logger.debug("exiting"); | ||
}); | ||
logger.debug("config used:", mergedConfig); | ||
const restructureMap = getRestructureMap(mergedConfig.include); | ||
const filesToEdit = Object.values(restructureMap).flat(); | ||
logger.debug("restructured map:", restructureMap); | ||
@@ -865,2 +961,3 @@ if (filesToEdit.length === 0) { | ||
const rootOptions = generateTrees(restructureMap, mergedConfig); | ||
logger.debug("generated tree(s):", rootOptions); | ||
@@ -867,0 +964,0 @@ if (mergedConfig.write) { |
#!/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=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. | ||
"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("fs")),t=e(require("glob")),r=require("cosmiconfig"),o=require("fs-extra"),s=e(o),i=e(require("path")),l=e(require("chalk")),c=e(require("simple-git/promise")),a=e(require("resolve"));require("core-js/modules/es.symbol.description");let u="";const f=(e,n=0)=>{const t=e instanceof Error?e:l.red.bold("ERROR: ")+e;u+=`${e}\n`,console.error(t),console.log("If you think this is a bug, you can report it: https://github.com/benawad/destiny/issues"),process.exit(n)};let d=!1,p=null;const g=(e,...n)=>{if(!d)return;const t=Date.now(),r=l.magenta.bold("DEBUG: ")+l.yellow.bold(`+${p?t-p:0}ms `)+e;u+=`${e}\n`,console.info(r),p=t,n.length>0&&(console.group(),n.forEach(e=>{console.log(e,"\n")}),console.groupEnd())};var h=f,m=e=>{const n=l.green.bold("INFO: ")+e;u+=`${e}\n`,console.info(n)},b=e=>{u+=`${e}\n`,console.log(e)},y=e=>{const n=l.yellow.bold("WARN: ")+e;u+=`${e}\n`,console.warn(n)},v=()=>{d=!0},j=g,$=e=>{if(!d)return;const t=i.resolve(e);n.existsSync(t)&&f(`The debug file output already exist "${t}".\nPlease give a path to a non existing file.`),n.writeFileSync(t,u,"utf8"),g(`stdout written in "${t}"`)};const S=e=>s.lstatSync(e).isDirectory(),w=e=>s.lstatSync(e).isFile(),x=e=>{const n=t.sync(e),r=n.filter(e=>w(e));return j(`glob matches for "${e}":`,n),0===r.length&&h("Could not find any files for: "+e,1),r},F=e=>{const n=[],r=[e];for(;r.length>0;){const e=r.shift();if(null==e||0===e.length)break;if(t.hasMagic(e))n.push(...x(e));else{if(!s.existsSync(e)){h(`Unable to resolve the path: ${e}`);break}S(e)?r.push(i.resolve(e,"./**/*.*")):w(e)&&n.push(e)}}return n};async function O(e,n){return e.silent(!0).raw(["ls-files","--error-unmatch",n]).then(()=>!0).catch(()=>!1)}async function E(e,n){const t=c(n),r=await t.checkIsRepo();for(const[o,l]of Object.entries(e)){if(o.includes(".."))continue;const e=i.resolve(n,o),c=i.resolve(n,l);if(e===c)continue;const a=i.dirname(c);s.ensureDirSync(a);const u=r&&await O(t,e);j(`moving "${e}" to "${c}"`),u?await t.mv(e,c):s.renameSync(e,c)}}function k(e){const t=n.readdirSync(e);if(!t)return n.rmdirSync(e);for(const r of t){const t=i.resolve(e,r);n.lstatSync(t).isDirectory()&&(k(t),0===n.readdirSync(t).length&&(n.rmdirSync(t),j(`removing "${t}" as empty folder`)))}}function q(e){const t=/(?:(?:import|from)\s+|(?:import|require)\s*\()['"]((?:\.{1,2})(?:\/.+)?)['"]/gm,r=[],o=n.readFileSync(e,{encoding:"utf8"}).replace(/\/\*[^]*?\*\/|^.*\/\/.*$/gm,"");let s;for(;null!==(s=t.exec(o));)s.index===t.lastIndex&&t.lastIndex++,r.push(s[1]);return r}const A=(e,n,t)=>{const r=i.dirname(e),o=i.relative(r,n),s=i.dirname(o),l=function(e){const n=i.extname(e);return[".js",".jsx",".ts",".tsx"].includes(n)?n:void 0}(o),c=i.basename(o,l);let a=i.join(s,c);return!a.startsWith(".")&&(a="./"+a),a=t?a.replace(/\\/g,"/"):a.replace(/\/|\\+/g,"\\\\"),a},R=[".js",".json",".jsx",".sass",".scss",".svg",".ts",".tsx"],C=(e,n)=>{try{return a.sync(e,{basedir:n,extensions:R})}catch(e){return null}},I=(e,n)=>{for(const{tree:t,parentFolder:r}of n){const n=i.relative(r,e);if(n in t)return i.resolve(i.join(r,t[n]))}return e},D=(e,n,t)=>{let r=!0;for(const{tree:o,parentFolder:s,useForwardSlash:l}of t){r=l;const t=i.relative(s,e);if(t in o)return A(n,i.resolve(i.join(s,o[t])),l)}return A(n,e,r)},M=async(e,n)=>{m("Fixing imports."),((e,n)=>{for(const t of e){j(`checking imports of "${t}"`);const e=q(t);if(0===e.length){j(`no import found in "${t}"`);continue}const r=i.dirname(t),s=I(t,n),l=o.readFileSync(t).toString();let c=l.repeat(1);for(const o of e){const e=C(o,r);if(null==e){h(`Cannot find import ${o}`);continue}const i=D(e,s,n);null!=i&&o!==i&&(j(`replacing import of "${o}" by "${i}" in "${t}"`),c=c.replace(`'${o}'`,`'${i}'`).replace(`"${o}"`,`"${i}"`))}c!==l&&(j(`writing new imports of "${t}"`),o.writeFileSync(t,c))}})(e,n);for(const{tree:e,parentFolder:t}of n)m("Moving files."),await E(e,t),k(t);m("Restructure was successful!")},N=e=>e.replace(/([^/]+)(?=\.)/g,String.fromCharCode(Number.MAX_SAFE_INTEGER)+"$1"),P=(e,n)=>N(e).localeCompare(N(n)),G=e=>{const n=e.reduce((e,n)=>{const t=n.split("/");return t.forEach((n,r)=>e.add(((e,n)=>i.join(...e.slice(0,n)))(t,r+1))),e},new Set);return Array.from(n).sort(P)},T=e=>{const n=(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}})})(G(e)),t=n.reduce((e,t,r)=>{var o;const s=null!=(o=e[r-1])?o:"",i=n.slice(r+1),c=i.length>0&&i[0].position>t.position,a=(t.position>0?"│ ".repeat(t.position):"")+(((e,n)=>{for(const t of n)if(!(t.position>e.position))return t.position<e.position;return!0})(t,i)?"└──":"├──")+(c?l.bold.blue(t.text):t.text);return e.push(((e,n)=>Array.from(e).map((e,t)=>"│"===e&&(e=>"└"===e||" "===e)(n[t])?" ":e).join(""))(a,s)),e},[]).join("\n");return b(t),t},_=e=>{if(1===e.length)return i.dirname(e[0]);const[n,t]=e.length>2?e.sort((e,n)=>e.length-n.length):e,r=t.split(i.sep);return n.split(i.sep).filter((e,n)=>e===r[n]).join(i.sep)},U=e=>[/^\.git/].some(n=>n.test(e));const W=e=>/\.test\.|\.spec\.|\.stories\./.test(e);function V(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=>W(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 z=(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=z(e,n,new Set(t));if(r)return r}return null};const B=e=>{const n=e.split(i.sep);if(1!==n.length)return n.pop(),n.join(i.sep)},X=e=>{const n=e.split(i.sep);return[...n.slice(0,n.length-2),n[n.length-1]].join(i.sep)};function H(e,{avoidSingleFile:n}){return Object.entries(e).reduce((e,[t,r])=>{if(r.length<=1)return e;m(`Generating tree for: ${t}`);const{graph:o,files:s,useForwardSlash:c,parentFolder:a}=function(e){const n=_(e),t={},r=[];for(let o of e){if(U(o))continue;o=i.resolve(o);const e=i.relative(n,o);r.push(e),q(o).forEach(r=>{const s=C(r,i.dirname(o));if(null==s)return void h(`Cannot find import ${r}`);const l=i.relative(n,s);Array.isArray(t[e])||(t[e]=[]),t[e].includes(l)||t[e].push(l)})}return{files:r,graph:t,parentFolder:n,useForwardSlash:"/"===i.sep}}(r);let u=function(e,n){const t={},r=new Set,o={},s=new Set;let l=!1;const c=(e,n)=>{Array.isArray(o[e])||(o[e]=[]),o[e].push(n)},a=(e,n,t)=>{if(r.has(e)){const e=i.join(n,t.replace(/\//g,"-"));return m(`File renamed: ${t} -> ${e}`),e}return e},u=(e,n,o)=>{const f=i.basename(e);if(W(f))return void s.add(e);let d=i.basename(e,i.extname(e));const p=i.basename(i.dirname(e)),g=e.includes(".."),h=g?e:i.join(n,"index"===d&&p&&"."!==p?p+i.extname(e):f),m=a(h,n,e);d=i.basename(m,i.extname(m)),g||(t[e]=m,r.add(m));const b=o[e];if((null==b?void 0:b.length)>0){const e=i.join(n,d);for(const n of b)if(n in t){const e=z(n,o,new Set);e?(l=!0,y(`Dependency cycle detected: ${e.join(" -> ")}`)):c(n,m)}else c(n,m),u(n,e,o)}};for(const t of n)u(t,"",e);if(l||Object.entries(o).forEach(([e,n])=>{if(n.length<=1||e.includes(".."))return;const o=_(n),s=i.basename(e),l=i.dirname(e),c=i.join(o,"shared","index"===i.basename(s,i.extname(s))&&l&&"."!==l?i.join(l+i.extname(s)):s);t[e]=c,r.add(c)}),s.size>0)for(const n of s){const[o]=e[n];if(!o)continue;const s=t[o];if(!s)continue;const l=a(i.join(i.dirname(s),i.basename(n)),i.dirname(s),n);t[n]=l,r.add(l)}return t}(o,V(o));n&&(u=(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=B(e);n&&(r[n]=n in r?r[n]+1:1)}for(const e of o){const o=B(e);if(!o)continue;let s=r[o],i=e,l=o;for(;1===s&&(l=B(i),l);)o===l?s=1:l in r&&(s=r[l]+1),r[l]=s,1===s&&(i=X(i));n[t[e]]=i}return n})(u));const f=new Set(Object.entries(o).flat(2)),d=s.filter(e=>!f.has(e));return b(l.bold.blue(a.split(i.sep).pop())),T(Object.values(u)),d.length>0&&y(`Found ${d.length} unused files:`+"\n"+d.join("\n")),e.push({parentFolder:a,tree:u,useForwardSlash:c}),e},[])}const{argv:J}=process,K={help:!1,include:[],version:!1,write:!1,avoidSingleFile:!1,debug:!1},L=e=>((e=>{console.log(l`{blue destiny} - Prettier for file structures. | ||
{bold USAGE} | ||
{blue destiny} [option...] [{underline path}] | ||
${" "}{blue destiny} [option...] [{underline path}] | ||
The {underline path} argument can consist of either a {bold file path} or a {bold glob}. | ||
${" "}The {underline path} argument can consist of either a {bold file path} or a {bold glob}. | ||
{bold OPTIONS} | ||
-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; | ||
`);const n=e.map(e=>({...e,flags:e.flags.join(", ")})),[t]=[...n].sort((e,n)=>n.flags.length-e.flags.length),r=`${t.flags} `.length,o=n.map(({flags:e,description:n})=>{const t=r-e.length;return` ${e}${Array(t).fill(" ").join("")}${n}`}).join("\n");console.log(o,"\n")})([{flags:["-V","--version"],description:"Output version number"},{flags:["-h","--help"],description:"Output usage information"},{flags:["-w","--write"],description:"Restructure and edit folders and files"},{flags:["-S","--avoid-single-file"],description:"Flag to indicate if single files in folders should be avoided"},{flags:["--debug [?output file]"],description:"Print debugging info"}]),process.exit(e)),Q=async e=>{const o=(e=>{var n,t;const o=null!=(n=null==(t=r.cosmiconfigSync("destiny").search())?void 0:t.config)?n:{};return{...K,...o,...e}})((e=>{const r={};for(;e.length>0;){const s=e.shift();if(null==s)break;switch(s){case"-h":case"--help":r.help=!0;break;case"-V":case"--version":r.version=!0;break;case"-w":case"--write":r.write=!0;break;case"-S":case"--avoid-single-file":r.avoidSingleFile=!0;break;case"--debug":r.debug=!(e[0]&&!e[0].startsWith("-"))||e.shift();break;default:var o;if(n.existsSync(s)||t.hasMagic(s))r.include=[...null!=(o=r.include)?o:[],s]}}return r})(e));if(o.help)return L(0);if(o.version)return console.log("v0.5.0");if(0===o.include.length)return L(1);o.debug&&v(),process.on("exit",()=>{"string"==typeof o.debug&&$(o.debug),j("exiting")}),j("config used:",o);const s=o.include.reduce((e,n)=>({...e,[n]:F(n)}),{});const i=Object.values(s).flat();if(j("restructured map:",s),0===i.length)return void h("Could not find any files to restructure",1);const l=H(s,o);j("generated tree(s):",l),o.write&&await M(i,l)};Q(J.slice(2,J.length)),exports.run=Q; |
{ | ||
"name": "destiny", | ||
"version": "0.4.0", | ||
"version": "0.5.0", | ||
"description": "Prettier for file structures", | ||
@@ -25,3 +25,3 @@ "license": "MIT", | ||
"format:prettier": "prettier --write '{**/*,*}.{js,json,md,ts,yaml}'", | ||
"lint": "eslint '{src,test}/**/*.{js,ts}'", | ||
"lint": "eslint '{**/*,*}.{js,ts}'", | ||
"prepare": "npm run build", | ||
@@ -28,0 +28,0 @@ "prepublishOnly": "npm run build", |
@@ -55,7 +55,29 @@ # Destiny | ||
``` | ||
$ destiny --help | ||
destiny - Prettier for file structures. | ||
USAGE | ||
destiny [option...] [path] | ||
The path argument can consist of either a file path or a glob. | ||
OPTIONS | ||
-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 | ||
--debug [?output file] Print debugging info | ||
``` | ||
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: | ||
``` | ||
@@ -65,2 +87,6 @@ npx destiny -w "src/**/*.*" | ||
## Documentation | ||
You can find the [full documentation at this url](https://github.com/benawad/destiny/wiki). | ||
## This tool might be useless | ||
@@ -67,0 +93,0 @@ |
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
55912
23
1000
109