Skip to content

Commit

Permalink
fix(semantic-release-clean-package-json): reworked this plugin to use…
Browse files Browse the repository at this point in the history
… the `publish` and `success` functions, to have the correct state on git (#112)

BREAKING-CHANGE: Changed plugin steps to publish and success from prepare
  • Loading branch information
prisis authored Jan 22, 2025
1 parent 79b973b commit 734d48a
Show file tree
Hide file tree
Showing 8 changed files with 213 additions and 38 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ Check the README for each package within the `packages` directory for specific u
| [rc](packages/rc/README.md) | ![npm](https://img.shields.io/npm/v/@anolilab/rc?style=flat-square&labelColor=292a44&color=663399&label=v) | This module provides a utility function to load rc configuration settings from various sources, including environment variables, default values, and configuration files located in multiple standard directories. It merges these settings into a single configuration object. | | |
| [semantic-release-pnpm](packages/semantic-release-pnpm/README.md) | ![npm](https://img.shields.io/npm/v/@anolilab/semantic-release-pnpm?style=flat-square&labelColor=292a44&color=663399&label=v) | Semantic-release plugin to publish a npm package with pnpm | | |
| [semantic-release-preset](packages/semantic-release-preset/README.md) | ![npm](https://img.shields.io/npm/v/@anolilab/semantic-release-preset?style=flat-square&labelColor=292a44&color=663399&label=v) | semantic-release is a fully automated version management and package publishing library |
| [semantic-release-clean-package-json](packages/semantic-release-clean-package-json/README.md) | ![npm](https://img.shields.io/npm/v/@anolilab/semantic-release-clean-package-json?style=flat-square&labelColor=292a44&color=663399&label=v) | A semantic-release plugin to clean and optimize package.json files before publishing |

## How We Version

Expand Down
6 changes: 3 additions & 3 deletions packages/semantic-release-clean-package-json/.releaserc.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
}
],
"@semantic-release/changelog",
"./dist/index.mjs",
"@anolilab/semantic-release-pnpm",
[
"@semantic-release/github",
{
Expand All @@ -39,8 +41,6 @@
{
"message": "chore(release): ${nextRelease.gitTag} [skip ci]\\n\\n${nextRelease.notes}"
}
],
"./dist/index.mjs",
"@anolilab/semantic-release-pnpm"
]
]
}
6 changes: 3 additions & 3 deletions packages/semantic-release-clean-package-json/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@

### Bug Fixes

* changed plugin order to not publish cleaned package.json to github ([6673e69](https://github.com/anolilab/semantic-release/commit/6673e69723fd340380250fa2d986e4c37091351f))
* changed plugin order to not publish cleaned package.json to GitHub ([6673e69](https://github.com/anolilab/semantic-release/commit/6673e69723fd340380250fa2d986e4c37091351f))
* remove usage of aggregate-error library ([e0c6e95](https://github.com/anolilab/semantic-release/commit/e0c6e95b07416d6694fa192ddca96e17a4c1c4b8))
* **semantic-release-clean-package-json:** fixed circular dependency ([93b51f9](https://github.com/anolilab/semantic-release/commit/93b51f9c03503e10f0c0a23dcd55273622b40aa0))
* **semantic-release-clean-package-json:** removed cjs exports, semantic-release dont supports both types at the same time ([48c93c0](https://github.com/anolilab/semantic-release/commit/48c93c09cd5d6429b7658bc9a4fc573ea23462e2))
* **semantic-release-clean-package-json:** removed cjs exports, semantic-release don't support both types at the same time ([48c93c0](https://github.com/anolilab/semantic-release/commit/48c93c09cd5d6429b7658bc9a4fc573ea23462e2))


### Dependencies
Expand All @@ -20,7 +20,7 @@

### Bug Fixes

* changed plugin order to not publish cleaned package.json to github ([6673e69](https://github.com/anolilab/semantic-release/commit/6673e69723fd340380250fa2d986e4c37091351f))
* changed plugin order to not publish cleaned package.json to GitHub ([6673e69](https://github.com/anolilab/semantic-release/commit/6673e69723fd340380250fa2d986e4c37091351f))

## @anolilab/semantic-release-clean-package-json 1.0.0-alpha.1 (2025-01-15)

Expand Down
77 changes: 67 additions & 10 deletions packages/semantic-release-clean-package-json/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<div align="center">
<h3>anolilab semantic-release-clean-package-json</h3>
<p>
Clean package.json before publish by removing unnecessary properties
A semantic-release plugin that cleans and optimizes package.json before publishing by removing unnecessary development and build-time properties
</p>
</div>

Expand All @@ -25,6 +25,16 @@

---

## Why?

When publishing packages to npm, many properties in `package.json` are only needed during development and build time, but not in the published package. This plugin automatically removes unnecessary properties while preserving essential ones needed for the package to work correctly in production.

Key benefits:
- Reduces package size by removing development-only properties
- Prevents leaking internal configuration and metadata
- Maintains a clean and focused package.json for end users
- Customizable property preservation through configuration

## Install

```sh
Expand All @@ -44,16 +54,16 @@ pnpm add @anolilab/semantic-release-clean-package-json
The plugin can be configured in the [**semantic-release** configuration file](https://github.com/semantic-release/semantic-release/blob/master/docs/usage/configuration.md#configuration):

> [!IMPORTANT]
> Very important: The plugin must be placed after the `@semantic-release/github` or `@semantic-release/git` and before `@anolilab/semantic-release-pnpm` or `@semantic-release/npm` plugin otherwise the `package.json` will be cleaned and published into GitHub / Your Git Provider.
> Very important: The plugin must be placed before the `@semantic-release/github` or `@semantic-release/git` and before `@anolilab/semantic-release-pnpm` or `@semantic-release/npm` plugin otherwise the `package.json` will be cleaned and published into GitHub / Your Git Provider.
```json
{
"plugins": [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
"@semantic-release/github",
"@anolilab/semantic-release-clean-package-json",
"@anolilab/semantic-release-pnpm"
"@anolilab/semantic-release-pnpm",
"@semantic-release/github"
]
}
```
Expand All @@ -62,7 +72,8 @@ The plugin can be configured in the [**semantic-release** configuration file](ht

| Step | Description |
| --------- | -------------------------------------------------------------------------------------------------------- |
| `prepare` | Modifing the `package.json` file with the [default preserved properties](#default-preserved-properties). |
| `publish` | - Creates a backup of the original package.json file<br>- Removes all non-preserved properties from package.json<br>- Keeps properties specified in the default list and custom `keep` option<br>- Preserves specific npm scripts if they are in the keep list<br>- Writes the cleaned package.json file |
| `success` | - Restores the original package.json from backup<br>- Updates the version number to match the released version<br>- Removes the backup file<br>- Logs success or error messages |

### Options

Expand All @@ -77,6 +88,56 @@ The plugin can be configured in the [**semantic-release** configuration file](ht

### Examples

The plugin can be configured with custom properties to keep in addition to the default preserved ones:

```json
{
"plugins": [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
[
"@anolilab/semantic-release-clean-package-json",
{
"keep": ["custom field"]
}
],
"@anolilab/semantic-release-pnpm",
"@semantic-release/github"
]
}
```

#### Example: Publishing a TypeScript Package

When publishing a TypeScript package, you might want to keep TypeScript-specific fields:

```jsonc
{
"plugins": [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
[
"@anolilab/semantic-release-clean-package-json",
{
// This are the default values, just a example
"keep": [
"types",
"typings",
"typesVersions",
"module"
]
}
],
"@anolilab/semantic-release-pnpm",
"@semantic-release/github"
]
}
```

#### Example: Custom Package Root

If your package.json is not in the root directory:

```json
{
"plugins": [
Expand All @@ -86,18 +147,14 @@ The plugin can be configured in the [**semantic-release** configuration file](ht
[
"@anolilab/semantic-release-clean-package-json",
{
"keep": ["custom filed"]
"pkgRoot": "dist"
}
],
"@anolilab/semantic-release-pnpm"
]
}
```

<!-- Copied from https://github.com/privatenumber/clean-pkg-json/blob/develop/src/default-keep-properties.ts -->
<!-- MIT License -->
<!-- Copyright (c) Hiroki Osame <hiroki.osame@gmail.com> -->

### Default preserved properties

By default, these properties are preserved in `package.json`:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { readJson, writeJson } from "@visulima/fs";
import { temporaryDirectory } from "tempy";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";

import { prepare } from "../src";
import type { PrepareContext } from "../src/definitions/context";
import { publish, success } from "../src";
import type { CommonContext, PublishContext } from "../src/definitions/context";

const DEFAULT_PACKAGE_JSON = {
dependencies: {
Expand All @@ -22,9 +22,10 @@ const DEFAULT_PACKAGE_JSON = {
postinstall: "echo postinstall",
test: "echo test",
},
version: "1.0.0",
};

const context: Partial<PrepareContext> = {
const context: Partial<PublishContext> = {
branch: {
name: "foo",
},
Expand Down Expand Up @@ -56,16 +57,18 @@ describe("semantic-release-clean-package-json", () => {

afterEach(async () => {
await rm(temporaryDirectoryPath, { recursive: true });

vi.resetAllMocks();
});

it("should removes unnecessary properties", async () => {
expect.assertions(1);
expect.assertions(4);

const packageJsonPath = `${temporaryDirectoryPath}/package.json`;

await writeJson(packageJsonPath, DEFAULT_PACKAGE_JSON);

await prepare({}, { cwd: temporaryDirectoryPath, ...context } as PrepareContext);
await publish({}, { cwd: temporaryDirectoryPath, ...context } as PublishContext);

await expect(readJson(packageJsonPath)).resolves.toStrictEqual({
dependencies: {
Expand All @@ -75,21 +78,25 @@ describe("semantic-release-clean-package-json", () => {
scripts: {
postinstall: "echo postinstall",
},
version: "1.0.0",
});
expect((context as PublishContext).logger.log).toHaveBeenCalledWith("Created a backup of the package.json file.");
expect((context as PublishContext).logger.log).toHaveBeenCalledWith('Removing property "devDependencies"');
expect((context as PublishContext).logger.log).toHaveBeenCalledWith('Removing property "eslintConfig"');
});

it("should keep flag from given config", async () => {
expect.assertions(1);
expect.assertions(3);

const packageJsonPath = `${temporaryDirectoryPath}/package.json`;

await writeJson(packageJsonPath, DEFAULT_PACKAGE_JSON);

await prepare(
await publish(
{
keep: ["eslintConfig", "devDependencies"],
},
{ cwd: temporaryDirectoryPath, ...context } as PrepareContext,
{ cwd: temporaryDirectoryPath, ...context } as PublishContext,
);

await expect(readJson(packageJsonPath)).resolves.toStrictEqual({
Expand All @@ -106,6 +113,70 @@ describe("semantic-release-clean-package-json", () => {
scripts: {
postinstall: "echo postinstall",
},
version: "1.0.0",
});
expect((context as PublishContext).logger.log).toHaveBeenCalledWith("Created a backup of the package.json file.");
expect((context as PublishContext).logger.log).toHaveBeenCalledWith(
"Keeping the following properties: name, version, private, publishConfig, scripts.preinstall, scripts.install, scripts.postinstall, scripts.dependencies, files, bin, browser, main, man, jsdelivr, unpkg, dependencies, peerDependencies, peerDependenciesMeta, bundledDependencies, optionalDependencies, engines, os, cpu, description, keywords, author, contributors, license, homepage, repository, bugs, funding, type, exports, imports, sponsor, publisher, displayName, categories, galleryBanner, preview, contributes, activationEvents, badges, markdown, qna, extensionPack, extensionDependencies, extensionKind, icon, fesm2020, fesm2015, esm2020, es2020, types, typings, typesVersions, module, sideEffects, eslintConfig, devDependencies",
);
});

describe("success", () => {
it("should restore package.json from backup and update version", async () => {
expect.assertions(3);

const packageJsonPath = `${temporaryDirectoryPath}/package.json`;

// Create and write initial package.json
await writeJson(packageJsonPath, DEFAULT_PACKAGE_JSON);

// Run publish to create backup and modify package.json
await publish({}, { cwd: temporaryDirectoryPath, ...context } as PublishContext);

// Run success to restore from backup
await success({}, { ...context, cwd: temporaryDirectoryPath } as CommonContext);

// Verify the restored package.json
const restoredPackageJson = await readJson(packageJsonPath);

expect(restoredPackageJson).toStrictEqual(DEFAULT_PACKAGE_JSON); // This is just mocked without the pnpm or npm semantic-release plugin

expect((context as PublishContext).logger.log).toHaveBeenCalledWith("Restored modified package.json from backup.");
expect((context as PublishContext).logger.error).not.toHaveBeenCalled();
});

it("should log error when backup file is not found", async () => {
expect.assertions(2);

// Run success without creating backup first
await success({}, { cwd: temporaryDirectoryPath, ...context } as CommonContext);

expect((context as PublishContext).logger.error).toHaveBeenCalledWith("No backup package.json found.");
expect((context as PublishContext).logger.log).not.toHaveBeenCalledWith("Restored modified package.json from backup.");
});

it("should handle custom pkgRoot", async () => {
expect.assertions(3);

const customRoot = `${temporaryDirectoryPath}/dist`;
const packageJsonPath = `${customRoot}/package.json`;

// Create custom directory and package.json
await rm(customRoot, { force: true, recursive: true });
await writeJson(packageJsonPath, DEFAULT_PACKAGE_JSON);

// Run publish with custom pkgRoot
await publish({ pkgRoot: "dist" }, { cwd: temporaryDirectoryPath, ...context } as PublishContext);

// Run success with custom pkgRoot
await success({ pkgRoot: "dist" }, { cwd: temporaryDirectoryPath, ...context } as CommonContext);

// Verify the restored package.json
const restoredPackageJson = await readJson(packageJsonPath);
expect(restoredPackageJson).toStrictEqual(DEFAULT_PACKAGE_JSON); // This is just mocked without the pnpm or npm semantic-release plugin

expect((context as PublishContext).logger.log).toHaveBeenCalledWith("Restored modified package.json from backup.");
expect((context as PublishContext).logger.error).not.toHaveBeenCalled();
});
});
});
2 changes: 1 addition & 1 deletion packages/semantic-release-clean-package-json/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@
"devDependencies": {
"@anolilab/eslint-config": "^15.0.3",
"@anolilab/prettier-config": "^5.0.14",
"@anolilab/semantic-release-pnpm": "1.1.8",
"@anolilab/semantic-release-pnpm": "1.1.9-alpha.1",
"@babel/core": "^7.26.0",
"@rushstack/eslint-plugin-security": "^0.8.3",
"@secretlint/secretlint-rule-preset-recommend": "^9.0.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ type CommonContext4 = CommonContext3 & {
nextRelease: Release;
};

// https://github.com/semantic-release/semantic-release/blob/27b105337b16dfdffb0dfa36d1178015e7ba68a3/index.js#L193
export type PrepareContext = CommonContext4;
// https://github.com/semantic-release/semantic-release/blob/27b105337b16dfdffb0dfa36d1178015e7ba68a3/index.js#L206
export type PublishContext = CommonContext4;

// @todo infer return type from https://github.com/semantic-release/semantic-release/blob/27b105337b16dfdffb0dfa36d1178015e7ba68a3/lib/branches/index.js#L70
export interface BranchSpec {
Expand Down
Loading

0 comments on commit 734d48a

Please sign in to comment.