@cloudcannon/sdk 0.0.0 → 0.0.2

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/dist/index.d.ts CHANGED
@@ -1,9 +1,13 @@
1
1
  import type { components, operations, paths } from './schema.ts';
2
+ import { BuildClient } from './src/build.ts';
3
+ import { type CommitEditingSessionOptions, type CommitEditingSessionResponse, EditingSessionClient } from './src/editing-session.ts';
4
+ import { EditingSessionFileClient } from './src/editing-session-file.ts';
2
5
  import { InboxClient } from './src/inbox.ts';
3
6
  import { OrgClient } from './src/org.ts';
4
7
  import { type BuildConfiguration, SiteClient } from './src/site.ts';
5
8
  import { SiteInboxClient } from './src/site-inbox.ts';
6
- export type { BuildConfiguration };
9
+ import { SyncClient } from './src/sync.ts';
10
+ export type { BuildConfiguration, CommitEditingSessionOptions, CommitEditingSessionResponse };
7
11
  export type Provider = operations['Providers_Repositories']['parameters']['path']['provider'];
8
12
  export type Site = components['schemas']['SiteBlueprint'];
9
13
  export type Org = components['schemas']['OrgBlueprintFull'];
@@ -15,6 +19,10 @@ export type SiteDam = components['schemas']['SiteDamBlueprint'];
15
19
  export type FormSubmission = components['schemas']['FormHookBlueprint'];
16
20
  export type Inbox = components['schemas']['InboxBlueprint'];
17
21
  export type Dam = components['schemas']['DamBlueprint'];
