@akanjs/cli 3.0.0-beta.27 → 3.0.0-beta.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.build-stamp +1 -1
- package/{CodeAgent-h19atbr3.js → CodeAgent-7acv4ah8.js} +2 -2
- package/{CodeTui-46zygq8p.js → CodeTui-9tkh34mj.js} +1 -1
- package/{agent.command-rp505zxa.js → agent.command-q9bmqrag.js} +5 -5
- package/akanCode.js +2 -2
- package/{application.command-1p0cpt6y.js → application.command-xpnkyx4z.js} +70 -8
- package/{applicationBuildRunner-r6b9rkb8.js → applicationBuildRunner-w4917tje.js} +67 -22
- package/buildBatch.proc.js +3 -3
- package/{cloud.command-ps9dabbg.js → cloud.command-7yd624qd.js} +9 -9
- package/cloud.script-25vzkz7b.js +20 -0
- package/{code.command-78qw3q3f.js → code.command-fpx4gzmy.js} +6 -6
- package/commandManifest.json +1 -1
- package/{context.command-x5f7t3gk.js → context.command-snn1k5q3.js} +13 -13
- package/context.runner-nzaf8c98.js +21 -0
- package/{guideline.command-d5qmxx29.js → guideline.command-ene55fkr.js} +2 -2
- package/guidelines/conventions/conventions.instruction.md +27 -7
- package/guidelines/runtimeRule/runtimeRule.instruction.md +107 -1
- package/incrementalBuilder.proc.js +2 -2
- package/{index-118vq9dh.js → index-0ej0sr6m.js} +225 -46
- package/{index-hzy4pe24.js → index-0pqzrbpr.js} +2 -2
- package/{index-6dt6zft6.js → index-20r2g1t3.js} +4 -4
- package/{index-xfgdmm5d.js → index-2ctqegdm.js} +2 -2
- package/{index-d65fzxvd.js → index-2s2e5qnd.js} +1 -1
- package/{index-t02g6dpn.js → index-2z30v4k5.js} +3 -3
- package/{index-vxqe24mp.js → index-52n8xr63.js} +1 -1
- package/{index-tg09x14f.js → index-9j0kykc6.js} +5 -5
- package/{index-58cyna2s.js → index-b68zfsvz.js} +1 -1
- package/{index-2zcm7fjd.js → index-ewbek95g.js} +3 -3
- package/{index-bp9b1g2r.js → index-fy42hyb9.js} +7 -3
- package/{index-zazvn45w.js → index-genegfnt.js} +4 -4
- package/{index-da72pfdv.js → index-h9ed99ax.js} +7 -7
- package/{index-per2kqy9.js → index-k7809wmg.js} +2 -2
- package/{index-j9z8vg2n.js → index-n9704w6y.js} +4 -4
- package/{index-860zjnr1.js → index-nbg9bfnr.js} +1 -1
- package/{index-5mm3g24t.js → index-qtz88kh0.js} +2 -2
- package/{index-0n9q1f23.js → index-r8gp8zk3.js} +231 -98
- package/{index-ggfhz9ab.js → index-rbeahw6j.js} +3 -3
- package/{index-56xwsgyn.js → index-rrt9pnb4.js} +2 -2
- package/{index-v4hnkqz9.js → index-ss828r32.js} +1 -1
- package/index-tfe3711h.js +947 -0
- package/{index-5kjsr2pg.js → index-ttn00pr3.js} +38 -3
- package/{index-w5pet0nq.js → index-vt64c067.js} +11 -9
- package/index.js +21 -21
- package/{library.command-sv6g1h2g.js → library.command-tz7vnwea.js} +3 -3
- package/{localRegistry.command-zppa6w7q.js → localRegistry.command-f39yk7be.js} +8 -8
- package/{module.command-ccdeder9.js → module.command-qhc9ns0s.js} +5 -5
- package/{package.command-xmw605b1.js → package.command-z3epnvas.js} +3 -3
- package/package.json +2 -2
- package/{page.command-ky7vafsn.js → page.command-exd2vakz.js} +3 -3
- package/{primitive.command-bh6j8nfs.js → primitive.command-0w6cha4j.js} +6 -6
- package/{quality.command-rqqrpe98.js → quality.command-je4kc9yv.js} +2 -2
- package/{repair.command-n9nsm9qe.js → repair.command-knvwfgtg.js} +5 -5
- package/{scalar.command-t09gmck8.js → scalar.command-60nxfchc.js} +4 -4
- package/{subspace.command-gx2cjpnv.js → subspace.command-0e53tvxk.js} +4 -4
- package/templates/server.ts +1 -1
- package/{tunnel.command-4g95vhsn.js → tunnel.command-7m2kxtat.js} +2 -2
- package/{workflow.command-b91ph8sx.js → workflow.command-j5tk13ds.js} +10 -10
- package/{workspace.command-1r29qzzk.js → workspace.command-wypqk2zb.js} +20 -20
- package/cloud.script-wrw1kz6k.js +0 -20
- package/context.runner-nfs8ksh3.js +0 -21
- package/index-y736f42m.js +0 -556
package/index-y736f42m.js
DELETED
|
@@ -1,556 +0,0 @@
|
|
|
1
|
-
// @bun
|
|
2
|
-
import {
|
|
3
|
-
Executor
|
|
4
|
-
} from "./index-0n9q1f23.js";
|
|
5
|
-
|
|
6
|
-
// pkgs/@akanjs/devkit/mobile/mobileTarget.ts
|
|
7
|
-
var trimSlashes = (value) => value.replace(/^\/+|\/+$/g, "");
|
|
8
|
-
var getMobileTargets = async (app) => {
|
|
9
|
-
const config = await app.getConfig();
|
|
10
|
-
return Object.entries(config.mobile.targets).map(([name, target]) => ({ name, config: target }));
|
|
11
|
-
};
|
|
12
|
-
var getMobileTargetChoices = async (app) => {
|
|
13
|
-
const config = await app.getConfig();
|
|
14
|
-
const targetNames = Object.keys(config.mobile.targets);
|
|
15
|
-
if (targetNames.length > 0)
|
|
16
|
-
return targetNames;
|
|
17
|
-
return [...config.basePaths];
|
|
18
|
-
};
|
|
19
|
-
var resolveMobileTargetByBasePath = (targets, basePath) => {
|
|
20
|
-
const normalizedBasePath = trimSlashes(basePath);
|
|
21
|
-
const byBasePath = targets.find(({ config }) => config.basePath !== undefined && trimSlashes(config.basePath) === normalizedBasePath);
|
|
22
|
-
if (byBasePath)
|
|
23
|
-
return byBasePath;
|
|
24
|
-
const [template] = targets;
|
|
25
|
-
if (!template)
|
|
26
|
-
return;
|
|
27
|
-
return {
|
|
28
|
-
name: normalizedBasePath,
|
|
29
|
-
config: { ...template.config, name: normalizedBasePath, basePath: normalizedBasePath }
|
|
30
|
-
};
|
|
31
|
-
};
|
|
32
|
-
var resolveMobileTargets = async (app, selection) => {
|
|
33
|
-
const config = await app.getConfig();
|
|
34
|
-
const targets = await getMobileTargets(app);
|
|
35
|
-
if (targets.length === 0)
|
|
36
|
-
throw new Error(`No mobile targets configured for ${app.name}`);
|
|
37
|
-
if (!selection && targets.length === 1)
|
|
38
|
-
return targets;
|
|
39
|
-
if (!selection) {
|
|
40
|
-
const choices = await getMobileTargetChoices(app);
|
|
41
|
-
if (choices.length === 1)
|
|
42
|
-
return resolveMobileTargets(app, choices[0]);
|
|
43
|
-
throw new Error(`Multiple mobile targets found for ${app.name}. Pass --target <${choices.join("|")}|all>.`);
|
|
44
|
-
}
|
|
45
|
-
if (selection === "all") {
|
|
46
|
-
if (Object.keys(config.mobile.targets).length > 1)
|
|
47
|
-
return targets;
|
|
48
|
-
const basePaths = [...config.basePaths];
|
|
49
|
-
if (basePaths.length > 1) {
|
|
50
|
-
return basePaths.flatMap((basePath) => {
|
|
51
|
-
const resolved = resolveMobileTargetByBasePath(targets, basePath);
|
|
52
|
-
return resolved ? [resolved] : [];
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
return targets;
|
|
56
|
-
}
|
|
57
|
-
const target = targets.find((candidate) => candidate.name === selection);
|
|
58
|
-
if (target)
|
|
59
|
-
return [target];
|
|
60
|
-
const basePathTarget = resolveMobileTargetByBasePath(targets, selection);
|
|
61
|
-
if (basePathTarget && config.basePaths.has(trimSlashes(selection)))
|
|
62
|
-
return [basePathTarget];
|
|
63
|
-
const choices = await getMobileTargetChoices(app);
|
|
64
|
-
throw new Error(`Mobile target '${selection}' was not found. Available: ${choices.join(", ")}`);
|
|
65
|
-
};
|
|
66
|
-
var targetHtmlFilename = (target) => {
|
|
67
|
-
const basePath = trimSlashes(target.basePath ?? "");
|
|
68
|
-
return basePath ? `${basePath}.html` : "index.html";
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
// pkgs/@akanjs/devkit/mobile/nativeApp.ts
|
|
72
|
-
import { mkdir as mkdir2 } from "fs/promises";
|
|
73
|
-
import path4 from "path";
|
|
74
|
-
|
|
75
|
-
// pkgs/@akanjs/devkit/mobile/nativeApi.ts
|
|
76
|
-
import path from "path";
|
|
77
|
-
|
|
78
|
-
class NativeApi {
|
|
79
|
-
static supportedMajor = 0;
|
|
80
|
-
static entryOf(appDir) {
|
|
81
|
-
const workspaceEntry = NativeApi.#resolve("@akanjs/native/api", appDir);
|
|
82
|
-
if (workspaceEntry)
|
|
83
|
-
return workspaceEntry;
|
|
84
|
-
const akanjsPackage = NativeApi.#resolve("akanjs/package.json", appDir);
|
|
85
|
-
if (!akanjsPackage)
|
|
86
|
-
throw new Error(`akanjs is not installed for ${appDir}; the native build ships inside it.`);
|
|
87
|
-
return path.join(path.dirname(akanjsPackage), "vendor", "@akanjs", "native", "packages", "cli", "src", "api.ts");
|
|
88
|
-
}
|
|
89
|
-
static async load(appDir) {
|
|
90
|
-
const entry = NativeApi.entryOf(appDir);
|
|
91
|
-
if (!await Bun.file(entry).exists())
|
|
92
|
-
throw new Error(`The installed akanjs has no native build at ${entry}; update akanjs.`);
|
|
93
|
-
const api = await import(entry);
|
|
94
|
-
const major = Number(api.API_VERSION.split(".")[0]);
|
|
95
|
-
if (major !== NativeApi.supportedMajor)
|
|
96
|
-
throw new Error(`The native build API is ${api.API_VERSION}, and this akan CLI speaks ${NativeApi.supportedMajor}.x; install matching versions of akanjs and @akanjs/cli.`);
|
|
97
|
-
return api;
|
|
98
|
-
}
|
|
99
|
-
static #resolve(specifier, from) {
|
|
100
|
-
try {
|
|
101
|
-
return Bun.resolveSync(specifier, from);
|
|
102
|
-
} catch {
|
|
103
|
-
return null;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// pkgs/@akanjs/devkit/mobile/nativeAppLine.ts
|
|
109
|
-
class NativeAppLine {
|
|
110
|
-
static #page = /^\[page(\+)?(?:#(\d+))? (trace|verbose|debug|log|info|warn|error)\] ?(.*)$/;
|
|
111
|
-
static #logcat = /^([VDIWEFA])\/([^(]+?)\s*\(\s*\d+\):\s?(.*)$/;
|
|
112
|
-
static #named = /^\[([^\]\s]+)\] ([\s\S]*)$/;
|
|
113
|
-
static #logcatLevels = {
|
|
114
|
-
V: "verbose",
|
|
115
|
-
D: "debug",
|
|
116
|
-
I: "info",
|
|
117
|
-
W: "warn",
|
|
118
|
-
E: "error",
|
|
119
|
-
F: "error",
|
|
120
|
-
A: "error"
|
|
121
|
-
};
|
|
122
|
-
static read(line, { verbose = false } = {}) {
|
|
123
|
-
const logcat = NativeAppLine.#logcat.exec(line);
|
|
124
|
-
if (logcat) {
|
|
125
|
-
const [, priority = "I", tag = "", text = ""] = logcat;
|
|
126
|
-
const level = NativeAppLine.#logcatLevels[priority] ?? "info";
|
|
127
|
-
if (tag === "AkanNativeConsole")
|
|
128
|
-
return NativeAppLine.#pageLine(level, text, { continuation: false });
|
|
129
|
-
if (tag === "AkanNative")
|
|
130
|
-
return NativeAppLine.#hostLine(`[akan-native] ${text}`, level);
|
|
131
|
-
return { level, message: `[${tag}] ${text}` };
|
|
132
|
-
}
|
|
133
|
-
const page = NativeAppLine.#page.exec(line);
|
|
134
|
-
if (page) {
|
|
135
|
-
const [, continuation, window, level = "info", text = ""] = page;
|
|
136
|
-
return NativeAppLine.#pageLine(level === "log" ? "info" : level, text, {
|
|
137
|
-
continuation: continuation === "+",
|
|
138
|
-
window
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
|
-
if (line.startsWith("[akan-native"))
|
|
142
|
-
return NativeAppLine.#hostLine(line, "info");
|
|
143
|
-
return { level: verbose ? "info" : "debug", message: line };
|
|
144
|
-
}
|
|
145
|
-
static #pageLine(level, text, { continuation, window }) {
|
|
146
|
-
if (continuation)
|
|
147
|
-
return { level, message: ` ${text}` };
|
|
148
|
-
const named = NativeAppLine.#named.exec(text);
|
|
149
|
-
const scope = `page${window ? `#${window}` : ""}${named ? `:${named[1]}` : ""}`;
|
|
150
|
-
return { level, message: `[${scope}] ${named ? named[2] : text}` };
|
|
151
|
-
}
|
|
152
|
-
static #hostLine(line, level) {
|
|
153
|
-
return { level: /\bpages from\b/.test(line) ? "debug" : level, message: line };
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// pkgs/@akanjs/devkit/mobile/nativeConfig.ts
|
|
158
|
-
import path2 from "path";
|
|
159
|
-
|
|
160
|
-
// pkgs/@akanjs/devkit/mobile/usageDescriptions.ts
|
|
161
|
-
import { capitalize } from "akanjs/common";
|
|
162
|
-
var iosUsageDescriptionKeys = {
|
|
163
|
-
cameraUsageDescription: ["NSCameraUsageDescription"],
|
|
164
|
-
photoAddUsageDescription: ["NSPhotoLibraryAddUsageDescription"],
|
|
165
|
-
photoUsageDescription: ["NSPhotoLibraryUsageDescription"],
|
|
166
|
-
contactsUsageDescription: ["NSContactsUsageDescription"],
|
|
167
|
-
locationAlwaysUsageDescription: ["NSLocationAlwaysAndWhenInUseUsageDescription", "NSLocationAlwaysUsageDescription"],
|
|
168
|
-
locationWhenInUseUsageDescription: ["NSLocationWhenInUseUsageDescription"],
|
|
169
|
-
microphoneUsageDescription: ["NSMicrophoneUsageDescription"],
|
|
170
|
-
speechRecognitionUsageDescription: ["NSSpeechRecognitionUsageDescription"]
|
|
171
|
-
};
|
|
172
|
-
var toIosInfoPlistUsageDescriptions = (descriptions) => Object.fromEntries(Object.entries(descriptions).flatMap(([key, value]) => {
|
|
173
|
-
const plistKeys = Object.hasOwn(iosUsageDescriptionKeys, key) ? iosUsageDescriptionKeys[key] : [`NS${capitalize(key)}`];
|
|
174
|
-
return plistKeys.map((plistKey) => [plistKey, value]);
|
|
175
|
-
}));
|
|
176
|
-
|
|
177
|
-
// pkgs/@akanjs/devkit/mobile/nativeConfig.ts
|
|
178
|
-
class NativeConfig {
|
|
179
|
-
static basePlugins = [
|
|
180
|
-
"app",
|
|
181
|
-
"app-state",
|
|
182
|
-
"browser",
|
|
183
|
-
"opener",
|
|
184
|
-
"auth-session",
|
|
185
|
-
"device",
|
|
186
|
-
"dialog",
|
|
187
|
-
"keyboard",
|
|
188
|
-
"preferences",
|
|
189
|
-
"secure-storage",
|
|
190
|
-
"haptics"
|
|
191
|
-
];
|
|
192
|
-
static builtinContributions = {
|
|
193
|
-
camera: {
|
|
194
|
-
permission: "camera",
|
|
195
|
-
plugins: ["camera"],
|
|
196
|
-
usageDescriptions: {
|
|
197
|
-
cameraUsageDescription: "$(PRODUCT_NAME) requires access to the camera to take photos.",
|
|
198
|
-
photoAddUsageDescription: "$(PRODUCT_NAME) requires access to the photo library to take photos.",
|
|
199
|
-
photoUsageDescription: "$(PRODUCT_NAME) requires access to the photo library to take photos."
|
|
200
|
-
}
|
|
201
|
-
},
|
|
202
|
-
contacts: {
|
|
203
|
-
permission: "contacts",
|
|
204
|
-
plugins: ["contacts"],
|
|
205
|
-
usageDescriptions: {
|
|
206
|
-
contactsUsageDescription: "$(PRODUCT_NAME) requires access to the contacts to find people you know."
|
|
207
|
-
}
|
|
208
|
-
},
|
|
209
|
-
location: {
|
|
210
|
-
permission: "location",
|
|
211
|
-
plugins: ["geolocation"],
|
|
212
|
-
usageDescriptions: {
|
|
213
|
-
locationAlwaysUsageDescription: "$(PRODUCT_NAME) requires access to the location to get the user's location.",
|
|
214
|
-
locationWhenInUseUsageDescription: "$(PRODUCT_NAME) requires access to the location to get the user's location."
|
|
215
|
-
}
|
|
216
|
-
},
|
|
217
|
-
push: { permission: "push", plugins: ["push"] },
|
|
218
|
-
speech: { permission: "speech" }
|
|
219
|
-
};
|
|
220
|
-
static build({ appPath, target, webDir, contributions, locales }) {
|
|
221
|
-
const warnings = [];
|
|
222
|
-
const applied = (target.permissions ?? []).flatMap((permission) => {
|
|
223
|
-
const claimed = contributions.filter((contribution) => contribution.permission === permission);
|
|
224
|
-
const used = claimed.length ? claimed : [NativeConfig.builtinContributions[permission]];
|
|
225
|
-
if (!used.some((contribution) => contribution.plugins?.length))
|
|
226
|
-
warnings.push(`Permission '${permission}' has no native plugin yet; the app ships without it.`);
|
|
227
|
-
return used;
|
|
228
|
-
});
|
|
229
|
-
const abs = (relative) => path2.resolve(appPath, relative);
|
|
230
|
-
const plugins = [
|
|
231
|
-
...new Set([
|
|
232
|
-
...NativeConfig.basePlugins,
|
|
233
|
-
...applied.flatMap((contribution) => contribution.plugins ?? []),
|
|
234
|
-
...target.native?.plugins ?? []
|
|
235
|
-
])
|
|
236
|
-
];
|
|
237
|
-
const usageDescriptions = Object.fromEntries(Object.entries(toIosInfoPlistUsageDescriptions(Object.assign({}, ...applied.map((contribution) => contribution.usageDescriptions ?? {})))).map(([key, text]) => [key, text.replaceAll("$(PRODUCT_NAME)", target.appName)]));
|
|
238
|
-
const ios = NativeConfig.#compact({
|
|
239
|
-
infoPlist: NativeConfig.#merged(applied.map((c) => c.infoPlist), target.native?.ios?.infoPlist),
|
|
240
|
-
entitlements: NativeConfig.#merged(applied.map((c) => c.entitlements), target.native?.ios?.entitlements)
|
|
241
|
-
});
|
|
242
|
-
const manifest = [
|
|
243
|
-
...[...new Set(applied.flatMap((c) => c.androidPermissions ?? []))].map((name) => `<uses-permission android:name="android.permission.${name}" />`),
|
|
244
|
-
...[...new Set(applied.flatMap((c) => c.androidFeatures ?? []))].map((name) => `<uses-feature android:name="${name}" android:required="false" />`),
|
|
245
|
-
...target.native?.android?.manifest ?? []
|
|
246
|
-
];
|
|
247
|
-
const android = NativeConfig.#compact({
|
|
248
|
-
manifest,
|
|
249
|
-
application: target.native?.android?.application ?? [],
|
|
250
|
-
activity: target.native?.android?.activity ?? []
|
|
251
|
-
});
|
|
252
|
-
const resources = Object.entries(target.files ?? {}).map(([to, from]) => ({ from: abs(from), to }));
|
|
253
|
-
const native = NativeConfig.#compact({ ios, android, resources });
|
|
254
|
-
const googleServices = target.native?.android?.googleServices;
|
|
255
|
-
const config = {
|
|
256
|
-
app: {
|
|
257
|
-
id: target.appId,
|
|
258
|
-
name: target.appName,
|
|
259
|
-
fileName: target.fileName ?? NativeConfig.fileNameOf(path2.basename(appPath)),
|
|
260
|
-
version: target.version,
|
|
261
|
-
build: target.buildNum
|
|
262
|
-
},
|
|
263
|
-
web: { dir: webDir },
|
|
264
|
-
plugins,
|
|
265
|
-
capabilities: [
|
|
266
|
-
{
|
|
267
|
-
identifier: "app",
|
|
268
|
-
description: `The plugins ${target.appName} ships, each with its default permissions`,
|
|
269
|
-
permissions: plugins.map((plugin) => `${plugin}:default`)
|
|
270
|
-
}
|
|
271
|
-
],
|
|
272
|
-
...Object.keys(usageDescriptions).length ? { usageDescriptions } : {},
|
|
273
|
-
...target.deepLinks?.schemes?.length || target.deepLinks?.domains?.length ? { deepLinks: NativeConfig.#deepLinks(target, locales) } : {},
|
|
274
|
-
...native ? { native } : {},
|
|
275
|
-
android: { debugAppIdSuffix: ".debug", ...googleServices ? { googleServices: abs(googleServices) } : {} },
|
|
276
|
-
keyboard: { resize: "none" },
|
|
277
|
-
...target.assets?.icon ? { icon: abs(target.assets.icon) } : {},
|
|
278
|
-
...target.assets?.splash ? { splash: { image: abs(target.assets.splash) } } : {}
|
|
279
|
-
};
|
|
280
|
-
return { config, warnings };
|
|
281
|
-
}
|
|
282
|
-
static fileNameOf(name) {
|
|
283
|
-
return name.replace(/[^A-Za-z0-9._-]/g, "-").replace(/^[-.]+|[-.]+$/g, "") || "app";
|
|
284
|
-
}
|
|
285
|
-
static #compact(value) {
|
|
286
|
-
const kept = Object.entries(value).filter(([, entry]) => Array.isArray(entry) ? entry.length > 0 : entry && typeof entry === "object" ? Object.keys(entry).length > 0 : entry !== undefined);
|
|
287
|
-
return kept.length ? Object.fromEntries(kept) : undefined;
|
|
288
|
-
}
|
|
289
|
-
static #merged(contributed, own) {
|
|
290
|
-
return Object.assign({}, ...contributed, own);
|
|
291
|
-
}
|
|
292
|
-
static #deepLinks(target, locales) {
|
|
293
|
-
const basePath = target.basePath?.replace(/^\/+|\/+$/g, "");
|
|
294
|
-
const pathPrefixes = basePath ? locales.map((locale) => `/${locale}/${basePath}`) : undefined;
|
|
295
|
-
return {
|
|
296
|
-
...target.deepLinks?.schemes?.length ? { schemes: target.deepLinks.schemes } : {},
|
|
297
|
-
...target.deepLinks?.domains?.length ? { domains: target.deepLinks.domains.map((host) => pathPrefixes ? { host, pathPrefixes } : host) } : {}
|
|
298
|
-
};
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
// pkgs/@akanjs/devkit/mobile/nativeWebDir.ts
|
|
303
|
-
import { cp, mkdir, rm, stat } from "fs/promises";
|
|
304
|
-
import path3 from "path";
|
|
305
|
-
|
|
306
|
-
class NativeWebDir {
|
|
307
|
-
dir;
|
|
308
|
-
static reservedEntry = "__akan_native";
|
|
309
|
-
constructor(dir) {
|
|
310
|
-
this.dir = dir;
|
|
311
|
-
}
|
|
312
|
-
static injectTarget(html, target) {
|
|
313
|
-
if (html.includes(NativeWebDir.#targetTag))
|
|
314
|
-
return html;
|
|
315
|
-
const basePath = target.basePath?.replace(/^\/+|\/+$/g, "") ?? "";
|
|
316
|
-
const script = `${NativeWebDir.#targetTag}${JSON.stringify({
|
|
317
|
-
name: target.name,
|
|
318
|
-
basePath,
|
|
319
|
-
indexPath: target.indexPath
|
|
320
|
-
})};</script>`;
|
|
321
|
-
const head = /<head(?:\s[^>]*)?>/i.exec(html);
|
|
322
|
-
if (!head)
|
|
323
|
-
return `${script}
|
|
324
|
-
${html}`;
|
|
325
|
-
const at = head.index + head[0].length;
|
|
326
|
-
return `${html.slice(0, at)}
|
|
327
|
-
${script}${html.slice(at)}`;
|
|
328
|
-
}
|
|
329
|
-
static #targetTag = "<script>window.__AKAN_MOBILE_TARGET__=";
|
|
330
|
-
async assemble(target, { html, publicDir, fontsDir }) {
|
|
331
|
-
if (!await Bun.file(html).exists())
|
|
332
|
-
throw new Error(`CSR html for mobile target '${target.name}' not found: ${html}`);
|
|
333
|
-
await rm(this.dir, { recursive: true, force: true });
|
|
334
|
-
await mkdir(this.dir, { recursive: true });
|
|
335
|
-
if (publicDir && await NativeWebDir.#isDir(publicDir))
|
|
336
|
-
await cp(publicDir, this.dir, { recursive: true });
|
|
337
|
-
if (fontsDir && await NativeWebDir.#isDir(fontsDir))
|
|
338
|
-
await cp(fontsDir, path3.join(this.dir, "_akan", "fonts"), { recursive: true });
|
|
339
|
-
if (await NativeWebDir.#isDir(path3.join(this.dir, NativeWebDir.reservedEntry)))
|
|
340
|
-
throw new Error(`public/${NativeWebDir.reservedEntry} is reserved for the native runtime.`);
|
|
341
|
-
await Bun.write(path3.join(this.dir, "index.html"), NativeWebDir.injectTarget(await Bun.file(html).text(), target));
|
|
342
|
-
const files = await Array.fromAsync(new Bun.Glob("**/*").scan({ cwd: this.dir, onlyFiles: true, dot: true }));
|
|
343
|
-
return files.map((file) => file.split(path3.sep).join("/")).sort();
|
|
344
|
-
}
|
|
345
|
-
static async#isDir(dir) {
|
|
346
|
-
try {
|
|
347
|
-
return (await stat(dir)).isDirectory();
|
|
348
|
-
} catch {
|
|
349
|
-
return false;
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
// pkgs/@akanjs/devkit/mobile/nativeApp.ts
|
|
355
|
-
class NativeApp {
|
|
356
|
-
app;
|
|
357
|
-
target;
|
|
358
|
-
targetRoot;
|
|
359
|
-
web;
|
|
360
|
-
#boot = null;
|
|
361
|
-
constructor(app, target) {
|
|
362
|
-
this.app = app;
|
|
363
|
-
this.target = target;
|
|
364
|
-
this.targetRoot = path4.join(app.cwdPath, ".akan", "mobile", target.name);
|
|
365
|
-
this.web = new NativeWebDir(path4.join(this.targetRoot, "web"));
|
|
366
|
-
}
|
|
367
|
-
outDir(platform) {
|
|
368
|
-
return path4.join(this.targetRoot, "native", platform);
|
|
369
|
-
}
|
|
370
|
-
async assembleWeb() {
|
|
371
|
-
const dist = this.app.dist.cwdPath;
|
|
372
|
-
return await this.web.assemble(this.target.config, {
|
|
373
|
-
html: path4.join(dist, "csr", targetHtmlFilename(this.target.config)),
|
|
374
|
-
publicDir: path4.join(dist, "public"),
|
|
375
|
-
fontsDir: path4.join(dist, ".akan", "artifact", "fonts")
|
|
376
|
-
});
|
|
377
|
-
}
|
|
378
|
-
async config() {
|
|
379
|
-
const [appConfig, plugins] = await Promise.all([this.app.getConfig(), this.app.collectPlugins()]);
|
|
380
|
-
return NativeConfig.build({
|
|
381
|
-
appPath: this.app.cwdPath,
|
|
382
|
-
target: this.target.config,
|
|
383
|
-
webDir: this.web.dir,
|
|
384
|
-
contributions: plugins.flatMap((plugin) => plugin.native ? [plugin.native] : []),
|
|
385
|
-
locales: appConfig.i18n.locales
|
|
386
|
-
});
|
|
387
|
-
}
|
|
388
|
-
async prepare() {
|
|
389
|
-
const [api, { config, warnings }] = await Promise.all([NativeApi.load(this.app.cwdPath), this.config()]);
|
|
390
|
-
for (const warning of warnings)
|
|
391
|
-
this.app.logger.warn(warning);
|
|
392
|
-
const problems = api.validateConfig(config, { appDir: this.app.cwdPath });
|
|
393
|
-
if (problems.length)
|
|
394
|
-
throw new Error(`Mobile target '${this.target.name}' makes an invalid native config:
|
|
395
|
-
- ${problems.join(`
|
|
396
|
-
- `)}`);
|
|
397
|
-
return { api, config };
|
|
398
|
-
}
|
|
399
|
-
#appLine = (line) => {
|
|
400
|
-
const read = NativeAppLine.read(line, { verbose: Executor.verbose });
|
|
401
|
-
if (this.#boot)
|
|
402
|
-
this.#boot.lines.push(read);
|
|
403
|
-
else
|
|
404
|
-
this.app.logger[read.level](read.message);
|
|
405
|
-
};
|
|
406
|
-
#task(platform, config) {
|
|
407
|
-
return {
|
|
408
|
-
appDir: this.app.cwdPath,
|
|
409
|
-
config,
|
|
410
|
-
platform,
|
|
411
|
-
outDir: this.outDir(platform),
|
|
412
|
-
envFiles: false,
|
|
413
|
-
skipWebBuild: true,
|
|
414
|
-
log: (event) => {
|
|
415
|
-
if (event.level === "error")
|
|
416
|
-
this.app.logger.error(event.message);
|
|
417
|
-
else if (event.level === "warn")
|
|
418
|
-
this.app.logger.warn(event.message);
|
|
419
|
-
else if (this.#boot)
|
|
420
|
-
this.#boot.steps.push(event.message);
|
|
421
|
-
else if (event.level === "tool")
|
|
422
|
-
this.app.logger.verbose(event.message);
|
|
423
|
-
else
|
|
424
|
-
this.app.logger.info(event.message);
|
|
425
|
-
}
|
|
426
|
-
};
|
|
427
|
-
}
|
|
428
|
-
async build(platform, { profile = "release" } = {}) {
|
|
429
|
-
await this.assembleWeb();
|
|
430
|
-
const { api, config } = await this.prepare();
|
|
431
|
-
return await api.build({ ...this.#task(platform, config), profile });
|
|
432
|
-
}
|
|
433
|
-
async run(platform, { device, teamId, profile = "debug" } = {}) {
|
|
434
|
-
await this.assembleWeb();
|
|
435
|
-
const { api, config } = await this.prepare();
|
|
436
|
-
return await api.run({
|
|
437
|
-
...this.#task(platform, config),
|
|
438
|
-
profile,
|
|
439
|
-
onLine: this.#appLine,
|
|
440
|
-
...device ? { device } : {},
|
|
441
|
-
...teamId ? { ios: { signing: { teamId } } } : {}
|
|
442
|
-
});
|
|
443
|
-
}
|
|
444
|
-
async dev(platform, { upstream, lang, device, teamId }) {
|
|
445
|
-
const startedAt = performance.now();
|
|
446
|
-
const boot = { steps: [], lines: [] };
|
|
447
|
-
this.#boot = boot;
|
|
448
|
-
try {
|
|
449
|
-
await mkdir2(this.web.dir, { recursive: true });
|
|
450
|
-
const [{ api, config }, { api: routes }] = await Promise.all([this.prepare(), this.app.getConfig()]);
|
|
451
|
-
const session = await api.dev({
|
|
452
|
-
...this.#task(platform, config),
|
|
453
|
-
upstream,
|
|
454
|
-
hmrPath: "/_akan/hmr",
|
|
455
|
-
wsPaths: [`${routes.prefix}${routes.websocketPrefix}`],
|
|
456
|
-
onLine: this.#appLine,
|
|
457
|
-
startPath: this.startPath(lang),
|
|
458
|
-
...device ? { device } : {},
|
|
459
|
-
...teamId ? { ios: { signing: { teamId } } } : {}
|
|
460
|
-
});
|
|
461
|
-
this.#boot = null;
|
|
462
|
-
this.app.logger.info(this.readyLine(platform, { upstream, device: session.device, startedAt }));
|
|
463
|
-
for (const step of [...boot.steps, `pages through ${session.gateway}`])
|
|
464
|
-
this.app.logger.debug(step);
|
|
465
|
-
for (const line of boot.lines)
|
|
466
|
-
this.app.logger[line.level](line.message);
|
|
467
|
-
return session;
|
|
468
|
-
} catch (error) {
|
|
469
|
-
this.#boot = null;
|
|
470
|
-
for (const step of boot.steps)
|
|
471
|
-
this.app.logger.info(step);
|
|
472
|
-
for (const line of boot.lines)
|
|
473
|
-
this.app.logger[line.level === "debug" ? "info" : line.level](line.message);
|
|
474
|
-
throw error;
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
readyLine(platform, { upstream, device, startedAt }) {
|
|
478
|
-
const label = platform === "ios" || platform === "android" ? platform : "desktop";
|
|
479
|
-
const target = this.target.name === "default" ? "" : `/${this.target.name}`;
|
|
480
|
-
const seconds = ((performance.now() - startedAt) / 1000).toFixed(1);
|
|
481
|
-
return [`${this.app.name}${target} ${label} ready`, ...device ? [device.name] : [], upstream, `${seconds}s`].join(" \xB7 ");
|
|
482
|
-
}
|
|
483
|
-
async releaseIos({ teamId, adHoc = false } = {}) {
|
|
484
|
-
await this.assembleWeb();
|
|
485
|
-
const { api, config } = await this.prepare();
|
|
486
|
-
return await api.release({
|
|
487
|
-
...this.#task("ios", config),
|
|
488
|
-
platform: "ios",
|
|
489
|
-
signing: { ...teamId ? { teamId } : {}, ...adHoc ? { distribution: "ad-hoc" } : {} }
|
|
490
|
-
});
|
|
491
|
-
}
|
|
492
|
-
async releaseAndroid({ formats = ["aab"] } = {}) {
|
|
493
|
-
const signing = NativeApp.androidSigning();
|
|
494
|
-
await this.assembleWeb();
|
|
495
|
-
const { api, config } = await this.prepare();
|
|
496
|
-
return await api.release({ ...this.#task("android", config), platform: "android", signing, formats });
|
|
497
|
-
}
|
|
498
|
-
static androidSigning(env = process.env) {
|
|
499
|
-
const keystore = env.MYAPP_RELEASE_STORE_FILE;
|
|
500
|
-
const storePassword = env.MYAPP_RELEASE_STORE_PASSWORD;
|
|
501
|
-
const alias = env.MYAPP_RELEASE_KEY_ALIAS;
|
|
502
|
-
const keyPassword = env.MYAPP_RELEASE_KEY_PASSWORD;
|
|
503
|
-
if (!keystore || !storePassword || !alias) {
|
|
504
|
-
const missing = Object.entries({
|
|
505
|
-
MYAPP_RELEASE_STORE_FILE: keystore,
|
|
506
|
-
MYAPP_RELEASE_STORE_PASSWORD: storePassword,
|
|
507
|
-
MYAPP_RELEASE_KEY_ALIAS: alias
|
|
508
|
-
}).flatMap(([key, value]) => value ? [] : [key]);
|
|
509
|
-
throw new Error(`An Android release is signed with the upload key; set ${missing.join(", ")} in the environment.`);
|
|
510
|
-
}
|
|
511
|
-
return { keystore, storePassword, alias, ...keyPassword ? { keyPassword } : {} };
|
|
512
|
-
}
|
|
513
|
-
startPath(lang) {
|
|
514
|
-
const basePath = this.target.config.basePath?.replace(/^\/+|\/+$/g, "");
|
|
515
|
-
const params = new URLSearchParams({ csr: "true", akanMobileTarget: this.target.name });
|
|
516
|
-
if (basePath)
|
|
517
|
-
params.set("akanMobileBasePath", basePath);
|
|
518
|
-
if (this.target.config.indexPath)
|
|
519
|
-
params.set("akanMobileIndexPath", this.target.config.indexPath);
|
|
520
|
-
const home = [lang, basePath, this.target.config.indexPath].flatMap((part) => (part ?? "").split("/")).filter((segment) => segment.length > 0);
|
|
521
|
-
return `/${home.join("/")}?${params}`;
|
|
522
|
-
}
|
|
523
|
-
static desktopPlatform(host = process.platform) {
|
|
524
|
-
if (host === "darwin")
|
|
525
|
-
return "macos";
|
|
526
|
-
if (host === "win32")
|
|
527
|
-
return "windows";
|
|
528
|
-
if (host === "linux")
|
|
529
|
-
return "linux";
|
|
530
|
-
throw new Error(`A desktop app builds on macOS, Windows or Linux, not on ${host}.`);
|
|
531
|
-
}
|
|
532
|
-
static async devices(appDir, platform) {
|
|
533
|
-
return await (await NativeApi.load(appDir)).devices({ platform });
|
|
534
|
-
}
|
|
535
|
-
static async doctor(appDir, platforms) {
|
|
536
|
-
return await (await NativeApi.load(appDir)).doctor({ platforms });
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
// pkgs/@akanjs/devkit/mobile/placeholderAppId.ts
|
|
541
|
-
var PLACEHOLDER_APP_IDS = [
|
|
542
|
-
"com.myapp.app",
|
|
543
|
-
"com.myorg.myapp",
|
|
544
|
-
"com.example.app",
|
|
545
|
-
"com.example.myapp"
|
|
546
|
-
];
|
|
547
|
-
var placeholderAppIdSegment = /^(example|examples|myorg|myapp|mycompany|myorganization|changeme|todo|sample|test)$/;
|
|
548
|
-
var isPlaceholderAppId = (appId) => {
|
|
549
|
-
const normalized = appId?.trim().toLowerCase() ?? "";
|
|
550
|
-
if (!normalized)
|
|
551
|
-
return true;
|
|
552
|
-
if (PLACEHOLDER_APP_IDS.includes(normalized))
|
|
553
|
-
return true;
|
|
554
|
-
return normalized.split(".").some((segment) => placeholderAppIdSegment.test(segment));
|
|
555
|
-
};
|
|
556
|
-
export { getMobileTargets, getMobileTargetChoices, resolveMobileTargets, NativeApp, isPlaceholderAppId };
|