@akanjs/cli 3.0.0-beta.19 → 3.0.0-beta.20
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-6cm97kay.js → CodeAgent-awv258wb.js} +2 -2
- package/{CodeTui-za4jsm41.js → CodeTui-1nqh35ez.js} +20 -22
- package/TunnelShare-scf4e4pd.js +9 -0
- package/{agent.command-e0wpbbyg.js → agent.command-1s9ag7sb.js} +8 -9
- package/akanCode.js +4 -4
- package/{application.command-8wyxqc8q.js → application.command-49hwy6va.js} +25 -56
- package/{applicationBuildRunner-n9gzaxd0.js → applicationBuildRunner-ybyp97pv.js} +33 -59
- package/{applicationReleasePackager-vpggfqgq.js → applicationReleasePackager-esnrphw8.js} +40 -60
- package/buildBatch.proc.js +19 -24
- package/{capacitorApp-q3z0qht0.js → capacitorApp-1synz4en.js} +3 -4
- package/{cloud.command-4zwa5dsx.js → cloud.command-d7rqtzt9.js} +16 -26
- package/cloud.script-bkbpcr96.js +19 -0
- package/{code.command-468c9d7s.js → code.command-nn48xzej.js} +22 -37
- package/context.command-s3mcmvpa.js +48 -0
- package/context.runner-k99nb5pk.js +21 -0
- package/{dependencyScanner-0z2h2tks.js → dependencyScanner-ye2e6pdz.js} +2 -2
- package/{devTui-ffjsarqa.js → devTui-q7k7bakd.js} +5 -9
- package/fileSys-5ngz93ga.js +9 -0
- package/{guideline.command-vhw13pqr.js → guideline.command-sct4qnt5.js} +4 -5
- package/guidelines/conventions/conventions.instruction.md +5 -1
- package/guidelines/mcpRule/mcpRule.instruction.md +7 -5
- package/incrementalBuilder.proc.js +30 -48
- package/{index-s5rhm04y.js → index-0bq14vr1.js} +1 -1
- package/{index-10dkrrww.js → index-13ffe9wp.js} +4 -4
- package/{index-srmay5jw.js → index-22d57v4c.js} +84 -67
- package/{index-53176s7j.js → index-31yr441d.js} +10 -17
- package/index-3hx1z96c.js +87 -0
- package/{index-4m929vvn.js → index-3sq321ak.js} +10 -18
- package/{index-bhtmj233.js → index-3t7g9e4r.js} +41 -78
- package/{index-q0y2jq9j.js → index-5b6tavv3.js} +6 -3
- package/{index-47pj3aee.js → index-5e0n4hqx.js} +11 -12
- package/{index-7ep8qtvp.js → index-5pc1qrbv.js} +5 -8
- package/{index-esk82rvk.js → index-8mna0947.js} +34 -71
- package/{index-sq2dzkzm.js → index-8sf7wmdr.js} +6 -6
- package/{index-crmeqnmf.js → index-9d9bgcx4.js} +283 -722
- package/{index-bwf965h6.js → index-ahemdr5n.js} +6 -6
- package/{index-ab0c38ea.js → index-as7xmyz8.js} +20 -20
- package/{index-bc88j1ry.js → index-axzjaqve.js} +94 -142
- package/{index-3d9y066c.js → index-bjwxs904.js} +9 -8
- package/{index-667w0yah.js → index-c6eggbee.js} +2 -5
- package/{index-a1w6934z.js → index-ddxvdd1p.js} +169 -305
- package/{index-92zztqy1.js → index-dy0jx9ay.js} +2 -17
- package/{index-22xtgmhn.js → index-dzf3r49a.js} +1 -1
- package/{index-89hy31kf.js → index-fgq75haa.js} +172 -287
- package/{index-by5gpp8x.js → index-hj6w4ka1.js} +2 -1
- package/{index-hwkygn9s.js → index-khpfpcsd.js} +302 -33
- package/{index-vqx8bdxz.js → index-mxav6j3c.js} +78 -231
- package/{index-2446k7b4.js → index-myhc3c1j.js} +512 -588
- package/{index-3jrp517k.js → index-nv5976x3.js} +45 -128
- package/{index-zqgj2rt2.js → index-przybvy5.js} +69 -147
- package/{index-n3cv0tm4.js → index-smme4r47.js} +1 -1
- package/{index-qdgzsnhg.js → index-w2zdcw3k.js} +110 -231
- package/{index-14zrtgfa.js → index-wa5jbxj1.js} +6 -39
- package/{index-bftkz6cp.js → index-ww4z397j.js} +198 -555
- package/{index-kwaq3n9n.js → index-xqjwd384.js} +4 -7
- package/{index-txr7dj2g.js → index-xqpzbab5.js} +106 -240
- package/{index-e4v6vhxq.js → index-xsjh6nzh.js} +178 -165
- package/{index-18zybgkc.js → index-ygn77a5w.js} +47 -99
- package/{index-9d6nvkz2.js → index-ztp3e73t.js} +79 -35
- package/index.js +23 -24
- package/{library.command-cqr175dy.js → library.command-1gx04jgn.js} +4 -5
- package/{localRegistry.command-49z55qqz.js → localRegistry.command-zndxj1q6.js} +21 -42
- package/{module.command-9p8mm3b0.js → module.command-3waddk95.js} +6 -7
- package/{package.command-wxyz909t.js → package.command-zrc7br1y.js} +5 -6
- package/package.json +2 -2
- package/{page.command-gsrsfqqs.js → page.command-vpjgardh.js} +4 -5
- package/{primitive.command-ns1mptje.js → primitive.command-sz84tk4t.js} +9 -20
- package/{prompter-1f8d9pcc.js → prompter-ptaa3nk0.js} +2 -2
- package/{quality.command-e76k2q9k.js → quality.command-g869hafs.js} +129 -363
- package/{recipeScanner-fpagtgc1.js → recipeScanner-4b69ybe0.js} +1 -1
- package/{repair.command-pkrsm3cw.js → repair.command-chsnjvvv.js} +11 -35
- package/{scalar.command-0cpt3fc8.js → scalar.command-2v21newx.js} +5 -6
- package/skills/akan-code/SKILL.md +12 -4
- package/{subspace.command-fktpwp0g.js → subspace.command-ckcbwfp1.js} +39 -58
- package/templates/appSample/srvkit/{AuthGuard.ts → guards.ts} +3 -3
- package/templates/appSample/srvkit/{SessionInternalArg.ts → internalArgs.ts} +3 -3
- package/templates/client.ts +7 -30
- package/templates/lib/__lib/lib.dictionary.ts +0 -2
- package/templates/lib/__lib/lib.document.ts +0 -2
- package/templates/lib/sig.ts +8 -30
- package/templates/server.ts +0 -2
- package/{tunnel.command-d5jy9gk8.js → tunnel.command-kd2yr4fx.js} +5 -6
- package/{typeChecker-3xgrzc2v.js → typeChecker-px49mezc.js} +1 -1
- package/typecheck.proc.js +1 -1
- package/workflow.command-44252cqv.js +79 -0
- package/{workspace.command-gv84eh1p.js → workspace.command-fqcztt5p.js} +72 -107
- package/TunnelShare-2j6s511d.js +0 -9
- package/cloud.script-fwx3vb34.js +0 -20
- package/context.command-dsnw0jby.js +0 -80
- package/context.runner-rs4rj15d.js +0 -22
- package/devTuiView-1q1mdgrv.js +0 -9
- package/getDirname-dr3jz1xg.js +0 -7
- package/index-6wwzs1bc.js +0 -7
- package/workflow.command-6tkcysmx.js +0 -105
|
@@ -216,6 +216,8 @@ Do not narrate code. Do document the thing the code cannot say. Both halves are
|
|
|
216
216
|
## Client / Server Boundaries (`apps/**`, `libs/**`, `pkgs/akanjs/**`)
|
|
217
217
|
|
|
218
218
|
- Use `"use client";` at the top of client component files.
|
|
219
|
+
- A `"use client"` file re-exports by name (`export { A, B } from "./x"`), never with `export *`: the server knows a
|
|
220
|
+
client module only by the export names it spells out, so `akan start` and `akan build` stop on a star re-export.
|
|
219
221
|
- Be careful when importing client-only code from page or layout modules.
|
|
220
222
|
- Keep page props serializable unless the existing route pattern clearly allows otherwise.
|
|
221
223
|
- In domain UI the boundary is mechanical, not a judgment call: `Template`, `Zone`, and `Util` are always client components with `"use client"` on line 1; `Unit` and `View` are always server components and never carry the directive.
|
|
@@ -679,7 +681,9 @@ it.
|
|
|
679
681
|
(RFC 7591; Cursor's `cursor://` redirect admitted) plus the consent page `libs/shared/page/oauth/consent` — and
|
|
680
682
|
names itself as the issuer, so `/mcp` demands a bearer token, verifies its signature, and refuses one carrying no
|
|
681
683
|
`aud`. A token is the app's own access JWT plus `aud`/`iss`/`client_id`, so `AccountMiddleware` and the guards
|
|
682
|
-
judge it unchanged
|
|
684
|
+
judge it unchanged. The tokens this built-in server mints carry no scope; `auth.scopes` / `AKAN_MCP_SCOPES` are for
|
|
685
|
+
tokens an external issuer mints, and declared beside this server they refuse every one of its tokens with 403
|
|
686
|
+
`insufficient_scope`. Per-app knobs live in `env.server.*` under `oauth` (`consentPath` and
|
|
683
687
|
`signinPath` with the basePath, `clients`, `dynamicRegistration`, `allowedRedirectSchemes`, `accessTokenSeconds`,
|
|
684
688
|
`clientIdMetadata`, `enabled`, plus `issuer` / `resource` — set those when a tunnel or an edge makes the derived
|
|
685
689
|
origin wrong, because an MCP client compares the issuer byte for byte). `/mcp` reads the `Authorization` header only — a cookie is dropped at the door — and `JWT_SECRET` (or
|
|
@@ -189,11 +189,13 @@ export default page()
|
|
|
189
189
|
- OAuth resource metadata is published at `/.well-known/oauth-protected-resource` (and at that path plus the mount
|
|
190
190
|
path, the spelling most clients try first). `auth.authorizationServers` / `AKAN_MCP_AUTH_SERVERS`,
|
|
191
191
|
`auth.scopes` / `AKAN_MCP_SCOPES`, `auth.resource` / `AKAN_MCP_RESOURCE` and `auth.verify` configure it;
|
|
192
|
-
`insufficient_scope` is enforced only once scopes are declared.
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
issuer
|
|
192
|
+
`insufficient_scope` is enforced only once scopes are declared. Declare them only for tokens an external issuer
|
|
193
|
+
mints: the built-in authorization server's tokens carry no scope, so beside it every one of them would be refused
|
|
194
|
+
with 403. Naming an issuer changes three things at once: a request with no credential is answered 401 from
|
|
195
|
+
`initialize` on (an MCP client starts its OAuth flow on nothing else), a token carrying no `aud` is refused (that
|
|
196
|
+
issuer mints for its other resources too), and the challenge carries no `error` code (RFC 6750 §3.1 reserves it
|
|
197
|
+
for a credential that was presented). A server naming no issuer keeps anonymous access and accepts a first-party
|
|
198
|
+
token, which is bound by app and environment instead.
|
|
197
199
|
- **The boot log names every published entry with no dictionary `.desc()`.** An agent picks a tool by its
|
|
198
200
|
description, so a missing one is a broken tool. What the framework generates has no text of its own and borrows
|
|
199
201
|
the model's: the generated list reads the `.of()` label, and the base CRUD tools append the model's `.desc()` to
|
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
// @bun
|
|
2
|
+
import {
|
|
3
|
+
ApplicationBuildReporter,
|
|
4
|
+
HmrWatcher,
|
|
5
|
+
WatchRootResolver
|
|
6
|
+
} from "./index-khpfpcsd.js";
|
|
2
7
|
import {
|
|
3
8
|
CodegenLock,
|
|
4
9
|
WorkspaceExecutor,
|
|
5
10
|
AppExecutor
|
|
6
|
-
} from "./index-
|
|
7
|
-
import"./index-
|
|
8
|
-
import"./index-6wwzs1bc.js";
|
|
11
|
+
} from "./index-9d9bgcx4.js";
|
|
12
|
+
import"./index-5b6tavv3.js";
|
|
9
13
|
import {
|
|
10
14
|
GraphClientEntryDiscovery,
|
|
11
15
|
RouteClientBuilder,
|
|
12
16
|
AutoImportSync,
|
|
13
17
|
DevChangePlanner,
|
|
14
18
|
DevGeneratedIndexSync
|
|
15
|
-
} from "./index-
|
|
16
|
-
import {
|
|
17
|
-
HmrWatcher,
|
|
18
|
-
WatchRootResolver
|
|
19
|
-
} from "./index-hwkygn9s.js";
|
|
19
|
+
} from "./index-fgq75haa.js";
|
|
20
20
|
import {
|
|
21
21
|
prepareDevWatchBatch
|
|
22
22
|
} from "./index-a5vmm6x4.js";
|
|
@@ -30,12 +30,6 @@ import path from "path";
|
|
|
30
30
|
import { Logger } from "akanjs/common";
|
|
31
31
|
|
|
32
32
|
class BuildBatchRunner {
|
|
33
|
-
static #entryCandidates = (workspaceRoot) => [
|
|
34
|
-
path.join(workspaceRoot, "pkgs/@akanjs/devkit/incrementalBuilder/buildBatch.proc.ts"),
|
|
35
|
-
path.join(workspaceRoot, "node_modules/@akanjs/devkit/incrementalBuilder/buildBatch.proc.ts"),
|
|
36
|
-
path.join(import.meta.dir, "buildBatch.proc.js"),
|
|
37
|
-
path.join(import.meta.dir, "buildBatch.proc.ts")
|
|
38
|
-
];
|
|
39
33
|
#logger = new Logger("BuildBatchRunner");
|
|
40
34
|
#entry = null;
|
|
41
35
|
#workspaceRoot;
|
|
@@ -47,7 +41,12 @@ class BuildBatchRunner {
|
|
|
47
41
|
async#resolveEntry() {
|
|
48
42
|
if (this.#entry)
|
|
49
43
|
return this.#entry;
|
|
50
|
-
const candidates =
|
|
44
|
+
const candidates = [
|
|
45
|
+
path.join(this.#workspaceRoot, "pkgs/@akanjs/devkit/incrementalBuilder/buildBatch.proc.ts"),
|
|
46
|
+
path.join(this.#workspaceRoot, "node_modules/@akanjs/devkit/incrementalBuilder/buildBatch.proc.ts"),
|
|
47
|
+
path.join(import.meta.dir, "buildBatch.proc.js"),
|
|
48
|
+
path.join(import.meta.dir, "buildBatch.proc.ts")
|
|
49
|
+
];
|
|
51
50
|
for (const candidate of candidates) {
|
|
52
51
|
if (!await Bun.file(candidate).exists())
|
|
53
52
|
continue;
|
|
@@ -196,7 +195,7 @@ class IncrementalBuilder {
|
|
|
196
195
|
}
|
|
197
196
|
};
|
|
198
197
|
} catch (err) {
|
|
199
|
-
const errMsg = err
|
|
198
|
+
const errMsg = ApplicationBuildReporter.formatError(err, this.#app.workspace.workspaceRoot);
|
|
200
199
|
this.#logger.error(`build-route failed routeId=${msg.routeId}: ${errMsg}`);
|
|
201
200
|
this.#sendBuildStatus("route", { generation: msg.generation, ok: false, files: msg.seeds, message: errMsg });
|
|
202
201
|
return { type: "build-route-res", id: msg.id, ok: false, error: errMsg };
|
|
@@ -336,20 +335,19 @@ class IncrementalBuilder {
|
|
|
336
335
|
}, 150);
|
|
337
336
|
}
|
|
338
337
|
async installWatcher() {
|
|
339
|
-
const artifactDir = this.#artifactDir;
|
|
340
338
|
const roots = await new WatchRootResolver(this.#app).resolve();
|
|
341
339
|
const watcher = new HmrWatcher({
|
|
342
340
|
roots,
|
|
343
341
|
logger: this.#logger,
|
|
344
342
|
onBatch: async (batch) => {
|
|
345
|
-
await this.#enqueueWork("hmr-batch", async () => this.#handleWatchBatch(
|
|
343
|
+
await this.#enqueueWork("hmr-batch", async () => this.#handleWatchBatch(batch));
|
|
346
344
|
}
|
|
347
345
|
});
|
|
348
346
|
await watcher.start();
|
|
349
347
|
this.#watcher = watcher;
|
|
350
348
|
this.#logger.verbose(`watching ${roots.length} roots`);
|
|
351
349
|
}
|
|
352
|
-
async#handleWatchBatch(
|
|
350
|
+
async#handleWatchBatch(batch) {
|
|
353
351
|
const rawKinds = new Set(batch.kinds);
|
|
354
352
|
if (rawKinds.size === 0)
|
|
355
353
|
return;
|
|
@@ -402,7 +400,7 @@ class IncrementalBuilder {
|
|
|
402
400
|
}
|
|
403
401
|
const needs = [];
|
|
404
402
|
if (kinds.includes("code") && rebuildClient) {
|
|
405
|
-
if (this.#
|
|
403
|
+
if (this.#csrActive)
|
|
406
404
|
needs.push("csr");
|
|
407
405
|
else
|
|
408
406
|
this.#logger.verbose(`csr-rebundle skipped; request /__csr or ?csr=true (or set AKAN_DEV_CSR_REBUILD=1) to enable per-save CSR rebuilds`);
|
|
@@ -417,13 +415,10 @@ class IncrementalBuilder {
|
|
|
417
415
|
this.#logger.verbose(`css-rebuild scheduled generation=${generation}`);
|
|
418
416
|
}
|
|
419
417
|
}
|
|
420
|
-
async#runBatch({
|
|
421
|
-
generation,
|
|
422
|
-
needs,
|
|
423
|
-
changedFiles
|
|
424
|
-
}) {
|
|
418
|
+
async#runBatch(work) {
|
|
419
|
+
const { generation, needs, changedFiles } = work;
|
|
425
420
|
const started = Date.now();
|
|
426
|
-
const result = await this.#batchRunner.run(await this.#batchRequest(
|
|
421
|
+
const result = await this.#batchRunner.run(await this.#batchRequest(work), (msg) => BuilderChannel.emit(msg));
|
|
427
422
|
if (result.optimizedFonts)
|
|
428
423
|
this.#optimizedFonts = result.optimizedFonts;
|
|
429
424
|
if (result.cssAssets)
|
|
@@ -437,11 +432,7 @@ class IncrementalBuilder {
|
|
|
437
432
|
this.#logger.verbose(`css-rebuild checked (${Date.now() - started}ms)`);
|
|
438
433
|
return result;
|
|
439
434
|
}
|
|
440
|
-
async#batchRequest({
|
|
441
|
-
generation,
|
|
442
|
-
needs,
|
|
443
|
-
changedFiles
|
|
444
|
-
}) {
|
|
435
|
+
async#batchRequest({ generation, needs, changedFiles }) {
|
|
445
436
|
return {
|
|
446
437
|
appName: this.#app.name,
|
|
447
438
|
workspaceRoot: this.#app.workspace.workspaceRoot,
|
|
@@ -506,9 +497,6 @@ class IncrementalBuilder {
|
|
|
506
497
|
await BuilderChannel.send({ type: "build-csr-res", id: msg.id, ok: true });
|
|
507
498
|
});
|
|
508
499
|
}
|
|
509
|
-
#shouldRebuildCsr() {
|
|
510
|
-
return this.#csrActive;
|
|
511
|
-
}
|
|
512
500
|
static #csrArmedByEnv() {
|
|
513
501
|
return process.env.AKAN_DEV_CSR_REBUILD === "1";
|
|
514
502
|
}
|
|
@@ -607,23 +595,17 @@ class IncrementalBuilder {
|
|
|
607
595
|
builder.shutdown(msg.reason);
|
|
608
596
|
return;
|
|
609
597
|
}
|
|
610
|
-
if (msg.type
|
|
598
|
+
if (msg.type !== "build-route" && msg.type !== "build-csr")
|
|
599
|
+
return;
|
|
600
|
+
if (!builder || builder.shuttingDown) {
|
|
611
601
|
const error = builder?.shuttingDown ? recyclingError : bootingError;
|
|
612
|
-
|
|
613
|
-
BuilderChannel.emit({ type: "build-route-res", id: msg.id, ok: false, error });
|
|
614
|
-
return;
|
|
615
|
-
}
|
|
616
|
-
builder.handleBuildRoute(msg);
|
|
602
|
+
BuilderChannel.emit({ type: `${msg.type}-res`, id: msg.id, ok: false, error });
|
|
617
603
|
return;
|
|
618
604
|
}
|
|
619
|
-
if (msg.type === "build-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
BuilderChannel.emit({ type: "build-csr-res", id: msg.id, ok: false, error });
|
|
623
|
-
return;
|
|
624
|
-
}
|
|
605
|
+
if (msg.type === "build-route")
|
|
606
|
+
builder.handleBuildRoute(msg);
|
|
607
|
+
else
|
|
625
608
|
builder.handleBuildCsr(msg);
|
|
626
|
-
}
|
|
627
609
|
});
|
|
628
610
|
process.on("disconnect", () => {
|
|
629
611
|
logger.warn("host IPC channel closed; exiting builder");
|
|
@@ -648,7 +630,7 @@ class IncrementalBuilder {
|
|
|
648
630
|
await builder.rearmCsrFromEnv();
|
|
649
631
|
}
|
|
650
632
|
}
|
|
651
|
-
IncrementalBuilder.main().catch((err) => {
|
|
633
|
+
await IncrementalBuilder.main().catch((err) => {
|
|
652
634
|
console.error(err);
|
|
653
635
|
process.exit(1);
|
|
654
636
|
});
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
import {
|
|
3
3
|
jsonText
|
|
4
|
-
} from "./index-
|
|
4
|
+
} from "./index-myhc3c1j.js";
|
|
5
5
|
import {
|
|
6
6
|
akanMcpInstallTargets
|
|
7
|
-
} from "./index-
|
|
7
|
+
} from "./index-ygn77a5w.js";
|
|
8
8
|
import {
|
|
9
9
|
script
|
|
10
|
-
} from "./index-
|
|
10
|
+
} from "./index-w2zdcw3k.js";
|
|
11
11
|
import {
|
|
12
12
|
ContextRunner2
|
|
13
|
-
} from "./index-
|
|
13
|
+
} from "./index-nv5976x3.js";
|
|
14
14
|
|
|
15
15
|
// pkgs/@akanjs/cli/context/context.script.ts
|
|
16
16
|
import { Logger } from "akanjs/common";
|
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
} from "./index-
|
|
3
|
+
FileSys2
|
|
4
|
+
} from "./index-5b6tavv3.js";
|
|
5
|
+
import {
|
|
6
|
+
ConfigLock
|
|
7
|
+
} from "./index-3hx1z96c.js";
|
|
5
8
|
|
|
6
9
|
// pkgs/@akanjs/devkit/cloud/globalConfig.ts
|
|
7
|
-
import {
|
|
10
|
+
import { mkdir, rename, rm, writeFile } from "fs/promises";
|
|
8
11
|
import dayjs from "dayjs";
|
|
9
12
|
|
|
10
13
|
// pkgs/@akanjs/devkit/cloud/constants.ts
|
|
11
|
-
|
|
12
|
-
var
|
|
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");
|
|
13
17
|
var getDefaultHostConfig = (host = GlobalConfig.akanCloudHost) => ({ host });
|
|
14
18
|
var defaultAkanGlobalConfig = {
|
|
15
19
|
cloudHost: {},
|
|
@@ -21,8 +25,8 @@ var defaultAkanGlobalConfig = {
|
|
|
21
25
|
class GlobalConfig {
|
|
22
26
|
static akanCloudHost = process.env.USE_AKANJS_PKGS === "true" ? `http://localhost:${process.env.CLOUD_HOST_PORT ?? 8283}` : "https://cloud.akanjs.com";
|
|
23
27
|
static async#getAkanGlobalConfig() {
|
|
24
|
-
const exists = await
|
|
25
|
-
const akanConfig = exists ? await
|
|
28
|
+
const exists = await FileSys2.fileExists(configPath);
|
|
29
|
+
const akanConfig = exists ? await FileSys2.readJson(configPath) : {};
|
|
26
30
|
return {
|
|
27
31
|
...defaultAkanGlobalConfig,
|
|
28
32
|
...akanConfig,
|
|
@@ -33,50 +37,82 @@ class GlobalConfig {
|
|
|
33
37
|
}
|
|
34
38
|
static async#setAkanGlobalConfig(akanConfig) {
|
|
35
39
|
await mkdir(basePath, { recursive: true, mode: 448 });
|
|
36
|
-
|
|
37
|
-
|
|
40
|
+
const temp = `${configPath}.${process.pid}.${crypto.randomUUID()}.tmp`;
|
|
41
|
+
try {
|
|
42
|
+
await writeFile(temp, JSON.stringify(akanConfig, null, 2), { mode: 384 });
|
|
43
|
+
await rename(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
|
+
});
|
|
38
54
|
}
|
|
39
55
|
static async getHostConfig(host = GlobalConfig.akanCloudHost) {
|
|
40
56
|
const akanConfig = await GlobalConfig.#getAkanGlobalConfig();
|
|
41
57
|
return GlobalConfig.toHostConfig(akanConfig.cloudHost[host] ?? getDefaultHostConfig(host));
|
|
42
58
|
}
|
|
43
59
|
static async setHostConfig(config = getDefaultHostConfig()) {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
60
|
+
await GlobalConfig.#update((akanConfig) => GlobalConfig.#withHost(akanConfig, config));
|
|
61
|
+
}
|
|
62
|
+
static #withHost(akanConfig, config) {
|
|
63
|
+
return {
|
|
64
|
+
...akanConfig,
|
|
65
|
+
cloudHost: { ...akanConfig.cloudHost, [config.host]: GlobalConfig.toHostConfigDto(config) }
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
static async refreshHostAuth(host, refresh) {
|
|
69
|
+
try {
|
|
70
|
+
return await ConfigLock.run(GlobalConfig.#lockPath, async () => {
|
|
71
|
+
const akanConfig = await GlobalConfig.#getAkanGlobalConfig();
|
|
72
|
+
const current = GlobalConfig.toHostConfig(akanConfig.cloudHost[host] ?? getDefaultHostConfig(host));
|
|
73
|
+
const accessToken = current.auth?.accessToken;
|
|
74
|
+
const refreshToken = accessToken?.refreshToken;
|
|
75
|
+
if (!accessToken || !refreshToken || !GlobalConfig.needRefreshToken(accessToken))
|
|
76
|
+
return current;
|
|
77
|
+
const claimed = {
|
|
78
|
+
...current,
|
|
79
|
+
auth: { ...current.auth, accessToken: { ...accessToken, refreshToken: null } }
|
|
80
|
+
};
|
|
81
|
+
await GlobalConfig.#setAkanGlobalConfig(GlobalConfig.#withHost(akanConfig, claimed));
|
|
82
|
+
const next = await refresh(refreshToken).catch(() => null);
|
|
83
|
+
if (!next?.jwt)
|
|
84
|
+
return claimed;
|
|
85
|
+
const refreshed = { ...current, auth: { ...current.auth, accessToken: next } };
|
|
86
|
+
await GlobalConfig.#setAkanGlobalConfig(GlobalConfig.#withHost(await GlobalConfig.#getAkanGlobalConfig(), refreshed));
|
|
87
|
+
return refreshed;
|
|
88
|
+
});
|
|
89
|
+
} catch {
|
|
90
|
+
return await GlobalConfig.getHostConfig(host);
|
|
91
|
+
}
|
|
47
92
|
}
|
|
48
93
|
static async getRemoteEnvServers() {
|
|
49
|
-
|
|
50
|
-
return akanConfig.remoteEnvServers;
|
|
94
|
+
return (await GlobalConfig.#getAkanGlobalConfig()).remoteEnvServers;
|
|
51
95
|
}
|
|
52
96
|
static async setRemoteEnvServer(name, config) {
|
|
53
|
-
|
|
54
|
-
await GlobalConfig.#setAkanGlobalConfig({
|
|
97
|
+
await GlobalConfig.#update((akanConfig) => ({
|
|
55
98
|
...akanConfig,
|
|
56
|
-
remoteEnvServers: {
|
|
57
|
-
|
|
58
|
-
[name]: config
|
|
59
|
-
}
|
|
60
|
-
});
|
|
99
|
+
remoteEnvServers: { ...akanConfig.remoteEnvServers, [name]: config }
|
|
100
|
+
}));
|
|
61
101
|
}
|
|
62
102
|
static async removeRemoteEnvServer(name) {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
...akanConfig,
|
|
67
|
-
remoteEnvServers
|
|
103
|
+
await GlobalConfig.#update((akanConfig) => {
|
|
104
|
+
const { [name]: _, ...remoteEnvServers } = akanConfig.remoteEnvServers;
|
|
105
|
+
return { ...akanConfig, remoteEnvServers };
|
|
68
106
|
});
|
|
69
107
|
}
|
|
70
108
|
static async getTestTargets() {
|
|
71
|
-
|
|
72
|
-
return akanConfig.testTargets;
|
|
109
|
+
return (await GlobalConfig.#getAkanGlobalConfig()).testTargets;
|
|
73
110
|
}
|
|
74
111
|
static async setTestTargets(testTargets) {
|
|
75
|
-
|
|
76
|
-
await GlobalConfig.#setAkanGlobalConfig({
|
|
112
|
+
await GlobalConfig.#update((akanConfig) => ({
|
|
77
113
|
...akanConfig,
|
|
78
114
|
testTargets: { ...akanConfig.testTargets, ...testTargets }
|
|
79
|
-
});
|
|
115
|
+
}));
|
|
80
116
|
}
|
|
81
117
|
static needRefreshToken(accessToken) {
|
|
82
118
|
return !!accessToken?.expiresAt?.isBefore(dayjs().add(1, "hour"));
|
|
@@ -125,11 +161,7 @@ class HttpClient {
|
|
|
125
161
|
}
|
|
126
162
|
async get(url, { headers, signal } = {}) {
|
|
127
163
|
const response = await fetch(`${this.baseUrl}${url}`, {
|
|
128
|
-
headers: {
|
|
129
|
-
"Content-Type": "application/json",
|
|
130
|
-
...this.headers,
|
|
131
|
-
...headers
|
|
132
|
-
},
|
|
164
|
+
headers: { "Content-Type": "application/json", ...this.headers, ...headers },
|
|
133
165
|
...signal ? { signal } : {}
|
|
134
166
|
});
|
|
135
167
|
return await HttpClient.#body(response, url);
|
|
@@ -172,7 +204,12 @@ class CloudApi {
|
|
|
172
204
|
url;
|
|
173
205
|
static async fromHost(workspace, host) {
|
|
174
206
|
const hostConfig = await GlobalConfig.getHostConfig(host);
|
|
175
|
-
|
|
207
|
+
const accessToken = hostConfig.auth?.accessToken;
|
|
208
|
+
if (!accessToken || !GlobalConfig.needRefreshToken(accessToken))
|
|
209
|
+
return new CloudApi(workspace, hostConfig);
|
|
210
|
+
const tokenless = new CloudApi(workspace, hostConfig);
|
|
211
|
+
const refreshed = await GlobalConfig.refreshHostAuth(hostConfig.host, (refreshToken) => tokenless.refreshAuthToken(refreshToken));
|
|
212
|
+
return new CloudApi(workspace, refreshed);
|
|
176
213
|
}
|
|
177
214
|
constructor(workspace, hostConfig) {
|
|
178
215
|
this.#workspace = workspace;
|
|
@@ -181,16 +218,18 @@ class CloudApi {
|
|
|
181
218
|
this.url = `${this.host}/api`;
|
|
182
219
|
this.#api = new HttpClient(this.url);
|
|
183
220
|
if (this.#accessToken && !GlobalConfig.needRefreshToken(this.#accessToken))
|
|
184
|
-
this.#api.setHeaders({
|
|
185
|
-
|
|
186
|
-
|
|
221
|
+
this.#api.setHeaders({ Authorization: `Bearer ${this.#accessToken.jwt}` });
|
|
222
|
+
}
|
|
223
|
+
#authorize(accessTokenDto) {
|
|
224
|
+
this.#accessToken = GlobalConfig.toAccessToken(accessTokenDto);
|
|
225
|
+
this.#api.setHeaders({ Authorization: `Bearer ${this.#accessToken.jwt}` });
|
|
226
|
+
return this.#accessToken;
|
|
187
227
|
}
|
|
188
228
|
async uploadEnv(devProjectId, file) {
|
|
189
229
|
const formData = new FormData;
|
|
190
230
|
formData.append("devProjectId", devProjectId);
|
|
191
231
|
formData.append("file", file);
|
|
192
|
-
|
|
193
|
-
return data;
|
|
232
|
+
return await this.#api.post(`/uploadEnv/${devProjectId}`, formData);
|
|
194
233
|
}
|
|
195
234
|
async downloadEnv(devProjectId) {
|
|
196
235
|
const localPath = `${this.#workspace.workspaceRoot}/local/env.tar`;
|
|
@@ -199,34 +238,13 @@ class CloudApi {
|
|
|
199
238
|
}
|
|
200
239
|
async getRemoteAuthToken(remoteId) {
|
|
201
240
|
try {
|
|
202
|
-
|
|
203
|
-
this.#accessToken = GlobalConfig.toAccessToken(accessToken);
|
|
204
|
-
this.#api.setHeaders({
|
|
205
|
-
Authorization: `Bearer ${this.#accessToken.jwt}`
|
|
206
|
-
});
|
|
207
|
-
return this.#accessToken;
|
|
241
|
+
return this.#authorize(await this.#api.get(`/getRemoteAuthToken/${remoteId}`));
|
|
208
242
|
} catch (_) {
|
|
209
243
|
return null;
|
|
210
244
|
}
|
|
211
245
|
}
|
|
212
|
-
async#ensureAccessTokenLive({
|
|
213
|
-
allowUnauthorized = false
|
|
214
|
-
} = {}) {
|
|
215
|
-
if (!this.#accessToken)
|
|
216
|
-
throw new Error("No access token");
|
|
217
|
-
const needRefresh = GlobalConfig.needRefreshToken(this.#accessToken);
|
|
218
|
-
if (!needRefresh)
|
|
219
|
-
return this.#accessToken;
|
|
220
|
-
const refreshToken = this.#accessToken?.refreshToken;
|
|
221
|
-
if (!refreshToken)
|
|
222
|
-
throw new Error("No refresh token");
|
|
223
|
-
return await this.refreshAuthToken(refreshToken);
|
|
224
|
-
}
|
|
225
246
|
async refreshAuthToken(refreshToken) {
|
|
226
|
-
|
|
227
|
-
this.#accessToken = GlobalConfig.toAccessToken(response);
|
|
228
|
-
this.#api.setHeaders({ Authorization: `Bearer ${this.#accessToken.jwt}` });
|
|
229
|
-
return this.#accessToken;
|
|
247
|
+
return this.#authorize(await this.#api.post(`/refreshAuthToken`, { refreshToken }, { signal: AbortSignal.timeout(20000) }));
|
|
230
248
|
}
|
|
231
249
|
async requestTunnel(input) {
|
|
232
250
|
return await this.#api.post(`/tunnel/requestTunnel`, input);
|
|
@@ -239,8 +257,7 @@ class CloudApi {
|
|
|
239
257
|
}
|
|
240
258
|
async getRemoteSelf() {
|
|
241
259
|
try {
|
|
242
|
-
|
|
243
|
-
return data;
|
|
260
|
+
return await this.#api.get(`/getRemoteSelf`);
|
|
244
261
|
} catch {
|
|
245
262
|
return null;
|
|
246
263
|
}
|
|
@@ -5,33 +5,26 @@ import {
|
|
|
5
5
|
writeGeneratedSyncState,
|
|
6
6
|
createRepairReport,
|
|
7
7
|
renderRepairReport
|
|
8
|
-
} from "./index-
|
|
8
|
+
} from "./index-myhc3c1j.js";
|
|
9
9
|
import {
|
|
10
10
|
AkanContextAnalyzer
|
|
11
|
-
} from "./index-
|
|
11
|
+
} from "./index-ygn77a5w.js";
|
|
12
12
|
import {
|
|
13
13
|
runner
|
|
14
|
-
} from "./index-
|
|
14
|
+
} from "./index-w2zdcw3k.js";
|
|
15
15
|
|
|
16
16
|
// pkgs/@akanjs/cli/repair/repair.runner.ts
|
|
17
|
-
var
|
|
17
|
+
var spawnShell = async (workspace, command) => await workspace.spawn("bash", ["-lc", command.startsWith("akan ") ? `bun run ${command}` : command], {
|
|
18
|
+
cwd: workspace.workspaceRoot
|
|
19
|
+
});
|
|
18
20
|
var defaultExecutor = (workspace) => async (command) => {
|
|
21
|
+
const base = { command: command.command, reason: command.reason };
|
|
19
22
|
try {
|
|
20
|
-
|
|
21
|
-
cwd: workspace.workspaceRoot
|
|
22
|
-
});
|
|
23
|
-
return {
|
|
24
|
-
command: command.command,
|
|
25
|
-
reason: command.reason,
|
|
26
|
-
status: "passed",
|
|
27
|
-
exitCode: 0,
|
|
28
|
-
stdout
|
|
29
|
-
};
|
|
23
|
+
return { ...base, status: "passed", exitCode: 0, stdout: await spawnShell(workspace, command.command) };
|
|
30
24
|
} catch (error) {
|
|
31
25
|
const commandError = error;
|
|
32
26
|
return {
|
|
33
|
-
|
|
34
|
-
reason: command.reason,
|
|
27
|
+
...base,
|
|
35
28
|
status: "failed",
|
|
36
29
|
exitCode: commandError.code ?? 1,
|
|
37
30
|
stdout: commandError.stdout,
|
|
@@ -158,4 +151,4 @@ class RepairRunner extends runner("repair") {
|
|
|
158
151
|
}
|
|
159
152
|
}
|
|
160
153
|
|
|
161
|
-
export { RepairRunner };
|
|
154
|
+
export { spawnShell, RepairRunner };
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// pkgs/@akanjs/devkit/cloud/configLock.ts
|
|
3
|
+
import { mkdir, open, readFile, rm, stat } from "fs/promises";
|
|
4
|
+
import path from "path";
|
|
5
|
+
|
|
6
|
+
class ConfigLock {
|
|
7
|
+
static waitTimeoutMs = 30000;
|
|
8
|
+
static #unknownHolderStaleMs = 1e4;
|
|
9
|
+
static #pollMs = 50;
|
|
10
|
+
static async run(lockPath, fn) {
|
|
11
|
+
await mkdir(path.dirname(lockPath), { recursive: true, mode: 448 });
|
|
12
|
+
const deadline = Date.now() + ConfigLock.waitTimeoutMs;
|
|
13
|
+
while (!await ConfigLock.#take(lockPath)) {
|
|
14
|
+
if (Date.now() >= deadline) {
|
|
15
|
+
const holder = (await ConfigLock.#holder(lockPath))?.pid ?? "unknown";
|
|
16
|
+
throw new Error(`${lockPath} is held by process ${holder}; if no akan command is running, delete it and retry.`);
|
|
17
|
+
}
|
|
18
|
+
await ConfigLock.#reclaimIfAbandoned(lockPath);
|
|
19
|
+
await Bun.sleep(ConfigLock.#pollMs);
|
|
20
|
+
}
|
|
21
|
+
try {
|
|
22
|
+
return await fn();
|
|
23
|
+
} finally {
|
|
24
|
+
await rm(lockPath, { force: true });
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
static async#take(lockPath) {
|
|
28
|
+
const handle = await open(lockPath, "wx", 384).catch((error) => {
|
|
29
|
+
if (error.code === "EEXIST")
|
|
30
|
+
return null;
|
|
31
|
+
throw error;
|
|
32
|
+
});
|
|
33
|
+
if (!handle)
|
|
34
|
+
return false;
|
|
35
|
+
try {
|
|
36
|
+
await handle.writeFile(JSON.stringify({ pid: process.pid, at: Date.now() }));
|
|
37
|
+
} finally {
|
|
38
|
+
await handle.close();
|
|
39
|
+
}
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
static async#reclaimIfAbandoned(lockPath) {
|
|
43
|
+
if (!await ConfigLock.#abandoned(lockPath))
|
|
44
|
+
return;
|
|
45
|
+
const guard = `${lockPath}.reclaim`;
|
|
46
|
+
if (!await ConfigLock.#take(guard)) {
|
|
47
|
+
if (await ConfigLock.#abandoned(guard))
|
|
48
|
+
await rm(guard, { force: true });
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
try {
|
|
52
|
+
if (await ConfigLock.#abandoned(lockPath))
|
|
53
|
+
await rm(lockPath, { force: true });
|
|
54
|
+
} finally {
|
|
55
|
+
await rm(guard, { force: true });
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
static async#abandoned(lockPath) {
|
|
59
|
+
const info = await stat(lockPath).catch(() => null);
|
|
60
|
+
if (!info)
|
|
61
|
+
return false;
|
|
62
|
+
const holder = await ConfigLock.#holder(lockPath);
|
|
63
|
+
if (!holder)
|
|
64
|
+
return Date.now() - info.mtimeMs > ConfigLock.#unknownHolderStaleMs;
|
|
65
|
+
return !ConfigLock.#isAlive(holder.pid);
|
|
66
|
+
}
|
|
67
|
+
static async#holder(lockPath) {
|
|
68
|
+
try {
|
|
69
|
+
const parsed = JSON.parse(await readFile(lockPath, "utf8"));
|
|
70
|
+
return typeof parsed.pid === "number" && typeof parsed.at === "number" ? { pid: parsed.pid, at: parsed.at } : null;
|
|
71
|
+
} catch {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
static #isAlive(pid) {
|
|
76
|
+
if (!Number.isInteger(pid) || pid <= 0)
|
|
77
|
+
return false;
|
|
78
|
+
try {
|
|
79
|
+
process.kill(pid, 0);
|
|
80
|
+
return true;
|
|
81
|
+
} catch (error) {
|
|
82
|
+
return error.code === "EPERM";
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export { ConfigLock };
|