22
+ export type EditingSession = components['schemas']['EditingSessionBlueprint'];
23
+ export type EditingSessionFile = components['schemas']['EditingSessionFileBlueprint'];
24
+ export type EditingSessionFileContribution = components['schemas']['EditingSessionFileContributionBlueprint'];
25
+ export type UploadData = operations['Index_UploadData']['responses']['200']['content']['application/json'];
18
26
  export type ProviderDetails = {
19
27
  provider: Provider;
20
28
  repository: string;
@@ -73,5 +81,10 @@ export default class CloudCannonClient {
73
81
  site(uuid: string): SiteClient;
74
82
  inbox(uuid: string): InboxClient;
75
83
  siteInbox(uuid: string): SiteInboxClient;
84
+ editingSession(uuid: string): EditingSessionClient;
85
+ editingSessionFile(uuid: string): EditingSessionFileClient;
86
+ build(uuid: string): BuildClient;
87
+ sync(uuid: string): SyncClient;
76
88
  orgs(): Promise<Org[]>;
89
+ getUploadData(): Promise<UploadData>;
77
90
  }
package/dist/index.js CHANGED
@@ -1,7 +1,11 @@
1
+ import { BuildClient } from "./src/build.js";
2
+ import { EditingSessionClient, } from "./src/editing-session.js";
3
+ import { EditingSessionFileClient } from "./src/editing-session-file.js";
1
4
  import { InboxClient } from "./src/inbox.js";
2
5
  import { OrgClient } from "./src/org.js";
3
6
  import { SiteClient } from "./src/site.js";
4
7
  import { SiteInboxClient } from "./src/site-inbox.js";
8
+ import { SyncClient } from "./src/sync.js";
5
9
  export default class CloudCannonClient {
6
10
  #apiKey;
7
11
  #appDomain;
@@ -52,9 +56,32 @@ export default class CloudCannonClient {
52
56
  siteInbox(uuid) {
53
57
  return new SiteInboxClient(uuid, this);
54
58
  }
59
+ editingSession(uuid) {
60
+ return new EditingSessionClient(uuid, this);
61
+ }
62
+ editingSessionFile(uuid) {
63
+ return new EditingSessionFileClient(uuid, this);
64
+ }
65
+ build(uuid) {
66
+ return new BuildClient(uuid, this);
67
+ }
68
+ sync(uuid) {
69
+ return new SyncClient(uuid, this);
70
+ }
55
71
  async orgs() {
56
72
  const resp = await this.fetch('/orgs');
57
73
  const orgs = await resp.json();
58
74
  return orgs;
59
75
  }
76
+ async getUploadData() {
77
+ const resp = await this.fetch('/upload-data');
78
+ if (resp.status === 403) {
79
+ throw new Error('Error fetching upload data. Permission denied');
80
+ }
81
+ if (resp.status === 422) {
82
+ throw new Error('Error fetching upload data. Invalid request');
83
+ }
84
+ const uploadData = await resp.json();
85
+ return uploadData;
86
+ }
60
87
  }
package/dist/schema.d.ts CHANGED
@@ -2202,6 +2202,23 @@ export interface paths {
2202
2202
  patch?: never;
2203
2203
  trace?: never;
2204
2204
  };
2205
+ '/api/v0/upload-data': {
2206
+ parameters: {
2207
+ query?: never;
2208
+ header?: never;
2209
+ path?: never;
2210
+ cookie?: never;
2211
+ };
2212
+ /** @description Get the presigned data for file upload */
2213
+ get: operations['Index_UploadData'];
2214
+ put?: never;
2215
+ post?: never;
2216
+ delete?: never;
2217
+ options?: never;
2218
+ head?: never;
2219
+ patch?: never;
2220
+ trace?: never;
2221
+ };
2205
2222
  '/api/v0/users': {
2206
2223
  parameters: {
2207
2224
  query?: never;
@@ -3841,7 +3858,7 @@ export interface operations {
3841
3858
  [name: string]: unknown;
3842
3859
  };
3843
3860
  content: {
3844
- 'application/json': components['schemas']['EditingSessionFileBlueprint'][];
3861
+ 'application/json': components['schemas']['EditingSessionFileContributionBlueprint'][];
3845
3862
  };
3846
3863
  };
3847
3864
  403: components['responses']['ForbiddenResp'];
@@ -3859,9 +3876,9 @@ export interface operations {
3859
3876
  requestBody: {
3860
3877
  content: {
3861
3878
  'application/json': {
3862
- s3_key?: Record<string, never>;
3879
+ s3_key: string;
3863
3880
  content_hash: string;
3864
- previous_content_hash?: Record<string, never>;
3881
+ previous_content_hash?: string;
3865
3882
  };
3866
3883
  };
3867
3884
  };
@@ -4039,7 +4056,13 @@ export interface operations {
4039
4056
  };
4040
4057
  cookie?: never;
4041
4058
  };
4042
- requestBody?: never;
4059
+ requestBody: {
4060
+ content: {
4061
+ 'application/json': {
4062
+ previous_content_hash: string;
4063
+ };
4064
+ };
4065
+ };
4043
4066
  responses: {
4044
4067
  /** @description Created */
4045
4068
  200: {
@@ -4257,8 +4280,8 @@ export interface operations {
4257
4280
  edit_type?: string | null;
4258
4281
  path?: string | null;
4259
4282
  source_path?: string | null;
4260
- discard_unsaved?: Record<string, never>;
4261
- previous_content_hash?: Record<string, never>;
4283
+ discard_unsaved?: boolean;
4284
+ previous_content_hash?: string;
4262
4285
  metadata?: {
4263
4286
  [key: string]: unknown;
4264
4287
  };
@@ -4266,6 +4289,15 @@ export interface operations {
4266
4289
  };
4267
4290
  };
4268
4291
  responses: {
4292
+ /** @description Success */
4293
+ 200: {
4294
+ headers: {
4295
+ [name: string]: unknown;
4296
+ };
4297
+ content: {
4298
+ 'application/json': components['schemas']['EditingSessionFileBlueprint'];
4299
+ };
4300
+ };
4269
4301
  /** @description Created */
4270
4302
  201: {
4271
4303
  headers: {
@@ -8613,6 +8645,34 @@ export interface operations {
8613
8645
  403: components['responses']['ForbiddenResp'];
8614
8646
  };
8615
8647
  };
8648
+ Index_UploadData: {
8649
+ parameters: {
8650
+ query?: never;
8651
+ header?: never;
8652
+ path?: never;
8653
+ cookie?: never;
8654
+ };
8655
+ requestBody?: never;
8656
+ responses: {
8657
+ /** @description OK */
8658
+ 200: {
8659
+ headers: {
8660
+ [name: string]: unknown;
8661
+ };
8662
+ content: {
8663
+ 'application/json': {
8664
+ prefix: string;
8665
+ url: string;
8666
+ fields: {
8667
+ [key: string]: string;
8668
+ };
8669
+ };
8670
+ };
8671
+ };
8672
+ 403: components['responses']['ForbiddenResp'];
8673
+ 422: components['responses']['ErrorResp'];
8674
+ };
8675
+ };
8616
8676
  Users_ShowCurrentUser: {
8617
8677
  parameters: {
8618
8678
  query?: never;
@@ -0,0 +1,6 @@
1
+ import type CloudCannonClient from '../index.ts';
2
+ export declare class BuildClient {
3
+ #private;
4
+ constructor(uuid: string, client: CloudCannonClient);
5
+ get(): Promise<Response>;
6
+ }
@@ -0,0 +1,15 @@
1
+ export class BuildClient {
2
+ #uuid;
3
+ #client;
4
+ constructor(uuid, client) {
5
+ this.#uuid = uuid;
6
+ this.#client = client;
7
+ }
8
+ async get() {
9
+ const resp = await this.#client.fetch(`/builds/${this.#uuid}`);
10
+ if (resp.status === 401 || resp.status === 403) {
11
+ throw new Error('Error fetching build. Permission denied');
12
+ }
13
+ return resp;
14
+ }
15
+ }
@@ -0,0 +1,13 @@
1
+ import type CloudCannonClient from '../index.ts';
2
+ import type { EditingSessionFile, EditingSessionFileContribution } from '../index.ts';
3
+ import type { operations } from '../schema.js';
4
+ export type CreateContributionOptions = operations['Editing Session Contributions_Create']['requestBody']['content']['application/json'];
5
+ export type UnlockOptions = operations['Editing Session File_Unlock']['requestBody']['content']['application/json'];
6
+ export declare class EditingSessionFileClient {
7
+ #private;
8
+ constructor(uuid: string, client: CloudCannonClient);
9
+ get(): Promise<EditingSessionFile>;
10
+ getContributions(): Promise<EditingSessionFileContribution[]>;
11
+ createContribution(body: CreateContributionOptions): Promise<EditingSessionFileContribution>;
12
+ unlock(body: UnlockOptions): Promise<EditingSessionFileContribution>;
13
+ }
@@ -0,0 +1,58 @@
1
+ import { ApiError } from "./errors.js";
2
+ export class EditingSessionFileClient {
3
+ #uuid;
4
+ #client;
5
+ constructor(uuid, client) {
6
+ this.#uuid = uuid;
7
+ this.#client = client;
8
+ }
9
+ async get() {
10
+ const resp = await this.#client.fetch(`/editing_session_files/${this.#uuid}`);
11
+ if (resp.status === 403) {
12
+ throw new Error('Error fetching editing session file. Permission denied');
13
+ }
14
+ const file = await resp.json();
15
+ return file;
16
+ }
17
+ async getContributions() {
18
+ const resp = await this.#client.fetch(`/editing_session_files/${this.#uuid}/contributions`);
19
+ if (resp.status === 403) {
20
+ throw new Error('Error fetching editing session file contributions. Permission denied');
21
+ }
22
+ const contributions = await resp.json();
23
+ return contributions;
24
+ }
25
+ async createContribution(body) {
26
+ const resp = await this.#client.fetch(`/editing_session_files/${this.#uuid}/contributions`, {
27
+ method: 'POST',
28
+ body,
29
+ });
30
+ if (resp.status === 401 || resp.status === 403) {
31
+ throw new Error('Error creating editing session file contribution. Permission denied');
32
+ }
33
+ if (resp.status === 422) {
34
+ const errorResp = await resp.json();
35
+ throw new ApiError('Error creating editing session file contribution. Invalid request', errorResp.errors, `/editing_session_files/${this.#uuid}/contributions`, { method: 'POST', body }, resp.status);
36
+ }
37
+ const contribution = await resp.json();
38
+ return contribution;
39
+ }
40
+ async unlock(body) {
41
+ const resp = await this.#client.fetch(`/editing_session_files/${this.#uuid}/unlock`, {
42
+ method: 'PUT',
43
+ body,
44
+ });
45
+ if (resp.status === 403) {
46
+ throw new Error('Error unlocking editing session file. Permission denied');
47
+ }
48
+ if (resp.status === 404) {
49
+ throw new Error('Error unlocking editing session file. File not found');
50
+ }
51
+ if (resp.status === 422) {
52
+ const errorResp = await resp.json();
53
+ throw new ApiError('Error unlocking editing session file. Invalid request', errorResp.errors, `/editing_session_files/${this.#uuid}/unlock`, { method: 'PUT', body }, resp.status);
54
+ }
55
+ const contribution = await resp.json();
56
+ return contribution;
57
+ }
58
+ }
@@ -0,0 +1,14 @@
1
+ import type CloudCannonClient from '../index.ts';
2
+ import type { EditingSession, EditingSessionFile } from '../index.ts';
3
+ import type { operations } from '../schema.js';
4
+ export type CreateEditingSessionFileOptions = operations['Editing Session Files_Create']['requestBody']['content']['application/json'];
5
+ export type CommitEditingSessionOptions = operations['Editing Session_Commit']['requestBody']['content']['application/json'];
6
+ export type CommitEditingSessionResponse = operations['Editing Session_Commit']['responses']['200']['content']['application/json'];
7
+ export declare class EditingSessionClient {
8
+ #private;
9
+ constructor(uuid: string, client: CloudCannonClient);
10
+ get(): Promise<EditingSession>;
11
+ getFiles(): Promise<EditingSessionFile[]>;
12
+ createFile(body: CreateEditingSessionFileOptions): Promise<EditingSessionFile>;
13
+ commit(): Promise<CommitEditingSessionResponse>;
14
+ }
@@ -0,0 +1,54 @@
1
+ import { ApiError } from "./errors.js";
2
+ export class EditingSessionClient {
3
+ #uuid;
4
+ #client;
5
+ constructor(uuid, client) {
6
+ this.#uuid = uuid;
7
+ this.#client = client;
8
+ }
9
+ async get() {
10
+ const resp = await this.#client.fetch(`/editing_sessions/${this.#uuid}`);
11
+ if (resp.status === 403) {
12
+ throw new Error('Error fetching editing session. Permission denied');
13
+ }
14
+ const editingSession = await resp.json();
15
+ return editingSession;
16
+ }
17
+ async getFiles() {
18
+ const resp = await this.#client.fetch(`/editing_sessions/${this.#uuid}/files`);
19
+ if (resp.status === 403) {
20
+ throw new Error('Error fetching editing session files. Permission denied');
21
+ }
22
+ const files = await resp.json();
23
+ return files;
24
+ }
25
+ async createFile(body) {
26
+ const resp = await this.#client.fetch(`/editing_sessions/${this.#uuid}/files`, {
27
+ method: 'POST',
28
+ body,
29
+ });
30
+ if (resp.status === 403) {
31
+ throw new Error('Error creating editing session file. Permission denied');
32
+ }
33
+ if (resp.status === 422) {
34
+ const errorResp = await resp.json();
35
+ throw new ApiError('Error creating editing session file. Invalid request', errorResp.errors, `/editing_sessions/${this.#uuid}/files`, { method: 'POST', body }, resp.status);
36
+ }
37
+ const file = await resp.json();
38
+ return file;
39
+ }
40
+ async commit() {
41
+ const resp = await this.#client.fetch(`/editing_sessions/${this.#uuid}/commit`, {
42
+ method: 'POST',
43
+ });
44
+ if (resp.status === 403) {
45
+ throw new Error('Error committing editing session. Permission denied');
46
+ }
47
+ if (resp.status === 422) {
48
+ const errorResp = await resp.json();
49
+ throw new ApiError('Error committing editing session. Invalid request', errorResp.errors, `/editing_sessions/${this.#uuid}/commit`, { method: 'POST' }, resp.status);
50
+ }
51
+ const result = await resp.json();
52
+ return result;
53
+ }
54
+ }
@@ -1,5 +1,5 @@
1
1
  import type CloudCannonClient from '../index.ts';
2
- import type { Build, ProviderDetails, Site, SiteDam, SiteInbox, SiteScan, Sync } from '../index.ts';
2
+ import type { Build, EditingSession, ProviderDetails, Site, SiteDam, SiteInbox, SiteScan, Sync } from '../index.ts';
3
3
  import type { operations } from '../schema.js';
4
4
  export type BuildConfiguration = Partial<Omit<operations['Sites_UpdateBuild']['requestBody']['content']['application/json'], 'build_configuration'>> & {
5
5
  compile?: {
@@ -24,6 +24,11 @@ export type UpdateSiteOptions = operations['Sites_Update']['requestBody']['conte
24
24
  export type CopySiteOptions = operations['Sites_Copy']['requestBody']['content']['application/json'];
25
25
  export type ConnectInboxOptions = operations['Site Inboxes_Create']['requestBody']['content']['application/json'];
26
26
  export type ConnectDamOptions = operations['Dams_Create']['requestBody']['content']['application/json'];
27
+ export type FileListing = operations['Files_Index']['responses']['200']['content']['application/json'][number];
28
+ export type UploadFileOptions = {
29
+ type?: string;
30
+ overwriteExistingFile?: boolean;
31
+ };
27
32
  export declare class SiteClient {
28
33
  #private;
29
34
  constructor(uuid: string, client: CloudCannonClient);
@@ -34,6 +39,7 @@ export declare class SiteClient {
34
39
  updateBuildConfig(options: BuildConfiguration): Promise<Site>;
35
40
  getBuilds(): Promise<Build[]>;
36
41
  rebuild(): Promise<void>;
42
+ listFiles(): Promise<FileListing[]>;
37
43
  getFile(path: string): Promise<Response>;
38
44
  getSyncs(): Promise<Sync[]>;
39
45
  getScan(): Promise<SiteScan>;
@@ -48,4 +54,8 @@ export declare class SiteClient {
48
54
  connectInbox(body: ConnectInboxOptions): Promise<SiteInbox>;
49
55
  getDamConnections(): Promise<SiteDam[]>;
50
56
  connectDam(body: ConnectDamOptions): Promise<SiteDam>;
57
+ getEditingSessions(): Promise<EditingSession[]>;
58
+ createEditingSession(): Promise<EditingSession>;
59
+ getLatestEditingSession(): Promise<EditingSession>;
60
+ uploadFile(path: string, content: BlobPart, options?: UploadFileOptions): Promise<void>;
51
61
  }
package/dist/src/site.js CHANGED
@@ -86,6 +86,18 @@ export class SiteClient {
86
86
  throw new Error('Error creating build. Permission denied');
87
87
  }
88
88
  }
89
+ async listFiles() {
90
+ const resp = await this.#client.fetch(`/sites/${this.#uuid}/files`);
91
+ if (resp.status === 401) {
92
+ throw new Error('Error fetching files. Permission denied');
93
+ }
94
+ if (resp.status === 422) {
95
+ const errorResp = await resp.json();
96
+ throw new ApiError('Error fetching files. Invalid request', errorResp.errors, `/sites/${this.#uuid}/files`, {}, resp.status);
97
+ }
98
+ const files = await resp.json();
99
+ return files;
100
+ }
89
101
  async getFile(path) {
90
102
  const resp = await this.#client.fetch(`/sites/${this.#uuid}/files/${encodeURIComponent(path)}`);
91
103
  if (resp.status === 401 || resp.status === 403) {
@@ -251,4 +263,83 @@ export class SiteClient {
251
263
  const dam = await resp.json();
252
264
  return dam;
253
265
  }
266
+ async getEditingSessions() {
267
+ const resp = await this.#client.fetch(`/sites/${this.#uuid}/editing_sessions`);
268
+ if (resp.status === 403) {
269
+ throw new Error('Error fetching editing sessions. Permission denied');
270
+ }
271
+ const editingSessions = await resp.json();
272
+ return editingSessions;
273
+ }
274
+ async createEditingSession() {
275
+ const resp = await this.#client.fetch(`/sites/${this.#uuid}/editing_sessions`, {
276
+ method: 'POST',
277
+ });
278
+ if (resp.status === 401 || resp.status === 403) {
279
+ throw new Error('Error creating editing session. Permission denied');
280
+ }
281
+ if (resp.status === 422) {
282
+ const errorResp = await resp.json();
283
+ throw new ApiError('Error creating editing session. Invalid request', errorResp.errors, `/sites/${this.#uuid}/editing_sessions`, { method: 'POST' }, resp.status);
284
+ }
285
+ const editingSession = await resp.json();
286
+ return editingSession;
287
+ }
288
+ async getLatestEditingSession() {
289
+ const resp = await this.#client.fetch(`/sites/${this.#uuid}/editing_sessions/latest`);
290
+ if (resp.status === 403) {
291
+ throw new Error('Error fetching latest editing session. Permission denied');
292
+ }
293
+ const editingSession = await resp.json();
294
+ return editingSession;
295
+ }
296
+ async uploadFile(path, content, options = {}) {
297
+ if (!path.startsWith('/')) {
298
+ path = `/${path}`;
299
+ }
300
+ const files = await this.listFiles();
301
+ const existingFile = files.find((file) => file.sitePath === path);
302
+ if (existingFile && !options.overwriteExistingFile) {
303
+ throw new Error('File already exists and overwriteExistingFile is not set');
304
+ }
305
+ const uploadData = await this.#client.getUploadData();
306
+ const editingSession = await this.createEditingSession();
307
+ const file = await this.#client.editingSession(editingSession.uuid).createFile({
308
+ path,
309
+ source_path: existingFile ? path : undefined,
310
+ edit_type: 'update',
311
+ });
312
+ const contributions = await this.#client.editingSessionFile(file.uuid).getContributions();
313
+ contributions.sort((a, b) => a.updated_at.localeCompare(b.updated_at));
314
+ const latestContribution = contributions.at(-1);
315
+ if (latestContribution && !options.overwriteExistingFile) {
316
+ throw new Error('File already exists and overwriteExistingFile is not set');
317
+ }
318
+ const s3Key = `${uploadData.prefix}/${Date.now()}${path}`;
319
+ const formData = new FormData();
320
+ formData.append('key', s3Key);
321
+ Object.keys(uploadData.fields).forEach((field) => {
322
+ if (field !== 'key') {
323
+ formData.append(field, uploadData.fields[field]);
324
+ }
325
+ });
326
+ formData.append('file', new Blob([content], { type: options.type ?? 'text/plain' }));
327
+ const uploadResp = await fetch(uploadData.url, {
328
+ method: 'POST',
329
+ body: formData,
330
+ });
331
+ const etag = uploadResp.headers.get('ETag');
332
+ if (!etag) {
333
+ throw new Error('ETag not found in upload response');
334
+ }
335
+ const contentHash = etag.slice(1, -1);
336
+ await this.#client.editingSessionFile(file.uuid).createContribution({
337
+ s3_key: s3Key,
338
+ content_hash: contentHash,
339
+ previous_content_hash: latestContribution?.content_hash ?? existingFile?.md5,
340
+ });
341
+ await this.#client.editingSessionFile(file.uuid).unlock({
342
+ previous_content_hash: contentHash,
343
+ });
344
+ }
254
345
  }
