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.
Files changed (33) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +386 -0
  3. package/bin/cli.ts +48 -0
  4. package/dist/bin/cli.js +25 -0
  5. package/dist/src/cli-messages.js +84 -0
  6. package/dist/src/file-helpers.js +94 -0
  7. package/dist/src/formatting-helpers.js +63 -0
  8. package/dist/src/hbs-process-helpers.js +22 -0
  9. package/dist/src/process-helpers.js +50 -0
  10. package/examples/advanced_csharp-url-helpers/Result.gen.cs +455 -0
  11. package/examples/advanced_csharp-url-helpers/external-input/links.ts +182 -0
  12. package/examples/advanced_csharp-url-helpers/input/hbs-helpers.ts +93 -0
  13. package/examples/advanced_csharp-url-helpers/input/input-data.json +244 -0
  14. package/examples/advanced_csharp-url-helpers/input/preparation-script.ts +175 -0
  15. package/examples/advanced_csharp-url-helpers/input/template.hbs +44 -0
  16. package/examples/advanced_csharp-url-helpers/input/template_partial_node.hbs +32 -0
  17. package/examples/from-api_ts-api-client/Result.gen.ts +189 -0
  18. package/examples/from-api_ts-api-client/input/hbs-helpers.ts +53 -0
  19. package/examples/from-api_ts-api-client/input/template.hbs +30 -0
  20. package/examples/simple_assets-helper/Result.gen.ts +36 -0
  21. package/examples/simple_assets-helper/external-input/dummy_audio_1.mp3 +0 -0
  22. package/examples/simple_assets-helper/external-input/dummy_audio_2.mp3 +0 -0
  23. package/examples/simple_assets-helper/external-input/dummy_audio_3.mp3 +0 -0
  24. package/examples/simple_assets-helper/input/preparation-script.ts +45 -0
  25. package/examples/simple_assets-helper/input/template.hbs +31 -0
  26. package/package.json +52 -0
  27. package/src/cli-messages.ts +88 -0
  28. package/src/file-helpers.ts +108 -0
  29. package/src/formatting-helpers.ts +81 -0
  30. package/src/hbs-process-helpers.ts +36 -0
  31. package/src/process-helpers.ts +78 -0
  32. package/tsconfig.json +14 -0
  33. package/tsconfig.node.json +8 -0
