@phpsandbox/sdk 0.1.2 → 0.1.6

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.
@@ -1,4 +1,4 @@
1
- import { type Client, type SecretRef } from './index.js';
1
+ import { type Client } from './index.js';
2
2
  import type { PublicationBuildStatus, PublicationDomainDnsRecordType, PublicationEventData, PublicationJurisdiction, PublicationLogChunkData, PublicationProtectionMode, PublicationRegion, PublicationReleaseStatus, PublicationSize, PublicationStatus, PublishStreamEvent, PublishStreamResult } from './schemas/publications.js';
3
3
  export * from './schemas/publications.js';
4
4
  export interface PublicationPlacement {
@@ -13,10 +13,6 @@ export interface PublicationProtectionData {
13
13
  expiresAt?: string;
14
14
  url?: string;
15
15
  }
16
- export interface PublicationFeedbackData {
17
- enabled: boolean;
18
- config: Record<string, unknown>;
19
- }
20
16
  export type PublicationProtectionInput = {
21
17
  mode: 'none';
22
18
  password?: never;
@@ -24,11 +20,8 @@ export type PublicationProtectionInput = {
24
20
  mode: 'password';
25
21
  password: string;
26
22
  };
27
- export interface PublicationFeedbackInput {
28
- enabled: boolean;
29
- config?: Record<string, unknown>;
30
- }
31
23
  export interface CloudflareContainersProviderOptions {
24
+ accountId: string;
32
25
  size?: PublicationSize;
33
26
  sleepAfter?: string;
34
27
  placement?: PublicationPlacement;
@@ -42,11 +35,9 @@ export interface LaravelCloudProviderData {
42
35
  repository?: string;
43
36
  branch?: string;
44
37
  sourceControlProviderType?: 'github' | 'gitlab' | 'bitbucket';
45
- apiKeyConfigured?: boolean;
46
38
  }
47
39
  export interface LaravelCloudProviderInput {
48
- apiKey: SecretRef;
49
- region?: LaravelCloudRegion;
40
+ region: LaravelCloudRegion;
50
41
  }
51
42
  export interface PublicationProviderInputs {
52
43
  'cloudflare-containers': CloudflareContainersProviderOptions;
@@ -73,9 +64,9 @@ export interface PublishInput<TName extends PublicationProviderName = Publicatio
73
64
  slug: string;
74
65
  provider: PublicationProviderInput<TName>;
75
66
  protection?: PublicationProtectionInput;
76
- feedback?: PublicationFeedbackInput;
77
67
  metadata?: Record<string, unknown>;
78
68
  }
69
+ export type PublicationRunResult = PublishStreamResult;
79
70
  export interface PublicationBuildData<TProvider extends PublicationProviderName = PublicationProviderName> {
80
71
  id: string;
81
72
  status: PublicationBuildStatus;
@@ -124,7 +115,6 @@ export interface PublicationData<TProvider extends PublicationProviderName = Pub
124
115
  strategy: string;
125
116
  provider: PublicationProvider<TProvider>;
126
117
  protection: PublicationProtectionData;
127
- feedback: PublicationFeedbackData;
128
118
  eventStreamUrl: string | null;
129
119
  originUrl: string | null;
130
120
  currentRelease?: PublicationReleaseData | null;
@@ -144,10 +134,6 @@ export interface PublicationProtectionSessionData {
144
134
  expiresAt: string;
145
135
  url: string;
146
136
  }
147
- export interface UpdatePublicationFeedbackInput {
148
- enabled: boolean;
149
- config?: Record<string, unknown>;
150
- }
151
137
  export declare class NotebookPublicationApi {
152
138
  private readonly client;
153
139
  constructor(client: Client);
@@ -163,7 +149,6 @@ export declare class PublicationApi {
163
149
  protection(id: string): Promise<PublicationProtectionData>;
164
150
  createProtectionSession(id: string): Promise<PublicationProtectionSessionData>;
165
151
  disableProtection<TProvider extends PublicationProviderName = PublicationProviderName>(id: string): Promise<PublicationInstance<TProvider>>;
166
- setFeedback<TProvider extends PublicationProviderName = PublicationProviderName>(id: string, input: UpdatePublicationFeedbackInput): Promise<PublicationInstance<TProvider>>;
167
152
  listDomains(id: string): Promise<PublicationDomainData[]>;
168
153
  createDomain(id: string, input: CreatePublicationDomainInput): Promise<PublicationDomainData>;
169
154
  refreshDomain(id: string, domainId: string): Promise<PublicationDomainData>;
@@ -186,7 +171,7 @@ export declare class PublicationRun<TProvider extends PublicationProviderName =
186
171
  private readonly subscribers;
187
172
  constructor(initial: PublicationInstance<TProvider>, client: Client);
188
173
  events(): AsyncIterable<PublishStreamEvent>;
189
- result(): Promise<PublishStreamResult>;
174
+ result(): Promise<PublicationRunResult>;
190
175
  publication(): Promise<PublicationInstance<TProvider>>;
191
176
  start(): this;
192
177
  private subscribe;
@@ -214,7 +199,6 @@ export declare class PublicationInstance<TProvider extends PublicationProviderNa
214
199
  protection(): Promise<PublicationProtectionData>;
215
200
  createProtectionSession(): Promise<PublicationProtectionSessionData>;
216
201
  disableProtection(): Promise<PublicationInstance<TProvider>>;
217
- setFeedback(input: UpdatePublicationFeedbackInput): Promise<PublicationInstance<TProvider>>;
218
202
  domains(): Promise<PublicationDomainData[]>;
219
203
  addDomain(input: CreatePublicationDomainInput): Promise<PublicationDomainData>;
220
204
  refreshDomain(domainId: string): Promise<PublicationDomainData>;
@@ -51,10 +51,6 @@ export class PublicationApi {
51
51
  const response = await this.client.delete(`/publications/${id}/protection`);
52
52
  return new PublicationInstance(response.data, this.client);
53
53
  }
54
- async setFeedback(id, input) {
55
- const response = await this.client.put(`/publications/${id}/feedback`, input);
56
- return new PublicationInstance(response.data, this.client);
57
- }
58
54
  async listDomains(id) {
59
55
  const response = await this.client.get(`/publications/${id}/domains`);
60
56
  return unwrapPublicationDomainCollection(response.data);
@@ -156,7 +152,7 @@ export class PublicationRun {
156
152
  if (event.type === 'result') {
157
153
  const { type: _, ...result } = event;
158
154
  this.finalResult = result;
159
- this.resolveResult?.(result);
155
+ this.resolveResult?.(this.finalResult);
160
156
  }
161
157
  }
162
158
  if (!this.finalResult) {
@@ -248,9 +244,6 @@ export class PublicationInstance {
248
244
  disableProtection() {
249
245
  return this.client.publications.disableProtection(this.data.id);
250
246
  }
251
- setFeedback(input) {
252
- return this.client.publications.setFeedback(this.data.id, input);
253
- }
254
247
  domains() {
255
248
  return this.client.publications.listDomains(this.data.id);
256
249
  }
@@ -15,18 +15,13 @@ const restActionRoutes = {
15
15
  'container.stats': { method: 'GET', path: '/runtime/container/stats' },
16
16
  'terminal.list': { method: 'GET', path: '/runtime/terminals' },
17
17
  'shell.exec': { method: 'POST', path: '/runtime/executions' },
18
- 'fs.info': { method: 'POST', path: '/runtime/files/info' },
19
18
  'fs.readFile': { method: 'GET', path: '/runtime/files/content' },
20
19
  'fs.writeFile': { method: 'PUT', path: '/runtime/files/content' },
21
- 'fs.write': { method: 'PUT', path: '/runtime/files/text' },
22
- 'fs.mkdir': { method: 'POST', path: '/runtime/directories' },
23
20
  'fs.createDirectory': { method: 'PUT', path: '/runtime/directories' },
24
21
  'fs.readDirectory': { method: 'POST', path: '/runtime/directories/read' },
25
22
  'fs.stat': { method: 'POST', path: '/runtime/files/stat' },
26
- 'fs.move': { method: 'POST', path: '/runtime/files/move' },
27
23
  'fs.rename': { method: 'POST', path: '/runtime/files/rename' },
28
24
  'fs.copy': { method: 'POST', path: '/runtime/files/copy' },
29
- 'fs.remove': { method: 'POST', path: '/runtime/files/remove' },
30
25
  'fs.delete': { method: 'DELETE', path: '/runtime/files' },
31
26
  'fs.tail': { method: 'POST', path: '/runtime/files/tail' },
32
27
  'fs.find': { method: 'POST', path: '/runtime/files/find' },
@@ -0,0 +1,44 @@
1
+ import type { Client } from './index.js';
2
+ import { Integration, type IntegrationData, type IntegrationReference } from './integrations.js';
3
+ export type SandboxIntegrationEffect = 'network';
4
+ export interface AttachSandboxIntegrationInput {
5
+ integration: IntegrationReference;
6
+ effects?: SandboxIntegrationEffect[];
7
+ }
8
+ export type UpdateSandboxIntegrationInput = {
9
+ integration: IntegrationReference;
10
+ effects?: SandboxIntegrationEffect[];
11
+ } | {
12
+ integration?: IntegrationReference;
13
+ effects: SandboxIntegrationEffect[];
14
+ };
15
+ export interface SandboxIntegrationData {
16
+ id: string;
17
+ integration: IntegrationData;
18
+ effects: SandboxIntegrationEffect[];
19
+ }
20
+ export type SandboxIntegrationReference = string | SandboxIntegration;
21
+ export declare class SandboxIntegrationApi {
22
+ private readonly notebookId;
23
+ private readonly client;
24
+ constructor(notebookId: string, client: Client);
25
+ attach(input: AttachSandboxIntegrationInput): Promise<SandboxIntegration>;
26
+ list(): Promise<SandboxIntegration[]>;
27
+ get(integration: SandboxIntegrationReference): Promise<SandboxIntegration>;
28
+ update(integration: SandboxIntegrationReference, input: UpdateSandboxIntegrationInput): Promise<SandboxIntegration>;
29
+ detach(integration: SandboxIntegrationReference): Promise<void>;
30
+ private path;
31
+ private bindingPath;
32
+ }
33
+ export declare class SandboxIntegration {
34
+ readonly data: SandboxIntegrationData;
35
+ private readonly notebookId;
36
+ private readonly client;
37
+ constructor(data: SandboxIntegrationData, notebookId: string, client: Client);
38
+ get id(): string;
39
+ get integration(): Integration;
40
+ get effects(): SandboxIntegrationEffect[];
41
+ update(input: UpdateSandboxIntegrationInput): Promise<SandboxIntegration>;
42
+ detach(): Promise<void>;
43
+ }
44
+ export declare function sandboxIntegrationId(integration: SandboxIntegrationReference): string;
@@ -0,0 +1,63 @@
1
+ import { Integration, integrationId, } from './integrations.js';
2
+ export class SandboxIntegrationApi {
3
+ constructor(notebookId, client) {
4
+ this.notebookId = notebookId;
5
+ this.client = client;
6
+ }
7
+ async attach(input) {
8
+ const response = await this.client.post(this.path(), {
9
+ ...input,
10
+ integration: integrationId(input.integration),
11
+ });
12
+ return new SandboxIntegration(response.data, this.notebookId, this.client);
13
+ }
14
+ async list() {
15
+ const response = await this.client.get(this.path());
16
+ return response.data.map((integration) => new SandboxIntegration(integration, this.notebookId, this.client));
17
+ }
18
+ async get(integration) {
19
+ const response = await this.client.get(this.bindingPath(integration));
20
+ return new SandboxIntegration(response.data, this.notebookId, this.client);
21
+ }
22
+ async update(integration, input) {
23
+ const response = await this.client.patch(this.bindingPath(integration), {
24
+ ...input,
25
+ integration: input.integration === undefined ? undefined : integrationId(input.integration),
26
+ });
27
+ return new SandboxIntegration(response.data, this.notebookId, this.client);
28
+ }
29
+ async detach(integration) {
30
+ await this.client.delete(this.bindingPath(integration));
31
+ }
32
+ path() {
33
+ return `/notebook/${this.notebookId}/integrations`;
34
+ }
35
+ bindingPath(integration) {
36
+ return `${this.path()}/${encodeURIComponent(sandboxIntegrationId(integration))}`;
37
+ }
38
+ }
39
+ export class SandboxIntegration {
40
+ constructor(data, notebookId, client) {
41
+ this.data = data;
42
+ this.notebookId = notebookId;
43
+ this.client = client;
44
+ }
45
+ get id() {
46
+ return this.data.id;
47
+ }
48
+ get integration() {
49
+ return new Integration(this.data.integration, this.client);
50
+ }
51
+ get effects() {
52
+ return this.data.effects;
53
+ }
54
+ update(input) {
55
+ return new SandboxIntegrationApi(this.notebookId, this.client).update(this, input);
56
+ }
57
+ detach() {
58
+ return new SandboxIntegrationApi(this.notebookId, this.client).detach(this);
59
+ }
60
+ }
61
+ export function sandboxIntegrationId(integration) {
62
+ return typeof integration === 'string' ? integration : integration.id;
63
+ }
@@ -76,7 +76,7 @@ export declare const PublicationEventDataSchema: z.ZodObject<{
76
76
  id: z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>>;
77
77
  sequence: z.ZodNumber;
78
78
  type: z.ZodString;
79
- payload: z.ZodRecord<z.ZodString, z.ZodUnknown>;
79
+ payload: z.ZodPreprocess<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
80
80
  createdAt: z.ZodNullable<z.ZodString>;
81
81
  }, z.core.$loose>;
82
82
  export declare const PublicationLogChunkDataSchema: z.ZodObject<{
@@ -108,6 +108,7 @@ export declare const PublishStreamLogSchema: z.ZodObject<{
108
108
  export declare const PublishStreamResultSchema: z.ZodObject<{
109
109
  success: z.ZodBoolean;
110
110
  publicationId: z.ZodString;
111
+ url: z.ZodString;
111
112
  buildId: z.ZodString;
112
113
  releaseId: z.ZodString;
113
114
  status: z.ZodEnum<{
@@ -145,6 +146,7 @@ export declare const PublishStreamLogEventSchema: z.ZodObject<{
145
146
  export declare const PublishStreamResultEventSchema: z.ZodObject<{
146
147
  success: z.ZodBoolean;
147
148
  publicationId: z.ZodString;
149
+ url: z.ZodString;
148
150
  buildId: z.ZodString;
149
151
  releaseId: z.ZodString;
150
152
  status: z.ZodEnum<{
@@ -181,6 +183,7 @@ export declare const PublishStreamEventSchema: z.ZodDiscriminatedUnion<[z.ZodObj
181
183
  }, z.core.$strict>, z.ZodObject<{
182
184
  success: z.ZodBoolean;
183
185
  publicationId: z.ZodString;
186
+ url: z.ZodString;
184
187
  buildId: z.ZodString;
185
188
  releaseId: z.ZodString;
186
189
  status: z.ZodEnum<{
@@ -235,6 +238,7 @@ export declare const PublishStreamSseDoneEventSchema: z.ZodObject<{
235
238
  data: z.ZodObject<{
236
239
  success: z.ZodBoolean;
237
240
  publicationId: z.ZodString;
241
+ url: z.ZodString;
238
242
  buildId: z.ZodString;
239
243
  releaseId: z.ZodString;
240
244
  status: z.ZodEnum<{
@@ -256,6 +260,7 @@ export declare const PublishStreamSseResultEventSchema: z.ZodObject<{
256
260
  data: z.ZodObject<{
257
261
  success: z.ZodBoolean;
258
262
  publicationId: z.ZodString;
263
+ url: z.ZodString;
259
264
  buildId: z.ZodString;
260
265
  releaseId: z.ZodString;
261
266
  status: z.ZodEnum<{
@@ -304,6 +309,7 @@ export declare const PublishStreamSseEventSchema: z.ZodPipe<z.ZodDiscriminatedUn
304
309
  data: z.ZodObject<{
305
310
  success: z.ZodBoolean;
306
311
  publicationId: z.ZodString;
312
+ url: z.ZodString;
307
313
  buildId: z.ZodString;
308
314
  releaseId: z.ZodString;
309
315
  status: z.ZodEnum<{
@@ -324,6 +330,7 @@ export declare const PublishStreamSseEventSchema: z.ZodPipe<z.ZodDiscriminatedUn
324
330
  data: z.ZodObject<{
325
331
  success: z.ZodBoolean;
326
332
  publicationId: z.ZodString;
333
+ url: z.ZodString;
327
334
  buildId: z.ZodString;
328
335
  releaseId: z.ZodString;
329
336
  status: z.ZodEnum<{
@@ -356,6 +363,7 @@ export declare const PublishStreamSseEventSchema: z.ZodPipe<z.ZodDiscriminatedUn
356
363
  } | {
357
364
  success: boolean;
358
365
  publicationId: string;
366
+ url: string;
359
367
  buildId: string;
360
368
  releaseId: string;
361
369
  status: "stopped" | "deleted" | "pending" | "queued" | "building" | "deploying" | "healthy" | "failed" | "deleting" | "delete_failed";
@@ -378,6 +386,7 @@ export declare const PublishStreamSseEventSchema: z.ZodPipe<z.ZodDiscriminatedUn
378
386
  data: {
379
387
  success: boolean;
380
388
  publicationId: string;
389
+ url: string;
381
390
  buildId: string;
382
391
  releaseId: string;
383
392
  status: "stopped" | "deleted" | "pending" | "queued" | "building" | "deploying" | "healthy" | "failed" | "deleting" | "delete_failed";
@@ -387,6 +396,7 @@ export declare const PublishStreamSseEventSchema: z.ZodPipe<z.ZodDiscriminatedUn
387
396
  data: {
388
397
  success: boolean;
389
398
  publicationId: string;
399
+ url: string;
390
400
  buildId: string;
391
401
  releaseId: string;
392
402
  status: "stopped" | "deleted" | "pending" | "queued" | "building" | "deploying" | "healthy" | "failed" | "deleting" | "delete_failed";
@@ -51,11 +51,12 @@ export const PublicationDomainDnsRecordTypeSchema = z.enum(['CNAME', 'ALIAS', 'A
51
51
  export const PublicationProtectionModeSchema = z.enum(['none', 'password']);
52
52
  export const PublicationLogStreamSchema = z.enum(['build', 'runtime']);
53
53
  export const PublishStreamLogStreamSchema = z.enum(['build', 'publish', 'runtime']);
54
+ const PublicationEventPayloadSchema = z.preprocess((payload) => Array.isArray(payload) && payload.length === 0 ? {} : payload, z.record(z.string(), z.unknown()));
54
55
  export const PublicationEventDataSchema = z.object({
55
56
  id: z.union([z.number(), z.string()]).optional(),
56
57
  sequence: z.number(),
57
58
  type: z.string(),
58
- payload: z.record(z.string(), z.unknown()),
59
+ payload: PublicationEventPayloadSchema,
59
60
  createdAt: z.string().nullable(),
60
61
  }).passthrough();
61
62
  export const PublicationLogChunkDataSchema = z.object({
@@ -76,6 +77,7 @@ export const PublishStreamLogSchema = z.object({
76
77
  export const PublishStreamResultSchema = z.object({
77
78
  success: z.boolean(),
78
79
  publicationId: z.string(),
80
+ url: z.string().url(),
79
81
  buildId: z.string(),
80
82
  releaseId: z.string(),
81
83
  status: PublicationStatusSchema,
@@ -17,12 +17,6 @@ export interface TerminalCreateInput {
17
17
  env?: Record<string, string | number | boolean>;
18
18
  cwd?: string;
19
19
  }
20
- export interface TerminalSpawnInput {
21
- command: string | string[];
22
- opts: {
23
- id: string;
24
- } & SpawnOptions;
25
- }
26
20
  export interface TerminalEventData {
27
21
  'terminal.output': {
28
22
  output: string;
@@ -50,7 +44,6 @@ export interface TerminalActions {
50
44
  }>;
51
45
  'terminal.list': Action<{}, Task[]>;
52
46
  'terminal.create': Action<TerminalCreateInput, Task>;
53
- 'terminal.spawn': Action<TerminalSpawnInput, Task>;
54
47
  'terminal.attach': Action<{
55
48
  id: string;
56
49
  replayHistory?: boolean;
@@ -91,7 +91,8 @@ Connection lifecycle methods, streaming actions, and event subscriptions require
91
91
  ### Git (`notebook.git`)
92
92
 
93
93
  - `checkpoint(author, message, branch = 'main')`
94
- - `sync({ provider, repository, author, branch?, credential, direction?, visibility?, setDefault? })`
94
+ - `targets.create({ provider, repository, author, branch?, setDefault? })`
95
+ - `targets.list()`, `targets.get(target)`, and `target.sync({ direction?, author? })`
95
96
  - `diff({ url?, token?, ref?, base?, target? })`
96
97
  - `merge({ url?, token?, ref?, target?, expectedBaseRef?, author?, message? })`
97
98
  - `conflicts()`
@@ -104,6 +105,16 @@ Connection lifecycle methods, streaming actions, and event subscriptions require
104
105
  - `log(ref = 'main')`
105
106
  - `restore(ref)`
106
107
 
108
+ ### Sandbox integrations (`notebook.integrations`)
109
+
110
+ - `attach({ integration, effects? })`
111
+ - A sandbox has one attached integration per provider. `binding.update({ integration })` replaces that provider selection.
112
+ - Provider-backed operations resolve the attached integration when they run; persistent sandbox behavior remains controlled by explicit effects.
113
+ - `list()`
114
+ - `get(binding)`
115
+ - Binding resources expose `update({ effects })` and `detach()`
116
+ - The currently supported sandbox effect is GitHub `network`; effects are explicit and provider-specific.
117
+
107
118
  ### Mail (`notebook.mail`)
108
119
 
109
120
  - `status()` returning non-sensitive mail configuration state
@@ -113,6 +124,16 @@ Connection lifecycle methods, streaming actions, and event subscriptions require
113
124
  - `get(hash)`
114
125
  - `delete(hash)`
115
126
 
127
+ ### Feedback (`sandbox.feedback`)
128
+
129
+ - `configure({ widget })`, where `widget` is `null` or contains `targets`, optional `label`, and optional `types`
130
+ - `submit({ type, message, author?, context?, metadata? })`
131
+ - `list({ type?, status?, source?, page? })`
132
+ - `get(id)`
133
+ - `update(id, { status })`
134
+
135
+ Feedback ingestion is always available through the authenticated API. Configuration controls only the optional widget and can target `preview`, `publication`, or both.
136
+
116
137
  ### LSP (`notebook.lsp`)
117
138
 
118
139
  - Messaging: `start(id)`, `close(id)`, `message(id, payload)`
@@ -130,11 +151,20 @@ Connection lifecycle methods, streaming actions, and event subscriptions require
130
151
 
131
152
  ## Publications
132
153
 
133
- - `notebook.publish({ slug, provider, protection?, feedback?, metadata? })`
154
+ - `notebook.publish({ slug, provider, protection?, metadata? })`
134
155
  - `notebook.publication()` returns the current publication or `null`
135
156
  - `client.publications.get(id)`, `destroy(id)`, `events(id)`, `logs(id)`, and `buildLogs(id, buildId)`
136
- - A publish run exposes `events()` as an `AsyncIterable`, `result()`, and `publication()`.
157
+ - A publish run exposes `events()` as an `AsyncIterable`, `result()` with the final status and URL, and `publication()` when refreshed deployment state is needed.
137
158
  - Built-in provider names are `cloudflare-containers`, `ssh-server`, and `laravel-cloud`. Provider maps support TypeScript declaration merging.
159
+ - Cloudflare Containers input uses `{ name: 'cloudflare-containers', accountId, size?, sleepAfter?, placement?, instances? }` and resolves the sandbox's attached Cloudflare integration when each operation runs.
160
+ - Laravel Cloud input uses `{ name: 'laravel-cloud', region }` and resolves the sandbox's attached Laravel Cloud integration when each operation runs.
161
+
162
+ ## Account integrations
163
+
164
+ - `client.integrations.link({ provider, label?, authorization })`
165
+ - `client.integrations.list()`, `get(integration)`, `update(integration, input)`, and `unlink(integration)`
166
+ - Resource instances expose `update(input)` and `unlink()`.
167
+ - Built-in providers are `cloudflare`, `github`, and `laravel-cloud`; current public authorization uses opaque caller-managed tokens.
138
168
 
139
169
  ## Common Errors
140
170
 
@@ -32,7 +32,7 @@ PHPSANDBOX_TOKEN=your-token PHPSANDBOX_NOTEBOOK_ID=notebook-id \
32
32
  npx tsx examples/publish-laravel-cloud.ts
33
33
  ```
34
34
 
35
- Publication examples accept `PHPSANDBOX_API_URL` and `PHPSANDBOX_PUBLISH_SLUG`. On the first run they create the notebook's publication; later runs publish it again without changing slug or provider. The SSH example creates a server registration when `PHPSANDBOX_PUBLISH_SERVER_ID` is not set; on local `.test` APIs it also starts local Rook from `PHPSANDBOX_ROOK_DIR` or the sibling `rook` repo. The Laravel Cloud example syncs the notebook to `PHPSANDBOX_LARAVEL_CLOUD_REPOSITORY`, upserts `PHPSANDBOX_GITHUB_TOKEN` into `GITHUB_TOKEN`, and upserts `PHPSANDBOX_LARAVEL_CLOUD_API_KEY` into `LARAVEL_CLOUD_API_KEY` on first publish.
35
+ Publication examples accept `PHPSANDBOX_API_URL` and `PHPSANDBOX_PUBLISH_SLUG`. On the first run they create the notebook's publication; later runs publish it again without changing slug or provider. The SSH example creates a server registration when `PHPSANDBOX_PUBLISH_SERVER_ID` is not set; on local `.test` APIs it also starts local Rook from `PHPSANDBOX_ROOK_DIR` or the sibling `rook` repo. The Laravel Cloud example links or refreshes the GitHub and Laravel Cloud integrations, attaches each provider to the sandbox, synchronizes the notebook to `PHPSANDBOX_LARAVEL_CLOUD_REPOSITORY`, and publishes through the attached Laravel Cloud integration.
36
36
 
37
37
  ### Running The Laravel Load Test
38
38
 
@@ -13,10 +13,24 @@ if (!notebookId) {
13
13
 
14
14
  const slug = process.env.PHPSANDBOX_PUBLISH_SLUG ?? `sdk-cloudflare-${Date.now().toString(36)}`;
15
15
  const apiUrl = process.env.PHPSANDBOX_API_URL;
16
+ const cloudflareAccountId = process.env.CLOUDFLARE_ACCOUNT_ID;
17
+ const cloudflareApiToken = process.env.CLOUDFLARE_API_TOKEN;
18
+ if (!cloudflareAccountId || !cloudflareApiToken) {
19
+ throw new Error('Missing CLOUDFLARE_ACCOUNT_ID or CLOUDFLARE_API_TOKEN');
20
+ }
16
21
  const client = new Client(token, apiUrl);
17
22
  const notebook = await client.notebook.get(notebookId);
18
23
 
19
24
  try {
25
+ const existingIntegration = (await client.integrations.list()).find(({ provider }) => provider === 'cloudflare');
26
+ const integration = existingIntegration
27
+ ? await existingIntegration.update({ authorization: { type: 'token', token: cloudflareApiToken } })
28
+ : await client.integrations.link({
29
+ provider: 'cloudflare',
30
+ authorization: { type: 'token', token: cloudflareApiToken },
31
+ });
32
+ await notebook.integrations.attach({ integration });
33
+
20
34
  const publication = await notebook.publication();
21
35
  if (publication && publication.data.provider.name !== 'cloudflare-containers') {
22
36
  throw new Error(`Notebook is already published to ${publicationProviderLabel(publication.data.provider.name)}. Destroy that publication before changing providers.`);
@@ -28,6 +42,7 @@ try {
28
42
  slug,
29
43
  provider: {
30
44
  name: 'cloudflare-containers',
45
+ accountId: cloudflareAccountId,
31
46
  size: 'small',
32
47
  sleepAfter: '30m',
33
48
  },
@@ -1,4 +1,4 @@
1
- import { Client, type LaravelCloudRegion } from '../src/index.js';
1
+ import { Client, type Integration, type LaravelCloudRegion } from '../src/index.js';
2
2
  import { publicationProviderLabel } from './publication-provider-label.js';
3
3
 
4
4
  const token = process.env.PHPSANDBOX_TOKEN;
@@ -11,8 +11,6 @@ if (!notebookId) {
11
11
  throw new Error('Missing PHPSANDBOX_NOTEBOOK_ID');
12
12
  }
13
13
 
14
- const apiKeySecret = 'LARAVEL_CLOUD_API_KEY';
15
- const githubTokenSecret = 'GITHUB_TOKEN';
16
14
  const slug = process.env.PHPSANDBOX_PUBLISH_SLUG ?? `sdk-laravel-cloud-${Date.now().toString(36)}`;
17
15
  const region: LaravelCloudRegion = 'eu-central-1';
18
16
  const apiUrl = process.env.PHPSANDBOX_API_URL ?? 'https://api.phpsandbox.io/v1';
@@ -74,39 +72,52 @@ async function publishFirstLaravelCloudPublication() {
74
72
  throw new Error('Missing PHPSANDBOX_LARAVEL_CLOUD_API_KEY');
75
73
  }
76
74
 
77
- await notebook.secrets.set(githubTokenSecret, {
78
- value: githubToken,
79
- type: 'credential',
80
- });
81
-
82
- const source = await notebook.git.sync({
75
+ const existingIntegration = (await client.integrations.list()).find(({ provider }) => provider === 'github');
76
+ const github = existingIntegration
77
+ ? await existingIntegration.update({ authorization: { type: 'token', token: githubToken } })
78
+ : await client.integrations.link({
79
+ provider: 'github',
80
+ authorization: { type: 'token', token: githubToken },
81
+ });
82
+ await selectSandboxIntegration(github);
83
+ const target = await notebook.git.targets.create({
83
84
  provider: 'github',
84
85
  repository,
85
86
  author: { name: gitAuthorName, email: gitAuthorEmail },
86
87
  branch: 'main',
87
- credential: {
88
- secret: githubTokenSecret,
89
- },
90
88
  setDefault: true,
91
89
  });
90
+ const source = await target.sync();
92
91
 
93
- console.log('source:', source.repository, source.branch, source.lastCommitSha);
94
-
95
- const secret = await notebook.secrets.set(apiKeySecret, {
96
- value: apiKey,
97
- type: 'credential',
98
- });
92
+ console.log('source:', source.data.repository, source.data.branch, source.data.lastCommitSha);
99
93
 
100
- console.log('secret:', secret.name);
94
+ const existingLaravelCloudIntegration = (await client.integrations.list())
95
+ .find(({ provider }) => provider === 'laravel-cloud');
96
+ const laravelCloud = existingLaravelCloudIntegration
97
+ ? await existingLaravelCloudIntegration.update({ authorization: { type: 'token', token: apiKey } })
98
+ : await client.integrations.link({
99
+ provider: 'laravel-cloud',
100
+ authorization: { type: 'token', token: apiKey },
101
+ });
102
+ await selectSandboxIntegration(laravelCloud);
101
103
 
102
104
  return notebook.publish({
103
105
  slug,
104
106
  provider: {
105
107
  name: 'laravel-cloud',
106
- apiKey: {
107
- secret: apiKeySecret,
108
- },
109
108
  region,
110
109
  },
111
110
  });
112
111
  }
112
+
113
+ async function selectSandboxIntegration(integration: Integration): Promise<void> {
114
+ const binding = (await notebook.integrations.list())
115
+ .find((candidate) => candidate.integration.provider === integration.provider);
116
+
117
+ if (binding) {
118
+ await binding.update({ integration });
119
+ return;
120
+ }
121
+
122
+ await notebook.integrations.attach({ integration });
123
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phpsandbox/sdk",
3
- "version": "0.1.2",
3
+ "version": "0.1.6",
4
4
  "description": "A comprehensive TypeScript SDK for interacting with cloud-based PHP development environments",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",
@@ -32,7 +32,16 @@
32
32
  "build:tsc": "tsc",
33
33
  "build:browser": "node build.mjs",
34
34
  "clean": "rm -rf dist",
35
- "test": "vitest run",
35
+ "test": "npm run build:tsc && vitest run --config vitest.config.ts",
36
+ "smoke": "npm run smoke:pr",
37
+ "smoke:pr": "npm run build:tsc && vitest run --config tests/smoke/vitest.config.ts tests/smoke/pr",
38
+ "smoke:nightly": "npm run build:tsc && vitest run --config tests/smoke/vitest.config.ts tests/smoke/pr tests/smoke/nightly --exclude tests/smoke/nightly/publication.smoke.ts",
39
+ "smoke:publications": "npm run build:tsc && vitest run --reporter=verbose --config tests/smoke/vitest.config.ts tests/smoke/nightly/publication.smoke.ts",
40
+ "smoke:composer-credentials": "npm run build:tsc && vitest run --reporter=verbose --config tests/smoke/vitest.config.ts tests/smoke/integrations/composer-credentials.smoke.ts",
41
+ "smoke:beacon": "npm run build:tsc && vitest run --reporter=verbose --config tests/smoke/vitest.config.ts tests/smoke/integrations/beacon.smoke.ts",
42
+ "smoke:feedback-widget": "npm run build:tsc && vitest run --reporter=verbose --config tests/smoke/vitest.config.ts tests/smoke/integrations/feedback-widget.smoke.ts",
43
+ "smoke:reaper": "npm run build:tsc && vitest run --reporter=verbose --config tests/smoke/vitest.config.ts tests/smoke/maintenance/resources.reaper.smoke.ts",
44
+ "smoke:typecheck": "npm run build:tsc && tsc --project tests/smoke/tsconfig.json --noEmit",
36
45
  "check:examples": "tsc --noEmit --module NodeNext --moduleResolution NodeNext --target ES2020 --types node --skipLibCheck --allowSyntheticDefaultImports --esModuleInterop examples/basic.ts examples/publish-cloudflare-container.ts examples/publish-laravel-cloud.ts examples/publish-ssh-agent.ts examples/get.ts examples/git-merge-smoke.ts examples/laravel-load-test.ts examples/notebook-readonly-smoke.ts",
37
46
  "prepublishOnly": "npm test && npm run check:examples && npm run clean && npm run build",
38
47
  "bumpAndPublish": "npm version patch && npm publish --access public"
@@ -74,6 +83,7 @@
74
83
  "@types/ws": "^8.18.1",
75
84
  "axios": "^1.17.0",
76
85
  "esbuild": "^0.27.1",
86
+ "playwright": "^1.62.1",
77
87
  "typescript": "^5.7.2",
78
88
  "vitest": "^4.1.8"
79
89
  }