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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

## [Unreleased]

### Added
- **`transitrix` CLI binary** — the primary command is now `transitrix`; it is added as a `bin` entry (and an `npm run transitrix` script) pointing at the same `dist/cli.js`. `--help` and usage text recommend `transitrix`.

### Deprecated
- **`cervin` CLI is deprecated, use `transitrix`.** The `cervin` bin is kept as a compatibility alias (no removal in this release; slated for 2.0.0). Invoking the tool under the `cervin` name prints a one-line deprecation notice to stderr. First phase of the Cervin → Transitrix CLI rename (CLAUDE.md §Cervin naming, P1).

## [1.4.1] — 2026-06-09

### Fixed
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"test:diagrams": "npm --workspace packages/diagrams test",
"test:watch": "vitest",
"metrics:baseline": "npm run build && node scripts/measure-baseline.mjs",
"transitrix": "tsx src/cli.ts",
"cervin": "tsx src/cli.ts",
"ui:dev": "vite dev --config ui/vite.config.ts",
"ui:build": "vite build --config ui/vite.config.ts",
Expand All @@ -49,6 +50,7 @@
"sync-examples": "node scripts/sync-examples-from-methodology.mjs"
},
"bin": {
"transitrix": "./dist/cli.js",
"cervin": "./dist/cli.js"
},
"dependencies": {
Expand Down
23 changes: 23 additions & 0 deletions src/cli-parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,26 @@ export function inputMatchesExtension(filePath: string, exts: string[]): boolean
const lowered = filePath.replace(/\\/g, '/').toLowerCase();
return exts.some((e) => lowered.endsWith(e.toLowerCase()));
}

/**
* Cervin → Transitrix deprecation (CLAUDE.md §Cervin naming, P1). The `cervin`
* binary is a kept-for-compatibility alias of `transitrix`; both bin entries
* resolve to the same `dist/cli.js`. We surface a one-line deprecation notice
* when the tool was launched under the legacy name.
*
* Detection is best-effort from the invocation path (argv[1]): on POSIX, npm
* installs the bin as a symlink whose basename is the alias the user typed
* (`.../bin/cervin`), so this fires. On Windows the `.cmd` shim invokes node
* with the resolved `cli.js` path, so the legacy name is not observable there
* and no notice is shown — acceptable graceful degradation for a hint.
*/
export const CERVIN_DEPRECATION_NOTICE =
'cervin: the `cervin` command is deprecated and will be removed in 2.0.0 — use `transitrix` instead.';

export function invokedAsCervin(argv1: string | undefined): boolean {
if (!argv1) return false;
const base = argv1.replace(/\\/g, '/').split('/').pop() ?? '';
// Strip a trailing extension (.js, .cmd, .exe) before matching the stem.
const stem = base.replace(/\.[^.]+$/, '').toLowerCase();
return stem === 'cervin';
}
36 changes: 23 additions & 13 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ import { writeFileSync } from 'node:fs';
import { readFile } from 'node:fs/promises';

import {
CERVIN_DEPRECATION_NOTICE,
DEFAULT_CERVIN_FILE_EXTENSIONS,
inputMatchesExtension,
invokedAsCervin,
parseCliFileArgv,
} from './cli-parse.js';
import { compileCervinYamlWithLayout } from './compiler.js';
Expand All @@ -16,14 +18,16 @@ import type { ProcessIr } from './ir.js';

