@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 ADDED
@@ -0,0 +1,15 @@
1
+ # @codeandmoney/jargal
2
+
3
+ To install dependencies:
4
+
5
+ ```bash
6
+ bun install
7
+ ```
8
+
9
+ To run:
10
+
11
+ ```bash
12
+ bun run
13
+ ```
14
+
15
+ This project was created using `bun init` in bun v1.3.0. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime.
package/TODO.md ADDED
@@ -0,0 +1,2 @@
1
+ 1. Write tests
2
+ 2. Document
package/actions/ai.ts ADDED
@@ -0,0 +1,7 @@
1
+ import type { Action } from "../types.ts";
2
+
3
+ export function ai(): Action {
4
+ return function execute(_params) {
5
+ throw new Error("Action is not implemented");
6
+ };
7
+ }
@@ -0,0 +1,7 @@
1
+ import type { Action } from "../types.ts";
2
+
3
+ export function answer(): Action {
4
+ return function execute(_params) {
5
+ throw new Error("Action is not implemented");
6
+ };
7
+ }
@@ -0,0 +1,55 @@
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
+ }
@@ -0,0 +1,10 @@
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({ action, context: params.context, renderer: params.renderer });
8
+ }
9
+ };
10
+ }
@@ -0,0 +1,7 @@
1
+ import type { Action } from "../types.ts";
2
+
3
+ export function config(): Action {
4
+ return function execute(_params) {
5
+ throw new Error("Action is not implemented");
6
+ };
7
+ }
@@ -0,0 +1,44 @@
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
+ }
@@ -0,0 +1,10 @@
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
+ }
@@ -0,0 +1,19 @@
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
+ }
@@ -0,0 +1,9 @@
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";
@@ -0,0 +1,21 @@
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
+ }
@@ -0,0 +1,71 @@
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 = { templatePath: string; savePath: string; templateContent: string; metadata?: Record<string, any> };
6
+
7
+ export type TemplatesMap = Record<string, TemplateData>;
8
+
9
+ type Templates<Key extends string> = { templates: Record<Key, TemplatesMap> };
10
+
11
+ export async function templates<Scope extends string | undefined = undefined>(params: {
12
+ path: string;
13
+ scope?: Scope;
14
+ engine?: "handlebars";
15
+ hooks?: { onDataReady?: ((path: string, data: TemplateData) => [path: string, data: TemplateData])[] };
16
+ }): Promise<(ctx: Record<string, any>) => Templates<Scope extends undefined ? "default" : Scope>> {
17
+ const engine = params?.engine ?? "handlebars";
18
+ const removeExtension = engine === "handlebars" ? ".hbs" : "";
19
+ const scopeKey = params.scope ? params.scope : "default";
20
+
21
+ const record = { [scopeKey]: {} } as Record<string, TemplatesMap>;
22
+
23
+ const resolvedPath = resolve(params.path);
24
+
25
+ for await (const templatePath of walkDir(resolvedPath)) {
26
+ if (path.basename(templatePath).startsWith("_")) {
27
+ continue;
28
+ }
29
+
30
+ const savePath = path.relative(resolvedPath, templatePath).replace(removeExtension, "");
31
+
32
+ const contentRaw = await readFile(path.resolve(resolvedPath, templatePath));
33
+ const data: TemplateData = { templateContent: new TextDecoder().decode(contentRaw), templatePath, savePath };
34
+
35
+ if (!params.hooks) {
36
+ assert(record[scopeKey]);
37
+ Object.assign(record[scopeKey], { [savePath]: data });
38
+ continue;
39
+ }
40
+
41
+ if (params.hooks?.onDataReady?.length) {
42
+ let path_ = savePath;
43
+ let data_ = data;
44
+
45
+ for (const hook of params.hooks.onDataReady) {
46
+ [path_, data_] = hook(path_, data_);
47
+ }
48
+
49
+ assert(record[scopeKey]);
50
+ Object.assign(record[scopeKey], { [path_]: data_ });
51
+ }
52
+ }
53
+
54
+ return function setter(_ctx: Record<string, any>) {
55
+ return { templates: record };
56
+ };
57
+ }
58
+
59
+ async function* walkDir(dir: string): AsyncGenerator<string, void, void> {
60
+ const entries = await readdir(dir, { withFileTypes: true });
61
+
62
+ for (const entry of entries) {
63
+ const templatePath = path.join(dir, entry.name);
64
+
65
+ if (entry.isDirectory()) {
66
+ yield* walkDir(templatePath);
67
+ } else if (entry.isFile()) {
68
+ yield templatePath;
69
+ }
70
+ }
71
+ }
@@ -0,0 +1,46 @@
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
+ }
@@ -0,0 +1,107 @@
1
+ import type { ContextAction } from "../types.ts";
2
+ import { readdir, readFile } from "node:fs/promises";
3
+ import path, { resolve } from "node:path";
4
+
5
+ type TemplateData = { templatePath: string; realativePath: string; templateContent: string };
6
+
7
+ export function loadTemplates(
8
+ templatesPath: string,
9
+ options: {
10
+ scope?: string;
11
+ hooks?: { onDataReady?: ((path: string, data: TemplateData) => [path: string, data: TemplateData])[] };
12
+ },
13
+ ): ContextAction<{ templates: Record<string, Map<string, TemplateData>> }> {
14
+ return async function execute(params) {
15
+ const record = { ...params.context.templates } as any as Record<string, Map<string, TemplateData>>;
16
+
17
+ if (options.scope) {
18
+ Object.assign(record, { [options.scope]: new Map() });
19
+ }
20
+
21
+ for await (const templatePath of walkDir(templatesPath)) {
22
+ const realativePath = path.relative(templatesPath, templatePath);
23
+
24
+ const contentRaw = await readFile(path.resolve(templatesPath, templatePath));
25
+ const data: TemplateData = { templateContent: new TextDecoder().decode(contentRaw), templatePath, realativePath };
26
+
27
+ if (!options.hooks) {
28
+ record[options.scope ? options.scope : "templates"]?.set(realativePath, data);
29
+ continue;
30
+ }
31
+
32
+ if (options.hooks?.onDataReady?.length) {
33
+ let path_ = realativePath;
34
+ let data_ = data;
35
+
36
+ for (const hook of options.hooks.onDataReady) {
37
+ [path_, data_] = hook(path_, data_);
38
+ }
39
+
40
+ record[options.scope ? options.scope : "templates"]?.set(path_, data_);
41
+ }
42
+ }
43
+
44
+ return { templates: record };
45
+ };
46
+ }
47
+
48
+ export async function templates<Scope extends string | undefined = undefined>(params: {
49
+ path: string;
50
+ scope?: Scope;
51
+ hooks?: { onDataReady?: ((path: string, data: TemplateData) => [path: string, data: TemplateData])[] };
52
+ }): Promise<
53
+ (ctx: Record<string, any>) => {
54
+ templates: Record<Scope extends undefined ? "default" : Scope, Map<string, TemplateData>>;
55
+ }
56
+ > {
57
+ let record: undefined | Record<string, Map<string, TemplateData>> = undefined;
58
+
59
+ if (params.scope) {
60
+ record = { [params.scope]: new Map() };
61
+ } else {
62
+ record = { default: new Map() };
63
+ }
64
+
65
+ const resolvedPath = resolve(params.path);
66
+
67
+ for await (const templatePath of walkDir(resolvedPath)) {
68
+ const realativePath = path.relative(resolvedPath, templatePath);
69
+
70
+ const contentRaw = await readFile(path.resolve(resolvedPath, templatePath));
71
+ const data: TemplateData = { templateContent: new TextDecoder().decode(contentRaw), templatePath, realativePath };
72
+
73
+ if (!params.hooks) {
74
+ record[params.scope ? params.scope : "default"]?.set(realativePath, data);
75
+ continue;
76
+ }
77
+
78
+ if (params.hooks?.onDataReady?.length) {
79
+ let path_ = realativePath;
80
+ let data_ = data;
81
+
82
+ for (const hook of params.hooks.onDataReady) {
83
+ [path_, data_] = hook(path_, data_);
84
+ }
85
+
86
+ record[params.scope ? params.scope : "default"]?.set(path_, data_);
87
+ }
88
+ }
89
+
90
+ return function setter(_ctx: Record<string, any>): { templates: Record<string, Map<string, TemplateData>> } {
91
+ return { templates: record };
92
+ };
93
+ }
94
+
95
+ async function* walkDir(dir: string): AsyncGenerator<string, void, void> {
96
+ const entries = await readdir(dir, { withFileTypes: true });
97
+
98
+ for (const entry of entries) {
99
+ const templatePath = path.join(dir, entry.name);
100
+
101
+ if (entry.isDirectory()) {
102
+ yield* walkDir(templatePath);
103
+ } else if (entry.isFile()) {
104
+ yield templatePath;
105
+ }
106
+ }
107
+ }
@@ -0,0 +1,7 @@
1
+ import type { Action } from "../types.ts";
2
+
3
+ export function modify(): Action {
4
+ return function execute(_params) {
5
+ throw new Error("Action is not implemented");
6
+ };
7
+ }
@@ -0,0 +1,8 @@
1
+ import { executeAction } from "../runner.ts";
2
+ import type { Action } from "../types.ts";
3
+
4
+ export function parallel(actions: Action[]): Action {
5
+ return async function execute(params) {
6
+ await Promise.all(actions.map((action) => executeAction({ action, context: params.context, renderer: params.renderer })));
7
+ };
8
+ }