@hanzo/build 0.2.0
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/LICENSE-APACHE +205 -0
- package/LICENSE-MIT +21 -0
- package/NOTICE +37 -0
- package/README.md +120 -0
- package/lib/account.d.ts +11 -0
- package/lib/account.js +26 -0
- package/lib/api/call.d.ts +38 -0
- package/lib/api/call.js +75 -0
- package/lib/api/coding.d.ts +56 -0
- package/lib/api/coding.js +58 -0
- package/lib/api/git.d.ts +27 -0
- package/lib/api/git.js +39 -0
- package/lib/api/github.d.ts +78 -0
- package/lib/api/github.js +73 -0
- package/lib/api/models.d.ts +17 -0
- package/lib/api/models.js +28 -0
- package/lib/api/places.d.ts +25 -0
- package/lib/api/places.js +31 -0
- package/lib/api/platform.d.ts +59 -0
- package/lib/api/platform.js +69 -0
- package/lib/api/projects.d.ts +52 -0
- package/lib/api/projects.js +97 -0
- package/lib/api/sessions.d.ts +74 -0
- package/lib/api/sessions.js +120 -0
- package/lib/api/sse.d.ts +24 -0
- package/lib/api/sse.js +68 -0
- package/lib/api/turn.d.ts +51 -0
- package/lib/api/turn.js +121 -0
- package/lib/api/verdict.d.ts +12 -0
- package/lib/api/verdict.js +20 -0
- package/lib/builder.d.ts +5 -0
- package/lib/builder.js +81 -0
- package/lib/data.d.ts +42 -0
- package/lib/data.js +178 -0
- package/lib/host.d.ts +57 -0
- package/lib/host.js +26 -0
- package/lib/index.d.ts +11 -0
- package/lib/index.js +10 -0
- package/lib/landing.d.ts +13 -0
- package/lib/landing.js +132 -0
- package/lib/out.d.ts +10 -0
- package/lib/out.js +4 -0
- package/lib/project.d.ts +3 -0
- package/lib/project.js +250 -0
- package/lib/publish.d.ts +15 -0
- package/lib/publish.js +94 -0
- package/lib/route.d.ts +30 -0
- package/lib/route.js +28 -0
- package/lib/run.d.ts +3 -0
- package/lib/run.js +67 -0
- package/lib/shelf.d.ts +2 -0
- package/lib/shelf.js +60 -0
- package/lib/tools.d.ts +25 -0
- package/lib/tools.js +64 -0
- package/lib/voice.d.ts +17 -0
- package/lib/voice.js +86 -0
- package/package.json +70 -0
package/lib/api/git.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A repository's files at one revision, from the platform's native git.
|
|
3
|
+
*
|
|
4
|
+
* GET /v1/git/repos/{name}/tree?ref=&path= one directory, dirs first
|
|
5
|
+
* GET /v1/git/repos/{name}/blob?ref=&path= one file; binary is base64,
|
|
6
|
+
* past 1 MiB it is truncated with no content
|
|
7
|
+
*
|
|
8
|
+
* A run pushes its branch to native git, so these read what a run wrote at the
|
|
9
|
+
* branch it wrote it on.
|
|
10
|
+
*/
|
|
11
|
+
import { type Target } from './call.ts';
|
|
12
|
+
export interface Entry {
|
|
13
|
+
name: string;
|
|
14
|
+
path: string;
|
|
15
|
+
dir: boolean;
|
|
16
|
+
size: number;
|
|
17
|
+
}
|
|
18
|
+
export interface Blob {
|
|
19
|
+
path: string;
|
|
20
|
+
/** Text, or '' when the file is binary or past the view cap. */
|
|
21
|
+
text: string;
|
|
22
|
+
binary: boolean;
|
|
23
|
+
truncated: boolean;
|
|
24
|
+
size: number;
|
|
25
|
+
}
|
|
26
|
+
export declare function tree(t: Target, repo: string, ref: string, path?: string): Promise<Entry[]>;
|
|
27
|
+
export declare function blob(t: Target, repo: string, ref: string, path: string): Promise<Blob>;
|
package/lib/api/git.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A repository's files at one revision, from the platform's native git.
|
|
3
|
+
*
|
|
4
|
+
* GET /v1/git/repos/{name}/tree?ref=&path= one directory, dirs first
|
|
5
|
+
* GET /v1/git/repos/{name}/blob?ref=&path= one file; binary is base64,
|
|
6
|
+
* past 1 MiB it is truncated with no content
|
|
7
|
+
*
|
|
8
|
+
* A run pushes its branch to native git, so these read what a run wrote at the
|
|
9
|
+
* branch it wrote it on.
|
|
10
|
+
*/
|
|
11
|
+
import { call, query, seg } from './call.js';
|
|
12
|
+
const str = (v) => (typeof v === 'string' ? v : '');
|
|
13
|
+
export async function tree(t, repo, ref, path = '') {
|
|
14
|
+
const raw = await call(t, 'GET', `/v1/git/repos/${seg(repo)}/tree${query({ ref, path })}`);
|
|
15
|
+
const rows = Array.isArray(raw?.entries) ? raw.entries : [];
|
|
16
|
+
return rows
|
|
17
|
+
.map((r) => {
|
|
18
|
+
const o = (r && typeof r === 'object' ? r : {});
|
|
19
|
+
return {
|
|
20
|
+
name: str(o.name),
|
|
21
|
+
path: str(o.path),
|
|
22
|
+
dir: o.type === 'tree',
|
|
23
|
+
size: typeof o.size === 'number' ? o.size : 0,
|
|
24
|
+
};
|
|
25
|
+
})
|
|
26
|
+
.filter((e) => e.path);
|
|
27
|
+
}
|
|
28
|
+
export async function blob(t, repo, ref, path) {
|
|
29
|
+
const o = (await call(t, 'GET', `/v1/git/repos/${seg(repo)}/blob${query({ ref, path })}`)) ?? {};
|
|
30
|
+
const binary = o.binary === true;
|
|
31
|
+
const truncated = o.truncated === true;
|
|
32
|
+
return {
|
|
33
|
+
path: str(o.path) || path,
|
|
34
|
+
text: binary || truncated ? '' : str(o.content),
|
|
35
|
+
binary,
|
|
36
|
+
truncated,
|
|
37
|
+
size: typeof o.size === 'number' ? o.size : 0,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub, as the platform's GitHub App sees it for the signed-in person.
|
|
3
|
+
*
|
|
4
|
+
* GET /v1/provider/github/repos?q=&owner=&limit=&after= most recently pushed first
|
|
5
|
+
* GET /v1/provider/github/repos/{owner}/{repo}/branches?q=&limit=&after= default first
|
|
6
|
+
* GET /v1/provider/github/user the person's own connection
|
|
7
|
+
* POST /v1/provider/github/user/connect → {authorizeUrl}
|
|
8
|
+
* POST /v1/provider/github/user/disconnect
|
|
9
|
+
*
|
|
10
|
+
* Paged by an opaque `after` cursor; `next` is the cursor for the page after
|
|
11
|
+
* this one, or empty on the last. Rows are read defensively: a field the
|
|
12
|
+
* platform left out is an empty value here, never `undefined` in a label.
|
|
13
|
+
*
|
|
14
|
+
* `connected` false with repositories is an org admin seeing the org's
|
|
15
|
+
* installations; false with none is the cue to connect. GitHub returns a
|
|
16
|
+
* connecting person to the console's /connectors?complete=github&grant=<id>,
|
|
17
|
+
* which completes it.
|
|
18
|
+
*/
|
|
19
|
+
import { type Target } from './call.ts';
|
|
20
|
+
export interface Repo {
|
|
21
|
+
owner: string;
|
|
22
|
+
name: string;
|
|
23
|
+
/** `owner/name`. */
|
|
24
|
+
full_name: string;
|
|
25
|
+
private: boolean;
|
|
26
|
+
default_branch: string;
|
|
27
|
+
/** RFC 3339, or '' when GitHub did not say. */
|
|
28
|
+
pushed_at: string;
|
|
29
|
+
/** The App installation that grants access, or 0 for the person's own grant. */
|
|
30
|
+
installation_id: number;
|
|
31
|
+
}
|
|
32
|
+
export interface Repos {
|
|
33
|
+
repos: Repo[];
|
|
34
|
+
next: string;
|
|
35
|
+
/** How many repositories match, across every page. */
|
|
36
|
+
total: number;
|
|
37
|
+
/** The installations that could not be read: a short list is then not the whole one. */
|
|
38
|
+
unread: string[];
|
|
39
|
+
/** Whether this answer is the person's OWN GitHub. */
|
|
40
|
+
connected: boolean;
|
|
41
|
+
}
|
|
42
|
+
export interface Branch {
|
|
43
|
+
name: string;
|
|
44
|
+
/** The sha it points at, or ''. */
|
|
45
|
+
commit: string;
|
|
46
|
+
default: boolean;
|
|
47
|
+
}
|
|
48
|
+
export interface Branches {
|
|
49
|
+
branches: Branch[];
|
|
50
|
+
next: string;
|
|
51
|
+
total: number;
|
|
52
|
+
}
|
|
53
|
+
export interface Connection {
|
|
54
|
+
/** Whether this deployment can connect anyone at all. */
|
|
55
|
+
configured: boolean;
|
|
56
|
+
connected: boolean;
|
|
57
|
+
/** The GitHub login, when connected. */
|
|
58
|
+
login: string;
|
|
59
|
+
}
|
|
60
|
+
export declare function repo(raw: unknown): Repo;
|
|
61
|
+
export declare function branch(raw: unknown): Branch;
|
|
62
|
+
export interface RepoQuery {
|
|
63
|
+
q?: string;
|
|
64
|
+
owner?: string;
|
|
65
|
+
limit?: number;
|
|
66
|
+
after?: string;
|
|
67
|
+
}
|
|
68
|
+
export declare function repos(t: Target, p?: RepoQuery, signal?: AbortSignal): Promise<Repos>;
|
|
69
|
+
export interface BranchQuery {
|
|
70
|
+
q?: string;
|
|
71
|
+
limit?: number;
|
|
72
|
+
after?: string;
|
|
73
|
+
}
|
|
74
|
+
export declare function branches(t: Target, owner: string, name: string, p?: BranchQuery, signal?: AbortSignal): Promise<Branches>;
|
|
75
|
+
export declare function connection(t: Target): Promise<Connection>;
|
|
76
|
+
/** Where to send the person to authorize the platform's GitHub App — github.com only. */
|
|
77
|
+
export declare function connect(t: Target): Promise<string>;
|
|
78
|
+
export declare function disconnect(t: Target): Promise<void>;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub, as the platform's GitHub App sees it for the signed-in person.
|
|
3
|
+
*
|
|
4
|
+
* GET /v1/provider/github/repos?q=&owner=&limit=&after= most recently pushed first
|
|
5
|
+
* GET /v1/provider/github/repos/{owner}/{repo}/branches?q=&limit=&after= default first
|
|
6
|
+
* GET /v1/provider/github/user the person's own connection
|
|
7
|
+
* POST /v1/provider/github/user/connect → {authorizeUrl}
|
|
8
|
+
* POST /v1/provider/github/user/disconnect
|
|
9
|
+
*
|
|
10
|
+
* Paged by an opaque `after` cursor; `next` is the cursor for the page after
|
|
11
|
+
* this one, or empty on the last. Rows are read defensively: a field the
|
|
12
|
+
* platform left out is an empty value here, never `undefined` in a label.
|
|
13
|
+
*
|
|
14
|
+
* `connected` false with repositories is an org admin seeing the org's
|
|
15
|
+
* installations; false with none is the cue to connect. GitHub returns a
|
|
16
|
+
* connecting person to the console's /connectors?complete=github&grant=<id>,
|
|
17
|
+
* which completes it.
|
|
18
|
+
*/
|
|
19
|
+
import { call, query, seg } from './call.js';
|
|
20
|
+
const str = (v) => (typeof v === 'string' ? v : '');
|
|
21
|
+
const num = (v) => (typeof v === 'number' && Number.isFinite(v) ? v : 0);
|
|
22
|
+
const obj = (v) => v && typeof v === 'object' ? v : {};
|
|
23
|
+
export function repo(raw) {
|
|
24
|
+
const r = obj(raw);
|
|
25
|
+
const owner = str(r.owner);
|
|
26
|
+
const name = str(r.name);
|
|
27
|
+
return {
|
|
28
|
+
owner,
|
|
29
|
+
name,
|
|
30
|
+
full_name: str(r.full_name) || (owner && name ? `${owner}/${name}` : name),
|
|
31
|
+
private: r.private === true,
|
|
32
|
+
default_branch: str(r.default_branch),
|
|
33
|
+
pushed_at: str(r.pushed_at),
|
|
34
|
+
installation_id: num(r.installation_id),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
export function branch(raw) {
|
|
38
|
+
const b = obj(raw);
|
|
39
|
+
return { name: str(b.name), commit: str(b.commit), default: b.default === true };
|
|
40
|
+
}
|
|
41
|
+
export async function repos(t, p = {}, signal) {
|
|
42
|
+
const raw = obj(await call(t, 'GET', `/v1/provider/github/repos${query({ q: p.q?.trim(), owner: p.owner, limit: p.limit ?? 50, after: p.after })}`, undefined, { signal }));
|
|
43
|
+
return {
|
|
44
|
+
repos: (Array.isArray(raw.repos) ? raw.repos : []).map(repo).filter((r) => r.name),
|
|
45
|
+
next: str(raw.next),
|
|
46
|
+
total: num(raw.total),
|
|
47
|
+
unread: (Array.isArray(raw.unread) ? raw.unread : []).filter((x) => typeof x === 'string'),
|
|
48
|
+
connected: raw.connected === true,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
export async function branches(t, owner, name, p = {}, signal) {
|
|
52
|
+
const raw = obj(await call(t, 'GET', `/v1/provider/github/repos/${seg(owner)}/${seg(name)}/branches${query({ q: p.q?.trim(), limit: p.limit ?? 50, after: p.after })}`, undefined, { signal }));
|
|
53
|
+
return {
|
|
54
|
+
branches: (Array.isArray(raw.branches) ? raw.branches : []).map(branch).filter((b) => b.name),
|
|
55
|
+
next: str(raw.next),
|
|
56
|
+
total: num(raw.total),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
export async function connection(t) {
|
|
60
|
+
const raw = obj(await call(t, 'GET', '/v1/provider/github/user'));
|
|
61
|
+
return { configured: raw.configured === true, connected: raw.connected === true, login: str(raw.login) };
|
|
62
|
+
}
|
|
63
|
+
/** Where to send the person to authorize the platform's GitHub App — github.com only. */
|
|
64
|
+
export async function connect(t) {
|
|
65
|
+
const raw = obj(await call(t, 'POST', '/v1/provider/github/user/connect'));
|
|
66
|
+
const url = str(raw.authorizeUrl);
|
|
67
|
+
if (!/^https:\/\/github\.com\//.test(url))
|
|
68
|
+
throw new Error('The platform did not name a GitHub address to connect at');
|
|
69
|
+
return url;
|
|
70
|
+
}
|
|
71
|
+
export async function disconnect(t) {
|
|
72
|
+
await call(t, 'POST', '/v1/provider/github/user/disconnect');
|
|
73
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The models a run can be routed through, from the platform's own catalog.
|
|
3
|
+
*
|
|
4
|
+
* GET /v1/models → {data: [{id, …}]}
|
|
5
|
+
*
|
|
6
|
+
* `enso` is the router: it picks a model per step, which is why it is the
|
|
7
|
+
* default and why it is not a model a person has to understand to use.
|
|
8
|
+
*/
|
|
9
|
+
import { type Target } from './call.ts';
|
|
10
|
+
export interface Model {
|
|
11
|
+
id: string;
|
|
12
|
+
label: string;
|
|
13
|
+
}
|
|
14
|
+
export declare const ENSO = "enso";
|
|
15
|
+
/** A readable name for an id: `anthropic/claude-opus-5.5` → `Claude Opus 5.5`, `zen5-coder` → `Zen5 Coder`. */
|
|
16
|
+
export declare function label(id: string): string;
|
|
17
|
+
export declare function models(t: Target): Promise<Model[]>;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The models a run can be routed through, from the platform's own catalog.
|
|
3
|
+
*
|
|
4
|
+
* GET /v1/models → {data: [{id, …}]}
|
|
5
|
+
*
|
|
6
|
+
* `enso` is the router: it picks a model per step, which is why it is the
|
|
7
|
+
* default and why it is not a model a person has to understand to use.
|
|
8
|
+
*/
|
|
9
|
+
import { call } from './call.js';
|
|
10
|
+
export const ENSO = 'enso';
|
|
11
|
+
/** A readable name for an id: `anthropic/claude-opus-5.5` → `Claude Opus 5.5`, `zen5-coder` → `Zen5 Coder`. */
|
|
12
|
+
export function label(id) {
|
|
13
|
+
const tail = id.slice(id.lastIndexOf('/') + 1).replace(/:.*$/, '');
|
|
14
|
+
return tail
|
|
15
|
+
.split('-')
|
|
16
|
+
.filter(Boolean)
|
|
17
|
+
.map((w) => (/^\d/.test(w) ? w : w[0].toUpperCase() + w.slice(1)))
|
|
18
|
+
.join(' ');
|
|
19
|
+
}
|
|
20
|
+
export async function models(t) {
|
|
21
|
+
const raw = await call(t, 'GET', '/v1/models');
|
|
22
|
+
const rows = Array.isArray(raw?.data) ? raw.data : [];
|
|
23
|
+
const ids = rows
|
|
24
|
+
.map((r) => (r && typeof r === 'object' && typeof r.id === 'string' ? r.id : ''))
|
|
25
|
+
.filter((id) => id && !id.endsWith(':batch'));
|
|
26
|
+
const out = [...new Set([ENSO, ...ids])];
|
|
27
|
+
return out.map((id) => ({ id, label: label(id) }));
|
|
28
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a run can run: the platform's sandbox, or one of the org's machines.
|
|
3
|
+
*
|
|
4
|
+
* GET /v1/agent/targets → {targets: [...]}, newest first
|
|
5
|
+
*
|
|
6
|
+
* The sandbox is not a row the platform lists — it is what a run gets when it
|
|
7
|
+
* names no target — so it is the first place here and carries no id. A machine
|
|
8
|
+
* can take a run only while it is online.
|
|
9
|
+
*/
|
|
10
|
+
import { type Target } from './call.ts';
|
|
11
|
+
export interface Place {
|
|
12
|
+
/** `tgt_…`, or '' for the sandbox. */
|
|
13
|
+
id: string;
|
|
14
|
+
label: string;
|
|
15
|
+
/** laptop | gpu | cloud | … as the platform names it; 'sandbox' for the sandbox. */
|
|
16
|
+
kind: string;
|
|
17
|
+
/** online | offline | draining; the sandbox is always online. */
|
|
18
|
+
status: string;
|
|
19
|
+
/** "10 vCPU / 122G / 1× GB10", when the machine reported one. */
|
|
20
|
+
capacity: string;
|
|
21
|
+
}
|
|
22
|
+
export declare const SANDBOX: Place;
|
|
23
|
+
export declare function places(t: Target): Promise<Place[]>;
|
|
24
|
+
/** Whether a run sent here now would be taken. */
|
|
25
|
+
export declare const ready: (p: Place) => boolean;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a run can run: the platform's sandbox, or one of the org's machines.
|
|
3
|
+
*
|
|
4
|
+
* GET /v1/agent/targets → {targets: [...]}, newest first
|
|
5
|
+
*
|
|
6
|
+
* The sandbox is not a row the platform lists — it is what a run gets when it
|
|
7
|
+
* names no target — so it is the first place here and carries no id. A machine
|
|
8
|
+
* can take a run only while it is online.
|
|
9
|
+
*/
|
|
10
|
+
import { call } from './call.js';
|
|
11
|
+
export const SANDBOX = { id: '', label: 'Hanzo sandbox', kind: 'sandbox', status: 'online', capacity: '' };
|
|
12
|
+
const str = (v) => (typeof v === 'string' ? v : '');
|
|
13
|
+
export async function places(t) {
|
|
14
|
+
const raw = (await call(t, 'GET', '/v1/agent/targets')) ?? {};
|
|
15
|
+
const rows = Array.isArray(raw.targets) ? raw.targets : [];
|
|
16
|
+
const machines = rows
|
|
17
|
+
.map((r) => {
|
|
18
|
+
const o = (r && typeof r === 'object' ? r : {});
|
|
19
|
+
return {
|
|
20
|
+
id: str(o.id),
|
|
21
|
+
label: str(o.label) || str(o.host) || str(o.id),
|
|
22
|
+
kind: str(o.kind),
|
|
23
|
+
status: str(o.status),
|
|
24
|
+
capacity: str(o.capacity),
|
|
25
|
+
};
|
|
26
|
+
})
|
|
27
|
+
.filter((p) => p.id);
|
|
28
|
+
return [SANDBOX, ...machines];
|
|
29
|
+
}
|
|
30
|
+
/** Whether a run sent here now would be taken. */
|
|
31
|
+
export const ready = (p) => p.status === 'online';
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adding a repository to a project, which is how a project is published.
|
|
3
|
+
*
|
|
4
|
+
* POST /v1/platform/apps {repo, ref, name, partOf, mode, tag?}
|
|
5
|
+
* → 202 {app, build?: {id, job, image, status}, declaration: {mode, ref, review, live, …}}
|
|
6
|
+
* GET /v1/platform/builds → {builds: [{id, repo, commit, status, startedAt, duration}]}
|
|
7
|
+
*
|
|
8
|
+
* The declaration is a values file in universe. `branch` (the default) opens a
|
|
9
|
+
* review and deploys nothing; `commit` writes main and CD applies it — and it
|
|
10
|
+
* refuses to name an image that is not built yet, so publishing to main is two
|
|
11
|
+
* calls: build on a branch, then commit that tag once the build is green. An
|
|
12
|
+
* org admin gets both; anyone else gets the review link.
|
|
13
|
+
*/
|
|
14
|
+
import { type Target } from './call.ts';
|
|
15
|
+
export interface Declare {
|
|
16
|
+
/** The https clone URL. */
|
|
17
|
+
repo: string;
|
|
18
|
+
/** Branch, tag or sha to build. */
|
|
19
|
+
ref: string;
|
|
20
|
+
/** The app's name: a DNS-1123 label. */
|
|
21
|
+
name: string;
|
|
22
|
+
/** The project it joins. */
|
|
23
|
+
project: string;
|
|
24
|
+
mode: 'branch' | 'commit';
|
|
25
|
+
/** Release an image an earlier call built, instead of building. */
|
|
26
|
+
tag?: string;
|
|
27
|
+
}
|
|
28
|
+
export interface Declared {
|
|
29
|
+
build: {
|
|
30
|
+
id: string;
|
|
31
|
+
job: string;
|
|
32
|
+
image: string;
|
|
33
|
+
status: string;
|
|
34
|
+
} | null;
|
|
35
|
+
mode: string;
|
|
36
|
+
/** Where the declaration landed: main for a commit, the review branch otherwise. */
|
|
37
|
+
ref: string;
|
|
38
|
+
/** The pull request to open for a branch write, or ''. */
|
|
39
|
+
review: string;
|
|
40
|
+
/** Whether anything can deploy from this write. */
|
|
41
|
+
live: boolean;
|
|
42
|
+
}
|
|
43
|
+
export interface Build {
|
|
44
|
+
id: string;
|
|
45
|
+
repo: string;
|
|
46
|
+
commit: string;
|
|
47
|
+
status: string;
|
|
48
|
+
startedAt: string;
|
|
49
|
+
duration: string;
|
|
50
|
+
}
|
|
51
|
+
/** A DNS-1123 label from anything: what an app may be named. */
|
|
52
|
+
export declare function label(s: string): string;
|
|
53
|
+
/** An https address, or '' — a review link is shown as a link only when it is one. */
|
|
54
|
+
export declare const https: (url: string) => string;
|
|
55
|
+
export declare function body(d: Declare): Record<string, string>;
|
|
56
|
+
export declare function declare(t: Target, d: Declare): Promise<Declared>;
|
|
57
|
+
export declare function builds(t: Target): Promise<Build[]>;
|
|
58
|
+
/** The clone URL for a GitHub repository. */
|
|
59
|
+
export declare const clone: (fullName: string) => string;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adding a repository to a project, which is how a project is published.
|
|
3
|
+
*
|
|
4
|
+
* POST /v1/platform/apps {repo, ref, name, partOf, mode, tag?}
|
|
5
|
+
* → 202 {app, build?: {id, job, image, status}, declaration: {mode, ref, review, live, …}}
|
|
6
|
+
* GET /v1/platform/builds → {builds: [{id, repo, commit, status, startedAt, duration}]}
|
|
7
|
+
*
|
|
8
|
+
* The declaration is a values file in universe. `branch` (the default) opens a
|
|
9
|
+
* review and deploys nothing; `commit` writes main and CD applies it — and it
|
|
10
|
+
* refuses to name an image that is not built yet, so publishing to main is two
|
|
11
|
+
* calls: build on a branch, then commit that tag once the build is green. An
|
|
12
|
+
* org admin gets both; anyone else gets the review link.
|
|
13
|
+
*/
|
|
14
|
+
import { call } from './call.js';
|
|
15
|
+
const str = (v) => (typeof v === 'string' ? v : '');
|
|
16
|
+
const obj = (v) => v && typeof v === 'object' ? v : {};
|
|
17
|
+
/** A DNS-1123 label from anything: what an app may be named. */
|
|
18
|
+
export function label(s) {
|
|
19
|
+
const out = s
|
|
20
|
+
.toLowerCase()
|
|
21
|
+
.replace(/[^a-z0-9-]+/g, '-')
|
|
22
|
+
.replace(/-+/g, '-')
|
|
23
|
+
.replace(/^-+|-+$/g, '')
|
|
24
|
+
.slice(0, 63)
|
|
25
|
+
.replace(/-+$/g, '');
|
|
26
|
+
return out || 'app';
|
|
27
|
+
}
|
|
28
|
+
/** An https address, or '' — a review link is shown as a link only when it is one. */
|
|
29
|
+
export const https = (url) => (/^https:\/\/[^\s]+$/.test(url) ? url : '');
|
|
30
|
+
export function body(d) {
|
|
31
|
+
const out = {
|
|
32
|
+
repo: d.repo,
|
|
33
|
+
ref: d.ref || 'main',
|
|
34
|
+
name: label(d.name),
|
|
35
|
+
partOf: label(d.project),
|
|
36
|
+
mode: d.mode,
|
|
37
|
+
};
|
|
38
|
+
if (d.tag)
|
|
39
|
+
out.tag = d.tag;
|
|
40
|
+
return out;
|
|
41
|
+
}
|
|
42
|
+
export async function declare(t, d) {
|
|
43
|
+
const raw = obj(await call(t, 'POST', '/v1/platform/apps', body(d)));
|
|
44
|
+
const b = raw.build ? obj(raw.build) : null;
|
|
45
|
+
const dec = obj(raw.declaration);
|
|
46
|
+
return {
|
|
47
|
+
build: b ? { id: str(b.id), job: str(b.job), image: str(b.image), status: str(b.status) } : null,
|
|
48
|
+
mode: str(dec.mode),
|
|
49
|
+
ref: str(dec.ref),
|
|
50
|
+
review: https(str(dec.review)),
|
|
51
|
+
live: dec.live === true,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
export async function builds(t) {
|
|
55
|
+
const raw = obj(await call(t, 'GET', '/v1/platform/builds'));
|
|
56
|
+
return (Array.isArray(raw.builds) ? raw.builds : []).map((r) => {
|
|
57
|
+
const o = obj(r);
|
|
58
|
+
return {
|
|
59
|
+
id: str(o.id),
|
|
60
|
+
repo: str(o.repo),
|
|
61
|
+
commit: str(o.commit),
|
|
62
|
+
status: str(o.status),
|
|
63
|
+
startedAt: str(o.startedAt),
|
|
64
|
+
duration: str(o.duration),
|
|
65
|
+
};
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
/** The clone URL for a GitHub repository. */
|
|
69
|
+
export const clone = (fullName) => `https://github.com/${fullName}.git`;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The org's projects: what it has built, and where each one is served.
|
|
3
|
+
*
|
|
4
|
+
* GET /v1/projects → Project[]
|
|
5
|
+
*
|
|
6
|
+
* `slug` is the one key every surface shares — the address under /dev, the
|
|
7
|
+
* site's name, the `project` a run is tagged with.
|
|
8
|
+
*/
|
|
9
|
+
import { type Target } from './call.ts';
|
|
10
|
+
export interface Project {
|
|
11
|
+
slug: string;
|
|
12
|
+
name: string;
|
|
13
|
+
/** The clone URL, or '' for a project with no repository. */
|
|
14
|
+
repo: string;
|
|
15
|
+
/** The branch pushes to which rebuild it, or ''. */
|
|
16
|
+
branch: string;
|
|
17
|
+
status: string;
|
|
18
|
+
/** The deployed address, or '' when nothing has shipped. */
|
|
19
|
+
live: string;
|
|
20
|
+
updated: number;
|
|
21
|
+
}
|
|
22
|
+
/** An address the preview may frame: https, or http on loopback when the builder is local too. */
|
|
23
|
+
export declare function safe(url: string, local?: boolean): string;
|
|
24
|
+
export declare function project(raw: unknown): Project;
|
|
25
|
+
export declare function projects(t: Target): Promise<Project[]>;
|
|
26
|
+
/** The repository name a project's clone URL names — the last path segment, without `.git`. */
|
|
27
|
+
export declare function name(clone: string): string;
|
|
28
|
+
/** A starter from the public catalog. */
|
|
29
|
+
export interface Template {
|
|
30
|
+
slug: string;
|
|
31
|
+
title: string;
|
|
32
|
+
category: string;
|
|
33
|
+
description: string;
|
|
34
|
+
framework: string;
|
|
35
|
+
/** The repository the starter is cut from. */
|
|
36
|
+
source: string;
|
|
37
|
+
}
|
|
38
|
+
/** The public catalog. It is reference content: no bearer, no org. */
|
|
39
|
+
export declare function templates(t: Target): Promise<Template[]>;
|
|
40
|
+
/**
|
|
41
|
+
* Take a copy of a starter: a project whose repository is the starter's own,
|
|
42
|
+
* so what opens is that template rather than a model's imitation of it.
|
|
43
|
+
*/
|
|
44
|
+
export declare function fork(t: Target, slug: string): Promise<Project>;
|
|
45
|
+
/** `owner/name` from a clone URL — its last two path segments, without `.git`. */
|
|
46
|
+
export declare function address(clone: string): string;
|
|
47
|
+
/**
|
|
48
|
+
* Whether a run worked on this project's repository. A run's record can be
|
|
49
|
+
* moved into any project of the org, so a project shows only the runs on its
|
|
50
|
+
* own code — their branch is what Files, Code and Publish read.
|
|
51
|
+
*/
|
|
52
|
+
export declare function ours(runRepo: string, projectClone: string): boolean;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The org's projects: what it has built, and where each one is served.
|
|
3
|
+
*
|
|
4
|
+
* GET /v1/projects → Project[]
|
|
5
|
+
*
|
|
6
|
+
* `slug` is the one key every surface shares — the address under /dev, the
|
|
7
|
+
* site's name, the `project` a run is tagged with.
|
|
8
|
+
*/
|
|
9
|
+
import { call } from './call.js';
|
|
10
|
+
const str = (v) => (typeof v === 'string' ? v : '');
|
|
11
|
+
const loopback = (host) => host === 'localhost' || host === '127.0.0.1';
|
|
12
|
+
/** Whether this page itself is served from loopback — local work. */
|
|
13
|
+
const here = () => typeof window !== 'undefined' && loopback(window.location.hostname);
|
|
14
|
+
/** An address the preview may frame: https, or http on loopback when the builder is local too. */
|
|
15
|
+
export function safe(url, local = here()) {
|
|
16
|
+
try {
|
|
17
|
+
const u = new URL(url);
|
|
18
|
+
if (u.protocol === 'https:')
|
|
19
|
+
return u.toString();
|
|
20
|
+
// Loopback only for a builder that is itself on loopback: a published row
|
|
21
|
+
// must not point colleagues' browsers at their own local services.
|
|
22
|
+
if (local && u.protocol === 'http:' && loopback(u.hostname))
|
|
23
|
+
return u.toString();
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
/* not an address */
|
|
27
|
+
}
|
|
28
|
+
return '';
|
|
29
|
+
}
|
|
30
|
+
export function project(raw) {
|
|
31
|
+
const p = (raw && typeof raw === 'object' ? raw : {});
|
|
32
|
+
const repo = (p.repo && typeof p.repo === 'object' ? p.repo : {});
|
|
33
|
+
return {
|
|
34
|
+
slug: str(p.slug),
|
|
35
|
+
name: str(p.name) || str(p.slug),
|
|
36
|
+
repo: str(repo.url),
|
|
37
|
+
branch: str(repo.branch),
|
|
38
|
+
status: str(p.status),
|
|
39
|
+
live: safe(str(p.liveUrl)),
|
|
40
|
+
updated: typeof p.updatedAt === 'number' ? p.updatedAt : 0,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export async function projects(t) {
|
|
44
|
+
const raw = await call(t, 'GET', '/v1/projects');
|
|
45
|
+
const rows = Array.isArray(raw) ? raw : Array.isArray(raw?.data) ? raw.data : [];
|
|
46
|
+
return rows
|
|
47
|
+
.map(project)
|
|
48
|
+
.filter((p) => p.slug)
|
|
49
|
+
.sort((a, b) => b.updated - a.updated);
|
|
50
|
+
}
|
|
51
|
+
/** The repository name a project's clone URL names — the last path segment, without `.git`. */
|
|
52
|
+
export function name(clone) {
|
|
53
|
+
const cut = clone.replace(/\.git$/, '').replace(/\/+$/, '');
|
|
54
|
+
return cut.slice(cut.lastIndexOf('/') + 1);
|
|
55
|
+
}
|
|
56
|
+
/** The public catalog. It is reference content: no bearer, no org. */
|
|
57
|
+
export async function templates(t) {
|
|
58
|
+
const raw = await call({ api: t.api, token: () => null, org: null }, 'GET', '/v1/templates');
|
|
59
|
+
const rows = Array.isArray(raw?.data) ? raw.data : [];
|
|
60
|
+
return rows
|
|
61
|
+
.map((r) => {
|
|
62
|
+
const o = (r && typeof r === 'object' ? r : {});
|
|
63
|
+
return {
|
|
64
|
+
slug: str(o.slug),
|
|
65
|
+
title: str(o.title) || str(o.slug),
|
|
66
|
+
category: str(o.category),
|
|
67
|
+
description: str(o.description),
|
|
68
|
+
framework: str(o.framework),
|
|
69
|
+
source: str(o.source),
|
|
70
|
+
};
|
|
71
|
+
})
|
|
72
|
+
.filter((x) => x.slug);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Take a copy of a starter: a project whose repository is the starter's own,
|
|
76
|
+
* so what opens is that template rather than a model's imitation of it.
|
|
77
|
+
*/
|
|
78
|
+
export async function fork(t, slug) {
|
|
79
|
+
return project(await call(t, 'POST', '/v1/projects/fork', { slug }));
|
|
80
|
+
}
|
|
81
|
+
/** `owner/name` from a clone URL — its last two path segments, without `.git`. */
|
|
82
|
+
export function address(clone) {
|
|
83
|
+
const parts = clone.replace(/\.git$/, '').replace(/\/+$/, '').split('/');
|
|
84
|
+
return parts.length >= 2 ? `${parts[parts.length - 2]}/${parts[parts.length - 1]}` : '';
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Whether a run worked on this project's repository. A run's record can be
|
|
88
|
+
* moved into any project of the org, so a project shows only the runs on its
|
|
89
|
+
* own code — their branch is what Files, Code and Publish read.
|
|
90
|
+
*/
|
|
91
|
+
export function ours(runRepo, projectClone) {
|
|
92
|
+
if (!projectClone)
|
|
93
|
+
return true;
|
|
94
|
+
const want = address(projectClone).toLowerCase();
|
|
95
|
+
const have = runRepo.toLowerCase();
|
|
96
|
+
return have === want || (!have.includes('/') && have === want.split('/')[1]);
|
|
97
|
+
}
|