@akanjs/cli 2.4.1-rc.1 → 2.4.1-rc.3
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/agent.command-h4afc69n.js +1 -1
- package/{application.command-47mj9qsy.js → application.command-jkmbsd1x.js} +1 -1
- package/{applicationBuildRunner-yz144508.js → applicationBuildRunner-ny84tjhc.js} +1 -1
- package/buildBatch.proc.js +175 -0
- package/{cloud.command-qgjxja3n.js → cloud.command-53j76grx.js} +6 -6
- package/{context.command-nqbtak4f.js → context.command-ds3mka9p.js} +9 -9
- package/incrementalBuilder.proc.js +249 -151
- package/{index-z9gvz7b0.js → index-0hjg9qs5.js} +7 -7
- package/{index-qhtr07v8.js → index-1fpjvqhs.js} +13 -13
- package/{index-sgmas1fc.js → index-dynknvzd.js} +3 -3
- package/{index-73pr2cmy.js → index-m9pca6jv.js} +1 -1
- package/{index-wq8jwx8z.js → index-n6h3482q.js} +161 -6
- package/{index-a5rmdgy4.js → index-q7zcac6n.js} +1403 -1405
- package/{index-77crfweb.js → index-rgfxj6qc.js} +176 -10
- package/index.js +10 -10
- package/{localRegistry.command-p1pgxw78.js → localRegistry.command-25sv5hmt.js} +4 -4
- package/{module.command-qrj3kmyz.js → module.command-tnj2bzst.js} +2 -2
- package/{package.command-r8sq5kzp.js → package.command-5x5m0ej1.js} +1 -1
- package/package.json +2 -2
- package/{primitive.command-pv9ssmtf.js → primitive.command-q1ycj5vr.js} +3 -3
- package/{scalar.command-kabkd6wd.js → scalar.command-bmrmp8n4.js} +2 -2
- package/{workflow.command-64r6cw0w.js → workflow.command-64550hka.js} +5 -5
- package/{workspace.command-xk68sd6c.js → workspace.command-pmfxxfew.js} +16 -16
- package/{index-8pkbzj26.js → index-fmky5k3p.js} +3 -3
- package/{index-y3hdhy4p.js → index-gb26e7z0.js} +3 -3
|
@@ -2636,1555 +2636,1553 @@ ${CsrArtifactBuilder.escapeInlineScript(await loadScript(src))}
|
|
|
2636
2636
|
}
|
|
2637
2637
|
}
|
|
2638
2638
|
|
|
2639
|
-
// pkgs/@akanjs/devkit/frontendBuild/
|
|
2639
|
+
// pkgs/@akanjs/devkit/frontendBuild/cssCompiler.ts
|
|
2640
|
+
import path15 from "path";
|
|
2641
|
+
import { Logger as Logger2 } from "akanjs/common";
|
|
2642
|
+
import { compile } from "tailwindcss";
|
|
2643
|
+
|
|
2644
|
+
// pkgs/@akanjs/devkit/frontendBuild/cssImportResolver.ts
|
|
2640
2645
|
import path14 from "path";
|
|
2641
|
-
var
|
|
2642
|
-
var CONFIG_BASENAMES = new Set(["akan.config.ts", "bunfig.toml", "tsconfig.json", "package.json"]);
|
|
2643
|
-
var BARREL_FACETS = new Set(["common", "srvkit", "ui", "webkit", "plugin"]);
|
|
2644
|
-
var CLIENT_SUFFIXES = [".Template.tsx", ".Unit.tsx", ".Util.tsx", ".View.tsx", ".Zone.tsx", ".store.ts"];
|
|
2645
|
-
var SHARED_SUFFIXES = [".constant.ts", ".dictionary.ts", ".signal.ts"];
|
|
2646
|
-
var SERVER_SUFFIXES = [".service.ts", ".document.ts"];
|
|
2647
|
-
var RUNTIME_METADATA_BASENAMES = new Set(["dict.ts", "sig.ts", "useClient.ts", "useServer.ts"]);
|
|
2646
|
+
var CSS_IMPORT_EXTS = ["", ".css", "/styles.css", "/index.css"];
|
|
2648
2647
|
|
|
2649
|
-
class
|
|
2648
|
+
class CssImportResolver {
|
|
2650
2649
|
#workspaceRoot;
|
|
2651
|
-
|
|
2652
|
-
|
|
2650
|
+
#paths;
|
|
2651
|
+
#wildcardEntries;
|
|
2652
|
+
constructor(workspaceRoot, paths = {}) {
|
|
2653
|
+
this.#workspaceRoot = workspaceRoot;
|
|
2654
|
+
this.#paths = paths;
|
|
2655
|
+
this.#wildcardEntries = Object.entries(paths).filter(([key]) => key.endsWith("/*")).map(([key, replacements]) => ({ prefix: key.slice(0, -1), replacements })).sort((a, b) => b.prefix.length - a.prefix.length);
|
|
2653
2656
|
}
|
|
2654
|
-
|
|
2655
|
-
const
|
|
2656
|
-
|
|
2657
|
-
const kindSet = new Set(kinds);
|
|
2658
|
-
const roles = new Set;
|
|
2659
|
-
const actions = new Set;
|
|
2660
|
-
const reasonByFile = {};
|
|
2661
|
-
for (const kind of kindSet) {
|
|
2662
|
-
if (kind === "css") {
|
|
2663
|
-
roles.add("css");
|
|
2664
|
-
actions.add("rebuild-css");
|
|
2665
|
-
}
|
|
2666
|
-
if (kind === "config") {
|
|
2667
|
-
roles.add("config");
|
|
2668
|
-
actions.add("restart-dev-host");
|
|
2669
|
-
}
|
|
2670
|
-
}
|
|
2671
|
-
for (const file of fileList) {
|
|
2672
|
-
const reasons = new Set;
|
|
2673
|
-
const fileRoles = this.#rolesForFile(file, { isGenerated: generatedSet.has(path14.resolve(file)), reasons });
|
|
2674
|
-
for (const role of fileRoles)
|
|
2675
|
-
roles.add(role);
|
|
2676
|
-
if (reasons.has("runtime-metadata"))
|
|
2677
|
-
actions.add("restart-builder");
|
|
2678
|
-
if (reasons.size > 0)
|
|
2679
|
-
reasonByFile[path14.resolve(file)] = [...reasons].sort();
|
|
2680
|
-
}
|
|
2681
|
-
if (roles.has("barrel"))
|
|
2682
|
-
actions.add("sync-generated");
|
|
2683
|
-
if (roles.has("server") || roles.has("shared"))
|
|
2684
|
-
actions.add("restart-backend");
|
|
2685
|
-
if (roles.has("client") || roles.has("shared"))
|
|
2686
|
-
actions.add("rebuild-client");
|
|
2687
|
-
if (roles.has("css"))
|
|
2688
|
-
actions.add("rebuild-css");
|
|
2689
|
-
return {
|
|
2690
|
-
generation,
|
|
2691
|
-
files: fileList,
|
|
2692
|
-
generatedFiles: uniqueResolved(generatedFiles),
|
|
2693
|
-
roles: [...roles].sort(),
|
|
2694
|
-
actions: [...actions].sort(),
|
|
2695
|
-
reasonByFile
|
|
2696
|
-
};
|
|
2657
|
+
static async create(app) {
|
|
2658
|
+
const tsconfig = await app.getTsConfig();
|
|
2659
|
+
return new CssImportResolver(app.workspace.workspaceRoot, tsconfig.compilerOptions.paths ?? {});
|
|
2697
2660
|
}
|
|
2698
|
-
|
|
2699
|
-
const
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
reasons.add("config-file");
|
|
2709
|
-
}
|
|
2710
|
-
if (ext === ".css") {
|
|
2711
|
-
roles.add("css");
|
|
2712
|
-
reasons.add("css-file");
|
|
2713
|
-
}
|
|
2714
|
-
if (isGenerated || isSource && this.#isBarrelFacetChild(parts)) {
|
|
2715
|
-
roles.add("barrel");
|
|
2716
|
-
reasons.add(isGenerated ? "generated-index" : "barrel-facet-child");
|
|
2661
|
+
async resolve(id, fromBase) {
|
|
2662
|
+
for (const resolve of [
|
|
2663
|
+
() => this.#resolveWithTsconfig(id),
|
|
2664
|
+
() => this.#resolveWithBun(id, fromBase),
|
|
2665
|
+
() => this.#resolveWithRequire(id, fromBase),
|
|
2666
|
+
() => this.#resolvePackageStyle(id, fromBase)
|
|
2667
|
+
]) {
|
|
2668
|
+
const resolved = await resolve();
|
|
2669
|
+
if (resolved)
|
|
2670
|
+
return resolved;
|
|
2717
2671
|
}
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2672
|
+
return null;
|
|
2673
|
+
}
|
|
2674
|
+
#resolveWithBun(id, fromBase) {
|
|
2675
|
+
for (const base of this.#resolutionBases(fromBase)) {
|
|
2676
|
+
try {
|
|
2677
|
+
const resolved = Bun.resolveSync(id, base);
|
|
2678
|
+
if (CssImportResolver.isCssFile(resolved))
|
|
2679
|
+
return resolved;
|
|
2680
|
+
} catch {}
|
|
2721
2681
|
}
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2682
|
+
return null;
|
|
2683
|
+
}
|
|
2684
|
+
#resolveWithRequire(id, fromBase) {
|
|
2685
|
+
for (const base of this.#resolutionBases(fromBase)) {
|
|
2686
|
+
try {
|
|
2687
|
+
const resolved = __require.resolve(id, { paths: [base] });
|
|
2688
|
+
if (CssImportResolver.isCssFile(resolved))
|
|
2689
|
+
return resolved;
|
|
2690
|
+
} catch {}
|
|
2725
2691
|
}
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2692
|
+
return null;
|
|
2693
|
+
}
|
|
2694
|
+
async#resolveWithTsconfig(id) {
|
|
2695
|
+
const exact = this.#paths[id];
|
|
2696
|
+
if (exact) {
|
|
2697
|
+
for (const repl of exact) {
|
|
2698
|
+
const resolved = await this.#firstExisting(path14.resolve(this.#workspaceRoot, repl));
|
|
2699
|
+
if (resolved)
|
|
2700
|
+
return resolved;
|
|
2701
|
+
}
|
|
2729
2702
|
}
|
|
2730
|
-
|
|
2731
|
-
|
|
2703
|
+
for (const { prefix, replacements } of this.#wildcardEntries) {
|
|
2704
|
+
if (!id.startsWith(prefix))
|
|
2705
|
+
continue;
|
|
2706
|
+
const suffix = id.slice(prefix.length);
|
|
2707
|
+
for (const repl of replacements) {
|
|
2708
|
+
const replPath = repl.endsWith("/*") ? repl.slice(0, -1) : repl;
|
|
2709
|
+
const resolved = await this.#firstExisting(path14.resolve(this.#workspaceRoot, replPath + suffix));
|
|
2710
|
+
if (resolved)
|
|
2711
|
+
return resolved;
|
|
2712
|
+
}
|
|
2732
2713
|
}
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2714
|
+
return null;
|
|
2715
|
+
}
|
|
2716
|
+
async#resolvePackageStyle(id, fromBase) {
|
|
2717
|
+
const pkgName = CssImportResolver.getPackageName(id);
|
|
2718
|
+
if (!pkgName)
|
|
2719
|
+
return null;
|
|
2720
|
+
for (const base of this.#resolutionBases(fromBase)) {
|
|
2721
|
+
try {
|
|
2722
|
+
const pkgPath = __require.resolve(`${pkgName}/package.json`, { paths: [base] });
|
|
2723
|
+
const resolved = await this.#resolvePackageStyleFromPackageJson(id, pkgName, pkgPath);
|
|
2724
|
+
if (resolved)
|
|
2725
|
+
return resolved;
|
|
2726
|
+
} catch {}
|
|
2738
2727
|
}
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2728
|
+
for (const pkgPath of this.#packageJsonCandidates(pkgName)) {
|
|
2729
|
+
const resolved = await this.#resolvePackageStyleFromPackageJson(id, pkgName, pkgPath);
|
|
2730
|
+
if (resolved)
|
|
2731
|
+
return resolved;
|
|
2742
2732
|
}
|
|
2743
|
-
return
|
|
2733
|
+
return null;
|
|
2744
2734
|
}
|
|
2745
|
-
#
|
|
2746
|
-
|
|
2747
|
-
|
|
2735
|
+
async#resolvePackageStyleFromPackageJson(id, pkgName, pkgPath) {
|
|
2736
|
+
try {
|
|
2737
|
+
if (!await Bun.file(pkgPath).exists())
|
|
2738
|
+
return null;
|
|
2739
|
+
const pkgDir = path14.dirname(pkgPath);
|
|
2740
|
+
const pkg = await Bun.file(pkgPath).json();
|
|
2741
|
+
const subpath = id === pkgName ? "." : `.${id.slice(pkgName.length)}`;
|
|
2742
|
+
const exportValue = pkg.exports?.[subpath];
|
|
2743
|
+
const styleEntry = (typeof exportValue === "string" ? exportValue : exportValue?.style || exportValue?.import || exportValue?.default) || pkg.exports?.["."]?.style || pkg.style || "index.css";
|
|
2744
|
+
return await this.#firstExisting(path14.resolve(pkgDir, styleEntry));
|
|
2745
|
+
} catch {
|
|
2746
|
+
return null;
|
|
2747
|
+
}
|
|
2748
2748
|
}
|
|
2749
|
-
#
|
|
2750
|
-
|
|
2751
|
-
return parts.includes("ui") || parts.includes("webkit") || parts.includes("page") || CLIENT_SUFFIXES.some((suffix) => base.endsWith(suffix));
|
|
2749
|
+
#resolutionBases(fromBase) {
|
|
2750
|
+
return [fromBase, this.#workspaceRoot, path14.dirname(Bun.main), path14.resolve(path14.dirname(Bun.main), "../..")];
|
|
2752
2751
|
}
|
|
2753
|
-
#
|
|
2754
|
-
|
|
2755
|
-
|
|
2752
|
+
#packageJsonCandidates(pkgName) {
|
|
2753
|
+
return [
|
|
2754
|
+
path14.join(this.#workspaceRoot, "pkgs", pkgName, "package.json"),
|
|
2755
|
+
path14.join(this.#workspaceRoot, "node_modules", pkgName, "package.json"),
|
|
2756
|
+
path14.join(path14.dirname(Bun.main), "node_modules", pkgName, "package.json"),
|
|
2757
|
+
path14.join(path14.dirname(Bun.main), "../../", pkgName, "package.json")
|
|
2758
|
+
];
|
|
2756
2759
|
}
|
|
2757
|
-
#
|
|
2758
|
-
const
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
return base.endsWith(".dictionary.ts") || base.endsWith(".signal.ts");
|
|
2760
|
+
async#firstExisting(basePath) {
|
|
2761
|
+
for (const suffix of CSS_IMPORT_EXTS) {
|
|
2762
|
+
const candidate = `${basePath}${suffix}`;
|
|
2763
|
+
if (await Bun.file(candidate).exists())
|
|
2764
|
+
return candidate;
|
|
2765
|
+
}
|
|
2766
|
+
return null;
|
|
2765
2767
|
}
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
return false;
|
|
2772
|
-
if (!facet || !BARREL_FACETS.has(facet))
|
|
2773
|
-
return false;
|
|
2774
|
-
if (!child || child.startsWith(".") || child === "index.ts" || child === "index.tsx")
|
|
2775
|
-
return false;
|
|
2776
|
-
return true;
|
|
2768
|
+
static getPackageName(id) {
|
|
2769
|
+
const parts = id.split("/");
|
|
2770
|
+
if (id.startsWith("@"))
|
|
2771
|
+
return parts.length >= 2 ? `${parts[0]}/${parts[1]}` : null;
|
|
2772
|
+
return parts[0] ?? null;
|
|
2777
2773
|
}
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
return false;
|
|
2781
|
-
const [scope] = rel.split(path14.sep);
|
|
2782
|
-
return scope === "apps" || scope === "libs" || scope === "pkgs";
|
|
2774
|
+
static isCssFile(filePath) {
|
|
2775
|
+
return path14.extname(filePath) === ".css";
|
|
2783
2776
|
}
|
|
2784
2777
|
}
|
|
2785
|
-
var uniqueResolved = (files) => [...new Set(files.map((file) => path14.resolve(file)))].sort();
|
|
2786
2778
|
|
|
2787
|
-
// pkgs/@akanjs/devkit/frontendBuild/
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
var
|
|
2791
|
-
var
|
|
2792
|
-
var FACET_EXCLUDED_FILE_RE = /(^index\.tsx?$|\.d\.ts$|\.(test|spec)\.(ts|tsx)$|\.css$|\.scss$|\.sass$)/;
|
|
2793
|
-
var FACET_PASCAL_CASE_RE = /^[A-Z][A-Za-z0-9]*$/;
|
|
2794
|
-
var FACET_CAMEL_CASE_RE = /^[a-z][A-Za-z0-9]*$/;
|
|
2795
|
-
var MODULE_UI_TYPES = ["Template", "Unit", "Util", "View", "Zone"];
|
|
2796
|
-
var SERVICE_UI_TYPES = ["Util", "Zone"];
|
|
2797
|
-
var SCALAR_UI_TYPES = ["Template", "Unit"];
|
|
2779
|
+
// pkgs/@akanjs/devkit/frontendBuild/cssCompiler.ts
|
|
2780
|
+
var SOURCE_EXTS2 = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
|
|
2781
|
+
var NON_SOURCE_EXT_RE2 = /\.(json|svg|png|jpe?g|webp|gif|avif|ico|woff2?|ttf|otf|mp3|mp4|wav)$/i;
|
|
2782
|
+
var NODE_MODULES_RE3 = /[\\/]node_modules[\\/]/;
|
|
2783
|
+
var AKANJS_NODE_MODULE_RE3 = /[\\/]node_modules[\\/]akanjs[\\/]/;
|
|
2798
2784
|
|
|
2799
|
-
class
|
|
2800
|
-
#
|
|
2801
|
-
|
|
2802
|
-
|
|
2785
|
+
class CssCompiler {
|
|
2786
|
+
#logger = new Logger2("CssCompiler");
|
|
2787
|
+
#transpiler = new Bun.Transpiler({ loader: "tsx" });
|
|
2788
|
+
#app;
|
|
2789
|
+
#cssImportResolver = null;
|
|
2790
|
+
constructor(app) {
|
|
2791
|
+
this.#app = app;
|
|
2803
2792
|
}
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
errors.push(`[generated-index] module detection failed for ${file}: ${formatError2(err)}`);
|
|
2813
|
-
return null;
|
|
2814
|
-
});
|
|
2815
|
-
if (moduleIndex)
|
|
2816
|
-
indexPaths.add(moduleIndex);
|
|
2817
|
-
}
|
|
2818
|
-
const changedFiles = [];
|
|
2819
|
-
for (const indexPath of [...indexPaths].sort()) {
|
|
2820
|
-
try {
|
|
2821
|
-
const changed = await this.#syncIndex(indexPath);
|
|
2822
|
-
if (changed)
|
|
2823
|
-
changedFiles.push(indexPath);
|
|
2824
|
-
} catch (err) {
|
|
2825
|
-
errors.push(`[generated-index] sync failed for ${indexPath}: ${formatError2(err)}`);
|
|
2826
|
-
}
|
|
2827
|
-
}
|
|
2828
|
-
return { changedFiles, errors };
|
|
2829
|
-
}
|
|
2830
|
-
#facetIndexFor(file) {
|
|
2831
|
-
const abs = path15.resolve(file);
|
|
2832
|
-
const rel = path15.relative(this.#workspaceRoot, abs);
|
|
2833
|
-
if (rel.startsWith("..") || path15.isAbsolute(rel))
|
|
2834
|
-
return null;
|
|
2835
|
-
const parts = rel.split(path15.sep).filter(Boolean);
|
|
2836
|
-
if (parts.length < 4)
|
|
2837
|
-
return null;
|
|
2838
|
-
const [scope, project, facet, child] = parts;
|
|
2839
|
-
if (scope !== "apps" && scope !== "libs" || !project || !facet || !BARREL_FACETS2.has(facet))
|
|
2840
|
-
return null;
|
|
2841
|
-
if (!child || child.startsWith(".") || child === "index.ts" || child === "index.tsx")
|
|
2842
|
-
return null;
|
|
2843
|
-
return path15.join(this.#workspaceRoot, scope, project, facet, "index.ts");
|
|
2844
|
-
}
|
|
2845
|
-
async#moduleIndexForDirectoryEvent(file) {
|
|
2846
|
-
const abs = path15.resolve(file);
|
|
2847
|
-
const rel = path15.relative(this.#workspaceRoot, abs);
|
|
2848
|
-
if (rel.startsWith("..") || path15.isAbsolute(rel))
|
|
2849
|
-
return null;
|
|
2850
|
-
const parts = rel.split(path15.sep).filter(Boolean);
|
|
2851
|
-
if (parts.length !== 4 && parts.length !== 5)
|
|
2852
|
-
return null;
|
|
2853
|
-
const [scope, project, libSegment, moduleSegment, scalarSegment] = parts;
|
|
2854
|
-
if (scope !== "apps" && scope !== "libs" || !project || libSegment !== "lib")
|
|
2855
|
-
return null;
|
|
2856
|
-
const isExistingDirectory = await stat2(abs).then((s) => s.isDirectory()).catch(() => false);
|
|
2857
|
-
const looksLikeDeletedDirectory = !path15.extname(abs);
|
|
2858
|
-
if (!isExistingDirectory && !looksLikeDeletedDirectory)
|
|
2859
|
-
return null;
|
|
2860
|
-
if (moduleSegment === "__scalar" && scalarSegment) {
|
|
2861
|
-
return path15.join(this.#workspaceRoot, scope, project, "lib", "__scalar", scalarSegment, "index.ts");
|
|
2862
|
-
}
|
|
2863
|
-
if (!moduleSegment || moduleSegment === "__scalar")
|
|
2864
|
-
return null;
|
|
2865
|
-
return path15.join(this.#workspaceRoot, scope, project, "lib", moduleSegment, "index.ts");
|
|
2866
|
-
}
|
|
2867
|
-
async#syncIndex(indexPath) {
|
|
2868
|
-
const dir = path15.dirname(indexPath);
|
|
2869
|
-
const content = await this.#contentForIndex(indexPath);
|
|
2870
|
-
if (content === null) {
|
|
2871
|
-
if (!await exists(indexPath))
|
|
2872
|
-
return false;
|
|
2873
|
-
await rm2(indexPath, { force: true });
|
|
2874
|
-
return true;
|
|
2875
|
-
}
|
|
2876
|
-
const current = await readFile2(indexPath, "utf8").catch(() => null);
|
|
2877
|
-
if (current === content)
|
|
2878
|
-
return false;
|
|
2879
|
-
await mkdir4(dir, { recursive: true });
|
|
2880
|
-
await writeFile2(indexPath, content);
|
|
2881
|
-
return true;
|
|
2882
|
-
}
|
|
2883
|
-
async#contentForIndex(indexPath) {
|
|
2884
|
-
const parts = path15.relative(this.#workspaceRoot, indexPath).split(path15.sep).filter(Boolean);
|
|
2885
|
-
const facet = parts.at(-2);
|
|
2886
|
-
if (facet && BARREL_FACETS2.has(facet))
|
|
2887
|
-
return this.#facetContent(path15.dirname(indexPath));
|
|
2888
|
-
return this.#moduleContent(path15.dirname(indexPath));
|
|
2889
|
-
}
|
|
2890
|
-
async#facetContent(dir) {
|
|
2891
|
-
const nameCasePattern = path15.basename(dir) === "ui" ? FACET_PASCAL_CASE_RE : FACET_CAMEL_CASE_RE;
|
|
2892
|
-
const entries = await readdir2(dir, { withFileTypes: true }).catch(() => []);
|
|
2893
|
-
const exportNames = entries.flatMap((entry) => {
|
|
2894
|
-
const name = entry.name;
|
|
2895
|
-
if (name.startsWith("."))
|
|
2896
|
-
return [];
|
|
2897
|
-
if (entry.isDirectory())
|
|
2898
|
-
return nameCasePattern.test(name) ? [name] : [];
|
|
2899
|
-
if (!entry.isFile())
|
|
2900
|
-
return [];
|
|
2901
|
-
if (!FACET_SOURCE_FILE_RE.test(name) || FACET_EXCLUDED_FILE_RE.test(name))
|
|
2902
|
-
return [];
|
|
2903
|
-
const exportName = name.replace(FACET_SOURCE_FILE_RE, "");
|
|
2904
|
-
return nameCasePattern.test(exportName) ? [exportName] : [];
|
|
2905
|
-
}).sort();
|
|
2906
|
-
if (exportNames.length === 0)
|
|
2907
|
-
return null;
|
|
2908
|
-
return `${exportNames.map((name) => `export * from "./${name}";`).join(`
|
|
2909
|
-
`)}
|
|
2910
|
-
`;
|
|
2911
|
-
}
|
|
2912
|
-
async#moduleContent(dir) {
|
|
2913
|
-
const rel = path15.relative(this.#workspaceRoot, dir);
|
|
2914
|
-
const parts = rel.split(path15.sep).filter(Boolean);
|
|
2915
|
-
const moduleSegment = parts.at(-1);
|
|
2916
|
-
if (!moduleSegment)
|
|
2917
|
-
return null;
|
|
2918
|
-
const isScalar = parts.at(-2) === "__scalar";
|
|
2919
|
-
const rawModel = moduleSegment.startsWith("_") ? moduleSegment.slice(1) : moduleSegment;
|
|
2920
|
-
if (!rawModel)
|
|
2921
|
-
return null;
|
|
2922
|
-
const modelName = capitalize(rawModel);
|
|
2923
|
-
const allowedTypes = isScalar ? SCALAR_UI_TYPES : moduleSegment.startsWith("_") ? SERVICE_UI_TYPES : MODULE_UI_TYPES;
|
|
2924
|
-
const fileTypes = [];
|
|
2925
|
-
for (const type of allowedTypes) {
|
|
2926
|
-
if (await exists(path15.join(dir, `${modelName}.${type}.tsx`)))
|
|
2927
|
-
fileTypes.push(type);
|
|
2928
|
-
}
|
|
2929
|
-
if (fileTypes.length === 0)
|
|
2930
|
-
return null;
|
|
2931
|
-
return `
|
|
2932
|
-
${fileTypes.map((type) => `import * as ${type} from "./${modelName}.${type}";`).join(`
|
|
2933
|
-
`)}
|
|
2934
|
-
|
|
2935
|
-
export const ${modelName} = { ${fileTypes.join(", ")} };`;
|
|
2936
|
-
}
|
|
2937
|
-
}
|
|
2938
|
-
var exists = async (file) => stat2(file).then(() => true).catch(() => false);
|
|
2939
|
-
var capitalize = (value) => `${value.charAt(0).toUpperCase()}${value.slice(1)}`;
|
|
2940
|
-
var formatError2 = (err) => err instanceof Error ? err.message : String(err);
|
|
2941
|
-
|
|
2942
|
-
// pkgs/@akanjs/devkit/frontendBuild/fontOptimizer.ts
|
|
2943
|
-
import { mkdir as mkdir5 } from "fs/promises";
|
|
2944
|
-
import path16 from "path";
|
|
2945
|
-
import {
|
|
2946
|
-
generateFontFace,
|
|
2947
|
-
getMetricsForFamily,
|
|
2948
|
-
readMetrics,
|
|
2949
|
-
resolveCategoryFallbacks
|
|
2950
|
-
} from "fontaine";
|
|
2951
|
-
import { createFont, woff2 } from "fonteditor-core";
|
|
2952
|
-
import subsetFont from "subset-font";
|
|
2953
|
-
import ts4 from "typescript";
|
|
2954
|
-
var FONT_URL_PREFIX = "/_akan/fonts";
|
|
2955
|
-
var DEFAULT_FONT_SUBSETS = ["latin"];
|
|
2956
|
-
|
|
2957
|
-
class FontOptimizer {
|
|
2958
|
-
#app;
|
|
2959
|
-
#command;
|
|
2960
|
-
#artifactRoot;
|
|
2961
|
-
#files = [];
|
|
2962
|
-
#cssParts = [];
|
|
2963
|
-
#woff2Ready = null;
|
|
2964
|
-
static #ksX1001Text = null;
|
|
2965
|
-
constructor(app, command = "start") {
|
|
2966
|
-
this.#app = app;
|
|
2967
|
-
this.#command = command;
|
|
2968
|
-
this.#artifactRoot = path16.join(command === "build" ? app.dist.cwdPath : app.cwdPath, ".akan/artifact");
|
|
2969
|
-
}
|
|
2970
|
-
async optimize() {
|
|
2971
|
-
const fonts = await this.discoverFonts();
|
|
2972
|
-
for (const font of fonts) {
|
|
2973
|
-
if (!this.#isFontOptimizationEnabled(font))
|
|
2974
|
-
continue;
|
|
2975
|
-
await this.#optimizeFont(font);
|
|
2976
|
-
}
|
|
2977
|
-
const fontUtilityCss = this.#buildFontUtilityRules(fonts);
|
|
2978
|
-
if (fontUtilityCss)
|
|
2979
|
-
this.#cssParts.push(fontUtilityCss);
|
|
2980
|
-
return { css: this.#cssParts.join(`
|
|
2981
|
-
`), fonts, files: this.#files };
|
|
2982
|
-
}
|
|
2983
|
-
async discoverFonts() {
|
|
2984
|
-
const pageKeys = await this.#app.getPageKeys();
|
|
2985
|
-
const fonts = [];
|
|
2986
|
-
await Promise.all(pageKeys.map(async (key) => {
|
|
2987
|
-
const filePath = path16.resolve(this.#app.cwdPath, "page", key);
|
|
2988
|
-
const file = Bun.file(filePath);
|
|
2989
|
-
if (!await file.exists())
|
|
2990
|
-
return;
|
|
2991
|
-
fonts.push(...this.#extractFontsExport(await file.text(), filePath));
|
|
2992
|
-
}));
|
|
2993
|
-
return this.#dedupeFonts(fonts);
|
|
2994
|
-
}
|
|
2995
|
-
async#optimizeFont(font) {
|
|
2996
|
-
const faceCss = [];
|
|
2997
|
-
for (const face of this.#getFontFaces(font)) {
|
|
2998
|
-
const sourcePath = await this.#resolveFontSourcePath(face.src);
|
|
2999
|
-
if (!sourcePath) {
|
|
3000
|
-
this.#app.logger.warn(`[font] source not found: ${face.src}`);
|
|
3001
|
-
continue;
|
|
3002
|
-
}
|
|
3003
|
-
const outputPath = path16.join(this.#artifactRoot, face.optimizedSrc.replace(/^\/_akan\//, ""));
|
|
3004
|
-
await mkdir5(path16.dirname(outputPath), { recursive: true });
|
|
3005
|
-
const sourceBuffer = Buffer.from(await Bun.file(sourcePath).arrayBuffer());
|
|
3006
|
-
const outputBuffer = font.subset === false ? await this.#convertToWoff2(sourceBuffer, sourcePath) : await subsetFont(sourceBuffer, await this.#getSubsetText(font), { targetFormat: "woff2" });
|
|
3007
|
-
await Bun.write(outputPath, outputBuffer);
|
|
3008
|
-
this.#files.push(outputPath);
|
|
3009
|
-
faceCss.push(this.#buildOptimizedFontFaceRule(font, face));
|
|
3010
|
-
const fallbackCss = await this.#buildFontaineFallbackCss(font, face, outputPath);
|
|
3011
|
-
if (fallbackCss)
|
|
3012
|
-
faceCss.push(fallbackCss);
|
|
3013
|
-
}
|
|
3014
|
-
if (faceCss.length > 0)
|
|
3015
|
-
this.#cssParts.push(...faceCss, this.#buildRootVariableRule(font));
|
|
3016
|
-
}
|
|
3017
|
-
#extractFontsExport(source, filePath) {
|
|
3018
|
-
const sourceFile = ts4.createSourceFile(filePath, source, ts4.ScriptTarget.Latest, true, ts4.ScriptKind.TSX);
|
|
3019
|
-
const fonts = [];
|
|
3020
|
-
for (const statement of sourceFile.statements) {
|
|
3021
|
-
if (!ts4.isVariableStatement(statement))
|
|
3022
|
-
continue;
|
|
3023
|
-
const modifiers = ts4.canHaveModifiers(statement) ? ts4.getModifiers(statement) : undefined;
|
|
3024
|
-
const isExported = modifiers?.some((modifier) => modifier.kind === ts4.SyntaxKind.ExportKeyword) ?? false;
|
|
3025
|
-
if (!isExported)
|
|
3026
|
-
continue;
|
|
3027
|
-
for (const declaration of statement.declarationList.declarations) {
|
|
3028
|
-
if (!ts4.isIdentifier(declaration.name) || declaration.name.text !== "fonts")
|
|
3029
|
-
continue;
|
|
3030
|
-
const value = declaration.initializer ? this.#literalToValue(declaration.initializer) : null;
|
|
3031
|
-
if (Array.isArray(value)) {
|
|
3032
|
-
fonts.push(...value.map((font) => this.#withFontDefaults(font)));
|
|
3033
|
-
}
|
|
3034
|
-
}
|
|
3035
|
-
}
|
|
3036
|
-
return fonts;
|
|
3037
|
-
}
|
|
3038
|
-
#literalToValue(node) {
|
|
3039
|
-
if (ts4.isStringLiteralLike(node))
|
|
3040
|
-
return node.text;
|
|
3041
|
-
if (ts4.isNumericLiteral(node))
|
|
3042
|
-
return Number(node.text);
|
|
3043
|
-
if (node.kind === ts4.SyntaxKind.TrueKeyword)
|
|
3044
|
-
return true;
|
|
3045
|
-
if (node.kind === ts4.SyntaxKind.FalseKeyword)
|
|
3046
|
-
return false;
|
|
3047
|
-
if (ts4.isArrayLiteralExpression(node))
|
|
3048
|
-
return node.elements.map((element) => this.#literalToValue(element));
|
|
3049
|
-
if (ts4.isObjectLiteralExpression(node)) {
|
|
3050
|
-
const obj = {};
|
|
3051
|
-
for (const prop of node.properties) {
|
|
3052
|
-
if (!ts4.isPropertyAssignment(prop))
|
|
3053
|
-
continue;
|
|
3054
|
-
const name = this.#getPropertyName(prop.name);
|
|
3055
|
-
if (!name)
|
|
3056
|
-
continue;
|
|
3057
|
-
obj[name] = this.#literalToValue(prop.initializer);
|
|
3058
|
-
}
|
|
3059
|
-
return obj;
|
|
3060
|
-
}
|
|
3061
|
-
if (ts4.isAsExpression(node) || ts4.isSatisfiesExpression(node) || ts4.isParenthesizedExpression(node)) {
|
|
3062
|
-
return this.#literalToValue(node.expression);
|
|
3063
|
-
}
|
|
3064
|
-
return;
|
|
3065
|
-
}
|
|
3066
|
-
#getPropertyName(name) {
|
|
3067
|
-
if (ts4.isIdentifier(name) || ts4.isStringLiteral(name) || ts4.isNumericLiteral(name))
|
|
3068
|
-
return name.text;
|
|
3069
|
-
return null;
|
|
3070
|
-
}
|
|
3071
|
-
#dedupeFonts(fonts) {
|
|
3072
|
-
const map = new Map;
|
|
3073
|
-
for (const font of fonts)
|
|
3074
|
-
map.set(JSON.stringify(font), font);
|
|
3075
|
-
return [...map.values()];
|
|
3076
|
-
}
|
|
3077
|
-
#withFontDefaults(font) {
|
|
3078
|
-
return { ...font, subsets: font.subsets ?? [...DEFAULT_FONT_SUBSETS] };
|
|
3079
|
-
}
|
|
3080
|
-
#getFontSubsets(font) {
|
|
3081
|
-
return font.subsets ?? DEFAULT_FONT_SUBSETS;
|
|
3082
|
-
}
|
|
3083
|
-
#getFontVariableName(font) {
|
|
3084
|
-
return font.variable ?? `--font-${font.name}`;
|
|
3085
|
-
}
|
|
3086
|
-
#getFontFallbackName(font) {
|
|
3087
|
-
return font.fallbackName ?? `${font.name} fallback`;
|
|
3088
|
-
}
|
|
3089
|
-
#isFontOptimizationEnabled(font) {
|
|
3090
|
-
return font.optimize !== false;
|
|
3091
|
-
}
|
|
3092
|
-
#getFontStyles(font) {
|
|
3093
|
-
return font.styles?.length ? font.styles : ["normal"];
|
|
3094
|
-
}
|
|
3095
|
-
#getFontFaces(font) {
|
|
3096
|
-
const enabledStyles = new Set(this.#getFontStyles(font));
|
|
3097
|
-
return font.paths.map((fontPath) => {
|
|
3098
|
-
const style = fontPath.style ?? "normal";
|
|
3099
|
-
return {
|
|
3100
|
-
font,
|
|
3101
|
-
path: fontPath,
|
|
3102
|
-
src: fontPath.src,
|
|
3103
|
-
weight: fontPath.weight,
|
|
3104
|
-
style,
|
|
3105
|
-
optimizedSrc: this.#getOptimizedFontSrc(font, fontPath)
|
|
3106
|
-
};
|
|
3107
|
-
}).filter((face) => enabledStyles.has(face.style));
|
|
3108
|
-
}
|
|
3109
|
-
#getOptimizedFontSrc(font, fontPath) {
|
|
3110
|
-
const style = fontPath.style ?? "normal";
|
|
3111
|
-
const hash = this.#hashFontConfig({
|
|
3112
|
-
name: font.name,
|
|
3113
|
-
src: fontPath.src,
|
|
3114
|
-
weight: fontPath.weight,
|
|
3115
|
-
style,
|
|
3116
|
-
display: font.display,
|
|
3117
|
-
subset: font.subset,
|
|
3118
|
-
subsets: this.#getFontSubsets(font),
|
|
3119
|
-
subsetText: font.subsetText,
|
|
3120
|
-
subsetFiles: font.subsetFiles,
|
|
3121
|
-
declarations: [...font.declarations ?? [], ...fontPath.declarations ?? []]
|
|
3122
|
-
});
|
|
3123
|
-
return `${FONT_URL_PREFIX}/${this.#slugFontPart(font.name)}-${this.#slugFontPart(String(fontPath.weight))}-${style}-${hash}.woff2`;
|
|
3124
|
-
}
|
|
3125
|
-
#hashFontConfig(value) {
|
|
3126
|
-
const input = this.#stableStringify(value);
|
|
3127
|
-
let hash = 2166136261;
|
|
3128
|
-
for (let i = 0;i < input.length; i++) {
|
|
3129
|
-
hash ^= input.charCodeAt(i);
|
|
3130
|
-
hash = Math.imul(hash, 16777619);
|
|
3131
|
-
}
|
|
3132
|
-
return (hash >>> 0).toString(36);
|
|
3133
|
-
}
|
|
3134
|
-
#stableStringify(value) {
|
|
3135
|
-
if (Array.isArray(value))
|
|
3136
|
-
return `[${value.map((item) => this.#stableStringify(item)).join(",")}]`;
|
|
3137
|
-
if (value && typeof value === "object") {
|
|
3138
|
-
const entries = Object.entries(value).filter(([, v]) => v !== undefined).sort(([a], [b]) => a.localeCompare(b));
|
|
3139
|
-
return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${this.#stableStringify(v)}`).join(",")}}`;
|
|
3140
|
-
}
|
|
3141
|
-
return JSON.stringify(value);
|
|
3142
|
-
}
|
|
3143
|
-
#slugFontPart(value) {
|
|
3144
|
-
return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "font";
|
|
3145
|
-
}
|
|
3146
|
-
async#resolveFontSourcePath(src) {
|
|
3147
|
-
if (!src.startsWith("/"))
|
|
3148
|
-
return null;
|
|
3149
|
-
const rel = src.replace(/^\//, "");
|
|
3150
|
-
const candidates = [
|
|
3151
|
-
this.#command === "build" ? path16.join(this.#app.dist.cwdPath, "public", rel) : null,
|
|
3152
|
-
path16.join(this.#app.cwdPath, "public", rel),
|
|
3153
|
-
this.#resolveWorkspacePublicPath(rel)
|
|
3154
|
-
].filter(Boolean);
|
|
3155
|
-
for (const candidate of candidates) {
|
|
3156
|
-
if (await Bun.file(candidate).exists())
|
|
3157
|
-
return candidate;
|
|
3158
|
-
}
|
|
3159
|
-
return null;
|
|
3160
|
-
}
|
|
3161
|
-
async#convertToWoff2(buffer, sourcePath) {
|
|
3162
|
-
await this.#initWoff2();
|
|
3163
|
-
const font = createFont(buffer, { type: this.#getFontType(sourcePath, buffer) });
|
|
3164
|
-
return font.write({ type: "woff2", toBuffer: true });
|
|
3165
|
-
}
|
|
3166
|
-
async#initWoff2() {
|
|
3167
|
-
this.#woff2Ready ??= woff2.init().then(() => {
|
|
3168
|
-
return;
|
|
3169
|
-
});
|
|
3170
|
-
return this.#woff2Ready;
|
|
3171
|
-
}
|
|
3172
|
-
#getFontType(sourcePath, buffer) {
|
|
3173
|
-
const signature = buffer.toString("ascii", 0, 4);
|
|
3174
|
-
if (signature === "wOFF")
|
|
3175
|
-
return "woff";
|
|
3176
|
-
if (signature === "wOF2")
|
|
3177
|
-
return "woff2";
|
|
3178
|
-
if (signature === "OTTO")
|
|
3179
|
-
return "otf";
|
|
3180
|
-
const ext = path16.extname(sourcePath).slice(1).toLowerCase();
|
|
3181
|
-
if (ext === "otf" || ext === "woff" || ext === "woff2")
|
|
3182
|
-
return ext;
|
|
3183
|
-
return "ttf";
|
|
3184
|
-
}
|
|
3185
|
-
#resolveWorkspacePublicPath(rel) {
|
|
3186
|
-
const [root, dep, ...rest] = rel.split("/");
|
|
3187
|
-
if (root !== "libs" || !dep || rest.length === 0)
|
|
3188
|
-
return null;
|
|
3189
|
-
return path16.join(this.#app.workspace.workspaceRoot, "libs", dep, "public", ...rest);
|
|
2793
|
+
#cssText = null;
|
|
2794
|
+
#cssTextByBasePath = null;
|
|
2795
|
+
async getCss({ refresh } = {}) {
|
|
2796
|
+
if (this.#cssText !== null && !refresh)
|
|
2797
|
+
return this.#cssText;
|
|
2798
|
+
const { cssPaths, sourcePaths } = await this.discoverCssAndSources({ refresh });
|
|
2799
|
+
this.#cssText = await this.compileCss(cssPaths, sourcePaths);
|
|
2800
|
+
return this.#cssText;
|
|
3190
2801
|
}
|
|
3191
|
-
async
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
2802
|
+
async getCssByBasePath({ refresh } = {}) {
|
|
2803
|
+
if (this.#cssTextByBasePath !== null && !refresh)
|
|
2804
|
+
return this.#cssTextByBasePath;
|
|
2805
|
+
const akanConfig = await this.#app.getConfig({ refresh });
|
|
2806
|
+
const pageKeys = await this.#app.getPageKeys({ refresh });
|
|
2807
|
+
const basePaths = [...akanConfig.basePaths];
|
|
2808
|
+
const rootPageKeys = pageKeys.filter((pageKey) => getPageKeyBasePath(pageKey, basePaths) === null);
|
|
2809
|
+
const cssEntries = await Promise.all([
|
|
2810
|
+
(async () => {
|
|
2811
|
+
if (rootPageKeys.length === 0)
|
|
2812
|
+
return ["", ""];
|
|
2813
|
+
const started = Date.now();
|
|
2814
|
+
const { cssPaths, sourcePaths } = await this.discoverCssAndSources({ refresh, pageKeys: rootPageKeys });
|
|
2815
|
+
const css = await this.compileCss(cssPaths, sourcePaths);
|
|
2816
|
+
this.#logger.verbose(`css base=root paths=${cssPaths.length} sources=${sourcePaths.length} in ${Date.now() - started}ms`);
|
|
2817
|
+
return ["", css];
|
|
2818
|
+
})(),
|
|
2819
|
+
...basePaths.map(async (basePath) => {
|
|
2820
|
+
const basePathPageKeys = pageKeys.filter((pageKey) => getPageKeyBasePath(pageKey, basePaths) === basePath);
|
|
2821
|
+
if (basePathPageKeys.length === 0)
|
|
2822
|
+
return [basePath, ""];
|
|
2823
|
+
const started = Date.now();
|
|
2824
|
+
const { cssPaths, sourcePaths } = await this.discoverCssAndSources({ refresh, pageKeys: basePathPageKeys });
|
|
2825
|
+
const css = await this.compileCss(cssPaths, sourcePaths);
|
|
2826
|
+
this.#logger.verbose(`css base=${basePath} paths=${cssPaths.length} sources=${sourcePaths.length} in ${Date.now() - started}ms`);
|
|
2827
|
+
return [basePath, css];
|
|
2828
|
+
})
|
|
2829
|
+
]);
|
|
2830
|
+
this.#cssTextByBasePath = Object.fromEntries(cssEntries);
|
|
2831
|
+
return this.#cssTextByBasePath;
|
|
3205
2832
|
}
|
|
3206
|
-
async
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
if (subset === "latin-ext")
|
|
3210
|
-
return `${this.#rangeText(32, 126)}${this.#rangeText(160, 591)}`;
|
|
3211
|
-
if (subset === "ks-x-1001")
|
|
3212
|
-
return FontOptimizer.#getKsX1001Text();
|
|
3213
|
-
if (subset === "auto")
|
|
3214
|
-
return this.#collectAutoSubsetText();
|
|
3215
|
-
return "";
|
|
2833
|
+
async discoverCss({ refresh } = {}) {
|
|
2834
|
+
const { cssPaths } = await this.discoverCssAndSources({ refresh });
|
|
2835
|
+
return cssPaths;
|
|
3216
2836
|
}
|
|
3217
|
-
async
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
await
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
2837
|
+
async discoverCssAndSources({
|
|
2838
|
+
refresh,
|
|
2839
|
+
pageKeys
|
|
2840
|
+
} = {}) {
|
|
2841
|
+
pageKeys ??= await this.#app.getPageKeys({ refresh });
|
|
2842
|
+
const seeds = pageKeys.map((key) => path15.resolve(this.#app.cwdPath, "page", key));
|
|
2843
|
+
const cssFiles = new Set;
|
|
2844
|
+
const sourceFiles = new Set;
|
|
2845
|
+
const queue = [...seeds];
|
|
2846
|
+
const resolvePackage = await createTsconfigPackageResolver(this.#app);
|
|
2847
|
+
const analyzer = new BarrelAnalyzer({ resolvePackage });
|
|
2848
|
+
const akanConfig = await this.#app.getConfig({ refresh });
|
|
2849
|
+
while (queue.length > 0) {
|
|
2850
|
+
const filePath = queue.shift();
|
|
2851
|
+
if (!filePath || sourceFiles.has(filePath) || isIgnoredNodeModuleSource(filePath))
|
|
2852
|
+
continue;
|
|
2853
|
+
sourceFiles.add(filePath);
|
|
2854
|
+
let content;
|
|
2855
|
+
try {
|
|
2856
|
+
content = await Bun.file(filePath).text();
|
|
2857
|
+
} catch {
|
|
2858
|
+
continue;
|
|
3226
2859
|
}
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
const
|
|
3242
|
-
for (
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
2860
|
+
let source = content;
|
|
2861
|
+
if (akanConfig.barrelImports.length > 0) {
|
|
2862
|
+
try {
|
|
2863
|
+
const rewritten = await rewriteBarrelImports(content, akanConfig.barrelImports, analyzer);
|
|
2864
|
+
if (rewritten !== null)
|
|
2865
|
+
source = rewritten;
|
|
2866
|
+
} catch {}
|
|
2867
|
+
}
|
|
2868
|
+
let imports;
|
|
2869
|
+
try {
|
|
2870
|
+
imports = this.#transpiler.scanImports(source);
|
|
2871
|
+
} catch {
|
|
2872
|
+
continue;
|
|
2873
|
+
}
|
|
2874
|
+
const importerDir = path15.dirname(filePath);
|
|
2875
|
+
for (const imp of imports) {
|
|
2876
|
+
const spec = imp.path;
|
|
2877
|
+
if (!spec)
|
|
2878
|
+
continue;
|
|
2879
|
+
if (spec.endsWith(".css")) {
|
|
2880
|
+
const cssPath = await this.#resolveCssImport(spec, importerDir);
|
|
2881
|
+
cssFiles.add(cssPath);
|
|
2882
|
+
continue;
|
|
3247
2883
|
}
|
|
2884
|
+
if (NON_SOURCE_EXT_RE2.test(spec))
|
|
2885
|
+
continue;
|
|
2886
|
+
const resolved = await this.#resolveSourceImport(spec, importerDir, resolvePackage);
|
|
2887
|
+
if (!resolved || sourceFiles.has(resolved) || isIgnoredNodeModuleSource(resolved))
|
|
2888
|
+
continue;
|
|
2889
|
+
queue.push(resolved);
|
|
3248
2890
|
}
|
|
3249
|
-
FontOptimizer.#ksX1001Text = [...chars].join("");
|
|
3250
|
-
} catch {
|
|
3251
|
-
FontOptimizer.#ksX1001Text = FontOptimizer.#rangeTextStatic(44032, 55203);
|
|
3252
2891
|
}
|
|
3253
|
-
return
|
|
3254
|
-
}
|
|
3255
|
-
static #rangeTextStatic(start, end) {
|
|
3256
|
-
let text = "";
|
|
3257
|
-
for (let code = start;code <= end; code++)
|
|
3258
|
-
text += String.fromCodePoint(code);
|
|
3259
|
-
return text;
|
|
3260
|
-
}
|
|
3261
|
-
#buildOptimizedFontFaceRule(font, face) {
|
|
3262
|
-
const declarations = [
|
|
3263
|
-
["font-family", this.#quote(font.name)],
|
|
3264
|
-
["src", `url(${this.#quote(face.optimizedSrc)}) format("woff2")`],
|
|
3265
|
-
["font-weight", String(face.weight)],
|
|
3266
|
-
["font-style", face.style],
|
|
3267
|
-
["font-display", font.display ?? "swap"],
|
|
3268
|
-
...this.#toDeclarationEntries(font.declarations),
|
|
3269
|
-
...this.#toDeclarationEntries(face.path.declarations)
|
|
3270
|
-
];
|
|
3271
|
-
return `@font-face {
|
|
3272
|
-
${declarations.map(([prop, value]) => ` ${prop}: ${value};`).join(`
|
|
3273
|
-
`)}
|
|
3274
|
-
}`;
|
|
2892
|
+
return { cssPaths: [...cssFiles], sourcePaths: [...sourceFiles] };
|
|
3275
2893
|
}
|
|
3276
|
-
async
|
|
3277
|
-
if (
|
|
3278
|
-
return "";
|
|
3279
|
-
const metrics = await readMetrics(outputPath).catch(() => null);
|
|
3280
|
-
if (!metrics)
|
|
2894
|
+
async compileCss(cssPaths, sourcePaths) {
|
|
2895
|
+
if (cssPaths.length === 0)
|
|
3281
2896
|
return "";
|
|
3282
|
-
const
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
2897
|
+
const compileStarted = Date.now();
|
|
2898
|
+
const compilers = await Promise.all(cssPaths.map(async (cssPath) => {
|
|
2899
|
+
const css = await Bun.file(cssPath).text();
|
|
2900
|
+
const base = path15.dirname(cssPath);
|
|
2901
|
+
const compiler = await compile(css, {
|
|
2902
|
+
base,
|
|
2903
|
+
loadStylesheet: (id, fromBase) => this.#loadStylesheet(id, fromBase),
|
|
2904
|
+
loadModule: (id, fromBase) => this.#loadModule(id, fromBase)
|
|
2905
|
+
});
|
|
2906
|
+
return { cssPath, compiler };
|
|
2907
|
+
}));
|
|
2908
|
+
const sourceDirs = new Set;
|
|
2909
|
+
for (const entry of compilers) {
|
|
2910
|
+
if (!entry)
|
|
3292
2911
|
continue;
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
font: fallback,
|
|
3296
|
-
metrics: fallbackMetrics,
|
|
3297
|
-
"font-weight": String(face.weight),
|
|
3298
|
-
"font-style": face.style
|
|
3299
|
-
}));
|
|
2912
|
+
for (const s of entry.compiler.sources)
|
|
2913
|
+
sourceDirs.add(s.base);
|
|
3300
2914
|
}
|
|
3301
|
-
|
|
2915
|
+
const scanStarted = Date.now();
|
|
2916
|
+
const candidates = await this.#scanCandidates(sourcePaths, [...sourceDirs]);
|
|
2917
|
+
this.#logger.verbose(`css candidates scanned count=${candidates.length} sources=${sourcePaths.length} dirs=${sourceDirs.size} in ${Date.now() - scanStarted}ms`);
|
|
2918
|
+
const parts = [];
|
|
2919
|
+
for (const entry of compilers) {
|
|
2920
|
+
if (!entry)
|
|
2921
|
+
continue;
|
|
2922
|
+
parts.push(entry.compiler.build(candidates));
|
|
2923
|
+
}
|
|
2924
|
+
this.#logger.verbose(`css compiled paths=${cssPaths.length} candidates=${candidates.length} in ${Date.now() - compileStarted}ms`);
|
|
2925
|
+
return parts.join(`
|
|
2926
|
+
`);
|
|
2927
|
+
}
|
|
2928
|
+
async#loadStylesheet(id, fromBase) {
|
|
2929
|
+
const p = await this.#resolveCssImport(id, fromBase);
|
|
2930
|
+
const content = await Bun.file(p).text();
|
|
2931
|
+
return { path: p, base: path15.dirname(p), content };
|
|
2932
|
+
}
|
|
2933
|
+
async#resolveCssImport(id, fromBase) {
|
|
2934
|
+
if (id.startsWith(".") || id.startsWith("/"))
|
|
2935
|
+
return path15.resolve(fromBase, id);
|
|
2936
|
+
const resolver = await this.#getCssImportResolver();
|
|
2937
|
+
const resolved = await resolver.resolve(id, fromBase);
|
|
2938
|
+
if (resolved)
|
|
2939
|
+
return resolved;
|
|
2940
|
+
throw new Error(`[css] failed to resolve stylesheet import "${id}" from ${fromBase}`);
|
|
2941
|
+
}
|
|
2942
|
+
async#getCssImportResolver() {
|
|
2943
|
+
if (this.#cssImportResolver)
|
|
2944
|
+
return this.#cssImportResolver;
|
|
2945
|
+
this.#cssImportResolver = await CssImportResolver.create(this.#app);
|
|
2946
|
+
return this.#cssImportResolver;
|
|
3302
2947
|
}
|
|
3303
|
-
#
|
|
3304
|
-
|
|
2948
|
+
async#loadModule(id, fromBase) {
|
|
2949
|
+
const p = __require.resolve(id, { paths: [fromBase] });
|
|
2950
|
+
const mod = await import(p);
|
|
2951
|
+
return { path: p, base: path15.dirname(p), module: mod.default ?? mod };
|
|
3305
2952
|
}
|
|
3306
|
-
#
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
const className = font.className || `font-${font.name}`;
|
|
3311
|
-
const selector = `.${this.#escapeCssClassName(className)}`;
|
|
3312
|
-
const rule = `${selector} { font-family: var(${this.#getFontVariableName(font)}); }`;
|
|
3313
|
-
if (seen.has(rule))
|
|
3314
|
-
continue;
|
|
3315
|
-
seen.add(rule);
|
|
3316
|
-
rules.push(rule);
|
|
2953
|
+
async#resolveSourceImport(id, fromBase, resolvePackage) {
|
|
2954
|
+
if (id.startsWith(".") || id.startsWith("/")) {
|
|
2955
|
+
const abs = id.startsWith("/") ? id : path15.resolve(fromBase, id);
|
|
2956
|
+
return resolveSourceFileCandidate(abs);
|
|
3317
2957
|
}
|
|
3318
|
-
|
|
3319
|
-
|
|
2958
|
+
const pkg = await resolvePackage(id);
|
|
2959
|
+
if (pkg)
|
|
2960
|
+
return pkg.entryFile;
|
|
2961
|
+
for (const resolve of [() => resolveSourceWithBun(id, fromBase), () => resolveSourceWithRequire(id, fromBase)]) {
|
|
2962
|
+
const resolved = await resolve();
|
|
2963
|
+
if (resolved)
|
|
2964
|
+
return resolved;
|
|
2965
|
+
}
|
|
2966
|
+
return null;
|
|
3320
2967
|
}
|
|
3321
|
-
#
|
|
3322
|
-
|
|
2968
|
+
async#scanCandidates(sourcePaths, dirs) {
|
|
2969
|
+
const CANDIDATE_RE = /-?[\w@][\w:/.-]*(?:\[[^\]]+\][\w:/.-]*)*/g;
|
|
2970
|
+
const candidates = new Set;
|
|
2971
|
+
const glob = new Bun.Glob("**/*.{tsx,ts,jsx,js,html}");
|
|
2972
|
+
const files = new Set(sourcePaths);
|
|
2973
|
+
await Promise.all(dirs.map(async (dir) => {
|
|
2974
|
+
for await (const file of glob.scan({ cwd: dir, absolute: true })) {
|
|
2975
|
+
if (isIgnoredNodeModuleSource(file))
|
|
2976
|
+
continue;
|
|
2977
|
+
files.add(file);
|
|
2978
|
+
}
|
|
2979
|
+
}));
|
|
2980
|
+
await Promise.all([...files].map(async (file) => {
|
|
2981
|
+
const content = await Bun.file(file).text();
|
|
2982
|
+
for (const m of content.matchAll(CANDIDATE_RE))
|
|
2983
|
+
candidates.add(m[0]);
|
|
2984
|
+
}));
|
|
2985
|
+
return [...candidates];
|
|
3323
2986
|
}
|
|
3324
|
-
|
|
3325
|
-
|
|
2987
|
+
}
|
|
2988
|
+
async function resolveSourceFileCandidate(absPathNoExt) {
|
|
2989
|
+
if (await Bun.file(absPathNoExt).exists())
|
|
2990
|
+
return isSourceFile(absPathNoExt) ? absPathNoExt : null;
|
|
2991
|
+
for (const ext of SOURCE_EXTS2) {
|
|
2992
|
+
const filePath = `${absPathNoExt}${ext}`;
|
|
2993
|
+
if (await Bun.file(filePath).exists())
|
|
2994
|
+
return filePath;
|
|
3326
2995
|
}
|
|
3327
|
-
|
|
3328
|
-
|
|
2996
|
+
for (const ext of SOURCE_EXTS2) {
|
|
2997
|
+
const filePath = path15.join(absPathNoExt, `index${ext}`);
|
|
2998
|
+
if (await Bun.file(filePath).exists())
|
|
2999
|
+
return filePath;
|
|
3329
3000
|
}
|
|
3001
|
+
return null;
|
|
3330
3002
|
}
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
import path17 from "path";
|
|
3338
|
-
var SOURCE_EXTS3 = new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
|
|
3339
|
-
var CSS_EXTS = new Set([".css"]);
|
|
3340
|
-
var CONFIG_BASENAMES2 = new Set(["akan.config.ts", "bunfig.toml", "tsconfig.json", "package.json"]);
|
|
3341
|
-
|
|
3342
|
-
class HmrChangeClassifier {
|
|
3343
|
-
classify(abs) {
|
|
3344
|
-
if (this.#isUninteresting(abs))
|
|
3345
|
-
return "ignore";
|
|
3346
|
-
const base = path17.basename(abs);
|
|
3347
|
-
if (CONFIG_BASENAMES2.has(base))
|
|
3348
|
-
return "config";
|
|
3349
|
-
const ext = path17.extname(abs).toLowerCase();
|
|
3350
|
-
if (CSS_EXTS.has(ext))
|
|
3351
|
-
return "css";
|
|
3352
|
-
if (SOURCE_EXTS3.has(ext))
|
|
3353
|
-
return "code";
|
|
3354
|
-
return "ignore";
|
|
3003
|
+
function resolveSourceWithBun(id, fromBase) {
|
|
3004
|
+
try {
|
|
3005
|
+
const resolved = Bun.resolveSync(id, fromBase);
|
|
3006
|
+
return isSourceFile(resolved) ? resolved : null;
|
|
3007
|
+
} catch {
|
|
3008
|
+
return null;
|
|
3355
3009
|
}
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
return true;
|
|
3364
|
-
if (abs.includes(`${path17.sep}node_modules${path17.sep}`))
|
|
3365
|
-
return true;
|
|
3366
|
-
if (abs.includes(`${path17.sep}.akan${path17.sep}`))
|
|
3367
|
-
return true;
|
|
3368
|
-
return false;
|
|
3010
|
+
}
|
|
3011
|
+
function resolveSourceWithRequire(id, fromBase) {
|
|
3012
|
+
try {
|
|
3013
|
+
const resolved = __require.resolve(id, { paths: [fromBase] });
|
|
3014
|
+
return isSourceFile(resolved) ? resolved : null;
|
|
3015
|
+
} catch {
|
|
3016
|
+
return null;
|
|
3369
3017
|
}
|
|
3370
3018
|
}
|
|
3019
|
+
function isSourceFile(filePath) {
|
|
3020
|
+
return SOURCE_EXTS2.includes(path15.extname(filePath));
|
|
3021
|
+
}
|
|
3022
|
+
function isIgnoredNodeModuleSource(filePath) {
|
|
3023
|
+
return NODE_MODULES_RE3.test(filePath) && !AKANJS_NODE_MODULE_RE3.test(filePath);
|
|
3024
|
+
}
|
|
3025
|
+
function getPageKeyBasePath(pageKey, basePaths) {
|
|
3026
|
+
const normalized = pageKey.split(path15.sep).join("/").replace(/^\.\//, "");
|
|
3027
|
+
const segments = normalized.split("/");
|
|
3028
|
+
const firstPublicSegment = segments.find((segment) => segment !== "[lang]" && !/^\(.+\)$/.test(segment));
|
|
3029
|
+
return firstPublicSegment && basePaths.includes(firstPublicSegment) ? firstPublicSegment : null;
|
|
3030
|
+
}
|
|
3371
3031
|
|
|
3372
|
-
// pkgs/@akanjs/devkit/frontendBuild/
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
#
|
|
3384
|
-
constructor(
|
|
3385
|
-
this.#
|
|
3386
|
-
this.#debounceMs = opts.debounceMs ?? 80;
|
|
3387
|
-
this.#onBatch = opts.onBatch;
|
|
3388
|
-
this.#logger = opts.logger;
|
|
3032
|
+
// pkgs/@akanjs/devkit/frontendBuild/devChangePlanner.ts
|
|
3033
|
+
import path16 from "path";
|
|
3034
|
+
var SOURCE_EXTS3 = new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
|
|
3035
|
+
var CONFIG_BASENAMES = new Set(["akan.config.ts", "bunfig.toml", "tsconfig.json", "package.json"]);
|
|
3036
|
+
var BARREL_FACETS = new Set(["common", "srvkit", "ui", "webkit", "plugin"]);
|
|
3037
|
+
var CLIENT_SUFFIXES = [".Template.tsx", ".Unit.tsx", ".Util.tsx", ".View.tsx", ".Zone.tsx", ".store.ts"];
|
|
3038
|
+
var SHARED_SUFFIXES = [".constant.ts", ".dictionary.ts", ".signal.ts"];
|
|
3039
|
+
var SERVER_SUFFIXES = [".service.ts", ".document.ts"];
|
|
3040
|
+
var RUNTIME_METADATA_BASENAMES = new Set(["dict.ts", "sig.ts", "useClient.ts", "useServer.ts"]);
|
|
3041
|
+
|
|
3042
|
+
class DevChangePlanner {
|
|
3043
|
+
#workspaceRoot;
|
|
3044
|
+
constructor({ workspaceRoot }) {
|
|
3045
|
+
this.#workspaceRoot = path16.resolve(workspaceRoot);
|
|
3389
3046
|
}
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
}
|
|
3402
|
-
|
|
3047
|
+
plan({ generation, files, kinds, generatedFiles = [] }) {
|
|
3048
|
+
const fileList = uniqueResolved([...files, ...generatedFiles]);
|
|
3049
|
+
const generatedSet = new Set(generatedFiles.map((file) => path16.resolve(file)));
|
|
3050
|
+
const kindSet = new Set(kinds);
|
|
3051
|
+
const roles = new Set;
|
|
3052
|
+
const actions = new Set;
|
|
3053
|
+
const reasonByFile = {};
|
|
3054
|
+
for (const kind of kindSet) {
|
|
3055
|
+
if (kind === "css") {
|
|
3056
|
+
roles.add("css");
|
|
3057
|
+
actions.add("rebuild-css");
|
|
3058
|
+
}
|
|
3059
|
+
if (kind === "config") {
|
|
3060
|
+
roles.add("config");
|
|
3061
|
+
actions.add("restart-dev-host");
|
|
3403
3062
|
}
|
|
3404
3063
|
}
|
|
3064
|
+
for (const file of fileList) {
|
|
3065
|
+
const reasons = new Set;
|
|
3066
|
+
const fileRoles = this.#rolesForFile(file, { isGenerated: generatedSet.has(path16.resolve(file)), reasons });
|
|
3067
|
+
for (const role of fileRoles)
|
|
3068
|
+
roles.add(role);
|
|
3069
|
+
if (reasons.has("runtime-metadata"))
|
|
3070
|
+
actions.add("restart-builder");
|
|
3071
|
+
if (reasons.size > 0)
|
|
3072
|
+
reasonByFile[path16.resolve(file)] = [...reasons].sort();
|
|
3073
|
+
}
|
|
3074
|
+
if (roles.has("barrel"))
|
|
3075
|
+
actions.add("sync-generated");
|
|
3076
|
+
if (roles.has("server") || roles.has("shared"))
|
|
3077
|
+
actions.add("restart-backend");
|
|
3078
|
+
if (roles.has("client") || roles.has("shared"))
|
|
3079
|
+
actions.add("rebuild-client");
|
|
3080
|
+
if (roles.has("css"))
|
|
3081
|
+
actions.add("rebuild-css");
|
|
3082
|
+
return {
|
|
3083
|
+
generation,
|
|
3084
|
+
files: fileList,
|
|
3085
|
+
generatedFiles: uniqueResolved(generatedFiles),
|
|
3086
|
+
roles: [...roles].sort(),
|
|
3087
|
+
actions: [...actions].sort(),
|
|
3088
|
+
reasonByFile
|
|
3089
|
+
};
|
|
3405
3090
|
}
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3091
|
+
#rolesForFile(file, { isGenerated, reasons }) {
|
|
3092
|
+
const roles = new Set;
|
|
3093
|
+
const abs = path16.resolve(file);
|
|
3094
|
+
const base = path16.basename(abs);
|
|
3095
|
+
const ext = path16.extname(abs).toLowerCase();
|
|
3096
|
+
const isSource = SOURCE_EXTS3.has(ext);
|
|
3097
|
+
const rel = path16.relative(this.#workspaceRoot, abs);
|
|
3098
|
+
const parts = rel.split(path16.sep).filter(Boolean);
|
|
3099
|
+
if (CONFIG_BASENAMES.has(base)) {
|
|
3100
|
+
roles.add("config");
|
|
3101
|
+
reasons.add("config-file");
|
|
3102
|
+
}
|
|
3103
|
+
if (ext === ".css") {
|
|
3104
|
+
roles.add("css");
|
|
3105
|
+
reasons.add("css-file");
|
|
3106
|
+
}
|
|
3107
|
+
if (isGenerated || isSource && this.#isBarrelFacetChild(parts)) {
|
|
3108
|
+
roles.add("barrel");
|
|
3109
|
+
reasons.add(isGenerated ? "generated-index" : "barrel-facet-child");
|
|
3110
|
+
}
|
|
3111
|
+
if (isSource && this.#isServerBiased(abs, parts)) {
|
|
3112
|
+
roles.add("server");
|
|
3113
|
+
reasons.add("server-path");
|
|
3114
|
+
}
|
|
3115
|
+
if (isSource && this.#isClientBiased(abs, parts)) {
|
|
3116
|
+
roles.add("client");
|
|
3117
|
+
reasons.add("client-path");
|
|
3118
|
+
}
|
|
3119
|
+
if (isSource && this.#isSharedBiased(abs, parts)) {
|
|
3120
|
+
roles.add("shared");
|
|
3121
|
+
reasons.add("shared-path");
|
|
3122
|
+
}
|
|
3123
|
+
if (isSource && this.#isRuntimeMetadataFile(parts, base)) {
|
|
3124
|
+
reasons.add("runtime-metadata");
|
|
3125
|
+
}
|
|
3126
|
+
if (roles.has("server") && roles.has("client")) {
|
|
3127
|
+
roles.delete("server");
|
|
3128
|
+
roles.delete("client");
|
|
3129
|
+
roles.add("shared");
|
|
3130
|
+
reasons.add("server-client-overlap");
|
|
3131
|
+
}
|
|
3132
|
+
if (roles.size === 0 && SOURCE_EXTS3.has(ext) && this.#isWorkspaceSource(rel)) {
|
|
3133
|
+
roles.add("shared");
|
|
3134
|
+
reasons.add("workspace-source-fallback");
|
|
3414
3135
|
}
|
|
3136
|
+
return roles;
|
|
3415
3137
|
}
|
|
3416
|
-
#
|
|
3417
|
-
const
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
if (this.#timer)
|
|
3424
|
-
clearTimeout(this.#timer);
|
|
3425
|
-
this.#timer = setTimeout(() => this.#flush(), this.#debounceMs);
|
|
3138
|
+
#isServerBiased(abs, parts) {
|
|
3139
|
+
const base = path16.basename(abs);
|
|
3140
|
+
return parts.includes("srvkit") || SERVER_SUFFIXES.some((suffix) => base.endsWith(suffix)) || base === "main.ts" || base === "server.ts";
|
|
3141
|
+
}
|
|
3142
|
+
#isClientBiased(abs, parts) {
|
|
3143
|
+
const base = path16.basename(abs);
|
|
3144
|
+
return parts.includes("ui") || parts.includes("webkit") || parts.includes("page") || CLIENT_SUFFIXES.some((suffix) => base.endsWith(suffix));
|
|
3426
3145
|
}
|
|
3427
|
-
#
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
return;
|
|
3431
|
-
this.#drain();
|
|
3146
|
+
#isSharedBiased(abs, parts) {
|
|
3147
|
+
const base = path16.basename(abs);
|
|
3148
|
+
return parts.includes("common") || SHARED_SUFFIXES.some((suffix) => base.endsWith(suffix)) || RUNTIME_METADATA_BASENAMES.has(base);
|
|
3432
3149
|
}
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3150
|
+
#isRuntimeMetadataFile(parts, base) {
|
|
3151
|
+
const parent = parts.at(-2);
|
|
3152
|
+
if (parent === "lib" && RUNTIME_METADATA_BASENAMES.has(base))
|
|
3153
|
+
return true;
|
|
3154
|
+
const libIndex = parts.lastIndexOf("lib");
|
|
3155
|
+
if (libIndex < 0 || parts.length <= libIndex + 1)
|
|
3156
|
+
return false;
|
|
3157
|
+
return base.endsWith(".dictionary.ts") || base.endsWith(".signal.ts");
|
|
3158
|
+
}
|
|
3159
|
+
#isBarrelFacetChild(parts) {
|
|
3160
|
+
if (parts.length < 4)
|
|
3161
|
+
return false;
|
|
3162
|
+
const [scope, , facet, child] = parts;
|
|
3163
|
+
if (scope !== "apps" && scope !== "libs")
|
|
3164
|
+
return false;
|
|
3165
|
+
if (!facet || !BARREL_FACETS.has(facet))
|
|
3166
|
+
return false;
|
|
3167
|
+
if (!child || child.startsWith(".") || child === "index.ts" || child === "index.tsx")
|
|
3168
|
+
return false;
|
|
3169
|
+
return true;
|
|
3170
|
+
}
|
|
3171
|
+
#isWorkspaceSource(rel) {
|
|
3172
|
+
if (!rel || rel.startsWith("..") || path16.isAbsolute(rel))
|
|
3173
|
+
return false;
|
|
3174
|
+
const [scope] = rel.split(path16.sep);
|
|
3175
|
+
return scope === "apps" || scope === "libs" || scope === "pkgs";
|
|
3451
3176
|
}
|
|
3452
3177
|
}
|
|
3178
|
+
var uniqueResolved = (files) => [...new Set(files.map((file) => path16.resolve(file)))].sort();
|
|
3453
3179
|
|
|
3454
|
-
// pkgs/@akanjs/devkit/frontendBuild/
|
|
3455
|
-
import
|
|
3180
|
+
// pkgs/@akanjs/devkit/frontendBuild/devGeneratedIndexSync.ts
|
|
3181
|
+
import { mkdir as mkdir4, readdir as readdir2, readFile as readFile2, rm as rm2, stat as stat2, writeFile as writeFile2 } from "fs/promises";
|
|
3182
|
+
import path17 from "path";
|
|
3183
|
+
var BARREL_FACETS2 = new Set(["common", "srvkit", "ui", "webkit", "plugin"]);
|
|
3184
|
+
var FACET_SOURCE_FILE_RE = /\.(ts|tsx)$/;
|
|
3185
|
+
var FACET_EXCLUDED_FILE_RE = /(^index\.tsx?$|\.d\.ts$|\.(test|spec)\.(ts|tsx)$|\.css$|\.scss$|\.sass$)/;
|
|
3186
|
+
var FACET_PASCAL_CASE_RE = /^[A-Z][A-Za-z0-9]*$/;
|
|
3187
|
+
var FACET_CAMEL_CASE_RE = /^[a-z][A-Za-z0-9]*$/;
|
|
3188
|
+
var MODULE_UI_TYPES = ["Template", "Unit", "Util", "View", "Zone"];
|
|
3189
|
+
var SERVICE_UI_TYPES = ["Util", "Zone"];
|
|
3190
|
+
var SCALAR_UI_TYPES = ["Template", "Unit"];
|
|
3456
3191
|
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
"react-server-dom-webpack",
|
|
3476
|
-
"react-server-dom-webpack/server.node",
|
|
3477
|
-
"react-server-dom-webpack/client.node",
|
|
3478
|
-
"react-server-dom-webpack/client.browser"
|
|
3479
|
-
]);
|
|
3480
|
-
var RUNTIME_EXTERNAL_PREFIX = ["react-dom/", "react-server-dom-webpack/"];
|
|
3481
|
-
var CANDIDATE_EXTS3 = [".tsx", ".ts", ".jsx", ".js", ".mjs", ".cjs"];
|
|
3482
|
-
async function createExternalizeFrameworkPlugin(options) {
|
|
3483
|
-
const tsconfig = await options.app.getTsConfig();
|
|
3484
|
-
const includePrefixes = options.include ?? DEFAULT_INCLUDE;
|
|
3485
|
-
const extraExact = new Set(options.extra ?? []);
|
|
3486
|
-
const workspaceRoot = options.app.workspace.workspaceRoot;
|
|
3487
|
-
const tsconfigPaths = tsconfig.compilerOptions.paths ?? {};
|
|
3488
|
-
const rootEntries = Object.entries(tsconfigPaths).filter(([k]) => !k.endsWith("/*")).map(([k, v]) => ({ pkg: k, entryFile: v[0] ?? null })).filter((e) => e.entryFile !== null);
|
|
3489
|
-
const wildcardEntries = Object.entries(tsconfigPaths).filter(([k]) => k.endsWith("/*")).map(([k, v]) => ({ prefix: k.slice(0, -1), replacements: v })).sort((a, b) => b.prefix.length - a.prefix.length);
|
|
3490
|
-
async function resolveWorkspaceSubpath(spec) {
|
|
3491
|
-
if (!workspaceRoot)
|
|
3492
|
-
return null;
|
|
3493
|
-
for (const { prefix, replacements } of wildcardEntries) {
|
|
3494
|
-
if (!spec.startsWith(prefix))
|
|
3495
|
-
continue;
|
|
3496
|
-
const suffix = spec.slice(prefix.length);
|
|
3497
|
-
for (const repl of replacements) {
|
|
3498
|
-
const replPath = repl?.endsWith("/*") ? repl.slice(0, -1) : repl ?? "";
|
|
3499
|
-
if (!replPath)
|
|
3500
|
-
continue;
|
|
3501
|
-
const candidate = path19.resolve(workspaceRoot, replPath + suffix);
|
|
3502
|
-
const hit = await firstExisting(candidate);
|
|
3503
|
-
if (hit)
|
|
3504
|
-
return hit;
|
|
3505
|
-
}
|
|
3192
|
+
class DevGeneratedIndexSync {
|
|
3193
|
+
#workspaceRoot;
|
|
3194
|
+
constructor({ workspaceRoot }) {
|
|
3195
|
+
this.#workspaceRoot = path17.resolve(workspaceRoot);
|
|
3196
|
+
}
|
|
3197
|
+
async syncForBatch(files) {
|
|
3198
|
+
const indexPaths = new Set;
|
|
3199
|
+
const errors = [];
|
|
3200
|
+
for (const file of files) {
|
|
3201
|
+
const facetIndex = this.#facetIndexFor(file);
|
|
3202
|
+
if (facetIndex)
|
|
3203
|
+
indexPaths.add(facetIndex);
|
|
3204
|
+
const moduleIndex = await this.#moduleIndexForDirectoryEvent(file).catch((err) => {
|
|
3205
|
+
errors.push(`[generated-index] module detection failed for ${file}: ${formatError2(err)}`);
|
|
3206
|
+
return null;
|
|
3207
|
+
});
|
|
3208
|
+
if (moduleIndex)
|
|
3209
|
+
indexPaths.add(moduleIndex);
|
|
3506
3210
|
}
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
if (hit)
|
|
3517
|
-
return hit;
|
|
3211
|
+
const changedFiles = [];
|
|
3212
|
+
for (const indexPath of [...indexPaths].sort()) {
|
|
3213
|
+
try {
|
|
3214
|
+
const changed = await this.#syncIndex(indexPath);
|
|
3215
|
+
if (changed)
|
|
3216
|
+
changedFiles.push(indexPath);
|
|
3217
|
+
} catch (err) {
|
|
3218
|
+
errors.push(`[generated-index] sync failed for ${indexPath}: ${formatError2(err)}`);
|
|
3219
|
+
}
|
|
3518
3220
|
}
|
|
3519
|
-
return
|
|
3221
|
+
return { changedFiles, errors };
|
|
3520
3222
|
}
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
|
|
3223
|
+
#facetIndexFor(file) {
|
|
3224
|
+
const abs = path17.resolve(file);
|
|
3225
|
+
const rel = path17.relative(this.#workspaceRoot, abs);
|
|
3226
|
+
if (rel.startsWith("..") || path17.isAbsolute(rel))
|
|
3227
|
+
return null;
|
|
3228
|
+
const parts = rel.split(path17.sep).filter(Boolean);
|
|
3229
|
+
if (parts.length < 4)
|
|
3230
|
+
return null;
|
|
3231
|
+
const [scope, project, facet, child] = parts;
|
|
3232
|
+
if (scope !== "apps" && scope !== "libs" || !project || !facet || !BARREL_FACETS2.has(facet))
|
|
3233
|
+
return null;
|
|
3234
|
+
if (!child || child.startsWith(".") || child === "index.ts" || child === "index.tsx")
|
|
3235
|
+
return null;
|
|
3236
|
+
return path17.join(this.#workspaceRoot, scope, project, facet, "index.ts");
|
|
3237
|
+
}
|
|
3238
|
+
async#moduleIndexForDirectoryEvent(file) {
|
|
3239
|
+
const abs = path17.resolve(file);
|
|
3240
|
+
const rel = path17.relative(this.#workspaceRoot, abs);
|
|
3241
|
+
if (rel.startsWith("..") || path17.isAbsolute(rel))
|
|
3242
|
+
return null;
|
|
3243
|
+
const parts = rel.split(path17.sep).filter(Boolean);
|
|
3244
|
+
if (parts.length !== 4 && parts.length !== 5)
|
|
3245
|
+
return null;
|
|
3246
|
+
const [scope, project, libSegment, moduleSegment, scalarSegment] = parts;
|
|
3247
|
+
if (scope !== "apps" && scope !== "libs" || !project || libSegment !== "lib")
|
|
3248
|
+
return null;
|
|
3249
|
+
const isExistingDirectory = await stat2(abs).then((s) => s.isDirectory()).catch(() => false);
|
|
3250
|
+
const looksLikeDeletedDirectory = !path17.extname(abs);
|
|
3251
|
+
if (!isExistingDirectory && !looksLikeDeletedDirectory)
|
|
3252
|
+
return null;
|
|
3253
|
+
if (moduleSegment === "__scalar" && scalarSegment) {
|
|
3254
|
+
return path17.join(this.#workspaceRoot, scope, project, "lib", "__scalar", scalarSegment, "index.ts");
|
|
3552
3255
|
}
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
if (await Bun.file(basePath).exists())
|
|
3557
|
-
return basePath;
|
|
3558
|
-
for (const ext of CANDIDATE_EXTS3) {
|
|
3559
|
-
const candidate = `${basePath}${ext}`;
|
|
3560
|
-
if (await Bun.file(candidate).exists())
|
|
3561
|
-
return candidate;
|
|
3256
|
+
if (!moduleSegment || moduleSegment === "__scalar")
|
|
3257
|
+
return null;
|
|
3258
|
+
return path17.join(this.#workspaceRoot, scope, project, "lib", moduleSegment, "index.ts");
|
|
3562
3259
|
}
|
|
3563
|
-
|
|
3564
|
-
const
|
|
3565
|
-
|
|
3566
|
-
|
|
3260
|
+
async#syncIndex(indexPath) {
|
|
3261
|
+
const dir = path17.dirname(indexPath);
|
|
3262
|
+
const content = await this.#contentForIndex(indexPath);
|
|
3263
|
+
if (content === null) {
|
|
3264
|
+
if (!await exists(indexPath))
|
|
3265
|
+
return false;
|
|
3266
|
+
await rm2(indexPath, { force: true });
|
|
3267
|
+
return true;
|
|
3268
|
+
}
|
|
3269
|
+
const current = await readFile2(indexPath, "utf8").catch(() => null);
|
|
3270
|
+
if (current === content)
|
|
3271
|
+
return false;
|
|
3272
|
+
await mkdir4(dir, { recursive: true });
|
|
3273
|
+
await writeFile2(indexPath, content);
|
|
3274
|
+
return true;
|
|
3567
3275
|
}
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
|
|
3276
|
+
async#contentForIndex(indexPath) {
|
|
3277
|
+
const parts = path17.relative(this.#workspaceRoot, indexPath).split(path17.sep).filter(Boolean);
|
|
3278
|
+
const facet = parts.at(-2);
|
|
3279
|
+
if (facet && BARREL_FACETS2.has(facet))
|
|
3280
|
+
return this.#facetContent(path17.dirname(indexPath));
|
|
3281
|
+
return this.#moduleContent(path17.dirname(indexPath));
|
|
3282
|
+
}
|
|
3283
|
+
async#facetContent(dir) {
|
|
3284
|
+
const nameCasePattern = path17.basename(dir) === "ui" ? FACET_PASCAL_CASE_RE : FACET_CAMEL_CASE_RE;
|
|
3285
|
+
const entries = await readdir2(dir, { withFileTypes: true }).catch(() => []);
|
|
3286
|
+
const exportNames = entries.flatMap((entry) => {
|
|
3287
|
+
const name = entry.name;
|
|
3288
|
+
if (name.startsWith("."))
|
|
3289
|
+
return [];
|
|
3290
|
+
if (entry.isDirectory())
|
|
3291
|
+
return nameCasePattern.test(name) ? [name] : [];
|
|
3292
|
+
if (!entry.isFile())
|
|
3293
|
+
return [];
|
|
3294
|
+
if (!FACET_SOURCE_FILE_RE.test(name) || FACET_EXCLUDED_FILE_RE.test(name))
|
|
3295
|
+
return [];
|
|
3296
|
+
const exportName = name.replace(FACET_SOURCE_FILE_RE, "");
|
|
3297
|
+
return nameCasePattern.test(exportName) ? [exportName] : [];
|
|
3298
|
+
}).sort();
|
|
3299
|
+
if (exportNames.length === 0)
|
|
3300
|
+
return null;
|
|
3301
|
+
return `${exportNames.map((name) => `export * from "./${name}";`).join(`
|
|
3302
|
+
`)}
|
|
3303
|
+
`;
|
|
3304
|
+
}
|
|
3305
|
+
async#moduleContent(dir) {
|
|
3306
|
+
const rel = path17.relative(this.#workspaceRoot, dir);
|
|
3307
|
+
const parts = rel.split(path17.sep).filter(Boolean);
|
|
3308
|
+
const moduleSegment = parts.at(-1);
|
|
3309
|
+
if (!moduleSegment)
|
|
3310
|
+
return null;
|
|
3311
|
+
const isScalar = parts.at(-2) === "__scalar";
|
|
3312
|
+
const rawModel = moduleSegment.startsWith("_") ? moduleSegment.slice(1) : moduleSegment;
|
|
3313
|
+
if (!rawModel)
|
|
3314
|
+
return null;
|
|
3315
|
+
const modelName = capitalize(rawModel);
|
|
3316
|
+
const allowedTypes = isScalar ? SCALAR_UI_TYPES : moduleSegment.startsWith("_") ? SERVICE_UI_TYPES : MODULE_UI_TYPES;
|
|
3317
|
+
const fileTypes = [];
|
|
3318
|
+
for (const type of allowedTypes) {
|
|
3319
|
+
if (await exists(path17.join(dir, `${modelName}.${type}.tsx`)))
|
|
3320
|
+
fileTypes.push(type);
|
|
3593
3321
|
}
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
return "ts";
|
|
3603
|
-
return "js";
|
|
3322
|
+
if (fileTypes.length === 0)
|
|
3323
|
+
return null;
|
|
3324
|
+
return `
|
|
3325
|
+
${fileTypes.map((type) => `import * as ${type} from "./${modelName}.${type}";`).join(`
|
|
3326
|
+
`)}
|
|
3327
|
+
|
|
3328
|
+
export const ${modelName} = { ${fileTypes.join(", ")} };`;
|
|
3329
|
+
}
|
|
3604
3330
|
}
|
|
3331
|
+
var exists = async (file) => stat2(file).then(() => true).catch(() => false);
|
|
3332
|
+
var capitalize = (value) => `${value.charAt(0).toUpperCase()}${value.slice(1)}`;
|
|
3333
|
+
var formatError2 = (err) => err instanceof Error ? err.message : String(err);
|
|
3605
3334
|
|
|
3606
|
-
// pkgs/@akanjs/devkit/frontendBuild/
|
|
3607
|
-
|
|
3335
|
+
// pkgs/@akanjs/devkit/frontendBuild/fontOptimizer.ts
|
|
3336
|
+
import { mkdir as mkdir5 } from "fs/promises";
|
|
3337
|
+
import path18 from "path";
|
|
3338
|
+
import {
|
|
3339
|
+
generateFontFace,
|
|
3340
|
+
getMetricsForFamily,
|
|
3341
|
+
readMetrics,
|
|
3342
|
+
resolveCategoryFallbacks
|
|
3343
|
+
} from "fontaine";
|
|
3344
|
+
import { createFont, woff2 } from "fonteditor-core";
|
|
3345
|
+
import subsetFont from "subset-font";
|
|
3346
|
+
import ts4 from "typescript";
|
|
3347
|
+
var FONT_URL_PREFIX = "/_akan/fonts";
|
|
3348
|
+
var DEFAULT_FONT_SUBSETS = ["latin"];
|
|
3608
3349
|
|
|
3609
|
-
class
|
|
3350
|
+
class FontOptimizer {
|
|
3610
3351
|
#app;
|
|
3611
3352
|
#command;
|
|
3612
|
-
#
|
|
3613
|
-
#
|
|
3614
|
-
|
|
3353
|
+
#artifactRoot;
|
|
3354
|
+
#files = [];
|
|
3355
|
+
#cssParts = [];
|
|
3356
|
+
#woff2Ready = null;
|
|
3357
|
+
static #ksX1001Text = null;
|
|
3358
|
+
constructor(app, command = "start") {
|
|
3615
3359
|
this.#app = app;
|
|
3616
3360
|
this.#command = command;
|
|
3617
|
-
this.#
|
|
3361
|
+
this.#artifactRoot = path18.join(command === "build" ? app.dist.cwdPath : app.cwdPath, ".akan/artifact");
|
|
3618
3362
|
}
|
|
3619
|
-
async
|
|
3620
|
-
const
|
|
3621
|
-
const
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
naming: {
|
|
3632
|
-
entry: "pages-[hash].[ext]",
|
|
3633
|
-
chunk: "chunks/[name]-[hash].[ext]",
|
|
3634
|
-
asset: "assets/[name]-[hash].[ext]"
|
|
3635
|
-
},
|
|
3636
|
-
define: this.#define(),
|
|
3637
|
-
plugins: [
|
|
3638
|
-
PagesBundleBuilder.createPagesEntryPlugin(entrySource),
|
|
3639
|
-
PagesBundleBuilder.createServerCssStubPlugin(),
|
|
3640
|
-
PagesBundleBuilder.createServerUseClientFetchPlugin(),
|
|
3641
|
-
await createExternalizeFrameworkPlugin({ app: this.#app, extra: akanConfig.externalLibs }),
|
|
3642
|
-
akanConfig.barrelImports.length > 0 ? await createBarrelImportsPlugin(this.#app, {
|
|
3643
|
-
pipeAfter: (source, args) => transformUseClient(source, {
|
|
3644
|
-
path: args.path,
|
|
3645
|
-
workspaceRoot
|
|
3646
|
-
})
|
|
3647
|
-
}) : createUseClientBundlePlugin({ workspaceRoot })
|
|
3648
|
-
]
|
|
3649
|
-
});
|
|
3650
|
-
if (!result.success)
|
|
3651
|
-
throw new AggregateError(result.logs, "[PagesBundleBuilder] Bun.build failed");
|
|
3652
|
-
const entryArtifact = result.outputs.find((a) => a.kind === "entry-point");
|
|
3653
|
-
if (!entryArtifact)
|
|
3654
|
-
throw new Error("[PagesBundleBuilder] Bun.build emitted no entry-point artifact");
|
|
3655
|
-
const bundlePath = path20.resolve(entryArtifact.path);
|
|
3656
|
-
const buildId = Date.now();
|
|
3657
|
-
const outputBytes = result.outputs.reduce((sum, output) => sum + output.size, 0);
|
|
3658
|
-
const chunkCount = result.outputs.filter((output) => output.kind === "chunk").length;
|
|
3659
|
-
this.#app.verbose(`[PagesBundleBuilder] ${path20.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`);
|
|
3660
|
-
return {
|
|
3661
|
-
bundlePath,
|
|
3662
|
-
buildId,
|
|
3663
|
-
splitting: this.#splitting,
|
|
3664
|
-
entryBytes: entryArtifact.size,
|
|
3665
|
-
outputBytes,
|
|
3666
|
-
outputCount: result.outputs.length,
|
|
3667
|
-
chunkCount
|
|
3668
|
-
};
|
|
3363
|
+
async optimize() {
|
|
3364
|
+
const fonts = await this.discoverFonts();
|
|
3365
|
+
for (const font of fonts) {
|
|
3366
|
+
if (!this.#isFontOptimizationEnabled(font))
|
|
3367
|
+
continue;
|
|
3368
|
+
await this.#optimizeFont(font);
|
|
3369
|
+
}
|
|
3370
|
+
const fontUtilityCss = this.#buildFontUtilityRules(fonts);
|
|
3371
|
+
if (fontUtilityCss)
|
|
3372
|
+
this.#cssParts.push(fontUtilityCss);
|
|
3373
|
+
return { css: this.#cssParts.join(`
|
|
3374
|
+
`), fonts, files: this.#files };
|
|
3669
3375
|
}
|
|
3670
|
-
|
|
3671
|
-
|
|
3376
|
+
async discoverFonts() {
|
|
3377
|
+
const pageKeys = await this.#app.getPageKeys();
|
|
3378
|
+
const fonts = [];
|
|
3379
|
+
await Promise.all(pageKeys.map(async (key) => {
|
|
3380
|
+
const filePath = path18.resolve(this.#app.cwdPath, "page", key);
|
|
3381
|
+
const file = Bun.file(filePath);
|
|
3382
|
+
if (!await file.exists())
|
|
3383
|
+
return;
|
|
3384
|
+
fonts.push(...this.#extractFontsExport(await file.text(), filePath));
|
|
3385
|
+
}));
|
|
3386
|
+
return this.#dedupeFonts(fonts);
|
|
3672
3387
|
}
|
|
3673
|
-
|
|
3674
|
-
|
|
3388
|
+
async#optimizeFont(font) {
|
|
3389
|
+
const faceCss = [];
|
|
3390
|
+
for (const face of this.#getFontFaces(font)) {
|
|
3391
|
+
const sourcePath = await this.#resolveFontSourcePath(face.src);
|
|
3392
|
+
if (!sourcePath) {
|
|
3393
|
+
this.#app.logger.warn(`[font] source not found: ${face.src}`);
|
|
3394
|
+
continue;
|
|
3395
|
+
}
|
|
3396
|
+
const outputPath = path18.join(this.#artifactRoot, face.optimizedSrc.replace(/^\/_akan\//, ""));
|
|
3397
|
+
await mkdir5(path18.dirname(outputPath), { recursive: true });
|
|
3398
|
+
const sourceBuffer = Buffer.from(await Bun.file(sourcePath).arrayBuffer());
|
|
3399
|
+
const outputBuffer = font.subset === false ? await this.#convertToWoff2(sourceBuffer, sourcePath) : await subsetFont(sourceBuffer, await this.#getSubsetText(font), { targetFormat: "woff2" });
|
|
3400
|
+
await Bun.write(outputPath, outputBuffer);
|
|
3401
|
+
this.#files.push(outputPath);
|
|
3402
|
+
faceCss.push(this.#buildOptimizedFontFaceRule(font, face));
|
|
3403
|
+
const fallbackCss = await this.#buildFontaineFallbackCss(font, face, outputPath);
|
|
3404
|
+
if (fallbackCss)
|
|
3405
|
+
faceCss.push(fallbackCss);
|
|
3406
|
+
}
|
|
3407
|
+
if (faceCss.length > 0)
|
|
3408
|
+
this.#cssParts.push(...faceCss, this.#buildRootVariableRule(font));
|
|
3675
3409
|
}
|
|
3676
|
-
#
|
|
3677
|
-
const
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3410
|
+
#extractFontsExport(source, filePath) {
|
|
3411
|
+
const sourceFile = ts4.createSourceFile(filePath, source, ts4.ScriptTarget.Latest, true, ts4.ScriptKind.TSX);
|
|
3412
|
+
const fonts = [];
|
|
3413
|
+
for (const statement of sourceFile.statements) {
|
|
3414
|
+
if (!ts4.isVariableStatement(statement))
|
|
3415
|
+
continue;
|
|
3416
|
+
const modifiers = ts4.canHaveModifiers(statement) ? ts4.getModifiers(statement) : undefined;
|
|
3417
|
+
const isExported = modifiers?.some((modifier) => modifier.kind === ts4.SyntaxKind.ExportKeyword) ?? false;
|
|
3418
|
+
if (!isExported)
|
|
3419
|
+
continue;
|
|
3420
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
3421
|
+
if (!ts4.isIdentifier(declaration.name) || declaration.name.text !== "fonts")
|
|
3422
|
+
continue;
|
|
3423
|
+
const value = declaration.initializer ? this.#literalToValue(declaration.initializer) : null;
|
|
3424
|
+
if (Array.isArray(value)) {
|
|
3425
|
+
fonts.push(...value.map((font) => this.#withFontDefaults(font)));
|
|
3426
|
+
}
|
|
3427
|
+
}
|
|
3428
|
+
}
|
|
3429
|
+
return fonts;
|
|
3683
3430
|
}
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3431
|
+
#literalToValue(node) {
|
|
3432
|
+
if (ts4.isStringLiteralLike(node))
|
|
3433
|
+
return node.text;
|
|
3434
|
+
if (ts4.isNumericLiteral(node))
|
|
3435
|
+
return Number(node.text);
|
|
3436
|
+
if (node.kind === ts4.SyntaxKind.TrueKeyword)
|
|
3437
|
+
return true;
|
|
3438
|
+
if (node.kind === ts4.SyntaxKind.FalseKeyword)
|
|
3439
|
+
return false;
|
|
3440
|
+
if (ts4.isArrayLiteralExpression(node))
|
|
3441
|
+
return node.elements.map((element) => this.#literalToValue(element));
|
|
3442
|
+
if (ts4.isObjectLiteralExpression(node)) {
|
|
3443
|
+
const obj = {};
|
|
3444
|
+
for (const prop of node.properties) {
|
|
3445
|
+
if (!ts4.isPropertyAssignment(prop))
|
|
3446
|
+
continue;
|
|
3447
|
+
const name = this.#getPropertyName(prop.name);
|
|
3448
|
+
if (!name)
|
|
3449
|
+
continue;
|
|
3450
|
+
obj[name] = this.#literalToValue(prop.initializer);
|
|
3696
3451
|
}
|
|
3697
|
-
|
|
3452
|
+
return obj;
|
|
3453
|
+
}
|
|
3454
|
+
if (ts4.isAsExpression(node) || ts4.isSatisfiesExpression(node) || ts4.isParenthesizedExpression(node)) {
|
|
3455
|
+
return this.#literalToValue(node.expression);
|
|
3456
|
+
}
|
|
3457
|
+
return;
|
|
3458
|
+
}
|
|
3459
|
+
#getPropertyName(name) {
|
|
3460
|
+
if (ts4.isIdentifier(name) || ts4.isStringLiteral(name) || ts4.isNumericLiteral(name))
|
|
3461
|
+
return name.text;
|
|
3462
|
+
return null;
|
|
3463
|
+
}
|
|
3464
|
+
#dedupeFonts(fonts) {
|
|
3465
|
+
const map = new Map;
|
|
3466
|
+
for (const font of fonts)
|
|
3467
|
+
map.set(JSON.stringify(font), font);
|
|
3468
|
+
return [...map.values()];
|
|
3469
|
+
}
|
|
3470
|
+
#withFontDefaults(font) {
|
|
3471
|
+
return { ...font, subsets: font.subsets ?? [...DEFAULT_FONT_SUBSETS] };
|
|
3698
3472
|
}
|
|
3699
|
-
|
|
3700
|
-
return
|
|
3701
|
-
name: "akan-server-css-stub",
|
|
3702
|
-
setup(build) {
|
|
3703
|
-
build.onLoad({ filter: /\.css$/ }, () => ({
|
|
3704
|
-
contents: "",
|
|
3705
|
-
loader: "js"
|
|
3706
|
-
}));
|
|
3707
|
-
}
|
|
3708
|
-
};
|
|
3473
|
+
#getFontSubsets(font) {
|
|
3474
|
+
return font.subsets ?? DEFAULT_FONT_SUBSETS;
|
|
3709
3475
|
}
|
|
3710
|
-
|
|
3711
|
-
return {
|
|
3712
|
-
name: "akan-server-use-client-fetch",
|
|
3713
|
-
setup(build) {
|
|
3714
|
-
build.onLoad({ filter: /[/\\]lib[/\\]useClient\.(ts|tsx|js|jsx)$/ }, async (args) => {
|
|
3715
|
-
const source = await Bun.file(args.path).text();
|
|
3716
|
-
const transformed = PagesBundleBuilder.transformServerUseClientFetchSource(source);
|
|
3717
|
-
if (transformed === source)
|
|
3718
|
-
return;
|
|
3719
|
-
return { contents: transformed, loader: loaderFor4(args.path) };
|
|
3720
|
-
});
|
|
3721
|
-
}
|
|
3722
|
-
};
|
|
3476
|
+
#getFontVariableName(font) {
|
|
3477
|
+
return font.variable ?? `--font-${font.name}`;
|
|
3723
3478
|
}
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
return source;
|
|
3727
|
-
return source.replace(/import\s+\{\s*getSerializedSignal\s*\}\s+from\s+["']\.\/sig["']\s+with\s+\{\s*type\s*:\s*["']macro["']\s*\};/, `import { fetch as serverFetch } from "./sig";`).replace(/const\s+fetchProto\s*=\s*FetchClient\.build<[^;]+;/, "const fetchProto = FetchClient.build<typeof signal>(cnst, serverFetch.serializedSignal, { Err: pageProto.Err, base: serverFetch });");
|
|
3479
|
+
#getFontFallbackName(font) {
|
|
3480
|
+
return font.fallbackName ?? `${font.name} fallback`;
|
|
3728
3481
|
}
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
if (absPath.endsWith(".tsx"))
|
|
3732
|
-
return "tsx";
|
|
3733
|
-
if (absPath.endsWith(".jsx"))
|
|
3734
|
-
return "jsx";
|
|
3735
|
-
if (absPath.endsWith(".ts"))
|
|
3736
|
-
return "ts";
|
|
3737
|
-
return "js";
|
|
3738
|
-
}
|
|
3739
|
-
|
|
3740
|
-
// pkgs/@akanjs/devkit/frontendBuild/precompressArtifacts.ts
|
|
3741
|
-
import fs4 from "fs";
|
|
3742
|
-
import path21 from "path";
|
|
3743
|
-
var COMPRESSIBLE_EXTS = new Set([".css", ".html", ".js", ".json", ".svg"]);
|
|
3744
|
-
var MIN_COMPRESS_BYTES = 1024;
|
|
3745
|
-
async function precompressArtifacts(app) {
|
|
3746
|
-
const roots = [path21.join(app.dist.cwdPath, ".akan/artifact/client")];
|
|
3747
|
-
const result = { files: 0, inputBytes: 0, outputBytes: 0 };
|
|
3748
|
-
await Promise.all(roots.map((root) => precompressRoot(root, result)));
|
|
3749
|
-
if (result.files > 0) {
|
|
3750
|
-
app.verbose(`[precompress] wrote ${result.files} gzip sidecars (${formatBytes(result.inputBytes)} -> ${formatBytes(result.outputBytes)})`);
|
|
3482
|
+
#isFontOptimizationEnabled(font) {
|
|
3483
|
+
return font.optimize !== false;
|
|
3751
3484
|
}
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
async function precompressRoot(root, result) {
|
|
3755
|
-
if (!fs4.existsSync(root) || !fs4.statSync(root).isDirectory())
|
|
3756
|
-
return;
|
|
3757
|
-
const glob = new Bun.Glob("**/*");
|
|
3758
|
-
for await (const filePath of glob.scan({ cwd: root, absolute: true })) {
|
|
3759
|
-
if (!await shouldPrecompress(filePath))
|
|
3760
|
-
continue;
|
|
3761
|
-
const bytes = await Bun.file(filePath).bytes();
|
|
3762
|
-
const gz = Bun.gzipSync(toArrayBuffer(bytes));
|
|
3763
|
-
await Bun.write(`${filePath}.gz`, gz);
|
|
3764
|
-
result.files += 1;
|
|
3765
|
-
result.inputBytes += bytes.byteLength;
|
|
3766
|
-
result.outputBytes += gz.byteLength;
|
|
3485
|
+
#getFontStyles(font) {
|
|
3486
|
+
return font.styles?.length ? font.styles : ["normal"];
|
|
3767
3487
|
}
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
}
|
|
3782
|
-
function formatBytes(bytes) {
|
|
3783
|
-
if (bytes < 1024)
|
|
3784
|
-
return `${bytes}B`;
|
|
3785
|
-
if (bytes < 1024 * 1024)
|
|
3786
|
-
return `${(bytes / 1024).toFixed(1)}KB`;
|
|
3787
|
-
return `${(bytes / 1024 / 1024).toFixed(1)}MB`;
|
|
3788
|
-
}
|
|
3789
|
-
|
|
3790
|
-
// pkgs/@akanjs/devkit/frontendBuild/ssrBaseArtifactBuilder.ts
|
|
3791
|
-
import path24 from "path";
|
|
3792
|
-
import { optimize } from "@tailwindcss/node";
|
|
3793
|
-
|
|
3794
|
-
// pkgs/@akanjs/devkit/frontendBuild/cssCompiler.ts
|
|
3795
|
-
import path23 from "path";
|
|
3796
|
-
import { Logger as Logger2 } from "akanjs/common";
|
|
3797
|
-
import { compile } from "tailwindcss";
|
|
3798
|
-
|
|
3799
|
-
// pkgs/@akanjs/devkit/frontendBuild/cssImportResolver.ts
|
|
3800
|
-
import path22 from "path";
|
|
3801
|
-
var CSS_IMPORT_EXTS = ["", ".css", "/styles.css", "/index.css"];
|
|
3802
|
-
|
|
3803
|
-
class CssImportResolver {
|
|
3804
|
-
#workspaceRoot;
|
|
3805
|
-
#paths;
|
|
3806
|
-
#wildcardEntries;
|
|
3807
|
-
constructor(workspaceRoot, paths = {}) {
|
|
3808
|
-
this.#workspaceRoot = workspaceRoot;
|
|
3809
|
-
this.#paths = paths;
|
|
3810
|
-
this.#wildcardEntries = Object.entries(paths).filter(([key]) => key.endsWith("/*")).map(([key, replacements]) => ({ prefix: key.slice(0, -1), replacements })).sort((a, b) => b.prefix.length - a.prefix.length);
|
|
3488
|
+
#getFontFaces(font) {
|
|
3489
|
+
const enabledStyles = new Set(this.#getFontStyles(font));
|
|
3490
|
+
return font.paths.map((fontPath) => {
|
|
3491
|
+
const style = fontPath.style ?? "normal";
|
|
3492
|
+
return {
|
|
3493
|
+
font,
|
|
3494
|
+
path: fontPath,
|
|
3495
|
+
src: fontPath.src,
|
|
3496
|
+
weight: fontPath.weight,
|
|
3497
|
+
style,
|
|
3498
|
+
optimizedSrc: this.#getOptimizedFontSrc(font, fontPath)
|
|
3499
|
+
};
|
|
3500
|
+
}).filter((face) => enabledStyles.has(face.style));
|
|
3811
3501
|
}
|
|
3812
|
-
|
|
3813
|
-
const
|
|
3814
|
-
|
|
3502
|
+
#getOptimizedFontSrc(font, fontPath) {
|
|
3503
|
+
const style = fontPath.style ?? "normal";
|
|
3504
|
+
const hash = this.#hashFontConfig({
|
|
3505
|
+
name: font.name,
|
|
3506
|
+
src: fontPath.src,
|
|
3507
|
+
weight: fontPath.weight,
|
|
3508
|
+
style,
|
|
3509
|
+
display: font.display,
|
|
3510
|
+
subset: font.subset,
|
|
3511
|
+
subsets: this.#getFontSubsets(font),
|
|
3512
|
+
subsetText: font.subsetText,
|
|
3513
|
+
subsetFiles: font.subsetFiles,
|
|
3514
|
+
declarations: [...font.declarations ?? [], ...fontPath.declarations ?? []]
|
|
3515
|
+
});
|
|
3516
|
+
return `${FONT_URL_PREFIX}/${this.#slugFontPart(font.name)}-${this.#slugFontPart(String(fontPath.weight))}-${style}-${hash}.woff2`;
|
|
3815
3517
|
}
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
]) {
|
|
3823
|
-
const resolved = await resolve();
|
|
3824
|
-
if (resolved)
|
|
3825
|
-
return resolved;
|
|
3518
|
+
#hashFontConfig(value) {
|
|
3519
|
+
const input = this.#stableStringify(value);
|
|
3520
|
+
let hash = 2166136261;
|
|
3521
|
+
for (let i = 0;i < input.length; i++) {
|
|
3522
|
+
hash ^= input.charCodeAt(i);
|
|
3523
|
+
hash = Math.imul(hash, 16777619);
|
|
3826
3524
|
}
|
|
3827
|
-
return
|
|
3525
|
+
return (hash >>> 0).toString(36);
|
|
3828
3526
|
}
|
|
3829
|
-
#
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
} catch {}
|
|
3527
|
+
#stableStringify(value) {
|
|
3528
|
+
if (Array.isArray(value))
|
|
3529
|
+
return `[${value.map((item) => this.#stableStringify(item)).join(",")}]`;
|
|
3530
|
+
if (value && typeof value === "object") {
|
|
3531
|
+
const entries = Object.entries(value).filter(([, v]) => v !== undefined).sort(([a], [b]) => a.localeCompare(b));
|
|
3532
|
+
return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${this.#stableStringify(v)}`).join(",")}}`;
|
|
3836
3533
|
}
|
|
3837
|
-
return
|
|
3534
|
+
return JSON.stringify(value);
|
|
3838
3535
|
}
|
|
3839
|
-
#
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3536
|
+
#slugFontPart(value) {
|
|
3537
|
+
return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "font";
|
|
3538
|
+
}
|
|
3539
|
+
async#resolveFontSourcePath(src) {
|
|
3540
|
+
if (!src.startsWith("/"))
|
|
3541
|
+
return null;
|
|
3542
|
+
const rel = src.replace(/^\//, "");
|
|
3543
|
+
const candidates = [
|
|
3544
|
+
this.#command === "build" ? path18.join(this.#app.dist.cwdPath, "public", rel) : null,
|
|
3545
|
+
path18.join(this.#app.cwdPath, "public", rel),
|
|
3546
|
+
this.#resolveWorkspacePublicPath(rel)
|
|
3547
|
+
].filter(Boolean);
|
|
3548
|
+
for (const candidate of candidates) {
|
|
3549
|
+
if (await Bun.file(candidate).exists())
|
|
3550
|
+
return candidate;
|
|
3846
3551
|
}
|
|
3847
3552
|
return null;
|
|
3848
3553
|
}
|
|
3849
|
-
async#
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
|
|
3554
|
+
async#convertToWoff2(buffer, sourcePath) {
|
|
3555
|
+
await this.#initWoff2();
|
|
3556
|
+
const font = createFont(buffer, { type: this.#getFontType(sourcePath, buffer) });
|
|
3557
|
+
return font.write({ type: "woff2", toBuffer: true });
|
|
3558
|
+
}
|
|
3559
|
+
async#initWoff2() {
|
|
3560
|
+
this.#woff2Ready ??= woff2.init().then(() => {
|
|
3561
|
+
return;
|
|
3562
|
+
});
|
|
3563
|
+
return this.#woff2Ready;
|
|
3564
|
+
}
|
|
3565
|
+
#getFontType(sourcePath, buffer) {
|
|
3566
|
+
const signature = buffer.toString("ascii", 0, 4);
|
|
3567
|
+
if (signature === "wOFF")
|
|
3568
|
+
return "woff";
|
|
3569
|
+
if (signature === "wOF2")
|
|
3570
|
+
return "woff2";
|
|
3571
|
+
if (signature === "OTTO")
|
|
3572
|
+
return "otf";
|
|
3573
|
+
const ext = path18.extname(sourcePath).slice(1).toLowerCase();
|
|
3574
|
+
if (ext === "otf" || ext === "woff" || ext === "woff2")
|
|
3575
|
+
return ext;
|
|
3576
|
+
return "ttf";
|
|
3577
|
+
}
|
|
3578
|
+
#resolveWorkspacePublicPath(rel) {
|
|
3579
|
+
const [root, dep, ...rest] = rel.split("/");
|
|
3580
|
+
if (root !== "libs" || !dep || rest.length === 0)
|
|
3581
|
+
return null;
|
|
3582
|
+
return path18.join(this.#app.workspace.workspaceRoot, "libs", dep, "public", ...rest);
|
|
3583
|
+
}
|
|
3584
|
+
async#getSubsetText(font) {
|
|
3585
|
+
const parts = new Set;
|
|
3586
|
+
const subsets = this.#getFontSubsets(font);
|
|
3587
|
+
for (const subset of subsets)
|
|
3588
|
+
parts.add(await this.#getSubsetPresetText(subset));
|
|
3589
|
+
if (font.subsetText)
|
|
3590
|
+
parts.add(font.subsetText);
|
|
3591
|
+
for (const filePath of font.subsetFiles ?? []) {
|
|
3592
|
+
const abs = path18.isAbsolute(filePath) ? filePath : path18.join(this.#app.cwdPath, filePath);
|
|
3593
|
+
const file = Bun.file(abs);
|
|
3594
|
+
if (await file.exists())
|
|
3595
|
+
parts.add(await file.text());
|
|
3857
3596
|
}
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
3597
|
+
return [...parts].join("");
|
|
3598
|
+
}
|
|
3599
|
+
async#getSubsetPresetText(subset) {
|
|
3600
|
+
if (subset === "latin")
|
|
3601
|
+
return this.#rangeText(32, 126);
|
|
3602
|
+
if (subset === "latin-ext")
|
|
3603
|
+
return `${this.#rangeText(32, 126)}${this.#rangeText(160, 591)}`;
|
|
3604
|
+
if (subset === "ks-x-1001")
|
|
3605
|
+
return FontOptimizer.#getKsX1001Text();
|
|
3606
|
+
if (subset === "auto")
|
|
3607
|
+
return this.#collectAutoSubsetText();
|
|
3608
|
+
return "";
|
|
3609
|
+
}
|
|
3610
|
+
async#collectAutoSubsetText() {
|
|
3611
|
+
const roots = ["page", "ui"].map((dir) => path18.join(this.#app.cwdPath, dir));
|
|
3612
|
+
const glob = new Bun.Glob("**/*.{ts,tsx,js,jsx,html,md}");
|
|
3613
|
+
const parts = [];
|
|
3614
|
+
await Promise.all(roots.map(async (root) => {
|
|
3615
|
+
if (!await Bun.file(root).exists())
|
|
3616
|
+
return;
|
|
3617
|
+
for await (const filePath of glob.scan({ cwd: root, absolute: true })) {
|
|
3618
|
+
parts.push(await Bun.file(filePath).text());
|
|
3867
3619
|
}
|
|
3868
|
-
}
|
|
3869
|
-
return
|
|
3620
|
+
}));
|
|
3621
|
+
return parts.join("");
|
|
3870
3622
|
}
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
try {
|
|
3877
|
-
const pkgPath = __require.resolve(`${pkgName}/package.json`, { paths: [base] });
|
|
3878
|
-
const resolved = await this.#resolvePackageStyleFromPackageJson(id, pkgName, pkgPath);
|
|
3879
|
-
if (resolved)
|
|
3880
|
-
return resolved;
|
|
3881
|
-
} catch {}
|
|
3882
|
-
}
|
|
3883
|
-
for (const pkgPath of this.#packageJsonCandidates(pkgName)) {
|
|
3884
|
-
const resolved = await this.#resolvePackageStyleFromPackageJson(id, pkgName, pkgPath);
|
|
3885
|
-
if (resolved)
|
|
3886
|
-
return resolved;
|
|
3887
|
-
}
|
|
3888
|
-
return null;
|
|
3623
|
+
#rangeText(start, end) {
|
|
3624
|
+
let text = "";
|
|
3625
|
+
for (let code = start;code <= end; code++)
|
|
3626
|
+
text += String.fromCodePoint(code);
|
|
3627
|
+
return text;
|
|
3889
3628
|
}
|
|
3890
|
-
|
|
3629
|
+
static #getKsX1001Text() {
|
|
3630
|
+
if (FontOptimizer.#ksX1001Text)
|
|
3631
|
+
return FontOptimizer.#ksX1001Text;
|
|
3891
3632
|
try {
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3633
|
+
const decoder = new TextDecoder("euc-kr");
|
|
3634
|
+
const chars = new Set;
|
|
3635
|
+
for (let lead = 161;lead <= 254; lead++) {
|
|
3636
|
+
for (let trail = 161;trail <= 254; trail++) {
|
|
3637
|
+
const char = decoder.decode(Uint8Array.of(lead, trail));
|
|
3638
|
+
if (char && char !== "\uFFFD")
|
|
3639
|
+
chars.add(char);
|
|
3640
|
+
}
|
|
3641
|
+
}
|
|
3642
|
+
FontOptimizer.#ksX1001Text = [...chars].join("");
|
|
3900
3643
|
} catch {
|
|
3901
|
-
|
|
3644
|
+
FontOptimizer.#ksX1001Text = FontOptimizer.#rangeTextStatic(44032, 55203);
|
|
3902
3645
|
}
|
|
3646
|
+
return FontOptimizer.#ksX1001Text;
|
|
3903
3647
|
}
|
|
3904
|
-
#
|
|
3905
|
-
|
|
3648
|
+
static #rangeTextStatic(start, end) {
|
|
3649
|
+
let text = "";
|
|
3650
|
+
for (let code = start;code <= end; code++)
|
|
3651
|
+
text += String.fromCodePoint(code);
|
|
3652
|
+
return text;
|
|
3906
3653
|
}
|
|
3907
|
-
#
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
|
|
3654
|
+
#buildOptimizedFontFaceRule(font, face) {
|
|
3655
|
+
const declarations = [
|
|
3656
|
+
["font-family", this.#quote(font.name)],
|
|
3657
|
+
["src", `url(${this.#quote(face.optimizedSrc)}) format("woff2")`],
|
|
3658
|
+
["font-weight", String(face.weight)],
|
|
3659
|
+
["font-style", face.style],
|
|
3660
|
+
["font-display", font.display ?? "swap"],
|
|
3661
|
+
...this.#toDeclarationEntries(font.declarations),
|
|
3662
|
+
...this.#toDeclarationEntries(face.path.declarations)
|
|
3913
3663
|
];
|
|
3664
|
+
return `@font-face {
|
|
3665
|
+
${declarations.map(([prop, value]) => ` ${prop}: ${value};`).join(`
|
|
3666
|
+
`)}
|
|
3667
|
+
}`;
|
|
3914
3668
|
}
|
|
3915
|
-
async#
|
|
3916
|
-
|
|
3917
|
-
|
|
3918
|
-
|
|
3919
|
-
|
|
3669
|
+
async#buildFontaineFallbackCss(font, face, outputPath) {
|
|
3670
|
+
if (font.adjustFontFallback === false)
|
|
3671
|
+
return "";
|
|
3672
|
+
const metrics = await readMetrics(outputPath).catch(() => null);
|
|
3673
|
+
if (!metrics)
|
|
3674
|
+
return "";
|
|
3675
|
+
const fallbacks = resolveCategoryFallbacks({
|
|
3676
|
+
fontFamily: font.name,
|
|
3677
|
+
fallbacks: font.fallbacks ?? {},
|
|
3678
|
+
metrics: { ...metrics, category: font.category }
|
|
3679
|
+
});
|
|
3680
|
+
const css = [];
|
|
3681
|
+
for (let i = fallbacks.length - 1;i >= 0; i--) {
|
|
3682
|
+
const fallback = fallbacks[i];
|
|
3683
|
+
const fallbackMetrics = await getMetricsForFamily(fallback);
|
|
3684
|
+
if (!fallbackMetrics)
|
|
3685
|
+
continue;
|
|
3686
|
+
css.push(generateFontFace({ ...metrics, category: font.category }, {
|
|
3687
|
+
name: this.#getFontFallbackName(font),
|
|
3688
|
+
font: fallback,
|
|
3689
|
+
metrics: fallbackMetrics,
|
|
3690
|
+
"font-weight": String(face.weight),
|
|
3691
|
+
"font-style": face.style
|
|
3692
|
+
}));
|
|
3920
3693
|
}
|
|
3921
|
-
return
|
|
3694
|
+
return css.join("");
|
|
3922
3695
|
}
|
|
3923
|
-
|
|
3924
|
-
|
|
3925
|
-
if (id.startsWith("@"))
|
|
3926
|
-
return parts.length >= 2 ? `${parts[0]}/${parts[1]}` : null;
|
|
3927
|
-
return parts[0] ?? null;
|
|
3696
|
+
#buildRootVariableRule(font) {
|
|
3697
|
+
return `:root { ${this.#getFontVariableName(font)}: ${this.#quote(font.name)}, ${this.#quote(this.#getFontFallbackName(font))}; }`;
|
|
3928
3698
|
}
|
|
3929
|
-
|
|
3930
|
-
|
|
3699
|
+
#buildFontUtilityRules(fonts) {
|
|
3700
|
+
const rules = [];
|
|
3701
|
+
const seen = new Set;
|
|
3702
|
+
for (const font of fonts) {
|
|
3703
|
+
const className = font.className || `font-${font.name}`;
|
|
3704
|
+
const selector = `.${this.#escapeCssClassName(className)}`;
|
|
3705
|
+
const rule = `${selector} { font-family: var(${this.#getFontVariableName(font)}); }`;
|
|
3706
|
+
if (seen.has(rule))
|
|
3707
|
+
continue;
|
|
3708
|
+
seen.add(rule);
|
|
3709
|
+
rules.push(rule);
|
|
3710
|
+
}
|
|
3711
|
+
return rules.join(`
|
|
3712
|
+
`);
|
|
3713
|
+
}
|
|
3714
|
+
#escapeCssClassName(value) {
|
|
3715
|
+
return value.replace(/^-?\d|[^a-zA-Z0-9_-]/g, (part) => [...part].map((char) => `\\${char.codePointAt(0)?.toString(16)} `).join(""));
|
|
3716
|
+
}
|
|
3717
|
+
#toDeclarationEntries(declarations = []) {
|
|
3718
|
+
return declarations.map((declaration) => [declaration.prop, declaration.value]);
|
|
3719
|
+
}
|
|
3720
|
+
#quote(value) {
|
|
3721
|
+
return JSON.stringify(value);
|
|
3931
3722
|
}
|
|
3932
3723
|
}
|
|
3933
3724
|
|
|
3934
|
-
// pkgs/@akanjs/devkit/frontendBuild/
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
var NODE_MODULES_RE3 = /[\\/]node_modules[\\/]/;
|
|
3938
|
-
var AKANJS_NODE_MODULE_RE3 = /[\\/]node_modules[\\/]akanjs[\\/]/;
|
|
3725
|
+
// pkgs/@akanjs/devkit/frontendBuild/hmrWatcher.ts
|
|
3726
|
+
import fs3 from "fs";
|
|
3727
|
+
import path20 from "path";
|
|
3939
3728
|
|
|
3940
|
-
|
|
3941
|
-
|
|
3942
|
-
|
|
3943
|
-
|
|
3944
|
-
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
if (
|
|
3952
|
-
return
|
|
3953
|
-
const
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
|
|
3958
|
-
|
|
3959
|
-
return this.#cssTextByBasePath;
|
|
3960
|
-
const akanConfig = await this.#app.getConfig({ refresh });
|
|
3961
|
-
const pageKeys = await this.#app.getPageKeys({ refresh });
|
|
3962
|
-
const basePaths = [...akanConfig.basePaths];
|
|
3963
|
-
const rootPageKeys = pageKeys.filter((pageKey) => getPageKeyBasePath(pageKey, basePaths) === null);
|
|
3964
|
-
const cssEntries = await Promise.all([
|
|
3965
|
-
(async () => {
|
|
3966
|
-
if (rootPageKeys.length === 0)
|
|
3967
|
-
return ["", ""];
|
|
3968
|
-
const started = Date.now();
|
|
3969
|
-
const { cssPaths, sourcePaths } = await this.discoverCssAndSources({ refresh, pageKeys: rootPageKeys });
|
|
3970
|
-
const css = await this.compileCss(cssPaths, sourcePaths);
|
|
3971
|
-
this.#logger.verbose(`css base=root paths=${cssPaths.length} sources=${sourcePaths.length} in ${Date.now() - started}ms`);
|
|
3972
|
-
return ["", css];
|
|
3973
|
-
})(),
|
|
3974
|
-
...basePaths.map(async (basePath) => {
|
|
3975
|
-
const basePathPageKeys = pageKeys.filter((pageKey) => getPageKeyBasePath(pageKey, basePaths) === basePath);
|
|
3976
|
-
if (basePathPageKeys.length === 0)
|
|
3977
|
-
return [basePath, ""];
|
|
3978
|
-
const started = Date.now();
|
|
3979
|
-
const { cssPaths, sourcePaths } = await this.discoverCssAndSources({ refresh, pageKeys: basePathPageKeys });
|
|
3980
|
-
const css = await this.compileCss(cssPaths, sourcePaths);
|
|
3981
|
-
this.#logger.verbose(`css base=${basePath} paths=${cssPaths.length} sources=${sourcePaths.length} in ${Date.now() - started}ms`);
|
|
3982
|
-
return [basePath, css];
|
|
3983
|
-
})
|
|
3984
|
-
]);
|
|
3985
|
-
this.#cssTextByBasePath = Object.fromEntries(cssEntries);
|
|
3986
|
-
return this.#cssTextByBasePath;
|
|
3729
|
+
// pkgs/@akanjs/devkit/frontendBuild/hmrChangeClassifier.ts
|
|
3730
|
+
import path19 from "path";
|
|
3731
|
+
var SOURCE_EXTS4 = new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
|
|
3732
|
+
var CSS_EXTS = new Set([".css"]);
|
|
3733
|
+
var CONFIG_BASENAMES2 = new Set(["akan.config.ts", "bunfig.toml", "tsconfig.json", "package.json"]);
|
|
3734
|
+
|
|
3735
|
+
class HmrChangeClassifier {
|
|
3736
|
+
classify(abs) {
|
|
3737
|
+
if (this.#isUninteresting(abs))
|
|
3738
|
+
return "ignore";
|
|
3739
|
+
const base = path19.basename(abs);
|
|
3740
|
+
if (CONFIG_BASENAMES2.has(base))
|
|
3741
|
+
return "config";
|
|
3742
|
+
const ext = path19.extname(abs).toLowerCase();
|
|
3743
|
+
if (CSS_EXTS.has(ext))
|
|
3744
|
+
return "css";
|
|
3745
|
+
if (SOURCE_EXTS4.has(ext))
|
|
3746
|
+
return "code";
|
|
3747
|
+
return "ignore";
|
|
3987
3748
|
}
|
|
3988
|
-
|
|
3989
|
-
const
|
|
3990
|
-
|
|
3749
|
+
#isUninteresting(abs) {
|
|
3750
|
+
const base = path19.basename(abs);
|
|
3751
|
+
if (!base)
|
|
3752
|
+
return true;
|
|
3753
|
+
if (base.startsWith("."))
|
|
3754
|
+
return true;
|
|
3755
|
+
if (base.endsWith("~") || base.endsWith(".swp") || base.endsWith(".swx") || base.endsWith(".tmp"))
|
|
3756
|
+
return true;
|
|
3757
|
+
if (abs.includes(`${path19.sep}node_modules${path19.sep}`))
|
|
3758
|
+
return true;
|
|
3759
|
+
if (abs.includes(`${path19.sep}.akan${path19.sep}`))
|
|
3760
|
+
return true;
|
|
3761
|
+
return false;
|
|
3991
3762
|
}
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
-
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
|
|
3763
|
+
}
|
|
3764
|
+
|
|
3765
|
+
// pkgs/@akanjs/devkit/frontendBuild/hmrWatcher.ts
|
|
3766
|
+
class HmrWatcher {
|
|
3767
|
+
#roots;
|
|
3768
|
+
#debounceMs;
|
|
3769
|
+
#onBatch;
|
|
3770
|
+
#logger;
|
|
3771
|
+
#watchers = [];
|
|
3772
|
+
#pending = new Map;
|
|
3773
|
+
#classifier = new HmrChangeClassifier;
|
|
3774
|
+
#timer = null;
|
|
3775
|
+
#stopped = false;
|
|
3776
|
+
#flushing = false;
|
|
3777
|
+
constructor(opts) {
|
|
3778
|
+
this.#roots = [...new Set(opts.roots.map((r) => path20.resolve(r)))];
|
|
3779
|
+
this.#debounceMs = opts.debounceMs ?? 80;
|
|
3780
|
+
this.#onBatch = opts.onBatch;
|
|
3781
|
+
this.#logger = opts.logger;
|
|
3782
|
+
}
|
|
3783
|
+
start() {
|
|
3784
|
+
for (const root of this.#roots) {
|
|
4010
3785
|
try {
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
3786
|
+
const w = fs3.watch(root, { recursive: true, persistent: false }, (_event, filename) => {
|
|
3787
|
+
if (!filename)
|
|
3788
|
+
return;
|
|
3789
|
+
const abs = path20.resolve(root, filename.toString());
|
|
3790
|
+
this.#queue(abs);
|
|
3791
|
+
});
|
|
3792
|
+
this.#watchers.push(w);
|
|
3793
|
+
this.#logger.verbose(`[hmr] watching ${root}`);
|
|
3794
|
+
} catch (err) {
|
|
3795
|
+
this.#logger.error(`[hmr] failed to watch ${root}: ${err.message}`);
|
|
4014
3796
|
}
|
|
4015
|
-
|
|
4016
|
-
|
|
3797
|
+
}
|
|
3798
|
+
}
|
|
3799
|
+
stop() {
|
|
3800
|
+
this.#stopped = true;
|
|
3801
|
+
if (this.#timer)
|
|
3802
|
+
clearTimeout(this.#timer);
|
|
3803
|
+
for (const w of this.#watchers) {
|
|
3804
|
+
try {
|
|
3805
|
+
w.close();
|
|
3806
|
+
} catch {}
|
|
3807
|
+
}
|
|
3808
|
+
}
|
|
3809
|
+
#queue(abs) {
|
|
3810
|
+
const kind = this.#classifier.classify(abs);
|
|
3811
|
+
if (kind === "ignore")
|
|
3812
|
+
return;
|
|
3813
|
+
this.#pending.set(abs, kind);
|
|
3814
|
+
if (this.#flushing)
|
|
3815
|
+
return;
|
|
3816
|
+
if (this.#timer)
|
|
3817
|
+
clearTimeout(this.#timer);
|
|
3818
|
+
this.#timer = setTimeout(() => this.#flush(), this.#debounceMs);
|
|
3819
|
+
}
|
|
3820
|
+
#flush() {
|
|
3821
|
+
this.#timer = null;
|
|
3822
|
+
if (this.#stopped || this.#pending.size === 0 || this.#flushing)
|
|
3823
|
+
return;
|
|
3824
|
+
this.#drain();
|
|
3825
|
+
}
|
|
3826
|
+
async#drain() {
|
|
3827
|
+
this.#flushing = true;
|
|
3828
|
+
try {
|
|
3829
|
+
while (!this.#stopped && this.#pending.size > 0) {
|
|
3830
|
+
const files = Array.from(this.#pending.keys());
|
|
3831
|
+
const kinds = new Set(this.#pending.values());
|
|
3832
|
+
this.#pending.clear();
|
|
4017
3833
|
try {
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
}
|
|
3834
|
+
await this.#onBatch({ files, kinds });
|
|
3835
|
+
} catch (e) {
|
|
3836
|
+
this.#logger.error(`[hmr] onBatch error: ${e.message}`);
|
|
3837
|
+
}
|
|
4022
3838
|
}
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
3839
|
+
} finally {
|
|
3840
|
+
this.#flushing = false;
|
|
3841
|
+
if (!this.#stopped && this.#pending.size > 0)
|
|
3842
|
+
this.#timer = setTimeout(() => this.#flush(), this.#debounceMs);
|
|
3843
|
+
}
|
|
3844
|
+
}
|
|
3845
|
+
}
|
|
3846
|
+
|
|
3847
|
+
// pkgs/@akanjs/devkit/frontendBuild/pagesBundleBuilder.ts
|
|
3848
|
+
import path22 from "path";
|
|
3849
|
+
|
|
3850
|
+
// pkgs/@akanjs/devkit/transforms/externalizeFrameworkPlugin.ts
|
|
3851
|
+
import path21 from "path";
|
|
3852
|
+
var DEFAULT_INCLUDE = ["akanjs/", "@apps/", "@libs/"];
|
|
3853
|
+
var DEFAULT_EXCLUDE_EXACT = new Set(["akanjs/webkit", "@akanjs/cli", "@akanjs/devkit"]);
|
|
3854
|
+
var DEFAULT_EXCLUDE_PREFIX = ["@akanjs/cli/", "@akanjs/devkit/"];
|
|
3855
|
+
var OPTIONAL_BACKEND_EXTERNAL_EXACT = new Set([
|
|
3856
|
+
"@libsql/client",
|
|
3857
|
+
"bullmq",
|
|
3858
|
+
"croner",
|
|
3859
|
+
"ioredis",
|
|
3860
|
+
"postgres",
|
|
3861
|
+
"protobufjs"
|
|
3862
|
+
]);
|
|
3863
|
+
var RUNTIME_EXTERNAL_EXACT = new Set([
|
|
3864
|
+
"react",
|
|
3865
|
+
"react-dom",
|
|
3866
|
+
"react/jsx-runtime",
|
|
3867
|
+
"react/jsx-dev-runtime",
|
|
3868
|
+
"react-server-dom-webpack",
|
|
3869
|
+
"react-server-dom-webpack/server.node",
|
|
3870
|
+
"react-server-dom-webpack/client.node",
|
|
3871
|
+
"react-server-dom-webpack/client.browser"
|
|
3872
|
+
]);
|
|
3873
|
+
var RUNTIME_EXTERNAL_PREFIX = ["react-dom/", "react-server-dom-webpack/"];
|
|
3874
|
+
var CANDIDATE_EXTS3 = [".tsx", ".ts", ".jsx", ".js", ".mjs", ".cjs"];
|
|
3875
|
+
async function createExternalizeFrameworkPlugin(options) {
|
|
3876
|
+
const tsconfig = await options.app.getTsConfig();
|
|
3877
|
+
const includePrefixes = options.include ?? DEFAULT_INCLUDE;
|
|
3878
|
+
const extraExact = new Set(options.extra ?? []);
|
|
3879
|
+
const workspaceRoot = options.app.workspace.workspaceRoot;
|
|
3880
|
+
const tsconfigPaths = tsconfig.compilerOptions.paths ?? {};
|
|
3881
|
+
const rootEntries = Object.entries(tsconfigPaths).filter(([k]) => !k.endsWith("/*")).map(([k, v]) => ({ pkg: k, entryFile: v[0] ?? null })).filter((e) => e.entryFile !== null);
|
|
3882
|
+
const wildcardEntries = Object.entries(tsconfigPaths).filter(([k]) => k.endsWith("/*")).map(([k, v]) => ({ prefix: k.slice(0, -1), replacements: v })).sort((a, b) => b.prefix.length - a.prefix.length);
|
|
3883
|
+
async function resolveWorkspaceSubpath(spec) {
|
|
3884
|
+
if (!workspaceRoot)
|
|
3885
|
+
return null;
|
|
3886
|
+
for (const { prefix, replacements } of wildcardEntries) {
|
|
3887
|
+
if (!spec.startsWith(prefix))
|
|
4027
3888
|
continue;
|
|
4028
|
-
|
|
4029
|
-
const
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
if (!spec)
|
|
4033
|
-
continue;
|
|
4034
|
-
if (spec.endsWith(".css")) {
|
|
4035
|
-
const cssPath = await this.#resolveCssImport(spec, importerDir);
|
|
4036
|
-
cssFiles.add(cssPath);
|
|
4037
|
-
continue;
|
|
4038
|
-
}
|
|
4039
|
-
if (NON_SOURCE_EXT_RE2.test(spec))
|
|
4040
|
-
continue;
|
|
4041
|
-
const resolved = await this.#resolveSourceImport(spec, importerDir, resolvePackage);
|
|
4042
|
-
if (!resolved || sourceFiles.has(resolved) || isIgnoredNodeModuleSource(resolved))
|
|
3889
|
+
const suffix = spec.slice(prefix.length);
|
|
3890
|
+
for (const repl of replacements) {
|
|
3891
|
+
const replPath = repl?.endsWith("/*") ? repl.slice(0, -1) : repl ?? "";
|
|
3892
|
+
if (!replPath)
|
|
4043
3893
|
continue;
|
|
4044
|
-
|
|
3894
|
+
const candidate = path21.resolve(workspaceRoot, replPath + suffix);
|
|
3895
|
+
const hit = await firstExisting(candidate);
|
|
3896
|
+
if (hit)
|
|
3897
|
+
return hit;
|
|
4045
3898
|
}
|
|
4046
3899
|
}
|
|
4047
|
-
|
|
3900
|
+
for (const { pkg, entryFile } of rootEntries) {
|
|
3901
|
+
if (spec !== pkg && !spec.startsWith(`${pkg}/`))
|
|
3902
|
+
continue;
|
|
3903
|
+
if (spec === pkg)
|
|
3904
|
+
continue;
|
|
3905
|
+
const suffix = spec.slice(pkg.length + 1);
|
|
3906
|
+
const pkgDir = path21.dirname(path21.resolve(workspaceRoot, entryFile));
|
|
3907
|
+
const candidate = path21.join(pkgDir, suffix);
|
|
3908
|
+
const hit = await firstExisting(candidate);
|
|
3909
|
+
if (hit)
|
|
3910
|
+
return hit;
|
|
3911
|
+
}
|
|
3912
|
+
return null;
|
|
4048
3913
|
}
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
3914
|
+
return {
|
|
3915
|
+
name: "akan-externalize-framework",
|
|
3916
|
+
setup(build) {
|
|
3917
|
+
build.onResolve({ filter: /.*/ }, async (args) => {
|
|
3918
|
+
const spec = args.path;
|
|
3919
|
+
if (spec === "." || spec === ".." || spec.startsWith("./") || spec.startsWith("../") || spec.startsWith("/"))
|
|
3920
|
+
return;
|
|
3921
|
+
if (extraExact.has(spec) || DEFAULT_EXCLUDE_EXACT.has(spec))
|
|
3922
|
+
return { path: spec, external: true };
|
|
3923
|
+
for (const prefix of DEFAULT_EXCLUDE_PREFIX) {
|
|
3924
|
+
if (spec.startsWith(prefix))
|
|
3925
|
+
return { path: spec, external: true };
|
|
3926
|
+
}
|
|
3927
|
+
if (OPTIONAL_BACKEND_EXTERNAL_EXACT.has(spec))
|
|
3928
|
+
return { path: spec, external: true };
|
|
3929
|
+
if (RUNTIME_EXTERNAL_EXACT.has(spec))
|
|
3930
|
+
return { path: spec, external: true };
|
|
3931
|
+
for (const prefix of RUNTIME_EXTERNAL_PREFIX) {
|
|
3932
|
+
if (spec.startsWith(prefix))
|
|
3933
|
+
return { path: spec, external: true };
|
|
3934
|
+
}
|
|
3935
|
+
for (const prefix of includePrefixes) {
|
|
3936
|
+
if (!spec.startsWith(prefix))
|
|
3937
|
+
continue;
|
|
3938
|
+
const resolved = await resolveWorkspaceSubpath(spec);
|
|
3939
|
+
if (resolved)
|
|
3940
|
+
return { path: resolved };
|
|
3941
|
+
return;
|
|
3942
|
+
}
|
|
3943
|
+
return;
|
|
4060
3944
|
});
|
|
4061
|
-
return { cssPath, compiler };
|
|
4062
|
-
}));
|
|
4063
|
-
const sourceDirs = new Set;
|
|
4064
|
-
for (const entry of compilers) {
|
|
4065
|
-
if (!entry)
|
|
4066
|
-
continue;
|
|
4067
|
-
for (const s of entry.compiler.sources)
|
|
4068
|
-
sourceDirs.add(s.base);
|
|
4069
3945
|
}
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
3946
|
+
};
|
|
3947
|
+
}
|
|
3948
|
+
async function firstExisting(basePath) {
|
|
3949
|
+
if (await Bun.file(basePath).exists())
|
|
3950
|
+
return basePath;
|
|
3951
|
+
for (const ext of CANDIDATE_EXTS3) {
|
|
3952
|
+
const candidate = `${basePath}${ext}`;
|
|
3953
|
+
if (await Bun.file(candidate).exists())
|
|
3954
|
+
return candidate;
|
|
3955
|
+
}
|
|
3956
|
+
for (const ext of CANDIDATE_EXTS3) {
|
|
3957
|
+
const candidate = path21.join(basePath, `index${ext}`);
|
|
3958
|
+
if (await Bun.file(candidate).exists())
|
|
3959
|
+
return candidate;
|
|
3960
|
+
}
|
|
3961
|
+
return null;
|
|
3962
|
+
}
|
|
3963
|
+
|
|
3964
|
+
// pkgs/@akanjs/devkit/transforms/useClientBundlePlugin.ts
|
|
3965
|
+
function createUseClientBundlePlugin(options = {}) {
|
|
3966
|
+
return {
|
|
3967
|
+
name: "akan-use-client-bundle",
|
|
3968
|
+
setup(build) {
|
|
3969
|
+
build.onLoad({ filter: /\.(tsx|ts|jsx|js)$/ }, async (args) => {
|
|
3970
|
+
if (args.path.includes("/node_modules/"))
|
|
3971
|
+
return;
|
|
3972
|
+
let source;
|
|
3973
|
+
try {
|
|
3974
|
+
source = await Bun.file(args.path).text();
|
|
3975
|
+
} catch {
|
|
3976
|
+
return;
|
|
3977
|
+
}
|
|
3978
|
+
const stubbed = transformUseClient(source, {
|
|
3979
|
+
path: args.path,
|
|
3980
|
+
workspaceRoot: options.workspaceRoot
|
|
3981
|
+
});
|
|
3982
|
+
if (stubbed === null)
|
|
3983
|
+
return;
|
|
3984
|
+
return { contents: stubbed, loader: loaderFor3(args.path) };
|
|
3985
|
+
});
|
|
4078
3986
|
}
|
|
4079
|
-
|
|
4080
|
-
|
|
4081
|
-
|
|
3987
|
+
};
|
|
3988
|
+
}
|
|
3989
|
+
function loaderFor3(absPath) {
|
|
3990
|
+
if (absPath.endsWith(".tsx"))
|
|
3991
|
+
return "tsx";
|
|
3992
|
+
if (absPath.endsWith(".jsx"))
|
|
3993
|
+
return "jsx";
|
|
3994
|
+
if (absPath.endsWith(".ts"))
|
|
3995
|
+
return "ts";
|
|
3996
|
+
return "js";
|
|
3997
|
+
}
|
|
3998
|
+
|
|
3999
|
+
// pkgs/@akanjs/devkit/frontendBuild/pagesBundleBuilder.ts
|
|
4000
|
+
var VIRTUAL_PAGES_ENTRY = "akan-pages-entry";
|
|
4001
|
+
|
|
4002
|
+
class PagesBundleBuilder {
|
|
4003
|
+
#app;
|
|
4004
|
+
#command;
|
|
4005
|
+
#pageEntries;
|
|
4006
|
+
#started = Date.now();
|
|
4007
|
+
constructor(app, command = "start", pageEntries) {
|
|
4008
|
+
this.#app = app;
|
|
4009
|
+
this.#command = command;
|
|
4010
|
+
this.#pageEntries = pageEntries;
|
|
4082
4011
|
}
|
|
4083
|
-
async
|
|
4084
|
-
const
|
|
4085
|
-
const
|
|
4086
|
-
|
|
4012
|
+
async build() {
|
|
4013
|
+
const akanConfig = await this.#app.getConfig();
|
|
4014
|
+
const resolvedEntries = this.#pageEntries ?? await resolveSsrPageEntriesForApp(this.#app, await this.#app.getPageKeys());
|
|
4015
|
+
const entrySource = PagesEntrySourceGenerator.generate(resolvedEntries);
|
|
4016
|
+
const workspaceRoot = this.#app.workspace.workspaceRoot;
|
|
4017
|
+
const result = await Bun.build({
|
|
4018
|
+
entrypoints: [VIRTUAL_PAGES_ENTRY],
|
|
4019
|
+
outdir: `${this.#artifactDir}/server`,
|
|
4020
|
+
target: "bun",
|
|
4021
|
+
format: "esm",
|
|
4022
|
+
splitting: this.#splitting,
|
|
4023
|
+
minify: this.#command === "build",
|
|
4024
|
+
naming: {
|
|
4025
|
+
entry: "pages-[hash].[ext]",
|
|
4026
|
+
chunk: "chunks/[name]-[hash].[ext]",
|
|
4027
|
+
asset: "assets/[name]-[hash].[ext]"
|
|
4028
|
+
},
|
|
4029
|
+
define: this.#define(),
|
|
4030
|
+
plugins: [
|
|
4031
|
+
PagesBundleBuilder.createPagesEntryPlugin(entrySource),
|
|
4032
|
+
PagesBundleBuilder.createServerCssStubPlugin(),
|
|
4033
|
+
PagesBundleBuilder.createServerUseClientFetchPlugin(),
|
|
4034
|
+
await createExternalizeFrameworkPlugin({ app: this.#app, extra: akanConfig.externalLibs }),
|
|
4035
|
+
akanConfig.barrelImports.length > 0 ? await createBarrelImportsPlugin(this.#app, {
|
|
4036
|
+
pipeAfter: (source, args) => transformUseClient(source, {
|
|
4037
|
+
path: args.path,
|
|
4038
|
+
workspaceRoot
|
|
4039
|
+
})
|
|
4040
|
+
}) : createUseClientBundlePlugin({ workspaceRoot })
|
|
4041
|
+
]
|
|
4042
|
+
});
|
|
4043
|
+
if (!result.success)
|
|
4044
|
+
throw new AggregateError(result.logs, "[PagesBundleBuilder] Bun.build failed");
|
|
4045
|
+
const entryArtifact = result.outputs.find((a) => a.kind === "entry-point");
|
|
4046
|
+
if (!entryArtifact)
|
|
4047
|
+
throw new Error("[PagesBundleBuilder] Bun.build emitted no entry-point artifact");
|
|
4048
|
+
const bundlePath = path22.resolve(entryArtifact.path);
|
|
4049
|
+
const buildId = Date.now();
|
|
4050
|
+
const outputBytes = result.outputs.reduce((sum, output) => sum + output.size, 0);
|
|
4051
|
+
const chunkCount = result.outputs.filter((output) => output.kind === "chunk").length;
|
|
4052
|
+
this.#app.verbose(`[PagesBundleBuilder] ${path22.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`);
|
|
4053
|
+
return {
|
|
4054
|
+
bundlePath,
|
|
4055
|
+
buildId,
|
|
4056
|
+
splitting: this.#splitting,
|
|
4057
|
+
entryBytes: entryArtifact.size,
|
|
4058
|
+
outputBytes,
|
|
4059
|
+
outputCount: result.outputs.length,
|
|
4060
|
+
chunkCount
|
|
4061
|
+
};
|
|
4087
4062
|
}
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
return path23.resolve(fromBase, id);
|
|
4091
|
-
const resolver = await this.#getCssImportResolver();
|
|
4092
|
-
const resolved = await resolver.resolve(id, fromBase);
|
|
4093
|
-
if (resolved)
|
|
4094
|
-
return resolved;
|
|
4095
|
-
throw new Error(`[css] failed to resolve stylesheet import "${id}" from ${fromBase}`);
|
|
4063
|
+
get #artifactDir() {
|
|
4064
|
+
return `${this.#command === "build" ? this.#app.dist.cwdPath : this.#app.cwdPath}/.akan/artifact`;
|
|
4096
4065
|
}
|
|
4097
|
-
|
|
4098
|
-
|
|
4099
|
-
return this.#cssImportResolver;
|
|
4100
|
-
this.#cssImportResolver = await CssImportResolver.create(this.#app);
|
|
4101
|
-
return this.#cssImportResolver;
|
|
4066
|
+
get #splitting() {
|
|
4067
|
+
return process.env.AKAN_SERVER_PAGES_SPLITTING === "1";
|
|
4102
4068
|
}
|
|
4103
|
-
|
|
4104
|
-
const
|
|
4105
|
-
|
|
4106
|
-
|
|
4069
|
+
#define() {
|
|
4070
|
+
const nodeEnv = this.#command === "build" ? "production" : "development";
|
|
4071
|
+
return {
|
|
4072
|
+
"process.env.NODE_ENV": JSON.stringify(nodeEnv),
|
|
4073
|
+
"process.env.AKAN_PUBLIC_RENDER_ENV": JSON.stringify("ssr"),
|
|
4074
|
+
...Object.fromEntries(Object.entries(this.#app.getPublicEnv()).map(([key, value]) => [`process.env.${key}`, JSON.stringify(value)]))
|
|
4075
|
+
};
|
|
4107
4076
|
}
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4077
|
+
static createPagesEntryPlugin(source) {
|
|
4078
|
+
return {
|
|
4079
|
+
name: "akan-pages-entry",
|
|
4080
|
+
setup(build) {
|
|
4081
|
+
build.onResolve({ filter: /^akan-pages-entry$/ }, () => ({
|
|
4082
|
+
path: VIRTUAL_PAGES_ENTRY,
|
|
4083
|
+
namespace: "akan-virtual"
|
|
4084
|
+
}));
|
|
4085
|
+
build.onLoad({ filter: /^akan-pages-entry$/, namespace: "akan-virtual" }, () => ({
|
|
4086
|
+
contents: source,
|
|
4087
|
+
loader: "tsx"
|
|
4088
|
+
}));
|
|
4089
|
+
}
|
|
4090
|
+
};
|
|
4122
4091
|
}
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
continue;
|
|
4132
|
-
files.add(file);
|
|
4092
|
+
static createServerCssStubPlugin() {
|
|
4093
|
+
return {
|
|
4094
|
+
name: "akan-server-css-stub",
|
|
4095
|
+
setup(build) {
|
|
4096
|
+
build.onLoad({ filter: /\.css$/ }, () => ({
|
|
4097
|
+
contents: "",
|
|
4098
|
+
loader: "js"
|
|
4099
|
+
}));
|
|
4133
4100
|
}
|
|
4134
|
-
}
|
|
4135
|
-
await Promise.all([...files].map(async (file) => {
|
|
4136
|
-
const content = await Bun.file(file).text();
|
|
4137
|
-
for (const m of content.matchAll(CANDIDATE_RE))
|
|
4138
|
-
candidates.add(m[0]);
|
|
4139
|
-
}));
|
|
4140
|
-
return [...candidates];
|
|
4101
|
+
};
|
|
4141
4102
|
}
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4103
|
+
static createServerUseClientFetchPlugin() {
|
|
4104
|
+
return {
|
|
4105
|
+
name: "akan-server-use-client-fetch",
|
|
4106
|
+
setup(build) {
|
|
4107
|
+
build.onLoad({ filter: /[/\\]lib[/\\]useClient\.(ts|tsx|js|jsx)$/ }, async (args) => {
|
|
4108
|
+
const source = await Bun.file(args.path).text();
|
|
4109
|
+
const transformed = PagesBundleBuilder.transformServerUseClientFetchSource(source);
|
|
4110
|
+
if (transformed === source)
|
|
4111
|
+
return;
|
|
4112
|
+
return { contents: transformed, loader: loaderFor4(args.path) };
|
|
4113
|
+
});
|
|
4114
|
+
}
|
|
4115
|
+
};
|
|
4150
4116
|
}
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4117
|
+
static transformServerUseClientFetchSource(source) {
|
|
4118
|
+
if (!source.includes(`with { type: "macro" }`) || !source.includes("FetchClient.build"))
|
|
4119
|
+
return source;
|
|
4120
|
+
return source.replace(/import\s+\{\s*getSerializedSignal\s*\}\s+from\s+["']\.\/sig["']\s+with\s+\{\s*type\s*:\s*["']macro["']\s*\};/, `import { fetch as serverFetch } from "./sig";`).replace(/const\s+fetchProto\s*=\s*FetchClient\.build<[^;]+;/, "const fetchProto = FetchClient.build<typeof signal>(cnst, serverFetch.serializedSignal, { Err: pageProto.Err, base: serverFetch });");
|
|
4155
4121
|
}
|
|
4156
|
-
return null;
|
|
4157
4122
|
}
|
|
4158
|
-
function
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
|
|
4123
|
+
function loaderFor4(absPath) {
|
|
4124
|
+
if (absPath.endsWith(".tsx"))
|
|
4125
|
+
return "tsx";
|
|
4126
|
+
if (absPath.endsWith(".jsx"))
|
|
4127
|
+
return "jsx";
|
|
4128
|
+
if (absPath.endsWith(".ts"))
|
|
4129
|
+
return "ts";
|
|
4130
|
+
return "js";
|
|
4131
|
+
}
|
|
4132
|
+
|
|
4133
|
+
// pkgs/@akanjs/devkit/frontendBuild/precompressArtifacts.ts
|
|
4134
|
+
import fs4 from "fs";
|
|
4135
|
+
import path23 from "path";
|
|
4136
|
+
var COMPRESSIBLE_EXTS = new Set([".css", ".html", ".js", ".json", ".svg"]);
|
|
4137
|
+
var MIN_COMPRESS_BYTES = 1024;
|
|
4138
|
+
async function precompressArtifacts(app) {
|
|
4139
|
+
const roots = [path23.join(app.dist.cwdPath, ".akan/artifact/client")];
|
|
4140
|
+
const result = { files: 0, inputBytes: 0, outputBytes: 0 };
|
|
4141
|
+
await Promise.all(roots.map((root) => precompressRoot(root, result)));
|
|
4142
|
+
if (result.files > 0) {
|
|
4143
|
+
app.verbose(`[precompress] wrote ${result.files} gzip sidecars (${formatBytes(result.inputBytes)} -> ${formatBytes(result.outputBytes)})`);
|
|
4164
4144
|
}
|
|
4145
|
+
return result;
|
|
4165
4146
|
}
|
|
4166
|
-
function
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
|
|
4147
|
+
async function precompressRoot(root, result) {
|
|
4148
|
+
if (!fs4.existsSync(root) || !fs4.statSync(root).isDirectory())
|
|
4149
|
+
return;
|
|
4150
|
+
const glob = new Bun.Glob("**/*");
|
|
4151
|
+
for await (const filePath of glob.scan({ cwd: root, absolute: true })) {
|
|
4152
|
+
if (!await shouldPrecompress(filePath))
|
|
4153
|
+
continue;
|
|
4154
|
+
const bytes = await Bun.file(filePath).bytes();
|
|
4155
|
+
const gz = Bun.gzipSync(toArrayBuffer(bytes));
|
|
4156
|
+
await Bun.write(`${filePath}.gz`, gz);
|
|
4157
|
+
result.files += 1;
|
|
4158
|
+
result.inputBytes += bytes.byteLength;
|
|
4159
|
+
result.outputBytes += gz.byteLength;
|
|
4172
4160
|
}
|
|
4173
4161
|
}
|
|
4174
|
-
function
|
|
4175
|
-
|
|
4162
|
+
async function shouldPrecompress(filePath) {
|
|
4163
|
+
if (filePath.endsWith(".gz"))
|
|
4164
|
+
return false;
|
|
4165
|
+
if (!COMPRESSIBLE_EXTS.has(path23.extname(filePath).toLowerCase()))
|
|
4166
|
+
return false;
|
|
4167
|
+
const file = Bun.file(filePath);
|
|
4168
|
+
if (!await file.exists())
|
|
4169
|
+
return false;
|
|
4170
|
+
return file.size >= MIN_COMPRESS_BYTES;
|
|
4176
4171
|
}
|
|
4177
|
-
function
|
|
4178
|
-
return
|
|
4172
|
+
function toArrayBuffer(bytes) {
|
|
4173
|
+
return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
|
|
4179
4174
|
}
|
|
4180
|
-
function
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
|
|
4184
|
-
|
|
4175
|
+
function formatBytes(bytes) {
|
|
4176
|
+
if (bytes < 1024)
|
|
4177
|
+
return `${bytes}B`;
|
|
4178
|
+
if (bytes < 1024 * 1024)
|
|
4179
|
+
return `${(bytes / 1024).toFixed(1)}KB`;
|
|
4180
|
+
return `${(bytes / 1024 / 1024).toFixed(1)}MB`;
|
|
4185
4181
|
}
|
|
4186
4182
|
|
|
4187
4183
|
// pkgs/@akanjs/devkit/frontendBuild/ssrBaseArtifactBuilder.ts
|
|
4184
|
+
import path24 from "path";
|
|
4185
|
+
import { optimize } from "@tailwindcss/node";
|
|
4188
4186
|
function prepareCssAsset(command, basePath, cssText) {
|
|
4189
4187
|
return optimize(cssText, { file: `${basePath || "root"}.css`, minify: command === "build" }).code;
|
|
4190
4188
|
}
|
|
@@ -4356,4 +4354,4 @@ class WatchRootResolver {
|
|
|
4356
4354
|
return [...set];
|
|
4357
4355
|
}
|
|
4358
4356
|
}
|
|
4359
|
-
export { GraphClientEntryDiscovery, RouteClientBuilder, AllRoutesBuilder, AutoImportSync, CsrArtifactBuilder, DevChangePlanner, DevGeneratedIndexSync, FontOptimizer, HmrWatcher, PagesBundleBuilder, precompressArtifacts, SsrBaseArtifactBuilder, WatchRootResolver };
|
|
4357
|
+
export { GraphClientEntryDiscovery, RouteClientBuilder, AllRoutesBuilder, AutoImportSync, CsrArtifactBuilder, CssCompiler, DevChangePlanner, DevGeneratedIndexSync, FontOptimizer, HmrWatcher, PagesBundleBuilder, precompressArtifacts, SsrBaseArtifactBuilder, WatchRootResolver };
|