testpack-cli
v1.1.4
Published
Verifies that your package will work after it is published by running your unit tests in a special test folder.
Downloads
111
Maintainers
Readme
testpack: Test your package before publishing
Testpack attempts to verify that your npm package is set up properly by installing the packaged version in a special test folder with its own custom package.json file, and running a copy of your unit tests against it.
Usage: testpack [Options] [<Test patterns>]
<Test patterns>
are glob patterns used to recognize source files that are
test-related and so should be copied to the new project. The default test
patterns are test* *test.* *tests.* *test*/** tsconfig.json
.
Note: the glob
package is used to
match test patterns. It requires slash (/) as the path separator even on
Windows; backslashes escape "special" characters such as braces.
Here's what it does:
- It runs
npm pack
to create a preview copy of your package. - It creates a test folder. By default the folder is ../YPN-testpack
where YPN is Your Package's Name, and if the test folder already
exists, its contents are deleted. You can use
--test-folder
to change the folder's path and name. - In the test folder, it creates a new package.json file derived from
your existing one, in which all dependencies are deleted except common
unit test frameworks (e.g. jasmine, mocha, jest). With
--packagejson
you can request additional changes to the new package.json. - It runs
npm install
in the test folder - It installs any additional packages you requested with
--install
- It unpacks the tgz file generated by
npm pack
usingnpm install
- It copies your test files to the new folder, preserving their original directory structure.
- It changes import/require commands in the tests. Except when importing
test files, the "local" prefix
.
or..
is changed to the package name, so that your tests are importing from node_modules instead. For example, if your package is calledmypkg
and your test code saysimport {...} from "./module"
, testpack changes it toimport {...} from "mypkg/module"
. You should use the"main"
option in package.json to specify the default module, and then you can writerequire(".")
to import it, which will becomerequire("mypkg")
in the test folder. - It runs
npm run test
(or another script according to--test-script
)
Options
All command-line options are optional.
--dirty
The contents of the test folder are normally deleted at the start.
This option skips the deletion step, potentially leaving extra files
in the test folder (also: runs faster npm ci instead of npm install)
--setup-command=command
A setup command to run instead of `npm install` (your package is
still installed afterward with `npm install ________.tgz` and
`npm install` is still used by the `--install` option.) To save
time by skipping install when packages in the test folder are
already installed, use `--dirty --setup-command=""`.
-p, --packagejson=key:value
Merges data into the new package.json file. If the new value is a
primitive, it overwrites the old value. If the old value is a
primitive, it is treated as an array. If both are arrays, they are
concatenated. If either one is an object, they are merged in the
obvious way, recursively. For example:
Old value: `{"a":["hi"], "b":7, "c":[3], "x":{"D":4}}`
New value: `{"a":1,"b":[8],"c":[4],"x":{"D":{"two":2},"E":5}}`
Out: `{"a":1,"b":[7,8],"c":[3,4],"x":{"D":{"0":4,"two":2},"E":5}}`
You can use `undefined` to delete an existing value, e.g.
--packagejson={testpack:undefined,repository:undefined}
-o, --test-folder=path
Path to test folder. Created if necessary.
-r, --replace-import !pat1!pat2!
Searches js/mjs/ts/tsx test files for require/import filenames using
regex pattern 1, replacing it with pattern 2. Instead of `!`s you are
allowed to use any punctuation mark that doesn't appear in the
patterns. Pattern 2 can use $1 through $9 to re-emit captured
strings, and $P is replaced with your package's name. Replacements
only affect non-test files unless you add --replace-test-imports.
If this option is not used then the following default replacement
patterns are used:
|\.\.?|$P| and |\.\.?([\/\\].*)|$P$1|
Basically, `.` and `..` are replaced with the package name. UTF-8
encoding is assumed in test files, and the regex must match the whole
filename unless your regex uses `^` or `$`.
--regex ext/regex/
For the purpose of modifying import/require commands, files with the
specified extension(s) are searched using this regular expression,
and the first captured group is treated as a filename that may need
to be modified. For example, this built-in regex is used to match
require commands that use double quotes:
--regex js/require\s*\(\s*"((?:[^\\"]|\\.)*)"/
You can specify multiple extensions separated by commas: `js,mjs`
-R, --rmdir
Remove entire test folder when done (by default, only the the tgz
produced by `npm pack` is deleted.)
--delete-on-fail
Delete the test folder & tgz even when tests fail.
-s, --test-script=name
Name of test script to run with `npm run` (default: `test`).
--install package
Runs `npm install --save-dev package` in the test project.
--keep package
Prevents removal of package(s) from dependencies or devDependencies.
--prepacked
Skips running `npm pack` and looks for the .tgz file it normally
produces (name-version.tgz). This option also prevents the deletion
of the tar.gz file on exit.
--prepacked=file
Skips running `npm pack` and unpacks the specified file.
--show-json
Shows the JSON equivalent of the specified arguments, then quits.
You can put these settings in a "testpack" section of package.json.
-!, --nontest pattern
Ignores the specified files (glob pattern) when searching for tests.
-v, --verbose
Emits more text describing what testpack is doing.
Caution: your shell may transform special characters before they reach
testpack. For example, on Windows, --packagejson=key:"value"
doesn't work
because the shell removes the quotes, causing an error message from testpack.
If you prefer, options can be placed in a "testpack"
section of your
package.json file. For example,
"testpack": { "test-script":"foo", "verbose": true }
is equivalent to --test-script=foo --verbose
.
How to use testpack
Prepare to your package for publishing as you normally would (guide)
In a terminal:
npm install --save-dev --global testpack-cli
Run it with:
testpack
(add--verbose
for more detail)Run
npm publish
once your tests pass.To combine steps 4 and 5, use a script like
npm run safePublish
:"scripts": { "safePublish": "testpack && npm publish", ... }
Note: It's natural to want to put testpack --prepacked
in your postpack
script so it runs after npm pack
, but this doesn't work because npm pack
calls postpack
before it produces the tgz file.
If your package includes commands in "bin"
in package.json, npm has an undocumented requirement for a shebang. Consider adding some sort of test to make sure your installed command can be invoked (create an issue if you want advice).
Tips for TypeScript users
When publishing TypeScript packages on npm, it’s polite to publish code that has been compiled to JavaScript so that people who are not using TypeScript can still use your package. Here are some things to be aware of:
- You need to use
tsc --declaration
("declaration": true
in tsconfig.json) to create .d.ts typing files which contain type information. After building your .js and .d.ts files, you don’t even need to publish the original TypeScript source code. - When importing a published module, I found that VS Code will detect your typing files (e.g. if
"main"
isdist/index.js
then VS Code findsdist/index.d.ts
), buttsc
will not. Therefore, before you publish, you need an extra option in package.json such as"typings": "dist/index"
to telltsc
where the d.ts file is (the d.ts extension is inferred.) If you forget thetypings
option,testpack
can only help you notice your mistake if your unit-testing script includes a type-checking mechanism (certain TypeScript tools such as ts-jest do not include type checking.) tsc
may give a baffling error, "Cannot write file '[your-module].d.ts' because it would overwrite input file", when your"typings"
option includes the.d.ts
file extension. So remove that extension!- The
"main"
option in your package.json should refer to a .js file and it's a good idea to useimport {...} from "."
in your tests to verify that the"main"
option is set correctly. But if you do this,import {...} from "."
may not work in any modules seen bytsc
because thejs
file doesn't exist until after compilation is finished. You might solve the problem by stripping off the extension frommain
(e.g. use"main": "foo"
instead of"main": "foo.js"
). This allowstsc
to use.ts
as the default file extension while other tools likenode
use.js
as the default file extension. However, this won't work if your tsconfig.json is configured to place output in a different place (e.g."outDir": "dist"
). In that case, useimport {...} from "."
only in your tests and then exclude your tests with theexclude
option in tsconfig.json, e.g."exclude": ["node_modules", "**/*.test.ts"]
. This works if you are running tests with a tool that supports TypeScript but ignores theexclude
option, such asts-jest
orts-node
. - You should make sure that the tests which testpack copies to its test folder are importing the compiled JavaScript version, not the TypeScript version. The simplest way to guarantee this is to exclude all .ts files from your package. If you do that, be sure source maps are disabled in your tsconfig.json. In any case, your tests should still be written in TypeScript to make sure that your d.ts files work.
ts-jest users: I found it tricky to use ts-jest. In the "jest"
options in package.json, "js"
must be included in the list of "moduleFileExtensions"
, otherwise this error occurs: "Cannot find module './lib/source-map-generator' from 'source-map.js'". However we only want to run ts test files, not js files generated by tsc
. The solution I chose was to put tests in their own folder and exclude it in tsconfig.json. But if you exclude your tests from tsconfig.json and then run testpack
, ts-jest will hit you with bug #618 which the developers declined to fix. To work around this you can teach testpack
to create a dummy source file in package.json. For example, if tsconfig says your source files are in src
, use this:
"testpack": {
"packagejson": {
"scripts": {
"fix-ts-jest": "mkdir src && echo \"//avoid ts-jest bug 618\" > src/dummy.ts || cd .",
"test": "npm run fix-ts-jest && jest"
}
}
}
testpack-cli versus package-preview
I created testpack because I wasn't happy with package-preview. Package-preview does not create a special test folder and it cannot edit the import
/require
commands in your source files. Instead, package-preview seems to be designed with the assumption that it will always run before your tests. However, your package may need to be built (with Babel or TypeScript) before it is packaged, and package-preview installs it with a separate copy of all the dependencies of your package. This is usually a slow process - a process you don't want to run every time you run your unit tests.
For example, I wrote a very simple package whose unit tests took 1 second. Once I added package-preview, I needed 18 seconds to run package-preview before the unit tests could start.
Unfortunately, I couldn't find any way to import a module from node_modules if the path started with ./
- that prefix seems to be treated as proof that it's not in node_modules. Likewise, I couldn't find any way import a module from the current directory if it did not start with ./
. In normal JavaScript you could try using environment variables to figure out whether you need to use .
or not, e.g. require(process.env.TESTPACK ? "mymodule" : "./index")
. However if you are using TypeScript or import
statements, this is not possible.
Thus, the purpose of testpack
is to avoid slowing down the normal unit test process when you are not about to publish. Your tests can import a local copy of your code from ./
or ../
so they run quickly. When you're ready to publish, you use the slower testpack
process to create a special-purpose test environment with modified imports.
Note: I bet one of you JavaScript wizards knows how I could have avoided all this trouble with ./
. TypeScript users might think that TypeScript aliases could potentially solve this problem, but they can't because they are compile-time only. That is, if you tell TypeScript that ./A
is an alias for B
, then TypeScript loads B
for type checking but it generates code that still refers to ./A
! And ts-node inherits this problem; thus if you are writing code for the command line / Node.js, aliases don't seem to help at all.
Package-preview does have one special virtue: it uses pnpm
to isolate the packed package (i.e. the almost-published tgz) from any other packages installed in the same project that are not declared as dependencies in the packed package. Testpack doesn't have as much isolation, e.g. it keeps unit test frameworks. So, suppose that your packed package tries to use a dependency X but it doesn't declare the dependency in package.json like it should. If your unit test framework also uses dependency X, your code may still work when it should actually break. I apologize for that but I don't have time to implement fancier isolation. Perhaps someone will make a pull request?
Version history
v1.1.4
- Now works with scoped packages (#2)
v1.1.2
- Added
--setup-command
option. - Changed how imports are transformed. Initially the prefix
./
or../
or../src
was simply stripped off, but in v1.1.
or..
is replaced with the package name, andsrc
is no longer treated specially.
v1.0.3
Initial release.