@codeandmoney/jargal 0.0.0-dev.11
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 +15 -0
- package/TODO.md +2 -0
- package/actions/ai.ts +7 -0
- package/actions/answer.ts +7 -0
- package/actions/blank.ts +55 -0
- package/actions/combine.ts +10 -0
- package/actions/config.ts +7 -0
- package/actions/context/context.ts +44 -0
- package/actions/context.ts +10 -0
- package/actions/echo.ts +19 -0
- package/actions/exports.ts +9 -0
- package/actions/jargal-context.ts +21 -0
- package/actions/jargal-templates.ts +71 -0
- package/actions/jargal-write.ts +46 -0
- package/actions/load-templates.ts +107 -0
- package/actions/modify.ts +7 -0
- package/actions/parallel.ts +8 -0
- package/actions/pipe/pipe.ts +993 -0
- package/actions/pipe.ts +7 -0
- package/actions/prompt.ts +134 -0
- package/actions/render-template.ts +29 -0
- package/actions/run/run.ts +20 -0
- package/actions/select-generator.ts +24 -0
- package/actions/use.ts +9 -0
- package/actions/validate-answers.ts +13 -0
- package/actions/write/write.ts +69 -0
- package/actions/write.ts +52 -0
- package/biome.json +35 -0
- package/bun.lock +68 -0
- package/exports.ts +8 -0
- package/jargal.ts +156 -0
- package/lib.ts +101 -0
- package/package.json +39 -0
- package/renderer.ts +100 -0
- package/runner.test.ts +24 -0
- package/runner.ts +99 -0
- package/tsconfig.json +29 -0
- package/types.ts +91 -0
package/actions/pipe.ts
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import enquirer from "enquirer";
|
|
2
|
+
import type { Action } from "../types.ts";
|
|
3
|
+
import { EventEmitter } from "node:events";
|
|
4
|
+
|
|
5
|
+
export function prompt(questions: PromptOptions | PromptOptions[]): Action {
|
|
6
|
+
return async function executePrompts(params) {
|
|
7
|
+
const answers = await enquirer.prompt(questions);
|
|
8
|
+
|
|
9
|
+
if (!params.context.answers) {
|
|
10
|
+
Object.assign(params.context, { answers: {} });
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
for (const [key, value] of Object.entries(answers)) {
|
|
14
|
+
Object.assign(params.context.answers, { [key]: value });
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// NOTE: Stupid enquirer doesn't export types at all!!!
|
|
20
|
+
// So, I, being stupid dev, just copied them to get better inference
|
|
21
|
+
|
|
22
|
+
type PromptTypes =
|
|
23
|
+
| "autocomplete"
|
|
24
|
+
| "editable"
|
|
25
|
+
| "form"
|
|
26
|
+
| "multiselect"
|
|
27
|
+
| "select"
|
|
28
|
+
| "survey"
|
|
29
|
+
| "list"
|
|
30
|
+
| "scale"
|
|
31
|
+
| "confirm"
|
|
32
|
+
| "input"
|
|
33
|
+
| "invisible"
|
|
34
|
+
| "list"
|
|
35
|
+
| "password"
|
|
36
|
+
| "text"
|
|
37
|
+
| "numeral"
|
|
38
|
+
| "snippet"
|
|
39
|
+
| "sort";
|
|
40
|
+
|
|
41
|
+
interface BasePromptOptions {
|
|
42
|
+
name: string | (() => string);
|
|
43
|
+
type: PromptTypes | (() => PromptTypes);
|
|
44
|
+
message: string | (() => string) | (() => Promise<string>);
|
|
45
|
+
prefix?: string;
|
|
46
|
+
initial?: any;
|
|
47
|
+
required?: boolean;
|
|
48
|
+
enabled?: boolean | string;
|
|
49
|
+
disabled?: boolean | string;
|
|
50
|
+
format?(value: string): string | Promise<string>;
|
|
51
|
+
result?(value: string): string | Promise<string>;
|
|
52
|
+
skip?: ((state: object) => boolean | Promise<boolean>) | boolean;
|
|
53
|
+
validate?(value: string): boolean | string | Promise<boolean | string>;
|
|
54
|
+
onSubmit?(name: string, value: any, prompt: BasePrompt): boolean | Promise<boolean>;
|
|
55
|
+
onCancel?(name: string, value: any, prompt: BasePrompt): boolean | Promise<boolean>;
|
|
56
|
+
stdin?: NodeJS.ReadStream;
|
|
57
|
+
stdout?: NodeJS.WriteStream;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
interface Choice {
|
|
61
|
+
name: string;
|
|
62
|
+
message?: string;
|
|
63
|
+
value?: unknown;
|
|
64
|
+
hint?: string;
|
|
65
|
+
role?: string;
|
|
66
|
+
enabled?: boolean;
|
|
67
|
+
disabled?: boolean | string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
interface ArrayPromptOptions extends BasePromptOptions {
|
|
71
|
+
type: "autocomplete" | "editable" | "form" | "multiselect" | "select" | "survey" | "list" | "scale";
|
|
72
|
+
choices: (string | Choice)[];
|
|
73
|
+
maxChoices?: number;
|
|
74
|
+
multiple?: boolean;
|
|
75
|
+
initial?: number;
|
|
76
|
+
delay?: number;
|
|
77
|
+
separator?: boolean;
|
|
78
|
+
sort?: boolean;
|
|
79
|
+
linebreak?: boolean;
|
|
80
|
+
edgeLength?: number;
|
|
81
|
+
align?: "left" | "right";
|
|
82
|
+
scroll?: boolean;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
interface BooleanPromptOptions extends BasePromptOptions {
|
|
86
|
+
type: "confirm";
|
|
87
|
+
initial?: boolean;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
interface StringPromptOptions extends BasePromptOptions {
|
|
91
|
+
type: "input" | "invisible" | "list" | "password" | "text";
|
|
92
|
+
initial?: string;
|
|
93
|
+
multiline?: boolean;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
interface NumberPromptOptions extends BasePromptOptions {
|
|
97
|
+
type: "numeral";
|
|
98
|
+
min?: number;
|
|
99
|
+
max?: number;
|
|
100
|
+
delay?: number;
|
|
101
|
+
float?: boolean;
|
|
102
|
+
round?: boolean;
|
|
103
|
+
major?: number;
|
|
104
|
+
minor?: number;
|
|
105
|
+
initial?: number;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
interface SnippetPromptOptions extends BasePromptOptions {
|
|
109
|
+
type: "snippet";
|
|
110
|
+
newline?: string;
|
|
111
|
+
template?: string;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
interface SortPromptOptions extends BasePromptOptions {
|
|
115
|
+
type: "sort";
|
|
116
|
+
hint?: string;
|
|
117
|
+
drag?: boolean;
|
|
118
|
+
numbered?: boolean;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
type PromptOptions =
|
|
122
|
+
| BasePromptOptions
|
|
123
|
+
| ArrayPromptOptions
|
|
124
|
+
| BooleanPromptOptions
|
|
125
|
+
| StringPromptOptions
|
|
126
|
+
| NumberPromptOptions
|
|
127
|
+
| SnippetPromptOptions
|
|
128
|
+
| SortPromptOptions;
|
|
129
|
+
|
|
130
|
+
declare class BasePrompt extends EventEmitter {
|
|
131
|
+
constructor(options?: PromptOptions);
|
|
132
|
+
render(): void;
|
|
133
|
+
run(): Promise<any>;
|
|
134
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Action, Context } from "../types.ts";
|
|
2
|
+
|
|
3
|
+
export function renderTemplate({
|
|
4
|
+
templatePath,
|
|
5
|
+
template,
|
|
6
|
+
getData,
|
|
7
|
+
save,
|
|
8
|
+
}: {
|
|
9
|
+
template: string;
|
|
10
|
+
templatePath: string;
|
|
11
|
+
getData?: (ctx: Context) => Record<string, unknown>;
|
|
12
|
+
save?: Action<{ templateContent?: string; destination?: string }>;
|
|
13
|
+
}): Action {
|
|
14
|
+
if (!template) {
|
|
15
|
+
return () => undefined;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return function execute(params) {
|
|
19
|
+
const data = getData?.(params.context) ?? params.context;
|
|
20
|
+
|
|
21
|
+
const renderedTemplate = params.renderer.renderString({ template, data });
|
|
22
|
+
|
|
23
|
+
const renderedPath = params.renderer.renderString({ template: templatePath, data });
|
|
24
|
+
|
|
25
|
+
if (save) {
|
|
26
|
+
return save({ ...params, context: { ...params.context, templateContent: renderedTemplate, destination: renderedPath } });
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { BaseIssue, BaseAction, Config, InferIssue, InferOutput } from "../../types/index.ts";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Parses an unknown input based on a schema.
|
|
5
|
+
*
|
|
6
|
+
* @param action The action to be used.
|
|
7
|
+
* @param input The input to be parsed.
|
|
8
|
+
* @param config The run configuration.
|
|
9
|
+
*
|
|
10
|
+
* @returns The parsed input.
|
|
11
|
+
*/
|
|
12
|
+
export function run<const Action extends BaseAction<unknown, unknown>>(action: Action, input: unknown): InferOutput<Action> {
|
|
13
|
+
const context = action["~run"]({ value: input }, {});
|
|
14
|
+
|
|
15
|
+
if (context.issues) {
|
|
16
|
+
throw new Error("msg");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return context.value;
|
|
20
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Action, Config } from "../types.ts";
|
|
2
|
+
import { prompt } from "./prompt.ts";
|
|
3
|
+
import { Renderer } from "../renderer.ts";
|
|
4
|
+
import { runGenerator } from "../runner.ts";
|
|
5
|
+
import assert from "node:assert";
|
|
6
|
+
|
|
7
|
+
export function selectGenerator(config: Config): Action {
|
|
8
|
+
const choices = config.generators.map(({ name, description }) => ({
|
|
9
|
+
name,
|
|
10
|
+
hint: description,
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
return () => [
|
|
14
|
+
prompt([{ type: "select", choices, message: "select", name: "generator" }]),
|
|
15
|
+
|
|
16
|
+
function runSelectedGenerator(params) {
|
|
17
|
+
const generator = config.generators.find((generator) => generator.name === params?.context?.answers?.generator);
|
|
18
|
+
|
|
19
|
+
assert(generator);
|
|
20
|
+
|
|
21
|
+
return runGenerator({ context: { errors: [], answers: {} }, renderer: new Renderer(), generator });
|
|
22
|
+
},
|
|
23
|
+
];
|
|
24
|
+
}
|
package/actions/use.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Action } from "../types.ts";
|
|
2
|
+
|
|
3
|
+
// TODO: implement use in a way, that it can use actions from some repository... Maybe...
|
|
4
|
+
|
|
5
|
+
export function use(): Action {
|
|
6
|
+
return function execute(_params) {
|
|
7
|
+
throw new Error("Action is not implemented");
|
|
8
|
+
};
|
|
9
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Action } from "../types.ts";
|
|
2
|
+
|
|
3
|
+
import * as v from "valibot";
|
|
4
|
+
|
|
5
|
+
const primitiveSchema = v.union([v.pipe(v.string(), v.minLength(1)), v.boolean()]);
|
|
6
|
+
|
|
7
|
+
const defaultSchema = v.record(v.string(), v.union([primitiveSchema, v.array(primitiveSchema)]));
|
|
8
|
+
|
|
9
|
+
export function validateAnswers<Schema extends v.BaseAction<unknown, unknown, v.BaseIssue<unknown>>>(schema?: Schema): Action {
|
|
10
|
+
return function execute(params) {
|
|
11
|
+
v.assert(schema ?? defaultSchema, params.context.answers);
|
|
12
|
+
};
|
|
13
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { BaseIssue, BaseAction, ErrorMessage, Context, OutputContext, BaseContext } from "../../types/index.ts";
|
|
2
|
+
// import { _addIssue, _getStandardProps } from "../../utils/index.ts";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Write issue interface.
|
|
6
|
+
*/
|
|
7
|
+
export interface WriteIssue extends BaseIssue<unknown> {
|
|
8
|
+
/**
|
|
9
|
+
* The issue kind.
|
|
10
|
+
*/
|
|
11
|
+
readonly kind: "action";
|
|
12
|
+
/**
|
|
13
|
+
* The issue type.
|
|
14
|
+
*/
|
|
15
|
+
readonly type: "string";
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Write schema interface.
|
|
20
|
+
*/
|
|
21
|
+
export interface WriteAction<Config extends Record<string, any> | undefined, Output extends Record<string, any>> extends BaseAction<Config, Output> {
|
|
22
|
+
readonly config: Config;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The expected property.
|
|
26
|
+
*/
|
|
27
|
+
readonly expects: "string";
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export type WriteActionConfig = {
|
|
31
|
+
destination?: string;
|
|
32
|
+
templateContent?: string;
|
|
33
|
+
mode?: "force" | "skip-if-exists";
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// /**
|
|
37
|
+
// * Creates a string schema.
|
|
38
|
+
// *
|
|
39
|
+
// * @param config The error config.
|
|
40
|
+
// *
|
|
41
|
+
// * @returns A string schema.
|
|
42
|
+
// */
|
|
43
|
+
|
|
44
|
+
export function write<Ctx extends Record<string, any>>(config?: WriteActionConfig): WriteAction<WriteActionConfig, Ctx>;
|
|
45
|
+
export function write<Ctx extends undefined = undefined>(config: WriteActionConfig): WriteAction<WriteActionConfig, {}>;
|
|
46
|
+
export function write<Ctx extends undefined | Record<string, any> = undefined>(
|
|
47
|
+
config?: WriteActionConfig,
|
|
48
|
+
): WriteAction<WriteActionConfig, Ctx extends undefined ? {} : Ctx> {
|
|
49
|
+
return {
|
|
50
|
+
kind: "action",
|
|
51
|
+
expects: "string",
|
|
52
|
+
|
|
53
|
+
config: config ?? {},
|
|
54
|
+
"~run"(context, config) {
|
|
55
|
+
context.renderer;
|
|
56
|
+
|
|
57
|
+
if (typeof context.value === "string") {
|
|
58
|
+
// @ts-expect-error
|
|
59
|
+
context.typed = true;
|
|
60
|
+
} else {
|
|
61
|
+
// _addIssue(this, "type", context, config);
|
|
62
|
+
}
|
|
63
|
+
// @ts-expect-error
|
|
64
|
+
return context as OutputContext<string, WriteIssue>;
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// hooks?: ActionHooks;
|
package/actions/write.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { access, mkdir, rm, writeFile } from "node:fs/promises";
|
|
2
|
+
|
|
3
|
+
import type { Action } from "../types.ts";
|
|
4
|
+
import assert from "node:assert";
|
|
5
|
+
import { dirname } from "node:path";
|
|
6
|
+
|
|
7
|
+
export type WriteActionConfig = {
|
|
8
|
+
destination?: string;
|
|
9
|
+
templateContent?: string;
|
|
10
|
+
mode?: "force" | "skip-if-exists";
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export function write({
|
|
14
|
+
destination: writeDestination,
|
|
15
|
+
templateContent: writeContent,
|
|
16
|
+
mode,
|
|
17
|
+
}: WriteActionConfig): Action<{ templateContent?: string; destination?: string }> {
|
|
18
|
+
return async function execute({ context: { templateContent: executeContent, destination: executeDestination } }) {
|
|
19
|
+
const destination = executeDestination || writeDestination;
|
|
20
|
+
const templateContent = executeContent || writeContent;
|
|
21
|
+
|
|
22
|
+
assert(destination, "must provide `destination`");
|
|
23
|
+
assert(templateContent, "must provide `templateContent`");
|
|
24
|
+
|
|
25
|
+
await mkdir(dirname(destination), { recursive: true });
|
|
26
|
+
|
|
27
|
+
let doesExist = await fileExists(destination);
|
|
28
|
+
|
|
29
|
+
if (doesExist && mode === "force") {
|
|
30
|
+
await rm(destination, { recursive: true, force: true });
|
|
31
|
+
doesExist = false;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (doesExist && mode !== "skip-if-exists") {
|
|
35
|
+
throw `File already exists\n -> ${destination}`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (doesExist && mode === "skip-if-exists") {
|
|
39
|
+
console.info(`[SKIPPED] ${destination} (exists)`);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
await writeFile(destination, new TextEncoder().encode(templateContent));
|
|
44
|
+
} satisfies Action<{ templateContent?: string; destination?: string }>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function fileExists(destination: string) {
|
|
48
|
+
return access(destination).then(
|
|
49
|
+
() => true,
|
|
50
|
+
() => false,
|
|
51
|
+
);
|
|
52
|
+
}
|
package/biome.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://biomejs.dev/schemas/2.2.5/schema.json",
|
|
3
|
+
"vcs": {
|
|
4
|
+
"enabled": false,
|
|
5
|
+
"clientKind": "git",
|
|
6
|
+
"useIgnoreFile": false
|
|
7
|
+
},
|
|
8
|
+
"files": {
|
|
9
|
+
"ignoreUnknown": false
|
|
10
|
+
},
|
|
11
|
+
"formatter": {
|
|
12
|
+
"enabled": true,
|
|
13
|
+
"indentStyle": "space",
|
|
14
|
+
"lineWidth": 160
|
|
15
|
+
},
|
|
16
|
+
"linter": {
|
|
17
|
+
"enabled": true,
|
|
18
|
+
"rules": {
|
|
19
|
+
"recommended": true
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"javascript": {
|
|
23
|
+
"formatter": {
|
|
24
|
+
"quoteStyle": "double"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"assist": {
|
|
28
|
+
"enabled": true,
|
|
29
|
+
"actions": {
|
|
30
|
+
"source": {
|
|
31
|
+
"organizeImports": "on"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
package/bun.lock
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"lockfileVersion": 1,
|
|
3
|
+
"workspaces": {
|
|
4
|
+
"": {
|
|
5
|
+
"name": "@codeandmoney/jargal",
|
|
6
|
+
"dependencies": {
|
|
7
|
+
"change-case": "^5.4.4",
|
|
8
|
+
"enquirer": "^2.4.1",
|
|
9
|
+
"es-toolkit": "^1.40.0",
|
|
10
|
+
"handlebars": "^4.7.8",
|
|
11
|
+
"title-case": "^4.3.2",
|
|
12
|
+
"valibot": "^1.1.0",
|
|
13
|
+
},
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"@types/bun": "^1.3.0",
|
|
16
|
+
"@types/node": "^24.7.2",
|
|
17
|
+
},
|
|
18
|
+
"peerDependencies": {
|
|
19
|
+
"typescript": "^5",
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
"packages": {
|
|
24
|
+
"@types/bun": ["@types/bun@1.3.0", "", { "dependencies": { "bun-types": "1.3.0" } }, "sha512-+lAGCYjXjip2qY375xX/scJeVRmZ5cY0wyHYyCYxNcdEXrQ4AOe3gACgd4iQ8ksOslJtW4VNxBJ8llUwc3a6AA=="],
|
|
25
|
+
|
|
26
|
+
"@types/node": ["@types/node@24.7.2", "", { "dependencies": { "undici-types": "~7.14.0" } }, "sha512-/NbVmcGTP+lj5oa4yiYxxeBjRivKQ5Ns1eSZeB99ExsEQ6rX5XYU1Zy/gGxY/ilqtD4Etx9mKyrPxZRetiahhA=="],
|
|
27
|
+
|
|
28
|
+
"@types/react": ["@types/react@19.2.2", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA=="],
|
|
29
|
+
|
|
30
|
+
"ansi-colors": ["ansi-colors@4.1.3", "", {}, "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw=="],
|
|
31
|
+
|
|
32
|
+
"ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
|
33
|
+
|
|
34
|
+
"bun-types": ["bun-types@1.3.0", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-u8X0thhx+yJ0KmkxuEo9HAtdfgCBaM/aI9K90VQcQioAmkVp3SG3FkwWGibUFz3WdXAdcsqOcbU40lK7tbHdkQ=="],
|
|
35
|
+
|
|
36
|
+
"change-case": ["change-case@5.4.4", "", {}, "sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w=="],
|
|
37
|
+
|
|
38
|
+
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
|
|
39
|
+
|
|
40
|
+
"enquirer": ["enquirer@2.4.1", "", { "dependencies": { "ansi-colors": "^4.1.1", "strip-ansi": "^6.0.1" } }, "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ=="],
|
|
41
|
+
|
|
42
|
+
"es-toolkit": ["es-toolkit@1.40.0", "", {}, "sha512-8o6w0KFmU0CiIl0/Q/BCEOabF2IJaELM1T2PWj6e8KqzHv1gdx+7JtFnDwOx1kJH/isJ5NwlDG1nCr1HrRF94Q=="],
|
|
43
|
+
|
|
44
|
+
"handlebars": ["handlebars@4.7.8", "", { "dependencies": { "minimist": "^1.2.5", "neo-async": "^2.6.2", "source-map": "^0.6.1", "wordwrap": "^1.0.0" }, "optionalDependencies": { "uglify-js": "^3.1.4" }, "bin": { "handlebars": "bin/handlebars" } }, "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ=="],
|
|
45
|
+
|
|
46
|
+
"minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
|
|
47
|
+
|
|
48
|
+
"neo-async": ["neo-async@2.6.2", "", {}, "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="],
|
|
49
|
+
|
|
50
|
+
"source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
|
|
51
|
+
|
|
52
|
+
"strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
|
53
|
+
|
|
54
|
+
"title-case": ["title-case@4.3.2", "", {}, "sha512-I/nkcBo73mO42Idfv08jhInV61IMb61OdIFxk+B4Gu1oBjWBPOLmhZdsli+oJCVaD+86pYQA93cJfFt224ZFAA=="],
|
|
55
|
+
|
|
56
|
+
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
|
57
|
+
|
|
58
|
+
"uglify-js": ["uglify-js@3.19.3", "", { "bin": { "uglifyjs": "bin/uglifyjs" } }, "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ=="],
|
|
59
|
+
|
|
60
|
+
"undici-types": ["undici-types@7.14.0", "", {}, "sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA=="],
|
|
61
|
+
|
|
62
|
+
"valibot": ["valibot@1.1.0", "", { "peerDependencies": { "typescript": ">=5" }, "optionalPeers": ["typescript"] }, "sha512-Nk8lX30Qhu+9txPYTwM0cFlWLdPFsFr6LblzqIySfbZph9+BFsAHsNvHOymEviUepeIW6KFHzpX8TKhbptBXXw=="],
|
|
63
|
+
|
|
64
|
+
"wordwrap": ["wordwrap@1.0.0", "", {}, "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q=="],
|
|
65
|
+
|
|
66
|
+
"bun-types/@types/node": ["@types/node@24.7.0", "", { "dependencies": { "undici-types": "~7.14.0" } }, "sha512-IbKooQVqUBrlzWTi79E8Fw78l8k1RNtlDDNWsFZs7XonuQSJ8oNYfEeclhprUldXISRMLzBpILuKgPlIxm+/Yw=="],
|
|
67
|
+
}
|
|
68
|
+
}
|
package/exports.ts
ADDED
package/jargal.ts
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { merge, toMerged } from "es-toolkit";
|
|
2
|
+
import { Renderer } from "./renderer";
|
|
3
|
+
import { write } from "./actions/jargal-write";
|
|
4
|
+
import { resolve, dirname, join } from "path";
|
|
5
|
+
import { mkdir } from "fs/promises";
|
|
6
|
+
import { writeFile } from "fs/promises";
|
|
7
|
+
|
|
8
|
+
import { templates, type TemplatesMap } from "./actions/jargal-templates";
|
|
9
|
+
import { context } from "./actions/jargal-context";
|
|
10
|
+
|
|
11
|
+
export { templates, context };
|
|
12
|
+
|
|
13
|
+
export type RenderEntry = { control: "user"; data: any; pathTemplate: string; contentTemplate: string } | { control?: "auto"; data: any; destination: string };
|
|
14
|
+
|
|
15
|
+
export class Jargal<const in out Context> {
|
|
16
|
+
#context = {
|
|
17
|
+
templates: { default: {} },
|
|
18
|
+
renderData: [] as { baseSavePath: string; data: any }[],
|
|
19
|
+
renderedContent: [] as { savePath: string; content: string }[],
|
|
20
|
+
} as Context;
|
|
21
|
+
|
|
22
|
+
#renderer: Renderer;
|
|
23
|
+
|
|
24
|
+
get context() {
|
|
25
|
+
return this.#context;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
constructor() {
|
|
29
|
+
this.#renderer = new Renderer();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
setContext<Setter extends (...args: any) => any>(setter: Setter): Jargal<ReturnType<Setter> & Context>;
|
|
33
|
+
setContext<Setter extends Record<string, any>>(setter: Setter): Jargal<Setter & Context>;
|
|
34
|
+
setContext(setter: any) {
|
|
35
|
+
if (typeof setter === "function") {
|
|
36
|
+
const context = setter(this.#context);
|
|
37
|
+
this.#context = toMerged(this.#context as any, context);
|
|
38
|
+
return this as any;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
this.#context = toMerged(this.#context as any, setter);
|
|
42
|
+
return this as any;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async render(params?: {
|
|
46
|
+
composeRenderData?: (ctx: Context) => RenderEntry[] | Promise<RenderEntry[]>;
|
|
47
|
+
scope?: Context extends { templates: { [key: string]: any } } ? keyof Context["templates"] : "default";
|
|
48
|
+
}): Promise<Jargal<Context & { renderedContent: { savePath: string; content: string }[] }>> {
|
|
49
|
+
const composeData: (ctx: Context) => RenderEntry[] | Promise<RenderEntry[]> =
|
|
50
|
+
params?.composeRenderData ??
|
|
51
|
+
// @ts-expect-error
|
|
52
|
+
((ctx: Context) => ctx.renderData);
|
|
53
|
+
|
|
54
|
+
// @ts-expect-error
|
|
55
|
+
const templatesToRender = this.#context.templates[params?.scope ?? "default"] as TemplatesMap;
|
|
56
|
+
|
|
57
|
+
// console.log({ templatesToRender})
|
|
58
|
+
|
|
59
|
+
const composedData = await composeData(this.#context);
|
|
60
|
+
|
|
61
|
+
// console.log({ composedData})
|
|
62
|
+
|
|
63
|
+
for (const renderEntry of composedData) {
|
|
64
|
+
if (!renderEntry.control || renderEntry.control === "auto") {
|
|
65
|
+
for (const [filename, template] of Object.entries(templatesToRender)) {
|
|
66
|
+
// @ts-expect-error
|
|
67
|
+
this.#context.renderedContent.push({
|
|
68
|
+
savePath: this.#renderer.renderString({ template: join(renderEntry.destination, template.savePath), data: renderEntry.data }),
|
|
69
|
+
content: this.#renderer.renderString({ template: template.templateContent, data: renderEntry.data }),
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (renderEntry.control === "user") {
|
|
77
|
+
// @ts-expect-error
|
|
78
|
+
this.#context.renderedContent.push({
|
|
79
|
+
savePath: this.#renderer.renderString({ template: renderEntry.pathTemplate, data: renderEntry.data }),
|
|
80
|
+
content: this.#renderer.renderString({ template: renderEntry.contentTemplate, data: renderEntry.data }),
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// const entryContentTemplate = renderEntry.contentTemplate;
|
|
88
|
+
|
|
89
|
+
// let renderedContent: string | undefined = undefined;
|
|
90
|
+
// let renderedPath: string | undefined = undefined;
|
|
91
|
+
|
|
92
|
+
// if (renderEntry.contentTemplate) {
|
|
93
|
+
// const templateToRender =
|
|
94
|
+
// // @ts-expect-error
|
|
95
|
+
// (this.#context.templates[params?.scope ?? "default"] as TemplatesMap)[renderEntry.contentTemplate]?.templateContent || renderEntry.contentTemplate;
|
|
96
|
+
|
|
97
|
+
// renderedContent = this.#renderer.renderString({ template: templateToRender, data: renderEntry.data });
|
|
98
|
+
// continue;
|
|
99
|
+
// }
|
|
100
|
+
|
|
101
|
+
// if (renderEntry.pathTemplate) {
|
|
102
|
+
// renderedPath = this.#renderer.renderString({ template: renderEntry.pathTemplate, data: renderEntry.data });
|
|
103
|
+
// continue;
|
|
104
|
+
// }
|
|
105
|
+
|
|
106
|
+
// for (const [filename, template] of Object.entries(templatesToRender)) {
|
|
107
|
+
// // console.log({ renderEntry, template });
|
|
108
|
+
|
|
109
|
+
// // @ts-expect-error
|
|
110
|
+
// this.#context.renderedContent.push({
|
|
111
|
+
// savePath:
|
|
112
|
+
// renderedPath ||
|
|
113
|
+
// this.#renderer.renderString({
|
|
114
|
+
// template: renderEntry.baseSavePath ?? (renderEntry?.fullSavePath ? renderEntry?.fullSavePath : join(renderEntry.baseSavePath, template.savePath)),
|
|
115
|
+
// data: renderEntry.data,
|
|
116
|
+
// }),
|
|
117
|
+
// content:
|
|
118
|
+
// renderedContent || this.#renderer.renderString({ template: renderEntry?.contentTemplate ?? template.templateContent, data: renderEntry.data }),
|
|
119
|
+
// });
|
|
120
|
+
// }
|
|
121
|
+
|
|
122
|
+
return this as any;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async write(
|
|
126
|
+
write?: (params: {
|
|
127
|
+
savePath: string;
|
|
128
|
+
content: string;
|
|
129
|
+
defultWrite: (params: { savePath: string; content: string }) => Promise<void>;
|
|
130
|
+
}) => void | Promise<void>,
|
|
131
|
+
): Promise<void> {
|
|
132
|
+
if (typeof write === "function") {
|
|
133
|
+
// @ts-expect-error
|
|
134
|
+
for (const renderConfig of this.#context.renderedContent as { savePath: string; content: string }[]) {
|
|
135
|
+
await write({ ...renderConfig, defultWrite });
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// console.log({ renderedContent: this.#context.renderedContent})
|
|
142
|
+
// @ts-expect-error
|
|
143
|
+
for (const renderConfig of this.#context.renderedContent as { savePath: string; content: string }[]) {
|
|
144
|
+
defultWrite(renderConfig);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function defultWrite({ savePath, content }: { savePath: string; content: string }): Promise<void> {
|
|
150
|
+
try {
|
|
151
|
+
await mkdir(dirname(savePath), { recursive: true });
|
|
152
|
+
} catch (error) {
|
|
153
|
+
console.error({ savePath, content, error });
|
|
154
|
+
}
|
|
155
|
+
await writeFile(savePath, new TextEncoder().encode(content), {});
|
|
156
|
+
}
|