function printUsage(): void {
console.error(`Transitrix Studio CLI — usage:
cervin serve [--port 8765] [--host 127.0.0.1]
cervin <input.yaml> <output.bpmn> [--no-metrics] [--no-validate]
cervin [--ext=.cervin.yaml,.bpmn.transitrix.yaml] <input.yaml> <output.bpmn> [--no-metrics] [--no-validate]
cervin metrics <input.yaml> [--json]
cervin metrics [--ext=.cervin.yaml,.bpmn.transitrix.yaml] <input.yaml> [--json]
cervin validate <input.yaml> [--json]
cervin validate [--ext=.cervin.yaml,.bpmn.transitrix.yaml] <input.yaml> [--json]
cervin export-compliance [--format md|pdf] [--scope law:<ID>|product:<ID>|gap] [--output <path>] [--root <dir>]
transitrix serve [--port 8765] [--host 127.0.0.1]
transitrix <input.yaml> <output.bpmn> [--no-metrics] [--no-validate]
transitrix [--ext=.cervin.yaml,.bpmn.transitrix.yaml] <input.yaml> <output.bpmn> [--no-metrics] [--no-validate]
transitrix metrics <input.yaml> [--json]
transitrix metrics [--ext=.cervin.yaml,.bpmn.transitrix.yaml] <input.yaml> [--json]
transitrix validate <input.yaml> [--json]
transitrix validate [--ext=.cervin.yaml,.bpmn.transitrix.yaml] <input.yaml> [--json]
transitrix export-compliance [--format md|pdf] [--scope law:<ID>|product:<ID>|gap] [--output <path>] [--root <dir>]

('cervin' is a deprecated alias of 'transitrix'; both run the same CLI.)

serve — local web UI (run npm run ui:build once beforehand).
<compile> — YAML → BPMN 2.0 XML with layout metrics.
Expand All @@ -38,11 +42,11 @@ function printUsage(): void {
--no-validate suppress validation warnings (errors always run).

Examples:
npm run cervin -- compile input.cervin.yaml output.bpmn
npm run cervin -- serve
npm run cervin -- metrics example.cervin.yaml --json
npm run cervin -- validate example.cervin.yaml
npm run cervin -- validate example.cervin.yaml --json
npm run transitrix -- compile input.cervin.yaml output.bpmn
npm run transitrix -- serve
npm run transitrix -- metrics example.cervin.yaml --json
npm run transitrix -- validate example.cervin.yaml
npm run transitrix -- validate example.cervin.yaml --json
`);
}

Expand Down Expand Up @@ -345,6 +349,12 @@ async function handleMetricsCommand(argv: string[]): Promise<void> {
}
}

// Cervin → Transitrix deprecation (P1): warn once when launched under the
// legacy `cervin` bin name. Goes to stderr so it never pollutes --json output.
if (invokedAsCervin(process.argv[1])) {
console.error(CERVIN_DEPRECATION_NOTICE);
}

const subcommand = process.argv[2];

// TX-R004: wrap the top-level dispatch in a try/catch so a logic bug inside
Expand Down
32 changes: 32 additions & 0 deletions tests/cli-parse.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { describe, expect, it } from 'vitest';

import {
CERVIN_DEPRECATION_NOTICE,
DEFAULT_CERVIN_FILE_EXTENSIONS,
invokedAsCervin,
parseCliFileArgv,
inputMatchesExtension,
} from '../src/cli-parse.js';
Expand Down Expand Up @@ -49,3 +51,33 @@ describe('cli-parse', () => {
});
});
});

describe('invokedAsCervin (Cervin deprecation P1)', () => {
it('detects the legacy cervin bin on POSIX symlink paths', () => {
expect(invokedAsCervin('/usr/local/bin/cervin')).toBe(true);
expect(invokedAsCervin('/home/u/project/node_modules/.bin/cervin')).toBe(true);
});

it('detects cervin via a Windows path and extension stem', () => {
expect(invokedAsCervin('C:\\Users\\u\\AppData\\npm\\cervin')).toBe(true);
expect(invokedAsCervin('C:\\tools\\cervin.cmd')).toBe(true);
expect(invokedAsCervin('/path/cervin.js')).toBe(true);
});

it('is case-insensitive on the stem', () => {
expect(invokedAsCervin('/usr/bin/CERVIN')).toBe(true);
});

it('does not fire for transitrix or the bundled cli.js', () => {
expect(invokedAsCervin('/usr/local/bin/transitrix')).toBe(false);
expect(invokedAsCervin('/app/dist/cli.js')).toBe(false);
expect(invokedAsCervin('/path/cerviner')).toBe(false);
expect(invokedAsCervin(undefined)).toBe(false);
expect(invokedAsCervin('')).toBe(false);
});

it('exposes a deprecation notice that names transitrix', () => {
expect(CERVIN_DEPRECATION_NOTICE).toMatch(/transitrix/);
expect(CERVIN_DEPRECATION_NOTICE).toMatch(/deprecated/i);
});
});
Loading