@akanjs/cli 3.0.0-alpha.6 → 3.0.0-alpha.61
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/.build-stamp +1 -1
- package/README.ko.md +1 -1
- package/README.md +1 -1
- package/{abstractCompactor-e5yq60qz.js → abstractCompactor-20e0cwbs.js} +2 -2
- package/{agent.command-b0fcx3r2.js → agent.command-tsx1nsdj.js} +9 -7
- package/{application.command-cezdnvtq.js → application.command-n8ex8g9h.js} +8 -6
- package/{applicationBuildRunner-ha3y8ew8.js → applicationBuildRunner-x8h12rp0.js} +7 -7
- package/{applicationReleasePackager-gg8ccbeh.js → applicationReleasePackager-93nzdfsy.js} +1 -1
- package/buildBatch.proc.js +7 -5
- package/{capacitorApp-jaksw4cy.js → capacitorApp-8mv9e537.js} +29 -27
- package/{cloud.command-6xvsd487.js → cloud.command-f0rhw014.js} +12 -10
- package/{context.command-ajn1b683.js → context.command-s7a41qjs.js} +17 -15
- package/{dependencyScanner-f6grrep3.js → dependencyScanner-rbs2407b.js} +1 -1
- package/getDirname-ka11hm7e.js +8 -0
- package/{guideline.command-9ew6aqhm.js → guideline.command-qrw84ttb.js} +8 -6
- package/guidelines/agentRule/agentRule.generate.json +31 -0
- package/guidelines/agentRule/agentRule.instruction.md +525 -0
- package/guidelines/conventions/conventions.generate.json +25 -0
- package/guidelines/conventions/conventions.instruction.md +821 -0
- package/guidelines/cssRule/cssRule.instruction.md +7 -0
- package/guidelines/framework/framework.instruction.md +1 -0
- package/guidelines/mcpRule/mcpRule.generate.json +25 -0
- package/guidelines/mcpRule/mcpRule.instruction.md +176 -0
- package/guidelines/workspaceOnboarding/workspaceOnboarding.generate.json +24 -0
- package/guidelines/workspaceOnboarding/workspaceOnboarding.instruction.md +638 -0
- package/incrementalBuilder.proc.js +8 -6
- package/{index-zr2wvx69.js → index-0g9ajdz8.js} +6 -6
- package/index-1577bej2.js +7 -0
- package/{index-wrrb8rxk.js → index-4mtyaqj2.js} +4 -4
- package/{index-bjpxzr6s.js → index-5vy2jzar.js} +1 -1
- package/{index-47qxw1tz.js → index-681sx6nt.js} +176 -66
- package/{index-zbx0jheb.js → index-8md8zszv.js} +14 -13
- package/{index-pfrt7qwh.js → index-cc1mgv1f.js} +4 -4
- package/{index-hk9rbcsz.js → index-ce9755av.js} +1 -1
- package/{index-jkynkyfk.js → index-ckpwq052.js} +5 -3
- package/{index-c94nq3c1.js → index-dvg362vq.js} +3 -3
- package/{index-vkqxw0ek.js → index-ek4mkwxp.js} +13 -13
- package/{index-exhpp31y.js → index-f0t3wjyr.js} +15 -7
- package/index-f39512t6.js +255 -0
- package/{index-m4kpmmtj.js → index-g44fykfw.js} +1 -1
- package/{index-nfxq39kj.js → index-h0mnt0b0.js} +37 -6
- package/{index-qp089jp0.js → index-j37qq1f2.js} +1 -1
- package/{index-hrwzz2wf.js → index-kkya2ped.js} +1 -1
- package/{index-j9cxndc0.js → index-qnv0aqmm.js} +9 -6
- package/{index-r7nqv8qj.js → index-r8z20vnj.js} +14 -14
- package/{index-6npsfpxr.js → index-ryns915y.js} +3 -3
- package/{index-0cj2zxbm.js → index-sh01r3pm.js} +1 -1
- package/{index-60a3jfcz.js → index-tn46rr6w.js} +8 -8
- package/{index-jn9vfpwz.js → index-wc19q8mb.js} +54 -15
- package/{index-meh7mmz9.js → index-x1dbhd80.js} +8 -4
- package/{index-qb61h0k7.js → index-xj5b8c0w.js} +193 -565
- package/index.js +21 -19
- package/{library.command-aa1g8nsa.js → library.command-p3jgf5h2.js} +6 -4
- package/{localRegistry.command-jha3sn50.js → localRegistry.command-bms0qn6t.js} +11 -9
- package/{module.command-b1zzgqde.js → module.command-hb3xm07r.js} +10 -8
- package/{package.command-9qv6vr9s.js → package.command-fbpa6r4w.js} +6 -4
- package/package.json +3 -4
- package/{page.command-k16pw244.js → page.command-92vrmw96.js} +6 -4
- package/{primitive.command-s60dn1ae.js → primitive.command-k6b9jfxr.js} +11 -9
- package/{quality.command-p3vaze52.js → quality.command-89vwmjvf.js} +194 -112
- package/{recipeScanner-tghkzrrt.js → recipeScanner-zrzev25x.js} +3 -3
- package/{repair.command-mfv749ha.js → repair.command-nh9jn8rh.js} +8 -6
- package/{routeSourceValidator-cncd92fv.js → routeSourceValidator-7fycdqyv.js} +3 -39
- package/{scalar.command-hs3nwzv1.js → scalar.command-zcsjgvax.js} +9 -7
- package/templates/app/env/env.server.debug.ts.template +1 -5
- package/templates/app/env/env.server.develop.ts.template +1 -5
- package/templates/app/env/env.server.local.ts.template +1 -5
- package/templates/app/env/env.server.main.ts.template +1 -5
- package/templates/app/page/styles.css.template +3 -3
- package/templates/appSample/srvkit/AuthGuard.ts +9 -5
- package/templates/appSample/srvkit/SessionInternalArg.ts +4 -3
- package/templates/crudPages/[__model__Id]/edit/page.tsx +1 -1
- package/templates/crudPages/new/page.tsx +1 -1
- package/templates/crudSinglePage/page.tsx +1 -1
- package/templates/env/_env.server.type.ts +7 -5
- package/templates/lib/db.ts +1 -1
- package/templates/lib/dict.ts +1 -1
- package/templates/lib/sig.ts +1 -1
- package/templates/lib/srv.ts +6 -2
- package/templates/module/__Model__.Util.tsx +1 -1
- package/templates/module/__Model__.Zone.tsx +3 -5
- package/templates/workspaceRoot/AGENTS.md.template +0 -971
- package/templates/workspaceRoot/CLAUDE.md.template +19 -0
- package/templates/workspaceRoot/biome.json.template +4 -239
- package/templates/workspaceRoot/docs/AI-DEVELOPMENT.md.template +10 -14
- package/templates/workspaceRoot/docs/GENERATED.md.template +12 -10
- package/{typeChecker-kravn7ns.js → typeChecker-g6bh0xfv.js} +1 -1
- package/typecheck.proc.js +1 -1
- package/{workflow.command-z0e0fk0z.js → workflow.command-jj0cphfm.js} +16 -14
- package/{workspace.command-3ydyy991.js → workspace.command-ma3g994j.js} +71 -52
- package/templates/workspaceRoot/.cursor/rules/akan-page-routing.mdc.template +0 -43
- package/templates/workspaceRoot/.cursor/rules/akan-sync-conventions.mdc.template +0 -34
- package/templates/workspaceRoot/.cursor/rules/application-test-commands.mdc.template +0 -16
- package/templates/workspaceRoot/.cursor/rules/change-scope.mdc.template +0 -12
- package/templates/workspaceRoot/.cursor/rules/client-server-boundaries.mdc.template +0 -18
- package/templates/workspaceRoot/.cursor/rules/coding-comments.mdc.template +0 -25
- package/templates/workspaceRoot/.cursor/rules/coding-style.mdc.template +0 -44
- package/templates/workspaceRoot/.cursor/rules/domain-module-conventions.mdc.template +0 -17
- package/templates/workspaceRoot/.cursor/rules/format-lint.mdc.template +0 -12
- package/templates/workspaceRoot/.cursor/rules/lint-enforced-rules.mdc.template +0 -18
- package/templates/workspaceRoot/.cursor/rules/module-file-playbook.mdc.template +0 -61
- package/templates/workspaceRoot/.cursor/rules/naming-conventions.mdc.template +0 -15
- package/templates/workspaceRoot/.cursor/rules/react-component-style.mdc.template +0 -21
- package/templates/workspaceRoot/.cursor/rules/repo-overview.mdc.template +0 -17
- package/templates/workspaceRoot/.cursor/rules/scalar-modeling.mdc.template +0 -58
- package/templates/workspaceRoot/.cursor/rules/secrets-and-env.mdc.template +0 -13
- package/templates/workspaceRoot/.cursor/rules/service-signal-conventions.mdc.template +0 -83
- package/templates/workspaceRoot/.cursor/rules/srvkit-adapters-and-guards.mdc.template +0 -50
- package/templates/workspaceRoot/.cursor/rules/ssr-first.mdc.template +0 -59
- package/templates/workspaceRoot/.cursor/rules/typescript-imports.mdc.template +0 -16
- /package/{index-r24hmh0q.js → index-yb2wsc7f.js} +0 -0
|
@@ -9,276 +9,26 @@ import {
|
|
|
9
9
|
createTsconfigPackageResolver,
|
|
10
10
|
rewriteBarrelImports,
|
|
11
11
|
transformUseClient
|
|
12
|
-
} from "./index-
|
|
12
|
+
} from "./index-qnv0aqmm.js";
|
|
13
|
+
import {
|
|
14
|
+
resolveSsrPageEntriesForApp
|
|
15
|
+
} from "./index-f39512t6.js";
|
|
13
16
|
import {
|
|
14
17
|
__require
|
|
15
|
-
} from "./index-
|
|
18
|
+
} from "./index-yb2wsc7f.js";
|
|
16
19
|
|
|
17
20
|
// pkgs/@akanjs/devkit/frontendBuild/allRoutesBuilder.ts
|
|
18
|
-
import
|
|
19
|
-
|
|
20
|
-
// pkgs/@akanjs/devkit/artifact/implicitRootLayout.ts
|
|
21
|
-
import { mkdir } from "fs/promises";
|
|
22
|
-
import path from "path";
|
|
23
|
-
var LAYOUT_KEY_RE = /^\.\/(.+\/)?_layout\.(tsx|ts|jsx|js)$/;
|
|
24
|
-
async function appHasStModule(appCwdPath) {
|
|
25
|
-
return Bun.file(path.join(appCwdPath, "lib", "st.ts")).exists();
|
|
26
|
-
}
|
|
27
|
-
var IMPLICIT_LAYOUT_DIR = path.join(".akan", "generated", "root-layouts");
|
|
28
|
-
var IMPLICIT_DICT_DIR = path.join(".akan", "generated", "dict");
|
|
29
|
-
var IMPLICIT_OVERRIDES_DIR = path.join(".akan", "generated", "overrides");
|
|
30
|
-
var OVERRIDES_KEY_RE = /^\.\/(.+\/)?_overrides\.(tsx|ts|jsx|js)$/;
|
|
31
|
-
async function writeGeneratedOverridesLayoutFile(opts) {
|
|
32
|
-
const filename = `${opts.key.replace(/^\.\//, "").replace(/[^a-zA-Z0-9]+/g, "_")}.tsx`;
|
|
33
|
-
const absPath = path.join(path.resolve(opts.appCwdPath), IMPLICIT_OVERRIDES_DIR, filename);
|
|
34
|
-
const userRel = path.relative(path.dirname(absPath), opts.userAbsPath).split(path.sep).join("/");
|
|
35
|
-
const userSpecifier = userRel.startsWith(".") ? userRel : `./${userRel}`;
|
|
36
|
-
const source = `"use client";
|
|
37
|
-
import { UiOverrideProvider } from "akanjs/ui";
|
|
38
|
-
import { createElement, type ReactNode } from "react";
|
|
39
|
-
import value from ${JSON.stringify(userSpecifier)};
|
|
40
|
-
|
|
41
|
-
export default function AkanUiOverridesLayout({ children }: { children?: ReactNode }) {
|
|
42
|
-
return createElement(UiOverrideProvider, { value }, children);
|
|
43
|
-
}
|
|
44
|
-
`;
|
|
45
|
-
await Bun.write(absPath, source);
|
|
46
|
-
return absPath;
|
|
47
|
-
}
|
|
48
|
-
function getRootBoundarySegments(key) {
|
|
49
|
-
const match = LAYOUT_KEY_RE.exec(key);
|
|
50
|
-
if (!match)
|
|
51
|
-
return null;
|
|
52
|
-
const prefix = match[1]?.replace(/\/$/, "");
|
|
53
|
-
if (!prefix)
|
|
54
|
-
return [];
|
|
55
|
-
return prefix.split("/").filter(Boolean);
|
|
56
|
-
}
|
|
57
|
-
function implicitRootLayoutKey(segments) {
|
|
58
|
-
return `./${[...segments, "__root_layout"].join("/")}.tsx`;
|
|
59
|
-
}
|
|
60
|
-
function implicitRootLayoutAbsPath(appCwdPath, segments) {
|
|
61
|
-
const filename = segments.length ? `${segments.join("__")}__root_layout.tsx` : "__root_layout.tsx";
|
|
62
|
-
return path.join(path.resolve(appCwdPath), IMPLICIT_LAYOUT_DIR, filename);
|
|
63
|
-
}
|
|
64
|
-
function implicitDictionaryMacroAbsPath(appCwdPath) {
|
|
65
|
-
return path.join(path.resolve(appCwdPath), IMPLICIT_DICT_DIR, "useDict.ts");
|
|
66
|
-
}
|
|
67
|
-
function isRootBoundarySegments(segments, basePaths) {
|
|
68
|
-
const firstVisibleIndex = segments.findIndex((segment) => !/^\(.+\)$/.test(segment));
|
|
69
|
-
if (firstVisibleIndex === -1)
|
|
70
|
-
return segments.length <= 1;
|
|
71
|
-
if (segments.slice(firstVisibleIndex + 1).some((segment) => /^\(.+\)$/.test(segment)))
|
|
72
|
-
return false;
|
|
73
|
-
const visible = segments.slice(firstVisibleIndex);
|
|
74
|
-
const allowedBasePaths = new Set([...basePaths].map((basePath) => basePath.trim()).filter(Boolean));
|
|
75
|
-
return visible.length === 1 && (firstVisibleIndex > 0 || allowedBasePaths.has(visible[0] ?? ""));
|
|
76
|
-
}
|
|
77
|
-
function findRootBoundaries(pageKeys, appCwdPath, basePaths) {
|
|
78
|
-
const boundaries = new Map;
|
|
79
|
-
for (const key of pageKeys) {
|
|
80
|
-
const segments = getRootBoundarySegments(key);
|
|
81
|
-
if (!segments)
|
|
82
|
-
continue;
|
|
83
|
-
if (!isRootBoundarySegments(segments, basePaths))
|
|
84
|
-
continue;
|
|
85
|
-
const id = segments.join("/");
|
|
86
|
-
boundaries.set(id, {
|
|
87
|
-
sourceKey: key,
|
|
88
|
-
sourceAbsPath: path.resolve(appCwdPath, "page", key.replace(/^\.\//, "")),
|
|
89
|
-
segments
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
const hasExplicitRootBoundary = [...boundaries.values()].some((boundary) => boundary.segments.length === 0);
|
|
93
|
-
if (!hasExplicitRootBoundary && boundaries.size === 0) {
|
|
94
|
-
boundaries.set("", { sourceKey: null, sourceAbsPath: null, segments: [] });
|
|
95
|
-
}
|
|
96
|
-
return [...boundaries.values()].sort((a, b) => a.segments.join("/").localeCompare(b.segments.join("/")));
|
|
97
|
-
}
|
|
98
|
-
function hasAncestorRootBoundary(boundary, boundaries) {
|
|
99
|
-
return boundaries.some((candidate) => candidate !== boundary && candidate.segments.length < boundary.segments.length && candidate.segments.every((segment, index) => boundary.segments[index] === segment));
|
|
100
|
-
}
|
|
101
|
-
function findExplicitRootLayoutAbsPath(pageKeys, appCwdPath) {
|
|
102
|
-
const rootLayoutKey = pageKeys.find((key) => {
|
|
103
|
-
const segments = getRootBoundarySegments(key);
|
|
104
|
-
return segments !== null && segments.length === 0;
|
|
105
|
-
});
|
|
106
|
-
return rootLayoutKey ? path.resolve(appCwdPath, "page", rootLayoutKey.replace(/^\.\//, "")) : null;
|
|
107
|
-
}
|
|
108
|
-
function routePrefixForSegments(segments) {
|
|
109
|
-
const visible = segments.filter((segment) => !/^\(.+\)$/.test(segment));
|
|
110
|
-
return visible[0] ?? null;
|
|
111
|
-
}
|
|
112
|
-
async function assertEnvClientConvention(appCwdPath, appName) {
|
|
113
|
-
const envPath = path.join(appCwdPath, "env", "env.client.ts");
|
|
114
|
-
if (!await Bun.file(envPath).exists()) {
|
|
115
|
-
throw new Error(`[route-convention] app "${appName}" must provide env/env.client.ts exporting "env" for generated System.Provider`);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
async function writeGeneratedDictionaryMacroFile(appCwdPath, appName) {
|
|
119
|
-
const absPath = implicitDictionaryMacroAbsPath(appCwdPath);
|
|
120
|
-
await mkdir(path.dirname(absPath), { recursive: true });
|
|
121
|
-
await Bun.write(absPath, `import { getAllDictionary } from "@apps/${appName}/lib/dict" with { type: "macro" };
|
|
122
|
-
|
|
123
|
-
export const allDictionary = getAllDictionary();
|
|
124
|
-
`);
|
|
125
|
-
return absPath;
|
|
126
|
-
}
|
|
127
|
-
async function writeGeneratedRootLayoutFile(opts) {
|
|
128
|
-
await assertEnvClientConvention(opts.appCwdPath, opts.appName);
|
|
129
|
-
const dictMacroAbsPath = opts.includeSystemProvider ? await writeGeneratedDictionaryMacroFile(opts.appCwdPath, opts.appName) : null;
|
|
130
|
-
const absPath = implicitRootLayoutAbsPath(opts.appCwdPath, opts.boundary.segments);
|
|
131
|
-
await mkdir(path.dirname(absPath), { recursive: true });
|
|
132
|
-
const dictMacroRel = dictMacroAbsPath ? path.relative(path.dirname(absPath), dictMacroAbsPath).split(path.sep).join("/") : null;
|
|
133
|
-
const dictMacroSpecifier = dictMacroRel ? dictMacroRel.startsWith(".") ? dictMacroRel : `./${dictMacroRel}` : null;
|
|
134
|
-
const sourceRel = opts.boundary.sourceAbsPath ? path.relative(path.dirname(absPath), opts.boundary.sourceAbsPath).split(path.sep).join("/") : null;
|
|
135
|
-
const sourceSpecifier = sourceRel ? sourceRel.startsWith(".") ? sourceRel : `./${sourceRel}` : null;
|
|
136
|
-
const inheritedSourceAbsPath = opts.rootSourceAbsPath && opts.rootSourceAbsPath !== opts.boundary.sourceAbsPath ? opts.rootSourceAbsPath : null;
|
|
137
|
-
const inheritedSourceRel = inheritedSourceAbsPath ? path.relative(path.dirname(absPath), inheritedSourceAbsPath).split(path.sep).join("/") : null;
|
|
138
|
-
const inheritedSourceSpecifier = inheritedSourceRel ? inheritedSourceRel.startsWith(".") ? inheritedSourceRel : `./${inheritedSourceRel}` : null;
|
|
139
|
-
const clientImport = opts.includeStInit ? `import { st } from "@apps/${opts.appName}/client";
|
|
140
|
-
void st;
|
|
141
|
-
` : `import "@apps/${opts.appName}/client";
|
|
142
|
-
`;
|
|
143
|
-
const inheritedImport = inheritedSourceSpecifier ? `import * as inheritedLayout from ${JSON.stringify(inheritedSourceSpecifier)};
|
|
144
|
-
` : `const inheritedLayout = {};
|
|
145
|
-
`;
|
|
146
|
-
const prefix = routePrefixForSegments(opts.boundary.segments);
|
|
147
|
-
const userImport = sourceSpecifier ? `import UserLayout, * as userLayout from ${JSON.stringify(sourceSpecifier)};
|
|
148
|
-
` : `const UserLayout = ({ children }) => children;
|
|
149
|
-
const userLayout = {};
|
|
150
|
-
`;
|
|
151
|
-
const source = opts.includeSystemProvider ? `import type { LayoutProps, PageProps } from "akanjs/client";
|
|
152
|
-
import { loadFonts } from "akanjs/client";
|
|
153
|
-
import { System } from "akanjs/ui";
|
|
154
|
-
import { env } from "@apps/${opts.appName}/env/env.client";
|
|
155
|
-
import { allDictionary } from ${JSON.stringify(dictMacroSpecifier)};
|
|
156
|
-
${clientImport}${inheritedImport}${userImport}
|
|
157
|
-
const userFonts = userLayout.fonts ?? inheritedLayout.fonts ?? [];
|
|
158
|
-
const defaultFonts = userFonts.filter((font) => font.default);
|
|
159
|
-
if (defaultFonts.length > 1) throw new Error("[route-convention] only one default font is allowed per root layout");
|
|
160
|
-
const defaultFont = defaultFonts[0];
|
|
161
|
-
const defaultFontClassName = defaultFont ? (defaultFont.className ?? \`font-\${defaultFont.name}\`) : undefined;
|
|
162
|
-
|
|
163
|
-
export async function generateHead(props: PageProps) {
|
|
164
|
-
if (userLayout.generateHead) return userLayout.generateHead(props);
|
|
165
|
-
if (userLayout.head !== undefined) return userLayout.head;
|
|
166
|
-
if (inheritedLayout.generateHead) return inheritedLayout.generateHead(props);
|
|
167
|
-
return inheritedLayout.head;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
export async function generateMetadata(props: PageProps) {
|
|
171
|
-
if (userLayout.generateMetadata) return userLayout.generateMetadata(props);
|
|
172
|
-
if (userLayout.metadata !== undefined) return userLayout.metadata;
|
|
173
|
-
if (inheritedLayout.generateMetadata) return inheritedLayout.generateMetadata(props);
|
|
174
|
-
return inheritedLayout.metadata;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
export const NotFound = userLayout.NotFound ?? inheritedLayout.NotFound;
|
|
178
|
-
export const Error = userLayout.Error ?? inheritedLayout.Error;
|
|
179
|
-
export const pageConfig = userLayout.pageConfig ?? inheritedLayout.pageConfig;
|
|
180
|
-
|
|
181
|
-
export default function GeneratedLayout({ children, params, searchParams }: LayoutProps) {
|
|
182
|
-
return (
|
|
183
|
-
<System.Provider
|
|
184
|
-
of={GeneratedLayout as never}
|
|
185
|
-
appName=${JSON.stringify(opts.appName)}
|
|
186
|
-
${prefix ? `prefix=${JSON.stringify(prefix)}
|
|
187
|
-
` : ""}params={params}
|
|
188
|
-
manifest={userLayout.manifest ?? inheritedLayout.manifest}
|
|
189
|
-
env={env}
|
|
190
|
-
theme={userLayout.theme ?? inheritedLayout.theme}
|
|
191
|
-
fonts={loadFonts(userFonts)}
|
|
192
|
-
className={defaultFontClassName}
|
|
193
|
-
gaTrackingId={userLayout.gaTrackingId ?? inheritedLayout.gaTrackingId}
|
|
194
|
-
layoutStyle={userLayout.layoutStyle ?? inheritedLayout.layoutStyle}
|
|
195
|
-
reconnect={userLayout.reconnect ?? inheritedLayout.reconnect ?? false}
|
|
196
|
-
wsConnect={userLayout.wsConnect ?? inheritedLayout.wsConnect ?? true}
|
|
197
|
-
allDictionary={process.env.AKAN_PUBLIC_RENDER_ENV === "ssr" ? allDictionary : undefined}
|
|
198
|
-
>
|
|
199
|
-
<UserLayout params={params} searchParams={searchParams}>{children}</UserLayout>
|
|
200
|
-
</System.Provider>
|
|
201
|
-
);
|
|
202
|
-
}
|
|
203
|
-
` : `import type { LayoutProps, PageProps } from "akanjs/client";
|
|
204
|
-
${inheritedImport}${userImport}
|
|
205
|
-
export async function generateHead(props: PageProps) {
|
|
206
|
-
if (userLayout.generateHead) return userLayout.generateHead(props);
|
|
207
|
-
if (userLayout.head !== undefined) return userLayout.head;
|
|
208
|
-
if (inheritedLayout.generateHead) return inheritedLayout.generateHead(props);
|
|
209
|
-
return inheritedLayout.head;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
export async function generateMetadata(props: PageProps) {
|
|
213
|
-
if (userLayout.generateMetadata) return userLayout.generateMetadata(props);
|
|
214
|
-
if (userLayout.metadata !== undefined) return userLayout.metadata;
|
|
215
|
-
if (inheritedLayout.generateMetadata) return inheritedLayout.generateMetadata(props);
|
|
216
|
-
return inheritedLayout.metadata;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
export const NotFound = userLayout.NotFound ?? inheritedLayout.NotFound;
|
|
220
|
-
export const Error = userLayout.Error ?? inheritedLayout.Error;
|
|
221
|
-
export const pageConfig = userLayout.pageConfig ?? inheritedLayout.pageConfig;
|
|
222
|
-
|
|
223
|
-
export default function GeneratedLayout({ children, params, searchParams }: LayoutProps) {
|
|
224
|
-
return <UserLayout params={params} searchParams={searchParams}>{children}</UserLayout>;
|
|
225
|
-
}
|
|
226
|
-
`;
|
|
227
|
-
await Bun.write(absPath, source);
|
|
228
|
-
return absPath;
|
|
229
|
-
}
|
|
230
|
-
async function resolveSsrPageEntries(opts) {
|
|
231
|
-
const absPageDir = path.resolve(opts.appCwdPath, "page");
|
|
232
|
-
const hasSt = await appHasStModule(opts.appCwdPath);
|
|
233
|
-
const basePaths = opts.basePaths ?? [];
|
|
234
|
-
const rootSourceAbsPath = findExplicitRootLayoutAbsPath(opts.pageKeys, opts.appCwdPath);
|
|
235
|
-
const rootBoundaries = findRootBoundaries(opts.pageKeys, opts.appCwdPath, basePaths);
|
|
236
|
-
const rootLayoutKeys = new Set(opts.pageKeys.filter((key) => {
|
|
237
|
-
const segments = getRootBoundarySegments(key);
|
|
238
|
-
return segments !== null && isRootBoundarySegments(segments, basePaths);
|
|
239
|
-
}));
|
|
240
|
-
const base = await Promise.all(opts.pageKeys.filter((key) => !rootLayoutKeys.has(key)).map(async (key) => {
|
|
241
|
-
const userAbsPath = path.resolve(absPageDir, key);
|
|
242
|
-
if (OVERRIDES_KEY_RE.test(key)) {
|
|
243
|
-
const moduleAbsPath = await writeGeneratedOverridesLayoutFile({
|
|
244
|
-
appCwdPath: opts.appCwdPath,
|
|
245
|
-
key,
|
|
246
|
-
userAbsPath
|
|
247
|
-
});
|
|
248
|
-
return { key, moduleAbsPath, seedAbsPaths: [userAbsPath] };
|
|
249
|
-
}
|
|
250
|
-
return { key, moduleAbsPath: userAbsPath };
|
|
251
|
-
}));
|
|
252
|
-
const generated = await Promise.all(rootBoundaries.map(async (boundary) => ({
|
|
253
|
-
key: implicitRootLayoutKey(boundary.segments),
|
|
254
|
-
moduleAbsPath: await writeGeneratedRootLayoutFile({
|
|
255
|
-
appCwdPath: opts.appCwdPath,
|
|
256
|
-
appName: opts.appName,
|
|
257
|
-
boundary,
|
|
258
|
-
rootSourceAbsPath,
|
|
259
|
-
includeStInit: hasSt && boundary.segments.length === 0,
|
|
260
|
-
includeSystemProvider: !hasAncestorRootBoundary(boundary, rootBoundaries)
|
|
261
|
-
}),
|
|
262
|
-
seedAbsPaths: [...new Set([boundary.sourceAbsPath, rootSourceAbsPath].filter((absPath) => absPath !== null))]
|
|
263
|
-
})));
|
|
264
|
-
const entries = [...base, ...generated];
|
|
265
|
-
entries.sort((a, b) => a.key.localeCompare(b.key));
|
|
266
|
-
return entries;
|
|
267
|
-
}
|
|
268
|
-
async function resolveSsrPageEntriesForApp(app, pageKeys) {
|
|
269
|
-
const config = await app.getConfig();
|
|
270
|
-
return resolveSsrPageEntries({ appCwdPath: app.cwdPath, appName: app.name, pageKeys, basePaths: config.basePaths });
|
|
271
|
-
}
|
|
21
|
+
import path3 from "path";
|
|
272
22
|
|
|
273
23
|
// pkgs/@akanjs/devkit/artifact/routeSeedIndex.ts
|
|
274
|
-
import
|
|
24
|
+
import path from "path";
|
|
275
25
|
import { assertUniqueRoutePatterns, compareRouteSpecificity, parseRouteModuleKey } from "akanjs/common";
|
|
276
26
|
function computeRouteSeedIndex(pageEntries) {
|
|
277
27
|
const layoutsByPrefix = new Map;
|
|
278
28
|
const pagesBySegments = [];
|
|
279
29
|
for (const { key, moduleAbsPath, seedAbsPaths } of pageEntries) {
|
|
280
30
|
const parsed = parseRouteModuleKey(key);
|
|
281
|
-
const files = [
|
|
31
|
+
const files = [path.resolve(moduleAbsPath), ...(seedAbsPaths ?? []).map((seed) => path.resolve(seed))];
|
|
282
32
|
if (parsed.kind === "layout" || parsed.kind === "overrides") {
|
|
283
33
|
const prefix = parsed.routeSegments.join("/");
|
|
284
34
|
const prev = layoutsByPrefix.get(prefix) ?? [];
|
|
@@ -315,7 +65,7 @@ function computeRouteSeedIndex(pageEntries) {
|
|
|
315
65
|
}
|
|
316
66
|
var ROUTE_SEED_INDEX_JSON = "route-seed-index.json";
|
|
317
67
|
function serializeRouteSeedIndexForArtifact(index, artifactDir, options = {}) {
|
|
318
|
-
const normalizedArtifactDir =
|
|
68
|
+
const normalizedArtifactDir = path.resolve(artifactDir);
|
|
319
69
|
if (options.production) {
|
|
320
70
|
return {
|
|
321
71
|
entries: index.entries.map((entry) => ({ routeId: entry.routeId }))
|
|
@@ -330,19 +80,19 @@ function serializeRouteSeedIndexForArtifact(index, artifactDir, options = {}) {
|
|
|
330
80
|
};
|
|
331
81
|
}
|
|
332
82
|
async function saveRouteSeedIndex(artifactDir, index, options = {}) {
|
|
333
|
-
const absPath =
|
|
83
|
+
const absPath = path.join(path.resolve(artifactDir), ROUTE_SEED_INDEX_JSON);
|
|
334
84
|
await Bun.write(absPath, `${JSON.stringify(serializeRouteSeedIndexForArtifact(index, artifactDir, options), null, 2)}
|
|
335
85
|
`);
|
|
336
86
|
return absPath;
|
|
337
87
|
}
|
|
338
88
|
function serializeArtifactPath(artifactPath, artifactDir) {
|
|
339
|
-
if (!
|
|
89
|
+
if (!path.isAbsolute(artifactPath))
|
|
340
90
|
return artifactPath;
|
|
341
|
-
return
|
|
91
|
+
return path.relative(artifactDir, artifactPath).split(path.sep).join("/");
|
|
342
92
|
}
|
|
343
93
|
|
|
344
94
|
// pkgs/@akanjs/devkit/frontendBuild/routesManifestArtifactSerializer.ts
|
|
345
|
-
import
|
|
95
|
+
import path2 from "path";
|
|
346
96
|
|
|
347
97
|
class RoutesManifestArtifactSerializer {
|
|
348
98
|
#manifest;
|
|
@@ -350,7 +100,7 @@ class RoutesManifestArtifactSerializer {
|
|
|
350
100
|
#production;
|
|
351
101
|
constructor(manifest, artifactDir, options = {}) {
|
|
352
102
|
this.#manifest = manifest;
|
|
353
|
-
this.#artifactDir =
|
|
103
|
+
this.#artifactDir = path2.resolve(artifactDir);
|
|
354
104
|
this.#production = options.production ?? false;
|
|
355
105
|
}
|
|
356
106
|
static serialize(manifest, artifactDir, options = {}) {
|
|
@@ -383,9 +133,9 @@ class RoutesManifestArtifactSerializer {
|
|
|
383
133
|
};
|
|
384
134
|
}
|
|
385
135
|
#serializeArtifactPath(artifactPath) {
|
|
386
|
-
if (!
|
|
136
|
+
if (!path2.isAbsolute(artifactPath))
|
|
387
137
|
return artifactPath;
|
|
388
|
-
return
|
|
138
|
+
return path2.relative(this.#artifactDir, artifactPath).split(path2.sep).join("/");
|
|
389
139
|
}
|
|
390
140
|
}
|
|
391
141
|
|
|
@@ -435,7 +185,7 @@ class AllRoutesBuilder {
|
|
|
435
185
|
routeIds: this.#routeIds,
|
|
436
186
|
...this.#merged
|
|
437
187
|
};
|
|
438
|
-
const manifestPath =
|
|
188
|
+
const manifestPath = path3.join(path3.resolve(this.#artifactDir), "routes-manifest.json");
|
|
439
189
|
await Bun.write(manifestPath, `${JSON.stringify(RoutesManifestArtifactSerializer.serialize(manifest, this.#artifactDir, {
|
|
440
190
|
production: this.#command === "build"
|
|
441
191
|
}), null, 2)}
|
|
@@ -478,12 +228,12 @@ class AllRoutesBuilder {
|
|
|
478
228
|
}
|
|
479
229
|
|
|
480
230
|
// pkgs/@akanjs/devkit/frontendBuild/csrArtifactBuilder.ts
|
|
481
|
-
import { mkdir
|
|
482
|
-
import
|
|
231
|
+
import { mkdir, rm, unlink } from "fs/promises";
|
|
232
|
+
import path5 from "path";
|
|
483
233
|
|
|
484
234
|
// pkgs/@akanjs/devkit/frontendBuild/pagesEntrySourceGenerator.ts
|
|
485
235
|
import fs from "fs";
|
|
486
|
-
import
|
|
236
|
+
import path4 from "path";
|
|
487
237
|
import ts from "typescript";
|
|
488
238
|
|
|
489
239
|
class PagesEntrySourceGenerator {
|
|
@@ -526,11 +276,11 @@ ${entries.join(`
|
|
|
526
276
|
`;
|
|
527
277
|
}
|
|
528
278
|
static #toImportSpecifier(moduleAbsPath) {
|
|
529
|
-
return
|
|
279
|
+
return path4.resolve(moduleAbsPath).split(path4.sep).join("/");
|
|
530
280
|
}
|
|
531
281
|
static #hasAsyncDefaultExport(moduleAbsPath) {
|
|
532
282
|
try {
|
|
533
|
-
const source = fs.readFileSync(
|
|
283
|
+
const source = fs.readFileSync(path4.resolve(moduleAbsPath), "utf8");
|
|
534
284
|
const sourceFile = ts.createSourceFile(moduleAbsPath, source, ts.ScriptTarget.Latest, true, PagesEntrySourceGenerator.#scriptKind(moduleAbsPath));
|
|
535
285
|
return PagesEntrySourceGenerator.#sourceFileHasAsyncDefaultExport(sourceFile);
|
|
536
286
|
} catch {
|
|
@@ -609,7 +359,7 @@ class CsrArtifactBuilder {
|
|
|
609
359
|
const csrBasePaths = [...akanConfig.basePaths];
|
|
610
360
|
const htmlEntries = csrBasePaths.length > 0 ? csrBasePaths : ["index"];
|
|
611
361
|
await rm(this.#outputDir, { recursive: true, force: true });
|
|
612
|
-
await
|
|
362
|
+
await mkdir(path5.join(this.#app.cwdPath, ".akan/generated/csr"), { recursive: true });
|
|
613
363
|
const generatedHtmlFiles = Object.fromEntries(htmlEntries.map((basePath) => this.#createHtmlFile(basePath)));
|
|
614
364
|
const result = await Bun.build({
|
|
615
365
|
target: "browser",
|
|
@@ -641,7 +391,7 @@ ${logs}` : ""}`);
|
|
|
641
391
|
return { outputDir: this.#outputDir };
|
|
642
392
|
}
|
|
643
393
|
get #outputDir() {
|
|
644
|
-
return
|
|
394
|
+
return path5.join(this.#command === "build" ? this.#app.dist.cwdPath : this.#app.cwdPath, this.#command === "build" ? "csr" : ".akan/artifact/csr");
|
|
645
395
|
}
|
|
646
396
|
#define() {
|
|
647
397
|
const nodeEnv = this.#command === "build" ? "production" : "development";
|
|
@@ -672,8 +422,8 @@ ${logs}` : ""}`);
|
|
|
672
422
|
];
|
|
673
423
|
}
|
|
674
424
|
async#loadCsrArtifact() {
|
|
675
|
-
const artifactDir =
|
|
676
|
-
const artifactFile = Bun.file(
|
|
425
|
+
const artifactDir = path5.join(this.#command === "build" ? this.#app.dist.cwdPath : this.#app.cwdPath, ".akan/artifact");
|
|
426
|
+
const artifactFile = Bun.file(path5.join(artifactDir, "base-artifact.json"));
|
|
677
427
|
if (!await artifactFile.exists())
|
|
678
428
|
return { cssAssets: {} };
|
|
679
429
|
const artifact = await artifactFile.json();
|
|
@@ -686,7 +436,7 @@ ${logs}` : ""}`);
|
|
|
686
436
|
const htmlFile = Bun.file(htmlPath);
|
|
687
437
|
if (!await htmlFile.exists())
|
|
688
438
|
continue;
|
|
689
|
-
const basePath =
|
|
439
|
+
const basePath = path5.basename(htmlPath, ".html") === "index" ? "" : path5.basename(htmlPath, ".html");
|
|
690
440
|
const inlined = await this.#inlineHtmlAssets(await htmlFile.text(), htmlPath, cssAssets[basePath]);
|
|
691
441
|
for (const filePath of inlined.jsFiles)
|
|
692
442
|
jsFiles.add(filePath);
|
|
@@ -728,7 +478,7 @@ ${remainingAssets.join(`
|
|
|
728
478
|
next = CsrArtifactBuilder.injectBeforeHeadEnd(next, style);
|
|
729
479
|
}
|
|
730
480
|
if (cssAsset) {
|
|
731
|
-
const cssPath =
|
|
481
|
+
const cssPath = path5.join(this.#command === "build" ? this.#app.dist.cwdPath : this.#app.cwdPath, ".akan/artifact", cssAsset.cssRelPath);
|
|
732
482
|
const css = await Bun.file(cssPath).text();
|
|
733
483
|
const style = CsrArtifactBuilder.createInlineStyle(css);
|
|
734
484
|
if (!next.includes(style))
|
|
@@ -799,12 +549,12 @@ ${CsrArtifactBuilder.escapeInlineScript(await loadScript(src))}
|
|
|
799
549
|
throw new Error(`[csr-build] cannot inline external script: ${src}`);
|
|
800
550
|
}
|
|
801
551
|
const normalized = src.startsWith("/") ? src.slice(1) : src;
|
|
802
|
-
return
|
|
552
|
+
return path5.resolve(path5.dirname(htmlPath), normalized);
|
|
803
553
|
}
|
|
804
554
|
}
|
|
805
555
|
|
|
806
556
|
// pkgs/@akanjs/devkit/frontendBuild/cssCompiler.ts
|
|
807
|
-
import
|
|
557
|
+
import path7 from "path";
|
|
808
558
|
import { Logger } from "akanjs/common";
|
|
809
559
|
import { compile } from "tailwindcss";
|
|
810
560
|
|
|
@@ -875,7 +625,7 @@ class CssCandidateCache {
|
|
|
875
625
|
}
|
|
876
626
|
|
|
877
627
|
// pkgs/@akanjs/devkit/frontendBuild/cssImportResolver.ts
|
|
878
|
-
import
|
|
628
|
+
import path6 from "path";
|
|
879
629
|
var CSS_IMPORT_EXTS = ["", ".css", "/styles.css", "/index.css"];
|
|
880
630
|
|
|
881
631
|
class CssImportResolver {
|
|
@@ -928,7 +678,7 @@ class CssImportResolver {
|
|
|
928
678
|
const exact = this.#paths[id];
|
|
929
679
|
if (exact) {
|
|
930
680
|
for (const repl of exact) {
|
|
931
|
-
const resolved = await this.#firstExisting(
|
|
681
|
+
const resolved = await this.#firstExisting(path6.resolve(this.#workspaceRoot, repl));
|
|
932
682
|
if (resolved)
|
|
933
683
|
return resolved;
|
|
934
684
|
}
|
|
@@ -939,7 +689,7 @@ class CssImportResolver {
|
|
|
939
689
|
const suffix = id.slice(prefix.length);
|
|
940
690
|
for (const repl of replacements) {
|
|
941
691
|
const replPath = repl.endsWith("/*") ? repl.slice(0, -1) : repl;
|
|
942
|
-
const resolved = await this.#firstExisting(
|
|
692
|
+
const resolved = await this.#firstExisting(path6.resolve(this.#workspaceRoot, replPath + suffix));
|
|
943
693
|
if (resolved)
|
|
944
694
|
return resolved;
|
|
945
695
|
}
|
|
@@ -969,25 +719,29 @@ class CssImportResolver {
|
|
|
969
719
|
try {
|
|
970
720
|
if (!await Bun.file(pkgPath).exists())
|
|
971
721
|
return null;
|
|
972
|
-
const pkgDir =
|
|
722
|
+
const pkgDir = path6.dirname(pkgPath);
|
|
973
723
|
const pkg = await Bun.file(pkgPath).json();
|
|
974
724
|
const subpath = id === pkgName ? "." : `.${id.slice(pkgName.length)}`;
|
|
975
725
|
const exportValue = pkg.exports?.[subpath];
|
|
976
|
-
const
|
|
977
|
-
|
|
726
|
+
const exportedEntry = typeof exportValue === "string" ? exportValue : exportValue?.style || exportValue?.import || exportValue?.default;
|
|
727
|
+
if (exportedEntry)
|
|
728
|
+
return await this.#firstExisting(path6.resolve(pkgDir, exportedEntry));
|
|
729
|
+
if (subpath !== ".")
|
|
730
|
+
return await this.#firstExisting(path6.resolve(pkgDir, subpath));
|
|
731
|
+
return await this.#firstExisting(path6.resolve(pkgDir, pkg.exports?.["."]?.style || pkg.style || "index.css"));
|
|
978
732
|
} catch {
|
|
979
733
|
return null;
|
|
980
734
|
}
|
|
981
735
|
}
|
|
982
736
|
#resolutionBases(fromBase) {
|
|
983
|
-
return [fromBase, this.#workspaceRoot,
|
|
737
|
+
return [fromBase, this.#workspaceRoot, path6.dirname(Bun.main), path6.resolve(path6.dirname(Bun.main), "../..")];
|
|
984
738
|
}
|
|
985
739
|
#packageJsonCandidates(pkgName) {
|
|
986
740
|
return [
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
741
|
+
path6.join(this.#workspaceRoot, "pkgs", pkgName, "package.json"),
|
|
742
|
+
path6.join(this.#workspaceRoot, "node_modules", pkgName, "package.json"),
|
|
743
|
+
path6.join(path6.dirname(Bun.main), "node_modules", pkgName, "package.json"),
|
|
744
|
+
path6.join(path6.dirname(Bun.main), "../../", pkgName, "package.json")
|
|
991
745
|
];
|
|
992
746
|
}
|
|
993
747
|
async#firstExisting(basePath) {
|
|
@@ -1005,7 +759,7 @@ class CssImportResolver {
|
|
|
1005
759
|
return parts[0] ?? null;
|
|
1006
760
|
}
|
|
1007
761
|
static isCssFile(filePath) {
|
|
1008
|
-
return
|
|
762
|
+
return path6.extname(filePath) === ".css";
|
|
1009
763
|
}
|
|
1010
764
|
}
|
|
1011
765
|
|
|
@@ -1024,13 +778,15 @@ class CssCompiler {
|
|
|
1024
778
|
this.#app = app;
|
|
1025
779
|
}
|
|
1026
780
|
get #candidateCachePath() {
|
|
1027
|
-
return
|
|
781
|
+
return path7.join(this.#app.cwdPath, ".akan/cache/cssCandidates.json");
|
|
1028
782
|
}
|
|
1029
783
|
#cssText = null;
|
|
1030
784
|
#cssTextByBasePath = null;
|
|
1031
785
|
#fileExistsCache = new Map;
|
|
1032
786
|
#resolvedFileCache = new Map;
|
|
1033
787
|
#resolvedSpecifierCache = new Map;
|
|
788
|
+
#discoveredCssPaths = new Set;
|
|
789
|
+
importedStylesheetsByBasePath = {};
|
|
1034
790
|
#fileExists(absPath) {
|
|
1035
791
|
let cached = this.#fileExistsCache.get(absPath);
|
|
1036
792
|
if (!cached) {
|
|
@@ -1052,7 +808,7 @@ class CssCompiler {
|
|
|
1052
808
|
return filePath;
|
|
1053
809
|
}
|
|
1054
810
|
for (const ext of SOURCE_EXTS) {
|
|
1055
|
-
const filePath =
|
|
811
|
+
const filePath = path7.join(absPathNoExt, `index${ext}`);
|
|
1056
812
|
if (await this.#fileExists(filePath))
|
|
1057
813
|
return filePath;
|
|
1058
814
|
}
|
|
@@ -1064,13 +820,20 @@ class CssCompiler {
|
|
|
1064
820
|
async getCss({ refresh } = {}) {
|
|
1065
821
|
if (this.#cssText !== null && !refresh)
|
|
1066
822
|
return this.#cssText;
|
|
823
|
+
this.#discoveredCssPaths.clear();
|
|
824
|
+
this.importedStylesheetsByBasePath = {};
|
|
1067
825
|
const { cssPaths, sourcePaths } = await this.discoverCssAndSources({ refresh });
|
|
1068
|
-
|
|
826
|
+
const { css, imported } = await this.#compileWithImports(cssPaths, sourcePaths);
|
|
827
|
+
this.#cssText = css;
|
|
828
|
+
this.importedStylesheetsByBasePath = { "": imported };
|
|
829
|
+
await this.#warnUnreachableStylesheets();
|
|
1069
830
|
return this.#cssText;
|
|
1070
831
|
}
|
|
1071
832
|
async getCssByBasePath({ refresh } = {}) {
|
|
1072
833
|
if (this.#cssTextByBasePath !== null && !refresh)
|
|
1073
834
|
return this.#cssTextByBasePath;
|
|
835
|
+
this.#discoveredCssPaths.clear();
|
|
836
|
+
this.importedStylesheetsByBasePath = {};
|
|
1074
837
|
const akanConfig = await this.#app.getConfig({ refresh });
|
|
1075
838
|
const pageKeys = await this.#app.getPageKeys({ refresh });
|
|
1076
839
|
const basePaths = [...akanConfig.basePaths];
|
|
@@ -1081,7 +844,8 @@ class CssCompiler {
|
|
|
1081
844
|
return ["", ""];
|
|
1082
845
|
const started = Date.now();
|
|
1083
846
|
const { cssPaths, sourcePaths } = await this.discoverCssAndSources({ refresh, pageKeys: rootPageKeys });
|
|
1084
|
-
const css = await this
|
|
847
|
+
const { css, imported } = await this.#compileWithImports(cssPaths, sourcePaths);
|
|
848
|
+
this.importedStylesheetsByBasePath[""] = imported;
|
|
1085
849
|
this.#logger.verbose(`css base=root paths=${cssPaths.length} sources=${sourcePaths.length} in ${Date.now() - started}ms`);
|
|
1086
850
|
return ["", css];
|
|
1087
851
|
})(),
|
|
@@ -1091,14 +855,27 @@ class CssCompiler {
|
|
|
1091
855
|
return [basePath, ""];
|
|
1092
856
|
const started = Date.now();
|
|
1093
857
|
const { cssPaths, sourcePaths } = await this.discoverCssAndSources({ refresh, pageKeys: basePathPageKeys });
|
|
1094
|
-
const css = await this
|
|
858
|
+
const { css, imported } = await this.#compileWithImports(cssPaths, sourcePaths);
|
|
859
|
+
this.importedStylesheetsByBasePath[basePath] = imported;
|
|
1095
860
|
this.#logger.verbose(`css base=${basePath} paths=${cssPaths.length} sources=${sourcePaths.length} in ${Date.now() - started}ms`);
|
|
1096
861
|
return [basePath, css];
|
|
1097
862
|
})
|
|
1098
863
|
]);
|
|
1099
864
|
this.#cssTextByBasePath = Object.fromEntries(cssEntries);
|
|
865
|
+
await this.#warnUnreachableStylesheets();
|
|
1100
866
|
return this.#cssTextByBasePath;
|
|
1101
867
|
}
|
|
868
|
+
async#warnUnreachableStylesheets() {
|
|
869
|
+
const pageDir = path7.join(this.#app.cwdPath, "page");
|
|
870
|
+
const glob = new Bun.Glob("**/*.css");
|
|
871
|
+
for await (const cssPath of glob.scan({ cwd: pageDir, absolute: true })) {
|
|
872
|
+
if (cssPath.includes(`${path7.sep}(libs)${path7.sep}`))
|
|
873
|
+
continue;
|
|
874
|
+
if (this.#discoveredCssPaths.has(cssPath))
|
|
875
|
+
continue;
|
|
876
|
+
this.#logger.warn(`css ${path7.relative(this.#app.cwdPath, cssPath)} is imported by no route and never compiled`);
|
|
877
|
+
}
|
|
878
|
+
}
|
|
1102
879
|
async discoverCss({ refresh } = {}) {
|
|
1103
880
|
const { cssPaths } = await this.discoverCssAndSources({ refresh });
|
|
1104
881
|
return cssPaths;
|
|
@@ -1108,7 +885,7 @@ class CssCompiler {
|
|
|
1108
885
|
pageKeys
|
|
1109
886
|
} = {}) {
|
|
1110
887
|
pageKeys ??= await this.#app.getPageKeys({ refresh });
|
|
1111
|
-
const seeds = pageKeys.map((key) =>
|
|
888
|
+
const seeds = pageKeys.map((key) => path7.resolve(this.#app.cwdPath, "page", key));
|
|
1112
889
|
const cssFiles = new Set;
|
|
1113
890
|
const sourceFiles = new Set;
|
|
1114
891
|
const queue = [...seeds];
|
|
@@ -1140,7 +917,7 @@ class CssCompiler {
|
|
|
1140
917
|
} catch {
|
|
1141
918
|
continue;
|
|
1142
919
|
}
|
|
1143
|
-
const importerDir =
|
|
920
|
+
const importerDir = path7.dirname(filePath);
|
|
1144
921
|
for (const imp of imports) {
|
|
1145
922
|
const spec = imp.path;
|
|
1146
923
|
if (!spec)
|
|
@@ -1158,21 +935,47 @@ class CssCompiler {
|
|
|
1158
935
|
queue.push(resolved);
|
|
1159
936
|
}
|
|
1160
937
|
}
|
|
1161
|
-
|
|
938
|
+
const tokenPaths = await this.#libTokenStylesheets(sourceFiles);
|
|
939
|
+
const cssPaths = [...new Set([...tokenPaths, ...cssFiles])];
|
|
940
|
+
for (const cssPath of cssPaths)
|
|
941
|
+
this.#discoveredCssPaths.add(cssPath);
|
|
942
|
+
return { cssPaths, sourcePaths: [...sourceFiles] };
|
|
943
|
+
}
|
|
944
|
+
async#libTokenStylesheets(sourceFiles) {
|
|
945
|
+
const libsRoot = path7.join(this.#app.workspace.workspaceRoot, "libs");
|
|
946
|
+
const libNames = new Set;
|
|
947
|
+
for (const filePath of sourceFiles) {
|
|
948
|
+
const relPath = path7.relative(libsRoot, filePath);
|
|
949
|
+
if (relPath.startsWith("..") || path7.isAbsolute(relPath))
|
|
950
|
+
continue;
|
|
951
|
+
const [libName] = relPath.split(path7.sep);
|
|
952
|
+
if (libName)
|
|
953
|
+
libNames.add(libName);
|
|
954
|
+
}
|
|
955
|
+
const tokenPaths = await Promise.all([...libNames].sort().map(async (libName) => {
|
|
956
|
+
const tokensPath = path7.join(libsRoot, libName, "ui/tokens.css");
|
|
957
|
+
return await this.#fileExists(tokensPath) ? tokensPath : null;
|
|
958
|
+
}));
|
|
959
|
+
return tokenPaths.filter((tokensPath) => !!tokensPath);
|
|
1162
960
|
}
|
|
1163
961
|
async compileCss(cssPaths, sourcePaths) {
|
|
962
|
+
const { css } = await this.#compileWithImports(cssPaths, sourcePaths);
|
|
963
|
+
return css;
|
|
964
|
+
}
|
|
965
|
+
async#compileWithImports(cssPaths, sourcePaths) {
|
|
1164
966
|
if (cssPaths.length === 0)
|
|
1165
|
-
return "";
|
|
967
|
+
return { css: "", imported: [] };
|
|
1166
968
|
const compileStarted = Date.now();
|
|
1167
969
|
const compilers = await Promise.all(cssPaths.map(async (cssPath) => {
|
|
1168
970
|
const css = await Bun.file(cssPath).text();
|
|
1169
|
-
const base =
|
|
971
|
+
const base = path7.dirname(cssPath);
|
|
972
|
+
const imported2 = new Map;
|
|
1170
973
|
const compiler = await compile(css, {
|
|
1171
974
|
base,
|
|
1172
|
-
loadStylesheet: (id, fromBase) => this.#loadStylesheet(id, fromBase),
|
|
975
|
+
loadStylesheet: (id, fromBase) => this.#loadStylesheet(id, fromBase, imported2),
|
|
1173
976
|
loadModule: (id, fromBase) => this.#loadModule(id, fromBase)
|
|
1174
977
|
});
|
|
1175
|
-
return { cssPath, compiler };
|
|
978
|
+
return { cssPath, compiler, imported: imported2 };
|
|
1176
979
|
}));
|
|
1177
980
|
const sourceDirs = new Set;
|
|
1178
981
|
for (const entry of compilers) {
|
|
@@ -1185,23 +988,39 @@ class CssCompiler {
|
|
|
1185
988
|
const candidates = await this.#scanCandidates(sourcePaths, [...sourceDirs]);
|
|
1186
989
|
this.#logger.verbose(`css candidates scanned count=${candidates.length} sources=${sourcePaths.length} dirs=${sourceDirs.size} in ${Date.now() - scanStarted}ms`);
|
|
1187
990
|
const parts = [];
|
|
991
|
+
const imported = [];
|
|
1188
992
|
for (const entry of compilers) {
|
|
1189
993
|
if (!entry)
|
|
1190
994
|
continue;
|
|
1191
|
-
|
|
995
|
+
const part = entry.compiler.build(candidates);
|
|
996
|
+
parts.push(part);
|
|
997
|
+
for (const [cssPath, content] of entry.imported) {
|
|
998
|
+
const declaredNames = declaredCustomProperties(content);
|
|
999
|
+
imported.push({ cssPath, declaredNames });
|
|
1000
|
+
if (declaredNames.length === 0 || declaredNames.some((name) => part.includes(`${name}:`)))
|
|
1001
|
+
continue;
|
|
1002
|
+
this.#logger.warn(`css @import ${cssPath} was loaded by ${entry.cssPath} but none of its ${declaredNames.length} declaration(s) reached the compiled CSS`);
|
|
1003
|
+
}
|
|
1192
1004
|
}
|
|
1193
1005
|
this.#logger.verbose(`css compiled paths=${cssPaths.length} candidates=${candidates.length} in ${Date.now() - compileStarted}ms`);
|
|
1194
|
-
return parts.join(`
|
|
1195
|
-
`);
|
|
1006
|
+
return { css: parts.join(`
|
|
1007
|
+
`), imported };
|
|
1196
1008
|
}
|
|
1197
|
-
async#loadStylesheet(id, fromBase) {
|
|
1009
|
+
async#loadStylesheet(id, fromBase, imported) {
|
|
1198
1010
|
const p = await this.#resolveCssImport(id, fromBase);
|
|
1011
|
+
this.#discoveredCssPaths.add(p);
|
|
1199
1012
|
const content = await Bun.file(p).text();
|
|
1200
|
-
|
|
1013
|
+
imported?.set(p, content);
|
|
1014
|
+
this.#logger.verbose(`css import "${id}" from ${fromBase} -> ${p} (${content.length} bytes)`);
|
|
1015
|
+
return { path: p, base: path7.dirname(p), content };
|
|
1201
1016
|
}
|
|
1202
1017
|
async#resolveCssImport(id, fromBase) {
|
|
1203
|
-
if (id.startsWith(".") || id.startsWith("/"))
|
|
1204
|
-
|
|
1018
|
+
if (id.startsWith(".") || id.startsWith("/")) {
|
|
1019
|
+
const filePath = path7.resolve(fromBase, id);
|
|
1020
|
+
if (await this.#fileExists(filePath))
|
|
1021
|
+
return filePath;
|
|
1022
|
+
throw new Error(`[css] failed to resolve stylesheet import "${id}" from ${fromBase} (no file at ${filePath})`);
|
|
1023
|
+
}
|
|
1205
1024
|
const resolver = await this.#getCssImportResolver();
|
|
1206
1025
|
const resolved = await resolver.resolve(id, fromBase);
|
|
1207
1026
|
if (resolved)
|
|
@@ -1217,7 +1036,7 @@ class CssCompiler {
|
|
|
1217
1036
|
async#loadModule(id, fromBase) {
|
|
1218
1037
|
const p = __require.resolve(id, { paths: [fromBase] });
|
|
1219
1038
|
const mod = await import(p);
|
|
1220
|
-
return { path: p, base:
|
|
1039
|
+
return { path: p, base: path7.dirname(p), module: mod.default ?? mod };
|
|
1221
1040
|
}
|
|
1222
1041
|
#resolveSourceImport(id, fromBase, resolvePackage) {
|
|
1223
1042
|
const cacheKey = `${fromBase}\x00${id}`;
|
|
@@ -1230,7 +1049,7 @@ class CssCompiler {
|
|
|
1230
1049
|
}
|
|
1231
1050
|
async#resolveSourceImportUncached(id, fromBase, resolvePackage) {
|
|
1232
1051
|
if (id.startsWith(".") || id.startsWith("/")) {
|
|
1233
|
-
const abs = id.startsWith("/") ? id :
|
|
1052
|
+
const abs = id.startsWith("/") ? id : path7.resolve(fromBase, id);
|
|
1234
1053
|
return this.#resolveSourceFileCandidate(abs);
|
|
1235
1054
|
}
|
|
1236
1055
|
const pkg = await resolvePackage(id);
|
|
@@ -1281,21 +1100,25 @@ function resolveSourceWithRequire(id, fromBase) {
|
|
|
1281
1100
|
}
|
|
1282
1101
|
}
|
|
1283
1102
|
function isSourceFile(filePath) {
|
|
1284
|
-
return SOURCE_EXTS.includes(
|
|
1103
|
+
return SOURCE_EXTS.includes(path7.extname(filePath));
|
|
1285
1104
|
}
|
|
1286
1105
|
function isIgnoredNodeModuleSource(filePath) {
|
|
1287
1106
|
return NODE_MODULES_RE.test(filePath) && !AKANJS_NODE_MODULE_RE.test(filePath);
|
|
1288
1107
|
}
|
|
1108
|
+
function declaredCustomProperties(css) {
|
|
1109
|
+
const withoutThemeBlocks = css.replace(/@theme[^{]*\{[^}]*\}/g, "");
|
|
1110
|
+
return [...new Set([...withoutThemeBlocks.matchAll(/(?:^|[\s;{])(--[\w-]+)\s*:/g)].map(([, name]) => name))].filter((name) => !!name);
|
|
1111
|
+
}
|
|
1289
1112
|
function getPageKeyBasePath(pageKey, basePaths) {
|
|
1290
|
-
const normalized = pageKey.split(
|
|
1113
|
+
const normalized = pageKey.split(path7.sep).join("/").replace(/^\.\//, "");
|
|
1291
1114
|
const segments = normalized.split("/");
|
|
1292
1115
|
const firstPublicSegment = segments.find((segment) => segment !== "[lang]" && !/^\(.+\)$/.test(segment));
|
|
1293
1116
|
return firstPublicSegment && basePaths.includes(firstPublicSegment) ? firstPublicSegment : null;
|
|
1294
1117
|
}
|
|
1295
1118
|
|
|
1296
1119
|
// pkgs/@akanjs/devkit/frontendBuild/fontOptimizer.ts
|
|
1297
|
-
import { mkdir as
|
|
1298
|
-
import
|
|
1120
|
+
import { mkdir as mkdir2, stat as stat2 } from "fs/promises";
|
|
1121
|
+
import path8 from "path";
|
|
1299
1122
|
import ts2 from "typescript";
|
|
1300
1123
|
var FONT_URL_PREFIX = "/_akan/fonts";
|
|
1301
1124
|
var DEFAULT_FONT_SUBSETS = ["latin"];
|
|
@@ -1312,7 +1135,7 @@ class FontOptimizer {
|
|
|
1312
1135
|
constructor(app, command = "start") {
|
|
1313
1136
|
this.#app = app;
|
|
1314
1137
|
this.#command = command;
|
|
1315
|
-
this.#artifactRoot =
|
|
1138
|
+
this.#artifactRoot = path8.join(command === "build" ? app.dist.cwdPath : app.cwdPath, ".akan/artifact");
|
|
1316
1139
|
}
|
|
1317
1140
|
async optimize() {
|
|
1318
1141
|
const fonts = await this.discoverFonts();
|
|
@@ -1337,7 +1160,7 @@ class FontOptimizer {
|
|
|
1337
1160
|
return result;
|
|
1338
1161
|
}
|
|
1339
1162
|
get #cachePath() {
|
|
1340
|
-
return
|
|
1163
|
+
return path8.join(this.#artifactRoot, "fontCache.json");
|
|
1341
1164
|
}
|
|
1342
1165
|
async#buildCacheKey(fonts) {
|
|
1343
1166
|
const sources = [];
|
|
@@ -1352,7 +1175,7 @@ class FontOptimizer {
|
|
|
1352
1175
|
sources.push({ optimizedSrc: face.optimizedSrc, ...stamp });
|
|
1353
1176
|
}
|
|
1354
1177
|
for (const filePath of font.subsetFiles ?? []) {
|
|
1355
|
-
const abs =
|
|
1178
|
+
const abs = path8.isAbsolute(filePath) ? filePath : path8.join(this.#app.cwdPath, filePath);
|
|
1356
1179
|
const stamp = await this.#fileStamp(abs);
|
|
1357
1180
|
if (!stamp)
|
|
1358
1181
|
return null;
|
|
@@ -1375,7 +1198,7 @@ class FontOptimizer {
|
|
|
1375
1198
|
const cache = await Bun.file(this.#cachePath).json().catch(() => null);
|
|
1376
1199
|
if (cache?.version !== FontOptimizer.#cacheVersion || cache.key !== key)
|
|
1377
1200
|
return null;
|
|
1378
|
-
const files = cache.files.map((relativePath) =>
|
|
1201
|
+
const files = cache.files.map((relativePath) => path8.join(this.#artifactRoot, relativePath));
|
|
1379
1202
|
for (const filePath of files) {
|
|
1380
1203
|
if (!await Bun.file(filePath).exists())
|
|
1381
1204
|
return null;
|
|
@@ -1387,7 +1210,7 @@ class FontOptimizer {
|
|
|
1387
1210
|
version: FontOptimizer.#cacheVersion,
|
|
1388
1211
|
key,
|
|
1389
1212
|
css: result.css,
|
|
1390
|
-
files: result.files.map((filePath) =>
|
|
1213
|
+
files: result.files.map((filePath) => path8.relative(this.#artifactRoot, filePath))
|
|
1391
1214
|
};
|
|
1392
1215
|
await Bun.write(this.#cachePath, JSON.stringify(cache));
|
|
1393
1216
|
}
|
|
@@ -1395,7 +1218,7 @@ class FontOptimizer {
|
|
|
1395
1218
|
const pageKeys = await this.#app.getPageKeys();
|
|
1396
1219
|
const fonts = [];
|
|
1397
1220
|
await Promise.all(pageKeys.map(async (key) => {
|
|
1398
|
-
const filePath =
|
|
1221
|
+
const filePath = path8.resolve(this.#app.cwdPath, "page", key);
|
|
1399
1222
|
const file = Bun.file(filePath);
|
|
1400
1223
|
if (!await file.exists())
|
|
1401
1224
|
return;
|
|
@@ -1414,8 +1237,8 @@ class FontOptimizer {
|
|
|
1414
1237
|
this.#app.logger.warn(`[font] source not found: ${face.src}`);
|
|
1415
1238
|
continue;
|
|
1416
1239
|
}
|
|
1417
|
-
const outputPath =
|
|
1418
|
-
await
|
|
1240
|
+
const outputPath = path8.join(this.#artifactRoot, face.optimizedSrc.replace(/^\/_akan\//, ""));
|
|
1241
|
+
await mkdir2(path8.dirname(outputPath), { recursive: true });
|
|
1419
1242
|
const sourceBuffer = Buffer.from(await Bun.file(sourcePath).arrayBuffer());
|
|
1420
1243
|
const outputBuffer = await this.#buildFontBuffer(font, sourceBuffer, sourcePath);
|
|
1421
1244
|
await Bun.write(outputPath, outputBuffer);
|
|
@@ -1562,8 +1385,8 @@ class FontOptimizer {
|
|
|
1562
1385
|
return null;
|
|
1563
1386
|
const rel = src.replace(/^\//, "");
|
|
1564
1387
|
const candidates = [
|
|
1565
|
-
this.#command === "build" ?
|
|
1566
|
-
|
|
1388
|
+
this.#command === "build" ? path8.join(this.#app.dist.cwdPath, "public", rel) : null,
|
|
1389
|
+
path8.join(this.#app.cwdPath, "public", rel),
|
|
1567
1390
|
this.#resolveWorkspacePublicPath(rel)
|
|
1568
1391
|
].filter(Boolean);
|
|
1569
1392
|
for (const candidate of candidates) {
|
|
@@ -1598,7 +1421,7 @@ class FontOptimizer {
|
|
|
1598
1421
|
return "woff2";
|
|
1599
1422
|
if (signature === "OTTO")
|
|
1600
1423
|
return "otf";
|
|
1601
|
-
const ext =
|
|
1424
|
+
const ext = path8.extname(sourcePath).slice(1).toLowerCase();
|
|
1602
1425
|
if (ext === "otf" || ext === "woff" || ext === "woff2")
|
|
1603
1426
|
return ext;
|
|
1604
1427
|
return "ttf";
|
|
@@ -1607,7 +1430,7 @@ class FontOptimizer {
|
|
|
1607
1430
|
const [root, dep, ...rest] = rel.split("/");
|
|
1608
1431
|
if (root !== "libs" || !dep || rest.length === 0)
|
|
1609
1432
|
return null;
|
|
1610
|
-
return
|
|
1433
|
+
return path8.join(this.#app.workspace.workspaceRoot, "libs", dep, "public", ...rest);
|
|
1611
1434
|
}
|
|
1612
1435
|
async#getSubsetText(font) {
|
|
1613
1436
|
const parts = new Set;
|
|
@@ -1617,7 +1440,7 @@ class FontOptimizer {
|
|
|
1617
1440
|
if (font.subsetText)
|
|
1618
1441
|
parts.add(font.subsetText);
|
|
1619
1442
|
for (const filePath of font.subsetFiles ?? []) {
|
|
1620
|
-
const abs =
|
|
1443
|
+
const abs = path8.isAbsolute(filePath) ? filePath : path8.join(this.#app.cwdPath, filePath);
|
|
1621
1444
|
const file = Bun.file(abs);
|
|
1622
1445
|
if (await file.exists())
|
|
1623
1446
|
parts.add(await file.text());
|
|
@@ -1637,7 +1460,7 @@ class FontOptimizer {
|
|
|
1637
1460
|
}
|
|
1638
1461
|
async#collectAutoSubsetText() {
|
|
1639
1462
|
const libPageRoots = (await this.#app.getPageRoots()).filter((root) => root.keyPrefix).map((root) => root.dir);
|
|
1640
|
-
const roots = [...["page", "ui"].map((dir) =>
|
|
1463
|
+
const roots = [...["page", "ui"].map((dir) => path8.join(this.#app.cwdPath, dir)), ...libPageRoots];
|
|
1641
1464
|
const glob = new Bun.Glob("**/*.{ts,tsx,js,jsx,html,md}");
|
|
1642
1465
|
const parts = [];
|
|
1643
1466
|
await Promise.all(roots.map(async (root) => {
|
|
@@ -1753,17 +1576,16 @@ ${declarations.map(([prop, value]) => ` ${prop}: ${value};`).join(`
|
|
|
1753
1576
|
}
|
|
1754
1577
|
|
|
1755
1578
|
// pkgs/@akanjs/devkit/frontendBuild/pagesBundleBuilder.ts
|
|
1756
|
-
import
|
|
1579
|
+
import path10 from "path";
|
|
1757
1580
|
|
|
1758
1581
|
// pkgs/@akanjs/devkit/transforms/externalizeFrameworkPlugin.ts
|
|
1759
|
-
import
|
|
1582
|
+
import path9 from "path";
|
|
1760
1583
|
var DEFAULT_INCLUDE = ["akanjs/", "@apps/", "@libs/"];
|
|
1761
1584
|
var DEFAULT_EXCLUDE_EXACT = new Set(["akanjs/webkit", "@akanjs/cli", "@akanjs/devkit"]);
|
|
1762
1585
|
var DEFAULT_EXCLUDE_PREFIX = ["@akanjs/cli/", "@akanjs/devkit/"];
|
|
1763
1586
|
var OPTIONAL_BACKEND_EXTERNAL_EXACT = new Set([
|
|
1764
1587
|
"@libsql/client",
|
|
1765
1588
|
"bullmq",
|
|
1766
|
-
"croner",
|
|
1767
1589
|
"ioredis",
|
|
1768
1590
|
"postgres",
|
|
1769
1591
|
"protobufjs"
|
|
@@ -1799,7 +1621,7 @@ async function createExternalizeFrameworkPlugin(options) {
|
|
|
1799
1621
|
const replPath = repl?.endsWith("/*") ? repl.slice(0, -1) : repl ?? "";
|
|
1800
1622
|
if (!replPath)
|
|
1801
1623
|
continue;
|
|
1802
|
-
const candidate =
|
|
1624
|
+
const candidate = path9.resolve(workspaceRoot, replPath + suffix);
|
|
1803
1625
|
const hit = await firstExisting(candidate);
|
|
1804
1626
|
if (hit)
|
|
1805
1627
|
return hit;
|
|
@@ -1811,8 +1633,8 @@ async function createExternalizeFrameworkPlugin(options) {
|
|
|
1811
1633
|
if (spec === pkg)
|
|
1812
1634
|
continue;
|
|
1813
1635
|
const suffix = spec.slice(pkg.length + 1);
|
|
1814
|
-
const pkgDir =
|
|
1815
|
-
const candidate =
|
|
1636
|
+
const pkgDir = path9.dirname(path9.resolve(workspaceRoot, entryFile));
|
|
1637
|
+
const candidate = path9.join(pkgDir, suffix);
|
|
1816
1638
|
const hit = await firstExisting(candidate);
|
|
1817
1639
|
if (hit)
|
|
1818
1640
|
return hit;
|
|
@@ -1862,7 +1684,7 @@ async function firstExisting(basePath) {
|
|
|
1862
1684
|
return candidate;
|
|
1863
1685
|
}
|
|
1864
1686
|
for (const ext of CANDIDATE_EXTS) {
|
|
1865
|
-
const candidate =
|
|
1687
|
+
const candidate = path9.join(basePath, `index${ext}`);
|
|
1866
1688
|
if (await Bun.file(candidate).exists())
|
|
1867
1689
|
return candidate;
|
|
1868
1690
|
}
|
|
@@ -1953,11 +1775,11 @@ class PagesBundleBuilder {
|
|
|
1953
1775
|
const entryArtifact = result.outputs.find((a) => a.kind === "entry-point");
|
|
1954
1776
|
if (!entryArtifact)
|
|
1955
1777
|
throw new Error("[PagesBundleBuilder] Bun.build emitted no entry-point artifact");
|
|
1956
|
-
const bundlePath =
|
|
1778
|
+
const bundlePath = path10.resolve(entryArtifact.path);
|
|
1957
1779
|
const buildId = Date.now();
|
|
1958
1780
|
const outputBytes = result.outputs.reduce((sum, output) => sum + output.size, 0);
|
|
1959
1781
|
const chunkCount = result.outputs.filter((output) => output.kind === "chunk").length;
|
|
1960
|
-
this.#app.verbose(`[PagesBundleBuilder] ${
|
|
1782
|
+
this.#app.verbose(`[PagesBundleBuilder] ${path10.basename(bundlePath)} emitted in ${Date.now() - this.#started}ms splitting=${this.#splitting} entry=${entryArtifact.size} bytes outputs=${result.outputs.length} chunks=${chunkCount} total=${outputBytes} bytes`);
|
|
1961
1783
|
return {
|
|
1962
1784
|
bundlePath,
|
|
1963
1785
|
buildId,
|
|
@@ -2040,7 +1862,7 @@ function loaderFor2(absPath) {
|
|
|
2040
1862
|
|
|
2041
1863
|
// pkgs/@akanjs/devkit/frontendBuild/precompressArtifacts.ts
|
|
2042
1864
|
import fs2 from "fs";
|
|
2043
|
-
import
|
|
1865
|
+
import path11 from "path";
|
|
2044
1866
|
import zlib from "zlib";
|
|
2045
1867
|
var COMPRESSIBLE_EXTS = new Set([".css", ".html", ".js", ".json", ".svg"]);
|
|
2046
1868
|
var MIN_COMPRESS_BYTES = 1024;
|
|
@@ -2049,8 +1871,8 @@ var DEFAULT_BROTLI_QUALITY = 9;
|
|
|
2049
1871
|
var GZIP_LEVEL = 9;
|
|
2050
1872
|
async function precompressArtifacts(app) {
|
|
2051
1873
|
const roots = [
|
|
2052
|
-
|
|
2053
|
-
|
|
1874
|
+
path11.join(app.dist.cwdPath, ".akan/artifact/client"),
|
|
1875
|
+
path11.join(app.dist.cwdPath, ".akan/artifact/styles")
|
|
2054
1876
|
];
|
|
2055
1877
|
const result = { files: 0, inputBytes: 0, outputBytes: 0, brotliBytes: 0 };
|
|
2056
1878
|
await Promise.all(roots.map((root) => precompressRoot(root, result)));
|
|
@@ -2069,7 +1891,7 @@ async function precompressRoot(root, result) {
|
|
|
2069
1891
|
const bytes = await Bun.file(filePath).bytes();
|
|
2070
1892
|
const buffer = toArrayBuffer(bytes);
|
|
2071
1893
|
const gz = Bun.gzipSync(buffer, { level: GZIP_LEVEL });
|
|
2072
|
-
const br = brotliCompress(buffer,
|
|
1894
|
+
const br = brotliCompress(buffer, path11.extname(filePath).toLowerCase());
|
|
2073
1895
|
await Promise.all([Bun.write(`${filePath}.gz`, gz), Bun.write(`${filePath}.br`, br)]);
|
|
2074
1896
|
result.files += 1;
|
|
2075
1897
|
result.inputBytes += bytes.byteLength;
|
|
@@ -2089,7 +1911,7 @@ function brotliCompress(buffer, ext) {
|
|
|
2089
1911
|
async function shouldPrecompress(filePath) {
|
|
2090
1912
|
if (filePath.endsWith(".gz") || filePath.endsWith(".br"))
|
|
2091
1913
|
return false;
|
|
2092
|
-
if (!COMPRESSIBLE_EXTS.has(
|
|
1914
|
+
if (!COMPRESSIBLE_EXTS.has(path11.extname(filePath).toLowerCase()))
|
|
2093
1915
|
return false;
|
|
2094
1916
|
const file = Bun.file(filePath);
|
|
2095
1917
|
if (!await file.exists())
|
|
@@ -2108,7 +1930,7 @@ function formatBytes(bytes) {
|
|
|
2108
1930
|
}
|
|
2109
1931
|
|
|
2110
1932
|
// pkgs/@akanjs/devkit/frontendBuild/ssrBaseArtifactBuilder.ts
|
|
2111
|
-
import
|
|
1933
|
+
import path12 from "path";
|
|
2112
1934
|
import { optimize } from "@tailwindcss/node";
|
|
2113
1935
|
function prepareCssAsset(command, basePath, cssText) {
|
|
2114
1936
|
return optimize(cssText, { file: `${basePath || "root"}.css`, minify: command === "build" }).code;
|
|
@@ -2124,7 +1946,7 @@ class SsrBaseArtifactBuilder {
|
|
|
2124
1946
|
this.#app = app;
|
|
2125
1947
|
this.#command = command;
|
|
2126
1948
|
this.#artifactDir = `${command === "build" ? app.dist.cwdPath : app.cwdPath}/.akan/artifact`;
|
|
2127
|
-
this.#absArtifactDir =
|
|
1949
|
+
this.#absArtifactDir = path12.resolve(this.#artifactDir);
|
|
2128
1950
|
}
|
|
2129
1951
|
async build() {
|
|
2130
1952
|
const akanConfig = await this.#app.getConfig();
|
|
@@ -2146,7 +1968,7 @@ class SsrBaseArtifactBuilder {
|
|
|
2146
1968
|
rscRuntimeSsrManifest,
|
|
2147
1969
|
vendorMap,
|
|
2148
1970
|
cssAssets,
|
|
2149
|
-
pagesBundlePath: this.#command === "build" ?
|
|
1971
|
+
pagesBundlePath: this.#command === "build" ? path12.relative(this.#absArtifactDir, pagesBundle.bundlePath) : pagesBundle.bundlePath,
|
|
2150
1972
|
pagesBundleBuildId: pagesBundle.buildId,
|
|
2151
1973
|
domains: [...akanConfig.domains],
|
|
2152
1974
|
subRoutes: Object.fromEntries(Array.from(akanConfig.subRoutes.entries()).map(([basePath, domains]) => [basePath, [...domains]])),
|
|
@@ -2162,25 +1984,25 @@ class SsrBaseArtifactBuilder {
|
|
|
2162
1984
|
androidSha256CertFingerprints: target.deepLinks?.android?.sha256CertFingerprints
|
|
2163
1985
|
}))
|
|
2164
1986
|
};
|
|
2165
|
-
await Bun.write(
|
|
1987
|
+
await Bun.write(path12.join(this.#absArtifactDir, "base-artifact.json"), `${JSON.stringify(artifact, null, 2)}
|
|
2166
1988
|
`);
|
|
2167
1989
|
this.#app.verbose(`[base-artifact] complete in ${Date.now() - this.#started}ms`);
|
|
2168
1990
|
return { artifact, seedIndex, cssCompiler, optimizedFonts };
|
|
2169
1991
|
}
|
|
2170
1992
|
async#buildRuntimeClientEntries() {
|
|
2171
1993
|
const akanServerPath = await this.#resolveAkanServerPath();
|
|
2172
|
-
const rscClientEntry =
|
|
2173
|
-
const rscSegmentOutletEntry =
|
|
1994
|
+
const rscClientEntry = path12.resolve(akanServerPath, "rscClient.tsx");
|
|
1995
|
+
const rscSegmentOutletEntry = path12.resolve(akanServerPath, "rscSegmentOutlet.tsx");
|
|
2174
1996
|
const vendorEntries = VENDOR_SPECIFIERS.map((specifier) => ({
|
|
2175
1997
|
specifier,
|
|
2176
|
-
absPath:
|
|
1998
|
+
absPath: path12.resolve(akanServerPath, "vendor", `${specifier.replaceAll("/", "-").replaceAll(".", "-")}.ts`)
|
|
2177
1999
|
}));
|
|
2178
2000
|
const entries = [rscClientEntry, rscSegmentOutletEntry, ...vendorEntries.map((v) => v.absPath)];
|
|
2179
2001
|
const clientBundle = await new ClientEntriesBundler({ app: this.#app, entries, command: this.#command }).bundle();
|
|
2180
2002
|
const ssrBundle = await new ClientEntriesBundler({
|
|
2181
2003
|
app: this.#app,
|
|
2182
2004
|
entries: [rscSegmentOutletEntry],
|
|
2183
|
-
...RouteClientBuilder.
|
|
2005
|
+
...RouteClientBuilder.resolveSsrClientBundleOptions(this.#command),
|
|
2184
2006
|
outputSubdir: "client-ssr",
|
|
2185
2007
|
command: this.#command
|
|
2186
2008
|
}).bundle();
|
|
@@ -2210,15 +2032,15 @@ class SsrBaseArtifactBuilder {
|
|
|
2210
2032
|
async#resolveAkanServerPath() {
|
|
2211
2033
|
const candidates = [];
|
|
2212
2034
|
try {
|
|
2213
|
-
candidates.push(
|
|
2035
|
+
candidates.push(path12.dirname(Bun.resolveSync("akanjs/server", this.#app.workspace.workspaceRoot)));
|
|
2214
2036
|
} catch {}
|
|
2215
|
-
candidates.push(
|
|
2037
|
+
candidates.push(path12.join(this.#app.workspace.workspaceRoot, "pkgs/akanjs/server"), path12.join(this.#app.workspace.workspaceRoot, "node_modules/akanjs/server"));
|
|
2216
2038
|
try {
|
|
2217
|
-
candidates.push(
|
|
2039
|
+
candidates.push(path12.dirname(Bun.resolveSync("akanjs/server", path12.dirname(Bun.main))));
|
|
2218
2040
|
} catch {}
|
|
2219
|
-
candidates.push(
|
|
2041
|
+
candidates.push(path12.join(path12.dirname(Bun.main), "node_modules/akanjs/server"), path12.join(path12.dirname(Bun.main), "../../akanjs/server"), path12.resolve(import.meta.dir, "../../server"), path12.resolve(import.meta.dir, "../server"));
|
|
2220
2042
|
for (const candidate of candidates) {
|
|
2221
|
-
if (await Bun.file(
|
|
2043
|
+
if (await Bun.file(path12.join(candidate, "rscClient.tsx")).exists())
|
|
2222
2044
|
return candidate;
|
|
2223
2045
|
}
|
|
2224
2046
|
throw new Error(`[base-artifact] failed to locate akanjs/server; looked in: ${candidates.join(", ")}`);
|
|
@@ -2232,13 +2054,13 @@ class SsrBaseArtifactBuilder {
|
|
|
2232
2054
|
`);
|
|
2233
2055
|
if (!cssText)
|
|
2234
2056
|
return [];
|
|
2235
|
-
return [this.#writeCssAsset(basePath, cssText)];
|
|
2057
|
+
return [this.#writeCssAsset(basePath, cssText, cssCompiler.importedStylesheetsByBasePath[basePath] ?? [])];
|
|
2236
2058
|
})));
|
|
2237
2059
|
if (optimizedFonts.files.length > 0)
|
|
2238
2060
|
this.#app.verbose(`[base-artifact] optimized ${optimizedFonts.files.length} font files`);
|
|
2239
2061
|
return { cssCompiler, optimizedFonts, cssAssets };
|
|
2240
2062
|
}
|
|
2241
|
-
async#writeCssAsset(basePath, cssText) {
|
|
2063
|
+
async#writeCssAsset(basePath, cssText, imported) {
|
|
2242
2064
|
const cssAssetName = basePath || "root";
|
|
2243
2065
|
const preparedCssText = await prepareCssAsset(this.#command, basePath, cssText);
|
|
2244
2066
|
const cssHash = Bun.hash(`${basePath}
|
|
@@ -2247,10 +2069,18 @@ ${preparedCssText}`).toString(36);
|
|
|
2247
2069
|
`styles/${cssAssetName}-${cssHash}.css`,
|
|
2248
2070
|
`/_akan/styles/${cssAssetName}-${cssHash}.css`
|
|
2249
2071
|
];
|
|
2250
|
-
await Bun.write(
|
|
2072
|
+
await Bun.write(path12.join(this.#absArtifactDir, cssRelPath), preparedCssText);
|
|
2073
|
+
SsrBaseArtifactBuilder.#warnDroppedImports(this.#app, cssRelPath, preparedCssText, imported);
|
|
2251
2074
|
this.#app.verbose(`[base-artifact] wrote ${preparedCssText.length} bytes of CSS for ${basePath} -> ${cssRelPath}`);
|
|
2252
2075
|
return [basePath, { cssUrl, cssRelPath }];
|
|
2253
2076
|
}
|
|
2077
|
+
static #warnDroppedImports(app, cssRelPath, css, imported) {
|
|
2078
|
+
for (const { cssPath, declaredNames } of imported) {
|
|
2079
|
+
if (declaredNames.length === 0 || declaredNames.some((name) => css.includes(`${name}:`)))
|
|
2080
|
+
continue;
|
|
2081
|
+
app.logger.warn(`[base-artifact] @import ${cssPath} declares ${declaredNames.length} custom propert${declaredNames.length === 1 ? "y" : "ies"} and none of them are in ${cssRelPath}`);
|
|
2082
|
+
}
|
|
2083
|
+
}
|
|
2254
2084
|
}
|
|
2255
2085
|
// pkgs/@akanjs/devkit/frontendBuild/styleGuard.ts
|
|
2256
2086
|
var PREFIX = "(?:bg|text|border(?:-[tblrxy])?(?:-[se])?|ring(?:-offset)?|fill|stroke|shadow|from|to|via|divide|outline|decoration|placeholder|caret|accent)";
|
|
@@ -2258,210 +2088,8 @@ var PALETTE = "(?:slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|gre
|
|
|
2258
2088
|
var LEAD = "(^|[\\s\"'`{(\\[:!])";
|
|
2259
2089
|
var TAIL = "(?=[\\s\"'`})\\]:/!,]|$)";
|
|
2260
2090
|
var RAW_PALETTE_RE = new RegExp(`${LEAD}((?:${PREFIX})-)${PALETTE}-\\d{2,3}${TAIL}`, "g");
|
|
2261
|
-
var ARBITRARY_COLOR_RE = /\[(#[0-9a-fA-F]{3,8}|(?:rgb|rgba|hsl|hsla|oklch|oklab|lab|lch|hwb|color)\([^\]]*\))\]/g;
|
|
2262
2091
|
var INTERPOLATED_ARBITRARY_RE = new RegExp(`${LEAD}[a-z][a-z0-9-]*-\\[[^\\]\`]*\\$\\{`, "g");
|
|
2263
|
-
var STYLE_OBJECT_RE = /style=\{\{([\s\S]*?)\}\}/g;
|
|
2264
|
-
var STYLE_TAG_RE = /<style[^>]*>([\s\S]*?)<\/style>/g;
|
|
2265
|
-
var INLINE_COLOR_LITERAL_RE = /#[0-9a-fA-F]{3,8}\b|(?:rgb|rgba|hsl|hsla|oklch|oklab|lab|lch|hwb)\(/g;
|
|
2266
2092
|
var DAISYUI_LEGACY_RE = new RegExp(`${LEAD}(?:` + "btn-(?:primary|secondary|accent|neutral|info|success|warning|error|ghost|link|outline|square|circle|wide|block|xs|sm|md|lg)|" + "badge-(?:primary|secondary|accent|neutral|info|success|warning|error|ghost|outline)|" + "alert-(?:info|success|warning|error)|" + "input-(?:bordered|primary|secondary|accent|ghost|error)|" + "select-(?:bordered|primary|ghost)|textarea-(?:bordered|primary|ghost)|" + "checkbox-(?:primary|secondary|accent)|toggle-(?:primary|secondary|accent)|" + "loading-(?:spinner|dots|ring|ball|bars|infinity)|" + "card-(?:body|title|actions)|modal-(?:box|action|backdrop)|" + "collapse-(?:title|content|arrow|plus)|dropdown-(?:content|end|start|hover)|" + "stat-(?:title|value|desc)|tabs-(?:boxed|lifted|bordered)|tab-active|" + "menu-(?:title|dropdown)|steps-(?:horizontal|vertical)|join-item|" + "mockup-(?:code|phone|browser|window)|drawer-(?:side|content|toggle)" + `)${TAIL}`, "g");
|
|
2267
|
-
var
|
|
2268
|
-
|
|
2269
|
-
"arbitrary-color",
|
|
2270
|
-
"inline-color",
|
|
2271
|
-
"daisyui-legacy",
|
|
2272
|
-
"interpolated-arbitrary"
|
|
2273
|
-
];
|
|
2274
|
-
|
|
2275
|
-
class StyleGuard {
|
|
2276
|
-
run(files) {
|
|
2277
|
-
const out = [];
|
|
2278
|
-
for (const file of files) {
|
|
2279
|
-
const directives = this.#parseDirectives(file.content);
|
|
2280
|
-
const scan = this.#stripComments(file.content);
|
|
2281
|
-
const local = [];
|
|
2282
|
-
this.#scanClassLiterals(file, scan, local);
|
|
2283
|
-
this.#scanInlineColors(file, scan, local);
|
|
2284
|
-
for (const v of local)
|
|
2285
|
-
if (!this.#isSuppressed(v, directives))
|
|
2286
|
-
out.push(v);
|
|
2287
|
-
}
|
|
2288
|
-
return out;
|
|
2289
|
-
}
|
|
2290
|
-
#stripComments(src) {
|
|
2291
|
-
const out = src.split("");
|
|
2292
|
-
const blank = (i2) => {
|
|
2293
|
-
if (src[i2] !== `
|
|
2294
|
-
`)
|
|
2295
|
-
out[i2] = " ";
|
|
2296
|
-
};
|
|
2297
|
-
let state = "code";
|
|
2298
|
-
let i = 0;
|
|
2299
|
-
while (i < src.length) {
|
|
2300
|
-
const c = src[i];
|
|
2301
|
-
const d = src[i + 1];
|
|
2302
|
-
if (state === "code") {
|
|
2303
|
-
if (c === "/" && d === "/") {
|
|
2304
|
-
blank(i);
|
|
2305
|
-
blank(i + 1);
|
|
2306
|
-
state = "line";
|
|
2307
|
-
i += 2;
|
|
2308
|
-
} else if (c === "/" && d === "*") {
|
|
2309
|
-
blank(i);
|
|
2310
|
-
blank(i + 1);
|
|
2311
|
-
state = "block";
|
|
2312
|
-
i += 2;
|
|
2313
|
-
} else {
|
|
2314
|
-
if (c === "'")
|
|
2315
|
-
state = "squote";
|
|
2316
|
-
else if (c === '"')
|
|
2317
|
-
state = "dquote";
|
|
2318
|
-
else if (c === "`")
|
|
2319
|
-
state = "template";
|
|
2320
|
-
i++;
|
|
2321
|
-
}
|
|
2322
|
-
} else if (state === "line") {
|
|
2323
|
-
if (c === `
|
|
2324
|
-
`)
|
|
2325
|
-
state = "code";
|
|
2326
|
-
else
|
|
2327
|
-
blank(i);
|
|
2328
|
-
i++;
|
|
2329
|
-
} else if (state === "block") {
|
|
2330
|
-
if (c === "*" && d === "/") {
|
|
2331
|
-
blank(i);
|
|
2332
|
-
blank(i + 1);
|
|
2333
|
-
state = "code";
|
|
2334
|
-
i += 2;
|
|
2335
|
-
} else {
|
|
2336
|
-
blank(i);
|
|
2337
|
-
i++;
|
|
2338
|
-
}
|
|
2339
|
-
} else {
|
|
2340
|
-
const close = state === "squote" ? "'" : state === "dquote" ? '"' : "`";
|
|
2341
|
-
if (c === "\\")
|
|
2342
|
-
i += 2;
|
|
2343
|
-
else {
|
|
2344
|
-
if (c === close)
|
|
2345
|
-
state = "code";
|
|
2346
|
-
i++;
|
|
2347
|
-
}
|
|
2348
|
-
}
|
|
2349
|
-
}
|
|
2350
|
-
return out.join("");
|
|
2351
|
-
}
|
|
2352
|
-
#parseDirectives(content) {
|
|
2353
|
-
const directives = { file: null, nextLine: new Map };
|
|
2354
|
-
const lines = content.split(`
|
|
2355
|
-
`);
|
|
2356
|
-
for (let i = 0;i < lines.length; i++) {
|
|
2357
|
-
const line = lines[i];
|
|
2358
|
-
const nextLineMatch = line.match(/styleguard-disable-next-line\b(.*)$/);
|
|
2359
|
-
if (nextLineMatch) {
|
|
2360
|
-
directives.nextLine.set(i + 2, this.#extractRules(nextLineMatch[1]));
|
|
2361
|
-
continue;
|
|
2362
|
-
}
|
|
2363
|
-
const fileMatch = line.match(/styleguard-disable\b(.*)$/);
|
|
2364
|
-
if (fileMatch)
|
|
2365
|
-
directives.file = this.#mergeFileScope(directives.file, this.#extractRules(fileMatch[1]));
|
|
2366
|
-
}
|
|
2367
|
-
return directives;
|
|
2368
|
-
}
|
|
2369
|
-
#extractRules(tail) {
|
|
2370
|
-
const found = ALL_RULES.filter((rule) => tail.includes(rule));
|
|
2371
|
-
return found.length === 0 ? "all" : new Set(found);
|
|
2372
|
-
}
|
|
2373
|
-
#mergeFileScope(prev, next) {
|
|
2374
|
-
if (prev === "all" || next === "all")
|
|
2375
|
-
return "all";
|
|
2376
|
-
return new Set([...prev ?? [], ...next]);
|
|
2377
|
-
}
|
|
2378
|
-
#isSuppressed(v, directives) {
|
|
2379
|
-
if (directives.file === "all" || directives.file?.has(v.rule))
|
|
2380
|
-
return true;
|
|
2381
|
-
const nextLine = directives.nextLine.get(v.line);
|
|
2382
|
-
return nextLine === "all" || (nextLine?.has(v.rule) ?? false);
|
|
2383
|
-
}
|
|
2384
|
-
#scanClassLiterals(file, scan, out) {
|
|
2385
|
-
for (const m of scan.matchAll(RAW_PALETTE_RE)) {
|
|
2386
|
-
out.push(this.#violation(file, m.index ?? 0, {
|
|
2387
|
-
rule: "raw-palette",
|
|
2388
|
-
severity: "error",
|
|
2389
|
-
suggestion: "\uC2DC\uB9E8\uD2F1 \uD1A0\uD070\uC73C\uB85C \uAD50\uCCB4\uD558\uC138\uC694 \u2014 \uC608: text-gray-500\u2192text-muted-foreground, bg-red-500\u2192bg-destructive, bg-blue-500\u2192bg-info. \uBE0C\uB79C\uB4DC \uC0C9\uC740 page/styles.css \uD1A0\uD070\uC5D0\uC11C \uC870\uC815\uD569\uB2C8\uB2E4."
|
|
2390
|
-
}));
|
|
2391
|
-
}
|
|
2392
|
-
for (const m of scan.matchAll(ARBITRARY_COLOR_RE)) {
|
|
2393
|
-
out.push(this.#violation(file, m.index ?? 0, {
|
|
2394
|
-
rule: "arbitrary-color",
|
|
2395
|
-
severity: "error",
|
|
2396
|
-
suggestion: "\uC784\uC758 \uC0C9 \uB9AC\uD130\uB7F4 \uB300\uC2E0 \uD1A0\uD070\uC744 \uC4F0\uC138\uC694 \u2014 bg-[#3b82f6]\u2192bg-info, text-[rgb(...)]\u2192\uC2DC\uB9E8\uD2F1 \uD1A0\uD070. \uAF2D \uD544\uC694\uD558\uBA74 \uC2A4\uCF54\uD504 \uD1A0\uD070(.campaign-x { --primary: \u2026 })\uC744 \uC815\uC758\uD558\uC138\uC694."
|
|
2397
|
-
}));
|
|
2398
|
-
}
|
|
2399
|
-
for (const m of scan.matchAll(DAISYUI_LEGACY_RE)) {
|
|
2400
|
-
out.push(this.#violation(file, m.index ?? 0, {
|
|
2401
|
-
rule: "daisyui-legacy",
|
|
2402
|
-
severity: "error",
|
|
2403
|
-
suggestion: "daisyUI \uD074\uB798\uC2A4\uB294 \uC81C\uAC70\uB410\uC2B5\uB2C8\uB2E4. akanjs/ui \uD504\uB9AC\uBBF8\uD2F0\uBE0C(Button/Badge \uB4F1)\uB098 buttonRecipe()/badgeRecipe() + \uC2DC\uB9E8\uD2F1 \uD1A0\uD070\uC73C\uB85C \uAD50\uCCB4\uD558\uC138\uC694."
|
|
2404
|
-
}));
|
|
2405
|
-
}
|
|
2406
|
-
for (const m of scan.matchAll(INTERPOLATED_ARBITRARY_RE)) {
|
|
2407
|
-
out.push(this.#violation(file, m.index ?? 0, {
|
|
2408
|
-
rule: "interpolated-arbitrary",
|
|
2409
|
-
severity: "error",
|
|
2410
|
-
suggestion: "\uB7F0\uD0C0\uC784 \uAC12\uC73C\uB85C \uC784\uC758\uAC12 \uD074\uB798\uC2A4\uB97C \uC870\uB9BD\uD558\uBA74 CSS \uAC00 \uC0DD\uC131\uB418\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4(\uC2A4\uCE90\uB108\uB294 \uC18C\uC2A4 \uD14D\uC2A4\uD2B8\uB97C \uC77D\uC2B5\uB2C8\uB2E4). \uD06C\uAE30/\uC704\uCE58\uB294 style prop \uC73C\uB85C \uB118\uAE30\uC138\uC694 \u2014 style={{ minHeight }}. \uAC12\uC774 enum \uC774\uBA74 \uB9AC\uD130\uB7F4 \uD074\uB798\uC2A4 \uB9F5\uC73C\uB85C \uB450\uC138\uC694."
|
|
2411
|
-
}));
|
|
2412
|
-
}
|
|
2413
|
-
}
|
|
2414
|
-
#scanInlineColors(file, scan, out) {
|
|
2415
|
-
const push = (index) => out.push(this.#violation(file, index, {
|
|
2416
|
-
rule: "inline-color",
|
|
2417
|
-
severity: "error",
|
|
2418
|
-
suggestion: "\uC778\uB77C\uC778 \uC0C9 \uB9AC\uD130\uB7F4 \uB300\uC2E0 CSS \uBCC0\uC218/\uD1A0\uD070\uC744 \uCC38\uC870\uD558\uC138\uC694 \u2014 style={{ color: 'var(--primary)' }} \uB610\uB294 \uC2DC\uB9E8\uD2F1 \uD074\uB798\uC2A4(text-primary). \uD558\uB4DC\uCF54\uB529 hex/rgb \uB294 \uD14C\uB9C8 \uC804\uD658\uC744 \uAE68\uB728\uB9BD\uB2C8\uB2E4."
|
|
2419
|
-
}));
|
|
2420
|
-
for (const region of [STYLE_OBJECT_RE, STYLE_TAG_RE]) {
|
|
2421
|
-
for (const m of scan.matchAll(region)) {
|
|
2422
|
-
const inner = m[1] ?? "";
|
|
2423
|
-
const innerStart = (m.index ?? 0) + m[0].indexOf(inner);
|
|
2424
|
-
for (const lit of inner.matchAll(INLINE_COLOR_LITERAL_RE)) {
|
|
2425
|
-
push(innerStart + (lit.index ?? 0));
|
|
2426
|
-
}
|
|
2427
|
-
}
|
|
2428
|
-
}
|
|
2429
|
-
}
|
|
2430
|
-
#violation(file, index, base) {
|
|
2431
|
-
const before = file.content.slice(0, index);
|
|
2432
|
-
const line = before.length === 0 ? 1 : before.split(`
|
|
2433
|
-
`).length;
|
|
2434
|
-
const lineStart = before.lastIndexOf(`
|
|
2435
|
-
`) + 1;
|
|
2436
|
-
const lineEnd = file.content.indexOf(`
|
|
2437
|
-
`, index);
|
|
2438
|
-
const snippet = file.content.slice(lineStart, lineEnd === -1 ? undefined : lineEnd).trim();
|
|
2439
|
-
return { ...base, path: file.path, line, snippet };
|
|
2440
|
-
}
|
|
2441
|
-
static countNamedComponentClasses(css) {
|
|
2442
|
-
const names = new Set;
|
|
2443
|
-
const layerRe = /@layer\s+components\s*\{/g;
|
|
2444
|
-
for (const layer of css.matchAll(layerRe)) {
|
|
2445
|
-
const bodyStart = (layer.index ?? 0) + layer[0].length;
|
|
2446
|
-
const body = StyleGuard.#extractBalanced(css, bodyStart);
|
|
2447
|
-
for (const sel of body.matchAll(/(^|[\s}])\.(-?[a-zA-Z_][\w-]*)/g))
|
|
2448
|
-
names.add(sel[2]);
|
|
2449
|
-
}
|
|
2450
|
-
return { count: names.size, names: [...names].sort() };
|
|
2451
|
-
}
|
|
2452
|
-
static #extractBalanced(css, start) {
|
|
2453
|
-
let depth = 1;
|
|
2454
|
-
for (let i = start;i < css.length; i++) {
|
|
2455
|
-
const ch = css[i];
|
|
2456
|
-
if (ch === "{")
|
|
2457
|
-
depth++;
|
|
2458
|
-
else if (ch === "}") {
|
|
2459
|
-
depth--;
|
|
2460
|
-
if (depth === 0)
|
|
2461
|
-
return css.slice(start, i);
|
|
2462
|
-
}
|
|
2463
|
-
}
|
|
2464
|
-
return css.slice(start);
|
|
2465
|
-
}
|
|
2466
|
-
}
|
|
2093
|
+
var DAISYUI_TOKEN = "(?:base-(?:100|200|300|content)|(?:primary|secondary|accent|neutral|info|success|warning|error)-content|error)";
|
|
2094
|
+
var DAISYUI_TOKEN_RE = new RegExp(`${LEAD}(?:${PREFIX})-${DAISYUI_TOKEN}${TAIL}`, "g");
|
|
2467
2095
|
export { AllRoutesBuilder, CsrArtifactBuilder, CssCompiler, FontOptimizer, PagesBundleBuilder, precompressArtifacts, SsrBaseArtifactBuilder };
|