@akanjs/cli 3.1.0-rc.1 → 3.1.0-rc.10

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.
Files changed (98) hide show
  1. package/.build-stamp +1 -1
  2. package/{CodeAgent-kyprmy06.js → CodeAgent-rkjyxgec.js} +2 -2
  3. package/{CodeTui-21mtze3j.js → CodeTui-h7pyj6xa.js} +1 -1
  4. package/TunnelShare-661cxz5e.js +14 -0
  5. package/{agent.command-f3gsc1gd.js → agent.command-3qxf4xcj.js} +5 -5
  6. package/akanCode.js +2 -2
  7. package/{application.command-5s780vnz.js → application.command-m1kcd7jf.js} +54 -42
  8. package/{applicationBuildRunner-1yaqsrdq.js → applicationBuildRunner-dj0xkrep.js} +26 -33
  9. package/buildBatch.proc.js +24 -15
  10. package/{cloud.command-yj6xb2a7.js → cloud.command-9cp1t0f5.js} +60 -67
  11. package/cloud.script-dgstqt1m.js +24 -0
  12. package/{code.command-3wkn4ja5.js → code.command-0ad6gw2a.js} +6 -6
  13. package/{context.command-9jjn3gcw.js → context.command-0qx447mb.js} +17 -13
  14. package/context.runner-1vsjztjg.js +25 -0
  15. package/{dependencyScanner-06z2dmrr.js → dependencyScanner-3afj2vt1.js} +1 -1
  16. package/{guideline.command-dtczrqpf.js → guideline.command-cv5bmrx2.js} +2 -2
  17. package/guidelines/agentRule/agentRule.instruction.md +3 -3
  18. package/guidelines/conventions/conventions.instruction.md +457 -1071
  19. package/guidelines/cssRule/cssRule.instruction.md +28 -7
  20. package/guidelines/fieldRule/fieldRule.instruction.md +62 -0
  21. package/guidelines/framework/framework.instruction.md +4 -7
  22. package/guidelines/mcpRule/mcpRule.instruction.md +43 -20
  23. package/guidelines/modelConstant/modelConstant.instruction.md +2 -1
  24. package/guidelines/modelStore/modelStore.instruction.md +9 -0
  25. package/guidelines/pageRule/pageRule.instruction.md +80 -0
  26. package/guidelines/queryRule/queryRule.instruction.md +44 -11
  27. package/guidelines/runtimeRule/runtimeRule.instruction.md +113 -17
  28. package/guidelines/ssrRule/ssrRule.instruction.md +13 -0
  29. package/guidelines/transportRule/transportRule.instruction.md +135 -13
  30. package/guidelines/workspaceOnboarding/workspaceOnboarding.instruction.md +4 -4
  31. package/guidelines/workspaceRecipes/workspaceRecipes.instruction.md +68 -7
  32. package/incrementalBuilder.proc.js +10 -7
  33. package/index-192ha4xh.js +1173 -0
  34. package/{index-c76k723e.js → index-1p98exm2.js} +2 -2
  35. package/{index-rwq3ekqc.js → index-1xj2t7cb.js} +7 -7
  36. package/{index-rtct7d43.js → index-20bxxyvn.js} +80 -80
  37. package/{index-qt22xstj.js → index-35hc95h5.js} +5 -3
  38. package/{index-8r4d7cmt.js → index-4mhy6qme.js} +4 -4
  39. package/{index-ww9bhp67.js → index-4pf8g7ff.js} +1 -1
  40. package/{index-2mx692qq.js → index-5edzv8fn.js} +5 -5
  41. package/{index-k9rrten6.js → index-5nd1wtkv.js} +92 -94
  42. package/index-62md4dwy.js +260 -0
  43. package/index-665zp9rs.js +271 -0
  44. package/{index-2etcv0sc.js → index-86jt2a0e.js} +1 -1
  45. package/{index-krq7btbt.js → index-8fp3b47h.js} +419 -418
  46. package/{index-c2yrsxdh.js → index-9z426msb.js} +109 -328
  47. package/{index-ctanya41.js → index-awbdfcf2.js} +4 -4
  48. package/{index-3hx1z96c.js → index-bqbf654f.js} +1 -1
  49. package/{index-rc5rwyps.js → index-bspm21y1.js} +71 -60
  50. package/index-cpb3nvma.js +667 -0
  51. package/{index-jx4bzvwg.js → index-d47ypebv.js} +97 -55
  52. package/{index-ae5pwhgs.js → index-d72hfmdm.js} +15 -13
  53. package/{index-e0cvze1s.js → index-fmzcs8v6.js} +4 -4
  54. package/{index-8ggztp8k.js → index-fyq1z6kt.js} +68 -201
  55. package/{index-1w2qaee2.js → index-g1mbppa9.js} +331 -259
  56. package/{index-gnyf1fqy.js → index-j237xtew.js} +6 -6
  57. package/{index-fm46xrf1.js → index-j4mkhz93.js} +3 -3
  58. package/{index-6jjtnhkw.js → index-jw1zm273.js} +74 -34
  59. package/{index-q958tqqg.js → index-m9jj544v.js} +1 -1
  60. package/index-mdjc93e1.js +33 -0
  61. package/{index-c1x0be5j.js → index-pydbnfez.js} +2 -2
  62. package/{index-56d2g8ms.js → index-qwa5k1j8.js} +1 -1
  63. package/{index-v29746vb.js → index-tamvyvj3.js} +3 -3
  64. package/{index-05g3jayg.js → index-trpasv9q.js} +260 -259
  65. package/{index-2ahj896a.js → index-tvehn2sp.js} +1 -1
  66. package/{index-55mynnqs.js → index-va06nr10.js} +1434 -1173
  67. package/index-vk0pa0mn.js +240 -0
  68. package/{index-aprt7pkz.js → index-w44cy4vs.js} +2271 -3369
  69. package/index.js +22 -22
  70. package/{library.command-gaga5bmq.js → library.command-erp2f56n.js} +3 -3
  71. package/{localRegistry.command-vsreyy42.js → localRegistry.command-y176p558.js} +15 -11
  72. package/{module.command-7vpf6db4.js → module.command-gs2r615e.js} +5 -5
  73. package/{package.command-twmvvf69.js → package.command-j10r76ax.js} +4 -4
  74. package/package.json +21 -21
  75. package/{page.command-5asq7fdf.js → page.command-4h4a8pnh.js} +3 -3
  76. package/{pipeline.command-d0v38572.js → pipeline.command-a2nrk26m.js} +221 -220
  77. package/{primitive.command-dyrs2fm8.js → primitive.command-w6t0asqz.js} +6 -6
  78. package/{projectTypecheck-cvtkrzp2.js → projectTypecheck-f534enqj.js} +1 -1
  79. package/{quality.command-6emsqpbg.js → quality.command-77x7vbbx.js} +4 -4
  80. package/{repair.command-8t6aegv5.js → repair.command-y6sstev7.js} +5 -5
  81. package/{scalar.command-bnggy1x7.js → scalar.command-20b8cy0k.js} +4 -4
  82. package/{subspace.command-md2teygq.js → subspace.command-5c7yasd7.js} +424 -364
  83. package/templates/appSample/lib/task/Task.Util.tsx +7 -7
  84. package/templates/appSample/lib/task/Task.Zone.tsx +1 -1
  85. package/templates/appSample/ui/QuantityControl.tsx +2 -2
  86. package/templates/crudSinglePage/page.tsx +1 -1
  87. package/templates/lib/cnst.ts +0 -1
  88. package/templates/workspaceRoot/.gitignore.template +23 -52
  89. package/templates/workspaceRoot/biome.json.template +1 -1
  90. package/{tunnel.command-jx3tdm2d.js → tunnel.command-2kw8vt9g.js} +8 -4
  91. package/{workflow.command-jx0wag5r.js → workflow.command-2djwmpqf.js} +10 -10
  92. package/{workspace.command-yxd7rr7g.js → workspace.command-6qfgq4b4.js} +38 -34
  93. package/TunnelShare-q4jq08qg.js +0 -9
  94. package/cloud.script-h6pvcpa2.js +0 -20
  95. package/context.runner-atcrzz5b.js +0 -21
  96. package/index-6w0rbr9d.js +0 -68
  97. package/index-n68jx79w.js +0 -120
  98. package/index-pgj2rt78.js +0 -404
