@akanjs/cli 2.3.1-rc.3 → 2.3.1-rc.4
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/index.js +9 -2
- package/package.json +2 -2
- package/templates/appSample/common/formatters.ts +32 -0
- package/templates/appSample/common/validators.ts +32 -0
- package/templates/appSample/lib/__scalar/workHistory/workHistory.abstract.ts +47 -0
- package/templates/appSample/lib/__scalar/workHistory/workHistory.constant.ts +38 -0
- package/templates/appSample/lib/__scalar/workHistory/workHistory.dictionary.ts +28 -0
- package/templates/appSample/lib/_noti/noti.abstract.ts +38 -0
- package/templates/appSample/lib/_noti/noti.dictionary.ts +25 -0
- package/templates/appSample/lib/_noti/noti.service.ts +31 -0
- package/templates/appSample/lib/_noti/noti.signal.ts +30 -0
- package/templates/appSample/lib/_noti/noti.store.ts +38 -0
- package/templates/appSample/lib/task/Task.Template.tsx +49 -0
- package/templates/appSample/lib/task/Task.Unit.tsx +70 -0
- package/templates/appSample/lib/task/Task.Util.tsx +103 -0
- package/templates/appSample/lib/task/Task.View.tsx +94 -0
- package/templates/appSample/lib/task/Task.Zone.tsx +66 -0
- package/templates/appSample/lib/task/task.abstract.ts +40 -0
- package/templates/appSample/lib/task/task.constant.ts +54 -0
- package/templates/appSample/lib/task/task.dictionary.ts +85 -0
- package/templates/appSample/lib/task/task.document.ts +54 -0
- package/templates/appSample/lib/task/task.service.ts +48 -0
- package/templates/appSample/lib/task/task.signal.ts +49 -0
- package/templates/appSample/lib/task/task.store.ts +45 -0
- package/templates/appSample/page/task/[taskId]/_index.tsx +39 -0
- package/templates/appSample/page/task/[taskId]/edit.tsx +34 -0
- package/templates/appSample/page/task/_index.tsx +38 -0
- package/templates/appSample/page/task/_layout.tsx +24 -0
- package/templates/appSample/page/task/new.tsx +34 -0
- package/templates/appSample/srvkit/AuthGuard.ts +35 -0
- package/templates/appSample/srvkit/SessionInternalArg.ts +32 -0
- package/templates/appSample/ui/GlobalLoading.tsx +30 -0
- package/templates/appSample/ui/QuantityControl.tsx +52 -0
- package/templates/appSample/webkit/useDebounce.ts +41 -0
- package/templates/workspaceRoot/AGENTS.md.template +470 -16
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { AppInfo, LibInfo } from "akanjs";
|
|
2
|
+
|
|
3
|
+
export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: { appName: string }) {
|
|
4
|
+
return {
|
|
5
|
+
filename: "_layout.tsx",
|
|
6
|
+
content: `
|
|
7
|
+
// ===== page/task/_layout.tsx =====
|
|
8
|
+
// Convention: Akan.js file-based routing — _layout.tsx wraps every sub-page under /task/*.
|
|
9
|
+
// The underscores mark it as a routing file; children = sub-page component from nested routes.
|
|
10
|
+
// Rendered by Akan's router: URL → _layout.tsx → children (e.g., _index.tsx or [taskId]/_index.tsx).
|
|
11
|
+
|
|
12
|
+
interface LayoutProps {
|
|
13
|
+
children: React.ReactNode;
|
|
14
|
+
}
|
|
15
|
+
export default function Layout({ children }: LayoutProps) {
|
|
16
|
+
return (
|
|
17
|
+
<div className="min-h-screen bg-base-200">
|
|
18
|
+
{children}
|
|
19
|
+
</div>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
`,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { AppInfo, LibInfo } from "akanjs";
|
|
2
|
+
|
|
3
|
+
export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: { appName: string }) {
|
|
4
|
+
return {
|
|
5
|
+
filename: "new.tsx",
|
|
6
|
+
content: `import { fetch, usePage } from "@apps/${dict.appName}/client";
|
|
7
|
+
import { Load } from "akanjs/ui";
|
|
8
|
+
import { cnst, Task } from "@apps/${dict.appName}/client";
|
|
9
|
+
|
|
10
|
+
// ===== page/task/new.tsx =====
|
|
11
|
+
// Convention: Server-side form page using Load.Edit from akanjs/ui.
|
|
12
|
+
// async Page() — server-side component that pre-initializes form data before rendering.
|
|
13
|
+
// Load.Edit with type="form" renders the Template inside a form wrapper with submit/cancel actions.
|
|
14
|
+
// onCancel="back" navigates to the previous page; onSubmit specifies the redirect after success.
|
|
15
|
+
// Template is reused — same Task.Template.General used for create, edit, and client-side modals.
|
|
16
|
+
|
|
17
|
+
export default async function Page() {
|
|
18
|
+
const taskForm: Partial<cnst.Task> = { status: "todo" };
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<Load.Edit
|
|
22
|
+
slice={fetch.slice.task}
|
|
23
|
+
edit={taskForm}
|
|
24
|
+
type="form"
|
|
25
|
+
onCancel="back"
|
|
26
|
+
onSubmit="/task"
|
|
27
|
+
>
|
|
28
|
+
<Task.Template.General />
|
|
29
|
+
</Load.Edit>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
`,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { AppInfo, LibInfo } from "akanjs";
|
|
2
|
+
|
|
3
|
+
export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: { appName: string }) {
|
|
4
|
+
return {
|
|
5
|
+
filename: "AuthGuard.ts",
|
|
6
|
+
content: `import type { Guard, SignalContext } from "akanjs/signal";
|
|
7
|
+
|
|
8
|
+
// ===== AuthGuard.ts =====
|
|
9
|
+
// Convention: srvkit/ folder — server-only helpers, cannot import from client code.
|
|
10
|
+
// Implements the Guard interface from akanjs/signal.
|
|
11
|
+
// Guards are applied at endpoint/slice declaration: { guards: { root: SignedIn } }.
|
|
12
|
+
// Naming: PascalCase .ts, static name property matches the guard identifier.
|
|
13
|
+
// Scanned by akan scan into srvkit/index.ts barrel automatically.
|
|
14
|
+
|
|
15
|
+
export class SignedIn implements Guard {
|
|
16
|
+
static name = "SignedIn";
|
|
17
|
+
|
|
18
|
+
canPass(context: SignalContext): boolean {
|
|
19
|
+
const user = context.getHttpContext<{ user?: { id: string } }>().req.user;
|
|
20
|
+
return !!user;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// ---- Expandable additional fields: ----
|
|
25
|
+
// Role-based guard: only allow users with a specific role
|
|
26
|
+
// export class IsAdmin implements Guard {
|
|
27
|
+
// static name = "IsAdmin";
|
|
28
|
+
// canPass(context: SignalContext): boolean {
|
|
29
|
+
// const user = context.getHttpContext<{ user?: { role: string } }>().req.user;
|
|
30
|
+
// return user?.role === "admin";
|
|
31
|
+
// }
|
|
32
|
+
// }
|
|
33
|
+
`,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { AppInfo, LibInfo } from "akanjs";
|
|
2
|
+
|
|
3
|
+
export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: { appName: string }) {
|
|
4
|
+
return {
|
|
5
|
+
filename: "SessionInternalArg.ts",
|
|
6
|
+
content: `import type { InternalArg, SignalContext } from "akanjs/signal";
|
|
7
|
+
|
|
8
|
+
// ===== SessionInternalArg.ts =====
|
|
9
|
+
// Convention: srvkit/ folder — server-only helpers.
|
|
10
|
+
// Implements the InternalArg interface from akanjs/signal.
|
|
11
|
+
// InternalArg is an auto-injected argument for resolveField/endpoint .with() chains.
|
|
12
|
+
// Appended to a query/mutation via: .with(CurrentUserId, { nullable: true }).exec(...)
|
|
13
|
+
// Naming: PascalCase .ts, class name = arg identifier.
|
|
14
|
+
// Scanned by akan scan into srvkit/index.ts barrel automatically.
|
|
15
|
+
|
|
16
|
+
export class CurrentUserId implements InternalArg<string | null> {
|
|
17
|
+
getArg(context: SignalContext): string | null {
|
|
18
|
+
const user = context.getHttpContext<{ user?: { id: string } }>().req.user;
|
|
19
|
+
return user?.id ?? null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// ---- Expandable additional fields: ----
|
|
24
|
+
// Inject current session language/locale
|
|
25
|
+
// export class CurrentLocale implements InternalArg<string> {
|
|
26
|
+
// getArg(context: SignalContext): string {
|
|
27
|
+
// return context.getHttpContext<{ locale?: string }>().req.locale ?? "en";
|
|
28
|
+
// }
|
|
29
|
+
// }
|
|
30
|
+
`,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { AppInfo, LibInfo } from "akanjs";
|
|
2
|
+
|
|
3
|
+
export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: { appName: string }) {
|
|
4
|
+
return {
|
|
5
|
+
filename: "GlobalLoading.tsx",
|
|
6
|
+
content: `"use client";
|
|
7
|
+
|
|
8
|
+
import { clsx } from "akanjs/client";
|
|
9
|
+
|
|
10
|
+
// ===== GlobalLoading.tsx =====
|
|
11
|
+
// Convention: ui/ folder — reusable visual components. PascalCase .tsx, "use client" directive.
|
|
12
|
+
// File name = exported component name.
|
|
13
|
+
// Scanned by akan scan into ui/index.ts barrel automatically.
|
|
14
|
+
|
|
15
|
+
interface GlobalLoadingProps {
|
|
16
|
+
className?: string;
|
|
17
|
+
message?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const GlobalLoading = ({ className, message = "Loading..." }: GlobalLoadingProps) => {
|
|
21
|
+
return (
|
|
22
|
+
<div className={clsx("flex flex-col items-center justify-center gap-4 py-32", className)}>
|
|
23
|
+
<span className="loading loading-spinner loading-lg text-primary" />
|
|
24
|
+
<span className="text-base-content/60 text-sm">{message}</span>
|
|
25
|
+
</div>
|
|
26
|
+
);
|
|
27
|
+
};
|
|
28
|
+
`,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { AppInfo, LibInfo } from "akanjs";
|
|
2
|
+
|
|
3
|
+
export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: { appName: string }) {
|
|
4
|
+
return {
|
|
5
|
+
filename: "QuantityControl.tsx",
|
|
6
|
+
content: `"use client";
|
|
7
|
+
|
|
8
|
+
import { clsx } from "akanjs/client";
|
|
9
|
+
|
|
10
|
+
// ===== QuantityControl.tsx =====
|
|
11
|
+
// Convention: ui/ folder — reusable visual components. PascalCase .tsx, "use client" directive.
|
|
12
|
+
// File name = exported component name.
|
|
13
|
+
// Scanned by akan scan into ui/index.ts barrel automatically.
|
|
14
|
+
|
|
15
|
+
interface QuantityControlProps {
|
|
16
|
+
className?: string;
|
|
17
|
+
value: number;
|
|
18
|
+
onChange: (value: number) => void;
|
|
19
|
+
min?: number;
|
|
20
|
+
max?: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const QuantityControl = ({
|
|
24
|
+
className,
|
|
25
|
+
value,
|
|
26
|
+
onChange,
|
|
27
|
+
min = 0,
|
|
28
|
+
max = 99,
|
|
29
|
+
}: QuantityControlProps) => {
|
|
30
|
+
return (
|
|
31
|
+
<div className={clsx("inline-flex items-center gap-1", className)}>
|
|
32
|
+
<button
|
|
33
|
+
className="btn btn-circle btn-outline btn-xs"
|
|
34
|
+
disabled={value <= min}
|
|
35
|
+
onClick={() => onChange(Math.max(min, value - 1))}
|
|
36
|
+
>
|
|
37
|
+
−
|
|
38
|
+
</button>
|
|
39
|
+
<span className="w-8 text-center font-medium tabular-nums">{value}</span>
|
|
40
|
+
<button
|
|
41
|
+
className="btn btn-circle btn-outline btn-xs"
|
|
42
|
+
disabled={value >= max}
|
|
43
|
+
onClick={() => onChange(Math.min(max, value + 1))}
|
|
44
|
+
>
|
|
45
|
+
+
|
|
46
|
+
</button>
|
|
47
|
+
</div>
|
|
48
|
+
);
|
|
49
|
+
};
|
|
50
|
+
`,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { AppInfo, LibInfo } from "akanjs";
|
|
2
|
+
|
|
3
|
+
export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: { appName: string }) {
|
|
4
|
+
return {
|
|
5
|
+
filename: "useDebounce.ts",
|
|
6
|
+
content: `"use client";
|
|
7
|
+
|
|
8
|
+
import { useEffect, useState } from "react";
|
|
9
|
+
|
|
10
|
+
// ===== useDebounce.ts =====
|
|
11
|
+
// Convention: webkit/ folder — browser-only hooks; "use client" directive required.
|
|
12
|
+
// useEffect/useState are React client-side primitives that only work in the browser.
|
|
13
|
+
// Naming: camelCase .ts, file name = primary export name.
|
|
14
|
+
// Scanned by akan scan into webkit/index.ts barrel automatically.
|
|
15
|
+
|
|
16
|
+
export function useDebounce<T>(value: T, delay = 300): T {
|
|
17
|
+
const [debouncedValue, setDebouncedValue] = useState<T>(value);
|
|
18
|
+
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
const timer = setTimeout(() => setDebouncedValue(value), delay);
|
|
21
|
+
return () => clearTimeout(timer);
|
|
22
|
+
}, [value, delay]);
|
|
23
|
+
|
|
24
|
+
return debouncedValue;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// ---- Expandable additional fields: ----
|
|
28
|
+
// useViewportWidth: detect browser window width
|
|
29
|
+
// export function useViewportWidth() {
|
|
30
|
+
// const [width, setWidth] = useState(0);
|
|
31
|
+
// useEffect(() => {
|
|
32
|
+
// const handleResize = () => setWidth(window.innerWidth);
|
|
33
|
+
// handleResize();
|
|
34
|
+
// window.addEventListener("resize", handleResize);
|
|
35
|
+
// return () => window.removeEventListener("resize", handleResize);
|
|
36
|
+
// }, []);
|
|
37
|
+
// return width;
|
|
38
|
+
// }
|
|
39
|
+
`,
|
|
40
|
+
};
|
|
41
|
+
}
|