@boardwalk-labs/cli 0.1.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 +18 -0
- package/README.md +110 -0
- package/bin/boardwalk.js +4 -0
- package/dist/artifact.d.ts +53 -0
- package/dist/artifact.js +267 -0
- package/dist/artifact.js.map +1 -0
- package/dist/auth/discovery.d.ts +8 -0
- package/dist/auth/discovery.js +43 -0
- package/dist/auth/discovery.js.map +1 -0
- package/dist/auth/login.d.ts +13 -0
- package/dist/auth/login.js +74 -0
- package/dist/auth/login.js.map +1 -0
- package/dist/auth/pkce.d.ts +63 -0
- package/dist/auth/pkce.js +197 -0
- package/dist/auth/pkce.js.map +1 -0
- package/dist/auth/resolve.d.ts +12 -0
- package/dist/auth/resolve.js +51 -0
- package/dist/auth/resolve.js.map +1 -0
- package/dist/bundle.d.ts +35 -0
- package/dist/bundle.js +176 -0
- package/dist/bundle.js.map +1 -0
- package/dist/client.d.ts +65 -0
- package/dist/client.js +193 -0
- package/dist/client.js.map +1 -0
- package/dist/commands/cancel.d.ts +14 -0
- package/dist/commands/cancel.js +53 -0
- package/dist/commands/cancel.js.map +1 -0
- package/dist/commands/check.d.ts +7 -0
- package/dist/commands/check.js +34 -0
- package/dist/commands/check.js.map +1 -0
- package/dist/commands/deploy.d.ts +15 -0
- package/dist/commands/deploy.js +56 -0
- package/dist/commands/deploy.js.map +1 -0
- package/dist/commands/dev.d.ts +14 -0
- package/dist/commands/dev.js +128 -0
- package/dist/commands/dev.js.map +1 -0
- package/dist/commands/init.d.ts +12 -0
- package/dist/commands/init.js +171 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/run.d.ts +32 -0
- package/dist/commands/run.js +105 -0
- package/dist/commands/run.js.map +1 -0
- package/dist/commands/session.d.ts +13 -0
- package/dist/commands/session.js +58 -0
- package/dist/commands/session.js.map +1 -0
- package/dist/config.d.ts +14 -0
- package/dist/config.js +64 -0
- package/dist/config.js.map +1 -0
- package/dist/credentials.d.ts +23 -0
- package/dist/credentials.js +69 -0
- package/dist/credentials.js.map +1 -0
- package/dist/deployment.d.ts +44 -0
- package/dist/deployment.js +100 -0
- package/dist/deployment.js.map +1 -0
- package/dist/dev/host.d.ts +16 -0
- package/dist/dev/host.js +76 -0
- package/dist/dev/host.js.map +1 -0
- package/dist/errors.d.ts +8 -0
- package/dist/errors.js +18 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +147 -0
- package/dist/index.js.map +1 -0
- package/dist/manifest.d.ts +12 -0
- package/dist/manifest.js +64 -0
- package/dist/manifest.js.map +1 -0
- package/dist/project.d.ts +15 -0
- package/dist/project.js +79 -0
- package/dist/project.js.map +1 -0
- package/dist/render/renderer.d.ts +18 -0
- package/dist/render/renderer.js +108 -0
- package/dist/render/renderer.js.map +1 -0
- package/package.json +56 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export type FetchLike = typeof fetch;
|
|
2
|
+
export interface PkcePair {
|
|
3
|
+
verifier: string;
|
|
4
|
+
challenge: string;
|
|
5
|
+
}
|
|
6
|
+
/** A 48-byte verifier (base64url) + its S256 challenge. */
|
|
7
|
+
export declare function generatePkcePair(): PkcePair;
|
|
8
|
+
/** Opaque CSRF state (random UUID, hyphens stripped). */
|
|
9
|
+
export declare function randomState(): string;
|
|
10
|
+
export interface OAuthEndpoints {
|
|
11
|
+
authorize: string;
|
|
12
|
+
token: string;
|
|
13
|
+
}
|
|
14
|
+
/** The issuer's OAuth2 endpoints, derived from the issuer origin. */
|
|
15
|
+
export declare function oauthEndpoints(issuerUrl: string): OAuthEndpoints;
|
|
16
|
+
export interface AuthorizeUrlParams {
|
|
17
|
+
authorizeEndpoint: string;
|
|
18
|
+
clientId: string;
|
|
19
|
+
redirectUri: string;
|
|
20
|
+
codeChallenge: string;
|
|
21
|
+
state: string;
|
|
22
|
+
scope: string;
|
|
23
|
+
}
|
|
24
|
+
export declare function buildAuthorizeUrl(p: AuthorizeUrlParams): string;
|
|
25
|
+
export interface TokenResponse {
|
|
26
|
+
accessToken: string;
|
|
27
|
+
refreshToken: string | null;
|
|
28
|
+
/** Absolute expiry epoch ms, or null when the IdP didn't return `expires_in`. */
|
|
29
|
+
expiresAt: number | null;
|
|
30
|
+
scope: string | null;
|
|
31
|
+
}
|
|
32
|
+
export interface ExchangeCodeParams {
|
|
33
|
+
tokenEndpoint: string;
|
|
34
|
+
clientId: string;
|
|
35
|
+
code: string;
|
|
36
|
+
codeVerifier: string;
|
|
37
|
+
redirectUri: string;
|
|
38
|
+
fetchImpl?: FetchLike | undefined;
|
|
39
|
+
now?: number | undefined;
|
|
40
|
+
}
|
|
41
|
+
export declare function exchangeCode(p: ExchangeCodeParams): Promise<TokenResponse>;
|
|
42
|
+
export interface RefreshParams {
|
|
43
|
+
tokenEndpoint: string;
|
|
44
|
+
clientId: string;
|
|
45
|
+
refreshToken: string;
|
|
46
|
+
fetchImpl?: FetchLike | undefined;
|
|
47
|
+
now?: number | undefined;
|
|
48
|
+
}
|
|
49
|
+
export declare function refreshAccessToken(p: RefreshParams): Promise<TokenResponse>;
|
|
50
|
+
/** True when `expiresAt` is within the grace window of `now` (or already past). Null = never. */
|
|
51
|
+
export declare function isExpired(expiresAt: number | null, now?: number): boolean;
|
|
52
|
+
export interface Loopback {
|
|
53
|
+
redirectUri: string;
|
|
54
|
+
port: number;
|
|
55
|
+
/** Resolves with the authorization `code` once the browser redirects back (validates `state`). */
|
|
56
|
+
awaitCode(expectedState: string): Promise<string>;
|
|
57
|
+
close(): void;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Start a localhost callback server on the fixed `port`. The redirect URI it advertises must be
|
|
61
|
+
* allowlisted on the OAuth application.
|
|
62
|
+
*/
|
|
63
|
+
export declare function startLoopback(port: number): Promise<Loopback>;
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
// OAuth 2.0 Authorization Code + PKCE — the primitives `boardwalk login` runs against the
|
|
2
|
+
// platform's identity provider (an internal detail; the CLI only knows the issuer URL).
|
|
3
|
+
//
|
|
4
|
+
// Notable adaptations for this IdP:
|
|
5
|
+
// - The backend has no OAuth authorization server of its own (it only VERIFIES issued JWTs via
|
|
6
|
+
// JWKS), so we authenticate directly against the issuer's `/oauth/authorize` + `/oauth/token`.
|
|
7
|
+
// - No RFC 7591 dynamic client registration → the public client id is pre-provisioned (an OAuth
|
|
8
|
+
// application) and supplied via config, not registered on the fly.
|
|
9
|
+
// - The redirect allowlist wants exact URIs → we use a FIXED loopback port (config), not an
|
|
10
|
+
// ephemeral one. RFC 8707 resource indicators are dropped (single audience: the session).
|
|
11
|
+
//
|
|
12
|
+
// The pure pieces (pair/state/URL/exchange/refresh/expiry) are unit-tested with an injected fetch;
|
|
13
|
+
// the loopback server + browser open are exercised by the login command, not unit tests.
|
|
14
|
+
import { randomBytes, createHash, randomUUID } from "node:crypto";
|
|
15
|
+
import { createServer } from "node:http";
|
|
16
|
+
import { CliError } from "../errors.js";
|
|
17
|
+
const CALLBACK_PATH = "/callback";
|
|
18
|
+
const TOKEN_REQUEST_TIMEOUT_MS = 20_000;
|
|
19
|
+
const CALLBACK_TIMEOUT_MS = 180_000;
|
|
20
|
+
/** Treat a token as expired this long BEFORE its true expiry, to avoid using it mid-flight. */
|
|
21
|
+
const EXPIRY_GRACE_MS = 30_000;
|
|
22
|
+
/** A 48-byte verifier (base64url) + its S256 challenge. */
|
|
23
|
+
export function generatePkcePair() {
|
|
24
|
+
const verifier = randomBytes(48).toString("base64url");
|
|
25
|
+
const challenge = createHash("sha256").update(verifier, "ascii").digest().toString("base64url");
|
|
26
|
+
return { verifier, challenge };
|
|
27
|
+
}
|
|
28
|
+
/** Opaque CSRF state (random UUID, hyphens stripped). */
|
|
29
|
+
export function randomState() {
|
|
30
|
+
return randomUUID().replace(/-/g, "");
|
|
31
|
+
}
|
|
32
|
+
/** The issuer's OAuth2 endpoints, derived from the issuer origin. */
|
|
33
|
+
export function oauthEndpoints(issuerUrl) {
|
|
34
|
+
const base = issuerUrl.replace(/\/+$/, "");
|
|
35
|
+
return { authorize: `${base}/oauth/authorize`, token: `${base}/oauth/token` };
|
|
36
|
+
}
|
|
37
|
+
export function buildAuthorizeUrl(p) {
|
|
38
|
+
const params = new URLSearchParams({
|
|
39
|
+
response_type: "code",
|
|
40
|
+
client_id: p.clientId,
|
|
41
|
+
redirect_uri: p.redirectUri,
|
|
42
|
+
code_challenge: p.codeChallenge,
|
|
43
|
+
code_challenge_method: "S256",
|
|
44
|
+
state: p.state,
|
|
45
|
+
scope: p.scope,
|
|
46
|
+
});
|
|
47
|
+
return `${p.authorizeEndpoint}?${params.toString()}`;
|
|
48
|
+
}
|
|
49
|
+
export async function exchangeCode(p) {
|
|
50
|
+
const form = new URLSearchParams({
|
|
51
|
+
grant_type: "authorization_code",
|
|
52
|
+
client_id: p.clientId,
|
|
53
|
+
code: p.code,
|
|
54
|
+
code_verifier: p.codeVerifier,
|
|
55
|
+
redirect_uri: p.redirectUri,
|
|
56
|
+
});
|
|
57
|
+
return postToken(p.tokenEndpoint, form, p.fetchImpl, p.now);
|
|
58
|
+
}
|
|
59
|
+
export async function refreshAccessToken(p) {
|
|
60
|
+
const form = new URLSearchParams({
|
|
61
|
+
grant_type: "refresh_token",
|
|
62
|
+
client_id: p.clientId,
|
|
63
|
+
refresh_token: p.refreshToken,
|
|
64
|
+
});
|
|
65
|
+
return postToken(p.tokenEndpoint, form, p.fetchImpl, p.now);
|
|
66
|
+
}
|
|
67
|
+
/** True when `expiresAt` is within the grace window of `now` (or already past). Null = never. */
|
|
68
|
+
export function isExpired(expiresAt, now = Date.now()) {
|
|
69
|
+
if (expiresAt === null)
|
|
70
|
+
return false;
|
|
71
|
+
return now >= expiresAt - EXPIRY_GRACE_MS;
|
|
72
|
+
}
|
|
73
|
+
async function postToken(tokenEndpoint, form, fetchImpl = fetch, now = Date.now()) {
|
|
74
|
+
let res;
|
|
75
|
+
try {
|
|
76
|
+
res = await fetchImpl(tokenEndpoint, {
|
|
77
|
+
method: "POST",
|
|
78
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded", Accept: "application/json" },
|
|
79
|
+
body: form.toString(),
|
|
80
|
+
signal: AbortSignal.timeout(TOKEN_REQUEST_TIMEOUT_MS),
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
catch (err) {
|
|
84
|
+
throw new CliError(`Could not reach the token endpoint (${tokenEndpoint}).`, err instanceof Error ? err.message : undefined);
|
|
85
|
+
}
|
|
86
|
+
const bodyText = await res.text();
|
|
87
|
+
if (!res.ok) {
|
|
88
|
+
throw new CliError(`Token request failed (${String(res.status)}).`, oauthErrorDetail(bodyText));
|
|
89
|
+
}
|
|
90
|
+
return parseTokenBody(bodyText, now);
|
|
91
|
+
}
|
|
92
|
+
function parseTokenBody(bodyText, now) {
|
|
93
|
+
let body;
|
|
94
|
+
try {
|
|
95
|
+
body = JSON.parse(bodyText);
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
throw new CliError("Token endpoint returned a non-JSON body.");
|
|
99
|
+
}
|
|
100
|
+
if (typeof body !== "object" || body === null) {
|
|
101
|
+
throw new CliError("Token endpoint returned an unexpected body.");
|
|
102
|
+
}
|
|
103
|
+
const b = body;
|
|
104
|
+
const accessToken = b.access_token;
|
|
105
|
+
if (typeof accessToken !== "string" || accessToken.length === 0) {
|
|
106
|
+
throw new CliError("Token endpoint response is missing an access_token.");
|
|
107
|
+
}
|
|
108
|
+
const expiresIn = typeof b.expires_in === "number" ? b.expires_in : null;
|
|
109
|
+
return {
|
|
110
|
+
accessToken,
|
|
111
|
+
refreshToken: typeof b.refresh_token === "string" ? b.refresh_token : null,
|
|
112
|
+
expiresAt: expiresIn !== null ? now + expiresIn * 1000 : null,
|
|
113
|
+
scope: typeof b.scope === "string" ? b.scope : null,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
/** Best-effort `error_description`/`error` from an OAuth error body, for the hint line. */
|
|
117
|
+
function oauthErrorDetail(bodyText) {
|
|
118
|
+
try {
|
|
119
|
+
const body = JSON.parse(bodyText);
|
|
120
|
+
if (typeof body === "object" && body !== null) {
|
|
121
|
+
const b = body;
|
|
122
|
+
const detail = b.error_description ?? b.error;
|
|
123
|
+
if (typeof detail === "string" && detail.length > 0)
|
|
124
|
+
return detail;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
// not JSON — fall through
|
|
129
|
+
}
|
|
130
|
+
return bodyText.length > 0 ? bodyText.slice(0, 200) : undefined;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Start a localhost callback server on the fixed `port`. The redirect URI it advertises must be
|
|
134
|
+
* allowlisted on the OAuth application.
|
|
135
|
+
*/
|
|
136
|
+
export async function startLoopback(port) {
|
|
137
|
+
let resolveCode;
|
|
138
|
+
let rejectCode;
|
|
139
|
+
const codePromise = new Promise((resolve, reject) => {
|
|
140
|
+
resolveCode = resolve;
|
|
141
|
+
rejectCode = reject;
|
|
142
|
+
});
|
|
143
|
+
const server = createServer((req, res) => {
|
|
144
|
+
const url = new URL(req.url ?? "/", `http://127.0.0.1:${String(port)}`);
|
|
145
|
+
if (url.pathname !== CALLBACK_PATH) {
|
|
146
|
+
res.writeHead(404);
|
|
147
|
+
res.end();
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
const code = url.searchParams.get("code") ?? "";
|
|
151
|
+
const state = url.searchParams.get("state") ?? "";
|
|
152
|
+
const error = url.searchParams.get("error") ?? "";
|
|
153
|
+
const expectedState = pendingState;
|
|
154
|
+
let message;
|
|
155
|
+
if (error.length > 0) {
|
|
156
|
+
message = `Authorization failed: ${error}`;
|
|
157
|
+
rejectCode(new CliError(message));
|
|
158
|
+
}
|
|
159
|
+
else if (code.length === 0) {
|
|
160
|
+
message = "Authorization failed: no code in the callback.";
|
|
161
|
+
rejectCode(new CliError(message));
|
|
162
|
+
}
|
|
163
|
+
else if (expectedState === null || state !== expectedState) {
|
|
164
|
+
message = "Authorization failed: state mismatch (possible CSRF).";
|
|
165
|
+
rejectCode(new CliError(message));
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
message = "Boardwalk login complete — you can close this tab and return to the terminal.";
|
|
169
|
+
resolveCode(code);
|
|
170
|
+
}
|
|
171
|
+
res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8" });
|
|
172
|
+
res.end(message);
|
|
173
|
+
});
|
|
174
|
+
let pendingState = null;
|
|
175
|
+
await new Promise((resolve, reject) => {
|
|
176
|
+
server.once("error", reject);
|
|
177
|
+
server.listen(port, "127.0.0.1", resolve);
|
|
178
|
+
});
|
|
179
|
+
return {
|
|
180
|
+
redirectUri: `http://127.0.0.1:${String(port)}${CALLBACK_PATH}`,
|
|
181
|
+
port,
|
|
182
|
+
awaitCode(expectedState) {
|
|
183
|
+
pendingState = expectedState;
|
|
184
|
+
const timeout = new Promise((_resolve, reject) => {
|
|
185
|
+
const t = setTimeout(() => {
|
|
186
|
+
reject(new CliError("Timed out waiting for the browser authorization (3 min)."));
|
|
187
|
+
}, CALLBACK_TIMEOUT_MS);
|
|
188
|
+
t.unref();
|
|
189
|
+
});
|
|
190
|
+
return Promise.race([codePromise, timeout]);
|
|
191
|
+
},
|
|
192
|
+
close() {
|
|
193
|
+
server.close();
|
|
194
|
+
},
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
//# sourceMappingURL=pkce.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pkce.js","sourceRoot":"","sources":["../../src/auth/pkce.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { CliConfig } from "../config.js";
|
|
2
|
+
import type { CredentialStore } from "../credentials.js";
|
|
3
|
+
import { type FetchLike } from "./pkce.js";
|
|
4
|
+
export interface ResolveTokenDeps {
|
|
5
|
+
config: CliConfig;
|
|
6
|
+
store: CredentialStore;
|
|
7
|
+
tokenFlag?: string | undefined;
|
|
8
|
+
env?: NodeJS.ProcessEnv;
|
|
9
|
+
fetchImpl?: FetchLike;
|
|
10
|
+
now?: number;
|
|
11
|
+
}
|
|
12
|
+
export declare function resolveToken(deps: ResolveTokenDeps): Promise<string>;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// resolveToken — the single place a command turns "the user" into a Bearer token.
|
|
2
|
+
//
|
|
3
|
+
// Precedence (highest first):
|
|
4
|
+
// 1. an explicit `--token` flag (one-off / scripting)
|
|
5
|
+
// 2. `BOARDWALK_API_KEY` env (CI / headless — a `bwk_…` key)
|
|
6
|
+
// 3. the stored `boardwalk login` session — refreshed in place when expired
|
|
7
|
+
//
|
|
8
|
+
// Throws `CliError` (actionable) when none is available or a refresh can't proceed.
|
|
9
|
+
import { CliError } from "../errors.js";
|
|
10
|
+
import { isExpired, refreshAccessToken } from "./pkce.js";
|
|
11
|
+
export async function resolveToken(deps) {
|
|
12
|
+
const env = deps.env ?? process.env;
|
|
13
|
+
// 1. explicit --token
|
|
14
|
+
const flag = deps.tokenFlag?.trim();
|
|
15
|
+
if (flag !== undefined && flag.length > 0)
|
|
16
|
+
return flag;
|
|
17
|
+
// 2. env API key (CI)
|
|
18
|
+
const envKey = env.BOARDWALK_API_KEY?.trim();
|
|
19
|
+
if (envKey !== undefined && envKey.length > 0)
|
|
20
|
+
return envKey;
|
|
21
|
+
// 3. stored login session
|
|
22
|
+
const session = deps.store.getSession();
|
|
23
|
+
if (session === null) {
|
|
24
|
+
throw new CliError("Not authenticated.", "Run `boardwalk login`, or set BOARDWALK_API_KEY.");
|
|
25
|
+
}
|
|
26
|
+
if (!isExpired(session.expiresAt, deps.now))
|
|
27
|
+
return session.accessToken;
|
|
28
|
+
// expired → refresh in place
|
|
29
|
+
if (session.refreshToken === null ||
|
|
30
|
+
session.tokenEndpoint === null ||
|
|
31
|
+
session.clientId === null) {
|
|
32
|
+
throw new CliError("Your session has expired.", "Run `boardwalk login` again.");
|
|
33
|
+
}
|
|
34
|
+
const refreshed = await refreshAccessToken({
|
|
35
|
+
tokenEndpoint: session.tokenEndpoint,
|
|
36
|
+
clientId: session.clientId,
|
|
37
|
+
refreshToken: session.refreshToken,
|
|
38
|
+
fetchImpl: deps.fetchImpl,
|
|
39
|
+
now: deps.now,
|
|
40
|
+
});
|
|
41
|
+
deps.store.putSession({
|
|
42
|
+
accessToken: refreshed.accessToken,
|
|
43
|
+
refreshToken: refreshed.refreshToken ?? session.refreshToken,
|
|
44
|
+
expiresAt: refreshed.expiresAt,
|
|
45
|
+
clientId: session.clientId,
|
|
46
|
+
tokenEndpoint: session.tokenEndpoint,
|
|
47
|
+
scope: refreshed.scope ?? session.scope,
|
|
48
|
+
});
|
|
49
|
+
return refreshed.accessToken;
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=resolve.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve.js","sourceRoot":"","sources":["../../src/auth/resolve.ts"],"names":[],"mappings":"AAAA,kFAAkF;AAClF,EAAE;AACF,8BAA8B;AAC9B,wDAAwD;AACxD,+DAA+D;AAC/D,8EAA8E;AAC9E,EAAE;AACF,oFAAoF;AAEpF,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAGxC,OAAO,EAAE,SAAS,EAAE,kBAAkB,EAAkB,MAAM,WAAW,CAAC;AAW1E,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,IAAsB;IACvD,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC;IAEpC,sBAAsB;IACtB,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,CAAC;IACpC,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IAEvD,sBAAsB;IACtB,MAAM,MAAM,GAAG,GAAG,CAAC,iBAAiB,EAAE,IAAI,EAAE,CAAC;IAC7C,IAAI,MAAM,KAAK,SAAS,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,MAAM,CAAC;IAE7D,0BAA0B;IAC1B,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,EAAE,CAAC;IACxC,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;QACrB,MAAM,IAAI,QAAQ,CAAC,oBAAoB,EAAE,kDAAkD,CAAC,CAAC;IAC/F,CAAC;IACD,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,CAAC,GAAG,CAAC;QAAE,OAAO,OAAO,CAAC,WAAW,CAAC;IAExE,6BAA6B;IAC7B,IACE,OAAO,CAAC,YAAY,KAAK,IAAI;QAC7B,OAAO,CAAC,aAAa,KAAK,IAAI;QAC9B,OAAO,CAAC,QAAQ,KAAK,IAAI,EACzB,CAAC;QACD,MAAM,IAAI,QAAQ,CAAC,2BAA2B,EAAE,8BAA8B,CAAC,CAAC;IAClF,CAAC;IACD,MAAM,SAAS,GAAG,MAAM,kBAAkB,CAAC;QACzC,aAAa,EAAE,OAAO,CAAC,aAAa;QACpC,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,YAAY,EAAE,OAAO,CAAC,YAAY;QAClC,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,GAAG,EAAE,IAAI,CAAC,GAAG;KACd,CAAC,CAAC;IACH,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC;QACpB,WAAW,EAAE,SAAS,CAAC,WAAW;QAClC,YAAY,EAAE,SAAS,CAAC,YAAY,IAAI,OAAO,CAAC,YAAY;QAC5D,SAAS,EAAE,SAAS,CAAC,SAAS;QAC9B,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,aAAa,EAAE,OAAO,CAAC,aAAa;QACpC,KAAK,EAAE,SAAS,CAAC,KAAK,IAAI,OAAO,CAAC,KAAK;KACxC,CAAC,CAAC;IACH,OAAO,SAAS,CAAC,WAAW,CAAC;AAC/B,CAAC"}
|
package/dist/bundle.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/** True when `target` is a directory (a workflow package), vs. a single program file. */
|
|
2
|
+
export declare function isPackageDir(target: string): boolean;
|
|
3
|
+
/**
|
|
4
|
+
* Resolve a deploy/dev target to its entry file: a file path returns itself; a directory resolves
|
|
5
|
+
* via package.json `module`/`main`, then `index.{ts,mts,js,mjs}`.
|
|
6
|
+
*/
|
|
7
|
+
export declare function resolveEntry(target: string): string;
|
|
8
|
+
/**
|
|
9
|
+
* esbuild-bundle the entry into one self-contained ESM string. `@boardwalk-labs/workflow` is left
|
|
10
|
+
* external (host-provided). Not minified — the `meta` literal must stay statically extractable.
|
|
11
|
+
*/
|
|
12
|
+
export declare function bundleWorkflow(entryFile: string): Promise<string>;
|
|
13
|
+
/** A bundled program plus its external sourcemap (for run-error symbolication back to user files). */
|
|
14
|
+
export interface BundledProgram {
|
|
15
|
+
/** The bundled ESM (`index.mjs`); `@boardwalk-labs/workflow` left external, not minified. */
|
|
16
|
+
code: string;
|
|
17
|
+
/** The external sourcemap JSON (`index.mjs.map`). */
|
|
18
|
+
map: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* esbuild-bundle the entry into `{ code, map }` for the deploy ARTIFACT. Same externals/format as
|
|
22
|
+
* {@link bundleWorkflow} but emits a `linked` sourcemap so a run's stack traces resolve back to the
|
|
23
|
+
* author's files. Output names are fixed (`index.mjs` / `index.mjs.map`) so the linked
|
|
24
|
+
* `sourceMappingURL` matches the artifact's layout.
|
|
25
|
+
*/
|
|
26
|
+
export declare function bundleWorkflowWithMap(entryFile: string): Promise<BundledProgram>;
|
|
27
|
+
/**
|
|
28
|
+
* esbuild-bundle the entry for `boardwalk dev` — like {@link bundleWorkflow}, but
|
|
29
|
+
* `@boardwalk-labs/workflow` imports are rewritten to the ABSOLUTE path of the CLI's own installed
|
|
30
|
+
* copy. The SDK's host state is a module-level singleton, so the program and the CLI must load
|
|
31
|
+
* the SAME module instance for `installHost` (called by the CLI) to be visible to the program's
|
|
32
|
+
* hooks; an absolute specifier guarantees that regardless of where the bundle file lives or
|
|
33
|
+
* whether the project has its own `node_modules`.
|
|
34
|
+
*/
|
|
35
|
+
export declare function bundleForDev(entryFile: string): Promise<string>;
|
package/dist/bundle.js
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
// Bundle-at-deploy.
|
|
2
|
+
//
|
|
3
|
+
// A workflow is a package with an `index` entrypoint; a single file is the no-deps case. When a
|
|
4
|
+
// program pulls in npm packages, we bundle it AT DEPLOY (not at runtime) via esbuild into one
|
|
5
|
+
// self-contained, version-pinned ESM artifact and upload THAT as the run's `source`:
|
|
6
|
+
// - `@boardwalk-labs/workflow` is marked EXTERNAL (host-provided — bundling a copy would give the
|
|
7
|
+
// program its own host state, separate from the engine's, and break the host seam).
|
|
8
|
+
// - `meta` stays a pure literal in the output, so engines re-derive the manifest from it.
|
|
9
|
+
// - Bundling at deploy keeps run cold-start fast and pins deps at PR time.
|
|
10
|
+
import { build } from "esbuild";
|
|
11
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
12
|
+
import { createRequire } from "node:module";
|
|
13
|
+
import { join, resolve } from "node:path";
|
|
14
|
+
import { CliError } from "./errors.js";
|
|
15
|
+
const SDK_PACKAGE = "@boardwalk-labs/workflow";
|
|
16
|
+
const ENTRY_CANDIDATES = ["index.ts", "index.mts", "index.js", "index.mjs"];
|
|
17
|
+
/** True when `target` is a directory (a workflow package), vs. a single program file. */
|
|
18
|
+
export function isPackageDir(target) {
|
|
19
|
+
try {
|
|
20
|
+
return statSync(resolve(target)).isDirectory();
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Resolve a deploy/dev target to its entry file: a file path returns itself; a directory resolves
|
|
28
|
+
* via package.json `module`/`main`, then `index.{ts,mts,js,mjs}`.
|
|
29
|
+
*/
|
|
30
|
+
export function resolveEntry(target) {
|
|
31
|
+
const abs = resolve(target);
|
|
32
|
+
let isDir;
|
|
33
|
+
try {
|
|
34
|
+
isDir = statSync(abs).isDirectory();
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
throw new CliError(`Path not found: ${target}`);
|
|
38
|
+
}
|
|
39
|
+
if (!isDir)
|
|
40
|
+
return abs;
|
|
41
|
+
const pkgPath = join(abs, "package.json");
|
|
42
|
+
if (existsSync(pkgPath)) {
|
|
43
|
+
const entry = readPkgEntry(pkgPath);
|
|
44
|
+
if (entry !== null) {
|
|
45
|
+
const entryAbs = resolve(abs, entry);
|
|
46
|
+
if (existsSync(entryAbs))
|
|
47
|
+
return entryAbs;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
for (const name of ENTRY_CANDIDATES) {
|
|
51
|
+
const candidate = join(abs, name);
|
|
52
|
+
if (existsSync(candidate))
|
|
53
|
+
return candidate;
|
|
54
|
+
}
|
|
55
|
+
throw new CliError(`No entry file found in ${target}`, `Add an ${ENTRY_CANDIDATES.join(" / ")}, or set "module"/"main" in package.json.`);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* esbuild-bundle the entry into one self-contained ESM string. `@boardwalk-labs/workflow` is left
|
|
59
|
+
* external (host-provided). Not minified — the `meta` literal must stay statically extractable.
|
|
60
|
+
*/
|
|
61
|
+
export async function bundleWorkflow(entryFile) {
|
|
62
|
+
let result;
|
|
63
|
+
try {
|
|
64
|
+
result = await build({
|
|
65
|
+
entryPoints: [entryFile],
|
|
66
|
+
bundle: true,
|
|
67
|
+
format: "esm",
|
|
68
|
+
platform: "node",
|
|
69
|
+
target: "node24",
|
|
70
|
+
external: [SDK_PACKAGE, `${SDK_PACKAGE}/*`],
|
|
71
|
+
write: false,
|
|
72
|
+
logLevel: "silent",
|
|
73
|
+
legalComments: "none",
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
throw new CliError(`Bundling failed for ${entryFile}.`, err instanceof Error ? err.message : undefined);
|
|
78
|
+
}
|
|
79
|
+
const out = result.outputFiles[0];
|
|
80
|
+
if (out === undefined)
|
|
81
|
+
throw new CliError(`Bundling produced no output for ${entryFile}.`);
|
|
82
|
+
return out.text;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* esbuild-bundle the entry into `{ code, map }` for the deploy ARTIFACT. Same externals/format as
|
|
86
|
+
* {@link bundleWorkflow} but emits a `linked` sourcemap so a run's stack traces resolve back to the
|
|
87
|
+
* author's files. Output names are fixed (`index.mjs` / `index.mjs.map`) so the linked
|
|
88
|
+
* `sourceMappingURL` matches the artifact's layout.
|
|
89
|
+
*/
|
|
90
|
+
export async function bundleWorkflowWithMap(entryFile) {
|
|
91
|
+
let result;
|
|
92
|
+
try {
|
|
93
|
+
result = await build({
|
|
94
|
+
entryPoints: [entryFile],
|
|
95
|
+
outfile: "index.mjs",
|
|
96
|
+
bundle: true,
|
|
97
|
+
format: "esm",
|
|
98
|
+
platform: "node",
|
|
99
|
+
target: "node24",
|
|
100
|
+
external: [SDK_PACKAGE, `${SDK_PACKAGE}/*`],
|
|
101
|
+
sourcemap: "linked",
|
|
102
|
+
minify: false,
|
|
103
|
+
write: false,
|
|
104
|
+
logLevel: "silent",
|
|
105
|
+
legalComments: "none",
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
catch (err) {
|
|
109
|
+
throw new CliError(`Bundling failed for ${entryFile}.`, err instanceof Error ? err.message : undefined);
|
|
110
|
+
}
|
|
111
|
+
const js = result.outputFiles.find((f) => !f.path.endsWith(".map"));
|
|
112
|
+
const map = result.outputFiles.find((f) => f.path.endsWith(".map"));
|
|
113
|
+
if (js === undefined || map === undefined) {
|
|
114
|
+
throw new CliError(`Bundling produced no output for ${entryFile}.`);
|
|
115
|
+
}
|
|
116
|
+
return { code: js.text, map: map.text };
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* esbuild-bundle the entry for `boardwalk dev` — like {@link bundleWorkflow}, but
|
|
120
|
+
* `@boardwalk-labs/workflow` imports are rewritten to the ABSOLUTE path of the CLI's own installed
|
|
121
|
+
* copy. The SDK's host state is a module-level singleton, so the program and the CLI must load
|
|
122
|
+
* the SAME module instance for `installHost` (called by the CLI) to be visible to the program's
|
|
123
|
+
* hooks; an absolute specifier guarantees that regardless of where the bundle file lives or
|
|
124
|
+
* whether the project has its own `node_modules`.
|
|
125
|
+
*/
|
|
126
|
+
export async function bundleForDev(entryFile) {
|
|
127
|
+
const requireFromCli = createRequire(import.meta.url);
|
|
128
|
+
let result;
|
|
129
|
+
try {
|
|
130
|
+
result = await build({
|
|
131
|
+
entryPoints: [entryFile],
|
|
132
|
+
bundle: true,
|
|
133
|
+
format: "esm",
|
|
134
|
+
platform: "node",
|
|
135
|
+
target: "node24",
|
|
136
|
+
write: false,
|
|
137
|
+
logLevel: "silent",
|
|
138
|
+
legalComments: "none",
|
|
139
|
+
plugins: [
|
|
140
|
+
{
|
|
141
|
+
name: "boardwalk-sdk-shared-instance",
|
|
142
|
+
setup(b) {
|
|
143
|
+
b.onResolve({ filter: /^@boardwalk-labs\/workflow(\/.+)?$/ }, (args) => ({
|
|
144
|
+
path: requireFromCli.resolve(args.path),
|
|
145
|
+
external: true,
|
|
146
|
+
}));
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
],
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
catch (err) {
|
|
153
|
+
throw new CliError(`Bundling failed for ${entryFile}.`, err instanceof Error ? err.message : undefined);
|
|
154
|
+
}
|
|
155
|
+
const out = result.outputFiles[0];
|
|
156
|
+
if (out === undefined)
|
|
157
|
+
throw new CliError(`Bundling produced no output for ${entryFile}.`);
|
|
158
|
+
return out.text;
|
|
159
|
+
}
|
|
160
|
+
function readPkgEntry(pkgPath) {
|
|
161
|
+
try {
|
|
162
|
+
const parsed = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
163
|
+
if (typeof parsed === "object" && parsed !== null) {
|
|
164
|
+
const pkg = parsed;
|
|
165
|
+
if (typeof pkg.module === "string")
|
|
166
|
+
return pkg.module;
|
|
167
|
+
if (typeof pkg.main === "string")
|
|
168
|
+
return pkg.main;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
// Unreadable package.json → fall back to index.* discovery.
|
|
173
|
+
}
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
//# sourceMappingURL=bundle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bundle.js","sourceRoot":"","sources":["../src/bundle.ts"],"names":[],"mappings":"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"}
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { FetchLike } from "./auth/pkce.js";
|
|
2
|
+
export interface WorkflowSummary {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
currentVersionId: string | null;
|
|
6
|
+
}
|
|
7
|
+
export interface DeployResult {
|
|
8
|
+
workflow: WorkflowSummary;
|
|
9
|
+
version: {
|
|
10
|
+
id: string;
|
|
11
|
+
number: number;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
/** The verified-artifact reference the finalize call records on the new version. */
|
|
15
|
+
export interface DeployArtifactRef {
|
|
16
|
+
digest: string;
|
|
17
|
+
size: number;
|
|
18
|
+
entry: string;
|
|
19
|
+
sdkVersion: string;
|
|
20
|
+
lockfileDigest: string | null;
|
|
21
|
+
}
|
|
22
|
+
export interface RunSummary {
|
|
23
|
+
id: string;
|
|
24
|
+
workflowId: string;
|
|
25
|
+
status: string;
|
|
26
|
+
outcomeStatus: string | null;
|
|
27
|
+
startedAt: number | null;
|
|
28
|
+
completedAt: number | null;
|
|
29
|
+
}
|
|
30
|
+
export interface BoardwalkClientOptions {
|
|
31
|
+
baseUrl: string;
|
|
32
|
+
token: string;
|
|
33
|
+
fetchImpl?: FetchLike;
|
|
34
|
+
}
|
|
35
|
+
export declare class BoardwalkClient {
|
|
36
|
+
private readonly baseUrl;
|
|
37
|
+
private readonly token;
|
|
38
|
+
private readonly fetchImpl;
|
|
39
|
+
constructor(opts: BoardwalkClientOptions);
|
|
40
|
+
listWorkflows(orgSlug: string): Promise<WorkflowSummary[]>;
|
|
41
|
+
createWorkflow(orgSlug: string, artifact: DeployArtifactRef): Promise<DeployResult>;
|
|
42
|
+
updateWorkflow(id: string, artifact: DeployArtifactRef): Promise<DeployResult>;
|
|
43
|
+
/** Request a presigned PUT for a program artifact (the CLI then uploads the tarball directly). */
|
|
44
|
+
getArtifactUploadUrl(orgSlug: string, input: {
|
|
45
|
+
digest: string;
|
|
46
|
+
size: number;
|
|
47
|
+
}): Promise<{
|
|
48
|
+
uploadUrl: string;
|
|
49
|
+
contentType: string;
|
|
50
|
+
}>;
|
|
51
|
+
/** Upload the artifact bytes straight to storage via the presigned PUT. The Content-Type MUST
|
|
52
|
+
* equal the one signed into the URL, or the store rejects the PUT. No auth header — the URL
|
|
53
|
+
* carries the signature. */
|
|
54
|
+
uploadArtifact(uploadUrl: string, contentType: string, bytes: Uint8Array): Promise<void>;
|
|
55
|
+
triggerRun(orgSlug: string, workflowId: string, input: unknown): Promise<RunSummary>;
|
|
56
|
+
getRun(runId: string): Promise<RunSummary>;
|
|
57
|
+
/**
|
|
58
|
+
* Cancel a run. Idempotent server-side (a terminal/already-cancelling run is a no-op); the
|
|
59
|
+
* endpoint resolves the org from the run id, so no org slug is needed. Returns 204 No Content.
|
|
60
|
+
*/
|
|
61
|
+
cancelRun(runId: string): Promise<void>;
|
|
62
|
+
private request;
|
|
63
|
+
private deployResult;
|
|
64
|
+
private runSummary;
|
|
65
|
+
}
|