Simply package metadata inserter for NPM.
Looking for a simple solution to apply versions to TypeScript projects and NPM packages?
screw-up
could be the tool you need.
It is a Vite plugin that automatically inserts banner comments containing package metadata (name, version, description, author, license, etc.) into bundled files, and a CLI tool that applies them to NPM packages.
The Vite plugin automatically reads metadata from package.json
:
{
"name": "my-awesome-library",
"version": "2.1.0",
"description": "An awesome TypeScript library",
"author": "Jane Developer <jane@example.com>",
"license": "Apache-2.0",
"repository": {
"url": "https://github.com/user/my-awesome-library"
}
}
To insert banner header each bundled source files (dist/index.js
and etc.):
/*!
* name: my-awesome-library
* version: 2.1.0
* description: An awesome TypeScript library
* author: Jane Developer <jane@example.com>
* license: Apache-2.0
* repository.url: https://github.com/user/my-awesome-library
* git.commit.hash: c94eaf71dcc6522aae593c7daf85bb745112caf0
*/
// Your bundled code here...
You may have noticed the line git.commit.hash:
. That's right, if your project is managed by Git (it is, right?), you can also insert commit IDs, branch information, and tag information.
Most importantly, if a version is applied to a Git tag, you can automatically reflect that version tag in the version
field of package.json
. In other words, you can manage version numbers using only Git tags!
Instead of using npm pack
, you can use the CLI tool screw-up
to generate packages, which will automatically apply the collected metadata to the NPM package's package.json
:
# Generate a package using `screw-up` command
$ screw-up pack
my-awesome-library-2.1.0.tgz
- Automatic metadata extraction: Reads metadata from
package.json
automatically. - Workspace support: Works with monorepos and automatically inherits metadata from parent packages.
- Flexible output: Specify exactly which metadata to include and in what order.
- TypeScript metadata generation: Can automatically generates TypeScript files with metadata constants for use in your source code.
- Git metadata extraction: Automatically extracts Git commit hash, tags, branches, and version information from local Git repository.
- Supported pack/publish CLI interface: When publishing using this feature, the package is generated after applying the above processing to
package.json
.
Install as a devDependencies
since screw-up does not require any runtime code.
npm install --save-dev screw-up
The configuration method is described below. If you want to quickly learn about recommended configurations and operation methods, refer to the "Recommended configuration" section.
Add the plugin to your vite.config.ts
:
import { defineConfig } from "vite";
import screwUp from "screw-up";
export default defineConfig({
plugins: [
screwUp(), // Uses default output keys
],
// ...
});
When no outputKeys
are specified, the plugin uses these metadata keys with exact sequence:
name
, version
, description
, author
, license
, repository.url
and git.commit.hash
.
You can specify which metadata fields to include and in what order:
import { defineConfig } from "vite";
import screwUp from "screw-up";
export default defineConfig({
plugins: [
screwUp({
outputKeys: ["name", "version", "license"], // Only include these fields
}),
],
// ...
});
This will generate a banner with only the specified fields:
/*!
* name: my-awesome-library
* version: 2.1.0
* license: Apache-2.0
*/
The plugin automatically flattens nested objects using dot notation.
For example package.json
declarations:
{
"name": "my-package",
"author": {
// Nested metadata
"name": "Jane Developer",
"email": "jane@example.com"
},
"repository": {
// Nested metadata
"type": "git",
"url": "https://github.com/user/my-package"
}
}
You can reference nested fields in your outputKeys
:
screwUp({
outputKeys: ["name", "author.name", "author.email", "repository.url"],
});
Results in:
/*!
* name: my-package
* author.name: Jane Developer
* author.email: jane@example.com
* repository.url: https://github.com/user/my-package
*/
The plugin can generate TypeScript files containing metadata constants that you can import and use in your source code:
import { defineConfig } from "vite";
import screwUp from "screw-up";
export default defineConfig({
plugins: [
screwUp({
// Enable metadata file generation
outputMetadataFile: true,
// Custom path (optional)
outputMetadataFilePath: "src/generated/packageMetadata.ts",
// Keys to include (optional)
outputMetadataKeys: [
"name",
"version",
"description",
"author",
"license",
],
}),
],
// ...
});
This generates src/generated/packageMetadata.ts
with TypeScript constants:
// This file is auto-generated by screw-up plugin
// Do not edit manually
export const name = "my-awesome-library";
export const version = "2.1.0";
export const description = "An awesome TypeScript library";
export const author = "Jane Developer <jane@example.com>";
export const license = "Apache-2.0";
You can then import and use these constants in your source code:
import { name, version } from "./generated/packageMetadata.js";
// Output: my-awesome-library v2.1.0
console.log(`${name} v${version}`);
export function getLibraryInfo() {
return { name, version };
}
In addition to the TypeScript source file, a corresponding type definition file (.d.ts) is also generated for better TypeScript support.
By default, the extension of outputMetadataFilePath
is changed to .d.ts
.
You can customize the output file path by specifying outputMetadataFileTypePath
:
screwUp({
outputMetadataFile: true,
// Optional: Custom path for type definition file
outputMetadataFileTypePath: "src/generated/packageMetadata.d.ts",
});
The TypeScript type declarations look like this:
// This file is auto-generated by screw-up plugin
// Do not edit manually
export declare const name: string;
export declare const version: string;
export declare const description: string;
export declare const author: string;
export declare const license: string;
Note: Since type definition files are generated early in the plugin lifecycle, before the actual metadata source code packageMetadata.ts
is generated, there may be cases where it's unclear whether metadata items are actually output. For example, if a non-existent metadata item foobar
is specified in outputMetadataKeys
, a declare
statement will be generated, but the corresponding const foobar
variable will not be generated, resulting in a build error when referenced.
Keys with special characters are automatically sanitized to valid TypeScript identifiers:
repository.url
βrepository_url
custom-key
βcustom_key
123invalid
β_123invalid
The plugin automatically extracts Git metadata from your local Git repository and makes it available as metadata keys.
git.version
: Automatically calculated version based on Git tags and commit depth (see below)git.commit.hash
: Full commit hash of the current commitgit.commit.short
: Short commit hash (first 7 characters)git.commit.date
: Commit date in ISO formatgit.commit.message
: Commit messagegit.tags
: Array of all tags pointing to the current commitgit.branches
: Array of branches containing the current commit
By default, the version
value in package.json
is automatically overridden by the value of git.version
. Therefore, you can apply versions based on Git tags even when generating NPM packages (using the CLI).
The Git version calculation follows below algorithm:
- Tagged commits: Uses the tag version directly (e.g.,
v1.2.3
-->1.2.3
) - Untagged commits: Detects depth from the furthest ancestor tag and increments the last version component
- Modified working directory: When uncommitted changes exist, increments the version by one
- No tags found: Defaults to
0.0.1
and increments for each commit
Example with Git metadata:
screwUp({
outputKeys: [
"name",
"version",
"git.version",
"git.commit.hash",
"git.commit.short",
],
});
Results in:
/*!
* name: my-awesome-library
* version: 2.1.0
* git.version: 2.1.0
* git.commit.hash: c94eaf71dcc6522aae593c7daf85bb745112caf0
* git.commit.short: c94eaf7
*/
The plugin automatically detects workspace configurations and inherits metadata from parent packages:
my-monorepo/
βββ package.json # Root package with shared metadata
βββ packages/
β βββ ui/
β β βββ package.json # Child package
β βββ core/
β βββ package.json # Child package
Child packages automatically inherit metadata from the root package, with the ability to override specific fields:
// Root package.json
{
"name": "my-monorepo",
"version": "1.0.0",
"author": "Company Team",
"license": "MIT"
}
// packages/ui/package.json
{
"name": "@my-monorepo/ui",
"description": "UI components library"
}
When building the UI package, the banner will include:
/*!
* name: @my-monorepo/ui
* version: 1.0.0
* description: UI components library
* author: Company Team
* license: MIT
*/
The plugin automatically detects and supports:
- npm/yarn workspaces: Detected via
workspaces
field inpackage.json
- pnpm workspaces: Detected via
pnpm-workspace.yaml
file - Lerna: Detected via
lerna.json
file
The screw-up
package includes a command-line interface for packaging and publishing your projects.
# Dump how `package.json` is resolved (JSON)
screw-up dump
# Pack with custom README and limited inheritance
screw-up pack --readme ./docs/DIST_README.md --inheritable-fields "version,license"
# Pack with custom peerDependencies prefix
screw-up pack --peer-deps-prefix "~"
# Pack without peerDependencies replacement
screw-up pack --no-replace-peer-deps
# Build and publish with dry run
screw-up publish --dry-run
# Publish to beta channel
screw-up publish --tag beta
# Publish scoped package as public
screw-up publish --access public
# Pack to custom directory then publish
screw-up pack --pack-destination ./release
screw-up publish ./release/my-package-1.0.0.tgz
For help with any command:
screw-up --help
screw-up dump --help
screw-up pack --help
screw-up publish --help
Create a tar archive of your project:
# Pack current directory
screw-up pack
# Pack specific directory
screw-up pack ./my-project
# Pack to specific output directory
screw-up pack --pack-destination ./dist
The pack command:
- Automatically reads
package.json
for metadata - Supports workspace inheritance (inherits metadata from parent packages)
- Creates a compressed
.tgz
archive with format:{name}-{version}.tgz
The pack command uses npm pack
internally to generate a temporary package file. It then performs operations such as replacing package.json
and modifying README on the package file. Because it operates in this way, the handling of the files
key and other aspects are fully compliant with the specifications assumed by npm pack
.
However, to successfully pack the files, you must define the version
key. screw-up itself can automatically determine the version to specify in the version
key and reflect that value in the final NPM package file. When the version
key does not exist, an error will occur during the first npm pack
execution. To avoid this, the "Recommended configuration" section example specifies a DUMMY version
key.
--pack-destination <path>
: Specify output directory for the archive--readme <path>
: Replace README.md with specified file--inheritable-fields <list>
: Comma-separated list of fields to inherit from parent (default: version,description,author,license,repository,keywords,homepage,bugs,readme)--no-wds
: Disable working directory status check for version increment--no-replace-peer-deps
: Disable replacing "*" in peerDependencies with actual versions--peer-deps-prefix <prefix>
: Version prefix for replaced peerDependencies (default: "^")
Publish your project to registry server:
# Publish current directory (creates archive and publishes)
screw-up publish
# Publish specific directory
screw-up publish ./my-project
# Publish existing tarball
screw-up publish package.tgz
# Publish with npm options (all npm publish options are supported)
screw-up publish --dry-run --tag beta --access public
The publish command:
- Supports all
npm publish
options transparently. This command creates an archive and then executes the actual publishing by callingnpm publish
. - Can publish from directory (automatically creates archive) or existing tarball
- Handles workspace packages with proper metadata inheritance
- Uses the same packaging logic as the pack command
--inheritable-fields <list>
: Comma-separated list of fields to inherit from parent--no-wds
: Disable working directory status check for version increment--no-replace-peer-deps
: Disable replacing "*" in peerDependencies with actual versions--peer-deps-prefix <prefix>
: Version prefix for replaced peerDependencies (default: "^")- All
npm publish
options are supported (e.g.,--dry-run
,--tag
,--access
,--registry
)
Dump computed package.json as JSON:
# Dump current directory package.json
screw-up dump
# Dump specific directory package.json
screw-up dump ./my-project
# Dump with custom inheritable fields
screw-up dump --inheritable-fields "author,license"
The dump command:
- Shows the final computed
package.json
after all processing (workspace inheritance, Git metadata, etc.) - Useful for debugging and understanding how your package metadata will be resolved
- Outputs clean JSON that can be piped to other tools (ex:
jq
)
--inheritable-fields <list>
: Comma-separated list of fields to inherit from parent--no-wds
: Disable working directory status check for version increment
The pack command supports README replacement using multiple methods:
# Replace README.md with custom file
screw-up pack --readme ./docs/README_package.md
{
"name": "my-package",
"readme": "docs/PACKAGE_README.md"
}
When both are specified, the --readme
CLI option takes priority over the package.json
field.
In workspace environments, it's common to reference sibling packages using "*" in peerDependencies
to avoid version constraints during development. When packaging, screw-up automatically replaces these wildcards with actual version numbers:
{
"name": "@workspace/cli",
"peerDependencies": {
"@workspace/core": "*"
}
}
After packaging, the "*" is replaced with the actual version:
{
"name": "@workspace/cli",
"peerDependencies": {
"@workspace/core": "^2.1.0"
}
}
# Default behavior (uses "^" prefix)
screw-up pack
# Disable the feature entirely
screw-up pack --no-replace-peer-deps
# Use different version prefix
screw-up pack --peer-deps-prefix "~"
screw-up pack --peer-deps-prefix ">="
# Use exact version (no prefix)
screw-up pack --peer-deps-prefix ""
This feature:
- Only works in workspace environments (requires workspace root with
workspaces
field) - Only replaces "*" values that match workspace sibling package names
- Leaves non-workspace dependencies unchanged
- Is enabled by default for pack and publish commands
Control which metadata fields are inherited from parent packages in monorepos:
# Inherit only specific fields
screw-up pack --inheritable-fields "version,author,license"
# Disable inheritance completely
screw-up pack --inheritable-fields ""
# Use custom fields for publishing
screw-up publish --inheritable-fields "version,description,keywords"
Default inheritable fields: version
, description
, author
, license
, repository
, keywords
, homepage
, bugs
, readme
screw-up allows you to keep your development lifecycle simple. Below are typical configurations for single projects and monorepos using workspaces.
For single projects, follow these recommendations for optimal screw-up usage:
my-project/
βββ package.json # No version field
βββ README.md # Development README (show in github/gitlab)
βββ README_pack.md # Distribution README (optional)
βββ vite.config.ts # screw-up plugin configuration
βββ src/
β βββ index.ts
β βββ generated/
β βββ packageMetadata.ts # Auto-generated by `outputMetadataFile`
β βββ packageMetadata.d.ts # Auto-generated by `outputMetadataFile`
β βββ .gitignore # Ignored `packageMetadata.ts` file
βββ dist/ # Build output with metadata banners
{
"name": "my-awesome-library",
"description": "An awesome TypeScript library for developers",
"author": "Jane Developer <jane@example.com>",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/user/my-awesome-library"
},
"keywords": ["typescript", "library", "awesome"],
"homepage": "https://github.com/user/my-awesome-library#readme",
"bugs": {
"url": "https://github.com/user/my-awesome-library/issues"
},
"readme": "README_pack.md",
"files": ["dist/**/*"],
"scripts": {
"build": "vite build",
"test": "npm run build && vitest run",
"pack": "npm run build && screw-up pack --pack-destination artifacts/"
}
}
Key Points:
- DUMMY
version
: Version specification (0.0.1
in the above example) is NOT used, and version management is performed through Git tags by screw-up - Include metadata fields:
name
,description
,author
,license
, etc. - Specify
files
: Control which files are included in the package - Optional
readme
field: Point to a distribution-specific README file. The README file specified here does not need to be included infiles
. - Add
pack
toscripts
to enable packaging with screw-up
import { defineConfig } from "vite";
import screwUp from "screw-up";
export default defineConfig({
plugins: [
screwUp({
// (Generate `packageMetadata.ts` when you need it)
outputMetadataFile: true,
}),
],
// ...
});
Setting outputMetadataFile
to true
will generate packageMetadata.ts
and packageMetadata.d.ts
. By default, packageMetadata.ts
contains Git commit IDs, so it will be updated with each commit. Since this will cause Git to generate differences each time, we recommend excluding it with .gitignore
.
# Install as dev dependency
npm install --save-dev screw-up
For monorepo setups, organize shared and project-specific metadata:
my-monorepo/
βββ package.json # Root metadata (no version)
βββ README.md # Development README (show in GitHub/GitLab)
βββ README_shared.md # Shared README
βββ core/
β βββ package.json # Project-specific metadata (no version)
β βββ vite.config.ts
β βββ src/
βββ ui/
β βββ package.json # References core with "*" (no version)
β βββ src/
βββ cli/
βββ package.json # References core with "*" (no version)
βββ src/
{
"name": "my-monorepo",
"description": "Monorepo containing multiple packages",
"author": "Development Team <team@company.com>",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/company/my-monorepo"
},
"homepage": "https://github.com/company/my-monorepo#readme",
"bugs": {
"url": "https://github.com/company/my-monorepo/issues"
},
"readme": "README_shared.md",
"workspaces": ["core", "ui", "cli"],
"private": true,
"scripts": {
"build": "npm run build --workspaces",
"test": "npm run test --workspaces",
"pack": "npm run pack --workspaces"
}
}
Marked as private
to ignore packing. Since there is no packing, the version
key is also unnecessary.
{
"name": "@company/ui-components",
"version": "0.0.1",
"description": "Reusable UI components library",
"keywords": ["ui", "components", "react"],
"peerDependencies": {
"@company/core": "*",
"react": "^18.0.0"
},
"files": ["dist/**/*"],
"scripts": {
"build": "vite build",
"test": "npm run build && vitest run",
"pack": "npm run build && screw-up pack --pack-destination artifacts/"
}
}
Key Points:
- Root package: Define shared metadata (
author
,license
,repository
, etc.) - Sub-projects: Override with project-specific values (
name
,description
,keywords
) - Sibling references: Use
"*"
inpeerDependencies
for workspace siblings when you need to refer on peer - DUMMY
version
: Version specification (0.0.1
in the above example) is NOT used, and version management is performed through Git tags by screw-up - Shared README: Can be defined at root level and inherited by sub-projects
Same as single project configuration.
Install screw-up in each sub project.
# Pack individual sub-project
screw-up pack packages/ui-components
# Pack with custom inheritance
screw-up pack packages/cli --inheritable-fields "author,license,repository"
# Pack without peerDependencies replacement
screw-up pack packages/plugin --no-replace-peer-deps
# Publish with custom prefix
screw-up publish packages/core --peer-deps-prefix "~"
This project was developed as a successor to RelaxVersioner. While RelaxVersioner was designed for the .NET platform and added NPM support options, it did not integrate well with Git tags. Therefore, this project was designed with Vite plugin usage in mind, focusing on the most optimal workflow and specifications.
The algorithm for calculating version numbers from Git tags is identical to RelaxVersioner. This means that if you are maintaining server code with ASP.NET Core, you can treat the .NET version as completely unified.
Under MIT