@codeandmoney/jargal 0.0.0-rc.2 → 0.0.0-rc.5

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.
@@ -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
+ }
@@ -1,10 +1,10 @@
1
1
  import { merge } from "es-toolkit";
2
2
  import { readonly } from "../lib.ts";
3
- import type { Action, Context, ContextAction, DeepReadonly } from "../types.ts";
3
+ import type { Action, ContextAction } from "../types.ts";
4
4
 
5
5
  export function context(callback: ContextAction): Action {
6
6
  return async function execute(params) {
7
- const newContext = await callback(readonly(params.context));
7
+ const newContext = await callback(readonly(params));
8
8
  merge(params.context, newContext);
9
9
  };
10
10
  }
package/actions/echo.ts CHANGED
@@ -1,7 +1,19 @@
1
- import type { Action } from "../types.ts";
1
+ import type { Action, ActionParams } from "../types.ts";
2
2
 
3
- export function echo(message?: string): Action {
4
- return function execute() {
5
- console.log(message ?? "Hello, World!");
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
+ }
6
18
  };
7
19
  }
@@ -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,64 @@
1
+ import { readdir, readFile } from "node:fs/promises";
2
+ import path, { resolve } from "node:path";
3
+
4
+ export type TemplateData = { templatePath: string; savePath: string; templateContent: string; metadata?: Record<string, any> };
5
+
6
+ export type TemplatesMap = Record<string, TemplateData>;
7
+
8
+ type Templates<Key extends string> = { templates: Record<Key, TemplatesMap> };
9
+
10
+ export async function templates<Scope extends string | undefined = undefined>(params: {
11
+ path: string;
12
+ scope?: Scope;
13
+ engine?: "handlebars";
14
+ hooks?: { onDataReady?: ((path: string, data: TemplateData) => [path: string, data: TemplateData])[] };
15
+ }): Promise<(ctx: Record<string, any>) => Templates<Scope extends undefined ? "default" : Scope>> {
16
+ const engine = params?.engine ?? "handlebars";
17
+ const removeExtension = engine === "handlebars" ? ".hbs" : "";
18
+ const scopeKey = params.scope ? params.scope : "default";
19
+
20
+ let record = { [scopeKey]: {} } as Record<string, TemplatesMap>;
21
+
22
+ const resolvedPath = resolve(params.path);
23
+
24
+ for await (const templatePath of walkDir(resolvedPath)) {
25
+ const savePath = path.relative(resolvedPath, templatePath).replace(removeExtension, "");
26
+
27
+ const contentRaw = await readFile(path.resolve(resolvedPath, templatePath));
28
+ const data: TemplateData = { templateContent: new TextDecoder().decode(contentRaw), templatePath, savePath };
29
+
30
+ if (!params.hooks) {
31
+ Object.assign(record[scopeKey]!, { [savePath]: data });
32
+ continue;
33
+ }
34
+
35
+ if (params.hooks?.onDataReady?.length) {
36
+ let path_ = savePath;
37
+ let data_ = data;
38
+
39
+ for (const hook of params.hooks.onDataReady) {
40
+ [path_, data_] = hook(path_, data_);
41
+ }
42
+
43
+ Object.assign(record[scopeKey]!, { [path_]: data_ });
44
+ }
45
+ }
46
+
47
+ return function setter(_ctx: Record<string, any>) {
48
+ return { templates: record };
49
+ };
50
+ }
51
+
52
+ async function* walkDir(dir: string): AsyncGenerator<string, void, void> {
53
+ const entries = await readdir(dir, { withFileTypes: true });
54
+
55
+ for (const entry of entries) {
56
+ const templatePath = path.join(dir, entry.name);
57
+
58
+ if (entry.isDirectory()) {
59
+ yield* walkDir(templatePath);
60
+ } else if (entry.isFile()) {
61
+ yield templatePath;
62
+ }
63
+ }
64
+ }
@@ -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
+ }
@@ -1,24 +1,94 @@
1
1
  import type { ContextAction } from "../types.ts";
2
2
  import { readdir, readFile } from "node:fs/promises";
3
- import path from "node:path";
3
+ import path, { resolve } from "node:path";
4
4
 
5
- export function loadTemplates(templatesPath: string, scope?: string): ContextAction {
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>> }> {
6
14
  return async function execute(params) {
7
- const record = { ...params.context.templates } as Record<string, Map<string, { fullPath: string; realativePath: string; content: string }>>;
15
+ const record = { ...params.context.templates } as any as Record<string, Map<string, TemplateData>>;
8
16
 
9
- if (scope) {
10
- Object.assign(record, { [scope]: new Map() });
17
+ if (options.scope) {
18
+ Object.assign(record, { [options.scope]: new Map() });
11
19
  }
12
20
 
13
- for await (const fullPath of walkDir(templatesPath)) {
14
- const realativePath = path.relative(templatesPath, fullPath);
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
+ }
15
31
 
16
- const contentRaw = await readFile(path.resolve(templatesPath, fullPath));
32
+ if (options.hooks?.onDataReady?.length) {
33
+ let path_ = realativePath;
34
+ let data_ = data;
17
35
 
18
- record[scope ? scope : "templates"]?.set(realativePath, { content: new TextDecoder().decode(contentRaw), fullPath, realativePath });
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
+ }
19
42
  }
20
43
 
21
- return record;
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 };
22
92
  };
23
93
  }
24
94
 
@@ -26,12 +96,12 @@ async function* walkDir(dir: string): AsyncGenerator<string, void, void> {
26
96
  const entries = await readdir(dir, { withFileTypes: true });
27
97
 
28
98
  for (const entry of entries) {
29
- const fullPath = path.join(dir, entry.name);
99
+ const templatePath = path.join(dir, entry.name);
30
100
 
31
101
  if (entry.isDirectory()) {
32
- yield* walkDir(fullPath);
102
+ yield* walkDir(templatePath);
33
103
  } else if (entry.isFile()) {
34
- yield fullPath;
104
+ yield templatePath;
35
105
  }
36
106
  }
37
107
  }
@@ -1,7 +1,7 @@
1
1
  import { executeAction } from "../runner.ts";
2
2
  import type { Action } from "../types.ts";
3
3
 
4
- export function parallel(...actions: Action[]): Action {
4
+ export function parallel(actions: Action[]): Action {
5
5
  return async function execute(params) {
6
6
  await Promise.all(actions.map((action) => executeAction({ action, context: params.context, renderer: params.renderer })));
7
7
  };