CLI tool
paazmaya/shuji avatar
paazmaya/shuji

shuji: rebuilding readable sources from a sourcemap

Reverse engineering JavaScript and CSS sources from sourcemaps

1,247 stars189 forksJavaScriptMIT

At a glance

What is it?
shuji is a command line utility that takes a JavaScript or CSS sourcemap and writes out the original sources it describes, optionally preserving the folder structure the map implies. It is a small Node tool with three runtime dependencies and a dependency free test approach built on tape.
Who is it for?
Adopt shuji if you have a minified bundle with a sourcemap and you want to read the original sources, since it does the reconstruction and the folder layout in one command. Do not adopt it for obfuscated code without a sourcemap, because the tool works from the map and has nothing to work from otherwise.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 3 days ago.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What a sourcemap actually gives you

shuji is a command line utility for reverse engineering JavaScript and CSS sources from sourcemaps, and the name is worth a moment because it explains the scope precisely.

A sourcemap is a file that a build tool emits alongside a minified bundle. It records where each piece of the generated output came from, and, depending on how the build was configured, it either embeds the original source text or points at where the original files live. The mapping data is the part every browser devtools panel uses. The embedded or referenced original sources are the part a person can actually read.

shuji takes that map and reconstructs the readable files. The README calls this reverse engineering, and in a sense it is: you are working backwards from the artefact to something like the input. But the mechanism is not deobfuscation. The map already contains the answer, and the tool's job is to unpack it, and that distinction matters when you decide what to reach for.

The project is MIT licensed, written in JavaScript, and the version in the package manifest is 0.8.0, with the command line help output showing the same version. The last push to the repository was on 2026-10-14, there are no GitHub releases, and the package is published to npm as shuji.

One piece of the README is purely personal. There is a section on the background for the project name, saying the name is for honouring the legacy of a certain master from the Ryukyu archipelago who contributed to the martial arts we know as karate and ryukyu kobujutsu, with a link to a site called karatejukka.fi.

Installing it and the Node version floor

Installation is a global npm install, with the usual caveat about privileges.

sh
npm install --global shuji

The README notes that elevated privileges might be needed via sudo, depending on the platform, and that in most cases just the plain install works. The version floor is the detail to notice. The minimum supported version of Node.js is 24.12.0, which is the active Long Term Support version, and the package manifest declares the same bound in its engines field as node >=24.12.0.

That is a strict requirement for a small utility, and it is worth being aware of before you plan to use it in an older environment. A tool that only runs on the current LTS will not run on a node 18 or 20 runtime, so if you need it in an older build, you are looking at an older version of the package or a different approach.

The usage is one command:

sh
shuji file.js.map -o folder

A single map file in, a directory of reconstructed sources out. The project also supports a directory as the input, which is what you want when you have a build output tree with many maps in it.

The options, and what preserve actually does

The README says the output of shuji --help pretty much covers all the options, and it prints them in full.

sh
shuji - Reverse engineering JavaScript and CSS sources from sourcemaps
Usage: shuji [options] <file|directory>

  -h, --help               Help and usage instructions
  -o, --output-dir String  Output directory - default: .
  -p, --preserve           Preserve sourcemap's original folder structure.
  -M, --match String       Regular expression for matching and filtering files -
                           default: \.map$
  -v, --verbose            Verbose output, will print which file is currently being
                           processed
  -V, --version            Version number

The preserve option is the one that changes the output in a way that matters. Without it, the reconstructed sources land flat in the output directory, and with a map that describes files from several directories you end up with same-named files colliding. With it, the tool recreates the folder structure the sourcemap implies, so a file that came from src/lib/parser.js comes back as src/lib/parser.js.

The match option controls which files are processed when the input is a directory, and the default regular expression is \.map$, so by default only files ending in .map are treated as maps. That is the right default for a build output directory, and it means you can also point it at a tree that contains other files without worrying about them.

The verbose flag prints which file is currently being processed, which matters more than it sounds when the input is a large build directory.

The three runtime dependencies explain the whole implementation. optionator at 0.9.4 is the argument parser that produces the help output, source-map at 0.8.0 is the Mozilla source map library that does the actual decoding, and fs-extra at 11.4.1 handles the file writing.

The test fixtures, and why they are generated

The testing section describes an approach that is more interesting than it first looks.

Test files are generated with UglifyJS 3 and sass by using files from the stretchy project, with a set of specific commands. UglifyJS is run three times over the same source to produce three different map flavours, and the difference is what goes in the source map field:

