hbs-magic 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 +21 -0
- package/README.md +386 -0
- package/bin/cli.ts +48 -0
- package/dist/bin/cli.js +25 -0
- package/dist/src/cli-messages.js +84 -0
- package/dist/src/file-helpers.js +94 -0
- package/dist/src/formatting-helpers.js +63 -0
- package/dist/src/hbs-process-helpers.js +22 -0
- package/dist/src/process-helpers.js +50 -0
- package/examples/advanced_csharp-url-helpers/Result.gen.cs +455 -0
- package/examples/advanced_csharp-url-helpers/external-input/links.ts +182 -0
- package/examples/advanced_csharp-url-helpers/input/hbs-helpers.ts +93 -0
- package/examples/advanced_csharp-url-helpers/input/input-data.json +244 -0
- package/examples/advanced_csharp-url-helpers/input/preparation-script.ts +175 -0
- package/examples/advanced_csharp-url-helpers/input/template.hbs +44 -0
- package/examples/advanced_csharp-url-helpers/input/template_partial_node.hbs +32 -0
- package/examples/from-api_ts-api-client/Result.gen.ts +189 -0
- package/examples/from-api_ts-api-client/input/hbs-helpers.ts +53 -0
- package/examples/from-api_ts-api-client/input/template.hbs +30 -0
- package/examples/simple_assets-helper/Result.gen.ts +36 -0
- package/examples/simple_assets-helper/external-input/dummy_audio_1.mp3 +0 -0
- package/examples/simple_assets-helper/external-input/dummy_audio_2.mp3 +0 -0
- package/examples/simple_assets-helper/external-input/dummy_audio_3.mp3 +0 -0
- package/examples/simple_assets-helper/input/preparation-script.ts +45 -0
- package/examples/simple_assets-helper/input/template.hbs +31 -0
- package/package.json +52 -0
- package/src/cli-messages.ts +88 -0
- package/src/file-helpers.ts +108 -0
- package/src/formatting-helpers.ts +81 -0
- package/src/hbs-process-helpers.ts +36 -0
- package/src/process-helpers.ts +78 -0
- package/tsconfig.json +14 -0
- package/tsconfig.node.json +8 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ivan Kobtsev
|
|
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,386 @@
|
|
|
1
|
+
# hbs-magic
|
|
2
|
+
|
|
3
|
+
A CLI tool designed to simplify source-code generation with [Handlebars](https://handlebarsjs.com/) templates as much as humanly possible.
|
|
4
|
+
|
|
5
|
+
Point it at a folder with a `.hbs` template, give it some data — and it generates a fully formatted source file. That's it.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
yarn add -D hbs-magic
|
|
11
|
+
```
|
|
12
|
+
Or
|
|
13
|
+
```bash
|
|
14
|
+
npm install -D hbs-magic
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
## Quick Start
|
|
19
|
+
|
|
20
|
+
### 1. Create an input folder with a template and some data
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
my-gen/
|
|
24
|
+
template.hbs
|
|
25
|
+
input-data.json
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**`input-data.json`**
|
|
29
|
+
```json
|
|
30
|
+
{
|
|
31
|
+
"greeting": "Hello",
|
|
32
|
+
"items": ["Apple", "Banana", "Cherry"]
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
**`template.hbs`**
|
|
37
|
+
```handlebars
|
|
38
|
+
// {{greeting}}, World!
|
|
39
|
+
|
|
40
|
+
{{#each items}}
|
|
41
|
+
export const {{this}} = "{{this}}";
|
|
42
|
+
{{/each}}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### 2. Run hbs-magic
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
hbs-magic --hbs=my-gen --output=result.gen.ts
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### 3. Get a formatted output file ✅
|
|
52
|
+
|
|
53
|
+
**`result.gen.ts`**
|
|
54
|
+
```typescript
|
|
55
|
+
// Hello, World!
|
|
56
|
+
|
|
57
|
+
export const Apple = "Apple";
|
|
58
|
+
export const Banana = "Banana";
|
|
59
|
+
export const Cherry = "Cherry";
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
That's all it takes. No config files, no boilerplate — just a template and data.
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## How It Works
|
|
67
|
+
|
|
68
|
+
Everything lives inside a single **input folder** (the `--hbs` folder). Depending on your needs, it can contain:
|
|
69
|
+
|
|
70
|
+
| File | Purpose |
|
|
71
|
+
|------------------------------|--------------------------------------------------------------|
|
|
72
|
+
| `template.hbs` | The Handlebars template to render |
|
|
73
|
+
| `input-data.json` | Static JSON data for the template |
|
|
74
|
+
| `preparation-script.ts` | A script that programmatically builds the data object |
|
|
75
|
+
| `hbs-helpers.ts` | Custom Handlebars helpers |
|
|
76
|
+
| `template_partial_*.hbs` | Handlebars partials (named by the part after the second `_`) |
|
|
77
|
+
|
|
78
|
+
> If multiple data sources are provided, priority is: `--input` flag → `preparation-script` → `input-data.json`.
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## CLI Usage
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
hbs-magic --hbs=<folder> --output=<file> [--input=<json>] [--disable-formatting]
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
| Flag | Description |
|
|
89
|
+
|---|---|
|
|
90
|
+
| `--hbs` | **(required)** Path to the folder containing your template, data, helpers, etc. |
|
|
91
|
+
| `--output` | **(required)** Path for the generated output file |
|
|
92
|
+
| `--input` | Optional external JSON data source — a local file path **or a URL** |
|
|
93
|
+
| `--disable-formatting` | Skip auto-formatting the output (Prettier for JS/TS, CSharpier for C#) |
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## Data Input — Three Ways
|
|
98
|
+
|
|
99
|
+
### Option A: Static JSON — `input-data.json`
|
|
100
|
+
|
|
101
|
+
The simplest approach. Just drop a JSON file into the input folder:
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
my-gen/
|
|
105
|
+
template.hbs
|
|
106
|
+
input-data.json ← hbs-magic picks this up automatically
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Option B: Preparation Script — `preparation-script.ts`
|
|
110
|
+
|
|
111
|
+
Need to compute data dynamically? Export a default function that returns the data object:
|
|
112
|
+
|
|
113
|
+
```
|
|
114
|
+
my-gen/
|
|
115
|
+
template.hbs
|
|
116
|
+
preparation-script.ts
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
// preparation-script.ts
|
|
121
|
+
export default async function () {
|
|
122
|
+
// Read files, call APIs, parse ASTs — whatever you need
|
|
123
|
+
return {
|
|
124
|
+
items: ["generated", "at", "build-time"],
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
The function can be `async`. hbs-magic will bundle and execute it for you (TypeScript supported out of the box).
|
|
130
|
+
|
|
131
|
+
### Option C: External JSON via `--input` flag
|
|
132
|
+
|
|
133
|
+
Pass a local file or a remote URL:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
# From a local file
|
|
137
|
+
hbs-magic --hbs=my-gen --output=result.ts --input=./data.json
|
|
138
|
+
|
|
139
|
+
# From an API endpoint
|
|
140
|
+
hbs-magic --hbs=my-gen --output=result.ts --input=https://api.example.com/data.json
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## Custom Handlebars Helpers — `hbs-helpers.ts`
|
|
146
|
+
|
|
147
|
+
Export a default object whose keys are helper names and values are helper functions:
|
|
148
|
+
|
|
149
|
+
```
|
|
150
|
+
my-gen/
|
|
151
|
+
template.hbs
|
|
152
|
+
input-data.json
|
|
153
|
+
hbs-helpers.ts ← automatically registered
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
// hbs-helpers.ts
|
|
158
|
+
function shout(text: string) {
|
|
159
|
+
return text.toUpperCase() + "!!!";
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function isEven(index: number) {
|
|
163
|
+
return index % 2 === 0;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export default { shout, isEven };
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Then use them in your template:
|
|
170
|
+
|
|
171
|
+
```handlebars
|
|
172
|
+
{{#each items}}
|
|
173
|
+
{{shout this}}
|
|
174
|
+
{{/each}}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## Handlebars Partials
|
|
180
|
+
|
|
181
|
+
Any `.hbs` file in the input folder (other than `template.hbs`) is registered as a partial. The partial name is taken from the **third segment** of the filename, split by `_`:
|
|
182
|
+
|
|
183
|
+
```
|
|
184
|
+
template_partial_node.hbs → partial name: "node"
|
|
185
|
+
template_partial_row.hbs → partial name: "row"
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Use them in your template with `{{> node}}` or `{{> row}}`.
|
|
189
|
+
|
|
190
|
+
Partials are great for recursive structures — like rendering a nested tree of routes (see the [Advanced Example](#3-advanced--c-url-helpers-with-partials-preparation-script-and-helpers) below).
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## Auto-Formatting
|
|
195
|
+
|
|
196
|
+
hbs-magic automatically formats the generated output file based on its extension:
|
|
197
|
+
|
|
198
|
+
| Extension | Formatter |
|
|
199
|
+
|---|---|
|
|
200
|
+
| `.ts`, `.tsx`, `.js`, `.jsx` | [Prettier](https://prettier.io/) |
|
|
201
|
+
| `.cs` | [CSharpier](https://csharpier.com/) |
|
|
202
|
+
|
|
203
|
+
You can disable it with `--disable-formatting`.
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## Examples
|
|
208
|
+
|
|
209
|
+
### 1. Simple — Assets Helper (Preparation Script)
|
|
210
|
+
|
|
211
|
+
> Scans a folder of audio files and generates a typed TypeScript module with imports, an enum, and a record.
|
|
212
|
+
|
|
213
|
+
**Folder structure:**
|
|
214
|
+
```
|
|
215
|
+
examples/simple_assets-helper/
|
|
216
|
+
external-input/
|
|
217
|
+
dummy_audio_1.mp3
|
|
218
|
+
dummy_audio_2.mp3
|
|
219
|
+
dummy_audio_3.mp3
|
|
220
|
+
input/
|
|
221
|
+
preparation-script.ts
|
|
222
|
+
template.hbs
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
**Run:**
|
|
226
|
+
```bash
|
|
227
|
+
hbs-magic --hbs=examples/simple_assets-helper/input --output=examples/simple_assets-helper/Result.gen.ts
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
**Generated `Result.gen.ts`:**
|
|
231
|
+
```typescript
|
|
232
|
+
import dummy_audio_1SFX from "examples/simple_assets-helper/external-input/dummy_audio_1.mp3";
|
|
233
|
+
import dummy_audio_2SFX from "examples/simple_assets-helper/external-input/dummy_audio_2.mp3";
|
|
234
|
+
import dummy_audio_3SFX from "examples/simple_assets-helper/external-input/dummy_audio_3.mp3";
|
|
235
|
+
|
|
236
|
+
export enum SoundEffect {
|
|
237
|
+
dummy_audio_1 = "dummy_audio_1",
|
|
238
|
+
dummy_audio_2 = "dummy_audio_2",
|
|
239
|
+
dummy_audio_3 = "dummy_audio_3",
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export const SoundEffects: Record<SoundEffect, SoundSource> = {
|
|
243
|
+
[SoundEffect.dummy_audio_1]: {
|
|
244
|
+
src: dummy_audio_1SFX,
|
|
245
|
+
name: SoundEffect.dummy_audio_1,
|
|
246
|
+
},
|
|
247
|
+
[SoundEffect.dummy_audio_2]: {
|
|
248
|
+
src: dummy_audio_2SFX,
|
|
249
|
+
name: SoundEffect.dummy_audio_2,
|
|
250
|
+
},
|
|
251
|
+
[SoundEffect.dummy_audio_3]: {
|
|
252
|
+
src: dummy_audio_3SFX,
|
|
253
|
+
name: SoundEffect.dummy_audio_3,
|
|
254
|
+
},
|
|
255
|
+
};
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
### 2. From API — TypeScript API Client (Helpers + External JSON)
|
|
261
|
+
|
|
262
|
+
> Fetches an OpenAPI spec from a URL and generates TypeScript functions for every endpoint.
|
|
263
|
+
|
|
264
|
+
**Folder structure:**
|
|
265
|
+
```
|
|
266
|
+
examples/from-api_ts-api-client/
|
|
267
|
+
input/
|
|
268
|
+
template.hbs
|
|
269
|
+
hbs-helpers.ts
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
**Run** (note the `--input` pointing to a live API):
|
|
273
|
+
```bash
|
|
274
|
+
hbs-magic \
|
|
275
|
+
--input=https://petstore3.swagger.io/api/v3/openapi.json \
|
|
276
|
+
--hbs=examples/from-api_ts-api-client/input \
|
|
277
|
+
--output=examples/from-api_ts-api-client/Result.gen.ts
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
**Generated `Result.gen.ts`** (excerpt):
|
|
281
|
+
```typescript
|
|
282
|
+
export async function findPetsByStatus(status: string) {
|
|
283
|
+
console.log("Calling get /pet/findByStatus");
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export async function getPetById(petId: number) {
|
|
287
|
+
console.log("Calling get /pet/{petId}");
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
export async function loginUser(username: string, password: string) {
|
|
291
|
+
console.log("Calling get /user/login");
|
|
292
|
+
}
|
|
293
|
+
// ... and every other endpoint from the Swagger Petstore
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
---
|
|
297
|
+
|
|
298
|
+
### 3. Advanced — C# URL Helpers with Partials, Preparation Script, and Helpers
|
|
299
|
+
|
|
300
|
+
> Parses a TypeScript route definition file (using `ts-morph`), extracts route/param info, and generates a full C# helper class with nested static classes, query parameter models, and link-builder methods — using **partials** for recursive rendering.
|
|
301
|
+
|
|
302
|
+
**Folder structure:**
|
|
303
|
+
```
|
|
304
|
+
examples/advanced_csharp-url-helpers/
|
|
305
|
+
external-input/
|
|
306
|
+
links.ts ← source routes file (react-router-url-params)
|
|
307
|
+
input/
|
|
308
|
+
preparation-script.ts ← parses links.ts via ts-morph AST
|
|
309
|
+
hbs-helpers.ts ← TS→C# type converters
|
|
310
|
+
template.hbs ← main template
|
|
311
|
+
template_partial_node.hbs ← recursive partial for nested route groups
|
|
312
|
+
input-data.json ← generated intermediate JSON (by prep script)
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
**Run:**
|
|
316
|
+
```bash
|
|
317
|
+
hbs-magic \
|
|
318
|
+
--hbs=examples/advanced_csharp-url-helpers/input \
|
|
319
|
+
--output=examples/advanced_csharp-url-helpers/Result.gen.cs
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
**Generated `Result.gen.cs`** (excerpt):
|
|
323
|
+
```csharp
|
|
324
|
+
public static class Routes
|
|
325
|
+
{
|
|
326
|
+
public static class Unauthorized
|
|
327
|
+
{
|
|
328
|
+
public static class Login
|
|
329
|
+
{
|
|
330
|
+
public static string Link(string siteUrl, LoginQueryParams queryParams) =>
|
|
331
|
+
siteUrl + "/login" + "?" + queryParams.GetQueryString();
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
public static class ConfirmEmail
|
|
335
|
+
{
|
|
336
|
+
public static string Link(string siteUrl, string userId, string token) =>
|
|
337
|
+
siteUrl + "/confirm-email/:userId/:token"
|
|
338
|
+
.Replace(":userId", userId.ToString())
|
|
339
|
+
.Replace(":token", token.ToString());
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
public static class Authorized
|
|
344
|
+
{
|
|
345
|
+
// ... deeply nested route groups with full C# link builders
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
public class LoginQueryParams : IRouteQueryParams
|
|
350
|
+
{
|
|
351
|
+
public string? Redirect { get; init; }
|
|
352
|
+
|
|
353
|
+
public string GetQueryString()
|
|
354
|
+
{
|
|
355
|
+
var queryParams = new List<string>();
|
|
356
|
+
if (!string.IsNullOrEmpty(Redirect))
|
|
357
|
+
queryParams.Add($"redirect={Redirect}");
|
|
358
|
+
return string.Join("&", queryParams);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
---
|
|
364
|
+
|
|
365
|
+
## Adding to `package.json` scripts
|
|
366
|
+
|
|
367
|
+
A convenient pattern is to add generation commands as npm scripts:
|
|
368
|
+
|
|
369
|
+
```json
|
|
370
|
+
{
|
|
371
|
+
"scripts": {
|
|
372
|
+
"gen:assets": "hbs-magic --hbs=src/gen/assets/input --output=src/generated/assets.gen.ts",
|
|
373
|
+
"gen:api": "hbs-magic --input=https://api.example.com/openapi.json --hbs=src/gen/api/input --output=src/generated/api-client.gen.ts",
|
|
374
|
+
"gen:all": "npm run gen:assets && npm run gen:api"
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
Then simply run `npm run gen:all` to regenerate everything.
|
|
380
|
+
|
|
381
|
+
---
|
|
382
|
+
|
|
383
|
+
## License
|
|
384
|
+
|
|
385
|
+
[MIT](LICENSE) © Ivan Kobtsev
|
|
386
|
+
|
package/bin/cli.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
getHbsHelpersAndTemplatePaths,
|
|
4
|
+
getInputDataPath,
|
|
5
|
+
runPreparationScript,
|
|
6
|
+
} from "../src/file-helpers.js";
|
|
7
|
+
import { formatSourceFile } from "../src/formatting-helpers.js";
|
|
8
|
+
import {
|
|
9
|
+
compileHbsTemplateAndWriteToFile,
|
|
10
|
+
registerHbsHelpersFromFile,
|
|
11
|
+
registerHbsPartials,
|
|
12
|
+
} from "../src/hbs-process-helpers.js";
|
|
13
|
+
import {
|
|
14
|
+
extractArgument,
|
|
15
|
+
extractArguments,
|
|
16
|
+
extractFlag,
|
|
17
|
+
getInputObject,
|
|
18
|
+
} from "../src/process-helpers.js";
|
|
19
|
+
|
|
20
|
+
const args = extractArguments(process, ["hbs", "output"]);
|
|
21
|
+
const hbsFolder = args.hbs as string;
|
|
22
|
+
const outputFile = args.output as string;
|
|
23
|
+
const formatOutput = !extractFlag(process, "disable-formatting");
|
|
24
|
+
const externalJson = extractArgument(process, "input");
|
|
25
|
+
|
|
26
|
+
async function main() {
|
|
27
|
+
const { preparationScriptPath, hbsHelpersPath, hbsPartials, templatePath } =
|
|
28
|
+
getHbsHelpersAndTemplatePaths(hbsFolder);
|
|
29
|
+
|
|
30
|
+
const inputObject = await getInputObject(
|
|
31
|
+
externalJson,
|
|
32
|
+
await runPreparationScript(preparationScriptPath),
|
|
33
|
+
getInputDataPath(hbsFolder),
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
if (hbsHelpersPath) await registerHbsHelpersFromFile(hbsHelpersPath);
|
|
37
|
+
|
|
38
|
+
if (hbsPartials.length > 0) await registerHbsPartials(hbsPartials);
|
|
39
|
+
|
|
40
|
+
await compileHbsTemplateAndWriteToFile(templatePath, inputObject, outputFile);
|
|
41
|
+
|
|
42
|
+
if (formatOutput) await formatSourceFile(outputFile);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
main().catch((err) => {
|
|
46
|
+
console.error(err);
|
|
47
|
+
process.exit(1);
|
|
48
|
+
});
|
package/dist/bin/cli.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { getHbsHelpersAndTemplatePaths, getInputDataPath, runPreparationScript, } from "../src/file-helpers.js";
|
|
3
|
+
import { formatSourceFile } from "../src/formatting-helpers.js";
|
|
4
|
+
import { compileHbsTemplateAndWriteToFile, registerHbsHelpersFromFile, registerHbsPartials, } from "../src/hbs-process-helpers.js";
|
|
5
|
+
import { extractArgument, extractArguments, extractFlag, getInputObject, } from "../src/process-helpers.js";
|
|
6
|
+
const args = extractArguments(process, ["hbs", "output"]);
|
|
7
|
+
const hbsFolder = args.hbs;
|
|
8
|
+
const outputFile = args.output;
|
|
9
|
+
const formatOutput = !extractFlag(process, "disable-formatting");
|
|
10
|
+
const externalJson = extractArgument(process, "input");
|
|
11
|
+
async function main() {
|
|
12
|
+
const { preparationScriptPath, hbsHelpersPath, hbsPartials, templatePath } = getHbsHelpersAndTemplatePaths(hbsFolder);
|
|
13
|
+
const inputObject = await getInputObject(externalJson, await runPreparationScript(preparationScriptPath), getInputDataPath(hbsFolder));
|
|
14
|
+
if (hbsHelpersPath)
|
|
15
|
+
await registerHbsHelpersFromFile(hbsHelpersPath);
|
|
16
|
+
if (hbsPartials.length > 0)
|
|
17
|
+
await registerHbsPartials(hbsPartials);
|
|
18
|
+
await compileHbsTemplateAndWriteToFile(templatePath, inputObject, outputFile);
|
|
19
|
+
if (formatOutput)
|
|
20
|
+
await formatSourceFile(outputFile);
|
|
21
|
+
}
|
|
22
|
+
main().catch((err) => {
|
|
23
|
+
console.error(err);
|
|
24
|
+
process.exit(1);
|
|
25
|
+
});
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
export const inputDataNotFound = `No input data found.
|
|
2
|
+
|
|
3
|
+
Please provide data using one of the following options:
|
|
4
|
+
|
|
5
|
+
1. Use the "--input" flag with a path or URL to a JSON file
|
|
6
|
+
Example: hbs-magic --input=./data.json
|
|
7
|
+
|
|
8
|
+
2. Add a preparation script:
|
|
9
|
+
- File name: preparation-script.ts or preparation-script.js
|
|
10
|
+
- Must export a default function that returns the data object
|
|
11
|
+
- Place it inside your .hbs template folder
|
|
12
|
+
|
|
13
|
+
3. Add a default JSON file:
|
|
14
|
+
- File name: input-data.json
|
|
15
|
+
- Place it inside your .hbs template folder
|
|
16
|
+
|
|
17
|
+
Need help? Run: hbs-magic --help`;
|
|
18
|
+
export const multipleSourcesDetected = `Warning: Multiple input sources detected.
|
|
19
|
+
|
|
20
|
+
hbs-magic will use only one source based on the following priority:
|
|
21
|
+
|
|
22
|
+
1. --input flag
|
|
23
|
+
2. preparation-script.ts/js
|
|
24
|
+
3. input-data.json
|
|
25
|
+
|
|
26
|
+
The highest-priority source will be used. All others will be ignored.
|
|
27
|
+
|
|
28
|
+
Tip: Remove unused inputs to make your setup clearer.`;
|
|
29
|
+
export const positionalArgumentsNotAllowed = `No positional arguments allowed. Use '--key=value' format.`;
|
|
30
|
+
export const hbsMagicCliUsage = `Usage: [--input=<json>] --hbs=<folder> --output=<file> [--disable-formatting]
|
|
31
|
+
--input: Optional JSON input file path. If not provided, the script will look for "input-data.json" in the specified hbs folder.
|
|
32
|
+
--hbs: Required folder containing the Handlebars template, optional helpers, partials, and preparation script.
|
|
33
|
+
--output: Required output file path where the generated source code will be written.
|
|
34
|
+
--disable-formatting: Optional flag to disable auto-formatting of the output file after generation.`;
|
|
35
|
+
export const hbsMagicHelp = `hbs-magic — source-code generation with Handlebars templates
|
|
36
|
+
|
|
37
|
+
Usage:
|
|
38
|
+
hbs-magic --hbs=<folder> --output=<file> [--input=<json>] [--disable-formatting]
|
|
39
|
+
|
|
40
|
+
Required flags:
|
|
41
|
+
--hbs=<folder> Folder containing your Handlebars template and optional
|
|
42
|
+
helpers, partials, and preparation script.
|
|
43
|
+
--output=<file> Path where the generated file will be written.
|
|
44
|
+
|
|
45
|
+
Optional flags:
|
|
46
|
+
--input=<json> External JSON data source — a local file path or a URL.
|
|
47
|
+
--disable-formatting Skip auto-formatting of the generated output.
|
|
48
|
+
--help Show this help message.
|
|
49
|
+
|
|
50
|
+
Input folder structure:
|
|
51
|
+
The --hbs folder can contain any combination of these files:
|
|
52
|
+
|
|
53
|
+
template.hbs (required) The Handlebars template to render.
|
|
54
|
+
input-data.json (optional) Static JSON data for the template.
|
|
55
|
+
preparation-script.ts (optional) Script exporting a default function
|
|
56
|
+
that returns the data object.
|
|
57
|
+
hbs-helpers.ts (optional) Custom Handlebars helpers. Must export
|
|
58
|
+
a default object: { helperName: fn }.
|
|
59
|
+
template_partial_*.hbs (optional) Partials, named by the part after the
|
|
60
|
+
second "_" (e.g. template_partial_row.hbs
|
|
61
|
+
registers a partial called "row").
|
|
62
|
+
|
|
63
|
+
Data source priority:
|
|
64
|
+
If multiple sources are provided, only one is used:
|
|
65
|
+
1. --input flag (highest)
|
|
66
|
+
2. preparation-script
|
|
67
|
+
3. input-data.json (lowest)
|
|
68
|
+
|
|
69
|
+
Auto-formatting:
|
|
70
|
+
Output files are automatically formatted based on extension:
|
|
71
|
+
.ts .tsx .js .jsx → Prettier
|
|
72
|
+
.cs → CSharpier
|
|
73
|
+
|
|
74
|
+
Examples:
|
|
75
|
+
# Simple: static JSON + template
|
|
76
|
+
hbs-magic --hbs=gen/input --output=src/result.gen.ts
|
|
77
|
+
|
|
78
|
+
# From a remote API
|
|
79
|
+
hbs-magic --input=https://api.example.com/openapi.json --hbs=gen/input --output=src/client.gen.ts
|
|
80
|
+
|
|
81
|
+
# With formatting disabled
|
|
82
|
+
hbs-magic --hbs=gen/input --output=src/result.gen.ts --disable-formatting
|
|
83
|
+
|
|
84
|
+
Documentation: https://github.com/IvanKobtsev/hbs-magic#readme`;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import fetch from "node-fetch";
|
|
3
|
+
import https from "https";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import { pathToFileURL } from "url";
|
|
6
|
+
import { build } from "esbuild";
|
|
7
|
+
const agent = new https.Agent({ rejectUnauthorized: false });
|
|
8
|
+
export async function getJson(source) {
|
|
9
|
+
if (source.startsWith("http://") || source.startsWith("https://")) {
|
|
10
|
+
let response;
|
|
11
|
+
try {
|
|
12
|
+
response = await fetch(source, { agent });
|
|
13
|
+
}
|
|
14
|
+
catch (err) {
|
|
15
|
+
throw new Error(`Failed to fetch JSON from URL: ${err}`);
|
|
16
|
+
}
|
|
17
|
+
if (!response.ok)
|
|
18
|
+
throw new Error(`HTTP error: ${response.status}`);
|
|
19
|
+
return await response.json();
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
return JSON.parse(fs.readFileSync(source, "utf8"));
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export async function getDefaultExport(source) {
|
|
26
|
+
if (source.endsWith(".ts")) {
|
|
27
|
+
const tempFile = source.replace(/\.ts$/, ".temp.js");
|
|
28
|
+
await build({
|
|
29
|
+
entryPoints: [source],
|
|
30
|
+
outfile: tempFile,
|
|
31
|
+
bundle: true,
|
|
32
|
+
platform: "node",
|
|
33
|
+
format: "esm",
|
|
34
|
+
packages: "external",
|
|
35
|
+
banner: {
|
|
36
|
+
js: 'import { createRequire as __createRequire } from "module"; const require = __createRequire(import.meta.url);',
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
const defaultExport = (await import(pathToFileURL(tempFile).href)).default;
|
|
40
|
+
fs.unlinkSync(tempFile);
|
|
41
|
+
return defaultExport;
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
return (await import(pathToFileURL(source).href)).default;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
export async function runPreparationScript(fullPath) {
|
|
48
|
+
if (!fullPath)
|
|
49
|
+
return undefined;
|
|
50
|
+
const preparationScript = await getDefaultExport(fullPath);
|
|
51
|
+
if (typeof preparationScript === "function")
|
|
52
|
+
return await preparationScript();
|
|
53
|
+
else
|
|
54
|
+
throw new Error(`Failed to run preparation script: ${fullPath}. Default export is not a function.`);
|
|
55
|
+
}
|
|
56
|
+
export function getAllHbsPartialsInFolder(folder) {
|
|
57
|
+
const partials = [];
|
|
58
|
+
const files = fs.readdirSync(folder);
|
|
59
|
+
for (const file of files) {
|
|
60
|
+
if (file.endsWith(".hbs")) {
|
|
61
|
+
const partialFullName = path.basename(file, ".hbs");
|
|
62
|
+
if (partialFullName === "template")
|
|
63
|
+
continue; // skip main template file
|
|
64
|
+
const partialName = partialFullName.split("_")[2];
|
|
65
|
+
const partialPath = path.join(folder, file);
|
|
66
|
+
partials.push({
|
|
67
|
+
name: partialName,
|
|
68
|
+
content: fs.readFileSync(partialPath, "utf8"),
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return partials;
|
|
73
|
+
}
|
|
74
|
+
export function getInputDataPath(hbsFolder) {
|
|
75
|
+
const inputPath = path.join(hbsFolder, "input-data.json");
|
|
76
|
+
return fs.existsSync(inputPath) ? inputPath : undefined;
|
|
77
|
+
}
|
|
78
|
+
export function getHbsHelpersAndTemplatePaths(hbsFolder) {
|
|
79
|
+
const preparationScriptPath = path.join(hbsFolder, "preparation-script.ts");
|
|
80
|
+
const helpersPath = path.join(hbsFolder, "hbs-helpers.ts");
|
|
81
|
+
const templatePath = path.join(hbsFolder, "template.hbs");
|
|
82
|
+
const partials = getAllHbsPartialsInFolder(hbsFolder);
|
|
83
|
+
if (!fs.existsSync(templatePath)) {
|
|
84
|
+
throw new Error(`Template file not found: ${templatePath}`);
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
preparationScriptPath: fs.existsSync(preparationScriptPath)
|
|
88
|
+
? preparationScriptPath
|
|
89
|
+
: undefined,
|
|
90
|
+
hbsHelpersPath: fs.existsSync(helpersPath) ? helpersPath : undefined,
|
|
91
|
+
templatePath: templatePath,
|
|
92
|
+
hbsPartials: partials,
|
|
93
|
+
};
|
|
94
|
+
}
|