@akanjs/cli 3.1.0-rc.2 → 3.1.0-rc.21
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-q50k4s45.js → CodeAgent-gzfc5t52.js} +2 -2
- package/{CodeAgentRpcHost-p7h6n2hp.js → CodeAgentRpcHost-9g27hmer.js} +1 -1
- package/{CodeTui-62g35ztk.js → CodeTui-bpsjs8wz.js} +47 -4
- package/TunnelShare-tfrnftgr.js +14 -0
- package/{agent.command-adfd5299.js → agent.command-67ysh3n6.js} +5 -5
- package/akanCode.js +7 -5
- package/{application.command-hj3gv1rx.js → application.command-q0tqw5pc.js} +38 -11
- package/{applicationBuildRunner-by10r9gf.js → applicationBuildRunner-wsszr305.js} +16 -9
- package/buildBatch.proc.js +7 -7
- package/{cloud.command-k2fhaape.js → cloud.command-e7tkxyx6.js} +17 -16
- package/cloud.script-krzh3d7k.js +24 -0
- package/{code.command-kt5zgcc2.js → code.command-ttk8tje2.js} +25 -12
- package/commandManifest.json +1 -1
- package/{context.command-8fgn42jw.js → context.command-bdsk2z2r.js} +17 -16
- package/context.runner-bxzv0gm0.js +25 -0
- package/{dependencyScanner-06z2dmrr.js → dependencyScanner-3afj2vt1.js} +1 -1
- package/{guideline.command-3yxxr2mm.js → guideline.command-man90n70.js} +2 -2
- package/guidelines/agentRule/agentRule.instruction.md +25 -10
- package/guidelines/conventions/conventions.instruction.md +486 -1084
- package/guidelines/cssRule/cssRule.instruction.md +16 -1
- package/guidelines/fieldRule/fieldRule.instruction.md +74 -0
- package/guidelines/framework/framework.instruction.md +4 -7
- package/guidelines/mcpRule/mcpRule.instruction.md +16 -9
- package/guidelines/modelConstant/modelConstant.instruction.md +2 -1
- package/guidelines/modelStore/modelStore.instruction.md +16 -0
- package/guidelines/pageRule/pageRule.instruction.md +80 -0
- package/guidelines/queryRule/queryRule.instruction.md +47 -12
- package/guidelines/runtimeRule/runtimeRule.instruction.md +89 -2
- package/guidelines/ssrRule/ssrRule.instruction.md +13 -0
- package/guidelines/transportRule/transportRule.instruction.md +107 -8
- package/guidelines/workspaceOnboarding/workspaceOnboarding.instruction.md +4 -4
- package/guidelines/workspaceRecipes/workspaceRecipes.instruction.md +70 -7
- package/incrementalBuilder.proc.js +4 -4
- package/{index-rhbabmja.js → index-033wne2j.js} +260 -259
- package/{index-xpdgxsj5.js → index-1r5a8bzx.js} +2216 -2216
- package/{index-jwqtchha.js → index-27y8t17p.js} +1 -1
- package/{index-wa5jbxj1.js → index-2mrn1mrg.js} +1 -0
- package/{index-t7nws9mx.js → index-2w8j2fnv.js} +661 -448
- package/{index-0909wckt.js → index-35m6bzm7.js} +238 -236
- package/{index-sqfdmb5b.js → index-4393nb4b.js} +4 -4
- package/{index-sby8fe1p.js → index-48edp6q4.js} +1 -1
- package/{index-g0vwzy9m.js → index-4v43h00g.js} +20 -21
- package/{index-azh29dc8.js → index-54wc2phm.js} +19 -14
- package/{index-k9rrten6.js → index-5nd1wtkv.js} +92 -94
- package/index-62md4dwy.js +260 -0
- package/{index-hnby7p9n.js → index-75n5q4ph.js} +7 -7
- package/{index-fjsv10hd.js → index-844q7f8z.js} +7 -7
- package/{index-tv8fjaba.js → index-8jehkey8.js} +4 -4
- package/{index-bea2gwc6.js → index-9mt4smfy.js} +2 -2
- package/{index-d2242ksy.js → index-at7bz0xq.js} +3 -0
- package/{index-3hx1z96c.js → index-bqbf654f.js} +1 -1
- package/{index-8zdwpxef.js → index-cae9m5jz.js} +119 -81
- package/{index-tz5evhdr.js → index-ebarbgba.js} +6 -6
- package/{index-2etqq75t.js → index-g6ax17x4.js} +1114 -1082
- package/{index-h85cxfkg.js → index-g9ks2k8c.js} +2 -2
- package/{index-nq045px5.js → index-h3r3nr9y.js} +356 -307
- package/{index-20cebtth.js → index-h6czey01.js} +79 -24
- package/{index-6726qr1m.js → index-jrmzcp66.js} +57 -58
- package/{index-q958tqqg.js → index-m9jj544v.js} +1 -1
- package/{index-8935h3n1.js → index-mdjc93e1.js} +1 -1
- package/{index-rk9sdys3.js → index-nv79n88g.js} +5 -5
- package/{index-kc8z134d.js → index-nx64dz92.js} +38 -15
- package/{index-z507nzhg.js → index-p2r64bpa.js} +4 -4
- package/{index-nq2ja5e0.js → index-pg1t468s.js} +3 -3
- package/{index-3reaaa1h.js → index-rc51f9ht.js} +29 -27
- package/{index-e9prnmyt.js → index-ta1dychm.js} +3 -3
- package/{index-2ahj896a.js → index-tvehn2sp.js} +1 -1
- package/{index-31ce9vhg.js → index-vkb9m4z7.js} +80 -38
- package/{index-58vvz603.js → index-xsd96yzw.js} +1 -1
- package/{index-pyxn1b53.js → index-zv5s8x58.js} +13 -13
- package/index.js +22 -22
- package/{library.command-8tw59xzj.js → library.command-6amzsrb8.js} +3 -3
- package/{localRegistry.command-tf3dmznd.js → localRegistry.command-yqc0nyhh.js} +15 -14
- package/{module.command-9vm8hffy.js → module.command-10x6jv6c.js} +5 -5
- package/{package.command-erbxe2tz.js → package.command-pwfvecjf.js} +4 -4
- package/package.json +21 -21
- package/{page.command-jpavcj0p.js → page.command-ahhgx9y1.js} +3 -3
- package/{pipeline.command-b8rzvjvv.js → pipeline.command-1ptr3e9y.js} +3 -3
- package/{primitive.command-sm32chzk.js → primitive.command-g5xwv6tj.js} +6 -6
- package/{projectTypecheck-jamtx95x.js → projectTypecheck-fx8x7cat.js} +1 -1
- package/{quality.command-pkd17n6y.js → quality.command-fw9ck061.js} +4 -4
- package/{repair.command-8y4mq6hs.js → repair.command-g8kecj96.js} +5 -5
- package/{scalar.command-q8khmyx5.js → scalar.command-fprap51w.js} +4 -4
- package/skills/akan-code/SKILL.md +4 -0
- package/skills/akan-store/SKILL.md +2 -1
- package/{subspace.command-51n0ykbx.js → subspace.command-sc0t527y.js} +11 -10
- package/templates/appSample/lib/task/Task.Util.tsx +7 -7
- package/templates/appSample/lib/task/Task.Zone.tsx +1 -1
- package/templates/appSample/ui/QuantityControl.tsx +2 -2
- package/templates/crudSinglePage/page.tsx +1 -1
- package/templates/lib/cnst.ts +0 -1
- package/templates/migration/__migrationId__.ts +23 -0
- package/templates/server.ts +8 -1
- package/templates/workspaceRoot/.gitignore.template +23 -52
- package/templates/workspaceRoot/biome.json.template +1 -1
- package/{tunnel.command-7820vtxq.js → tunnel.command-mqnsqbwf.js} +8 -7
- package/{workflow.command-n5sv4wqz.js → workflow.command-j0jgkr13.js} +10 -10
- package/{workspace.command-zdy60jh8.js → workspace.command-1r9hvvw0.js} +29 -28
- package/TunnelShare-f1egrktx.js +0 -13
- package/cloud.script-z8p5dz7e.js +0 -23
- package/context.runner-hnh0rg7m.js +0 -24
- package/index-n68jx79w.js +0 -120
|
@@ -4,206 +4,21 @@ import {
|
|
|
4
4
|
ServerEnvProfile,
|
|
5
5
|
Executor,
|
|
6
6
|
WorkspaceExecutor
|
|
7
|
-
} from "./index-
|
|
7
|
+
} from "./index-g6ax17x4.js";
|
|
8
8
|
import {
|
|
9
9
|
resolveAppId,
|
|
10
10
|
targetHtmlFilename
|
|
11
|
-
} from "./index-
|
|
11
|
+
} from "./index-m9jj544v.js";
|
|
12
12
|
import {
|
|
13
13
|
ExitCodeError
|
|
14
|
-
} from "./index-
|
|
14
|
+
} from "./index-vkb9m4z7.js";
|
|
15
15
|
import {
|
|
16
16
|
CsrArtifactBuilder
|
|
17
|
-
} from "./index-
|
|
18
|
-
|
|
19
|
-
// pkgs/@akanjs/devkit/mobile/nativePublicEnv.ts
|
|
20
|
-
import { createHash } from "crypto";
|
|
21
|
-
class NativePublicEnv {
|
|
22
|
-
static derivedKeys = [
|
|
23
|
-
"AKAN_PUBLIC_APP_NAME",
|
|
24
|
-
"AKAN_PUBLIC_BASE_PATHS",
|
|
25
|
-
"AKAN_PUBLIC_API_PREFIX",
|
|
26
|
-
"AKAN_PUBLIC_WS_PREFIX",
|
|
27
|
-
"AKAN_PUBLIC_RENDER_ENV",
|
|
28
|
-
"AKAN_PUBLIC_DEFAULT_LOCALE",
|
|
29
|
-
"AKAN_PUBLIC_LOCALES"
|
|
30
|
-
];
|
|
31
|
-
static #keyPattern = /^AKAN_PUBLIC_[A-Z0-9_]{1,128}$/;
|
|
32
|
-
static #secretWords = ["SECRET", "TOKEN", "PASSWORD", "PRIVATE", "CREDENTIAL"];
|
|
33
|
-
static #maxKeys = 256;
|
|
34
|
-
static #maxBytes = 64 * 1024;
|
|
35
|
-
static of(env) {
|
|
36
|
-
return Object.fromEntries(Object.keys(env).filter((key) => key.startsWith("AKAN_PUBLIC_") && !NativePublicEnv.derivedKeys.includes(key)).sort().map((key) => [key, String(env[key])]));
|
|
37
|
-
}
|
|
38
|
-
static hash(publicEnv) {
|
|
39
|
-
return createHash("sha256").update(JSON.stringify(NativePublicEnv.of(publicEnv))).digest("hex");
|
|
40
|
-
}
|
|
41
|
-
static problems(publicEnv) {
|
|
42
|
-
const keys = Object.keys(publicEnv);
|
|
43
|
-
const problems = keys.flatMap((key) => {
|
|
44
|
-
const value = publicEnv[key];
|
|
45
|
-
if (!NativePublicEnv.#keyPattern.test(key))
|
|
46
|
-
return [`${key} is not an AKAN_PUBLIC_[A-Z0-9_] name`];
|
|
47
|
-
const word = NativePublicEnv.#secretWords.find((secret) => key.includes(secret));
|
|
48
|
-
if (word)
|
|
49
|
-
return [`${key} names a ${word.toLowerCase()}, which a page bundle must not carry`];
|
|
50
|
-
if (typeof value !== "string")
|
|
51
|
-
return [`${key} is not a string`];
|
|
52
|
-
if (/[\u0000-\u001f\u007f]/.test(value))
|
|
53
|
-
return [`${key} holds a control character`];
|
|
54
|
-
return [];
|
|
55
|
-
});
|
|
56
|
-
if (keys.length > NativePublicEnv.#maxKeys)
|
|
57
|
-
problems.push(`${keys.length} keys, more than ${NativePublicEnv.#maxKeys}`);
|
|
58
|
-
const bytes = Buffer.byteLength(JSON.stringify(publicEnv));
|
|
59
|
-
if (bytes > NativePublicEnv.#maxBytes)
|
|
60
|
-
problems.push(`${bytes} bytes, more than ${NativePublicEnv.#maxBytes}`);
|
|
61
|
-
return problems;
|
|
62
|
-
}
|
|
63
|
-
static diff(a, b) {
|
|
64
|
-
return [...new Set([...Object.keys(a), ...Object.keys(b)])].filter((key) => a[key] !== b[key]).sort((x, y) => x < y ? -1 : x > y ? 1 : 0);
|
|
65
|
-
}
|
|
66
|
-
static async built(app) {
|
|
67
|
-
const file = Bun.file(CsrArtifactBuilder.publicEnvPath(app));
|
|
68
|
-
if (!await file.exists())
|
|
69
|
-
return null;
|
|
70
|
-
return NativePublicEnv.of(await file.json());
|
|
71
|
-
}
|
|
72
|
-
static async recordedIn(bundleFile) {
|
|
73
|
-
const file = Bun.file(bundleFile);
|
|
74
|
-
if (!await file.exists())
|
|
75
|
-
return null;
|
|
76
|
-
const bundle = await file.json();
|
|
77
|
-
return bundle.web?.publicEnv ?? null;
|
|
78
|
-
}
|
|
79
|
-
static async load(file) {
|
|
80
|
-
const source = Bun.file(file);
|
|
81
|
-
if (!await source.exists())
|
|
82
|
-
throw new Error(`--public-env ${file} does not exist.`);
|
|
83
|
-
const json = await source.json().catch(() => null);
|
|
84
|
-
if (!json || typeof json !== "object" || Array.isArray(json))
|
|
85
|
-
throw new Error(`--public-env ${file} is not a JSON object of AKAN_PUBLIC_* values or a bundle.json.`);
|
|
86
|
-
const isBundle = json.schema === 1 && typeof json.web === "object";
|
|
87
|
-
const map = isBundle ? json.web.publicEnv : json;
|
|
88
|
-
if (!map)
|
|
89
|
-
throw new Error(`--public-env ${file} is a bundle.json from a build that recorded no public env.`);
|
|
90
|
-
const derived = Object.keys(map).filter((key) => NativePublicEnv.derivedKeys.includes(key));
|
|
91
|
-
if (derived.length)
|
|
92
|
-
throw new Error(`--public-env ${file} sets ${derived.join(", ")}, which akan.config.ts decides.`);
|
|
93
|
-
const problems = NativePublicEnv.problems(map);
|
|
94
|
-
if (problems.length)
|
|
95
|
-
throw new Error(`--public-env ${file} is refused:
|
|
96
|
-
- ${problems.join(`
|
|
97
|
-
- `)}`);
|
|
98
|
-
return NativePublicEnv.of(map);
|
|
99
|
-
}
|
|
100
|
-
static pin(publicEnv) {
|
|
101
|
-
for (const key of Object.keys(process.env))
|
|
102
|
-
if (key.startsWith("AKAN_PUBLIC_") && !NativePublicEnv.derivedKeys.includes(key))
|
|
103
|
-
delete process.env[key];
|
|
104
|
-
Object.assign(process.env, publicEnv);
|
|
105
|
-
WorkspaceExecutor.publicEnvPin = publicEnv;
|
|
106
|
-
}
|
|
107
|
-
static async problemAgainst(shipped, bundle) {
|
|
108
|
-
const [before, now] = await Promise.all([NativePublicEnv.recordedIn(shipped), NativePublicEnv.recordedIn(bundle)]);
|
|
109
|
-
if (!before)
|
|
110
|
-
return null;
|
|
111
|
-
const differs = NativePublicEnv.diff(before, now ?? {});
|
|
112
|
-
if (!differs.length)
|
|
113
|
-
return null;
|
|
114
|
-
return `public env ${differs.join(", ")} differs from the store build's (pass --public-env ${shipped} to repeat it)`;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// pkgs/@akanjs/devkit/mobile/updatePackFolder.ts
|
|
119
|
-
import { mkdtemp, rm } from "fs/promises";
|
|
120
|
-
import os from "os";
|
|
121
|
-
import path from "path";
|
|
122
|
-
class UpdatePackFolder {
|
|
123
|
-
static packFile = "pack.json";
|
|
124
|
-
static compatFile = "compat.json";
|
|
125
|
-
static bundleFile = "bundle.json";
|
|
126
|
-
static templateFile = "manifest.template.json";
|
|
127
|
-
static filesDir = "files";
|
|
128
|
-
static #records = [
|
|
129
|
-
UpdatePackFolder.templateFile,
|
|
130
|
-
UpdatePackFolder.bundleFile,
|
|
131
|
-
UpdatePackFolder.packFile,
|
|
132
|
-
UpdatePackFolder.compatFile
|
|
133
|
-
];
|
|
134
|
-
static #sha256 = /^[0-9a-f]{64}$/;
|
|
135
|
-
static async clear(dir) {
|
|
136
|
-
await Promise.all([UpdatePackFolder.packFile, UpdatePackFolder.compatFile].map((name) => rm(path.join(dir, name), { force: true })));
|
|
137
|
-
}
|
|
138
|
-
static async read(dir) {
|
|
139
|
-
const file = Bun.file(path.join(dir, UpdatePackFolder.packFile));
|
|
140
|
-
if (!await file.exists())
|
|
141
|
-
throw new Error(`${dir} holds no ${UpdatePackFolder.packFile}: give the folder \`akan pack-update\` wrote.`);
|
|
142
|
-
const pack = await file.json();
|
|
143
|
-
if (pack.schema !== 1)
|
|
144
|
-
throw new Error(`${dir}/${UpdatePackFolder.packFile} is schema ${pack.schema}, and this akan reads schema 1.`);
|
|
145
|
-
return { ...pack, out: path.resolve(dir) };
|
|
146
|
-
}
|
|
147
|
-
static async write({ out, ...record }) {
|
|
148
|
-
await Bun.write(path.join(out, UpdatePackFolder.packFile), `${JSON.stringify(record, null, 2)}
|
|
149
|
-
`);
|
|
150
|
-
}
|
|
151
|
-
static async writeCompat(pack, compat) {
|
|
152
|
-
pack.compat = compat;
|
|
153
|
-
await Bun.write(path.join(pack.out, UpdatePackFolder.compatFile), `${JSON.stringify(compat, null, 2)}
|
|
154
|
-
`);
|
|
155
|
-
}
|
|
156
|
-
static async readBundle(file) {
|
|
157
|
-
return await Bun.file(file).json();
|
|
158
|
-
}
|
|
159
|
-
static storeBuildVerdict({ out, compat }, then) {
|
|
160
|
-
if (!compat || compat.compatible)
|
|
161
|
-
return null;
|
|
162
|
-
return new ExitCodeError(`The pack in ${out} needs a new store build; it differs from the one ${compat.against} came from:
|
|
163
|
-
- ${compat.problems.join(`
|
|
164
|
-
- `)}${then ? `
|
|
165
|
-
${then}` : ""}`, 2);
|
|
166
|
-
}
|
|
167
|
-
static async withArchive(pack, use) {
|
|
168
|
-
const { templateFile, filesDir } = UpdatePackFolder;
|
|
169
|
-
const template = Bun.file(path.join(pack.out, templateFile));
|
|
170
|
-
if (!await template.exists())
|
|
171
|
-
throw new Error(`${pack.out} holds no ${templateFile}: give the folder \`akan pack-update\` wrote.`);
|
|
172
|
-
const { files = [] } = await template.json();
|
|
173
|
-
const hashes = [...new Set(files.map((file) => file.sha256))];
|
|
174
|
-
if (!hashes.every((hash) => typeof hash === "string" && UpdatePackFolder.#sha256.test(hash)))
|
|
175
|
-
throw new Error(`${pack.out}/${templateFile} lists a file whose sha256 is not 64 hex digits.`);
|
|
176
|
-
const present = await Promise.all(UpdatePackFolder.#records.map((name) => Bun.file(path.join(pack.out, name)).exists()));
|
|
177
|
-
const entries = [
|
|
178
|
-
...UpdatePackFolder.#records.filter((_, idx) => present[idx]),
|
|
179
|
-
...hashes.map((hash) => `${filesDir}/${hash}`)
|
|
180
|
-
];
|
|
181
|
-
const dir = await mkdtemp(path.join(os.tmpdir(), "akan-pack-"));
|
|
182
|
-
try {
|
|
183
|
-
const tar = Bun.spawn(["tar", "-czf", "pack.tgz", "-C", pack.out, "-T", "-"], {
|
|
184
|
-
cwd: dir,
|
|
185
|
-
stdin: new Blob([entries.map((entry) => `./${entry}
|
|
186
|
-
`).join("")]),
|
|
187
|
-
stdout: "ignore",
|
|
188
|
-
stderr: "pipe"
|
|
189
|
-
});
|
|
190
|
-
const [code, stderr] = await Promise.all([tar.exited, new Response(tar.stderr).text()]);
|
|
191
|
-
if (code !== 0)
|
|
192
|
-
throw new Error(`tar of ${pack.out} failed: ${stderr.trim()}`);
|
|
193
|
-
return await use(path.join(dir, "pack.tgz"));
|
|
194
|
-
} finally {
|
|
195
|
-
await rm(dir, { recursive: true, force: true });
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// pkgs/@akanjs/devkit/mobile/nativeApp.ts
|
|
201
|
-
import path8 from "path";
|
|
202
|
-
import { toError } from "akanjs/common";
|
|
17
|
+
} from "./index-nx64dz92.js";
|
|
203
18
|
|
|
204
19
|
// pkgs/@akanjs/devkit/mobile/desktopBin.ts
|
|
205
|
-
import { chmod, mkdir, rename, rm
|
|
206
|
-
import
|
|
20
|
+
import { chmod, mkdir, rename, rm, writeFile } from "fs/promises";
|
|
21
|
+
import path from "path";
|
|
207
22
|
class DesktopBin {
|
|
208
23
|
app;
|
|
209
24
|
config;
|
|
@@ -236,23 +51,23 @@ class DesktopBin {
|
|
|
236
51
|
static fileName(name, executable, platform = process.platform) {
|
|
237
52
|
if (platform !== "win32" || /\.(exe|cmd|bat|com)$/i.test(name))
|
|
238
53
|
return name;
|
|
239
|
-
return `${name}${
|
|
54
|
+
return `${name}${path.extname(executable).toLowerCase() || ".exe"}`;
|
|
240
55
|
}
|
|
241
56
|
static downloadName(url) {
|
|
242
|
-
const extension = /(?:\.tar)?\.[A-Za-z0-9]{1,8}$/.exec(
|
|
57
|
+
const extension = /(?:\.tar)?\.[A-Za-z0-9]{1,8}$/.exec(path.posix.basename(new URL(url).pathname))?.[0] ?? "";
|
|
243
58
|
return `download${extension.toLowerCase()}`;
|
|
244
59
|
}
|
|
245
60
|
static extractCommand(archive, dir, platform = process.platform) {
|
|
246
61
|
if (platform === "linux" && /\.zip$/i.test(archive))
|
|
247
62
|
return ["unzip", "-q", "-o", archive, "-d", dir];
|
|
248
|
-
const tar = platform === "win32" ?
|
|
63
|
+
const tar = platform === "win32" ? path.join(process.env.SystemRoot ?? "C:\\Windows", "System32", "tar.exe") : "tar";
|
|
249
64
|
return [tar, "-xf", archive, "-C", dir];
|
|
250
65
|
}
|
|
251
66
|
static async sha256(file) {
|
|
252
67
|
return new Bun.CryptoHasher("sha256").update(await Bun.file(file).arrayBuffer()).digest("hex");
|
|
253
68
|
}
|
|
254
69
|
get #cacheDir() {
|
|
255
|
-
return
|
|
70
|
+
return path.join(this.app.cwdPath, ".akan", "cache", "bin");
|
|
256
71
|
}
|
|
257
72
|
async stage(dir, arch = process.arch) {
|
|
258
73
|
const scanInfo = this.app.getScanInfo({ allowEmpty: true }) ?? await this.app.scan({ write: false });
|
|
@@ -262,14 +77,14 @@ class DesktopBin {
|
|
|
262
77
|
const platform = DesktopBin.platform(arch);
|
|
263
78
|
if (!platform)
|
|
264
79
|
throw new Error(`bin: this desktop app is for ${process.platform}-${arch}, and a bin names only ${binPlatforms.join(", ")}.`);
|
|
265
|
-
await
|
|
80
|
+
await rm(dir, { recursive: true, force: true });
|
|
266
81
|
await mkdir(dir, { recursive: true });
|
|
267
82
|
for (const [name, { owner, platforms }] of chosen) {
|
|
268
83
|
const source = platforms[platform];
|
|
269
84
|
if (!source)
|
|
270
85
|
throw new Error(`${owner}: bin.${name} names no ${platform} file, the platform this desktop app is built for (it names ${Object.keys(platforms).join(", ") || "none"}).`);
|
|
271
86
|
const executable = await this.#executable(`${owner}: bin.${name}.${platform}`, source);
|
|
272
|
-
const target =
|
|
87
|
+
const target = path.join(dir, DesktopBin.fileName(name, executable));
|
|
273
88
|
await Bun.write(target, Bun.file(executable));
|
|
274
89
|
await chmod(target, 493);
|
|
275
90
|
this.app.logger.info(`The desktop app carries ${name} from ${"url" in source ? source.url : source.path}`);
|
|
@@ -283,14 +98,14 @@ class DesktopBin {
|
|
|
283
98
|
if (!source.file)
|
|
284
99
|
return file;
|
|
285
100
|
const key = "url" in source ? source.sha256 : await DesktopBin.sha256(file);
|
|
286
|
-
const executable =
|
|
101
|
+
const executable = path.join(await this.#extract(file, key), ...source.file.split("/"));
|
|
287
102
|
if (!await Bun.file(executable).exists())
|
|
288
103
|
throw new Error(`${at}: the archive holds no ${source.file}`);
|
|
289
104
|
return executable;
|
|
290
105
|
}
|
|
291
106
|
async#download(at, { url, sha256 }) {
|
|
292
|
-
const dir =
|
|
293
|
-
const file =
|
|
107
|
+
const dir = path.join(this.#cacheDir, sha256);
|
|
108
|
+
const file = path.join(dir, DesktopBin.downloadName(url));
|
|
294
109
|
if (await Bun.file(file).exists())
|
|
295
110
|
return file;
|
|
296
111
|
await mkdir(dir, { recursive: true });
|
|
@@ -307,10 +122,10 @@ class DesktopBin {
|
|
|
307
122
|
return file;
|
|
308
123
|
}
|
|
309
124
|
async#extract(archive, key) {
|
|
310
|
-
const dir =
|
|
125
|
+
const dir = path.join(this.#cacheDir, key, "extracted");
|
|
311
126
|
if (await Bun.file(`${dir}.done`).exists())
|
|
312
127
|
return dir;
|
|
313
|
-
await
|
|
128
|
+
await rm(dir, { recursive: true, force: true });
|
|
314
129
|
await mkdir(dir, { recursive: true });
|
|
315
130
|
const [command = "tar", ...args] = DesktopBin.extractCommand(archive, dir);
|
|
316
131
|
if (command === "unzip" && !Bun.which("unzip"))
|
|
@@ -322,7 +137,7 @@ class DesktopBin {
|
|
|
322
137
|
}
|
|
323
138
|
|
|
324
139
|
// pkgs/@akanjs/devkit/mobile/nativeApi.ts
|
|
325
|
-
import
|
|
140
|
+
import path2 from "path";
|
|
326
141
|
|
|
327
142
|
class NativeApi {
|
|
328
143
|
static supportedMajor = 0;
|
|
@@ -334,7 +149,7 @@ class NativeApi {
|
|
|
334
149
|
const akanjsPackage = NativeApi.#resolve("akanjs/package.json", appDir);
|
|
335
150
|
if (!akanjsPackage)
|
|
336
151
|
throw new Error(`akanjs is not installed for ${appDir}; the native build ships inside it.`);
|
|
337
|
-
return
|
|
152
|
+
return path2.join(path2.dirname(akanjsPackage), "vendor", "@akanjs", "native", "packages", "cli", "src", "api.ts");
|
|
338
153
|
}
|
|
339
154
|
static async load(appDir) {
|
|
340
155
|
const entry = NativeApi.entryOf(appDir);
|
|
@@ -355,6 +170,10 @@ class NativeApi {
|
|
|
355
170
|
}
|
|
356
171
|
}
|
|
357
172
|
|
|
173
|
+
// pkgs/@akanjs/devkit/mobile/nativeApp.ts
|
|
174
|
+
import path8 from "path";
|
|
175
|
+
import { toError } from "akanjs/common";
|
|
176
|
+
|
|
358
177
|
// pkgs/@akanjs/devkit/mobile/nativeAppLine.ts
|
|
359
178
|
class NativeAppLine {
|
|
360
179
|
static #page = /^\[page(\+)?(?:#(\d+))? (trace|verbose|debug|log|info|warn|error)\] ?(.*)$/;
|
|
@@ -412,7 +231,8 @@ class NativeAppLine {
|
|
|
412
231
|
|
|
413
232
|
// pkgs/@akanjs/devkit/mobile/nativeConfig.ts
|
|
414
233
|
import { readFileSync } from "fs";
|
|
415
|
-
import
|
|
234
|
+
import path3 from "path";
|
|
235
|
+
import { trimSlashes } from "akanjs/common";
|
|
416
236
|
|
|
417
237
|
// pkgs/@akanjs/devkit/mobile/usageDescriptions.ts
|
|
418
238
|
import { capitalize } from "akanjs/common";
|
|
@@ -494,11 +314,11 @@ class NativeConfig {
|
|
|
494
314
|
warnings.push(`Permission '${permission}' has no native plugin yet; the app ships without it.`);
|
|
495
315
|
return used;
|
|
496
316
|
});
|
|
497
|
-
const abs = (relative) =>
|
|
317
|
+
const abs = (relative) => path3.resolve(appPath, relative);
|
|
498
318
|
const updates = NativeConfig.#updates(target, env);
|
|
499
|
-
const folders = new Set(nativePlugins.map((plugin) =>
|
|
319
|
+
const folders = new Set(nativePlugins.map((plugin) => path3.resolve(plugin.dir)));
|
|
500
320
|
const folderIds = new Set(nativePlugins.map((plugin) => plugin.id));
|
|
501
|
-
const listed = (target.plugins ?? []).filter((spec) => NativeConfig.#isId(spec) ? !folderIds.has(spec) : !folders.has(
|
|
321
|
+
const listed = (target.plugins ?? []).filter((spec) => NativeConfig.#isId(spec) ? !folderIds.has(spec) : !folders.has(path3.resolve(appPath, spec)));
|
|
502
322
|
const plugins = [
|
|
503
323
|
...new Set([
|
|
504
324
|
...NativeConfig.basePlugins,
|
|
@@ -554,7 +374,7 @@ class NativeConfig {
|
|
|
554
374
|
app: {
|
|
555
375
|
id: resolveAppId(target.appId, platform),
|
|
556
376
|
name: target.appName,
|
|
557
|
-
fileName: target.fileName ?? NativeConfig.fileNameOf(
|
|
377
|
+
fileName: target.fileName ?? NativeConfig.fileNameOf(path3.basename(appPath)),
|
|
558
378
|
version: target.version,
|
|
559
379
|
build: target.buildNum
|
|
560
380
|
},
|
|
@@ -614,20 +434,20 @@ class NativeConfig {
|
|
|
614
434
|
return url && keys ? { ...rest, url, ...keys, ...channel ? { channel } : {} } : undefined;
|
|
615
435
|
}
|
|
616
436
|
static fileNameOf(name) {
|
|
617
|
-
return name.replace(/[^A-Za-z0-9._-]/g, "-")
|
|
437
|
+
return /[^-.](?:.*[^-.])?/.exec(name.replace(/[^A-Za-z0-9._-]/g, "-"))?.[0] ?? "app";
|
|
618
438
|
}
|
|
619
439
|
static #pluginId(appPath, spec) {
|
|
620
440
|
if (NativeConfig.#isId(spec))
|
|
621
441
|
return spec;
|
|
622
442
|
try {
|
|
623
|
-
const { id } = JSON.parse(readFileSync(
|
|
443
|
+
const { id } = JSON.parse(readFileSync(path3.join(path3.resolve(appPath, spec), "native-plugin.json"), "utf8"));
|
|
624
444
|
return typeof id === "string" ? id : spec;
|
|
625
445
|
} catch {
|
|
626
446
|
return spec;
|
|
627
447
|
}
|
|
628
448
|
}
|
|
629
449
|
static #isId(spec) {
|
|
630
|
-
return !spec.startsWith(".") && !
|
|
450
|
+
return !spec.startsWith(".") && !path3.isAbsolute(spec);
|
|
631
451
|
}
|
|
632
452
|
static #compact(value) {
|
|
633
453
|
const kept = Object.entries(value).filter(([, entry]) => Array.isArray(entry) ? entry.length > 0 : entry && typeof entry === "object" ? Object.keys(entry).length > 0 : entry !== undefined);
|
|
@@ -637,7 +457,7 @@ class NativeConfig {
|
|
|
637
457
|
return Object.assign({}, ...contributed, own);
|
|
638
458
|
}
|
|
639
459
|
static #deepLinks(target, locales) {
|
|
640
|
-
const basePath = target.basePath
|
|
460
|
+
const basePath = target.basePath ? trimSlashes(target.basePath) : "";
|
|
641
461
|
const pathPrefixes = basePath ? locales.map((locale) => `/${locale}/${basePath}`) : undefined;
|
|
642
462
|
return {
|
|
643
463
|
...target.deepLinks?.schemes?.length ? { schemes: target.deepLinks.schemes } : {},
|
|
@@ -648,23 +468,23 @@ class NativeConfig {
|
|
|
648
468
|
|
|
649
469
|
// pkgs/@akanjs/devkit/mobile/nativePluginFolders.ts
|
|
650
470
|
import { readdir } from "fs/promises";
|
|
651
|
-
import
|
|
471
|
+
import path4 from "path";
|
|
652
472
|
|
|
653
473
|
class NativePluginFolders {
|
|
654
474
|
static folder = "native";
|
|
655
475
|
static async in(root, owner) {
|
|
656
|
-
const base =
|
|
476
|
+
const base = path4.join(root, NativePluginFolders.folder);
|
|
657
477
|
const entries = await readdir(base, { withFileTypes: true }).catch(() => []);
|
|
658
478
|
const folders = entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
659
479
|
return await Promise.all(folders.sort().map(async (name) => {
|
|
660
480
|
const where = `${owner}/${NativePluginFolders.folder}/${name}`;
|
|
661
|
-
const manifest = Bun.file(
|
|
481
|
+
const manifest = Bun.file(path4.join(base, name, "native-plugin.json"));
|
|
662
482
|
if (!await manifest.exists())
|
|
663
483
|
throw new Error(`${where} has no native-plugin.json; a folder in native/ is one native plugin.`);
|
|
664
484
|
const { id } = await manifest.json();
|
|
665
485
|
if (id !== name)
|
|
666
486
|
throw new Error(`${where}/native-plugin.json has id ${JSON.stringify(id)}; name the folder after its id.`);
|
|
667
|
-
return { id, dir:
|
|
487
|
+
return { id, dir: path4.join(base, name), owner };
|
|
668
488
|
}));
|
|
669
489
|
}
|
|
670
490
|
static select(own, fromLibs) {
|
|
@@ -683,15 +503,115 @@ class NativePluginFolders {
|
|
|
683
503
|
const root = app.workspace.workspaceRoot;
|
|
684
504
|
const [own, ...fromLibs] = await Promise.all([
|
|
685
505
|
NativePluginFolders.in(app.cwdPath, `apps/${app.name}`),
|
|
686
|
-
...scanInfo.getLibs().map((lib) => NativePluginFolders.in(
|
|
506
|
+
...scanInfo.getLibs().map((lib) => NativePluginFolders.in(path4.join(root, "libs", lib), `libs/${lib}`))
|
|
687
507
|
]);
|
|
688
508
|
return NativePluginFolders.select(own ?? [], fromLibs.flat());
|
|
689
509
|
}
|
|
690
510
|
}
|
|
691
511
|
|
|
512
|
+
// pkgs/@akanjs/devkit/mobile/nativePublicEnv.ts
|
|
513
|
+
import { createHash } from "crypto";
|
|
514
|
+
class NativePublicEnv {
|
|
515
|
+
static derivedKeys = [
|
|
516
|
+
"AKAN_PUBLIC_APP_NAME",
|
|
517
|
+
"AKAN_PUBLIC_BASE_PATHS",
|
|
518
|
+
"AKAN_PUBLIC_API_PREFIX",
|
|
519
|
+
"AKAN_PUBLIC_WS_PREFIX",
|
|
520
|
+
"AKAN_PUBLIC_RENDER_ENV",
|
|
521
|
+
"AKAN_PUBLIC_DEFAULT_LOCALE",
|
|
522
|
+
"AKAN_PUBLIC_LOCALES"
|
|
523
|
+
];
|
|
524
|
+
static #keyPattern = /^AKAN_PUBLIC_[A-Z0-9_]{1,128}$/;
|
|
525
|
+
static #secretWords = ["SECRET", "TOKEN", "PASSWORD", "PRIVATE", "CREDENTIAL"];
|
|
526
|
+
static #maxKeys = 256;
|
|
527
|
+
static #maxBytes = 64 * 1024;
|
|
528
|
+
static of(env) {
|
|
529
|
+
return Object.fromEntries(Object.keys(env).filter((key) => key.startsWith("AKAN_PUBLIC_") && !NativePublicEnv.derivedKeys.includes(key)).sort().map((key) => [key, String(env[key])]));
|
|
530
|
+
}
|
|
531
|
+
static hash(publicEnv) {
|
|
532
|
+
return createHash("sha256").update(JSON.stringify(NativePublicEnv.of(publicEnv))).digest("hex");
|
|
533
|
+
}
|
|
534
|
+
static problems(publicEnv) {
|
|
535
|
+
const keys = Object.keys(publicEnv);
|
|
536
|
+
const problems = keys.flatMap((key) => {
|
|
537
|
+
const value = publicEnv[key];
|
|
538
|
+
if (!NativePublicEnv.#keyPattern.test(key))
|
|
539
|
+
return [`${key} is not an AKAN_PUBLIC_[A-Z0-9_] name`];
|
|
540
|
+
const word = NativePublicEnv.#secretWords.find((secret) => key.includes(secret));
|
|
541
|
+
if (word)
|
|
542
|
+
return [`${key} names a ${word.toLowerCase()}, which a page bundle must not carry`];
|
|
543
|
+
if (typeof value !== "string")
|
|
544
|
+
return [`${key} is not a string`];
|
|
545
|
+
if (/[\u0000-\u001f\u007f]/.test(value))
|
|
546
|
+
return [`${key} holds a control character`];
|
|
547
|
+
return [];
|
|
548
|
+
});
|
|
549
|
+
if (keys.length > NativePublicEnv.#maxKeys)
|
|
550
|
+
problems.push(`${keys.length} keys, more than ${NativePublicEnv.#maxKeys}`);
|
|
551
|
+
const bytes = Buffer.byteLength(JSON.stringify(publicEnv));
|
|
552
|
+
if (bytes > NativePublicEnv.#maxBytes)
|
|
553
|
+
problems.push(`${bytes} bytes, more than ${NativePublicEnv.#maxBytes}`);
|
|
554
|
+
return problems;
|
|
555
|
+
}
|
|
556
|
+
static diff(a, b) {
|
|
557
|
+
return [...new Set([...Object.keys(a), ...Object.keys(b)])].filter((key) => a[key] !== b[key]).sort((x, y) => x < y ? -1 : x > y ? 1 : 0);
|
|
558
|
+
}
|
|
559
|
+
static async built(app) {
|
|
560
|
+
const file = Bun.file(CsrArtifactBuilder.publicEnvPath(app));
|
|
561
|
+
if (!await file.exists())
|
|
562
|
+
return null;
|
|
563
|
+
return NativePublicEnv.of(await file.json());
|
|
564
|
+
}
|
|
565
|
+
static async recordedIn(bundleFile) {
|
|
566
|
+
const file = Bun.file(bundleFile);
|
|
567
|
+
if (!await file.exists())
|
|
568
|
+
return null;
|
|
569
|
+
const bundle = await file.json();
|
|
570
|
+
return bundle.web?.publicEnv ?? null;
|
|
571
|
+
}
|
|
572
|
+
static async load(file) {
|
|
573
|
+
const source = Bun.file(file);
|
|
574
|
+
if (!await source.exists())
|
|
575
|
+
throw new Error(`--public-env ${file} does not exist.`);
|
|
576
|
+
const json = await source.json().catch(() => null);
|
|
577
|
+
if (!json || typeof json !== "object" || Array.isArray(json))
|
|
578
|
+
throw new Error(`--public-env ${file} is not a JSON object of AKAN_PUBLIC_* values or a bundle.json.`);
|
|
579
|
+
const isBundle = json.schema === 1 && typeof json.web === "object";
|
|
580
|
+
const map = isBundle ? json.web.publicEnv : json;
|
|
581
|
+
if (!map)
|
|
582
|
+
throw new Error(`--public-env ${file} is a bundle.json from a build that recorded no public env.`);
|
|
583
|
+
const derived = Object.keys(map).filter((key) => NativePublicEnv.derivedKeys.includes(key));
|
|
584
|
+
if (derived.length)
|
|
585
|
+
throw new Error(`--public-env ${file} sets ${derived.join(", ")}, which akan.config.ts decides.`);
|
|
586
|
+
const problems = NativePublicEnv.problems(map);
|
|
587
|
+
if (problems.length)
|
|
588
|
+
throw new Error(`--public-env ${file} is refused:
|
|
589
|
+
- ${problems.join(`
|
|
590
|
+
- `)}`);
|
|
591
|
+
return NativePublicEnv.of(map);
|
|
592
|
+
}
|
|
593
|
+
static pin(publicEnv) {
|
|
594
|
+
for (const key of Object.keys(process.env))
|
|
595
|
+
if (key.startsWith("AKAN_PUBLIC_") && !NativePublicEnv.derivedKeys.includes(key))
|
|
596
|
+
delete process.env[key];
|
|
597
|
+
Object.assign(process.env, publicEnv);
|
|
598
|
+
WorkspaceExecutor.publicEnvPin = publicEnv;
|
|
599
|
+
}
|
|
600
|
+
static async problemAgainst(shipped, bundle) {
|
|
601
|
+
const [before, now] = await Promise.all([NativePublicEnv.recordedIn(shipped), NativePublicEnv.recordedIn(bundle)]);
|
|
602
|
+
if (!before)
|
|
603
|
+
return null;
|
|
604
|
+
const differs = NativePublicEnv.diff(before, now ?? {});
|
|
605
|
+
if (!differs.length)
|
|
606
|
+
return null;
|
|
607
|
+
return `public env ${differs.join(", ")} differs from the store build's (pass --public-env ${shipped} to repeat it)`;
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
692
611
|
// pkgs/@akanjs/devkit/mobile/nativeWebDir.ts
|
|
693
|
-
import { cp, mkdir as mkdir2, rm as
|
|
694
|
-
import
|
|
612
|
+
import { cp, mkdir as mkdir2, rm as rm2, stat } from "fs/promises";
|
|
613
|
+
import path5 from "path";
|
|
614
|
+
import { trimSlashes as trimSlashes2 } from "akanjs/common";
|
|
695
615
|
|
|
696
616
|
class NativeWebDir {
|
|
697
617
|
dir;
|
|
@@ -702,13 +622,13 @@ class NativeWebDir {
|
|
|
702
622
|
static injectTarget(html, target) {
|
|
703
623
|
if (html.includes(NativeWebDir.#targetTag))
|
|
704
624
|
return html;
|
|
705
|
-
const basePath = target.basePath
|
|
625
|
+
const basePath = target.basePath ? trimSlashes2(target.basePath) : "";
|
|
706
626
|
const script = `${NativeWebDir.#targetTag}${JSON.stringify({
|
|
707
627
|
name: target.name,
|
|
708
628
|
basePath,
|
|
709
629
|
indexPath: target.indexPath
|
|
710
630
|
})};</script>`;
|
|
711
|
-
const head = /<head(?:\s[
|
|
631
|
+
const head = /<head(?:\s[^<>]*)?>/i.exec(html);
|
|
712
632
|
if (!head)
|
|
713
633
|
return `${script}
|
|
714
634
|
${html}`;
|
|
@@ -718,7 +638,7 @@ ${script}${html.slice(at)}`;
|
|
|
718
638
|
}
|
|
719
639
|
static #targetTag = "<script>window.__AKAN_MOBILE_TARGET__=";
|
|
720
640
|
async clear() {
|
|
721
|
-
await
|
|
641
|
+
await rm2(this.dir, { recursive: true, force: true });
|
|
722
642
|
await mkdir2(this.dir, { recursive: true });
|
|
723
643
|
}
|
|
724
644
|
async assemble(target, { html, publicDir, fontsDir }) {
|
|
@@ -728,12 +648,12 @@ ${script}${html.slice(at)}`;
|
|
|
728
648
|
if (publicDir && await NativeWebDir.#isDir(publicDir))
|
|
729
649
|
await cp(publicDir, this.dir, { recursive: true });
|
|
730
650
|
if (fontsDir && await NativeWebDir.#isDir(fontsDir))
|
|
731
|
-
await cp(fontsDir,
|
|
732
|
-
if (await NativeWebDir.#isDir(
|
|
651
|
+
await cp(fontsDir, path5.join(this.dir, "_akan", "fonts"), { recursive: true });
|
|
652
|
+
if (await NativeWebDir.#isDir(path5.join(this.dir, NativeWebDir.reservedEntry)))
|
|
733
653
|
throw new Error(`public/${NativeWebDir.reservedEntry} is reserved for the native runtime.`);
|
|
734
|
-
await Bun.write(
|
|
654
|
+
await Bun.write(path5.join(this.dir, "index.html"), NativeWebDir.injectTarget(await Bun.file(html).text(), target));
|
|
735
655
|
const files = await Array.fromAsync(new Bun.Glob("**/*").scan({ cwd: this.dir, onlyFiles: true, dot: true }));
|
|
736
|
-
return files.map((file) => file.split(
|
|
656
|
+
return files.map((file) => file.split(path5.sep).join("/")).sort();
|
|
737
657
|
}
|
|
738
658
|
static async#isDir(dir) {
|
|
739
659
|
try {
|
|
@@ -746,7 +666,7 @@ ${script}${html.slice(at)}`;
|
|
|
746
666
|
|
|
747
667
|
// pkgs/@akanjs/devkit/mobile/serverBundleSecrets.ts
|
|
748
668
|
import { readdir as readdir2, readFile, stat as stat2 } from "fs/promises";
|
|
749
|
-
import
|
|
669
|
+
import path6 from "path";
|
|
750
670
|
class ServerBundleSecrets {
|
|
751
671
|
workspaceRoot;
|
|
752
672
|
appDir;
|
|
@@ -758,8 +678,8 @@ class ServerBundleSecrets {
|
|
|
758
678
|
this.appDir = appDir;
|
|
759
679
|
}
|
|
760
680
|
async envFiles() {
|
|
761
|
-
const dirs = [
|
|
762
|
-
const files = await Promise.all(dirs.map(async (dir) => (await readdir2(dir).catch(() => [])).filter((name) => /^env\.server\.[A-Za-z0-9.-]+\.ts$/.test(name) && !/\.(type|example)\.ts$/.test(name)).map((name) =>
|
|
681
|
+
const dirs = [path6.join(this.appDir, "env"), ...(await this.#libDirs()).map((lib) => path6.join(lib, "env"))];
|
|
682
|
+
const files = await Promise.all(dirs.map(async (dir) => (await readdir2(dir).catch(() => [])).filter((name) => /^env\.server\.[A-Za-z0-9.-]+\.ts$/.test(name) && !/\.(type|example)\.ts$/.test(name)).map((name) => path6.join(dir, name))));
|
|
763
683
|
return files.flat();
|
|
764
684
|
}
|
|
765
685
|
async assertClean(bundleDir) {
|
|
@@ -767,7 +687,7 @@ class ServerBundleSecrets {
|
|
|
767
687
|
const secrets = [];
|
|
768
688
|
for (const file of await this.envFiles()) {
|
|
769
689
|
try {
|
|
770
|
-
const value = await ServerEnvProfile.evaluate(file,
|
|
690
|
+
const value = await ServerEnvProfile.evaluate(file, path6.dirname(path6.dirname(file)));
|
|
771
691
|
secrets.push(...ServerEnvProfile.secretValues(value).map((secret) => ({ file, ...secret })));
|
|
772
692
|
} catch {
|
|
773
693
|
unread.push(ServerBundleSecrets.#relative(this.workspaceRoot, file));
|
|
@@ -807,8 +727,8 @@ Build its server with a profile (native.desktop.server.profile), whose bundle le
|
|
|
807
727
|
return coded;
|
|
808
728
|
}
|
|
809
729
|
async#libDirs() {
|
|
810
|
-
const libsDir =
|
|
811
|
-
return (await readdir2(libsDir).catch(() => [])).map((lib) =>
|
|
730
|
+
const libsDir = path6.join(this.workspaceRoot, "libs");
|
|
731
|
+
return (await readdir2(libsDir).catch(() => [])).map((lib) => path6.join(libsDir, lib));
|
|
812
732
|
}
|
|
813
733
|
static #needles(value) {
|
|
814
734
|
const escaped = JSON.stringify(value).slice(1, -1);
|
|
@@ -821,12 +741,12 @@ Build its server with a profile (native.desktop.server.profile), whose bundle le
|
|
|
821
741
|
return /^export default (["'`])([\s\S]*)\1;?\s*$/.exec(code)?.[2] ?? value;
|
|
822
742
|
}
|
|
823
743
|
static #relative(from, to) {
|
|
824
|
-
return
|
|
744
|
+
return path6.relative(from, to).split(path6.sep).join("/");
|
|
825
745
|
}
|
|
826
746
|
static async#files(dir) {
|
|
827
747
|
const files = [];
|
|
828
748
|
for (const entry of await readdir2(dir, { withFileTypes: true })) {
|
|
829
|
-
const file =
|
|
749
|
+
const file = path6.join(dir, entry.name);
|
|
830
750
|
if (entry.isDirectory()) {
|
|
831
751
|
if (entry.name !== "node_modules")
|
|
832
752
|
files.push(...await ServerBundleSecrets.#files(file));
|
|
@@ -840,7 +760,7 @@ Build its server with a profile (native.desktop.server.profile), whose bundle le
|
|
|
840
760
|
for (const entry of await readdir2(dir, { withFileTypes: true }).catch(() => [])) {
|
|
841
761
|
if (entry.name.startsWith("."))
|
|
842
762
|
continue;
|
|
843
|
-
const file =
|
|
763
|
+
const file = path6.join(dir, entry.name);
|
|
844
764
|
if (entry.isDirectory()) {
|
|
845
765
|
if (!ServerBundleSecrets.#notSource.has(entry.name))
|
|
846
766
|
files.push(...await ServerBundleSecrets.#sources(file));
|
|
@@ -851,6 +771,88 @@ Build its server with a profile (native.desktop.server.profile), whose bundle le
|
|
|
851
771
|
}
|
|
852
772
|
}
|
|
853
773
|
|
|
774
|
+
// pkgs/@akanjs/devkit/mobile/updatePackFolder.ts
|
|
775
|
+
import { mkdtemp, rm as rm3 } from "fs/promises";
|
|
776
|
+
import os from "os";
|
|
777
|
+
import path7 from "path";
|
|
778
|
+
class UpdatePackFolder {
|
|
779
|
+
static packFile = "pack.json";
|
|
780
|
+
static compatFile = "compat.json";
|
|
781
|
+
static bundleFile = "bundle.json";
|
|
782
|
+
static templateFile = "manifest.template.json";
|
|
783
|
+
static filesDir = "files";
|
|
784
|
+
static #records = [
|
|
785
|
+
UpdatePackFolder.templateFile,
|
|
786
|
+
UpdatePackFolder.bundleFile,
|
|
787
|
+
UpdatePackFolder.packFile,
|
|
788
|
+
UpdatePackFolder.compatFile
|
|
789
|
+
];
|
|
790
|
+
static #sha256 = /^[0-9a-f]{64}$/;
|
|
791
|
+
static async clear(dir) {
|
|
792
|
+
await Promise.all([UpdatePackFolder.packFile, UpdatePackFolder.compatFile].map((name) => rm3(path7.join(dir, name), { force: true })));
|
|
793
|
+
}
|
|
794
|
+
static async read(dir) {
|
|
795
|
+
const file = Bun.file(path7.join(dir, UpdatePackFolder.packFile));
|
|
796
|
+
if (!await file.exists())
|
|
797
|
+
throw new Error(`${dir} holds no ${UpdatePackFolder.packFile}: give the folder \`akan pack-update\` wrote.`);
|
|
798
|
+
const pack = await file.json();
|
|
799
|
+
if (pack.schema !== 1)
|
|
800
|
+
throw new Error(`${dir}/${UpdatePackFolder.packFile} is schema ${pack.schema}, and this akan reads schema 1.`);
|
|
801
|
+
return { ...pack, out: path7.resolve(dir) };
|
|
802
|
+
}
|
|
803
|
+
static async write({ out, ...record }) {
|
|
804
|
+
await Bun.write(path7.join(out, UpdatePackFolder.packFile), `${JSON.stringify(record, null, 2)}
|
|
805
|
+
`);
|
|
806
|
+
}
|
|
807
|
+
static async writeCompat(pack, compat) {
|
|
808
|
+
pack.compat = compat;
|
|
809
|
+
await Bun.write(path7.join(pack.out, UpdatePackFolder.compatFile), `${JSON.stringify(compat, null, 2)}
|
|
810
|
+
`);
|
|
811
|
+
}
|
|
812
|
+
static async readBundle(file) {
|
|
813
|
+
return await Bun.file(file).json();
|
|
814
|
+
}
|
|
815
|
+
static storeBuildVerdict({ out, compat }, then) {
|
|
816
|
+
if (!compat || compat.compatible)
|
|
817
|
+
return null;
|
|
818
|
+
return new ExitCodeError(`The pack in ${out} needs a new store build; it differs from the one ${compat.against} came from:
|
|
819
|
+
- ${compat.problems.join(`
|
|
820
|
+
- `)}${then ? `
|
|
821
|
+
${then}` : ""}`, 2);
|
|
822
|
+
}
|
|
823
|
+
static async withArchive(pack, use) {
|
|
824
|
+
const { templateFile, filesDir } = UpdatePackFolder;
|
|
825
|
+
const template = Bun.file(path7.join(pack.out, templateFile));
|
|
826
|
+
if (!await template.exists())
|
|
827
|
+
throw new Error(`${pack.out} holds no ${templateFile}: give the folder \`akan pack-update\` wrote.`);
|
|
828
|
+
const { files = [] } = await template.json();
|
|
829
|
+
const hashes = [...new Set(files.map((file) => file.sha256))];
|
|
830
|
+
if (!hashes.every((hash) => typeof hash === "string" && UpdatePackFolder.#sha256.test(hash)))
|
|
831
|
+
throw new Error(`${pack.out}/${templateFile} lists a file whose sha256 is not 64 hex digits.`);
|
|
832
|
+
const present = await Promise.all(UpdatePackFolder.#records.map((name) => Bun.file(path7.join(pack.out, name)).exists()));
|
|
833
|
+
const entries = [
|
|
834
|
+
...UpdatePackFolder.#records.filter((_, idx) => present[idx]),
|
|
835
|
+
...hashes.map((hash) => `${filesDir}/${hash}`)
|
|
836
|
+
];
|
|
837
|
+
const dir = await mkdtemp(path7.join(os.tmpdir(), "akan-pack-"));
|
|
838
|
+
try {
|
|
839
|
+
const tar = Bun.spawn(["tar", "-czf", "pack.tgz", "-C", pack.out, "-T", "-"], {
|
|
840
|
+
cwd: dir,
|
|
841
|
+
stdin: new Blob([entries.map((entry) => `./${entry}
|
|
842
|
+
`).join("")]),
|
|
843
|
+
stdout: "ignore",
|
|
844
|
+
stderr: "pipe"
|
|
845
|
+
});
|
|
846
|
+
const [code, stderr] = await Promise.all([tar.exited, new Response(tar.stderr).text()]);
|
|
847
|
+
if (code !== 0)
|
|
848
|
+
throw new Error(`tar of ${pack.out} failed: ${stderr.trim()}`);
|
|
849
|
+
return await use(path7.join(dir, "pack.tgz"));
|
|
850
|
+
} finally {
|
|
851
|
+
await rm3(dir, { recursive: true, force: true });
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
|
|
854
856
|
// pkgs/@akanjs/devkit/mobile/nativeApp.ts
|
|
855
857
|
class NativeApp {
|
|
856
858
|
app;
|