@brand-map/generator 0.0.0-dev.14 → 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/.oxfmtrc.json +29 -0
- package/.oxlintrc.json +144 -0
- package/README.md +207 -7
- package/bun.lock +47 -19
- package/package.json +15 -20
- package/src/actions/load-templates.ts +58 -0
- package/src/exports.ts +2 -0
- package/src/generator.ts +138 -0
- package/{lib.ts → src/lib.ts} +4 -4
- package/src/renderer-handlebars.ts +27 -0
- package/src/renderer-vento.ts +21 -0
- package/{types.ts → src/types.ts} +6 -4
- package/tsconfig.json +1 -1
- package/TODO.md +0 -2
- package/actions/ai.ts +0 -7
- package/actions/answer.ts +0 -7
- package/actions/blank.ts +0 -55
- package/actions/combine.ts +0 -14
- package/actions/config.ts +0 -7
- package/actions/context/context.ts +0 -44
- package/actions/context.ts +0 -10
- package/actions/echo.ts +0 -19
- package/actions/exports.ts +0 -9
- package/actions/jargal-context.ts +0 -21
- package/actions/jargal-templates.ts +0 -82
- package/actions/jargal-write.ts +0 -46
- package/actions/load-templates.ts +0 -125
- package/actions/modify.ts +0 -7
- package/actions/parallel.ts +0 -16
- package/actions/pipe/pipe.ts +0 -993
- package/actions/pipe.ts +0 -7
- package/actions/prompt.ts +0 -134
- package/actions/render-template.ts +0 -39
- package/actions/run/run.ts +0 -20
- package/actions/select-generator.ts +0 -28
- package/actions/use.ts +0 -9
- package/actions/validate-answers.ts +0 -13
- package/actions/write/write.ts +0 -69
- package/actions/write.ts +0 -51
- package/biome.json +0 -35
- package/exports.ts +0 -8
- package/jargal.ts +0 -181
- package/renderer.ts +0 -100
- package/runner.test.ts +0 -24
- package/runner.ts +0 -99
package/src/generator.ts
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { mkdir } from "fs/promises";
|
|
2
|
+
import { writeFile } from "fs/promises";
|
|
3
|
+
import path, { dirname } from "path";
|
|
4
|
+
|
|
5
|
+
import { toMerged } from "es-toolkit";
|
|
6
|
+
|
|
7
|
+
import { type TemplateData } from "./actions/load-templates";
|
|
8
|
+
import { HandlebarsRenderer } from "./renderer-handlebars";
|
|
9
|
+
import { VentoRenderer } from "./renderer-vento";
|
|
10
|
+
import type { RendererAbstract } from "./types";
|
|
11
|
+
|
|
12
|
+
export class Genrator<const in out Context = { templates: Record<string, TemplateData>; data: unknown; rendered: { path: string; content: string }[] }> {
|
|
13
|
+
#context = {
|
|
14
|
+
templates: {} as Record<string, TemplateData>,
|
|
15
|
+
data: null as unknown,
|
|
16
|
+
rendered: [] as { path: string; content: string }[],
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
#renderer: RendererAbstract;
|
|
20
|
+
|
|
21
|
+
get context(): Context {
|
|
22
|
+
return this.#context as any;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
out: string;
|
|
26
|
+
|
|
27
|
+
constructor(config?: ({ engine?: "vento" } | { engine?: "handlebars" }) & { out?: string; cwd?: string }) {
|
|
28
|
+
this.out = config?.out ?? process.cwd();
|
|
29
|
+
|
|
30
|
+
const engine = config?.engine ?? "vento";
|
|
31
|
+
switch (engine) {
|
|
32
|
+
case "vento":
|
|
33
|
+
this.#renderer = new VentoRenderer();
|
|
34
|
+
break;
|
|
35
|
+
|
|
36
|
+
case "handlebars":
|
|
37
|
+
this.#renderer = new HandlebarsRenderer();
|
|
38
|
+
break;
|
|
39
|
+
|
|
40
|
+
default:
|
|
41
|
+
throw new Error(`Invalid render engine provided: ${engine}`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
addContext<Setter extends (args: Context) => any>(setter: Setter): Genrator<ReturnType<Setter> & Context>;
|
|
46
|
+
addContext<Setter extends Record<string, any>>(setter: Setter): Genrator<Setter & Context>;
|
|
47
|
+
addContext(setter: any) {
|
|
48
|
+
if (typeof setter === "function") {
|
|
49
|
+
const context = setter(this.#context);
|
|
50
|
+
this.#context = toMerged(this.#context as any, context);
|
|
51
|
+
return this as any;
|
|
52
|
+
}
|
|
53
|
+
this.#context = toMerged(this.#context as any, setter);
|
|
54
|
+
return this as any;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async render(
|
|
58
|
+
renderFn?: (ctx: Context) => {
|
|
59
|
+
path: string;
|
|
60
|
+
content: string;
|
|
61
|
+
}[],
|
|
62
|
+
): Promise<void> {
|
|
63
|
+
switch (true) {
|
|
64
|
+
case typeof renderFn === "function":
|
|
65
|
+
{
|
|
66
|
+
// @ts-expect-error: hard to type
|
|
67
|
+
const result = renderFn(this.#context);
|
|
68
|
+
this.#context.rendered.push(...result);
|
|
69
|
+
}
|
|
70
|
+
break;
|
|
71
|
+
|
|
72
|
+
case !this.#context.data:
|
|
73
|
+
{
|
|
74
|
+
for (const template of Object.values(this.#context.templates)) {
|
|
75
|
+
const content = await this.#renderer.renderString({ template: template.content, data: {} });
|
|
76
|
+
const savePath = await this.#renderer.renderString({ template: template.relative, data: {} });
|
|
77
|
+
|
|
78
|
+
this.#context.rendered.push({ content, path: path.resolve(this.out, savePath) });
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
break;
|
|
82
|
+
|
|
83
|
+
case Array.isArray(this.#context.data):
|
|
84
|
+
{
|
|
85
|
+
for (const data of this.#context.data) {
|
|
86
|
+
for (const template of Object.values(this.#context.templates)) {
|
|
87
|
+
const content = await this.#renderer.renderString({ template: template.content, data });
|
|
88
|
+
const savePath = await this.#renderer.renderString({ template: template.relative, data });
|
|
89
|
+
|
|
90
|
+
this.#context.rendered.push({ content, path: path.resolve(this.out, savePath) });
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
break;
|
|
95
|
+
|
|
96
|
+
case typeof this.#context.data === "object":
|
|
97
|
+
{
|
|
98
|
+
for (const template of Object.values(this.#context.templates)) {
|
|
99
|
+
const content = await this.#renderer.renderString({ template: template.content, data: this.#context.data });
|
|
100
|
+
const savePath = await this.#renderer.renderString({ template: template.relative, data: this.#context.data });
|
|
101
|
+
|
|
102
|
+
this.#context.rendered.push({ content, path: path.resolve(this.out, savePath) });
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
break;
|
|
106
|
+
|
|
107
|
+
default: {
|
|
108
|
+
console.log(
|
|
109
|
+
"Data to render in `.context.data` is not object or array and you didnt provide `renderFn` to `render()` call. So I cant render your stuff...",
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async write(
|
|
116
|
+
callback?: (params: { path: string; content: string; writeFn: (params: { path: string; content: string }) => Promise<void> }) => void | Promise<void>,
|
|
117
|
+
): Promise<void> {
|
|
118
|
+
if (typeof callback === "function") {
|
|
119
|
+
for (const renderConfig of this.#context.rendered) {
|
|
120
|
+
await callback({ ...renderConfig, writeFn });
|
|
121
|
+
}
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
for (const renderConfig of this.#context.rendered) {
|
|
126
|
+
await writeFn(renderConfig);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async function writeFn({ path, content }: { path: string; content: string }): Promise<void> {
|
|
132
|
+
try {
|
|
133
|
+
await mkdir(dirname(path), { recursive: true });
|
|
134
|
+
} catch (error) {
|
|
135
|
+
console.error({ path, content, error });
|
|
136
|
+
}
|
|
137
|
+
await writeFile(path, new TextEncoder().encode(content), {});
|
|
138
|
+
}
|
package/{lib.ts → src/lib.ts}
RENAMED
|
@@ -12,12 +12,12 @@ import {
|
|
|
12
12
|
snakeCase,
|
|
13
13
|
trainCase,
|
|
14
14
|
} from "change-case";
|
|
15
|
-
import { titleCase } from "title-case";
|
|
16
15
|
import { deburr, lowerFirst, startCase, trim } from "es-toolkit";
|
|
16
|
+
import { titleCase } from "title-case";
|
|
17
17
|
|
|
18
|
-
import type { ActionHooks, ActionHooksChanges, ActionHooksFailures
|
|
18
|
+
import type { TextHelpers, ActionHooks, ActionHooksChanges, ActionHooksFailures } from "./types";
|
|
19
19
|
|
|
20
|
-
export const textHelpers
|
|
20
|
+
export const textHelpers = {
|
|
21
21
|
upperCase: (str) => str.toUpperCase(),
|
|
22
22
|
lowerCase: (str) => str.toLowerCase(),
|
|
23
23
|
camelCase: (str) => camelCase(str),
|
|
@@ -40,7 +40,7 @@ export const textHelpers: TextHelpers = {
|
|
|
40
40
|
pascalSnakeCase: (str) => pascalSnakeCase(str),
|
|
41
41
|
trainCase: (str) => trainCase(str),
|
|
42
42
|
trim: (str) => trim(str),
|
|
43
|
-
};
|
|
43
|
+
} satisfies TextHelpers;
|
|
44
44
|
|
|
45
45
|
export class Hooks implements ActionHooks {
|
|
46
46
|
onComment(message: string) {
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import handlebars from "handlebars";
|
|
2
|
+
|
|
3
|
+
import { textHelpers, utilHelpers } from "./lib";
|
|
4
|
+
import { RendererAbstract, type Helpers, type Partials } from "./types";
|
|
5
|
+
|
|
6
|
+
export class HandlebarsRenderer extends RendererAbstract {
|
|
7
|
+
#partials: Partials = {};
|
|
8
|
+
#helpers: Helpers = { ...textHelpers, ...utilHelpers };
|
|
9
|
+
|
|
10
|
+
constructor() {
|
|
11
|
+
super();
|
|
12
|
+
|
|
13
|
+
for (const [name, helper] of Object.entries(this.#helpers)) {
|
|
14
|
+
handlebars.registerHelper(name, helper);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
for (const [name, partial] of Object.entries(this.#partials)) {
|
|
18
|
+
handlebars.registerPartial(name, partial);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public override renderString(params: { template: string; data: Record<string, unknown> }): Promise<string> {
|
|
23
|
+
const compiled = handlebars.compile(params.template);
|
|
24
|
+
|
|
25
|
+
return Promise.resolve(compiled(params.data));
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import vento from "ventojs";
|
|
2
|
+
|
|
3
|
+
import { textHelpers } from "./lib";
|
|
4
|
+
import { RendererAbstract } from "./types";
|
|
5
|
+
|
|
6
|
+
export class VentoRenderer extends RendererAbstract {
|
|
7
|
+
#env = vento();
|
|
8
|
+
|
|
9
|
+
constructor() {
|
|
10
|
+
super();
|
|
11
|
+
|
|
12
|
+
for (const [key, fn] of Object.entries(textHelpers)) {
|
|
13
|
+
Object.assign(this.#env.filters, { [key]: fn });
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
public override async renderString(params: { template: string; data: Record<string, unknown> }): Promise<string> {
|
|
18
|
+
const result = await this.#env.runString(params.template, params.data);
|
|
19
|
+
return result.content;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
export abstract class RendererAbstract {
|
|
2
|
+
public abstract renderString(params: { template: string; data: object }): Promise<string>;
|
|
3
|
+
}
|
|
2
4
|
|
|
3
5
|
export interface Config {
|
|
4
6
|
generators: GeneratorConfig[];
|
|
@@ -15,20 +17,20 @@ export type DeepReadonly<T> = {
|
|
|
15
17
|
|
|
16
18
|
export interface GeneratorParams {
|
|
17
19
|
context: Context;
|
|
18
|
-
renderer:
|
|
20
|
+
renderer: RendererAbstract;
|
|
19
21
|
generator: GeneratorConfig;
|
|
20
22
|
hooks?: ActionHooks;
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
export interface ActionParams<Ctx extends object = {}> {
|
|
24
26
|
context: Context<Ctx>;
|
|
25
|
-
renderer:
|
|
27
|
+
renderer: RendererAbstract;
|
|
26
28
|
hooks?: ActionHooks;
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
export interface ExecuteActionParams<Ctx extends object = {}> {
|
|
30
32
|
context: Context<Ctx>;
|
|
31
|
-
renderer:
|
|
33
|
+
renderer: RendererAbstract;
|
|
32
34
|
action: Action;
|
|
33
35
|
}
|
|
34
36
|
|
package/tsconfig.json
CHANGED
package/TODO.md
DELETED
package/actions/ai.ts
DELETED
package/actions/answer.ts
DELETED
package/actions/blank.ts
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import type { Action } from "../types.ts";
|
|
2
|
-
|
|
3
|
-
export const blank1: Action = () => {
|
|
4
|
-
console.log("blank1:", "1");
|
|
5
|
-
|
|
6
|
-
return function execute(_params) {
|
|
7
|
-
console.log("blank1:", "2");
|
|
8
|
-
|
|
9
|
-
return function action() {
|
|
10
|
-
console.log("blank1:", "3");
|
|
11
|
-
};
|
|
12
|
-
};
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
export const blank2: Action = () => {
|
|
16
|
-
console.log("blank2:", 1);
|
|
17
|
-
|
|
18
|
-
return function execute(_params) {
|
|
19
|
-
console.log("blank2:", 2);
|
|
20
|
-
|
|
21
|
-
return [
|
|
22
|
-
function action() {
|
|
23
|
-
console.log("blank2:", 3);
|
|
24
|
-
},
|
|
25
|
-
|
|
26
|
-
function action() {
|
|
27
|
-
console.log("blank2:", 4);
|
|
28
|
-
return [() => console.log("blank2:", 4, 1), () => console.log("blank2:", 4, 2), () => console.log("blank2:", 4, 3)];
|
|
29
|
-
},
|
|
30
|
-
];
|
|
31
|
-
};
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
export const blank3: Action = () => {
|
|
35
|
-
console.log("blank3:", 1);
|
|
36
|
-
|
|
37
|
-
return function execute(_params) {
|
|
38
|
-
console.log("blank3:", 2);
|
|
39
|
-
};
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
export function blankRecursive(depth = 10): Action {
|
|
43
|
-
return () => (Math.random() > 0.5 ? recursive(depth) : [recursive(depth)]);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function recursive(depth: number): Action {
|
|
47
|
-
if (depth > 0) {
|
|
48
|
-
return () => {
|
|
49
|
-
console.log(depth);
|
|
50
|
-
return [blank1, blank2, blank3, recursive(depth - 1)];
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return () => console.log(0);
|
|
55
|
-
}
|
package/actions/combine.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { executeAction } from "../runner.ts";
|
|
2
|
-
import type { Action } from "../types.ts";
|
|
3
|
-
|
|
4
|
-
export function combine(...actions: Action[]): Action {
|
|
5
|
-
return async function execute(params) {
|
|
6
|
-
for (const action of actions) {
|
|
7
|
-
await executeAction({
|
|
8
|
-
action,
|
|
9
|
-
context: params.context,
|
|
10
|
-
renderer: params.renderer,
|
|
11
|
-
});
|
|
12
|
-
}
|
|
13
|
-
};
|
|
14
|
-
}
|
package/actions/config.ts
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import type { BaseIssue, BaseAction, ErrorMessage, Context, OutputContext, BaseContext } from "../../types/index.ts";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Context issue interface.
|
|
5
|
-
*/
|
|
6
|
-
export interface ContextIssue extends BaseIssue<unknown> {
|
|
7
|
-
/**
|
|
8
|
-
* The issue kind.
|
|
9
|
-
*/
|
|
10
|
-
readonly kind: "action";
|
|
11
|
-
/**
|
|
12
|
-
* The issue type.
|
|
13
|
-
*/
|
|
14
|
-
readonly type: "string";
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Context schema interface.
|
|
19
|
-
*/
|
|
20
|
-
export interface ContextAction<Config extends Record<string, any> | undefined, Output extends Record<string, any>> extends BaseAction<Config, Output> {
|
|
21
|
-
readonly config: Config;
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* The expected property.
|
|
25
|
-
*/
|
|
26
|
-
readonly expects: "string";
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export type ContextActionConfig = {
|
|
30
|
-
callback: any;
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
// export function context<Ctx extends Record<string, any>>(config?: ContextActionConfig): ContextAction<ContextActionConfig, Ctx>;
|
|
34
|
-
// export function context<Ctx extends undefined = undefined>(config: ContextActionConfig): ContextAction<ContextActionConfig, {}>;
|
|
35
|
-
export function context<Ctx extends BaseContext, R extends BaseContext>(fn: (ctx: Ctx) => OutputContext<R>): BaseAction<Ctx & R> {
|
|
36
|
-
// ContextAction<ContextActionConfig, Ctx>
|
|
37
|
-
return {
|
|
38
|
-
kind: "action",
|
|
39
|
-
"~run"(context) {
|
|
40
|
-
const updated = fn(context);
|
|
41
|
-
return updated as any;
|
|
42
|
-
},
|
|
43
|
-
};
|
|
44
|
-
}
|
package/actions/context.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { merge } from "es-toolkit";
|
|
2
|
-
import { readonly } from "../lib.ts";
|
|
3
|
-
import type { Action, ContextAction } from "../types.ts";
|
|
4
|
-
|
|
5
|
-
export function context(callback: ContextAction): Action {
|
|
6
|
-
return async function execute(params) {
|
|
7
|
-
const newContext = await callback(readonly(params));
|
|
8
|
-
merge(params.context, newContext);
|
|
9
|
-
};
|
|
10
|
-
}
|
package/actions/echo.ts
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import type { Action, ActionParams } from "../types.ts";
|
|
2
|
-
|
|
3
|
-
export function echo(arg?: string | ((params: ActionParams) => void)): Action {
|
|
4
|
-
return function execute(params) {
|
|
5
|
-
switch (typeof arg) {
|
|
6
|
-
case "function":
|
|
7
|
-
arg(params);
|
|
8
|
-
break;
|
|
9
|
-
|
|
10
|
-
case "string":
|
|
11
|
-
console.log(arg);
|
|
12
|
-
break;
|
|
13
|
-
|
|
14
|
-
default:
|
|
15
|
-
console.log("Hello, World!");
|
|
16
|
-
break;
|
|
17
|
-
}
|
|
18
|
-
};
|
|
19
|
-
}
|
package/actions/exports.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
export { context } from "./context.ts";
|
|
2
|
-
export { prompt } from "./prompt.ts";
|
|
3
|
-
export { parallel } from "./parallel.ts";
|
|
4
|
-
export { write } from "./write.ts";
|
|
5
|
-
export { combine } from "./combine.ts";
|
|
6
|
-
export { echo } from "./echo.ts";
|
|
7
|
-
export { validateAnswers } from "./validate-answers.ts";
|
|
8
|
-
export { loadTemplates } from "./load-templates.ts";
|
|
9
|
-
export { renderTemplate } from "./render-template.ts";
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { merge } from "es-toolkit";
|
|
2
|
-
import { readonly } from "../lib.ts";
|
|
3
|
-
import type { Action, ContextAction } from "../types.ts";
|
|
4
|
-
|
|
5
|
-
export async function contextAsync<Callback extends (...arg: any[]) => any>(callback: Callback): Promise<(ctx: Record<string, any>) => ReturnType<Callback>> {
|
|
6
|
-
const newContext = callback();
|
|
7
|
-
|
|
8
|
-
return function execute(ctx) {
|
|
9
|
-
// merge(params.context, newContext);
|
|
10
|
-
return newContext;
|
|
11
|
-
};
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function context<Callback extends (...arg: any[]) => any>(callback: Callback): (ctx: Record<string, any>) => ReturnType<Callback> {
|
|
15
|
-
const newContext = callback();
|
|
16
|
-
|
|
17
|
-
return function execute(ctx) {
|
|
18
|
-
// merge(params.context, newContext);
|
|
19
|
-
return newContext;
|
|
20
|
-
};
|
|
21
|
-
}
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert";
|
|
2
|
-
import { readdir, readFile } from "node:fs/promises";
|
|
3
|
-
import path, { resolve } from "node:path";
|
|
4
|
-
|
|
5
|
-
export type TemplateData = {
|
|
6
|
-
templatePath: string;
|
|
7
|
-
savePath: string;
|
|
8
|
-
templateContent: string;
|
|
9
|
-
metadata?: Record<string, any>;
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
export type TemplatesMap = Record<string, TemplateData>;
|
|
13
|
-
|
|
14
|
-
type Templates<Key extends string> = { templates: Record<Key, TemplatesMap> };
|
|
15
|
-
|
|
16
|
-
export async function templates<Scope extends string | undefined = undefined>(params: {
|
|
17
|
-
path: string;
|
|
18
|
-
scope?: Scope;
|
|
19
|
-
engine?: "handlebars";
|
|
20
|
-
hooks?: {
|
|
21
|
-
onDataReady?: ((path: string, data: TemplateData) => [path: string, data: TemplateData])[];
|
|
22
|
-
};
|
|
23
|
-
}): Promise<(ctx: Record<string, any>) => Templates<Scope extends undefined ? "default" : Scope>> {
|
|
24
|
-
const engine = params?.engine ?? "handlebars";
|
|
25
|
-
const removeExtension = engine === "handlebars" ? ".hbs" : "";
|
|
26
|
-
const scopeKey = params.scope ? params.scope : "default";
|
|
27
|
-
|
|
28
|
-
const record = { [scopeKey]: {} } as Record<string, TemplatesMap>;
|
|
29
|
-
|
|
30
|
-
const resolvedPath = resolve(params.path);
|
|
31
|
-
|
|
32
|
-
for await (const templatePath of walkDir(resolvedPath)) {
|
|
33
|
-
if (path.basename(templatePath).startsWith("_")) {
|
|
34
|
-
continue;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const savePath = path.relative(resolvedPath, templatePath).replace(removeExtension, "");
|
|
38
|
-
|
|
39
|
-
const contentRaw = await readFile(path.resolve(resolvedPath, templatePath));
|
|
40
|
-
const data: TemplateData = {
|
|
41
|
-
templateContent: new TextDecoder().decode(contentRaw),
|
|
42
|
-
templatePath,
|
|
43
|
-
savePath,
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
if (!params.hooks) {
|
|
47
|
-
assert(record[scopeKey]);
|
|
48
|
-
Object.assign(record[scopeKey], { [savePath]: data });
|
|
49
|
-
continue;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (params.hooks?.onDataReady?.length) {
|
|
53
|
-
let path_ = savePath;
|
|
54
|
-
let data_ = data;
|
|
55
|
-
|
|
56
|
-
for (const hook of params.hooks.onDataReady) {
|
|
57
|
-
[path_, data_] = hook(path_, data_);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
assert(record[scopeKey]);
|
|
61
|
-
Object.assign(record[scopeKey], { [path_]: data_ });
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
return function setter(_ctx: Record<string, any>) {
|
|
66
|
-
return { templates: record };
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
async function* walkDir(dir: string): AsyncGenerator<string, void, void> {
|
|
71
|
-
const entries = await readdir(dir, { withFileTypes: true });
|
|
72
|
-
|
|
73
|
-
for (const entry of entries) {
|
|
74
|
-
const templatePath = path.join(dir, entry.name);
|
|
75
|
-
|
|
76
|
-
if (entry.isDirectory()) {
|
|
77
|
-
yield* walkDir(templatePath);
|
|
78
|
-
} else if (entry.isFile()) {
|
|
79
|
-
yield templatePath;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
}
|
package/actions/jargal-write.ts
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { access, mkdir, rm, writeFile } from "node:fs/promises";
|
|
2
|
-
|
|
3
|
-
import assert from "node:assert";
|
|
4
|
-
import { dirname } from "node:path";
|
|
5
|
-
import type { TemplateData } from "./jargal-templates";
|
|
6
|
-
|
|
7
|
-
export type WriteActionConfig = {
|
|
8
|
-
destination: string;
|
|
9
|
-
mode?: "force" | "skip-if-exists";
|
|
10
|
-
} & TemplateData;
|
|
11
|
-
|
|
12
|
-
export async function write({ destination, templateContent, mode }: WriteActionConfig): Promise<void> {
|
|
13
|
-
if (!templateContent) {
|
|
14
|
-
return;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
assert(destination, "must provide `destination`");
|
|
18
|
-
assert(templateContent, "must provide `templateContent`");
|
|
19
|
-
|
|
20
|
-
await mkdir(dirname(destination), { recursive: true });
|
|
21
|
-
|
|
22
|
-
let doesExist = await fileExists(destination);
|
|
23
|
-
|
|
24
|
-
if (doesExist && mode === "force") {
|
|
25
|
-
await rm(destination, { recursive: true, force: true });
|
|
26
|
-
doesExist = false;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
if (doesExist && mode !== "skip-if-exists") {
|
|
30
|
-
throw `File already exists\n -> ${destination}`;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
if (doesExist && mode === "skip-if-exists") {
|
|
34
|
-
console.info(`[SKIPPED] ${destination} (exists)`);
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
await writeFile(destination, new TextEncoder().encode(templateContent));
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function fileExists(destination: string) {
|
|
42
|
-
return access(destination).then(
|
|
43
|
-
() => true,
|
|
44
|
-
() => false,
|
|
45
|
-
);
|
|
46
|
-
}
|