@skmtc/core 0.0.828 โ 0.0.830
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/README.md +90 -63
- package/esm/helpers/strings.d.ts +12 -1
- package/esm/helpers/strings.d.ts.map +1 -1
- package/esm/helpers/strings.js +31 -8
- package/package.json +1 -1
- package/script/helpers/strings.d.ts +12 -1
- package/script/helpers/strings.d.ts.map +1 -1
- package/script/helpers/strings.js +31 -8
package/README.md
CHANGED
|
@@ -1,89 +1,116 @@
|
|
|
1
|
-
[](https://opensource.org/licenses/Apache-2.0)
|
|
2
|
-
[](https://deno.land/)
|
|
3
|
-
[](https://discord.com/invite/Mg88C8Xu5Y)
|
|
4
|
-
|
|
5
1
|
<p align="center">
|
|
6
|
-
<img src="
|
|
2
|
+
<img src="./skmtc.svg" />
|
|
7
3
|
</p>
|
|
8
4
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
**Stop writing the same code over and over.** Generate TypeScript types, API clients, validation schemas, and more directly from your OpenAPI specifications.
|
|
5
|
+
**Skmtc is a declarative code generation framework**. It lets you generate TypeScript code from OpenAPI schemas. Specify the exact output you want using string templates and get it in seconds.
|
|
12
6
|
|
|
13
|
-
|
|
14
|
-
**Pain:** Hours spent manually writing TypeScript interfaces from OpenAPI docs, fighting typos and sync issues.
|
|
15
|
-
**Solution:** Generate accurate types with perfect synchronization.
|
|
7
|
+
## โจ Features
|
|
16
8
|
|
|
17
|
-
|
|
18
|
-
**
|
|
19
|
-
**
|
|
9
|
+
- ๐๏ธ **Fast** - Generates 550k+ tokens per second
|
|
10
|
+
- ๐งต **Easy to edit code generators** - Outputs specified using string templates, not ASTs
|
|
11
|
+
- ๐ฅ **Stackable generators** - Combine multiple generators to generate deep functionality
|
|
12
|
+
- ๐๏ธ **Use your own code conventions** - Full control over naming and file structure
|
|
20
13
|
|
|
21
|
-
|
|
22
|
-
**Pain:** No compile-time guarantees that API calls match backend schemas.
|
|
23
|
-
**Solution:** End-to-end type safety from schema through clients to UI components.
|
|
14
|
+
## ๐ Quick Start
|
|
24
15
|
|
|
25
|
-
|
|
16
|
+
```bash
|
|
17
|
+
# Run directly with npx
|
|
18
|
+
npx skmtc
|
|
26
19
|
|
|
27
|
-
|
|
20
|
+
```
|
|
28
21
|
|
|
29
|
-
###
|
|
22
|
+
### Running code generators
|
|
30
23
|
|
|
31
24
|
```bash
|
|
32
|
-
|
|
33
|
-
|
|
25
|
+
npx skmtc generate @skmtc/supabase-backend https://petstore3.swagger.io/api/v3/openapi.json
|
|
26
|
+
# Generated 9 files (507 lines, 3,383 tokens) in 9ms
|
|
27
|
+
|
|
28
|
+
npx skmtc generate @skmtc/supabase-react-client https://raw.githubusercontent.com/cloudflare/api-schemas/refs/heads/main/openapi.json
|
|
29
|
+
# Generated 6,797 files (104,752 lines, 1,635,227 tokens) in 2,969ms
|
|
34
30
|
```
|
|
35
31
|
|
|
36
|
-
###
|
|
32
|
+
### Example generator code
|
|
37
33
|
|
|
38
34
|
```typescript
|
|
39
|
-
import {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
}
|
|
35
|
+
import { ZodInsertable } from '@skmtc/gen-zod'
|
|
36
|
+
|
|
37
|
+
class ZodFetch extends BaseTransformer {
|
|
38
|
+
zodName: string;
|
|
39
|
+
|
|
40
|
+
constructor({context, operation, settings}){
|
|
41
|
+
super({context, operation, settings})
|
|
42
|
+
|
|
43
|
+
// Generate Zod schema for API response and insert it into current file
|
|
44
|
+
const response = operation.toSuccessResponse()?.resolve().toSchema()
|
|
45
|
+
const zodResponse = this.insert(ZodInsertable, response)
|
|
46
|
+
|
|
47
|
+
// Grab Zod schema name to use in output code
|
|
48
|
+
this.zodName = zodResponse.identifier.name
|
|
56
49
|
}
|
|
57
|
-
}
|
|
58
50
|
|
|
59
|
-
//
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
51
|
+
// Define code output
|
|
52
|
+
toString(){
|
|
53
|
+
return `() => {
|
|
54
|
+
const res = await fetch('${this.operation.path}')
|
|
55
|
+
const data = await res.json()
|
|
64
56
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
57
|
+
return ${zodName}.parse(data)
|
|
58
|
+
}`
|
|
59
|
+
}
|
|
68
60
|
}
|
|
69
61
|
```
|
|
70
62
|
|
|
71
|
-
|
|
63
|
+
## ๐ฆ Available Generators
|
|
72
64
|
|
|
73
|
-
|
|
74
|
-
// @/types/user.generated.ts
|
|
75
|
-
export interface User {
|
|
76
|
-
id?: string
|
|
77
|
-
name?: string
|
|
78
|
-
}
|
|
79
|
-
```
|
|
65
|
+
Choose from our growing collection of generators, combone them or create your own:
|
|
80
66
|
|
|
81
|
-
|
|
67
|
+
- **Tanstack Query** - React Query hooks with Zod validation
|
|
68
|
+
- **MSW** - Mock Service Worker handlers from OpenAPI examples
|
|
69
|
+
- **Zod Schemas** - Runtime validation schemas
|
|
70
|
+
- **TypeScript Types** - Pure type definitions
|
|
71
|
+
- **Supabase/Hono Functions** - Edge function handlers
|
|
72
|
+
- See full list at https://github.com/skmtc/skmtc-generators
|
|
82
73
|
|
|
83
|
-
|
|
84
|
-
- **[How-To Guides โ](docs/how-to/)** - Task-oriented tutorials
|
|
85
|
-
- **[API Reference โ](docs/api/)** - Complete technical documentation
|
|
74
|
+
## โ FAQ
|
|
86
75
|
|
|
87
|
-
|
|
76
|
+
### **What OpenAPI versions are supported?**
|
|
77
|
+
Skmtc supports OpenAPI v3.0. Swagger 2.0 and OpenAPI v3.1 are automatically converted to OpenAPI v3.0.
|
|
88
78
|
|
|
89
|
-
|
|
79
|
+
### **Can I customize the generated code?**
|
|
80
|
+
Yes! Each Transformer specifies its output using plain string templates, which means you can
|
|
81
|
+
edit them as would you edit any other code.
|
|
82
|
+
|
|
83
|
+
### **Can I use this with my existing React app?**
|
|
84
|
+
Yes! Skmtc generates standalone code that integrates with any React application. The generated components work with your existing setup.
|
|
85
|
+
|
|
86
|
+
### **How does this compare to OpenAPI Generator?**
|
|
87
|
+
Skmtc is the only code generation framework that provides full control over the generated code. You are not limited by library-specific settings and you do not need to write complex AST code.
|
|
88
|
+
|
|
89
|
+
### **Does it work with Next.js/Remix/Vite?**
|
|
90
|
+
Yes! The generated code is framework-agnostic TypeScript that works with any build tool or library.
|
|
91
|
+
|
|
92
|
+
## ๐ค Contributing
|
|
93
|
+
|
|
94
|
+
We welcome contributions! Check out our [Contributing Guide](CONTRIBUTING.md) to get started.
|
|
95
|
+
|
|
96
|
+
<!-- ## ๐ Documentation
|
|
97
|
+
|
|
98
|
+
- [Full Documentation](https://docs.skmtc.dev)
|
|
99
|
+
- [API Reference](https://docs.skmtc.dev/api)
|
|
100
|
+
- [Custom Generators Guide](https://docs.skmtc.dev/generators)
|
|
101
|
+
- [Examples](https://github.com/skmtc/skmtc/tree/main/examples) -->
|
|
102
|
+
|
|
103
|
+
## ๐ Support
|
|
104
|
+
|
|
105
|
+
- [GitHub Issues](https://github.com/skmtc/skmtc/issues) - Bug reports and feature requests
|
|
106
|
+
- [Discord Community](https://discord.com/invite/Mg88C8Xu5Y) - Get help and share your experience
|
|
107
|
+
|
|
108
|
+
## ๐ License
|
|
109
|
+
|
|
110
|
+
Apache 2.0 ยฉ [Skmtc Contributors](LICENSE.md)
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
[](https://opensource.org/licenses/Apache-2.0)
|
|
115
|
+
[](https://deno.land/)
|
|
116
|
+
[](https://discord.com/invite/Mg88C8Xu5Y)
|
package/esm/helpers/strings.d.ts
CHANGED
|
@@ -77,6 +77,8 @@ export declare const decapitalize: (str: string) => string;
|
|
|
77
77
|
type CamelCaseOptions = {
|
|
78
78
|
/** Whether to capitalize the first character (PascalCase) */
|
|
79
79
|
upperFirst?: boolean;
|
|
80
|
+
/** Whether to retain underscores in place when converting strings */
|
|
81
|
+
retainUnderscores?: boolean;
|
|
80
82
|
};
|
|
81
83
|
/**
|
|
82
84
|
* Converts a string to camelCase, removing non-alphanumeric characters.
|
|
@@ -88,6 +90,7 @@ type CamelCaseOptions = {
|
|
|
88
90
|
* @param str - The string to convert to camelCase
|
|
89
91
|
* @param options - Options to control the conversion
|
|
90
92
|
* @param options.upperFirst - Whether to capitalize the first character (default: false)
|
|
93
|
+
* @param options.retainUnderscores - Whether to retain underscores in place (default: false)
|
|
91
94
|
* @returns The camelCase version of the input string
|
|
92
95
|
*
|
|
93
96
|
* @example Basic usage
|
|
@@ -110,7 +113,15 @@ type CamelCaseOptions = {
|
|
|
110
113
|
* camelCase('XML_HTTP_Request'); // 'xmlHttpRequest'
|
|
111
114
|
* camelCase('2fa-enabled'); // '2faEnabled'
|
|
112
115
|
* ```
|
|
116
|
+
*
|
|
117
|
+
* @example Retaining underscores (retainUnderscores)
|
|
118
|
+
* ```typescript
|
|
119
|
+
* camelCase('user_name', { retainUnderscores: true }); // 'user_name'
|
|
120
|
+
* camelCase('api-key_token', { retainUnderscores: true }); // 'apiKey_token'
|
|
121
|
+
* camelCase('get_user_by_id', { retainUnderscores: true }); // 'get_user_by_id'
|
|
122
|
+
* camelCase('user_profile', { upperFirst: true, retainUnderscores: true }); // 'User_profile'
|
|
123
|
+
* ```
|
|
113
124
|
*/
|
|
114
|
-
export declare const camelCase: (str: string, { upperFirst }?: CamelCaseOptions) => string;
|
|
125
|
+
export declare const camelCase: (str: string, { upperFirst, retainUnderscores }?: CamelCaseOptions) => string;
|
|
115
126
|
export {};
|
|
116
127
|
//# sourceMappingURL=strings.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"strings.d.ts","sourceRoot":"","sources":["../../src/helpers/strings.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAEH;;;;;;;;;;;;GAYG;AACH,OAAO,sBAAsB,CAAC;AAE9B,eAAO,MAAM,UAAU,GAAI,KAAK,MAAM,KAAG,MAExC,CAAA;AAED;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,YAAY,GAAI,KAAK,MAAM,KAAG,MAE1C,CAAA;AAED;;GAEG;AACH,KAAK,gBAAgB,GAAG;IACtB,6DAA6D;IAC7D,UAAU,CAAC,EAAE,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"strings.d.ts","sourceRoot":"","sources":["../../src/helpers/strings.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAEH;;;;;;;;;;;;GAYG;AACH,OAAO,sBAAsB,CAAC;AAE9B,eAAO,MAAM,UAAU,GAAI,KAAK,MAAM,KAAG,MAExC,CAAA;AAED;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,YAAY,GAAI,KAAK,MAAM,KAAG,MAE1C,CAAA;AAED;;GAEG;AACH,KAAK,gBAAgB,GAAG;IACtB,6DAA6D;IAC7D,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,qEAAqE;IACrE,iBAAiB,CAAC,EAAE,OAAO,CAAA;CAC5B,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AACH,eAAO,MAAM,SAAS,GAAI,KAAK,MAAM,EAAE,oCAAmC,gBAAqB,KAAG,MA+BjG,CAAA"}
|
package/esm/helpers/strings.js
CHANGED
|
@@ -85,6 +85,7 @@ export const decapitalize = (str) => {
|
|
|
85
85
|
* @param str - The string to convert to camelCase
|
|
86
86
|
* @param options - Options to control the conversion
|
|
87
87
|
* @param options.upperFirst - Whether to capitalize the first character (default: false)
|
|
88
|
+
* @param options.retainUnderscores - Whether to retain underscores in place (default: false)
|
|
88
89
|
* @returns The camelCase version of the input string
|
|
89
90
|
*
|
|
90
91
|
* @example Basic usage
|
|
@@ -107,16 +108,38 @@ export const decapitalize = (str) => {
|
|
|
107
108
|
* camelCase('XML_HTTP_Request'); // 'xmlHttpRequest'
|
|
108
109
|
* camelCase('2fa-enabled'); // '2faEnabled'
|
|
109
110
|
* ```
|
|
111
|
+
*
|
|
112
|
+
* @example Retaining underscores (retainUnderscores)
|
|
113
|
+
* ```typescript
|
|
114
|
+
* camelCase('user_name', { retainUnderscores: true }); // 'user_name'
|
|
115
|
+
* camelCase('api-key_token', { retainUnderscores: true }); // 'apiKey_token'
|
|
116
|
+
* camelCase('get_user_by_id', { retainUnderscores: true }); // 'get_user_by_id'
|
|
117
|
+
* camelCase('user_profile', { upperFirst: true, retainUnderscores: true }); // 'User_profile'
|
|
118
|
+
* ```
|
|
110
119
|
*/
|
|
111
|
-
export const camelCase = (str, { upperFirst } = {}) => {
|
|
112
|
-
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
120
|
+
export const camelCase = (str, { upperFirst, retainUnderscores } = {}) => {
|
|
121
|
+
// Check if string starts with a delimiter
|
|
122
|
+
const startsWithDelimiter = retainUnderscores
|
|
123
|
+
? /^[^a-zA-Z0-9_]/.test(str) // Non-alphanumeric except underscore
|
|
124
|
+
: /^[^a-zA-Z0-9]/.test(str); // Non-alphanumeric including underscore
|
|
125
|
+
let isFirstMatch = true;
|
|
126
|
+
// Determine delimiter pattern based on retainUnderscores
|
|
127
|
+
const delimiterPattern = retainUnderscores
|
|
128
|
+
? /[^a-zA-Z0-9_]+([a-zA-Z0-9_]+)/g // Treat underscore as regular character
|
|
129
|
+
: /[^a-zA-Z0-9]+([a-zA-Z0-9]+)/g; // Treat underscore as delimiter
|
|
130
|
+
const camelCased = str.replace(delimiterPattern, (_, matched) => {
|
|
131
|
+
// If string starts with delimiter and this is the first match:
|
|
132
|
+
// - Capitalize only if upperFirst is true
|
|
133
|
+
// Otherwise, always capitalize (word comes after delimiter)
|
|
134
|
+
if (isFirstMatch && startsWithDelimiter && !upperFirst) {
|
|
135
|
+
isFirstMatch = false;
|
|
136
|
+
return matched;
|
|
116
137
|
}
|
|
117
|
-
|
|
118
|
-
return matched;
|
|
138
|
+
isFirstMatch = false;
|
|
139
|
+
return capitalize(matched);
|
|
119
140
|
});
|
|
120
|
-
|
|
141
|
+
// Clean non-alphanumeric characters (preserving underscores if retainUnderscores)
|
|
142
|
+
const cleanPattern = retainUnderscores ? /[^a-zA-Z0-9_]/g : /[^a-zA-Z0-9]/g;
|
|
143
|
+
const cleaned = camelCased.replace(cleanPattern, '');
|
|
121
144
|
return upperFirst ? capitalize(cleaned) : cleaned;
|
|
122
145
|
};
|
package/package.json
CHANGED
|
@@ -77,6 +77,8 @@ export declare const decapitalize: (str: string) => string;
|
|
|
77
77
|
type CamelCaseOptions = {
|
|
78
78
|
/** Whether to capitalize the first character (PascalCase) */
|
|
79
79
|
upperFirst?: boolean;
|
|
80
|
+
/** Whether to retain underscores in place when converting strings */
|
|
81
|
+
retainUnderscores?: boolean;
|
|
80
82
|
};
|
|
81
83
|
/**
|
|
82
84
|
* Converts a string to camelCase, removing non-alphanumeric characters.
|
|
@@ -88,6 +90,7 @@ type CamelCaseOptions = {
|
|
|
88
90
|
* @param str - The string to convert to camelCase
|
|
89
91
|
* @param options - Options to control the conversion
|
|
90
92
|
* @param options.upperFirst - Whether to capitalize the first character (default: false)
|
|
93
|
+
* @param options.retainUnderscores - Whether to retain underscores in place (default: false)
|
|
91
94
|
* @returns The camelCase version of the input string
|
|
92
95
|
*
|
|
93
96
|
* @example Basic usage
|
|
@@ -110,7 +113,15 @@ type CamelCaseOptions = {
|
|
|
110
113
|
* camelCase('XML_HTTP_Request'); // 'xmlHttpRequest'
|
|
111
114
|
* camelCase('2fa-enabled'); // '2faEnabled'
|
|
112
115
|
* ```
|
|
116
|
+
*
|
|
117
|
+
* @example Retaining underscores (retainUnderscores)
|
|
118
|
+
* ```typescript
|
|
119
|
+
* camelCase('user_name', { retainUnderscores: true }); // 'user_name'
|
|
120
|
+
* camelCase('api-key_token', { retainUnderscores: true }); // 'apiKey_token'
|
|
121
|
+
* camelCase('get_user_by_id', { retainUnderscores: true }); // 'get_user_by_id'
|
|
122
|
+
* camelCase('user_profile', { upperFirst: true, retainUnderscores: true }); // 'User_profile'
|
|
123
|
+
* ```
|
|
113
124
|
*/
|
|
114
|
-
export declare const camelCase: (str: string, { upperFirst }?: CamelCaseOptions) => string;
|
|
125
|
+
export declare const camelCase: (str: string, { upperFirst, retainUnderscores }?: CamelCaseOptions) => string;
|
|
115
126
|
export {};
|
|
116
127
|
//# sourceMappingURL=strings.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"strings.d.ts","sourceRoot":"","sources":["../../src/helpers/strings.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAEH;;;;;;;;;;;;GAYG;AACH,OAAO,sBAAsB,CAAC;AAE9B,eAAO,MAAM,UAAU,GAAI,KAAK,MAAM,KAAG,MAExC,CAAA;AAED;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,YAAY,GAAI,KAAK,MAAM,KAAG,MAE1C,CAAA;AAED;;GAEG;AACH,KAAK,gBAAgB,GAAG;IACtB,6DAA6D;IAC7D,UAAU,CAAC,EAAE,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"strings.d.ts","sourceRoot":"","sources":["../../src/helpers/strings.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAEH;;;;;;;;;;;;GAYG;AACH,OAAO,sBAAsB,CAAC;AAE9B,eAAO,MAAM,UAAU,GAAI,KAAK,MAAM,KAAG,MAExC,CAAA;AAED;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,YAAY,GAAI,KAAK,MAAM,KAAG,MAE1C,CAAA;AAED;;GAEG;AACH,KAAK,gBAAgB,GAAG;IACtB,6DAA6D;IAC7D,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,qEAAqE;IACrE,iBAAiB,CAAC,EAAE,OAAO,CAAA;CAC5B,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AACH,eAAO,MAAM,SAAS,GAAI,KAAK,MAAM,EAAE,oCAAmC,gBAAqB,KAAG,MA+BjG,CAAA"}
|
|
@@ -90,6 +90,7 @@ exports.decapitalize = decapitalize;
|
|
|
90
90
|
* @param str - The string to convert to camelCase
|
|
91
91
|
* @param options - Options to control the conversion
|
|
92
92
|
* @param options.upperFirst - Whether to capitalize the first character (default: false)
|
|
93
|
+
* @param options.retainUnderscores - Whether to retain underscores in place (default: false)
|
|
93
94
|
* @returns The camelCase version of the input string
|
|
94
95
|
*
|
|
95
96
|
* @example Basic usage
|
|
@@ -112,17 +113,39 @@ exports.decapitalize = decapitalize;
|
|
|
112
113
|
* camelCase('XML_HTTP_Request'); // 'xmlHttpRequest'
|
|
113
114
|
* camelCase('2fa-enabled'); // '2faEnabled'
|
|
114
115
|
* ```
|
|
116
|
+
*
|
|
117
|
+
* @example Retaining underscores (retainUnderscores)
|
|
118
|
+
* ```typescript
|
|
119
|
+
* camelCase('user_name', { retainUnderscores: true }); // 'user_name'
|
|
120
|
+
* camelCase('api-key_token', { retainUnderscores: true }); // 'apiKey_token'
|
|
121
|
+
* camelCase('get_user_by_id', { retainUnderscores: true }); // 'get_user_by_id'
|
|
122
|
+
* camelCase('user_profile', { upperFirst: true, retainUnderscores: true }); // 'User_profile'
|
|
123
|
+
* ```
|
|
115
124
|
*/
|
|
116
|
-
const camelCase = (str, { upperFirst } = {}) => {
|
|
117
|
-
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
125
|
+
const camelCase = (str, { upperFirst, retainUnderscores } = {}) => {
|
|
126
|
+
// Check if string starts with a delimiter
|
|
127
|
+
const startsWithDelimiter = retainUnderscores
|
|
128
|
+
? /^[^a-zA-Z0-9_]/.test(str) // Non-alphanumeric except underscore
|
|
129
|
+
: /^[^a-zA-Z0-9]/.test(str); // Non-alphanumeric including underscore
|
|
130
|
+
let isFirstMatch = true;
|
|
131
|
+
// Determine delimiter pattern based on retainUnderscores
|
|
132
|
+
const delimiterPattern = retainUnderscores
|
|
133
|
+
? /[^a-zA-Z0-9_]+([a-zA-Z0-9_]+)/g // Treat underscore as regular character
|
|
134
|
+
: /[^a-zA-Z0-9]+([a-zA-Z0-9]+)/g; // Treat underscore as delimiter
|
|
135
|
+
const camelCased = str.replace(delimiterPattern, (_, matched) => {
|
|
136
|
+
// If string starts with delimiter and this is the first match:
|
|
137
|
+
// - Capitalize only if upperFirst is true
|
|
138
|
+
// Otherwise, always capitalize (word comes after delimiter)
|
|
139
|
+
if (isFirstMatch && startsWithDelimiter && !upperFirst) {
|
|
140
|
+
isFirstMatch = false;
|
|
141
|
+
return matched;
|
|
121
142
|
}
|
|
122
|
-
|
|
123
|
-
return matched;
|
|
143
|
+
isFirstMatch = false;
|
|
144
|
+
return (0, exports.capitalize)(matched);
|
|
124
145
|
});
|
|
125
|
-
|
|
146
|
+
// Clean non-alphanumeric characters (preserving underscores if retainUnderscores)
|
|
147
|
+
const cleanPattern = retainUnderscores ? /[^a-zA-Z0-9_]/g : /[^a-zA-Z0-9]/g;
|
|
148
|
+
const cleaned = camelCased.replace(cleanPattern, '');
|
|
126
149
|
return upperFirst ? (0, exports.capitalize)(cleaned) : cleaned;
|
|
127
150
|
};
|
|
128
151
|
exports.camelCase = camelCase;
|