@akanjs/devkit 3.0.0-beta.30 → 3.0.0-beta.31
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.test.ts +23 -0
- package/akanConfig/akanConfig.ts +13 -4
- package/mobile/nativeApp.test.ts +28 -2
- package/mobile/nativeApp.ts +27 -16
- package/mobile/nativeConfig.ts +1 -4
- package/package.json +2 -2
|
@@ -491,6 +491,29 @@ describe("AkanAppConfig", () => {
|
|
|
491
491
|
expect(config.native.targets.default).toMatchObject({ indexPath: "/explore", desktop: { server: true } });
|
|
492
492
|
});
|
|
493
493
|
|
|
494
|
+
test("a platform section may name its own indexPath, normalized like the section's", () => {
|
|
495
|
+
const config = new AkanAppConfig(
|
|
496
|
+
app,
|
|
497
|
+
[],
|
|
498
|
+
packageJson,
|
|
499
|
+
{
|
|
500
|
+
native: {
|
|
501
|
+
indexPath: "/mobile",
|
|
502
|
+
ios: { indexPath: "cockpit/" },
|
|
503
|
+
desktop: { indexPath: " / ", server: true },
|
|
504
|
+
},
|
|
505
|
+
},
|
|
506
|
+
baseDevEnv,
|
|
507
|
+
);
|
|
508
|
+
|
|
509
|
+
expect(config.native.targets.default).toMatchObject({
|
|
510
|
+
indexPath: "/mobile",
|
|
511
|
+
ios: { indexPath: "/cockpit" },
|
|
512
|
+
desktop: { indexPath: "/", server: true },
|
|
513
|
+
});
|
|
514
|
+
expect(config.native.targets.default.android).toBeUndefined();
|
|
515
|
+
});
|
|
516
|
+
|
|
494
517
|
test("a target takes the native section with its own fields over it: objects merge, lists and values replace", () => {
|
|
495
518
|
const config = new AkanAppConfig(
|
|
496
519
|
app,
|
package/akanConfig/akanConfig.ts
CHANGED
|
@@ -293,9 +293,11 @@ export class AkanAppConfig implements AppConfigResult {
|
|
|
293
293
|
} = merged;
|
|
294
294
|
const indexPath = normalizeIndexPath(rawIndexPath);
|
|
295
295
|
const deepLinks = normalizeDeepLinks(rawDeepLinks);
|
|
296
|
-
const { teamId: rawTeamId, ...iosRest } = merged.ios ?? {};
|
|
296
|
+
const { teamId: rawTeamId, ...iosRest } = AkanAppConfig.#platformIndexPath(merged.ios ?? {});
|
|
297
297
|
const teamId = rawTeamId?.trim();
|
|
298
|
-
const { sha256CertFingerprints: rawFingerprints, ...androidRest } =
|
|
298
|
+
const { sha256CertFingerprints: rawFingerprints, ...androidRest } = AkanAppConfig.#platformIndexPath(
|
|
299
|
+
merged.android ?? {},
|
|
300
|
+
);
|
|
299
301
|
const fingerprints = normalizeStringList(rawFingerprints);
|
|
300
302
|
const target: AkanNativeTarget = {
|
|
301
303
|
...rest,
|
|
@@ -307,6 +309,7 @@ export class AkanAppConfig implements AppConfigResult {
|
|
|
307
309
|
...(merged.android
|
|
308
310
|
? { android: { ...androidRest, ...(fingerprints ? { sha256CertFingerprints: fingerprints } : {}) } }
|
|
309
311
|
: {}),
|
|
312
|
+
...(merged.desktop ? { desktop: AkanAppConfig.#platformIndexPath(merged.desktop) } : {}),
|
|
310
313
|
appName: merged.appName ?? this.app.name,
|
|
311
314
|
appId: merged.appId ?? appId,
|
|
312
315
|
version: merged.version ?? version,
|
|
@@ -318,6 +321,11 @@ export class AkanAppConfig implements AppConfigResult {
|
|
|
318
321
|
);
|
|
319
322
|
return { appName, appId, ...(root.fileName ? { fileName: root.fileName } : {}), version, buildNum, targets };
|
|
320
323
|
}
|
|
324
|
+
static #platformIndexPath<T extends { indexPath?: string }>(section: T): T {
|
|
325
|
+
const { indexPath: rawIndexPath, ...rest } = section;
|
|
326
|
+
const indexPath = normalizeIndexPath(rawIndexPath);
|
|
327
|
+
return { ...rest, ...(indexPath ? { indexPath } : {}) } as T;
|
|
328
|
+
}
|
|
321
329
|
/** `own` over `base`: plain objects merge key by key, and any other value, a list included, replaces. */
|
|
322
330
|
static #overlay(base: AkanNativeSettings, own: AkanNativeSettings): AkanNativeSettings {
|
|
323
331
|
const merge = (kept: unknown, value: unknown, top: boolean, key: string): unknown => {
|
|
@@ -356,8 +364,9 @@ export class AkanAppConfig implements AppConfigResult {
|
|
|
356
364
|
"android",
|
|
357
365
|
"desktop",
|
|
358
366
|
],
|
|
359
|
-
ios: ["teamId", "infoPlist", "entitlements", "privacy", "files"],
|
|
367
|
+
ios: ["indexPath", "teamId", "infoPlist", "entitlements", "privacy", "files"],
|
|
360
368
|
android: [
|
|
369
|
+
"indexPath",
|
|
361
370
|
"sha256CertFingerprints",
|
|
362
371
|
"googleServices",
|
|
363
372
|
"push",
|
|
@@ -367,7 +376,7 @@ export class AkanAppConfig implements AppConfigResult {
|
|
|
367
376
|
"activity",
|
|
368
377
|
"files",
|
|
369
378
|
],
|
|
370
|
-
desktop: ["server", "recovery", "window", "screenCapture"],
|
|
379
|
+
desktop: ["indexPath", "server", "recovery", "window", "screenCapture"],
|
|
371
380
|
deepLinks: ["schemes", "domains"],
|
|
372
381
|
} as const;
|
|
373
382
|
//* Settings that moved when `mobile` became `native` are named with where they went, instead of "unknown".
|
package/mobile/nativeApp.test.ts
CHANGED
|
@@ -87,14 +87,40 @@ describe("NativeApp", () => {
|
|
|
87
87
|
});
|
|
88
88
|
|
|
89
89
|
test("opens a dev build on its target's home, the CSR shell under the locale", () => {
|
|
90
|
-
expect(new NativeApp(fakeApp(), target({ indexPath: "/explore" })).startPath("en")).toBe(
|
|
90
|
+
expect(new NativeApp(fakeApp(), target({ indexPath: "/explore" })).startPath("en", "ios")).toBe(
|
|
91
91
|
"/en/explore?csr=true&akanMobileTarget=default&akanMobileIndexPath=%2Fexplore",
|
|
92
92
|
);
|
|
93
|
-
expect(new NativeApp(fakeApp(), target({ name: "admin", basePath: "/admin/" })).startPath("ko")).toBe(
|
|
93
|
+
expect(new NativeApp(fakeApp(), target({ name: "admin", basePath: "/admin/" })).startPath("ko", "android")).toBe(
|
|
94
94
|
"/ko/admin?csr=true&akanMobileTarget=admin&akanMobileBasePath=admin",
|
|
95
95
|
);
|
|
96
96
|
});
|
|
97
97
|
|
|
98
|
+
test("a platform section's indexPath wins on that platform, and the others keep the target's", () => {
|
|
99
|
+
const app = new NativeApp(fakeApp(), target({ indexPath: "/mobile", desktop: { indexPath: "/", server: true } }));
|
|
100
|
+
|
|
101
|
+
expect(app.indexPath("ios")).toBe("/mobile");
|
|
102
|
+
expect(app.indexPath("android")).toBe("/mobile");
|
|
103
|
+
expect(app.indexPath("macos")).toBe("/");
|
|
104
|
+
expect(app.indexPath("windows")).toBe("/");
|
|
105
|
+
expect(app.startPath("en", "macos")).toBe("/en?csr=true&akanMobileTarget=default&akanMobileIndexPath=%2F");
|
|
106
|
+
expect(app.startPath("en", "ios")).toBe(
|
|
107
|
+
"/en/mobile?csr=true&akanMobileTarget=default&akanMobileIndexPath=%2Fmobile",
|
|
108
|
+
);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("assembles each platform's web root with that platform's indexPath", async () => {
|
|
112
|
+
const root = await makeTempRoot();
|
|
113
|
+
await writeText(path.join(root, "dist/csr/index.html"), "<html><head></head><body></body></html>");
|
|
114
|
+
const app = { ...fakeApp(), cwdPath: root, dist: { cwdPath: path.join(root, "dist") } } as unknown as App;
|
|
115
|
+
const nativeApp = new NativeApp(app, target({ indexPath: "/mobile", desktop: { indexPath: "/console" } }));
|
|
116
|
+
const injected = async () => await Bun.file(path.join(nativeApp.web.dir, "index.html")).text();
|
|
117
|
+
|
|
118
|
+
await nativeApp.assembleWeb("linux");
|
|
119
|
+
expect(await injected()).toContain('"indexPath":"/console"');
|
|
120
|
+
await nativeApp.assembleWeb("android");
|
|
121
|
+
expect(await injected()).toContain('"indexPath":"/mobile"');
|
|
122
|
+
});
|
|
123
|
+
|
|
98
124
|
test("builds its config from the app's locales and the plugins' native contributions", async () => {
|
|
99
125
|
const push: AkanPlugin = { name: "push-notification", native: { permission: "push", plugins: ["push"] } };
|
|
100
126
|
const app = new NativeApp(
|
package/mobile/nativeApp.ts
CHANGED
|
@@ -74,14 +74,24 @@ export class NativeApp {
|
|
|
74
74
|
return path.join(this.targetRoot, "dev", platform);
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
/** Where the app starts on `platform`: its platform section's `indexPath`, else the target's. */
|
|
78
|
+
indexPath(platform: NativePlatform) {
|
|
79
|
+
const { config } = this.target;
|
|
80
|
+
const section = platform === "ios" ? config.ios : platform === "android" ? config.android : config.desktop;
|
|
81
|
+
return section?.indexPath ?? config.indexPath;
|
|
82
|
+
}
|
|
83
|
+
|
|
77
84
|
/** The web root, from the production build the native commands run first. */
|
|
78
|
-
async assembleWeb() {
|
|
85
|
+
async assembleWeb(platform: NativePlatform) {
|
|
79
86
|
const dist = this.app.dist.cwdPath;
|
|
80
|
-
return await this.web.assemble(
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
87
|
+
return await this.web.assemble(
|
|
88
|
+
{ ...this.target.config, indexPath: this.indexPath(platform) },
|
|
89
|
+
{
|
|
90
|
+
html: path.join(dist, "csr", targetHtmlFilename(this.target.config)),
|
|
91
|
+
publicDir: path.join(dist, "public"),
|
|
92
|
+
fontsDir: path.join(dist, ".akan", "artifact", "fonts"),
|
|
93
|
+
},
|
|
94
|
+
);
|
|
85
95
|
}
|
|
86
96
|
|
|
87
97
|
/** Where a desktop build stages the executables it carries (akan.config.ts `bin`). */
|
|
@@ -168,7 +178,7 @@ export class NativeApp {
|
|
|
168
178
|
async build(platform: NativePlatform, { profile = "release", server, installer = false }: NativeBuildOptions = {}) {
|
|
169
179
|
NativeApp.#assertServerPlatform(platform, server);
|
|
170
180
|
if (installer && platform !== "windows") throw new Error(`An installer is built for Windows, not for ${platform}.`);
|
|
171
|
-
await this.assembleWeb();
|
|
181
|
+
await this.assembleWeb(platform);
|
|
172
182
|
const { api, config } = await this.prepare(platform, server);
|
|
173
183
|
return await api.build({
|
|
174
184
|
...this.#task(platform, config),
|
|
@@ -182,7 +192,7 @@ export class NativeApp {
|
|
|
182
192
|
{ device, teamId, profile = "debug", server }: NativeRunOptions & NativeBuildOptions = {},
|
|
183
193
|
) {
|
|
184
194
|
NativeApp.#assertServerPlatform(platform, server);
|
|
185
|
-
await this.assembleWeb();
|
|
195
|
+
await this.assembleWeb(platform);
|
|
186
196
|
const { api, config } = await this.prepare(platform, server);
|
|
187
197
|
return await api.run({
|
|
188
198
|
...this.#task(platform, config),
|
|
@@ -211,7 +221,7 @@ export class NativeApp {
|
|
|
211
221
|
wsPaths: [`${routes.prefix}${routes.websocketPrefix}`],
|
|
212
222
|
reversePorts: [Number(new URL(upstream).port)],
|
|
213
223
|
onLine: this.#appLine,
|
|
214
|
-
startPath: this.startPath(lang),
|
|
224
|
+
startPath: this.startPath(lang, platform),
|
|
215
225
|
...(device ? { device } : {}),
|
|
216
226
|
...(teamId ? { ios: { signing: { teamId } } } : {}),
|
|
217
227
|
});
|
|
@@ -242,7 +252,7 @@ export class NativeApp {
|
|
|
242
252
|
}
|
|
243
253
|
|
|
244
254
|
async releaseIos({ teamId, adHoc = false }: { teamId?: string; adHoc?: boolean } = {}) {
|
|
245
|
-
await this.assembleWeb();
|
|
255
|
+
await this.assembleWeb("ios");
|
|
246
256
|
const { api, config } = await this.prepare("ios");
|
|
247
257
|
return await api.release({
|
|
248
258
|
...this.#task("ios", config),
|
|
@@ -263,7 +273,7 @@ export class NativeApp {
|
|
|
263
273
|
) {
|
|
264
274
|
NativeApp.#assertServerPlatform(platform, server);
|
|
265
275
|
this.#assertUpdates();
|
|
266
|
-
await this.assembleWeb();
|
|
276
|
+
await this.assembleWeb(platform);
|
|
267
277
|
const { api, config } = await this.prepare(platform, server);
|
|
268
278
|
return await api.publishUpdate({
|
|
269
279
|
...this.#task(platform, config),
|
|
@@ -324,14 +334,14 @@ export class NativeApp {
|
|
|
324
334
|
|
|
325
335
|
async releaseAndroid({ formats = ["aab"] }: { formats?: ("aab" | "apk")[] } = {}) {
|
|
326
336
|
const signing = NativeApp.androidSigning();
|
|
327
|
-
await this.assembleWeb();
|
|
337
|
+
await this.assembleWeb("android");
|
|
328
338
|
const { api, config } = await this.prepare("android");
|
|
329
339
|
return await api.release({ ...this.#task("android", config), platform: "android", signing, formats });
|
|
330
340
|
}
|
|
331
341
|
|
|
332
342
|
/** An unsigned OTA update of this target's web bundle for `platform`, and the channel its binary follows. */
|
|
333
343
|
async packUpdate(platform: MobilePlatform, { out }: { out?: string } = {}) {
|
|
334
|
-
await this.assembleWeb();
|
|
344
|
+
await this.assembleWeb(platform);
|
|
335
345
|
const { api, config } = await this.prepare(platform);
|
|
336
346
|
if (!config.updates)
|
|
337
347
|
throw new Error(
|
|
@@ -370,12 +380,13 @@ export class NativeApp {
|
|
|
370
380
|
|
|
371
381
|
/** The first page of a dev build, the target's home as a release bundle opens it; the dev server answers the CSR
|
|
372
382
|
* shell only for `?csr=true`. */
|
|
373
|
-
startPath(lang: string) {
|
|
383
|
+
startPath(lang: string, platform: NativePlatform) {
|
|
374
384
|
const basePath = this.target.config.basePath?.replace(/^\/+|\/+$/g, "");
|
|
385
|
+
const indexPath = this.indexPath(platform);
|
|
375
386
|
const params = new URLSearchParams({ csr: "true", akanMobileTarget: this.target.name });
|
|
376
387
|
if (basePath) params.set("akanMobileBasePath", basePath);
|
|
377
|
-
if (
|
|
378
|
-
const home = [lang, basePath,
|
|
388
|
+
if (indexPath) params.set("akanMobileIndexPath", indexPath);
|
|
389
|
+
const home = [lang, basePath, indexPath]
|
|
379
390
|
.flatMap((part) => (part ?? "").split("/"))
|
|
380
391
|
.filter((segment) => segment.length > 0);
|
|
381
392
|
return `/${home.join("/")}?${params}`;
|
package/mobile/nativeConfig.ts
CHANGED
|
@@ -234,10 +234,7 @@ export class NativeConfig {
|
|
|
234
234
|
}
|
|
235
235
|
|
|
236
236
|
//? akanConfig refuses a target whose merged updates lack url or publicKey; the type still has them optional.
|
|
237
|
-
static #updates(
|
|
238
|
-
target: AkanNativeTarget,
|
|
239
|
-
env?: NativeEnv,
|
|
240
|
-
): NonNullable<AkanNativeConfig["updates"]> | undefined {
|
|
237
|
+
static #updates(target: AkanNativeTarget, env?: NativeEnv): NonNullable<AkanNativeConfig["updates"]> | undefined {
|
|
241
238
|
const { url, publicKey, channel = env, ...rest } = target.updates ?? {};
|
|
242
239
|
return url && publicKey ? { ...rest, url, publicKey, ...(channel ? { channel } : {}) } : undefined;
|
|
243
240
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@akanjs/devkit",
|
|
3
|
-
"version": "3.0.0-beta.
|
|
3
|
+
"version": "3.0.0-beta.31",
|
|
4
4
|
"sourceType": "module",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"@earendil-works/pi-tui": "0.86.1",
|
|
48
48
|
"@inquirer/prompts": "^8.4.3",
|
|
49
49
|
"@tailwindcss/node": "^4.3.0",
|
|
50
|
-
"akanjs": "3.0.0-beta.
|
|
50
|
+
"akanjs": "3.0.0-beta.31",
|
|
51
51
|
"chalk": "^5.6.2",
|
|
52
52
|
"commander": "^14.0.3",
|
|
53
53
|
"dayjs": "^1.11.20",
|