From 30cd79a4a82ab8d184b8080377246adba36ac7a9 Mon Sep 17 00:00:00 2001 From: CaptainAni187 Date: Tue, 8 Sep 2026 22:01:16 +0530 Subject: [PATCH] fix(project): keep non-ASCII characters in slugified identifiers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `slugify()` replaced every `\W` character with a separator. JavaScript's `\W` is ASCII-only, so any non-Latin character was treated as punctuation: `café` became `caf`, `résumé` became `r-sum`, and a name written entirely in a non-Latin script collapsed to an empty string. These slugs are identity, not display: they set workflow and job ids and the keys used for edge references, so the CLI's pull/deploy round-trip loses information for anyone naming steps outside ASCII. Match letters and digits with the Unicode properties `\p{L}` and `\p{N}` instead. Underscore is still kept, matching the old `\W` behaviour, and for ASCII-only input the output is unchanged. Unicode property escapes need an ES2018 target, so this sets `target` on the project package's tsconfig. Build output comes from tsup; that file only drives type validation. --- packages/project/src/util/slugify.ts | 18 +++++++++- packages/project/test/util/slugify.test.ts | 42 ++++++++++++++++++++++ packages/project/tsconfig.json | 3 ++ 3 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 packages/project/test/util/slugify.test.ts diff --git a/packages/project/src/util/slugify.ts b/packages/project/src/util/slugify.ts index 4c28a71c8..9a2f531ac 100644 --- a/packages/project/src/util/slugify.ts +++ b/packages/project/src/util/slugify.ts @@ -1,5 +1,21 @@ +/** + * Convert a human-readable name into a slug used as an identifier. + * + * Any run of characters which is neither a letter, a digit nor an underscore + * becomes a separator. Letters and digits are matched with the Unicode + * properties `\p{L}` and `\p{N}` rather than `\w`, which is ASCII-only and + * would drop every non-Latin character: `café` slugified to `caf`, and a name + * written entirely in a non-Latin script collapsed to an empty string. + * + * For ASCII-only input this produces exactly the same slug as before, so + * identifiers in existing projects are unaffected. + */ export default function slugify(text: string) { return ( - text?.replace(/\W/g, ' ').trim().replace(/\s+/g, '-').toLowerCase() ?? '' + text + ?.replace(/[^\p{L}\p{N}_]+/gu, ' ') + .trim() + .replace(/\s+/g, '-') + .toLowerCase() ?? '' ); } diff --git a/packages/project/test/util/slugify.test.ts b/packages/project/test/util/slugify.test.ts new file mode 100644 index 000000000..119be158e --- /dev/null +++ b/packages/project/test/util/slugify.test.ts @@ -0,0 +1,42 @@ +import test from 'ava'; +import slugify from '../../src/util/slugify'; + +test('lowercases and hyphenates a plain name', (t) => { + t.is(slugify('My Workflow'), 'my-workflow'); +}); + +test('collapses runs of separators', (t) => { + t.is(slugify('My Workflow -- Two'), 'my-workflow-two'); +}); + +test('keeps underscores and digits', (t) => { + t.is(slugify('step_1 of 2'), 'step_1-of-2'); +}); + +test('trims leading and trailing separators', (t) => { + t.is(slugify(' ...My Workflow! '), 'my-workflow'); +}); + +test('keeps accented latin characters', (t) => { + t.is(slugify('café'), 'café'); + t.is(slugify('résumé'), 'résumé'); +}); + +test('keeps a whole accented phrase readable', (t) => { + t.is(slugify("Vérifier l'état du patient"), 'vérifier-l-état-du-patient'); +}); + +test('keeps non-latin scripts instead of collapsing to an empty string', (t) => { + t.is(slugify('患者確認'), '患者確認'); + t.is(slugify('проверка пациента'), 'проверка-пациента'); + t.is(slugify('التحقق من المريض'), 'التحقق-من-المريض'); +}); + +test('still drops characters which are neither letters nor digits', (t) => { + t.is(slugify('deploy 🚀 now'), 'deploy-now'); +}); + +test('handles empty and nullish input', (t) => { + t.is(slugify(''), ''); + t.is(slugify(undefined as unknown as string), ''); +}); diff --git a/packages/project/tsconfig.json b/packages/project/tsconfig.json index 77e4d2e8f..71bf585a7 100644 --- a/packages/project/tsconfig.json +++ b/packages/project/tsconfig.json @@ -3,6 +3,9 @@ "include": ["src/**/*.ts", "test/**/*test.ts"], "compilerOptions": { "module": "ESNext", + // Unicode property escapes in slugify need ES2018 or later. Build output is + // controlled by tsup; this only affects type validation. + "target": "ES2020", "sourceMap": true } }