@@ -0,0 +1,44 @@
1
+ using System;
2
+ using System.Collections.Generic;
3
+
4
+ namespace ExampleProject.Common.FrontendUrlHelpers;
5
+
6
+ //--------------------------------------------------------------------------------------------------------------
7
+ // <auto-generated>
8
+ // This file was auto-generated by "hbs-magic" CLI.
9
+ // Do not modify this file manually. Instead, modify the template and regenerate the code.
10
+ // </auto-generated>
11
+ //--------------------------------------------------------------------------------------------------------------
12
+
13
+ public static class Routes
14
+ {
15
+ {{#each this.Routes}}
16
+ {{> node}}
17
+ {{/each}}
18
+ }
19
+
20
+ public interface IRouteQueryParams
21
+ {
22
+ public string GetQueryString();
23
+ }
24
+
25
+ {{#each this.QueryParamCollections}}
26
+ public class {{@key}}QueryParams : IRouteQueryParams
27
+ {
28
+ {{#each this}}
29
+ public {{{csType this}}}? {{csFieldName @key}} { get; init; }
30
+ {{/each}}
31
+
32
+ public string GetQueryString()
33
+ {
34
+ var queryParams = new List<string>();
35
+
36
+ {{#each this}}
37
+ if ({{{csNullableCheck @key this}}})
38
+ queryParams.Add($"{{@key}}={ {{{csNullableValue @key this}}} }");
39
+ {{/each}}
40
+
41
+ return string.Join("&", queryParams);
42
+ }
43
+ }
44
+ {{/each}}
@@ -0,0 +1,32 @@
1
+ {{#if (isGroup this)}}
2
+ public static class {{@key}}
3
+ {
4
+ {{#each this}}
5
+ {{#> node }}
6
+ Failover content
7
+ {{/node}}
8
+ {{/each}}
9
+ }
10
+ {{/if}}
11
+
12
+ {{#if (isRoute this)}}
13
+ public static class {{@key}}
14
+ {
15
+ public static string Link(
16
+ string siteUrl
17
+ {{#each routeParams}}
18
+ , {{{csType this}}} {{@key}}
19
+ {{/each}}
20
+ {{#if queryParams}}
21
+ , {{@key}}QueryParams queryParams
22
+ {{/if}}
23
+ ) =>
24
+ siteUrl + "{{route}}"
25
+ {{#each routeParams}}
26
+ .Replace(":{{@key}}", {{@key}}.ToString())
27
+ {{/each}}
28
+ {{#if queryParams}}
29
+ + "?" + queryParams.GetQueryString()
30
+ {{/if}};
31
+ }
32
+ {{/if}}
@@ -0,0 +1,189 @@
1
+ //--------------------------------------------------------------------------------------------------------------
2
+ // <auto-generated>
3
+ // This file was auto-generated by "hbs-magic" CLI.
4
+ // Do not modify this file manually. Instead, modify the template and regenerate the code.
5
+ // </auto-generated>
6
+ //--------------------------------------------------------------------------------------------------------------
7
+
8
+ export async function updatePet() {
9
+ // put /pet
10
+
11
+ console.log("Calling put /pet");
12
+ }
13
+
14
+ export async function addPet() {
15
+ // post /pet
16
+
17
+ console.log("Calling post /pet");
18
+ }
19
+
20
+ export async function findPetsByStatus(status: string) {
21
+ // get /pet/findByStatus
22
+
23
+ const params = {
24
+ status,
25
+ };
26
+ console.log("Params:", params);
27
+
28
+ console.log("Calling get /pet/findByStatus");
29
+ }
30
+
31
+ export async function findPetsByTags(tags: string[]) {
32
+ // get /pet/findByTags
33
+
34
+ const params = {
35
+ tags,
36
+ };
37
+ console.log("Params:", params);
38
+
39
+ console.log("Calling get /pet/findByTags");
40
+ }
41
+
42
+ export async function getPetById(petId: number) {
43
+ // get /pet/{petId}
44
+
45
+ const params = {
46
+ petId,
47
+ };
48
+ console.log("Params:", params);
49
+
50
+ console.log("Calling get /pet/{petId}");
51
+ }
52
+
53
+ export async function updatePetWithForm(
54
+ petId: number,
55
+ name: string,
56
+ status: string,
57
+ ) {
58
+ // post /pet/{petId}
59
+
60
+ const params = {
61
+ petId,
62
+ name,
63
+ status,
64
+ };
65
+ console.log("Params:", params);
66
+
67
+ console.log("Calling post /pet/{petId}");
68
+ }
69
+
70
+ export async function deletePet(api_key: string, petId: number) {
71
+ // delete /pet/{petId}
72
+
73
+ const params = {
74
+ api_key,
75
+ petId,
76
+ };
77
+ console.log("Params:", params);
78
+
79
+ console.log("Calling delete /pet/{petId}");
80
+ }
81
+
82
+ export async function uploadFile(petId: number, additionalMetadata: string) {
83
+ // post /pet/{petId}/uploadImage
84
+
85
+ const params = {
86
+ petId,
87
+ additionalMetadata,
88
+ };
89
+ console.log("Params:", params);
90
+
91
+ console.log("Calling post /pet/{petId}/uploadImage");
92
+ }
93
+
94
+ export async function getInventory() {
95
+ // get /store/inventory
96
+
97
+ console.log("Calling get /store/inventory");
98
+ }
99
+
100
+ export async function placeOrder() {
101
+ // post /store/order
102
+
103
+ console.log("Calling post /store/order");
104
+ }
105
+
106
+ export async function getOrderById(orderId: number) {
107
+ // get /store/order/{orderId}
108
+
109
+ const params = {
110
+ orderId,
111
+ };
112
+ console.log("Params:", params);
113
+
114
+ console.log("Calling get /store/order/{orderId}");
115
+ }
116
+
117
+ export async function deleteOrder(orderId: number) {
118
+ // delete /store/order/{orderId}
119
+
120
+ const params = {
121
+ orderId,
122
+ };
123
+ console.log("Params:", params);
124
+
125
+ console.log("Calling delete /store/order/{orderId}");
126
+ }
127
+
128
+ export async function createUser() {
129
+ // post /user
130
+
131
+ console.log("Calling post /user");
132
+ }
133
+
134
+ export async function createUsersWithListInput() {
135
+ // post /user/createWithList
136
+
137
+ console.log("Calling post /user/createWithList");
138
+ }
139
+
140
+ export async function loginUser(username: string, password: string) {
141
+ // get /user/login
142
+
143
+ const params = {
144
+ username,
145
+ password,
146
+ };
147
+ console.log("Params:", params);
148
+
149
+ console.log("Calling get /user/login");
150
+ }
151
+
152
+ export async function logoutUser() {
153
+ // get /user/logout
154
+
155
+ console.log("Calling get /user/logout");
156
+ }
157
+
158
+ export async function getUserByName(username: string) {
159
+ // get /user/{username}
160
+
161
+ const params = {
162
+ username,
163
+ };
164
+ console.log("Params:", params);
165
+
166
+ console.log("Calling get /user/{username}");
167
+ }
168
+
169
+ export async function updateUser(username: string) {
170
+ // put /user/{username}
171
+
172
+ const params = {
173
+ username,
174
+ };
175
+ console.log("Params:", params);
176
+
177
+ console.log("Calling put /user/{username}");
178
+ }
179
+
180
+ export async function deleteUser(username: string) {
181
+ // delete /user/{username}
182
+
183
+ const params = {
184
+ username,
185
+ };
186
+ console.log("Params:", params);
187
+
188
+ console.log("Calling delete /user/{username}");
189
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Builds a function name from operationId or falls back to method + path.
3
+ */
4
+ function operationName(op: any, method: string, path: string) {
5
+ if (op.operationId) return op.operationId;
6
+
7
+ return (
8
+ method +
9
+ path
10
+ .replace(/[{}]/g, "")
11
+ .split("/")
12
+ .filter(Boolean)
13
+ .map((p, i) => (i === 0 ? p : p.charAt(0).toUpperCase() + p.slice(1)))
14
+ .join("")
15
+ );
16
+ }
17
+
18
+ /**
19
+ * Extracts parameters array safely.
20
+ */
21
+ function getParams(op: any) {
22
+ return op.parameters || [];
23
+ }
24
+
25
+ function hasParams(op: any) {
26
+ return op.parameters && op.parameters.length > 0;
27
+ }
28
+
29
+ /**
30
+ * Converts OpenAPI types to TypeScript types (very minimal).
31
+ */
32
+ function tsType(schema: any): string {
33
+ if (!schema) return "any";
34
+
35
+ switch (schema.type) {
36
+ case "integer":
37
+ case "number":
38
+ return "number";
39
+ case "boolean":
40
+ return "boolean";
41
+ case "array":
42
+ return `${tsType(schema.items)}[]`;
43
+ default:
44
+ return "string";
45
+ }
46
+ }
47
+
48
+ export default {
49
+ operationName,
50
+ getParams,
51
+ hasParams,
52
+ tsType,
53
+ };
@@ -0,0 +1,30 @@
1
+ //--------------------------------------------------------------------------------------------------------------
2
+ // <auto-generated>
3
+ // This file was auto-generated by "hbs-magic" CLI.
4
+ // Do not modify this file manually. Instead, modify the template and regenerate the code.
5
+ // </auto-generated>
6
+ //--------------------------------------------------------------------------------------------------------------
7
+
8
+ {{#each paths}}
9
+ {{#each this}}
10
+ export async function {{operationName this @key @../key}}(
11
+ {{#each (getParams this)}}
12
+ {{name}}: {{tsType schema}}{{#unless @last}},{{/unless}}
13
+ {{/each}}
14
+ ) {
15
+ // {{@key}} {{@../key}}
16
+
17
+ {{#if (hasParams this)}}
18
+ const params = {
19
+ {{#each (getParams this)}}
20
+ {{name}},
21
+ {{/each}}
22
+ };
23
+ console.log("Params:", params);
24
+ {{/if}}
25
+
26
+ console.log("Calling {{@key}} {{@../key}}");
27
+ }
28
+
29
+ {{/each}}
30
+ {{/each}}
@@ -0,0 +1,36 @@
1
+ import dummy_audio_1SFX from "examples/simple_assets-helper/external-input/dummy_audio_1.mp3";
2
+ import dummy_audio_2SFX from "examples/simple_assets-helper/external-input/dummy_audio_2.mp3";
3
+ import dummy_audio_3SFX from "examples/simple_assets-helper/external-input/dummy_audio_3.mp3";
4
+
5
+ //--------------------------------------------------------------------------------------------------------------
6
+ // <auto-generated>
7
+ // This file was auto-generated by "hbs-magic" CLI.
8
+ // Do not modify this file manually. Instead, modify the template and regenerate the code.
9
+ // </auto-generated>
10
+ //--------------------------------------------------------------------------------------------------------------
11
+
12
+ export interface SoundSource {
13
+ src: string;
14
+ name: string;
15
+ }
16
+
17
+ export enum SoundEffect {
18
+ dummy_audio_1 = "dummy_audio_1",
19
+ dummy_audio_2 = "dummy_audio_2",
20
+ dummy_audio_3 = "dummy_audio_3",
21
+ }
22
+
23
+ export const SoundEffects: Record<SoundEffect, SoundSource> = {
24
+ [SoundEffect.dummy_audio_1]: {
25
+ src: dummy_audio_1SFX,
26
+ name: SoundEffect.dummy_audio_1,
27
+ },
28
+ [SoundEffect.dummy_audio_2]: {
29
+ src: dummy_audio_2SFX,
30
+ name: SoundEffect.dummy_audio_2,
31
+ },
32
+ [SoundEffect.dummy_audio_3]: {
33
+ src: dummy_audio_3SFX,
34
+ name: SoundEffect.dummy_audio_3,
35
+ },
36
+ };
@@ -0,0 +1,45 @@
1
+ import { readdir, stat } from "fs/promises";
2
+ import path from "path";
3
+
4
+ async function getFiles(dir: string) {
5
+ let results: string[] = [];
6
+
7
+ const items = await readdir(dir);
8
+
9
+ for (const item of items) {
10
+ const fullPath = path.join(dir, item);
11
+ const stats = await stat(fullPath);
12
+
13
+ if (stats.isDirectory()) {
14
+ results = results.concat(await getFiles(fullPath));
15
+ } else {
16
+ results.push(fullPath);
17
+ }
18
+ }
19
+
20
+ return results;
21
+ }
22
+
23
+ function getNameFromPath(
24
+ path: string,
25
+ includeExtension: boolean = true,
26
+ ): string {
27
+ return includeExtension
28
+ ? (path.split("\\").pop() ?? "")
29
+ : path.slice(path.lastIndexOf("\\") + 1, path.lastIndexOf("."));
30
+ }
31
+
32
+ /**
33
+ * Example of preparation script that reads the contents of some assets folder
34
+ * into an object for Handlebars template input.
35
+ */
36
+ export default async function preparationScript() {
37
+ const sounds = (
38
+ await getFiles("examples/simple_assets-helper/external-input")
39
+ ).map((path) => ({
40
+ name: getNameFromPath(path, false),
41
+ path: path.replace(/\\/g, "/"),
42
+ }));
43
+
44
+ return { soundEffects: sounds };
45
+ }
@@ -0,0 +1,31 @@
1
+ {{#each soundEffects}}
2
+ import {{name}}SFX from "{{path}}";
3
+ {{/each}}
4
+
5
+ //--------------------------------------------------------------------------------------------------------------
6
+ // <auto-generated>
7
+ // This file was auto-generated by "hbs-magic" CLI.
8
+ // Do not modify this file manually. Instead, modify the template and regenerate the code.
9
+ // </auto-generated>
10
+ //--------------------------------------------------------------------------------------------------------------
11
+
12
+ export interface SoundSource {
13
+ src: string;
14
+ name: string;
15
+ }
16
+
17
+ export enum SoundEffect {
18
+ {{#each soundEffects}}
19
+ {{name}} = '{{name}}',
20
+ {{/each}}
21
+ }
22
+
23
+ export const SoundEffects: Record<SoundEffect, SoundSource> = {
24
+ {{#each soundEffects}}
25
+ [SoundEffect.{{name}}]:
26
+ {
27
+ src: {{name}}SFX,
28
+ name: SoundEffect.{{name}},
29
+ },
30
+ {{/each}}
31
+ }
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "hbs-magic",
3
+ "version": "0.1.0",
4
+ "description": "A CLI tool designed to simplify source-generation with Handlebars templates as much as humanly possible.",
5
+ "keywords": [
6
+ "source-code-generation",
7
+ "source-generation",
8
+ "code-generation",
9
+ "handlebars",
10
+ "hbs",
11
+ "cli",
12
+ "typescript"
13
+ ],
14
+ "homepage": "https://github.com/IvanKobtsev/hbs-magic#readme",
15
+ "bugs": {
16
+ "url": "https://github.com/IvanKobtsev/hbs-magic/issues"
17
+ },
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://github.com/IvanKobtsev/hbs-magic.git"
21
+ },
22
+ "license": "MIT",
23
+ "author": "xubarashi",
24
+ "type": "module",
25
+ "main": "./dist/bin/cli.js",
26
+ "bin": {
27
+ "hbs-magic": "./dist/bin/cli.js"
28
+ },
29
+ "scripts": {
30
+ "build": "tsc",
31
+ "render-examples": "tsc && npm run assets-helper && npm run ts-api-client && npm run csharp-url-helpers",
32
+ "csharp-url-helpers": "hbs-magic --hbs=examples/advanced_csharp-url-helpers/input --output=examples/advanced_csharp-url-helpers/Result.gen.cs",
33
+ "ts-api-client": "hbs-magic --input=https://petstore3.swagger.io/api/v3/openapi.json --hbs=examples/from-api_ts-api-client/input --output=examples/from-api_ts-api-client/Result.gen.ts",
34
+ "assets-helper": "hbs-magic --hbs=examples/simple_assets-helper/input --output=examples/simple_assets-helper/Result.gen.ts"
35
+ },
36
+ "devDependencies": {
37
+ "@types/node": "17.0.24",
38
+ "@types/node-fetch": "^2.6.13",
39
+ "react-router-url-params": "^0.9.2",
40
+ "ts-morph": "^27.0.2",
41
+ "typescript": "^5.9.3"
42
+ },
43
+ "dependencies": {
44
+ "concurrently": "^9.2.1",
45
+ "esbuild": "^0.27.4",
46
+ "esbuild-register": "^3.6.0",
47
+ "handlebars": "^4.7.8",
48
+ "node-fetch": "^3.3.2",
49
+ "prettier": "^3.8.1",
50
+ "ts-node": "^10.9.2"
51
+ }
52
+ }
@@ -0,0 +1,88 @@
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
+
19
+ export const multipleSourcesDetected = `Warning: Multiple input sources detected.
20
+
21
+ hbs-magic will use only one source based on the following priority:
22
+
23
+ 1. --input flag
24
+ 2. preparation-script.ts/js
25
+ 3. input-data.json
26
+
27
+ The highest-priority source will be used. All others will be ignored.
28
+
29
+ Tip: Remove unused inputs to make your setup clearer.`;
30
+
31
+ export const positionalArgumentsNotAllowed = `No positional arguments allowed. Use '--key=value' format.`;
32
+
33
+ export const hbsMagicCliUsage = `Usage: [--input=<json>] --hbs=<folder> --output=<file> [--disable-formatting]
34
+ --input: Optional JSON input file path. If not provided, the script will look for "input-data.json" in the specified hbs folder.
35
+ --hbs: Required folder containing the Handlebars template, optional helpers, partials, and preparation script.
36
+ --output: Required output file path where the generated source code will be written.
37
+ --disable-formatting: Optional flag to disable auto-formatting of the output file after generation.`;
38
+
39
+ export const hbsMagicHelp = `hbs-magic — source-code generation with Handlebars templates
40
+
41
+ Usage:
42
+ hbs-magic --hbs=<folder> --output=<file> [--input=<json>] [--disable-formatting]
43
+
44
+ Required flags:
45
+ --hbs=<folder> Folder containing your Handlebars template and optional
46
+ helpers, partials, and preparation script.
47
+ --output=<file> Path where the generated file will be written.
48
+
49
+ Optional flags:
50
+ --input=<json> External JSON data source — a local file path or a URL.
51
+ --disable-formatting Skip auto-formatting of the generated output.
52
+ --help Show this help message.
53
+
54
+ Input folder structure:
55
+ The --hbs folder can contain any combination of these files:
56
+
57
+ template.hbs (required) The Handlebars template to render.
58
+ input-data.json (optional) Static JSON data for the template.
59
+ preparation-script.ts (optional) Script exporting a default function
60
+ that returns the data object.
61
+ hbs-helpers.ts (optional) Custom Handlebars helpers. Must export
62
+ a default object: { helperName: fn }.
63
+ template_partial_*.hbs (optional) Partials, named by the part after the
64
+ second "_" (e.g. template_partial_row.hbs
65
+ registers a partial called "row").
66
+
67
+ Data source priority:
68
+ If multiple sources are provided, only one is used:
69
+ 1. --input flag (highest)
70
+ 2. preparation-script
71
+ 3. input-data.json (lowest)
72
+
73
+ Auto-formatting:
74
+ Output files are automatically formatted based on extension:
75
+ .ts .tsx .js .jsx → Prettier
76
+ .cs → CSharpier
77
+
78
+ Examples:
79
+ # Simple: static JSON + template
80
+ hbs-magic --hbs=gen/input --output=src/result.gen.ts
81
+
82
+ # From a remote API
83
+ hbs-magic --input=https://api.example.com/openapi.json --hbs=gen/input --output=src/client.gen.ts
84
+
85
+ # With formatting disabled
86
+ hbs-magic --hbs=gen/input --output=src/result.gen.ts --disable-formatting
87
+
88
+ Documentation: https://github.com/IvanKobtsev/hbs-magic#readme`;