@@ -0,0 +1,6 @@
1
+ import type CloudCannonClient from '../index.ts';
2
+ export declare class SyncClient {
3
+ #private;
4
+ constructor(uuid: string, client: CloudCannonClient);
5
+ get(): Promise<Response>;
6
+ }
@@ -0,0 +1,15 @@
1
+ export class SyncClient {
2
+ #uuid;
3
+ #client;
4
+ constructor(uuid, client) {
5
+ this.#uuid = uuid;
6
+ this.#client = client;
7
+ }
8
+ async get() {
9
+ const resp = await this.#client.fetch(`/syncs/${this.#uuid}`);
10
+ if (resp.status === 401 || resp.status === 403) {
11
+ throw new Error('Error fetching sync. Permission denied');
12
+ }
13
+ return resp;
14
+ }
15
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@cloudcannon/sdk",
3
3
  "type": "module",
4
- "version": "0.0.0",
4
+ "version": "0.0.2",
5
5
  "description": "REST API client for the CloudCannon CMS.",
6
6
  "keywords": [
7
7
  "cloudcannon",
@@ -43,7 +43,8 @@
43
43
  "test:watch": "node --test --watch",
44
44
  "test:coverage": "node --test --test-coverage --test-reporter=lcov --test-reporter-destination=lcov.info",
45
45
  "lint": "biome check && tsc --noEmit",
46
- "lint:fix": "biome check --fix"
46
+ "lint:fix": "biome check --fix",
47
+ "generate-schema": "npx openapi-typescript $1 -o ./schema.ts"
47
48
  },
48
49
  "author": "CloudCannon <support@cloudcannon.com>",
49
50
  "license": "ISC",
@@ -52,6 +53,5 @@
52
53
  "@types/node": "25.6.0",
53
54
  "typescript": "6.0.3"
54
55
  },
55
- "dependencies": {
56
- }
56
+ "dependencies": {}
57
57
  }