Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
* text=auto eol=lf

*.ico binary
*.jpg binary
*.jpeg binary
*.png binary
*.webp binary
*.woff binary
*.woff2 binary
*.pdf binary
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/bug_report.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ body:
id: version
attributes:
label: IssueSafe version
placeholder: 0.1.0
placeholder: 0.1.1
validations:
required: true
- type: dropdown
Expand Down
15 changes: 14 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,18 @@ All notable changes to IssueSafe will be documented here. The project follows [S

## [Unreleased]

## [0.1.1] - 2026-07-22

### Fixed

- Run the packed npm CLI through a dedicated executable entry so `.bin` symlinks work consistently on Linux, macOS, and Windows.
- Normalize repository text files to LF so Prettier produces the same result on Windows and Linux CI runners.

### Changed

- Expand packed-package verification to cover the public file set, CLI version, stdin sanitization, and invalid-usage exit codes.
- Refresh installation, CLI, demo, and release documentation for the published package.

## [0.1.0] - 2026-07-21

### Added
Expand All @@ -15,5 +27,6 @@ All notable changes to IssueSafe will be documented here. The project follows [S
- Responsive local-only React review application with per-finding and per-category decisions.
- Safe Markdown fencing, synthetic scenarios, documentation, security policy, CI, and package verification.

[Unreleased]: https://github.com/aminhanifm/IssueSafe/compare/v0.1.0...HEAD
[Unreleased]: https://github.com/aminhanifm/IssueSafe/compare/v0.1.1...HEAD
[0.1.1]: https://github.com/aminhanifm/IssueSafe/compare/v0.1.0...v0.1.1
[0.1.0]: https://github.com/aminhanifm/IssueSafe/releases/tag/v0.1.0
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# IssueSafe

[![CI](https://github.com/aminhanifm/IssueSafe/actions/workflows/ci.yml/badge.svg)](https://github.com/aminhanifm/IssueSafe/actions/workflows/ci.yml)
[![npm version](https://img.shields.io/npm/v/@aminhanifm/issuesafe.svg)](https://www.npmjs.com/package/@aminhanifm/issuesafe)
[![MIT License](https://img.shields.io/badge/license-MIT-f4b95f.svg)](LICENSE)

**[Try the interactive demo](https://aminhanifm.github.io/projects/IssueSafe/)** · [Security policy](SECURITY.md)
Expand All @@ -21,17 +22,17 @@ IssueSafe occupies the useful middle. It finds a conservative set of common patt

## Install

IssueSafe requires Node.js 22 or newer. The package is prepared for npm but will remain unavailable until the owner publishes `v0.1.0`.
IssueSafe requires Node.js 22 or newer and is published on npm as [`@aminhanifm/issuesafe`](https://www.npmjs.com/package/@aminhanifm/issuesafe).

```bash
npm install --global @aminhanifm/issuesafe
issuesafe --version
```

Run without a global install after publication:
Run without a global install:

```bash
npx @aminhanifm/issuesafe sanitize application.log
npx @aminhanifm/issuesafe --version
```

## CLI
Expand All @@ -46,6 +47,9 @@ issuesafe sanitize application.log --output application.safe.log
# Standard input and versioned JSON
cat application.log | issuesafe sanitize --stdin --format json

# Standard input to sanitized text
cat application.log | issuesafe sanitize --stdin

# Keep diagnostically useful IP addresses and emails
issuesafe sanitize application.log --disable ip,email

Expand Down Expand Up @@ -192,7 +196,6 @@ Focused issues and pull requests are welcome. Read [CONTRIBUTING.md](CONTRIBUTIN

## Roadmap

- Owner-reviewed `v0.1.0` npm release
- Additional conservative vendor-specific token shapes backed by public documentation
- Import/export of redaction decisions without original values
- Expanded accessibility and cross-browser regression coverage
Expand Down
37 changes: 37 additions & 0 deletions docs/release-notes-v0.1.1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# IssueSafe v0.1.1

IssueSafe 0.1.1 is a focused launch repair for the published CLI and public browser demo.

## Fixed

- Added a dedicated Node executable entry that always runs the CLI, fixing empty output from `issuesafe --version` when npm launches the command through a `.bin` symlink on Linux.
- Kept `createCli()` and `runCli()` importable without side effects for library consumers and tests.
- Normalized repository text files to LF so Prettier behaves consistently across Ubuntu and Windows GitHub Actions runners.

## Demo and repository polish

- Deployed the browser application at the canonical portfolio URL: <https://aminhanifm.github.io/projects/IssueSafe/>.
- Kept the optional repository Pages URL as a redirect to the canonical demo.
- Updated installation and CLI documentation for the live npm package.

## Install

```bash
npm install --global @aminhanifm/issuesafe
issuesafe --version
```

Or run it without a global installation:

```bash
npx @aminhanifm/issuesafe --version
```

## Verification

- Cross-platform formatting, linting, strict TypeScript, unit, component, and browser tests.
- Production package and browser-demo builds.
- Packed npm consumer checks for the installed version, stdin sanitization, public files, and exit codes.
- Desktop and mobile checks of the deployed browser workflow.

IssueSafe detects common sensitive patterns, not every possible secret. Review sanitized output before sharing it, and revoke or rotate credentials that may have been exposed.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@aminhanifm/issuesafe",
"version": "0.1.0",
"version": "0.1.1",
"description": "Sanitize developer logs and create shareable bug reports without uploading anything.",
"type": "module",
"license": "MIT",
Expand Down Expand Up @@ -29,7 +29,7 @@
"access": "public"
},
"bin": {
"issuesafe": "dist/cli/index.js"
"issuesafe": "dist/cli/bin.js"
},
"exports": {
".": {
Expand Down
40 changes: 36 additions & 4 deletions scripts/verify-package.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const root = resolve(import.meta.dirname, "..");
const temporaryDirectory = await mkdtemp(join(tmpdir(), "issuesafe-package-"));
const npmCommand = process.platform === "win32" ? "npm.cmd" : "npm";

function run(command, args, options = {}) {
function runRaw(command, args, options = {}) {
const windowsBatch = process.platform === "win32" && command.endsWith(".cmd");
const executable = windowsBatch ? (process.env.ComSpec ?? "cmd.exe") : command;
const executableArguments = windowsBatch
Expand All @@ -20,12 +20,16 @@ function run(command, args, options = {}) {
.join(" "),
]
: args;
const result = spawnSync(executable, executableArguments, {
return spawnSync(executable, executableArguments, {
cwd: options.cwd ?? root,
encoding: "utf8",
input: options.input,
env: { ...process.env, npm_config_cache: join(root, ".npm-cache") },
});
}

function run(command, args, options = {}) {
const result = runRaw(command, args, options);
if (result.status !== 0) {
throw new Error(result.stderr || result.stdout || `${command} failed`);
}
Expand All @@ -37,6 +41,24 @@ try {
const details = JSON.parse(packOutput);
const filename = details[0]?.filename;
if (!filename) throw new Error("npm pack did not report a tarball filename");
const packedFiles = (details[0]?.files ?? []).map((file) => file.path);
const unexpectedFiles = packedFiles.filter(
(file) =>
file !== "CHANGELOG.md" &&
file !== "LICENSE" &&
file !== "README.md" &&
file !== "SECURITY.md" &&
file !== "package.json" &&
!file.startsWith("dist/"),
);
if (unexpectedFiles.length > 0) {
throw new Error(`unexpected public package files: ${unexpectedFiles.join(", ")}`);
}
for (const requiredFile of ["dist/cli/bin.js", "dist/cli/index.js", "dist/index.js"]) {
if (!packedFiles.includes(requiredFile)) {
throw new Error(`public package is missing ${requiredFile}`);
}
}
const tarball = join(temporaryDirectory, filename);
const installDirectory = join(temporaryDirectory, "consumer");
await mkdir(installDirectory);
Expand All @@ -49,14 +71,22 @@ try {
process.platform === "win32" ? "issuesafe.cmd" : "issuesafe",
);
const version = run(bin, ["--version"], { cwd: installDirectory }).trim();
if (version !== "0.1.0") throw new Error(`unexpected CLI version: ${version}`);
if (version !== "0.1.1") throw new Error(`unexpected CLI version: ${version}`);

const sanitized = run(bin, ["sanitize", "--stdin"], {
cwd: installDirectory,
input: "owner=package-test@example.invalid\n",
});
if (sanitized !== "owner=[EMAIL_1]\n") throw new Error("installed CLI did not sanitize stdin");

const invalidUsage = runRaw(bin, ["sanitize"], { cwd: installDirectory });
if (invalidUsage.status !== 2) {
throw new Error(`installed CLI returned ${invalidUsage.status} for invalid usage instead of 2`);
}
if (!invalidUsage.stderr.includes("provide an input file")) {
throw new Error("installed CLI did not explain invalid usage on stderr");
}

const packageJson = JSON.parse(
await readFile(
join(installDirectory, "node_modules", "@aminhanifm", "issuesafe", "package.json"),
Expand All @@ -66,7 +96,9 @@ try {
if (packageJson.name !== "@aminhanifm/issuesafe")
throw new Error("installed package name changed");

console.log(`Verified ${filename}: install, bin, version, and stdin workflow passed.`);
console.log(
`Verified ${filename}: public files, installed bin, version, stdin, and exit codes passed.`,
);
} finally {
await rm(temporaryDirectory, { recursive: true, force: true });
}
5 changes: 5 additions & 0 deletions src/cli/bin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/usr/bin/env node

import { runCli } from "./index.js";

process.exitCode = await runCli(process.argv.slice(2));
8 changes: 0 additions & 8 deletions src/cli/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
#!/usr/bin/env node

import { readFile, writeFile } from "node:fs/promises";
import { pathToFileURL } from "node:url";
import { Command, CommanderError, InvalidArgumentError, Option } from "commander";
import {
FINDING_CATEGORIES,
Expand Down Expand Up @@ -153,8 +150,3 @@ export async function runCli(argv: readonly string[], io: CliIo = defaultIo): Pr
return 2;
}
}

const invokedPath = process.argv[1];
if (invokedPath && import.meta.url === pathToFileURL(invokedPath).href) {
process.exitCode = await runCli(process.argv.slice(2));
}
2 changes: 1 addition & 1 deletion src/core/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export const ISSUE_SAFE_SCHEMA_VERSION = "1.0" as const;
export const ISSUE_SAFE_VERSION = "0.1.0" as const;
export const ISSUE_SAFE_VERSION = "0.1.1" as const;

export const FINDING_CATEGORIES = [
"private-key",
Expand Down
2 changes: 1 addition & 1 deletion test/cli/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ describe("CLI", () => {

const version = capture();
expect(await runCli(["--version"], version.io)).toBe(0);
expect(version.stdout.join("")).toBe("0.1.0\n");
expect(version.stdout.join("")).toBe("0.1.1\n");

const sanitize = capture("clean");
expect(await runCli(["--no-color", "sanitize", "--stdin"], sanitize.io)).toBe(0);
Expand Down
1 change: 1 addition & 0 deletions tsup.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export default defineConfig({
entry: {
index: "src/index.ts",
"cli/index": "src/cli/index.ts",
"cli/bin": "src/cli/bin.ts",
},
format: ["esm"],
target: "node22",
Expand Down
Loading