@ir-kit/codegen-core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Ahmed Rowaihi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,54 @@
1
+ # `@ir-kit/codegen-core`
2
+
3
+ Spec-agnostic codegen primitives shared by every native-SDK generator family — identifier transforms (`pascal`, `camel`, `safeIdent`, `safeCaseName`, `softCamel`, `synthName`, `avoidLeadingDigit`, `escapeIfReserved`), filesystem safety (`assertSafeOutputDir`), and project-name derivation (`defaultProjectName`). Pure functions, no spec dependencies.
4
+
5
+ ## Install
6
+
7
+ ```sh
8
+ npm install @ir-kit/codegen-core
9
+ ```
10
+
11
+ Zero runtime dependencies.
12
+
13
+ ## Identifier helpers
14
+
15
+ ```ts
16
+ import {
17
+ avoidLeadingDigit,
18
+ camel,
19
+ escapeIfReserved,
20
+ pascal,
21
+ safeCaseName,
22
+ safeIdent,
23
+ softCamel,
24
+ synthName,
25
+ } from "@ir-kit/codegen-core";
26
+
27
+ pascal("first_name"); // "FirstName"
28
+ camel("first_name"); // "firstName"
29
+ softCamel("FirstName"); // "FirstName" (preserves first-letter casing)
30
+ camel("FirstName"); // "firstName" (lowercases first letter)
31
+ safeIdent("2024_release"); // "_2024Release" (digit guard + pascal)
32
+ safeCaseName("4xx"); // "_4xx"
33
+ synthName("User", ["Address", "Street"]); // "User_Address_Street"
34
+
35
+ avoidLeadingDigit("2value"); // "_2value"
36
+ escapeIfReserved("class", new Set(["class"]), (s) => `\`${s}\``); // "`class`"
37
+ ```
38
+
39
+ ## Filesystem helpers
40
+
41
+ ```ts
42
+ import { assertSafeOutputDir, defaultProjectName } from "@ir-kit/codegen-core";
43
+
44
+ assertSafeOutputDir("./out/sdk"); // throws if outside CWD or in . / ..
45
+ defaultProjectName("./out/petstore-sdk"); // "petstore-sdk"
46
+ ```
47
+
48
+ ## Status
49
+
50
+ `0.1.0` — first release under the `@ir-kit/*` scope. Replaces the legacy `@ahmedrowaihi/codegen-core` (deprecated).
51
+
52
+ ## Repo
53
+
54
+ Source at [ir-kit/ir-kit](https://github.com/ir-kit/ir-kit/tree/main/packages/shared/codegen-core).
package/dist/fs.d.ts ADDED
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Refuse to wipe the current working directory or a filesystem root.
3
+ * `clean: true` runs `rm -rf` on the resolved output directory at the
4
+ * start of `generate()`, which would destroy the user's repo if they
5
+ * pointed `output` at `.` or `/`. Cheap guardrail with no false
6
+ * positives — generators always want output under a dedicated subdir.
7
+ */
8
+ export declare function assertSafeOutputDir(out: string): void;
9
+ /**
10
+ * Derive a project name from the output directory's basename, with
11
+ * non-alphanumerics collapsed to dashes. Used as the fallback for
12
+ * Gradle's `rootProject.name` / SwiftPM `Package(name:)` /
13
+ * `module name` defaults. Empty result (e.g. output is `/`) falls
14
+ * back to the literal `"generated-sdk"` so the build files always
15
+ * have a valid name slot.
16
+ */
17
+ export declare function defaultProjectName(outputDir: string): string;
18
+ //# sourceMappingURL=fs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fs.d.ts","sourceRoot":"","sources":["../src/fs.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAMrD;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAG5D"}
package/dist/fs.js ADDED
@@ -0,0 +1,26 @@
1
+ import { basename, parse as parsePath } from "node:path";
2
+ /**
3
+ * Refuse to wipe the current working directory or a filesystem root.
4
+ * `clean: true` runs `rm -rf` on the resolved output directory at the
5
+ * start of `generate()`, which would destroy the user's repo if they
6
+ * pointed `output` at `.` or `/`. Cheap guardrail with no false
7
+ * positives — generators always want output under a dedicated subdir.
8
+ */
9
+ export function assertSafeOutputDir(out) {
10
+ if (out === process.cwd() || out === parsePath(out).root) {
11
+ throw new Error(`Refusing to clean output directory: ${out} (would wipe cwd or filesystem root). Use a dedicated subdirectory.`);
12
+ }
13
+ }
14
+ /**
15
+ * Derive a project name from the output directory's basename, with
16
+ * non-alphanumerics collapsed to dashes. Used as the fallback for
17
+ * Gradle's `rootProject.name` / SwiftPM `Package(name:)` /
18
+ * `module name` defaults. Empty result (e.g. output is `/`) falls
19
+ * back to the literal `"generated-sdk"` so the build files always
20
+ * have a valid name slot.
21
+ */
22
+ export function defaultProjectName(outputDir) {
23
+ const base = basename(outputDir).replace(/[^A-Za-z0-9-]+/g, "-");
24
+ return base || "generated-sdk";
25
+ }
26
+ //# sourceMappingURL=fs.js.map
package/dist/fs.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fs.js","sourceRoot":"","sources":["../src/fs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,KAAK,IAAI,SAAS,EAAE,MAAM,WAAW,CAAC;AAEzD;;;;;;GAMG;AACH,MAAM,UAAU,mBAAmB,CAAC,GAAW;IAC7C,IAAI,GAAG,KAAK,OAAO,CAAC,GAAG,EAAE,IAAI,GAAG,KAAK,SAAS,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;QACzD,MAAM,IAAI,KAAK,CACb,uCAAuC,GAAG,qEAAqE,CAChH,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,kBAAkB,CAAC,SAAiB;IAClD,MAAM,IAAI,GAAG,QAAQ,CAAC,SAAS,CAAC,CAAC,OAAO,CAAC,iBAAiB,EAAE,GAAG,CAAC,CAAC;IACjE,OAAO,IAAI,IAAI,eAAe,CAAC;AACjC,CAAC"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Identifier transforms shared by every native-SDK generator. All
3
+ * functions here are pure string ↔ string — they do *not* know about
4
+ * any target language's reserved-word set or visibility rules. Each
5
+ * generator layers its own `paramIdent` / `exportedIdent` /
6
+ * `enumEntryIdent` on top, calling these as primitives.
7
+ *
8
+ * The pascal regex deliberately strips both leading and trailing
9
+ * non-alphanumeric runs so identifiers like `timeframe[]` (PHP-style
10
+ * array param names from real-world specs) become `Timeframe`, not
11
+ * `TimeframeNullNull` or similar. Wire-level names are unaffected —
12
+ * generators preserve the original param.name when emitting the URL.
13
+ */
14
+ export declare function pascal(s: string): string;
15
+ export declare function camel(s: string): string;
16
+ /** PascalCase, prepended with `_` if it would otherwise start with a
17
+ * digit. Used wherever a type-name slot can't begin with a number. */
18
+ export declare function safeIdent(s: string): string;
19
+ /** camelCase, prepended with `_` if it would otherwise start with a
20
+ * digit. Used for enum case names where the source is numeric. */
21
+ export declare function safeCaseName(s: string): string;
22
+ /**
23
+ * Camel-like transform that PRESERVES the first character's casing —
24
+ * collapses non-alnum word boundaries and uppercases the following
25
+ * character. Diverges from `camel()` in that it does NOT lowercase
26
+ * the leading letter, so `softCamel("FirstName")` stays `"FirstName"`
27
+ * while `camel("FirstName")` returns `"firstName"`. Used by emitters
28
+ * that want to respect user-given casing on parameter identifiers
29
+ * while still normalising word boundaries (snake_case → camelCase but
30
+ * already-cased inputs pass through).
31
+ */
32
+ export declare function softCamel(s: string): string;
33
+ /** Prepend `_` when the string would otherwise start with a digit;
34
+ * pass through unchanged otherwise. Most identifier-safe transforms
35
+ * end with this guard. */
36
+ export declare function avoidLeadingDigit(s: string): string;
37
+ /** Wrap a name through a target-specific escape iff it collides with
38
+ * the target's reserved-keyword set. Centralises the
39
+ * `RESERVED.has(name) ? escape(name) : name` pattern each emitter's
40
+ * `paramIdent` open-coded. */
41
+ export declare function escapeIfReserved(name: string, reserved: ReadonlySet<string>, escape: (name: string) => string): string;
42
+ /**
43
+ * Build a synthetic top-level type name for an inline schema, owned
44
+ * by some declared type. The owner part stays as-is so collisions
45
+ * across owners are impossible (`User_Address` vs `Order_Address`).
46
+ * Path segments get pascal'd individually so `["properties","streetName"]`
47
+ * collapses to `User_Properties_StreetName` rather than running the
48
+ * pascal regex over the joined string.
49
+ */
50
+ export declare function synthName(owner: string, path: ReadonlyArray<string>): string;
51
+ //# sourceMappingURL=identifiers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identifiers.d.ts","sourceRoot":"","sources":["../src/identifiers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,wBAAgB,MAAM,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAMxC;AAED,wBAAgB,KAAK,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAGvC;AAED;uEACuE;AACvE,wBAAgB,SAAS,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAE3C;AAED;mEACmE;AACnE,wBAAgB,YAAY,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAE9C;AAED;;;;;;;;;GASG;AACH,wBAAgB,SAAS,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAE3C;AAED;;2BAE2B;AAC3B,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED;;;+BAG+B;AAC/B,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,WAAW,CAAC,MAAM,CAAC,EAC7B,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAC/B,MAAM,CAER;AAED;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,GAAG,MAAM,CAE5E"}
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Identifier transforms shared by every native-SDK generator. All
3
+ * functions here are pure string ↔ string — they do *not* know about
4
+ * any target language's reserved-word set or visibility rules. Each
5
+ * generator layers its own `paramIdent` / `exportedIdent` /
6
+ * `enumEntryIdent` on top, calling these as primitives.
7
+ *
8
+ * The pascal regex deliberately strips both leading and trailing
9
+ * non-alphanumeric runs so identifiers like `timeframe[]` (PHP-style
10
+ * array param names from real-world specs) become `Timeframe`, not
11
+ * `TimeframeNullNull` or similar. Wire-level names are unaffected —
12
+ * generators preserve the original param.name when emitting the URL.
13
+ */
14
+ export function pascal(s) {
15
+ return s
16
+ .replace(/^[^a-zA-Z0-9]+/, "")
17
+ .replace(/[^a-zA-Z0-9]+$/, "")
18
+ .replace(/[^a-zA-Z0-9]+(.)/g, (_, c) => c.toUpperCase())
19
+ .replace(/^./, (c) => c.toUpperCase());
20
+ }
21
+ export function camel(s) {
22
+ const p = pascal(s);
23
+ return p.length > 0 ? p[0].toLowerCase() + p.slice(1) : p;
24
+ }
25
+ /** PascalCase, prepended with `_` if it would otherwise start with a
26
+ * digit. Used wherever a type-name slot can't begin with a number. */
27
+ export function safeIdent(s) {
28
+ return avoidLeadingDigit(pascal(s));
29
+ }
30
+ /** camelCase, prepended with `_` if it would otherwise start with a
31
+ * digit. Used for enum case names where the source is numeric. */
32
+ export function safeCaseName(s) {
33
+ return avoidLeadingDigit(camel(s));
34
+ }
35
+ /**
36
+ * Camel-like transform that PRESERVES the first character's casing —
37
+ * collapses non-alnum word boundaries and uppercases the following
38
+ * character. Diverges from `camel()` in that it does NOT lowercase
39
+ * the leading letter, so `softCamel("FirstName")` stays `"FirstName"`
40
+ * while `camel("FirstName")` returns `"firstName"`. Used by emitters
41
+ * that want to respect user-given casing on parameter identifiers
42
+ * while still normalising word boundaries (snake_case → camelCase but
43
+ * already-cased inputs pass through).
44
+ */
45
+ export function softCamel(s) {
46
+ return s.replace(/[^a-zA-Z0-9]+(.)/g, (_, c) => c.toUpperCase());
47
+ }
48
+ /** Prepend `_` when the string would otherwise start with a digit;
49
+ * pass through unchanged otherwise. Most identifier-safe transforms
50
+ * end with this guard. */
51
+ export function avoidLeadingDigit(s) {
52
+ return /^[0-9]/.test(s) ? `_${s}` : s;
53
+ }
54
+ /** Wrap a name through a target-specific escape iff it collides with
55
+ * the target's reserved-keyword set. Centralises the
56
+ * `RESERVED.has(name) ? escape(name) : name` pattern each emitter's
57
+ * `paramIdent` open-coded. */
58
+ export function escapeIfReserved(name, reserved, escape) {
59
+ return reserved.has(name) ? escape(name) : name;
60
+ }
61
+ /**
62
+ * Build a synthetic top-level type name for an inline schema, owned
63
+ * by some declared type. The owner part stays as-is so collisions
64
+ * across owners are impossible (`User_Address` vs `Order_Address`).
65
+ * Path segments get pascal'd individually so `["properties","streetName"]`
66
+ * collapses to `User_Properties_StreetName` rather than running the
67
+ * pascal regex over the joined string.
68
+ */
69
+ export function synthName(owner, path) {
70
+ return [owner, ...path.map(pascal)].join("_");
71
+ }
72
+ //# sourceMappingURL=identifiers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identifiers.js","sourceRoot":"","sources":["../src/identifiers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,MAAM,CAAC,CAAS;IAC9B,OAAO,CAAC;SACL,OAAO,CAAC,gBAAgB,EAAE,EAAE,CAAC;SAC7B,OAAO,CAAC,gBAAgB,EAAE,EAAE,CAAC;SAC7B,OAAO,CAAC,mBAAmB,EAAE,CAAC,CAAC,EAAE,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;SAC/D,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;AAC3C,CAAC;AAED,MAAM,UAAU,KAAK,CAAC,CAAS;IAC7B,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;IACpB,OAAO,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAE,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAC7D,CAAC;AAED;uEACuE;AACvE,MAAM,UAAU,SAAS,CAAC,CAAS;IACjC,OAAO,iBAAiB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;AACtC,CAAC;AAED;mEACmE;AACnE,MAAM,UAAU,YAAY,CAAC,CAAS;IACpC,OAAO,iBAAiB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;AACrC,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,SAAS,CAAC,CAAS;IACjC,OAAO,CAAC,CAAC,OAAO,CAAC,mBAAmB,EAAE,CAAC,CAAC,EAAE,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;AAC3E,CAAC;AAED;;2BAE2B;AAC3B,MAAM,UAAU,iBAAiB,CAAC,CAAS;IACzC,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;AACxC,CAAC;AAED;;;+BAG+B;AAC/B,MAAM,UAAU,gBAAgB,CAC9B,IAAY,EACZ,QAA6B,EAC7B,MAAgC;IAEhC,OAAO,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AAClD,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,SAAS,CAAC,KAAa,EAAE,IAA2B;IAClE,OAAO,CAAC,KAAK,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAChD,CAAC"}
@@ -0,0 +1,3 @@
1
+ export { assertSafeOutputDir, defaultProjectName } from "./fs.js";
2
+ export { avoidLeadingDigit, camel, escapeIfReserved, pascal, safeCaseName, safeIdent, softCamel, synthName, } from "./identifiers.js";
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAClE,OAAO,EACL,iBAAiB,EACjB,KAAK,EACL,gBAAgB,EAChB,MAAM,EACN,YAAY,EACZ,SAAS,EACT,SAAS,EACT,SAAS,GACV,MAAM,kBAAkB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { assertSafeOutputDir, defaultProjectName } from "./fs.js";
2
+ export { avoidLeadingDigit, camel, escapeIfReserved, pascal, safeCaseName, safeIdent, softCamel, synthName, } from "./identifiers.js";
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAClE,OAAO,EACL,iBAAiB,EACjB,KAAK,EACL,gBAAgB,EAChB,MAAM,EACN,YAAY,EACZ,SAAS,EACT,SAAS,EACT,SAAS,GACV,MAAM,kBAAkB,CAAC"}
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "@ir-kit/codegen-core",
3
+ "version": "0.1.0",
4
+ "description": "Spec-agnostic codegen primitives shared by OpenAPI and AsyncAPI generator families — identifier transforms (pascal/camel/safeIdent), filesystem safety, project-name derivation. Pure functions, no spec dependencies.",
5
+ "keywords": [
6
+ "codegen",
7
+ "openapi",
8
+ "asyncapi",
9
+ "sdk",
10
+ "shared",
11
+ "identifiers"
12
+ ],
13
+ "homepage": "https://github.com/ir-kit/ir-kit/tree/main/packages/shared/codegen-core",
14
+ "bugs": {
15
+ "url": "https://github.com/ir-kit/ir-kit/issues"
16
+ },
17
+ "license": "MIT",
18
+ "author": {
19
+ "name": "Ahmed Rowaihi",
20
+ "email": "ahmedrowaihi@gmail.com"
21
+ },
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "git+https://github.com/ir-kit/ir-kit.git",
25
+ "directory": "packages/shared/codegen-core"
26
+ },
27
+ "type": "module",
28
+ "sideEffects": false,
29
+ "main": "./dist/index.js",
30
+ "types": "./dist/index.d.ts",
31
+ "exports": {
32
+ ".": {
33
+ "types": "./dist/index.d.ts",
34
+ "import": "./dist/index.js"
35
+ },
36
+ "./package.json": "./package.json"
37
+ },
38
+ "files": [
39
+ "dist",
40
+ "LICENSE.md"
41
+ ],
42
+ "publishConfig": {
43
+ "access": "public",
44
+ "provenance": true
45
+ },
46
+ "devDependencies": {
47
+ "@types/node": "^22",
48
+ "vitest": "^4.0.0",
49
+ "@ir-kit/tsconfig": "0.0.0"
50
+ },
51
+ "engines": {
52
+ "node": ">=20.19.0"
53
+ },
54
+ "scripts": {
55
+ "build": "tsc -p tsconfig.build.json",
56
+ "dev": "tsc -p tsconfig.build.json --watch",
57
+ "typecheck": "tsc --noEmit",
58
+ "test": "vitest run --passWithNoTests"
59
+ }
60
+ }