sh
uglifyjs stretchy.js --compress --mangle \
 --output stretchy.min.js --source-map includeSources
mv stretchy.min.js.map stretchy-with-sources.min.js.map

uglifyjs stretchy.js --compress --mangle \
 --output stretchy.min.js --source-map "url=inline"
mv stretchy.min.js stretchy-inline-sources.min.js

uglifyjs stretchy.js --compress --mangle \
 --output stretchy.min.js --source-map "url=stretchy.min.js.map"

So the fixtures cover the three ways a sourcemap can carry its sources: the sources embedded in the map, the sources inlined into the JavaScript file itself, and the sources referenced by a URL to a separate map file. A reconstruction tool has to handle all three, and generating them rather than committing them keeps the fixtures honest.

The same is true for CSS. Sass is run twice, once plain and once with --embed-source-map, which is the CSS equivalent of the same three cases.

The unit tests are written with tape and run with npm test, coverage is inspected with nyc and can be executed with npm run coverage after running npm test, and the README asks that it be over 90% at all times. The package manifest has since been modernised to c8 in place of nyc and oxlint in place of the older ESLint setup, with the test script running c8 over the tape test files and then removing the tmp directory.

Where the tool stops

The limits of shuji are the limits of what a sourcemap contains, and they are worth being explicit about before anyone reaches for it.

The first limit is that a sourcemap has to exist. A bundle that was built without source maps, or with the map deliberately withheld, gives the tool nothing to work from. The mappings alone are not enough for reconstruction, which is why the tool needs the sources.

The second is that the sources in the map may not be the sources you think they are. A map can carry a sourcesContent array, and a build pipeline can put something other than the original file in it, or a transformed version, or nothing at all for some entries. When the reconstruction is faithful you get the pre-minification file. When it is not, the discrepancy is in the map, not in shuji.

The third is scope. This handles JavaScript and CSS sources, and it is not a general deobfuscator or a beautifier for arbitrary minified code. If you have minified JavaScript with no map, the tool will not give you variable names or structure back, and no tool built on sourcemaps will.

The fourth is about the workflow rather than the code. The output is files on disk, and there is no notion of a project here, no incremental rebuild, and no watch mode. You run it, you get a directory, and you then deal with that directory, including the question of what to do when a later build produces a newer map.

There is also a small packaging wrinkle to know about. The package ships an npm-shrinkwrap.json, which pins the exact dependency tree for consumers rather than letting them resolve fresh, and the files list in the manifest includes the shrinkwrap as a shipped file. That is a deliberate choice for a utility people install globally, and it means the dependency versions you get are the ones the author tested against, rather than the latest.

Editorial conclusion

Adopt shuji if you have a minified bundle with a sourcemap and you want to read the original sources, since it does the reconstruction and the folder layout in one command. Do not adopt it for obfuscated code without a sourcemap, because the tool works from the map and has nothing to work from otherwise. Verify first that the sourcemap contains the sources, since a map that only has mappings without embedded sources will not give you readable files.

Frequently asked questions

What does shuji do with a sourcemap?

It reverse engineers the original JavaScript or CSS sources described by the map and writes them out as files. The usage is shuji file.js.map -o folder, and a directory of maps can be passed instead of a single file.

What does the --preserve option do?

It preserves the sourcemap's original folder structure, so a file that came from src/lib/parser.js is written back as src/lib/parser.js rather than flattened into the output directory. Without it, the reconstructed sources land flat in the output directory.

Which Node.js version does shuji require?

The minimum supported version is 24.12.0, which is the active Long Term Support version, and the package manifest declares the same bound in its engines field as node >=24.12.0.

How are the shuji test fixtures generated?

With UglifyJS 3 and sass, using files from the stretchy project. UglifyJS is run three times to produce maps with sources included, sources inlined into the file, and sources referenced by URL, and sass is run with and without --embed-source-map, so the tests cover the different ways a map can carry its sources.

What are shuji's runtime dependencies?

Three. optionator at 0.9.4 parses the command line options, source-map at 0.8.0 decodes the map, and fs-extra at 11.4.1 handles writing the files. The dev dependencies are tape for the tests, c8 for coverage, and oxlint for linting.

Official sources

  1. Issues
  2. License: MIT
  3. paazmaya/shuji on GitHub
  4. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/paazmaya-shuji.svg)](https://hysenlabs.com/projects/paazmaya-shuji)

Was this useful?