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
45 changes: 45 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: CI

on:
push:
branches: [master]
pull_request:

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 24
- run: npm ci
- run: npm run typecheck
- run: npm test
- run: npm run build
- uses: actions/upload-artifact@v4
with:
name: dist
path: dist/

# The library claims engines >= 16; prove it by exercising the built
# ESM/CJS output on every supported major (the TS test suite itself needs
# Node 23+ for type stripping, so old majors smoke-test dist instead).
compat:
needs: test
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
node: [16, 18, 20, 22]
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
name: dist
path: dist
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
- run: node scripts/smoke.mjs
- run: node scripts/smoke.cjs
34 changes: 34 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: Release

# Publishes to npm when a version tag is pushed (git tag v1.2.3 && git push --tags).
#
# Authentication uses npm trusted publishing (OIDC): configure it once on
# npmjs.com under the package's Settings -> Trusted publisher, pointing at
# this repository and this workflow file. No token secret is needed, and
# provenance attestation is generated automatically.
#
# Token fallback: create a granular automation token on npmjs.com, save it
# as the NPM_TOKEN repository secret, and uncomment the env line below.

on:
push:
tags: ['v*']

permissions:
contents: read
id-token: write

jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 24
registry-url: 'https://registry.npmjs.org'
- run: npm ci
# prepublishOnly runs typecheck + tests + build before anything is sent.
- run: npm publish
# env:
# NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
46 changes: 46 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Changelog

All notable changes to this project are documented in this file.
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and the project adheres to [Semantic Versioning](https://semver.org/).

## [Unreleased]

### Added
- CI workflow: full test suite on Node 24 plus a compatibility matrix that
smoke-tests the built ESM/CJS output on Node 16/18/20/22, backing the
`engines: >=16` claim with an actual run.
- Release workflow publishing to npm via trusted publishing (OIDC) with
provenance attestation.

## [1.0.1] - 2026-08-28

### Changed
- Word/line modes now use a fused scan pipeline: token boundaries and
FNV-1a hashes are collected in one pass, interning runs through an
open-addressed table reading straight out of the input strings, and every
output text is a single slice — no token substrings are materialized.
- `char` mode bypasses tokenization entirely (code point scan straight into
typed arrays, surrogate-pair-safe boundary handling).
- Common token prefix/suffix is stripped before interning.

### Performance
- Word diff on a 44 KB / 10-edit document: 1.24 ms → 0.97 ms.
- Char diff on the same document: 1.63 ms → 0.62 ms.
- Line diff: 0.33 ms → 0.27 ms.

### Documentation
- README benchmarks now compare against jsdiff, diff-match-patch, and
fast-myers-diff end-to-end (`npm run bench`), with fairness caveats.
- npm search metadata: rewritten description and expanded keywords.

## [1.0.0] - 2026-08-28

### Added
- Initial release: Myers O(ND) shortest-edit-script diff with the
linear-space middle-snake refinement, token interning to `Int32Array`,
reusable scratch buffers, and Unicode-aware word/char/line tokenization.
- `diff(a, b, { mode })`, `diffTokens(aTokens, bTokens)`, and `tokenize`
public API; ESM + CJS + browser IIFE builds with bundled type definitions.
- Verification suite: seeded fuzz round-trips and optimality checks against
a reference LCS dynamic program.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# @krkarma777/string-diff

[![npm version](https://img.shields.io/npm/v/%40krkarma777%2Fstring-diff)](https://www.npmjs.com/package/@krkarma777/string-diff)
[![CI](https://img.shields.io/github/actions/workflow/status/krkarma777/string-difference-finder/ci.yml?branch=master&label=CI)](https://github.com/krkarma777/string-difference-finder/actions/workflows/ci.yml)
[![weekly downloads](https://img.shields.io/npm/dw/%40krkarma777%2Fstring-diff)](https://www.npmjs.com/package/@krkarma777/string-diff)
[![total downloads](https://badgen.net/npm/dt/@krkarma777/string-diff?label=total%20downloads)](https://npm-stat.com/charts.html?package=%40krkarma777%2Fstring-diff)
[![minzipped size](https://img.shields.io/badge/min%2Bgzip-2.9%20kB-blue)](#how-it-works)
Expand Down
23 changes: 23 additions & 0 deletions scripts/smoke.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Smoke test for the built CommonJS entry, runnable on every supported Node major.
const assert = require('node:assert');
const { diff } = require('../dist/index.cjs');

const entries = diff('a b c', 'a x c');
assert.deepStrictEqual(entries, [
{ operation: 'equal', text: 'a ' },
{ operation: 'delete', text: 'b' },
{ operation: 'insert', text: 'x' },
{ operation: 'equal', text: ' c' },
]);

const a = 'lorem ipsum dolor';
const b = 'lorem IPSUM dolor sit';
for (const mode of ['word', 'char', 'line']) {
const result = diff(a, b, { mode });
const joinedA = result.filter(e => e.operation !== 'insert').map(e => e.text).join('');
const joinedB = result.filter(e => e.operation !== 'delete').map(e => e.text).join('');
assert.strictEqual(joinedA, a);
assert.strictEqual(joinedB, b);
}

console.log(`smoke.cjs OK on node ${process.version}`);
28 changes: 28 additions & 0 deletions scripts/smoke.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Smoke test for the built ESM entry, runnable on every supported Node major.
import assert from 'node:assert';
import { diff, diffTokens, tokenize } from '../dist/index.js';

const entries = diff('the quick fox', 'the slow fox');
assert.deepStrictEqual(entries, [
{ operation: 'equal', text: 'the ' },
{ operation: 'delete', text: 'quick' },
{ operation: 'insert', text: 'slow' },
{ operation: 'equal', text: ' fox' },
]);

assert.deepStrictEqual(diff('안녕하세요 세계', '안녕하세요 지구')[0], { operation: 'equal', text: '안녕하세요 ' });

for (const mode of ['word', 'char', 'line']) {
const a = 'alpha beta\ngamma delta 😀';
const b = 'alpha BETA\ngamma epsilon 😀!';
const result = diff(a, b, { mode });
const joinedA = result.filter(e => e.operation !== 'insert').map(e => e.text).join('');
const joinedB = result.filter(e => e.operation !== 'delete').map(e => e.text).join('');
assert.strictEqual(joinedA, a, `round-trip a failed in ${mode} mode`);
assert.strictEqual(joinedB, b, `round-trip b failed in ${mode} mode`);
}

assert.deepStrictEqual(diffTokens(['x', 'y'], ['x', 'z']).length, 3);
assert.deepStrictEqual(tokenize('a b', 'word'), ['a', ' ', 'b']);

console.log(`smoke.mjs OK on node ${process.version}`);
Loading