├── .babelrc ├── .eslintrc ├── .gitignore ├── .npmignore ├── README.md ├── bin └── restore-source-tree.js ├── index.js └── package.json /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-0"] 3 | } -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint" 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | .DS_Store 4 | dist.js 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | index.js 2 | .* 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # restore-source-tree 2 | Restores file structure from source map (only Webpack source map files supported) 3 | 4 | ## Usage 5 | 6 | ```sh 7 | > npm i -g restore-source-tree 8 | 9 | > restore-source-tree --out-dir 10 | ``` 11 | -------------------------------------------------------------------------------- /bin/restore-source-tree.js: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | 3 | require('../dist.js'); 4 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import mkdirp from 'mkdirp'; 4 | import { SourceMapConsumer } from 'source-map'; 5 | import { Command } from 'commander'; 6 | import glob from 'glob'; 7 | import { version } from './package.json'; 8 | 9 | const WEBPACK_PREFIX = 'webpack:///'; 10 | const WEBPACK_FOOTER = [/\/*[*\s]+WEBPACK FOOTER/, /\/\/ WEBPACK FOOTER/]; 11 | 12 | const program = new Command('restore-source-tree') 13 | .version(version) 14 | .usage('[options] ') 15 | .description('Restores file structure from source map') 16 | .option('-o, --out-dir [dir]', 'Output directory (\'output\' by default)', 'output') 17 | .option('-n, --include-node-modules', 'Include source files in node_modules') 18 | .parse(process.argv); 19 | 20 | if (program.args.length === 0) { 21 | program.outputHelp(); 22 | process.exit(1); 23 | } 24 | 25 | const readJson = filename => { 26 | try { 27 | return JSON.parse(fs.readFileSync(filename, 'utf8')); 28 | } catch(e) { 29 | console.error(`Parsing file '${filename}' failed: ${e.message}`); 30 | process.exit(1); 31 | } 32 | } 33 | 34 | const getSourceList = smc => { 35 | let sources = smc.sources 36 | .filter(src => src.startsWith(WEBPACK_PREFIX)) 37 | .map(src => [src.replace(WEBPACK_PREFIX, ''), src]) 38 | .filter(([filePath]) => !filePath.startsWith('(webpack)')); 39 | 40 | if (!program.includeNodeModules) { 41 | sources = sources.filter(([filePath]) => !filePath.startsWith('~/')); 42 | } 43 | 44 | return sources; 45 | } 46 | 47 | const trimFooter = (str) => { 48 | const index = WEBPACK_FOOTER.reduce((result, footer) => { 49 | if (result >= 0) return result; 50 | const match = footer.exec(str); 51 | if (!match) return -1; 52 | return match.index; 53 | }, -1); 54 | if (index < 0) return str; 55 | return str.substr(0, index).trimRight() + '\n'; 56 | }; 57 | 58 | const saveSourceContent = (smc, filePath, src) => { 59 | const content = trimFooter(smc.sourceContentFor(src)); 60 | const outPath = path.join(program.outDir, filePath); 61 | const dir = path.dirname(outPath); 62 | 63 | if (content.length < 2) return; 64 | 65 | mkdirp(dir, err => { 66 | if (err) { 67 | console.error('Failed creating directory', dir); 68 | process.exit(1); 69 | } else { 70 | fs.writeFile(outPath, content, err => { 71 | if (err) { 72 | console.error('Failed writing file', outPath); 73 | process.exit(1); 74 | } 75 | }); 76 | } 77 | }) 78 | } 79 | 80 | function processFile(filename) { 81 | const json = readJson(filename); 82 | 83 | const smc = new SourceMapConsumer(json); 84 | 85 | const sources = getSourceList(smc); 86 | 87 | sources.forEach(([filePath, src]) => saveSourceContent(smc, filePath, src)); 88 | 89 | console.log(`Processed ${sources.length} files`); 90 | } 91 | 92 | program.args 93 | .map(pattern => glob.sync(pattern)) 94 | .reduce((prev, curr) => prev.concat(curr), []) 95 | .forEach((filename) => { 96 | try { 97 | fs.accessSync(filename); 98 | processFile(filename); 99 | } catch (err) { 100 | console.error(err.message); 101 | } 102 | }); 103 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "restore-source-tree", 3 | "version": "0.1.1", 4 | "description": "Restores file structure from source map", 5 | "main": "dist.js", 6 | "bin": { 7 | "restore-source-tree": "bin/restore-source-tree.js" 8 | }, 9 | "scripts": { 10 | "test": "echo \"Error: no test specified\" && exit 1", 11 | "build": "NODE_ENV=production babel index.js --out-file dist.js", 12 | "prepublish": "npm run build" 13 | }, 14 | "author": "Alexander (http://kuzya.org/)", 15 | "license": "MIT", 16 | "dependencies": { 17 | "commander": "^2.9.0", 18 | "glob": "^7.1.3", 19 | "minimist": "^1.2.0", 20 | "mkdirp": "^0.5.1", 21 | "source-map": "^0.5.6" 22 | }, 23 | "devDependencies": { 24 | "babel-cli": "^6.11.4", 25 | "babel-eslint": "^6.1.2", 26 | "babel-preset-es2015": "^6.9.0", 27 | "babel-preset-stage-0": "^6.5.0", 28 | "eslint": "^3.1.1" 29 | } 30 | } 31 | --------------------------------------------------------------------------------