@akanjs/cli 3.0.0-alpha.7 → 3.0.0-alpha.70
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-pggr92jj.js → agent.command-964bf8qg.js} +9 -7
- package/{application.command-692yap1d.js → application.command-n45n18sc.js} +8 -6
- package/{applicationBuildRunner-ha3y8ew8.js → applicationBuildRunner-af0wtspv.js} +19 -14
- package/{applicationReleasePackager-gg8ccbeh.js → applicationReleasePackager-93nzdfsy.js} +1 -1
- package/buildBatch.proc.js +7 -5
- package/{capacitorApp-p0z5yab2.js → capacitorApp-vnvnvdhf.js} +29 -27
- package/{cloud.command-wqfsyzv2.js → cloud.command-kx1bh5yq.js} +12 -10
- package/{context.command-9bxtdn0w.js → context.command-g1a65x6j.js} +17 -15
- package/{dependencyScanner-f6grrep3.js → dependencyScanner-rbs2407b.js} +1 -1
- package/getDirname-ka11hm7e.js +8 -0
- package/{guideline.command-nfqahxex.js → guideline.command-xphyhvwh.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 +1020 -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 +220 -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-azv0xjb5.js → index-0ngvvgry.js} +8 -4
- package/index-1577bej2.js +7 -0
- package/{index-p3f4q532.js → index-4rw4qhe6.js} +14 -13
- package/{index-bjpxzr6s.js → index-5vy2jzar.js} +1 -1
- package/{index-js368gk9.js → index-8gybxemv.js} +6 -6
- package/{index-qb61h0k7.js → index-abyjt72y.js} +333 -540
- package/{index-4hek879p.js → index-bqqts262.js} +1 -1
- package/{index-6rhabeev.js → index-e31ghfp5.js} +5 -3
- package/index-f39512t6.js +255 -0
- package/{index-5pmx7bkg.js → index-fahkq20v.js} +3 -3
- package/{index-xhgtkekh.js → index-hee7egtm.js} +1 -1
- package/{index-7myvn0f0.js → index-j37qq1f2.js} +1 -1
- package/{index-5dw57f0d.js → index-mkn5qagt.js} +14 -14
- package/{index-ztpw544k.js → index-nj3w0r34.js} +3 -3
- package/{index-vg0sj9nc.js → index-nkwmnqwn.js} +257 -97
- package/{index-a1e0j63x.js → index-p681w089.js} +15 -7
- package/{index-j9cxndc0.js → index-qnv0aqmm.js} +9 -6
- package/{index-w6wbb612.js → index-qt4nfr0n.js} +13 -13
- package/{index-bz2sfzzt.js → index-rb65xnw9.js} +4 -4
- package/{index-gex7c8kd.js → index-rbnpe8cp.js} +37 -6
- package/{index-0cj2zxbm.js → index-sh01r3pm.js} +1 -1
- package/{index-gmy45vz2.js → index-v5fdxdre.js} +4 -4
- package/{index-mssqn2sq.js → index-va15r8h5.js} +54 -15
- package/{index-vtj3zps1.js → index-yg3hxk2k.js} +8 -8
- package/{index-2c3e2xh8.js → index-ytaqtc04.js} +1 -1
- package/index.js +21 -19
- package/{library.command-md9715jz.js → library.command-rfpd27yv.js} +6 -4
- package/{localRegistry.command-exkbamyj.js → localRegistry.command-pyxfe0fs.js} +11 -9
- package/{module.command-w5aaddxk.js → module.command-qhaxad0p.js} +10 -8
- package/{package.command-yfkrcy0z.js → package.command-2k22q9nt.js} +6 -4
- package/package.json +4 -5
- package/{page.command-echbavp4.js → page.command-ymk9dqh4.js} +6 -4
- package/{primitive.command-9fbhspw2.js → primitive.command-9k45zpc1.js} +11 -9
- package/{quality.command-3pnt9yep.js → quality.command-bf6vkxe5.js} +192 -97
- package/{recipeScanner-tghkzrrt.js → recipeScanner-zrzev25x.js} +3 -3
- package/{repair.command-carj4vax.js → repair.command-egvvm6xf.js} +8 -6
- package/{routeSourceValidator-e4jmbfcx.js → routeSourceValidator-7fycdqyv.js} +1 -1
- package/{scalar.command-51ryzypc.js → scalar.command-bt1facyj.js} +9 -7
- package/templates/app/env/env.client.debug.ts.template +1 -5
- package/templates/app/env/env.client.develop.ts.template +1 -5
- package/templates/app/env/env.client.local.ts.template +1 -5
- package/templates/app/env/env.client.main.ts.template +1 -5
- package/templates/app/env/env.client.testing.ts.template +1 -5
- package/templates/app/env/env.client.type.ts +2 -6
- 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/main.ts +3 -1
- 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 -972
- package/templates/workspaceRoot/CLAUDE.md.template +19 -0
- package/templates/workspaceRoot/biome.json.template +4 -244
- package/templates/workspaceRoot/docs/AI-DEVELOPMENT.md.template +10 -14
- package/templates/workspaceRoot/docs/GENERATED.md.template +12 -10
- package/templates/workspaceRoot/package.json.template +1 -1
- package/{typeChecker-kravn7ns.js → typeChecker-g6bh0xfv.js} +1 -1
- package/typecheck.proc.js +1 -1
- package/{workflow.command-91wmvm8k.js → workflow.command-v1n3wg7v.js} +16 -14
- package/{workspace.command-wa10mfd9.js → workspace.command-yvvqvhzz.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 -19
- package/templates/workspaceRoot/.cursor/rules/module-file-playbook.mdc.template +0 -62
- 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 -99
- 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) => {
|
|
@@ -1752,6 +1575,170 @@ ${declarations.map(([prop, value]) => ` ${prop}: ${value};`).join(`
|
|
|
1752
1575
|
}
|
|
1753
1576
|
}
|
|
1754
1577
|
|
|
1578
|
+
// pkgs/@akanjs/devkit/frontendBuild/fontPruner.ts
|
|
1579
|
+
import { rm as rm2, rmdir, stat as stat3 } from "fs/promises";
|
|
1580
|
+
import path9 from "path";
|
|
1581
|
+
import { isFontOptimizationEnabled } from "akanjs/client";
|
|
1582
|
+
var FONT_GLOB = "**/*.{woff2,woff,ttf,otf,ttc,eot}";
|
|
1583
|
+
var REFERRER_GLOB = "**/*.{css,js,mjs,cjs,json,html,htm,svg,xml,webmanifest}";
|
|
1584
|
+
|
|
1585
|
+
class FontPruner {
|
|
1586
|
+
#app;
|
|
1587
|
+
#keepGlobs;
|
|
1588
|
+
#publicRoot;
|
|
1589
|
+
#artifactRoot;
|
|
1590
|
+
constructor(app, { keepFonts = [] } = {}) {
|
|
1591
|
+
this.#app = app;
|
|
1592
|
+
this.#keepGlobs = keepFonts;
|
|
1593
|
+
this.#publicRoot = path9.join(app.dist.cwdPath, "public");
|
|
1594
|
+
this.#artifactRoot = path9.join(app.dist.cwdPath, ".akan/artifact");
|
|
1595
|
+
}
|
|
1596
|
+
async prune() {
|
|
1597
|
+
const empty = { removed: [], kept: [], freedBytes: 0 };
|
|
1598
|
+
if (!await this.#isDirectory(this.#publicRoot))
|
|
1599
|
+
return empty;
|
|
1600
|
+
const candidates = await this.#collectCandidates();
|
|
1601
|
+
if (!candidates.length)
|
|
1602
|
+
return empty;
|
|
1603
|
+
const fonts = await new FontOptimizer(this.#app, "build").discoverFonts();
|
|
1604
|
+
const unoptimizedSrcs = new Set;
|
|
1605
|
+
const optimizedSrcs = new Set;
|
|
1606
|
+
for (const font of fonts) {
|
|
1607
|
+
const target = isFontOptimizationEnabled(font) ? optimizedSrcs : unoptimizedSrcs;
|
|
1608
|
+
for (const fontPath of font.paths)
|
|
1609
|
+
target.add(path9.posix.basename(fontPath.src));
|
|
1610
|
+
}
|
|
1611
|
+
const result = { removed: [], kept: [], freedBytes: 0 };
|
|
1612
|
+
const undecided = [];
|
|
1613
|
+
for (const candidate of candidates) {
|
|
1614
|
+
const reason = this.#staticKeepReason(candidate, unoptimizedSrcs);
|
|
1615
|
+
if (reason)
|
|
1616
|
+
result.kept.push({ file: candidate.rel, bytes: candidate.bytes, reason });
|
|
1617
|
+
else
|
|
1618
|
+
undecided.push(candidate);
|
|
1619
|
+
}
|
|
1620
|
+
if (undecided.length) {
|
|
1621
|
+
const referrers = await this.#findReferrers(undecided, optimizedSrcs);
|
|
1622
|
+
for (const candidate of undecided) {
|
|
1623
|
+
const referrer = referrers.get(candidate.rel);
|
|
1624
|
+
if (referrer)
|
|
1625
|
+
result.kept.push({ file: candidate.rel, bytes: candidate.bytes, reason: { kind: "referenced", referrer } });
|
|
1626
|
+
else
|
|
1627
|
+
result.removed.push({ file: candidate.rel, bytes: candidate.bytes });
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1630
|
+
await this.#remove(result.removed.map(({ file }) => file));
|
|
1631
|
+
result.freedBytes = result.removed.reduce((total, { bytes }) => total + bytes, 0);
|
|
1632
|
+
this.#report(result);
|
|
1633
|
+
return result;
|
|
1634
|
+
}
|
|
1635
|
+
#staticKeepReason(candidate, unoptimizedSrcs) {
|
|
1636
|
+
if (unoptimizedSrcs.has(candidate.basename))
|
|
1637
|
+
return { kind: "unoptimized" };
|
|
1638
|
+
for (const glob of this.#keepGlobs) {
|
|
1639
|
+
if (new Bun.Glob(glob).match(candidate.rel))
|
|
1640
|
+
return { kind: "declared", glob };
|
|
1641
|
+
}
|
|
1642
|
+
return null;
|
|
1643
|
+
}
|
|
1644
|
+
async#collectCandidates() {
|
|
1645
|
+
const candidates = [];
|
|
1646
|
+
for await (const rel of new Bun.Glob(FONT_GLOB).scan({ cwd: this.#publicRoot, dot: false })) {
|
|
1647
|
+
const entry = await stat3(path9.join(this.#publicRoot, rel)).catch(() => null);
|
|
1648
|
+
if (!entry?.isFile())
|
|
1649
|
+
continue;
|
|
1650
|
+
candidates.push({ rel: rel.split(path9.sep).join("/"), basename: path9.basename(rel), bytes: entry.size });
|
|
1651
|
+
}
|
|
1652
|
+
return candidates;
|
|
1653
|
+
}
|
|
1654
|
+
async#findReferrers(candidates, optimizedSrcs) {
|
|
1655
|
+
const referrers = new Map;
|
|
1656
|
+
const authoritative = candidates;
|
|
1657
|
+
const bundled = candidates.filter((candidate) => !optimizedSrcs.has(candidate.basename));
|
|
1658
|
+
const roots = [
|
|
1659
|
+
{ dir: this.#publicRoot, label: "public", needles: authoritative },
|
|
1660
|
+
{ dir: path9.join(this.#artifactRoot, "styles"), label: ".akan/artifact/styles", needles: authoritative },
|
|
1661
|
+
{ dir: path9.join(this.#artifactRoot, "client"), label: ".akan/artifact/client", needles: bundled },
|
|
1662
|
+
{ dir: path9.join(this.#artifactRoot, "client-ssr"), label: ".akan/artifact/client-ssr", needles: bundled },
|
|
1663
|
+
{ dir: path9.join(this.#artifactRoot, "server"), label: ".akan/artifact/server", needles: bundled },
|
|
1664
|
+
{ dir: path9.join(this.#app.dist.cwdPath, "csr"), label: "csr", needles: bundled }
|
|
1665
|
+
];
|
|
1666
|
+
for (const root of roots) {
|
|
1667
|
+
const pending = root.needles.filter((candidate) => !referrers.has(candidate.rel));
|
|
1668
|
+
if (!pending.length || !await this.#isDirectory(root.dir))
|
|
1669
|
+
continue;
|
|
1670
|
+
await this.#scanRoot(root, pending, referrers);
|
|
1671
|
+
}
|
|
1672
|
+
return referrers;
|
|
1673
|
+
}
|
|
1674
|
+
async#scanRoot(root, pending, referrers) {
|
|
1675
|
+
const needles = pending.map((candidate) => ({
|
|
1676
|
+
rel: candidate.rel,
|
|
1677
|
+
forms: [...new Set([candidate.basename, encodeURIComponent(candidate.basename)])]
|
|
1678
|
+
}));
|
|
1679
|
+
for await (const rel of new Bun.Glob(REFERRER_GLOB).scan({ cwd: root.dir, dot: false })) {
|
|
1680
|
+
const remaining = needles.filter((needle) => !referrers.has(needle.rel));
|
|
1681
|
+
if (!remaining.length)
|
|
1682
|
+
return;
|
|
1683
|
+
const text = await Bun.file(path9.join(root.dir, rel)).text().catch(() => "");
|
|
1684
|
+
if (!text)
|
|
1685
|
+
continue;
|
|
1686
|
+
for (const needle of remaining) {
|
|
1687
|
+
if (needle.forms.some((form) => text.includes(form)))
|
|
1688
|
+
referrers.set(needle.rel, `${root.label}/${rel.split(path9.sep).join("/")}`);
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
async#remove(files) {
|
|
1693
|
+
await Promise.all(files.map((file) => rm2(path9.join(this.#publicRoot, file), { force: true })));
|
|
1694
|
+
const dirs = [...new Set(files.map((file) => path9.posix.dirname(file)))].filter((dir) => dir !== "." && dir !== "/").sort((a, b) => b.length - a.length);
|
|
1695
|
+
for (const dir of dirs)
|
|
1696
|
+
await rmdir(path9.join(this.#publicRoot, dir)).catch(() => {
|
|
1697
|
+
return;
|
|
1698
|
+
});
|
|
1699
|
+
}
|
|
1700
|
+
#report(result) {
|
|
1701
|
+
if (!result.removed.length && !result.kept.length)
|
|
1702
|
+
return;
|
|
1703
|
+
for (const { file, bytes } of result.removed)
|
|
1704
|
+
this.#app.verbose(`[font-prune] dropped public/${file} (${FontPruner.formatBytes(bytes)})`);
|
|
1705
|
+
for (const { file, reason } of result.kept)
|
|
1706
|
+
this.#app.verbose(`[font-prune] kept public/${file} \u2014 ${FontPruner.describe(reason)}`);
|
|
1707
|
+
const byReferrer = new Map;
|
|
1708
|
+
for (const { bytes, reason } of result.kept) {
|
|
1709
|
+
if (reason.kind !== "referenced")
|
|
1710
|
+
continue;
|
|
1711
|
+
const entry = byReferrer.get(reason.referrer) ?? { files: 0, bytes: 0 };
|
|
1712
|
+
entry.files += 1;
|
|
1713
|
+
entry.bytes += bytes;
|
|
1714
|
+
byReferrer.set(reason.referrer, entry);
|
|
1715
|
+
}
|
|
1716
|
+
if (!byReferrer.size)
|
|
1717
|
+
return;
|
|
1718
|
+
const ranked = [...byReferrer.entries()].sort(([, a], [, b]) => b.bytes - a.bytes);
|
|
1719
|
+
const named = ranked.slice(0, 3).map(([referrer, { files, bytes }]) => `${referrer} (${files}, ${FontPruner.formatBytes(bytes)})`);
|
|
1720
|
+
const rest = ranked.length - named.length;
|
|
1721
|
+
this.#app.logger.info(`[font-prune] kept ${FontPruner.formatBytes(ranked.reduce((total, [, entry]) => total + entry.bytes, 0))} of fonts in public/ because these reference them: ${named.join(", ")}${rest > 0 ? ` and ${rest} more` : ""}`);
|
|
1722
|
+
}
|
|
1723
|
+
static describe(reason) {
|
|
1724
|
+
if (reason.kind === "unoptimized")
|
|
1725
|
+
return "declared with optimize: false, so the runtime CSS serves it";
|
|
1726
|
+
if (reason.kind === "declared")
|
|
1727
|
+
return `kept by assets.keepFonts "${reason.glob}"`;
|
|
1728
|
+
return `referenced by ${reason.referrer}`;
|
|
1729
|
+
}
|
|
1730
|
+
static formatBytes(bytes) {
|
|
1731
|
+
if (bytes < 1024)
|
|
1732
|
+
return `${bytes}B`;
|
|
1733
|
+
if (bytes < 1024 * 1024)
|
|
1734
|
+
return `${(bytes / 1024).toFixed(1)}KB`;
|
|
1735
|
+
return `${(bytes / 1024 / 1024).toFixed(1)}MB`;
|
|
1736
|
+
}
|
|
1737
|
+
async#isDirectory(dir) {
|
|
1738
|
+
return await stat3(dir).then((entry) => entry.isDirectory(), () => false);
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1755
1742
|
// pkgs/@akanjs/devkit/frontendBuild/pagesBundleBuilder.ts
|
|
1756
1743
|
import path11 from "path";
|
|
1757
1744
|
|
|
@@ -1763,7 +1750,6 @@ var DEFAULT_EXCLUDE_PREFIX = ["@akanjs/cli/", "@akanjs/devkit/"];
|
|
|
1763
1750
|
var OPTIONAL_BACKEND_EXTERNAL_EXACT = new Set([
|
|
1764
1751
|
"@libsql/client",
|
|
1765
1752
|
"bullmq",
|
|
1766
|
-
"croner",
|
|
1767
1753
|
"ioredis",
|
|
1768
1754
|
"postgres",
|
|
1769
1755
|
"protobufjs"
|
|
@@ -2154,6 +2140,7 @@ class SsrBaseArtifactBuilder {
|
|
|
2154
2140
|
branches: [...akanConfig.branches],
|
|
2155
2141
|
i18n: akanConfig.i18n,
|
|
2156
2142
|
imageConfig: akanConfig.images,
|
|
2143
|
+
web: akanConfig.web,
|
|
2157
2144
|
deepLinkAssociations: Object.values(akanConfig.mobile.targets).filter((target) => (target.deepLinks?.domains?.length ?? 0) > 0).map((target) => ({
|
|
2158
2145
|
targetName: target.name,
|
|
2159
2146
|
appId: target.appId,
|
|
@@ -2180,7 +2167,7 @@ class SsrBaseArtifactBuilder {
|
|
|
2180
2167
|
const ssrBundle = await new ClientEntriesBundler({
|
|
2181
2168
|
app: this.#app,
|
|
2182
2169
|
entries: [rscSegmentOutletEntry],
|
|
2183
|
-
...RouteClientBuilder.
|
|
2170
|
+
...RouteClientBuilder.resolveSsrClientBundleOptions(this.#command),
|
|
2184
2171
|
outputSubdir: "client-ssr",
|
|
2185
2172
|
command: this.#command
|
|
2186
2173
|
}).bundle();
|
|
@@ -2232,13 +2219,13 @@ class SsrBaseArtifactBuilder {
|
|
|
2232
2219
|
`);
|
|
2233
2220
|
if (!cssText)
|
|
2234
2221
|
return [];
|
|
2235
|
-
return [this.#writeCssAsset(basePath, cssText)];
|
|
2222
|
+
return [this.#writeCssAsset(basePath, cssText, cssCompiler.importedStylesheetsByBasePath[basePath] ?? [])];
|
|
2236
2223
|
})));
|
|
2237
2224
|
if (optimizedFonts.files.length > 0)
|
|
2238
2225
|
this.#app.verbose(`[base-artifact] optimized ${optimizedFonts.files.length} font files`);
|
|
2239
2226
|
return { cssCompiler, optimizedFonts, cssAssets };
|
|
2240
2227
|
}
|
|
2241
|
-
async#writeCssAsset(basePath, cssText) {
|
|
2228
|
+
async#writeCssAsset(basePath, cssText, imported) {
|
|
2242
2229
|
const cssAssetName = basePath || "root";
|
|
2243
2230
|
const preparedCssText = await prepareCssAsset(this.#command, basePath, cssText);
|
|
2244
2231
|
const cssHash = Bun.hash(`${basePath}
|
|
@@ -2248,9 +2235,17 @@ ${preparedCssText}`).toString(36);
|
|
|
2248
2235
|
`/_akan/styles/${cssAssetName}-${cssHash}.css`
|
|
2249
2236
|
];
|
|
2250
2237
|
await Bun.write(path13.join(this.#absArtifactDir, cssRelPath), preparedCssText);
|
|
2238
|
+
SsrBaseArtifactBuilder.#warnDroppedImports(this.#app, cssRelPath, preparedCssText, imported);
|
|
2251
2239
|
this.#app.verbose(`[base-artifact] wrote ${preparedCssText.length} bytes of CSS for ${basePath} -> ${cssRelPath}`);
|
|
2252
2240
|
return [basePath, { cssUrl, cssRelPath }];
|
|
2253
2241
|
}
|
|
2242
|
+
static #warnDroppedImports(app, cssRelPath, css, imported) {
|
|
2243
|
+
for (const { cssPath, declaredNames } of imported) {
|
|
2244
|
+
if (declaredNames.length === 0 || declaredNames.some((name) => css.includes(`${name}:`)))
|
|
2245
|
+
continue;
|
|
2246
|
+
app.logger.warn(`[base-artifact] @import ${cssPath} declares ${declaredNames.length} custom propert${declaredNames.length === 1 ? "y" : "ies"} and none of them are in ${cssRelPath}`);
|
|
2247
|
+
}
|
|
2248
|
+
}
|
|
2254
2249
|
}
|
|
2255
2250
|
// pkgs/@akanjs/devkit/frontendBuild/styleGuard.ts
|
|
2256
2251
|
var PREFIX = "(?:bg|text|border(?:-[tblrxy])?(?:-[se])?|ring(?:-offset)?|fill|stroke|shadow|from|to|via|divide|outline|decoration|placeholder|caret|accent)";
|
|
@@ -2258,210 +2253,8 @@ var PALETTE = "(?:slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|gre
|
|
|
2258
2253
|
var LEAD = "(^|[\\s\"'`{(\\[:!])";
|
|
2259
2254
|
var TAIL = "(?=[\\s\"'`})\\]:/!,]|$)";
|
|
2260
2255
|
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
2256
|
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
2257
|
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
|
-
|
|
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
|
-
}
|
|
2467
|
-
export { AllRoutesBuilder, CsrArtifactBuilder, CssCompiler, FontOptimizer, PagesBundleBuilder, precompressArtifacts, SsrBaseArtifactBuilder };
|
|
2258
|
+
var DAISYUI_TOKEN = "(?:base-(?:100|200|300|content)|(?:primary|secondary|accent|neutral|info|success|warning|error)-content|error)";
|
|
2259
|
+
var DAISYUI_TOKEN_RE = new RegExp(`${LEAD}(?:${PREFIX})-${DAISYUI_TOKEN}${TAIL}`, "g");
|
|
2260
|
+
export { AllRoutesBuilder, CsrArtifactBuilder, CssCompiler, FontOptimizer, FontPruner, PagesBundleBuilder, precompressArtifacts, SsrBaseArtifactBuilder };
|