diff --git a/src/index.ts b/src/index.ts index b4ac078fd..67712671a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -95,6 +95,9 @@ import { BlueprintListView, BlueprintPreviewParams, BlueprintPreviewView, + BlueprintRegisterParams, + BlueprintUploadParameters, + BlueprintUploadView, BlueprintView, BlueprintViewsBlueprintsCursorIDPage, Blueprints, @@ -662,6 +665,8 @@ export declare namespace Runloop { type BlueprintBuildParameters as BlueprintBuildParameters, type BlueprintListView as BlueprintListView, type BlueprintPreviewView as BlueprintPreviewView, + type BlueprintUploadParameters as BlueprintUploadParameters, + type BlueprintUploadView as BlueprintUploadView, type BlueprintView as BlueprintView, type BlueprintDeleteResponse as BlueprintDeleteResponse, BlueprintViewsBlueprintsCursorIDPage as BlueprintViewsBlueprintsCursorIDPage, @@ -669,6 +674,7 @@ export declare namespace Runloop { type BlueprintListParams as BlueprintListParams, type BlueprintListPublicParams as BlueprintListPublicParams, type BlueprintPreviewParams as BlueprintPreviewParams, + type BlueprintRegisterParams as BlueprintRegisterParams, }; export { diff --git a/src/resources/blueprints.ts b/src/resources/blueprints.ts index f6ed91e77..d4cfc3c91 100644 --- a/src/resources/blueprints.ts +++ b/src/resources/blueprints.ts @@ -88,6 +88,19 @@ export class Blueprints extends APIResource { return this._client.post('/v1/blueprints', { body, ...options }); } + /** + * Create a private Blueprint awaiting its image to be pushed out-of-band via + * docker push. Bypasses the build pipeline entirely: no Dockerfile is composed + * and no image is built. The Blueprint stays in the 'awaiting_upload' step until + * the push completes. + */ + register( + body: BlueprintRegisterParams, + options?: Core.RequestOptions, + ): Core.APIPromise { + return this._client.post('/v1/blueprints/register', { body, ...options }); + } + /** * Get the details of a previously created Blueprint including the build status. */ @@ -113,7 +126,7 @@ export class Blueprints extends APIResource { signal, ...(pollTimeoutMs !== undefined ? { timeoutMs: pollTimeoutMs } : {}), shouldStop: (result) => { - return !['queued', 'provisioning', 'building'].includes(result.status); + return !['queued', 'provisioning', 'building', 'awaiting_upload'].includes(result.status); }, }, ); @@ -413,6 +426,35 @@ export interface BlueprintPreviewView { dockerfile: string; } +export interface BlueprintUploadParameters { + /** + * Name of the Blueprint. + */ + name: string; + + /** + * Parameters to configure your Devbox at launch time. + */ + launch_parameters?: Shared.LaunchParameters | null; + + /** + * (Optional) User defined metadata for the Blueprint. + */ + metadata?: { [key: string]: string } | null; +} + +export interface BlueprintUploadView { + /** + * The created Blueprint, awaiting its image upload. + */ + blueprint: BlueprintView; + + /** + * The reference to push the image to, e.g. via docker push. + */ + push_reference: string; +} + /** * Blueprints are ways to create customized starting points for Devboxes. They * allow you to define custom starting points for Devboxes such that environment @@ -449,7 +491,7 @@ export interface BlueprintView { /** * The status of the Blueprint build. */ - status: 'queued' | 'provisioning' | 'building' | 'failed' | 'build_complete'; + status: 'queued' | 'provisioning' | 'building' | 'awaiting_upload' | 'failed' | 'build_complete'; /** * The ID of the base Blueprint. @@ -716,7 +758,7 @@ export interface BlueprintListParams extends BlueprintsCursorIDPageParams { name?: string; /** - * Filter by build status (queued, provisioning, building, failed, build_complete) + * Filter by build status (queued, provisioning, building, awaiting_upload, failed, build_complete) */ status?: string; } @@ -734,7 +776,7 @@ export interface BlueprintListPublicParams extends BlueprintsCursorIDPageParams name?: string; /** - * Filter by build status (queued, provisioning, building, failed, build_complete) + * Filter by build status (queued, provisioning, building, awaiting_upload, failed, build_complete) */ status?: string; } @@ -826,6 +868,23 @@ export interface BlueprintPreviewParams { system_setup_commands?: Array | null; } +export interface BlueprintRegisterParams { + /** + * Name of the Blueprint. + */ + name: string; + + /** + * Parameters to configure your Devbox at launch time. + */ + launch_parameters?: Shared.LaunchParameters | null; + + /** + * (Optional) User defined metadata for the Blueprint. + */ + metadata?: { [key: string]: string } | null; +} + export namespace BlueprintPreviewParams { /** * A build context backed by an Object. @@ -899,6 +958,8 @@ export declare namespace Blueprints { type BlueprintBuildParameters as BlueprintBuildParameters, type BlueprintListView as BlueprintListView, type BlueprintPreviewView as BlueprintPreviewView, + type BlueprintUploadParameters as BlueprintUploadParameters, + type BlueprintUploadView as BlueprintUploadView, type BlueprintView as BlueprintView, type BlueprintDeleteResponse as BlueprintDeleteResponse, BlueprintViewsBlueprintsCursorIDPage as BlueprintViewsBlueprintsCursorIDPage, @@ -906,5 +967,6 @@ export declare namespace Blueprints { type BlueprintListParams as BlueprintListParams, type BlueprintListPublicParams as BlueprintListPublicParams, type BlueprintPreviewParams as BlueprintPreviewParams, + type BlueprintRegisterParams as BlueprintRegisterParams, }; } diff --git a/src/resources/index.ts b/src/resources/index.ts index fe0f43691..be59bce05 100644 --- a/src/resources/index.ts +++ b/src/resources/index.ts @@ -74,12 +74,15 @@ export { type BlueprintBuildParameters, type BlueprintListView, type BlueprintPreviewView, + type BlueprintUploadParameters, + type BlueprintUploadView, type BlueprintView, type BlueprintDeleteResponse, type BlueprintCreateParams, type BlueprintListParams, type BlueprintListPublicParams, type BlueprintPreviewParams, + type BlueprintRegisterParams, } from './blueprints'; export { DevboxSnapshotViewsDiskSnapshotsCursorIDPage, diff --git a/tests/api-resources/blueprints.test.ts b/tests/api-resources/blueprints.test.ts index 3de333517..dac1b4af0 100644 --- a/tests/api-resources/blueprints.test.ts +++ b/tests/api-resources/blueprints.test.ts @@ -1,14 +1,76 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import { Runloop } from '@runloop/api-client'; -import { Response } from 'node-fetch'; +import { Response, type RequestInfo, type RequestInit } from 'node-fetch'; const client = new Runloop({ bearerToken: 'My Bearer Token', baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', }); +const createRegisterClient = () => { + const fetch = jest.fn( + async (_url: RequestInfo, _init?: RequestInit) => + new Response( + JSON.stringify({ + blueprint: { + id: 'bpt_123', + name: 'name', + status: 'awaiting_upload', + state: 'created', + create_time_ms: 0, + parameters: { name: 'name' }, + }, + push_reference: 'converter.runloop.ai/blueprints:bpt_123', + }), + { headers: { 'Content-Type': 'application/json' } }, + ), + ); + + return { + client: new Runloop({ + bearerToken: 'My Bearer Token', + baseURL: 'http://localhost:5000', + fetch, + }), + fetch, + }; +}; + describe('resource blueprints', () => { + test('register: only required params', async () => { + const { client, fetch } = createRegisterClient(); + const responsePromise = client.blueprints.register({ name: 'name' }); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + expect(response.push_reference).toBe('converter.runloop.ai/blueprints:bpt_123'); + expect(fetch).toHaveBeenCalledWith( + 'http://localhost:5000/v1/blueprints/register', + expect.objectContaining({ method: 'POST' }), + ); + expect(JSON.parse(fetch.mock.calls[0]![1]!.body as string)).toEqual({ name: 'name' }); + }); + + test('register: required and optional params', async () => { + const { client, fetch } = createRegisterClient(); + await client.blueprints.register({ + name: 'name', + launch_parameters: { architecture: 'x86_64' }, + metadata: { source: 'upload' }, + }); + expect(fetch).toHaveBeenCalledWith( + 'http://localhost:5000/v1/blueprints/register', + expect.objectContaining({ method: 'POST' }), + ); + expect(JSON.parse(fetch.mock.calls[0]![1]!.body as string)).toEqual({ + name: 'name', + launch_parameters: { architecture: 'x86_64' }, + metadata: { source: 'upload' }, + }); + }); + test('create: only required params', async () => { const responsePromise = client.blueprints.create({ name: 'name' }); const rawResponse = await responsePromise.asResponse();