package/index-n68jx79w.js DELETED
@@ -1,120 +0,0 @@
1
- // @bun
2
- // pkgs/@akanjs/devkit/frontendBuild/styleContract.ts
3
- var countBlocking = (violations) => violations.theme.length;
4
- var formatStyleContract = (violations) => violations.theme.flatMap((theme) => [
5
- ` [error] contrast ${theme.scope} ${theme.pair} = ${theme.ratio}:1 (min ${theme.threshold}:1)`,
6
- ` \u2192 ${theme.suggestion}`
7
- ]).join(`
8
- `);
9
-
10
- // pkgs/@akanjs/devkit/frontendBuild/themeValidator.ts
11
- var PAIRS = [
12
- { base: "background", fg: "foreground", threshold: 4.5 },
13
- { base: "primary", fg: "primary-foreground", threshold: 4.5 },
14
- { base: "secondary", fg: "secondary-foreground", threshold: 4.5 },
15
- { base: "accent", fg: "accent-foreground", threshold: 4.5 },
16
- { base: "neutral", fg: "neutral-foreground", threshold: 4.5 },
17
- { base: "card", fg: "card-foreground", threshold: 4.5 },
18
- { base: "popover", fg: "popover-foreground", threshold: 4.5 },
19
- { base: "muted", fg: "muted-foreground", threshold: 3 },
20
- { base: "info", fg: "info-foreground", threshold: 3 },
21
- { base: "success", fg: "success-foreground", threshold: 3 },
22
- { base: "warning", fg: "warning-foreground", threshold: 3 },
23
- { base: "destructive", fg: "destructive-foreground", threshold: 3 },
24
- { base: "open", fg: "open-foreground", threshold: 3 }
25
- ];
26
- var THEME_SCOPES = new Set([":root", '[data-theme="dark"]', '[data-theme="light"]']);
27
-
28
- class ThemeValidator {
29
- validate(css) {
30
- const tokensByScope = ThemeValidator.parseThemeTokens(css);
31
- const violations = [];
32
- for (const [scope, tokens] of Object.entries(tokensByScope)) {
33
- if (!THEME_SCOPES.has(scope))
34
- continue;
35
- violations.push(...this.validateScope(tokens, scope));
36
- }
37
- return violations;
38
- }
39
- validateScope(tokens, scope) {
40
- const violations = [];
41
- for (const { base, fg, threshold } of PAIRS) {
42
- const bg = tokens[base];
43
- const front = tokens[fg];
44
- if (!bg || !front)
45
- continue;
46
- const bgRgb = ThemeValidator.parseHex(bg);
47
- const fgRgb = ThemeValidator.parseHex(front);
48
- if (!bgRgb || !fgRgb)
49
- continue;
50
- const ratio = ThemeValidator.contrastRatio(bgRgb, fgRgb);
51
- if (ratio >= threshold)
52
- continue;
53
- violations.push({
54
- scope,
55
- pair: `${base} / ${fg}`,
56
- background: bg,
57
- foreground: front,
58
- ratio: Math.round(ratio * 100) / 100,
59
- threshold,
60
- suggestion: `In ${scope}, --${base} (${bg}) against --${fg} (${front}) is ${ratio.toFixed(2)}:1, under the ${threshold}:1 minimum. Lighten or darken one of them until it clears.`
61
- });
62
- }
63
- return violations;
64
- }
65
- static parseThemeTokens(css) {
66
- const result = {};
67
- const blockRe = /(?:^|})\s*([^{}@]+?)\s*\{([^{}]*)\}/g;
68
- for (const block of css.matchAll(blockRe)) {
69
- const selectors = block[1].split(",").map((s) => s.trim());
70
- const relevant = selectors.filter((s) => THEME_SCOPES.has(ThemeValidator.#normalizeScope(s)));
71
- if (relevant.length === 0)
72
- continue;
73
- const decls = {};
74
- for (const decl of block[2].matchAll(/--([\w-]+)\s*:\s*([^;]+?)\s*(?:;|$)/g)) {
75
- decls[decl[1]] = decl[2].trim();
76
- }
77
- for (const selector of relevant) {
78
- const scope = ThemeValidator.#normalizeScope(selector);
79
- result[scope] = { ...result[scope] ?? {}, ...decls };
80
- }
81
- }
82
- return result;
83
- }
84
- static #normalizeScope(selector) {
85
- return selector.replace(/\[data-theme=['"]?([\w-]+)['"]?\]/g, '[data-theme="$1"]').trim();
86
- }
87
- static parseHex(value) {
88
- const v = value.trim();
89
- if (!v.startsWith("#"))
90
- return null;
91
- const hex = v.slice(1);
92
- let full;
93
- if (hex.length === 3 || hex.length === 4)
94
- full = hex.slice(0, 3).split("").map((c) => c + c).join("");
95
- else if (hex.length === 6 || hex.length === 8)
96
- full = hex.slice(0, 6);
97
- else
98
- return null;
99
- if (!/^[0-9a-fA-F]{6}$/.test(full))
100
- return null;
101
- const n = Number.parseInt(full, 16);
102
- return [n >> 16 & 255, n >> 8 & 255, n & 255];
103
- }
104
- static #relativeLuminance([r, g, b]) {
105
- const channel = (c) => {
106
- const s = c / 255;
107
- return s <= 0.03928 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;
108
- };
109
- return 0.2126 * channel(r) + 0.7152 * channel(g) + 0.0722 * channel(b);
110
- }
111
- static contrastRatio(a, b) {
112
- const la = ThemeValidator.#relativeLuminance(a);
113
- const lb = ThemeValidator.#relativeLuminance(b);
114
- const hi = Math.max(la, lb);
115
- const lo = Math.min(la, lb);
116
- return (hi + 0.05) / (lo + 0.05);
117
- }
118
- }
119
-
120
- export { countBlocking, formatStyleContract, ThemeValidator };
package/index-pgj2rt78.js DELETED
@@ -1,404 +0,0 @@
1
- // @bun
2
- import {
3
- FileSys2
4
- } from "./index-52w0fvh8.js";
5
- import {
6
- ConfigLock
7
- } from "./index-3hx1z96c.js";
8
-
9
- // pkgs/@akanjs/devkit/cloud/globalConfig.ts
10
- import { mkdir, rm, writeFile } from "fs/promises";
11
- import dayjs from "dayjs";
12
-
13
- // pkgs/@akanjs/devkit/cloud/constants.ts
14
- import path from "path";
15
- var basePath = path.join(Bun.env.HOME ?? Bun.env.USERPROFILE ?? "", ".akan");
16
- var configPath = path.join(basePath, "config.json");
17
- var getDefaultHostConfig = (host = GlobalConfig.akanCloudHost) => ({ host });
18
- var defaultAkanGlobalConfig = {
19
- cloudHost: {},
20
- remoteEnvServers: {},
21
- testTargets: {}
22
- };
23
-
24
- // pkgs/@akanjs/devkit/cloud/globalConfig.ts
25
- class GlobalConfig {
26
- static akanCloudHost = process.env.USE_AKANJS_PKGS === "true" ? `http://localhost:${process.env.CLOUD_HOST_PORT ?? 8283}` : "https://cloud.akanjs.com";
27
- static async#getAkanGlobalConfig() {
28
- const exists = await FileSys2.fileExists(configPath);
29
- const akanConfig = exists ? await FileSys2.readJson(configPath) : {};
30
- return {
31
- ...defaultAkanGlobalConfig,
32
- ...akanConfig,
33
- cloudHost: akanConfig.cloudHost ?? defaultAkanGlobalConfig.cloudHost,
34
- remoteEnvServers: akanConfig.remoteEnvServers ?? defaultAkanGlobalConfig.remoteEnvServers,
35
- testTargets: akanConfig.testTargets ?? defaultAkanGlobalConfig.testTargets
36
- };
37
- }
38
- static async#setAkanGlobalConfig(akanConfig) {
39
- await mkdir(basePath, { recursive: true, mode: 448 });
40
- const temp = `${configPath}.${process.pid}.${crypto.randomUUID()}.tmp`;
41
- try {
42
- await writeFile(temp, JSON.stringify(akanConfig, null, 2), { mode: 384 });
43
- await FileSys2.replace(temp, configPath);
44
- } catch (error) {
45
- await rm(temp, { force: true });
46
- throw error;
47
- }
48
- }
49
- static #lockPath = `${configPath}.lock`;
50
- static async#update(mutate) {
51
- await ConfigLock.run(GlobalConfig.#lockPath, async () => {
52
- await GlobalConfig.#setAkanGlobalConfig(mutate(await GlobalConfig.#getAkanGlobalConfig()));
53
- });
54
- }
55
- static async hostFor(workspace, host) {
56
- if (host)
57
- return host;
58
- return (await workspace.getConfig()).cloudHost ?? GlobalConfig.akanCloudHost;
59
- }
60
- static async getHostConfig(host = GlobalConfig.akanCloudHost) {
61
- const akanConfig = await GlobalConfig.#getAkanGlobalConfig();
62
- return GlobalConfig.toHostConfig(akanConfig.cloudHost[host] ?? getDefaultHostConfig(host));
63
- }
64
- static async setHostConfig(config = getDefaultHostConfig()) {
65
- await GlobalConfig.#update((akanConfig) => GlobalConfig.#withHost(akanConfig, config));
66
- }
67
- static #withHost(akanConfig, config) {
68
- return {
69
- ...akanConfig,
70
- cloudHost: { ...akanConfig.cloudHost, [config.host]: GlobalConfig.toHostConfigDto(config) }
71
- };
72
- }
73
- static async refreshHostAuth(host, refresh) {
74
- try {
75
- return await ConfigLock.run(GlobalConfig.#lockPath, async () => {
76
- const akanConfig = await GlobalConfig.#getAkanGlobalConfig();
77
- const current = GlobalConfig.toHostConfig(akanConfig.cloudHost[host] ?? getDefaultHostConfig(host));
78
- const accessToken = current.auth?.accessToken;
79
- const refreshToken = accessToken?.refreshToken;
80
- if (!accessToken || !refreshToken || !GlobalConfig.needRefreshToken(accessToken))
81
- return current;
82
- const claimed = {
83
- ...current,
84
- auth: { ...current.auth, accessToken: { ...accessToken, refreshToken: null } }
85
- };
86
- await GlobalConfig.#setAkanGlobalConfig(GlobalConfig.#withHost(akanConfig, claimed));
87
- const next = await refresh(refreshToken).catch(() => null);
88
- if (!next?.jwt)
89
- return claimed;
90
- const refreshed = { ...current, auth: { ...current.auth, accessToken: next } };
91
- await GlobalConfig.#setAkanGlobalConfig(GlobalConfig.#withHost(await GlobalConfig.#getAkanGlobalConfig(), refreshed));
92
- return refreshed;
93
- });
94
- } catch {
95
- return await GlobalConfig.getHostConfig(host);
96
- }
97
- }
98
- static async getRemoteEnvServers() {
99
- return (await GlobalConfig.#getAkanGlobalConfig()).remoteEnvServers;
100
- }
101
- static async setRemoteEnvServer(name, config) {
102
- await GlobalConfig.#update((akanConfig) => ({
103
- ...akanConfig,
104
- remoteEnvServers: { ...akanConfig.remoteEnvServers, [name]: config }
105
- }));
106
- }
107
- static async removeRemoteEnvServer(name) {
108
- await GlobalConfig.#update((akanConfig) => {
109
- const { [name]: _, ...remoteEnvServers } = akanConfig.remoteEnvServers;
110
- return { ...akanConfig, remoteEnvServers };
111
- });
112
- }
113
- static async getTestTargets() {
114
- return (await GlobalConfig.#getAkanGlobalConfig()).testTargets;
115
- }
116
- static async setTestTargets(testTargets) {
117
- await GlobalConfig.#update((akanConfig) => ({
118
- ...akanConfig,
119
- testTargets: { ...akanConfig.testTargets, ...testTargets }
120
- }));
121
- }
122
- static needRefreshToken(accessToken) {
123
- return !!accessToken?.expiresAt?.isBefore(dayjs().add(1, "hour"));
124
- }
125
- static toAccessToken(accessToken) {
126
- return {
127
- jwt: accessToken.jwt,
128
- refreshToken: accessToken.refreshToken ?? null,
129
- expiresAt: accessToken.expiresAt ? dayjs(accessToken.expiresAt) : null
130
- };
131
- }
132
- static toAccessTokenDto(accessToken) {
133
- return {
134
- jwt: accessToken.jwt,
135
- refreshToken: accessToken.refreshToken ?? null,
136
- expiresAt: accessToken.expiresAt?.toString() ?? null
137
- };
138
- }
139
- static toHostConfigDto(hostConfig) {
140
- return {
141
- host: hostConfig.host,
142
- auth: {
143
- accessToken: hostConfig.auth?.accessToken ? GlobalConfig.toAccessTokenDto(hostConfig.auth.accessToken) : undefined,
144
- self: hostConfig.auth?.self
145
- }
146
- };
147
- }
148
- static toHostConfig(hostConfigDto) {
149
- return {
150
- host: hostConfigDto.host,
151
- auth: {
152
- accessToken: hostConfigDto.auth?.accessToken ? GlobalConfig.toAccessToken(hostConfigDto.auth.accessToken) : undefined,
153
- self: hostConfigDto.auth?.self
154
- }
155
- };
156
- }
157
- }
158
-
159
- // pkgs/@akanjs/devkit/cloud/cloudApi.ts
160
- class HttpClient {
161
- baseUrl;
162
- headers = {};
163
- constructor(baseUrl, headers = {}) {
164
- this.baseUrl = baseUrl;
165
- this.headers = headers;
166
- }
167
- async get(url, { headers, signal } = {}) {
168
- const response = await fetch(`${this.baseUrl}${url}`, {
169
- headers: { "Content-Type": "application/json", ...this.headers, ...headers },
170
- ...signal ? { signal } : {}
171
- });
172
- return await HttpClient.#body(response, url);
173
- }
174
- async getFile(url, localPath, headers) {
175
- const response = await fetch(`${this.baseUrl}${url}`, {
176
- headers: { ...this.headers, ...headers }
177
- });
178
- if (!response.ok)
179
- throw new Error(`Failed to download file: ${response.status} ${response.statusText}`);
180
- await Bun.write(localPath, response);
181
- }
182
- async post(url, data, { headers, signal } = {}) {
183
- const isFormData = data instanceof FormData;
184
- const response = await fetch(`${this.baseUrl}${url}`, {
185
- method: "POST",
186
- body: isFormData ? data : JSON.stringify(data),
187
- headers: isFormData ? { ...this.headers, ...headers } : { "Content-Type": "application/json", ...this.headers, ...headers },
188
- ...signal ? { signal } : {}
189
- });
190
- return await HttpClient.#body(response, url);
191
- }
192
- static async#body(response, url) {
193
- const text = await response.text();
194
- if (!response.ok)
195
- throw new Error(`${response.status} ${response.statusText} from ${url}: ${text.slice(0, 200)}`);
196
- return JSON.parse(text);
197
- }
198
- setHeaders(headers) {
199
- Object.assign(this.headers, headers);
200
- return this;
201
- }
202
- }
203
-
204
- class CloudApi {
205
- static statusOf(error) {
206
- const status = Number(/(?:^|: )(\d{3}) /.exec(error instanceof Error ? error.message : String(error))?.[1]);
207
- return Number.isNaN(status) ? null : status;
208
- }
209
- #api;
210
- #accessToken = null;
211
- #workspace;
212
- host;
213
- url;
214
- static async fromHost(workspace, host) {
215
- const hostConfig = await GlobalConfig.getHostConfig(host);
216
- const accessToken = hostConfig.auth?.accessToken;
217
- if (!accessToken || !GlobalConfig.needRefreshToken(accessToken))
218
- return new CloudApi(workspace, hostConfig);
219
- const tokenless = new CloudApi(workspace, hostConfig);
220
- const refreshed = await GlobalConfig.refreshHostAuth(hostConfig.host, (refreshToken) => tokenless.refreshAuthToken(refreshToken));
221
- return new CloudApi(workspace, refreshed);
222
- }
223
- constructor(workspace, hostConfig) {
224
- this.#workspace = workspace;
225
- this.#accessToken = hostConfig.auth?.accessToken ?? null;
226
- this.host = hostConfig.host;
227
- this.url = `${this.host}/api`;
228
- this.#api = new HttpClient(this.url);
229
- if (this.#accessToken && !GlobalConfig.needRefreshToken(this.#accessToken))
230
- this.#api.setHeaders({ Authorization: `Bearer ${this.#accessToken.jwt}` });
231
- }
232
- #authorize(accessTokenDto) {
233
- this.#accessToken = GlobalConfig.toAccessToken(accessTokenDto);
234
- this.#api.setHeaders({ Authorization: `Bearer ${this.#accessToken.jwt}` });
235
- return this.#accessToken;
236
- }
237
- async uploadEnv(devProjectId, file) {
238
- const formData = new FormData;
239
- formData.append("devProjectId", devProjectId);
240
- formData.append("file", file);
241
- return await this.#api.post(`/uploadEnv/${devProjectId}`, formData);
242
- }
243
- async uploadSecret(devProjectId, secret) {
244
- return await this.#api.post(`/uploadSecret/${encodeURIComponent(devProjectId)}`, secret);
245
- }
246
- async listSecrets(devProjectId) {
247
- return await this.#api.get(`/listSecrets/${encodeURIComponent(devProjectId)}`);
248
- }
249
- async removeSecret(devProjectId, secret) {
250
- return await this.#api.post(`/removeSecret/${encodeURIComponent(devProjectId)}`, secret);
251
- }
252
- async requestUpdateUpload(devProjectId, upload) {
253
- return await this.#api.post(`/requestUpdateUpload/${encodeURIComponent(devProjectId)}`, upload);
254
- }
255
- async commitUpdateUpload(devProjectId, uploadId) {
256
- return await this.#api.post(`/commitUpdateUpload/${encodeURIComponent(devProjectId)}`, { uploadId }, { signal: AbortSignal.timeout(11 * 60000) });
257
- }
258
- async registerAppBinary(devProjectId, binary) {
259
- const formData = new FormData;
260
- for (const key of ["appName", "target", "distribution"])
261
- formData.append(key, binary[key]);
262
- if (binary.channel)
263
- formData.append("channel", binary.channel);
264
- formData.append("bundle", binary.bundle, "bundle.json");
265
- if (binary.packUploadId)
266
- formData.append("packUploadId", binary.packUploadId);
267
- if (binary.pack)
268
- formData.append("pack", binary.pack, "pack.tgz");
269
- return await this.#api.post(`/registerAppBinary/${encodeURIComponent(devProjectId)}`, formData, {
270
- signal: AbortSignal.timeout(11 * 60000)
271
- });
272
- }
273
- async downloadEnv(devProjectId) {
274
- const localPath = `${this.#workspace.workspaceRoot}/local/env.tar`;
275
- await this.#api.getFile(`/downloadEnv/${devProjectId}`, localPath);
276
- return localPath;
277
- }
278
- async getRemoteAuthToken(remoteId) {
279
- try {
280
- return this.#authorize(await this.#api.get(`/getRemoteAuthToken/${remoteId}`));
281
- } catch (_) {
282
- return null;
283
- }
284
- }
285
- async refreshAuthToken(refreshToken) {
286
- return this.#authorize(await this.#api.post(`/refreshAuthToken`, { refreshToken }, { signal: AbortSignal.timeout(20000) }));
287
- }
288
- async requestTunnel(input) {
289
- return await this.#api.post(`/tunnel/requestTunnel`, input);
290
- }
291
- async tunnelListInSelf() {
292
- return await this.#api.get(`/tunnel/tunnelListInSelf`);
293
- }
294
- async revokeTunnel(code) {
295
- return await this.#api.post(`/tunnel/revokeTunnel`, { code }, { signal: AbortSignal.timeout(5000) });
296
- }
297
- async getRemoteSelf() {
298
- try {
299
- return await this.#api.get(`/getRemoteSelf`);
300
- } catch {
301
- return null;
302
- }
303
- }
304
- }
305
- // pkgs/@akanjs/devkit/cloud/updatePackUpload.ts
306
- import { Logger, sleep } from "akanjs/common";
307
- class UpdatePackUpload {
308
- static maxBytes = 200 * 1024 * 1024;
309
- static #transientPut = new Set([0, 408, 429, 500, 502, 503, 504]);
310
- #api;
311
- #devProjectId;
312
- #putRetryMs;
313
- #commitRetryMs;
314
- #cutOffWindowMs;
315
- #commitLimitMs;
316
- constructor(api, devProjectId, {
317
- putRetryMs = 1000,
318
- commitRetryMs = 1e4,
319
- cutOffWindowMs = 15 * 60000,
320
- commitLimitMs = 60 * 60000
321
- } = {}) {
322
- this.#api = api;
323
- this.#devProjectId = devProjectId;
324
- this.#putRetryMs = putRetryMs;
325
- this.#commitRetryMs = commitRetryMs;
326
- this.#cutOffWindowMs = cutOffWindowMs;
327
- this.#commitLimitMs = commitLimitMs;
328
- }
329
- async send(archive, fields) {
330
- const uploadId = await this.stage(archive, fields);
331
- return await this.settle(() => this.#api.commitUpdateUpload(this.#devProjectId, uploadId));
332
- }
333
- async stage(archive, fields) {
334
- const file = Bun.file(archive);
335
- if (file.size < 1 || file.size > UpdatePackUpload.maxBytes)
336
- throw new Error(`The pack archive is ${file.size} bytes; a project takes 1 byte to 200 MiB.`);
337
- const hasher = new Bun.CryptoHasher("sha256");
338
- for await (const chunk of file.stream())
339
- hasher.update(chunk);
340
- const upload = { ...fields, size: file.size, sha256: hasher.digest("hex") };
341
- for (let ticket = 1;; ticket++) {
342
- const { uploadId, uploadUrl, contentType } = await this.#request(upload);
343
- const status = await this.#put(uploadUrl, contentType, file);
344
- if (status >= 200 && status < 300)
345
- return uploadId;
346
- if (status !== 403 || ticket === 2)
347
- throw new Error(`The storage refused the pack upload (${status ? `HTTP ${status}` : "no response"}).`);
348
- }
349
- }
350
- async#request(upload) {
351
- return await this.#api.requestUpdateUpload(this.#devProjectId, upload).catch((error) => {
352
- const message = error instanceof Error ? error.message : String(error);
353
- if (CloudApi.statusOf(error) === 404 && !/"error"\s*:\s*"[\w$-]+\.error\.[\w$-]+"/.test(message))
354
- throw new Error("The project's server does not take direct pack uploads yet: update akasys and try again, or upload the pack from the project's release screen.");
355
- throw error;
356
- });
357
- }
358
- async#put(url, contentType, file) {
359
- for (let retry = 0;; retry++) {
360
- const status = await fetch(url, { method: "PUT", body: file, headers: { "Content-Type": contentType } }).then(async (response) => {
361
- await response.arrayBuffer().catch(() => {
362
- return;
363
- });
364
- return response.status;
365
- }).catch(() => 0);
366
- if (retry === 3 || !UpdatePackUpload.#transientPut.has(status))
367
- return status;
368
- await sleep(this.#putRetryMs * (retry + 1));
369
- }
370
- }
371
- async settle(call) {
372
- const limit = performance.now() + this.#commitLimitMs;
373
- let cutOffSince = null;
374
- for (;; ) {
375
- try {
376
- return await call();
377
- } catch (error) {
378
- const now = performance.now();
379
- if (UpdatePackUpload.#inProgress(error))
380
- cutOffSince = null;
381
- else if (UpdatePackUpload.#cutOff(error))
382
- cutOffSince ??= now;
383
- else
384
- throw error;
385
- const deadline = cutOffSince === null ? limit : Math.min(limit, cutOffSince + this.#cutOffWindowMs);
386
- if (now + this.#commitRetryMs > deadline)
387
- throw error;
388
- Logger.rawLog(`The project is still taking the pack; asking again in ${Math.round(this.#commitRetryMs / 1000)}s.`);
389
- await sleep(this.#commitRetryMs);
390
- }
391
- }
392
- }
393
- static #inProgress(error) {
394
- const message = error instanceof Error ? error.message : String(error);
395
- return CloudApi.statusOf(error) === 409 && message.includes("uploadCommitInProgress");
396
- }
397
- static #cutOff(error) {
398
- const status = CloudApi.statusOf(error);
399
- if (status === null)
400
- return !(error instanceof SyntaxError);
401
- return status === 502 || status === 504 || status >= 520 && status <= 527;
402
- }
403
- }
404
- export { getDefaultHostConfig, GlobalConfig, CloudApi, UpdatePackUpload };