@akanjs/devkit 3.1.0 → 3.1.1-rc.0
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/akanConfig/akanConfig.ts +1 -1
- package/artifact/implicitRootLayout.ts +1 -1
- package/frontendBuild/dictionaryAssetBuilder.test.ts +77 -0
- package/frontendBuild/dictionaryAssetBuilder.ts +49 -0
- package/frontendBuild/precompressArtifacts.test.ts +42 -0
- package/frontendBuild/precompressArtifacts.ts +21 -10
- package/frontendBuild/routeClientBuilder.ts +1 -0
- package/frontendBuild/ssrBaseArtifactBuilder.ts +3 -0
- package/frontendBuild/vendorSpecifiers.ts +1 -0
- package/mobile/desktopServerStage.ts +1 -1
- package/mobile/nativeApi.ts +1 -1
- package/mobile/nativeApp.ts +1 -1
- package/mobile/nativeConfig.ts +1 -1
- package/mobile/updatePackFolder.ts +1 -1
- package/mobile/updateUploader.ts +1 -1
- package/package.json +2 -2
- package/transforms/barrelImportsPlugin.ts +4 -2
package/akanConfig/akanConfig.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
|
-
import type { ServerPolicy } from "@akanjs/native/api";
|
|
5
4
|
import type { AkanPlugin } from "akanjs";
|
|
6
5
|
import { DatabaseModes, normalizeRoutePrefix } from "akanjs/base";
|
|
7
6
|
import { type AkanI18nConfig, resolveAkanI18nConfig, trimSlashes } from "akanjs/common";
|
|
7
|
+
import type { ServerPolicy } from "akanjs/native/api";
|
|
8
8
|
import type { AkanImageConfig } from "akanjs/server";
|
|
9
9
|
import type { App, Lib } from "../commandDecorators";
|
|
10
10
|
import { LibExecutor, WorkspaceExecutor } from "../executors";
|
|
@@ -244,7 +244,7 @@ ${layoutExports}(
|
|
|
244
244
|
className={defaultFontClassName}
|
|
245
245
|
layoutStyle={userLayout.layoutStyle ?? inheritedLayout.layoutStyle}
|
|
246
246
|
reconnect={userLayout.reconnect ?? inheritedLayout.reconnect ?? false}
|
|
247
|
-
wsConnect={userLayout.wsConnect ?? inheritedLayout.wsConnect
|
|
247
|
+
wsConnect={userLayout.wsConnect ?? inheritedLayout.wsConnect}
|
|
248
248
|
allDictionary={process.env.AKAN_PUBLIC_RENDER_ENV === "ssr" ? allDictionary : undefined}
|
|
249
249
|
>
|
|
250
250
|
${layoutChild}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { mkdir, readdir, writeFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import type { App } from "../commandDecorators";
|
|
5
|
+
import { tempDirs } from "../testHelpers";
|
|
6
|
+
import { DictionaryAssetBuilder } from "./dictionaryAssetBuilder";
|
|
7
|
+
|
|
8
|
+
const tempRoot = tempDirs("akan-devkit-dict-asset-");
|
|
9
|
+
|
|
10
|
+
const allDictionary = {
|
|
11
|
+
en: { app: { title: { t: "Title" }, partial: { t: "Partial" } }, libOnly: { signin: { t: "Sign In" } } },
|
|
12
|
+
ko: { app: { title: { t: "제목" } } },
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const makeApp = async (dictSource?: string) => {
|
|
16
|
+
const root = await tempRoot();
|
|
17
|
+
const cwdPath = path.join(root, "apps/demo");
|
|
18
|
+
await mkdir(path.join(cwdPath, "lib"), { recursive: true });
|
|
19
|
+
if (dictSource) await writeFile(path.join(cwdPath, "lib/dict.ts"), dictSource);
|
|
20
|
+
const app = { cwdPath, verbose: () => undefined } as unknown as App;
|
|
21
|
+
return { app, artifactDir: path.join(root, "artifact") };
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const dictSourceOf = (dictionary: object) => `export const getAllDictionary = () => (${JSON.stringify(dictionary)});\n`;
|
|
25
|
+
|
|
26
|
+
describe("DictionaryAssetBuilder", () => {
|
|
27
|
+
test("writes one module per locale, the default locale's text filled into the gaps", async () => {
|
|
28
|
+
const { app, artifactDir } = await makeApp(dictSourceOf(allDictionary));
|
|
29
|
+
const assets = await new DictionaryAssetBuilder(app, artifactDir).build({
|
|
30
|
+
defaultLocale: "en",
|
|
31
|
+
locales: ["en", "ko"],
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
expect(Object.keys(assets)).toEqual(["en", "ko"]);
|
|
35
|
+
expect(assets.ko.url).toMatch(/^\/_akan\/dict\/ko-[a-z0-9]+\.js$/);
|
|
36
|
+
expect(assets.ko.url).toBe(`/_akan/${assets.ko.relPath}`);
|
|
37
|
+
const ko = (await import(path.join(artifactDir, assets.ko.relPath))) as { lang: string; default: object };
|
|
38
|
+
expect(ko.lang).toBe("ko");
|
|
39
|
+
expect(ko.default).toEqual({
|
|
40
|
+
app: { title: { t: "제목" }, partial: { t: "Partial" } },
|
|
41
|
+
libOnly: { signin: { t: "Sign In" } },
|
|
42
|
+
});
|
|
43
|
+
const en = (await import(path.join(artifactDir, assets.en.relPath))) as { default: object };
|
|
44
|
+
expect(en.default).toEqual(allDictionary.en);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("takes the default locale from the config it is handed", async () => {
|
|
48
|
+
const { app, artifactDir } = await makeApp(dictSourceOf(allDictionary));
|
|
49
|
+
const assets = await new DictionaryAssetBuilder(app, artifactDir).build({
|
|
50
|
+
defaultLocale: "ko",
|
|
51
|
+
locales: ["en", "ko"],
|
|
52
|
+
});
|
|
53
|
+
const en = (await import(path.join(artifactDir, assets.en.relPath))) as { default: object };
|
|
54
|
+
expect(en.default).toEqual(allDictionary.en);
|
|
55
|
+
const ko = (await import(path.join(artifactDir, assets.ko.relPath))) as { default: object };
|
|
56
|
+
expect(ko.default).toEqual(allDictionary.ko);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("names a module by its content, so a rebuild of the same dictionary keeps the URL", async () => {
|
|
60
|
+
const { app, artifactDir } = await makeApp(dictSourceOf(allDictionary));
|
|
61
|
+
const i18n = { defaultLocale: "en", locales: ["en", "ko"] };
|
|
62
|
+
const first = await new DictionaryAssetBuilder(app, artifactDir).build(i18n);
|
|
63
|
+
const second = await new DictionaryAssetBuilder(app, artifactDir).build(i18n);
|
|
64
|
+
expect(second).toEqual(first);
|
|
65
|
+
expect((await readdir(path.join(artifactDir, "dict"))).length).toBe(2);
|
|
66
|
+
expect(DictionaryAssetBuilder.source("en", { a: { t: "A" } })).not.toBe(
|
|
67
|
+
DictionaryAssetBuilder.source("en", { a: { t: "B" } }),
|
|
68
|
+
);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("writes nothing for an app without a dictionary", async () => {
|
|
72
|
+
const { app, artifactDir } = await makeApp();
|
|
73
|
+
expect(await new DictionaryAssetBuilder(app, artifactDir).build({ defaultLocale: "en", locales: ["en"] })).toEqual(
|
|
74
|
+
{},
|
|
75
|
+
);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { type AllDictionary, Translator } from "akanjs/client";
|
|
3
|
+
import type { AkanI18nConfig } from "akanjs/common";
|
|
4
|
+
import type { DictAsset } from "akanjs/server";
|
|
5
|
+
import type { App } from "../commandDecorators";
|
|
6
|
+
|
|
7
|
+
export class DictionaryAssetBuilder {
|
|
8
|
+
static readonly urlPrefix = "/_akan/dict";
|
|
9
|
+
readonly #app: App;
|
|
10
|
+
readonly #absArtifactDir: string;
|
|
11
|
+
|
|
12
|
+
constructor(app: App, absArtifactDir: string) {
|
|
13
|
+
this.#app = app;
|
|
14
|
+
this.#absArtifactDir = absArtifactDir;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async build(i18n: AkanI18nConfig): Promise<Record<string, DictAsset>> {
|
|
18
|
+
const allDictionary = await this.#loadAllDictionary();
|
|
19
|
+
if (!allDictionary) return {};
|
|
20
|
+
const assets = await Promise.all(
|
|
21
|
+
i18n.locales.map((lang) =>
|
|
22
|
+
this.#write(lang, Translator.fillFromDefault(allDictionary, lang, i18n.defaultLocale)),
|
|
23
|
+
),
|
|
24
|
+
);
|
|
25
|
+
this.#app.verbose(`[base-artifact] dictionaries -> ${assets.map(([, asset]) => asset.relPath).join(", ")}`);
|
|
26
|
+
return Object.fromEntries(assets);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
//? The same module the root layout's macro evaluates in this process, so the asset and the RSC worker's copy agree.
|
|
30
|
+
async #loadAllDictionary(): Promise<AllDictionary | null> {
|
|
31
|
+
const dictPath = path.resolve(this.#app.cwdPath, "lib", "dict.ts");
|
|
32
|
+
if (!(await Bun.file(dictPath).exists())) return null;
|
|
33
|
+
const { getAllDictionary } = (await import(dictPath)) as { getAllDictionary?: () => AllDictionary };
|
|
34
|
+
return getAllDictionary ? getAllDictionary() : null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async #write(lang: string, dictionary: object): Promise<readonly [string, DictAsset]> {
|
|
38
|
+
const source = DictionaryAssetBuilder.source(lang, dictionary);
|
|
39
|
+
const name = `${lang}-${Bun.hash(source).toString(36)}.js`;
|
|
40
|
+
const relPath = `dict/${name}`;
|
|
41
|
+
await Bun.write(path.join(this.#absArtifactDir, relPath), source);
|
|
42
|
+
return [lang, { url: `${DictionaryAssetBuilder.urlPrefix}/${name}`, relPath }] as const;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
//? A string for JSON.parse, not an object literal: engines parse the JSON grammar faster than a JS expression.
|
|
46
|
+
static source(lang: string, dictionary: object): string {
|
|
47
|
+
return `export const lang = ${JSON.stringify(lang)};\nexport default JSON.parse(${JSON.stringify(JSON.stringify(dictionary))});\n`;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import zlib from "node:zlib";
|
|
5
|
+
import type { App } from "../commandDecorators";
|
|
6
|
+
import { tempDirs } from "../testHelpers";
|
|
7
|
+
import { precompressArtifacts } from "./precompressArtifacts";
|
|
8
|
+
|
|
9
|
+
const tempRoot = tempDirs("akan-devkit-precompress-");
|
|
10
|
+
|
|
11
|
+
const brotliAt = (bytes: Buffer, quality: number) =>
|
|
12
|
+
zlib.brotliCompressSync(bytes, {
|
|
13
|
+
params: {
|
|
14
|
+
[zlib.constants.BROTLI_PARAM_QUALITY]: quality,
|
|
15
|
+
[zlib.constants.BROTLI_PARAM_SIZE_HINT]: bytes.byteLength,
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
describe("precompressArtifacts", () => {
|
|
20
|
+
test("writes sidecars for client chunks, stylesheets and dictionaries, a dictionary's brotli at quality 11", async () => {
|
|
21
|
+
const distPath = await tempRoot();
|
|
22
|
+
const artifactDir = path.join(distPath, ".akan/artifact");
|
|
23
|
+
const files = {
|
|
24
|
+
"client/chunk.js": `export const x = ${JSON.stringify("chunk ".repeat(400))};`,
|
|
25
|
+
"styles/root.css": `.a{color:red}${".b{margin:0}".repeat(200)}`,
|
|
26
|
+
"dict/en-abc.js": `export default JSON.parse(${JSON.stringify(JSON.stringify({ a: { t: "word ".repeat(400) } }))});`,
|
|
27
|
+
};
|
|
28
|
+
for (const [rel, content] of Object.entries(files)) {
|
|
29
|
+
await mkdir(path.dirname(path.join(artifactDir, rel)), { recursive: true });
|
|
30
|
+
await writeFile(path.join(artifactDir, rel), content);
|
|
31
|
+
}
|
|
32
|
+
const app = { dist: { cwdPath: distPath }, verbose: () => undefined } as unknown as App;
|
|
33
|
+
|
|
34
|
+
expect((await precompressArtifacts(app)).files).toBe(3);
|
|
35
|
+
const dictPath = path.join(artifactDir, "dict/en-abc.js");
|
|
36
|
+
const dictBytes = await readFile(dictPath);
|
|
37
|
+
expect(await readFile(`${dictPath}.br`)).toEqual(brotliAt(dictBytes, 11));
|
|
38
|
+
const chunkPath = path.join(artifactDir, "client/chunk.js");
|
|
39
|
+
expect(await readFile(`${chunkPath}.br`)).toEqual(brotliAt(await readFile(chunkPath), 9));
|
|
40
|
+
expect(await Bun.file(`${dictPath}.gz`).exists()).toBe(true);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
@@ -6,8 +6,10 @@ import { FontPruner } from "./fontPruner";
|
|
|
6
6
|
|
|
7
7
|
const COMPRESSIBLE_EXTS = new Set([".css", ".html", ".js", ".json", ".svg"]);
|
|
8
8
|
const MIN_COMPRESS_BYTES = 1024;
|
|
9
|
-
// Brotli 11 is ~100x slower than gzip: worth it for the one CSS asset per basePath
|
|
10
|
-
|
|
9
|
+
// Brotli 11 is ~100x slower than gzip: worth it for the one CSS asset per basePath and one dictionary per locale, not
|
|
10
|
+
// for hundreds of JS chunks.
|
|
11
|
+
const MAX_BROTLI_QUALITY = 11;
|
|
12
|
+
const BROTLI_QUALITY_BY_EXT = { ".css": MAX_BROTLI_QUALITY } as const;
|
|
11
13
|
const DEFAULT_BROTLI_QUALITY = 9;
|
|
12
14
|
const GZIP_LEVEL = 9;
|
|
13
15
|
const { formatBytes } = FontPruner;
|
|
@@ -20,14 +22,16 @@ export interface PrecompressArtifactsResult {
|
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
export async function precompressArtifacts(app: App): Promise<PrecompressArtifactsResult> {
|
|
23
|
-
//* WebRouter serves client/ and
|
|
25
|
+
//* WebRouter serves client/, styles/ and dict/ through the same sidecar-aware #fileResponse.
|
|
26
|
+
const artifactDir = path.join(app.dist.cwdPath, ".akan/artifact");
|
|
24
27
|
const roots = [
|
|
25
|
-
path.join(
|
|
26
|
-
path.join(
|
|
28
|
+
{ root: path.join(artifactDir, "client") },
|
|
29
|
+
{ root: path.join(artifactDir, "styles") },
|
|
30
|
+
{ root: path.join(artifactDir, "dict"), brotliQuality: MAX_BROTLI_QUALITY },
|
|
27
31
|
];
|
|
28
32
|
const result: PrecompressArtifactsResult = { files: 0, inputBytes: 0, outputBytes: 0, brotliBytes: 0 };
|
|
29
33
|
|
|
30
|
-
await Promise.all(roots.map((root) => precompressRoot(root, result)));
|
|
34
|
+
await Promise.all(roots.map(({ root, brotliQuality }) => precompressRoot(root, result, brotliQuality)));
|
|
31
35
|
if (result.files > 0) {
|
|
32
36
|
app.verbose(
|
|
33
37
|
`[precompress] wrote ${result.files} sidecars (${formatBytes(result.inputBytes)} -> gzip ${formatBytes(
|
|
@@ -38,7 +42,11 @@ export async function precompressArtifacts(app: App): Promise<PrecompressArtifac
|
|
|
38
42
|
return result;
|
|
39
43
|
}
|
|
40
44
|
|
|
41
|
-
async function precompressRoot(
|
|
45
|
+
async function precompressRoot(
|
|
46
|
+
root: string,
|
|
47
|
+
result: PrecompressArtifactsResult,
|
|
48
|
+
brotliQuality?: number,
|
|
49
|
+
): Promise<void> {
|
|
42
50
|
if (!fs.existsSync(root) || !fs.statSync(root).isDirectory()) return;
|
|
43
51
|
const glob = new Bun.Glob("**/*");
|
|
44
52
|
for await (const filePath of glob.scan({ cwd: root, absolute: true })) {
|
|
@@ -46,7 +54,7 @@ async function precompressRoot(root: string, result: PrecompressArtifactsResult)
|
|
|
46
54
|
const bytes = await Bun.file(filePath).bytes();
|
|
47
55
|
const buffer = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength) as ArrayBuffer;
|
|
48
56
|
const gz = Bun.gzipSync(buffer, { level: GZIP_LEVEL });
|
|
49
|
-
const br = brotliCompress(buffer, path.extname(filePath).toLowerCase());
|
|
57
|
+
const br = brotliCompress(buffer, brotliQuality ?? brotliQualityOf(path.extname(filePath).toLowerCase()));
|
|
50
58
|
await Promise.all([Bun.write(`${filePath}.gz`, gz), Bun.write(`${filePath}.br`, br)]);
|
|
51
59
|
result.files += 1;
|
|
52
60
|
result.inputBytes += bytes.byteLength;
|
|
@@ -55,8 +63,11 @@ async function precompressRoot(root: string, result: PrecompressArtifactsResult)
|
|
|
55
63
|
}
|
|
56
64
|
}
|
|
57
65
|
|
|
58
|
-
function
|
|
59
|
-
|
|
66
|
+
function brotliQualityOf(ext: string): number {
|
|
67
|
+
return BROTLI_QUALITY_BY_EXT[ext as keyof typeof BROTLI_QUALITY_BY_EXT] ?? DEFAULT_BROTLI_QUALITY;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function brotliCompress(buffer: ArrayBuffer, quality: number): Buffer {
|
|
60
71
|
return zlib.brotliCompressSync(buffer, {
|
|
61
72
|
params: {
|
|
62
73
|
[zlib.constants.BROTLI_PARAM_QUALITY]: quality,
|
|
@@ -169,6 +169,7 @@ export class RouteClientBuilder {
|
|
|
169
169
|
plugins: [
|
|
170
170
|
await createBarrelImportsPlugin(this.#app, {
|
|
171
171
|
pipeAfter: reactFastRefresh ? RouteClientBuilder.normalizeNamedDefaultFunctionForFastRefresh : undefined,
|
|
172
|
+
external: VENDOR_SPECIFIERS,
|
|
172
173
|
}),
|
|
173
174
|
],
|
|
174
175
|
external: VENDOR_SPECIFIERS,
|
|
@@ -8,6 +8,7 @@ import { computeRouteSeedIndex, type RouteSeedIndex, saveRouteSeedIndex } from "
|
|
|
8
8
|
import type { App } from "../commandDecorators";
|
|
9
9
|
import { ClientEntriesBundler } from "./clientEntriesBundler";
|
|
10
10
|
import { CssCompiler, type ImportedStylesheet } from "./cssCompiler";
|
|
11
|
+
import { DictionaryAssetBuilder } from "./dictionaryAssetBuilder";
|
|
11
12
|
import { FontOptimizer } from "./fontOptimizer";
|
|
12
13
|
import { PagesBundleBuilder } from "./pagesBundleBuilder";
|
|
13
14
|
import { RouteClientBuilder } from "./routeClientBuilder";
|
|
@@ -56,6 +57,7 @@ export class SsrBaseArtifactBuilder {
|
|
|
56
57
|
|
|
57
58
|
const pageEntries = await resolveSsrPageEntriesForApp(this.#app, pageKeys);
|
|
58
59
|
const { cssCompiler, optimizedFonts, cssAssets } = await this.#buildStyleAssets();
|
|
60
|
+
const dictAssets = await new DictionaryAssetBuilder(this.#app, this.#absArtifactDir).build(akanConfig.i18n);
|
|
59
61
|
const pagesBundle = await new PagesBundleBuilder(this.#app, this.#command, pageEntries, this.#serverEnv).build();
|
|
60
62
|
this.#app.verbose(
|
|
61
63
|
`[base-artifact] pages bundle -> ${pagesBundle.bundlePath} (buildId=${pagesBundle.buildId}, splitting=${pagesBundle.splitting}, entry=${pagesBundle.entryBytes} bytes, outputs=${pagesBundle.outputCount}, chunks=${pagesBundle.chunkCount}, total=${pagesBundle.outputBytes} bytes)`,
|
|
@@ -75,6 +77,7 @@ export class SsrBaseArtifactBuilder {
|
|
|
75
77
|
rscRuntimeSsrManifest,
|
|
76
78
|
vendorMap,
|
|
77
79
|
cssAssets,
|
|
80
|
+
dictAssets,
|
|
78
81
|
pagesBundlePath:
|
|
79
82
|
this.#command === "build"
|
|
80
83
|
? path.relative(this.#absArtifactDir, pagesBundle.bundlePath)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { cp, mkdir, readdir, rm, writeFile } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import type { ServerPolicy } from "@akanjs/native/api";
|
|
4
3
|
import { toError } from "akanjs/common";
|
|
4
|
+
import type { ServerPolicy } from "akanjs/native/api";
|
|
5
5
|
import type { AkanAppConfig, NativeEnv } from "../akanConfig";
|
|
6
6
|
import type { App } from "../commandDecorators";
|
|
7
7
|
import type { PackageJson } from "../types";
|
package/mobile/nativeApi.ts
CHANGED
package/mobile/nativeApp.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import type { AkanNativeConfig } from "@akanjs/native/config";
|
|
3
2
|
import type { NativeEnv } from "akanjs";
|
|
4
3
|
import { toError } from "akanjs/common";
|
|
4
|
+
import type { AkanNativeConfig } from "akanjs/native/config";
|
|
5
5
|
import type { App } from "../commandDecorators";
|
|
6
6
|
import { Executor } from "../executors";
|
|
7
7
|
import { type DesktopArch, DesktopBin } from "./desktopBin";
|
package/mobile/nativeConfig.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import type { AkanNativeConfig } from "@akanjs/native/config";
|
|
4
3
|
import type {
|
|
5
4
|
AkanNativeDmgConfig,
|
|
6
5
|
AkanNativeTarget,
|
|
@@ -10,6 +9,7 @@ import type {
|
|
|
10
9
|
NativePermission,
|
|
11
10
|
} from "akanjs";
|
|
12
11
|
import { trimSlashes } from "akanjs/common";
|
|
12
|
+
import type { AkanNativeConfig } from "akanjs/native/config";
|
|
13
13
|
import type { DesktopServerBundle } from "./desktopServerStage";
|
|
14
14
|
import { type NativePlatform, resolveAppId } from "./mobileTarget";
|
|
15
15
|
import type { NativePluginFolder } from "./nativePluginFolders";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { mkdtemp, rm } from "node:fs/promises";
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import type { BundleInfo, UpdateManifest } from "@akanjs/native/api";
|
|
5
4
|
import type { NativeEnv } from "akanjs";
|
|
5
|
+
import type { BundleInfo, UpdateManifest } from "akanjs/native/api";
|
|
6
6
|
import { ExitCodeError } from "../commandDecorators/exitCodeError";
|
|
7
7
|
import type { MobilePlatform } from "./mobileTarget";
|
|
8
8
|
|
package/mobile/updateUploader.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readdir } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import type { UpdateEnvelope, UpdateManifest } from "@akanjs/native/api";
|
|
4
3
|
import { isRecord, trimTrailingSlashes } from "akanjs/common";
|
|
4
|
+
import type { UpdateEnvelope, UpdateManifest } from "akanjs/native/api";
|
|
5
5
|
import { UpdatePackFolder } from "./updatePackFolder";
|
|
6
6
|
|
|
7
7
|
/** What the uploader needs of an S3 client; Bun.S3Client is one. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@akanjs/devkit",
|
|
3
|
-
"version": "3.1.0",
|
|
3
|
+
"version": "3.1.1-rc.0",
|
|
4
4
|
"sourceType": "module",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"@earendil-works/pi-tui": "1.3.0",
|
|
49
49
|
"@inquirer/prompts": "^8.7.3",
|
|
50
50
|
"@tailwindcss/node": "^4.3.3",
|
|
51
|
-
"akanjs": "3.1.0",
|
|
51
|
+
"akanjs": "3.1.1-rc.0",
|
|
52
52
|
"chalk": "^6.0.1",
|
|
53
53
|
"commander": "^15.0.0",
|
|
54
54
|
"dayjs": "^1.11.23",
|
|
@@ -10,14 +10,16 @@ export interface BarrelImportsPluginOptions {
|
|
|
10
10
|
skipPath?: (absPath: string) => boolean;
|
|
11
11
|
/** Runs after the rewrite in the same onLoad (Bun's onLoad cannot fall through); null means no change. */
|
|
12
12
|
pipeAfter?: (source: string, args: { path: string }) => string | Promise<string | null> | null;
|
|
13
|
+
/** Barrels the bundle leaves external: a rewritten import would inline the modules the import map already serves. */
|
|
14
|
+
external?: readonly string[];
|
|
13
15
|
}
|
|
14
16
|
|
|
15
17
|
export const createBarrelImportsPlugin = async (
|
|
16
18
|
app: App,
|
|
17
|
-
{ skipPath = defaultSkipPath, pipeAfter }: BarrelImportsPluginOptions = {},
|
|
19
|
+
{ skipPath = defaultSkipPath, pipeAfter, external = [] }: BarrelImportsPluginOptions = {},
|
|
18
20
|
): Promise<BunPlugin> => {
|
|
19
21
|
const akanConfig = await app.getConfig();
|
|
20
|
-
const barrels = [...new Set(akanConfig.barrelImports)].filter(
|
|
22
|
+
const barrels = [...new Set(akanConfig.barrelImports)].filter((barrel) => barrel && !external.includes(barrel));
|
|
21
23
|
const analyzer = new BarrelAnalyzer({
|
|
22
24
|
resolvePackage: await createTsconfigPackageResolver(app),
|
|
23
25
|
});
|