@akanjs/devkit 3.0.0-beta.1 → 3.0.0-beta.11
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/cloud/cloudApi.ts +35 -0
- package/executors.ts +1 -0
- package/package.json +2 -2
- package/subspace.test.ts +21 -0
- package/subspace.ts +33 -0
- package/subspaceConfig.ts +10 -0
package/cloud/cloudApi.ts
CHANGED
|
@@ -43,6 +43,30 @@ class HttpClient {
|
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
/**
|
|
47
|
+
* A share the control plane just issued. `token` is the connector credential — whoever holds it can become the
|
|
48
|
+
* origin behind `hostname`, so it is never written to disk or logged, only handed to the agent in memory.
|
|
49
|
+
*/
|
|
50
|
+
export interface TunnelGrant {
|
|
51
|
+
code: string;
|
|
52
|
+
hostname: string;
|
|
53
|
+
url: string;
|
|
54
|
+
/** The gateway to dial, as `wss://tunnel.akanjs.com`. The control plane names it; the CLI never assumes one. */
|
|
55
|
+
gatewayUrl: string;
|
|
56
|
+
token: string;
|
|
57
|
+
expiresAt: string | null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface TunnelSummary {
|
|
61
|
+
code: string;
|
|
62
|
+
hostname: string;
|
|
63
|
+
url: string;
|
|
64
|
+
name: string | null;
|
|
65
|
+
createdAt: string;
|
|
66
|
+
expiresAt: string | null;
|
|
67
|
+
connected: boolean;
|
|
68
|
+
}
|
|
69
|
+
|
|
46
70
|
export class CloudApi {
|
|
47
71
|
readonly #api: HttpClient;
|
|
48
72
|
#accessToken: AccessToken | null = null;
|
|
@@ -108,6 +132,17 @@ export class CloudApi {
|
|
|
108
132
|
this.#api.setHeaders({ Authorization: `Bearer ${this.#accessToken.jwt}` });
|
|
109
133
|
return this.#accessToken;
|
|
110
134
|
}
|
|
135
|
+
async requestTunnel(input: { name: string; ttlMinutes?: number }): Promise<TunnelGrant> {
|
|
136
|
+
return await this.#api.post<TunnelGrant>(`/requestTunnel`, input);
|
|
137
|
+
}
|
|
138
|
+
/** The generated name of the model's own `inSelf` slice: a hand-written `listTunnels` would collide with it. */
|
|
139
|
+
async tunnelListInSelf(): Promise<TunnelSummary[]> {
|
|
140
|
+
return await this.#api.get<TunnelSummary[]>(`/tunnelListInSelf`);
|
|
141
|
+
}
|
|
142
|
+
/** Hands the grant back. The CLI calls this "stop", which is what the operator is doing, not what it does. */
|
|
143
|
+
async revokeTunnel(code: string): Promise<boolean> {
|
|
144
|
+
return await this.#api.post<boolean>(`/revokeTunnel`, { code });
|
|
145
|
+
}
|
|
111
146
|
async getRemoteSelf(): Promise<{ id: string; nickname: string } | null> {
|
|
112
147
|
try {
|
|
113
148
|
const data = await this.#api.get<{ id: string; nickname: string }>(`/getRemoteSelf`);
|
package/executors.ts
CHANGED
|
@@ -1714,6 +1714,7 @@ export class AppExecutor extends SysExecutor {
|
|
|
1714
1714
|
executor: this,
|
|
1715
1715
|
getPath: (rel) => this.getPath(rel),
|
|
1716
1716
|
fileExists: (rel) => FileSys.fileExists(this.getPath(rel)),
|
|
1717
|
+
readFile: (rel) => this.readFile(rel),
|
|
1717
1718
|
writeFile: async (rel, content, opts) => {
|
|
1718
1719
|
await this.writeFile(rel, content, opts);
|
|
1719
1720
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@akanjs/devkit",
|
|
3
|
-
"version": "3.0.0-beta.
|
|
3
|
+
"version": "3.0.0-beta.11",
|
|
4
4
|
"sourceType": "module",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"@langchain/openai": "^1.4.6",
|
|
45
45
|
"@tailwindcss/node": "^4.3.0",
|
|
46
46
|
"@trapezedev/project": "^7.1.4",
|
|
47
|
-
"akanjs": "3.0.0-beta.
|
|
47
|
+
"akanjs": "3.0.0-beta.11",
|
|
48
48
|
"chalk": "^5.6.2",
|
|
49
49
|
"commander": "^14.0.3",
|
|
50
50
|
"dayjs": "^1.11.20",
|
package/subspace.test.ts
CHANGED
|
@@ -424,6 +424,27 @@ describe("SubspaceConfig", () => {
|
|
|
424
424
|
).toThrow(/duplicate subspace/);
|
|
425
425
|
});
|
|
426
426
|
|
|
427
|
+
test("refuses two subspaces on one cloud workspace, and takes a declaration with none", () => {
|
|
428
|
+
expect(
|
|
429
|
+
() =>
|
|
430
|
+
new SubspaceConfig({
|
|
431
|
+
subspaces: [
|
|
432
|
+
{ name: "a", repo: "a.git", apps: ["one"], workspaceId: "project" },
|
|
433
|
+
{ name: "b", repo: "b.git", apps: ["two"], workspaceId: "project" },
|
|
434
|
+
],
|
|
435
|
+
}),
|
|
436
|
+
).toThrow(/same workspaceId/);
|
|
437
|
+
expect(
|
|
438
|
+
() =>
|
|
439
|
+
new SubspaceConfig({
|
|
440
|
+
subspaces: [
|
|
441
|
+
{ name: "a", repo: "a.git", apps: ["one"] },
|
|
442
|
+
{ name: "b", repo: "b.git", apps: ["two"], workspaceId: "project" },
|
|
443
|
+
],
|
|
444
|
+
}),
|
|
445
|
+
).not.toThrow();
|
|
446
|
+
});
|
|
447
|
+
|
|
427
448
|
test("defaults the pushable branches and rejects anything else", () => {
|
|
428
449
|
const config = new SubspaceConfig({ subspaces: [{ name: "a", repo: "a.git", apps: ["one"] }] });
|
|
429
450
|
expect(config.pushableBranches).toEqual(SubspaceConfig.defaultPushableBranches);
|
package/subspace.ts
CHANGED
|
@@ -55,6 +55,17 @@ export interface SubspacePushResult {
|
|
|
55
55
|
prunedDependencies?: string[];
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
export interface SubspaceEnvUploadResult {
|
|
59
|
+
name: string;
|
|
60
|
+
/** The cloud workspace whose env archive was replaced: the subspace's own, never this workspace's. */
|
|
61
|
+
workspaceId: string;
|
|
62
|
+
host: string;
|
|
63
|
+
outcome: "uploaded" | "cancelled";
|
|
64
|
+
apps: string[];
|
|
65
|
+
libs: string[];
|
|
66
|
+
files: string[];
|
|
67
|
+
}
|
|
68
|
+
|
|
58
69
|
export interface SubspacePullResult {
|
|
59
70
|
name: string;
|
|
60
71
|
applied: string[];
|
|
@@ -100,6 +111,9 @@ export class Subspace {
|
|
|
100
111
|
get apps() {
|
|
101
112
|
return this.#declaration.apps;
|
|
102
113
|
}
|
|
114
|
+
get workspaceId() {
|
|
115
|
+
return this.#declaration.workspaceId;
|
|
116
|
+
}
|
|
103
117
|
get #remote() {
|
|
104
118
|
return `subspace-${this.#declaration.name}`;
|
|
105
119
|
}
|
|
@@ -700,6 +714,25 @@ export function formatSubspacePullResult(result: SubspacePullResult) {
|
|
|
700
714
|
return sections.join("\n");
|
|
701
715
|
}
|
|
702
716
|
|
|
717
|
+
export function formatSubspaceEnvUpload(result: SubspaceEnvUploadResult) {
|
|
718
|
+
const sections = [
|
|
719
|
+
`Akan Subspace Env Upload — ${result.name}`,
|
|
720
|
+
`cloud workspace: ${result.workspaceId} · host: ${result.host}`,
|
|
721
|
+
"",
|
|
722
|
+
...(result.outcome === "cancelled"
|
|
723
|
+
? [" cancelled — the subspace's env archive is untouched."]
|
|
724
|
+
: [
|
|
725
|
+
`Uploaded (${result.files.length}), replacing that workspace's whole archive:`,
|
|
726
|
+
"",
|
|
727
|
+
...result.files.map((file) => ` ${file}`),
|
|
728
|
+
"",
|
|
729
|
+
` apps: ${result.apps.join(", ")}`,
|
|
730
|
+
` libs: ${result.libs.join(", ") || "(none)"}`,
|
|
731
|
+
]),
|
|
732
|
+
];
|
|
733
|
+
return sections.join("\n");
|
|
734
|
+
}
|
|
735
|
+
|
|
703
736
|
export function formatSubspaceDiff(result: SubspaceDiffResult) {
|
|
704
737
|
const total = result.app.files.length + result.libs.files.length;
|
|
705
738
|
const sections = [
|
package/subspaceConfig.ts
CHANGED
|
@@ -37,6 +37,7 @@ export class SubspaceConfig {
|
|
|
37
37
|
#assertValid() {
|
|
38
38
|
const seenNames = new Set<string>();
|
|
39
39
|
const appOwners = new Map<string, string>();
|
|
40
|
+
const idOwners = new Map<string, string>();
|
|
40
41
|
for (const subspace of this.subspaces) {
|
|
41
42
|
if (!subspace.name || !subspace.repo)
|
|
42
43
|
throw new Error(`${SubspaceConfig.fileName}: every subspace needs a name and a repo`);
|
|
@@ -55,6 +56,15 @@ export class SubspaceConfig {
|
|
|
55
56
|
);
|
|
56
57
|
appOwners.set(app, subspace.name);
|
|
57
58
|
}
|
|
59
|
+
if (!subspace.workspaceId) continue;
|
|
60
|
+
const idOwner = idOwners.get(subspace.workspaceId);
|
|
61
|
+
//* An env upload replaces the cloud workspace's archive whole, so two subspaces sharing one would
|
|
62
|
+
//* each overwrite the other's values.
|
|
63
|
+
if (idOwner)
|
|
64
|
+
throw new Error(
|
|
65
|
+
`${SubspaceConfig.fileName}: subspaces "${idOwner}" and "${subspace.name}" declare the same workspaceId`,
|
|
66
|
+
);
|
|
67
|
+
idOwners.set(subspace.workspaceId, subspace.name);
|
|
58
68
|
}
|
|
59
69
|
}
|
|
60
70
|
|