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
|
@@ -0,0 +1,108 @@
|
|
|
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
|
+
import { HbsPartial } from "./hbs-process-helpers.js";
|
|
8
|
+
|
|
9
|
+
const agent = new https.Agent({ rejectUnauthorized: false });
|
|
10
|
+
|
|
11
|
+
export async function getJson(source: string) {
|
|
12
|
+
if (source.startsWith("http://") || source.startsWith("https://")) {
|
|
13
|
+
let response;
|
|
14
|
+
try {
|
|
15
|
+
response = await fetch(source, { agent });
|
|
16
|
+
} catch (err) {
|
|
17
|
+
throw new Error(`Failed to fetch JSON from URL: ${err}`);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (!response.ok) throw new Error(`HTTP error: ${response.status}`);
|
|
21
|
+
return await response.json();
|
|
22
|
+
} else {
|
|
23
|
+
return JSON.parse(fs.readFileSync(source, "utf8"));
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function getDefaultExport(source: string) {
|
|
28
|
+
if (source.endsWith(".ts")) {
|
|
29
|
+
const tempFile = source.replace(/\.ts$/, ".temp.js");
|
|
30
|
+
await build({
|
|
31
|
+
entryPoints: [source],
|
|
32
|
+
outfile: tempFile,
|
|
33
|
+
bundle: true,
|
|
34
|
+
platform: "node",
|
|
35
|
+
format: "esm",
|
|
36
|
+
packages: "external",
|
|
37
|
+
banner: {
|
|
38
|
+
js: 'import { createRequire as __createRequire } from "module"; const require = __createRequire(import.meta.url);',
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const defaultExport = (await import(pathToFileURL(tempFile).href)).default;
|
|
43
|
+
fs.unlinkSync(tempFile);
|
|
44
|
+
|
|
45
|
+
return defaultExport;
|
|
46
|
+
} else {
|
|
47
|
+
return (await import(pathToFileURL(source).href)).default;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export async function runPreparationScript(fullPath: string | undefined) {
|
|
52
|
+
if (!fullPath) return undefined;
|
|
53
|
+
|
|
54
|
+
const preparationScript = await getDefaultExport(fullPath);
|
|
55
|
+
if (typeof preparationScript === "function") return await preparationScript();
|
|
56
|
+
else
|
|
57
|
+
throw new Error(
|
|
58
|
+
`Failed to run preparation script: ${fullPath}. Default export is not a function.`,
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function getAllHbsPartialsInFolder(folder: string) {
|
|
63
|
+
const partials: HbsPartial[] = [];
|
|
64
|
+
const files = fs.readdirSync(folder);
|
|
65
|
+
|
|
66
|
+
for (const file of files) {
|
|
67
|
+
if (file.endsWith(".hbs")) {
|
|
68
|
+
const partialFullName = path.basename(file, ".hbs");
|
|
69
|
+
if (partialFullName === "template") continue; // skip main template file
|
|
70
|
+
|
|
71
|
+
const partialName = partialFullName.split("_")[2];
|
|
72
|
+
const partialPath = path.join(folder, file);
|
|
73
|
+
|
|
74
|
+
partials.push({
|
|
75
|
+
name: partialName,
|
|
76
|
+
content: fs.readFileSync(partialPath, "utf8"),
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return partials;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function getInputDataPath(hbsFolder: string) {
|
|
85
|
+
const inputPath = path.join(hbsFolder, "input-data.json");
|
|
86
|
+
|
|
87
|
+
return fs.existsSync(inputPath) ? inputPath : undefined;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function getHbsHelpersAndTemplatePaths(hbsFolder: string) {
|
|
91
|
+
const preparationScriptPath = path.join(hbsFolder, "preparation-script.ts");
|
|
92
|
+
const helpersPath = path.join(hbsFolder, "hbs-helpers.ts");
|
|
93
|
+
const templatePath = path.join(hbsFolder, "template.hbs");
|
|
94
|
+
const partials = getAllHbsPartialsInFolder(hbsFolder);
|
|
95
|
+
|
|
96
|
+
if (!fs.existsSync(templatePath)) {
|
|
97
|
+
throw new Error(`Template file not found: ${templatePath}`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
preparationScriptPath: fs.existsSync(preparationScriptPath)
|
|
102
|
+
? preparationScriptPath
|
|
103
|
+
: undefined,
|
|
104
|
+
hbsHelpersPath: fs.existsSync(helpersPath) ? helpersPath : undefined,
|
|
105
|
+
templatePath: templatePath,
|
|
106
|
+
hbsPartials: partials,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import prettier from "prettier";
|
|
4
|
+
import fs from "fs";
|
|
5
|
+
|
|
6
|
+
const jsExtensions = [".ts", ".tsx", ".js", ".jsx"];
|
|
7
|
+
export async function formatJsFileWithPrettier(sourcePath: string) {
|
|
8
|
+
const filePath = path.resolve(sourcePath);
|
|
9
|
+
const source = fs.readFileSync(filePath, "utf8");
|
|
10
|
+
|
|
11
|
+
const options = await prettier.resolveConfig(filePath);
|
|
12
|
+
|
|
13
|
+
const formatted = await prettier.format(source, {
|
|
14
|
+
...options,
|
|
15
|
+
filepath: filePath,
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
if (formatted !== source) {
|
|
19
|
+
fs.writeFileSync(filePath, formatted, "utf8");
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const csExtensions = [".cs"];
|
|
24
|
+
function formatCsFileWithCSharpier(filePath: string) {
|
|
25
|
+
const resolved = path.resolve(filePath);
|
|
26
|
+
|
|
27
|
+
const toolDir = path.join(process.cwd(), ".csharpier");
|
|
28
|
+
|
|
29
|
+
if (!fs.existsSync(toolDir)) {
|
|
30
|
+
fs.mkdirSync(toolDir);
|
|
31
|
+
|
|
32
|
+
execSync("dotnet new tool-manifest", { cwd: toolDir, stdio: "ignore" });
|
|
33
|
+
execSync("dotnet tool install csharpier", {
|
|
34
|
+
cwd: toolDir,
|
|
35
|
+
stdio: "ignore",
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
execSync(`dotnet tool run csharpier format "${resolved}"`, {
|
|
40
|
+
cwd: toolDir,
|
|
41
|
+
stdio: "inherit",
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
enum OutputFileType {
|
|
46
|
+
JavaScript,
|
|
47
|
+
Csharp,
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
interface OutputFileFormatter {
|
|
51
|
+
extensions: string[];
|
|
52
|
+
formatterFunc: (inputFilePath: string) => Promise<void> | void;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const outputFileTypeMap: Record<OutputFileType, OutputFileFormatter> = {
|
|
56
|
+
[OutputFileType.JavaScript]: {
|
|
57
|
+
extensions: jsExtensions,
|
|
58
|
+
formatterFunc: formatJsFileWithPrettier,
|
|
59
|
+
},
|
|
60
|
+
[OutputFileType.Csharp]: {
|
|
61
|
+
extensions: csExtensions,
|
|
62
|
+
formatterFunc: formatCsFileWithCSharpier,
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/*
|
|
67
|
+
* Detects the file type based on extension and formats it with the appropriate formatter.
|
|
68
|
+
* Currently supports JavaScript/TypeScript files with Prettier and C# files with CSharpier.
|
|
69
|
+
*/
|
|
70
|
+
export async function formatSourceFile(filePath: string) {
|
|
71
|
+
const ext = filePath.slice(filePath.lastIndexOf("."));
|
|
72
|
+
|
|
73
|
+
for (const formatter of Object.values(outputFileTypeMap)) {
|
|
74
|
+
if (formatter.extensions.includes(ext)) {
|
|
75
|
+
await formatter.formatterFunc(filePath);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
throw new Error(`Unsupported output file type: ${ext}`);
|
|
81
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import Handlebars from "handlebars";
|
|
3
|
+
import { getDefaultExport } from "./file-helpers.js";
|
|
4
|
+
|
|
5
|
+
export type HbsPartial = {
|
|
6
|
+
name: string;
|
|
7
|
+
content: string;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export async function registerHbsHelpersFromFile(fullPath: string) {
|
|
11
|
+
const hbsHelpers = await getDefaultExport(fullPath);
|
|
12
|
+
|
|
13
|
+
for (const [name, value] of Object.entries(hbsHelpers)) {
|
|
14
|
+
if (typeof value === "function") {
|
|
15
|
+
Handlebars.registerHelper(name, value as Handlebars.HelperDelegate);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function registerHbsPartials(partials: HbsPartial[]) {
|
|
21
|
+
for (const partial of partials) {
|
|
22
|
+
Handlebars.registerPartial(partial.name, partial.content);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function compileHbsTemplateAndWriteToFile(
|
|
27
|
+
templatePath: string,
|
|
28
|
+
inputObject: any,
|
|
29
|
+
outputFilePath: string,
|
|
30
|
+
) {
|
|
31
|
+
const template = fs.readFileSync(templatePath, "utf8");
|
|
32
|
+
const compiled = Handlebars.compile(template);
|
|
33
|
+
|
|
34
|
+
const result = compiled(inputObject);
|
|
35
|
+
fs.writeFileSync(outputFilePath, result);
|
|
36
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import {
|
|
2
|
+
hbsMagicCliUsage,
|
|
3
|
+
hbsMagicHelp,
|
|
4
|
+
inputDataNotFound,
|
|
5
|
+
multipleSourcesDetected,
|
|
6
|
+
positionalArgumentsNotAllowed,
|
|
7
|
+
} from "./cli-messages.js";
|
|
8
|
+
import { getJson } from "./file-helpers.js";
|
|
9
|
+
|
|
10
|
+
type ParsedArgs = {
|
|
11
|
+
[key: string]: string | boolean | string[];
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export function extractArguments(process: NodeJS.Process, required: string[]) {
|
|
15
|
+
const raw = process.argv.slice(2);
|
|
16
|
+
|
|
17
|
+
if (raw.length === 1 && raw[0] === "--help") {
|
|
18
|
+
console.log(hbsMagicHelp);
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const args: ParsedArgs = {};
|
|
23
|
+
|
|
24
|
+
for (const arg of raw) {
|
|
25
|
+
if (arg.startsWith("--")) {
|
|
26
|
+
const [key, value] = arg.slice(2).split("=");
|
|
27
|
+
|
|
28
|
+
args[key] = value ?? true;
|
|
29
|
+
} else {
|
|
30
|
+
throw new Error(positionalArgumentsNotAllowed);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
for (const req of required) {
|
|
35
|
+
if (!(req in args)) {
|
|
36
|
+
console.error(`Missing required argument: --${req}`);
|
|
37
|
+
console.error(hbsMagicCliUsage);
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return args;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function extractFlag(
|
|
46
|
+
process: NodeJS.Process,
|
|
47
|
+
flagName: string,
|
|
48
|
+
): boolean {
|
|
49
|
+
const arg = process.argv.find((a) => a.startsWith(`--${flagName}`));
|
|
50
|
+
return !!arg;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function extractArgument(process: NodeJS.Process, argumentName: string) {
|
|
54
|
+
const arg = process.argv.find((a) => a.startsWith(`--${argumentName}=`));
|
|
55
|
+
return arg?.split("=")[1] ?? undefined;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export async function getInputObject(
|
|
59
|
+
externalJson: string | undefined,
|
|
60
|
+
preparationScriptResult: any | undefined,
|
|
61
|
+
inputPath: string | undefined,
|
|
62
|
+
) {
|
|
63
|
+
if (!externalJson && !preparationScriptResult && !inputPath)
|
|
64
|
+
throw new Error(inputDataNotFound);
|
|
65
|
+
|
|
66
|
+
const sourcesCount =
|
|
67
|
+
(externalJson ? 1 : 0) +
|
|
68
|
+
(preparationScriptResult ? 1 : 0) +
|
|
69
|
+
(inputPath ? 1 : 0);
|
|
70
|
+
|
|
71
|
+
if (sourcesCount > 1) console.warn(multipleSourcesDetected);
|
|
72
|
+
|
|
73
|
+
if (externalJson) return await getJson(externalJson);
|
|
74
|
+
|
|
75
|
+
if (preparationScriptResult) return preparationScriptResult;
|
|
76
|
+
|
|
77
|
+
if (inputPath) return await getJson(inputPath);
|
|
78
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"outDir": "dist",
|
|
7
|
+
"rootDir": ".",
|
|
8
|
+
"esModuleInterop": true,
|
|
9
|
+
"forceConsistentCasingInFileNames": true,
|
|
10
|
+
"strict": true,
|
|
11
|
+
"skipLibCheck": true
|
|
12
|
+
},
|
|
13
|
+
"include": ["src/**/*", "bin/**/*"]
|
|
14
|
+
}
|