t44 0.2.0-rc.1

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.

Potentially problematic release.


This version of t44 might be problematic. Click here for more details.

Files changed (86) hide show
  1. package/LICENSE.md +203 -0
  2. package/README.md +154 -0
  3. package/bin/activate +36 -0
  4. package/bin/activate.ts +30 -0
  5. package/bin/postinstall.sh +19 -0
  6. package/bin/shell +27 -0
  7. package/bin/t44 +27 -0
  8. package/caps/HomeRegistry.v0.ts +298 -0
  9. package/caps/OpenApiSchema.v0.ts +192 -0
  10. package/caps/ProjectDeployment.v0.ts +363 -0
  11. package/caps/ProjectDevelopment.v0.ts +246 -0
  12. package/caps/ProjectPublishing.v0.ts +307 -0
  13. package/caps/ProjectRack.v0.ts +128 -0
  14. package/caps/WorkspaceCli.v0.ts +391 -0
  15. package/caps/WorkspaceConfig.v0.ts +626 -0
  16. package/caps/WorkspaceConfig.yaml +53 -0
  17. package/caps/WorkspaceConnection.v0.ts +240 -0
  18. package/caps/WorkspaceEntityConfig.v0.ts +64 -0
  19. package/caps/WorkspaceEntityFact.v0.ts +193 -0
  20. package/caps/WorkspaceInfo.v0.ts +554 -0
  21. package/caps/WorkspaceInit.v0.ts +30 -0
  22. package/caps/WorkspaceKey.v0.ts +186 -0
  23. package/caps/WorkspaceProjects.v0.ts +455 -0
  24. package/caps/WorkspacePrompt.v0.ts +396 -0
  25. package/caps/WorkspaceShell.sh +39 -0
  26. package/caps/WorkspaceShell.v0.ts +104 -0
  27. package/caps/WorkspaceShell.yaml +65 -0
  28. package/caps/WorkspaceShellCli.v0.ts +109 -0
  29. package/caps/WorkspaceTest.v0.ts +167 -0
  30. package/caps/providers/LICENSE.md +8 -0
  31. package/caps/providers/README.md +2 -0
  32. package/caps/providers/bunny.net/ProjectDeployment.v0.ts +328 -0
  33. package/caps/providers/bunny.net/api-pull.v0.test.ts +319 -0
  34. package/caps/providers/bunny.net/api-pull.v0.ts +161 -0
  35. package/caps/providers/bunny.net/api-storage.v0.test.ts +168 -0
  36. package/caps/providers/bunny.net/api-storage.v0.ts +245 -0
  37. package/caps/providers/bunny.net/api.v0.ts +95 -0
  38. package/caps/providers/dynadot.com/ProjectDeployment.v0.ts +207 -0
  39. package/caps/providers/dynadot.com/api-domains.v0.test.ts +147 -0
  40. package/caps/providers/dynadot.com/api-domains.v0.ts +137 -0
  41. package/caps/providers/dynadot.com/api.v0.ts +88 -0
  42. package/caps/providers/git-scm.com/ProjectPublishing.v0.ts +231 -0
  43. package/caps/providers/github.com/ProjectPublishing.v0.ts +75 -0
  44. package/caps/providers/github.com/api.v0.ts +90 -0
  45. package/caps/providers/npmjs.com/ProjectPublishing.v0.ts +741 -0
  46. package/caps/providers/vercel.com/ProjectDeployment.v0.ts +339 -0
  47. package/caps/providers/vercel.com/api.v0.test.ts +67 -0
  48. package/caps/providers/vercel.com/api.v0.ts +132 -0
  49. package/caps/providers/vercel.com/bun.lock +194 -0
  50. package/caps/providers/vercel.com/package.json +10 -0
  51. package/caps/providers/vercel.com/project.v0.test.ts +108 -0
  52. package/caps/providers/vercel.com/project.v0.ts +150 -0
  53. package/caps/providers/vercel.com/tsconfig.json +28 -0
  54. package/docs/Overview.drawio +189 -0
  55. package/docs/Overview.svg +4 -0
  56. package/lib/crypto.ts +53 -0
  57. package/lib/openapi.ts +132 -0
  58. package/lib/ucan.ts +137 -0
  59. package/package.json +41 -0
  60. package/structs/HomeRegistryConfig.v0.ts +27 -0
  61. package/structs/ProjectDeploymentConfig.v0.ts +27 -0
  62. package/structs/ProjectDeploymentFact.v0.ts +110 -0
  63. package/structs/ProjectPublishingFact.v0.ts +69 -0
  64. package/structs/ProjectRackConfig.v0.ts +27 -0
  65. package/structs/WorkspaceCliConfig.v0.ts +27 -0
  66. package/structs/WorkspaceConfig.v0.ts +27 -0
  67. package/structs/WorkspaceKeyConfig.v0.ts +27 -0
  68. package/structs/WorkspaceMappings.v0.ts +27 -0
  69. package/structs/WorkspaceProjectsConfig.v0.ts +27 -0
  70. package/structs/WorkspaceRepositories.v0.ts +27 -0
  71. package/structs/WorkspaceShellConfig.v0.ts +45 -0
  72. package/structs/providers/LICENSE.md +8 -0
  73. package/structs/providers/README.md +2 -0
  74. package/structs/providers/bunny.net/ProjectDeploymentFact.v0.ts +41 -0
  75. package/structs/providers/bunny.net/WorkspaceConnectionConfig.v0.ts +42 -0
  76. package/structs/providers/dynadot.com/DomainFact.v0.ts +146 -0
  77. package/structs/providers/dynadot.com/WorkspaceConnectionConfig.v0.ts +41 -0
  78. package/structs/providers/git-scm.com/ProjectPublishingFact.v0.ts +46 -0
  79. package/structs/providers/github.com/ProjectPublishingFact.v0.ts +52 -0
  80. package/structs/providers/github.com/WorkspaceConnectionConfig.v0.ts +42 -0
  81. package/structs/providers/npmjs.com/ProjectPublishingFact.v0.ts +48 -0
  82. package/structs/providers/vercel.com/ProjectDeploymentFact.v0.ts +38 -0
  83. package/structs/providers/vercel.com/WorkspaceConnectionConfig.v0.ts +48 -0
  84. package/tsconfig.json +28 -0
  85. package/workspace-rt.ts +134 -0
  86. package/workspace.yaml +5 -0
@@ -0,0 +1,319 @@
1
+ #!/usr/bin/env bun test
2
+
3
+ export const testConfig = {
4
+ group: 'vendor',
5
+ runOnAll: false,
6
+ }
7
+
8
+ import * as bunTest from 'bun:test'
9
+ import { run } from '../../../workspace-rt'
10
+
11
+ const {
12
+ test: { describe, it, expect },
13
+ pull,
14
+ storage
15
+ } = await run(async ({ encapsulate, CapsulePropertyTypes, makeImportStack }: any) => {
16
+ const spine = await encapsulate({
17
+ '#@stream44.studio/encapsulate/spine-contracts/CapsuleSpineContract.v0': {
18
+ '#@stream44.studio/encapsulate/structs/Capsule.v0': {},
19
+ '#': {
20
+ test: {
21
+ type: CapsulePropertyTypes.Mapping,
22
+ value: 't44/caps/WorkspaceTest.v0',
23
+ options: {
24
+ '#': {
25
+ bunTest,
26
+ env: {
27
+ BUNNY_API_KEY: { factReference: 't44/structs/providers/bunny.net/WorkspaceConnectionConfig.v0:apiKey' }
28
+ }
29
+ }
30
+ }
31
+ },
32
+ pull: {
33
+ type: CapsulePropertyTypes.Mapping,
34
+ value: './api-pull.v0'
35
+ },
36
+ storage: {
37
+ type: CapsulePropertyTypes.Mapping,
38
+ value: './api-storage.v0'
39
+ },
40
+ }
41
+ }
42
+ }, {
43
+ importMeta: import.meta,
44
+ importStack: makeImportStack(),
45
+ capsuleName: 't44/caps/providers/bunny.net/api-pull.v0.test'
46
+ })
47
+ return { spine }
48
+ }, async ({ spine, apis }: any) => {
49
+ return apis[spine.capsuleSourceLineRef]
50
+ }, {
51
+ importMeta: import.meta
52
+ })
53
+
54
+ describe('Bunny Pull Zone API', function () {
55
+
56
+ it('listZones()', async function () {
57
+
58
+ const result = await pull.listZones()
59
+
60
+ expect(result).toBeArray()
61
+ })
62
+
63
+ describe('Pull Zone Lifecycle', function () {
64
+
65
+ let zoneId: number
66
+ let zoneName: string
67
+ const originUrl = 'https://example.com'
68
+
69
+ it('ensureZone()', async function () {
70
+ const timestamp = Date.now()
71
+ zoneName = `test-t44-pull-${timestamp}`
72
+
73
+ const zone = await pull.ensureZone({
74
+ name: zoneName,
75
+ originUrl: originUrl
76
+ })
77
+
78
+ expect(zone).toBeObject()
79
+ expect(zone.Id).toBeNumber()
80
+ expect(zone.Name).toBe(zoneName)
81
+ expect(zone.OriginUrl).toBe(originUrl)
82
+
83
+ zoneId = zone.Id
84
+ })
85
+
86
+ it('getZone()', async function () {
87
+ const retrievedZone = await pull.getZone(zoneId)
88
+
89
+ expect(retrievedZone).toBeObject()
90
+ expect(retrievedZone.Id).toBe(zoneId)
91
+ expect(retrievedZone.Name).toBe(zoneName)
92
+ expect(retrievedZone.OriginUrl).toBe(originUrl)
93
+ })
94
+
95
+ it('deleteZone()', async function () {
96
+ const deleteResult = await pull.deleteZone(zoneId)
97
+
98
+ expect(deleteResult).toBeDefined()
99
+ })
100
+
101
+ it('cleanup - delete all test zones', async function () {
102
+ const result = await pull.listZones()
103
+ const allZones = result.Items || result
104
+ expect(allZones).toBeArray()
105
+
106
+ const testZones = allZones.filter((zone: any) =>
107
+ zone.Name && zone.Name.match(/^test-t44-pull-\d+$/)
108
+ )
109
+
110
+ for (const zone of testZones) {
111
+ await pull.deleteZone(zone.Id)
112
+ }
113
+ })
114
+
115
+ })
116
+
117
+ describe('CDN Workflow with Storage and Pull Zone', function () {
118
+
119
+ let storageZoneId: number
120
+ let storageZoneName: string
121
+ let storagePassword: string
122
+ let storageHostname: string
123
+ let pullZoneId: number
124
+ let pullZoneName: string
125
+ let pullZoneHostname: string
126
+ let fileName: string
127
+
128
+ it('ensureStorageZone()', async function () {
129
+ const timestamp = Date.now()
130
+ storageZoneName = `test-t44-cdn-${timestamp}`
131
+
132
+ const zone = await storage.ensureZone({
133
+ name: storageZoneName,
134
+ region: 'LA'
135
+ })
136
+
137
+ expect(zone).toBeObject()
138
+ expect(zone.Id).toBeNumber()
139
+ expect(zone.Name).toBe(storageZoneName)
140
+ expect(zone.Password).toBeDefined()
141
+
142
+ storageZoneId = zone.Id
143
+ storagePassword = zone.Password
144
+ })
145
+
146
+ it('getStorageZone()', async function () {
147
+ const retrievedZone = await storage.getZone(storageZoneId)
148
+
149
+ expect(retrievedZone).toBeObject()
150
+ expect(retrievedZone.StorageHostname).toBeDefined()
151
+
152
+ storageHostname = retrievedZone.StorageHostname
153
+ })
154
+
155
+ it('ensurePullZone() tied to storage zone', async function () {
156
+ const timestamp = Date.now()
157
+ pullZoneName = `test-t44-cdn-pull-${timestamp}`
158
+
159
+ const zone = await pull.ensureZone({
160
+ name: pullZoneName,
161
+ originUrl: `https://${storageHostname}/${storageZoneName}`,
162
+ storageZoneId: storageZoneId
163
+ })
164
+
165
+ expect(zone).toBeObject()
166
+ expect(zone.Id).toBeNumber()
167
+ expect(zone.Name).toBe(pullZoneName)
168
+ expect(zone.Hostnames).toBeDefined()
169
+ expect(zone.Hostnames).toBeArray()
170
+ expect(zone.Hostnames.length).toBeGreaterThan(0)
171
+
172
+ pullZoneId = zone.Id
173
+ pullZoneHostname = zone.Hostnames[0].Value
174
+
175
+ await new Promise(resolve => setTimeout(resolve, 2500))
176
+ })
177
+
178
+ it('uploadFile() to storage zone', async function () {
179
+ const timestamp = Date.now()
180
+ fileName = `test-cdn-${timestamp}.txt`
181
+ const fileContent = 'Version 1: Initial content'
182
+
183
+ await storage.uploadFile({
184
+ storageZoneName: storageZoneName,
185
+ storageHostname: storageHostname,
186
+ fileName: fileName,
187
+ data: fileContent,
188
+ password: storagePassword
189
+ })
190
+ })
191
+
192
+ it('fetch file from pull zone public hostname', async function () {
193
+ const fileUrl = `https://${pullZoneHostname}/${fileName}`
194
+
195
+ const response = await fetch(fileUrl)
196
+ expect(response.ok).toBe(true)
197
+
198
+ const content = await response.text()
199
+ expect(content).toBe('Version 1: Initial content')
200
+ })
201
+
202
+ it('uploadFile() with updated content', async function () {
203
+ const fileContent = 'Version 2: Updated content'
204
+
205
+ await storage.uploadFile({
206
+ storageZoneName: storageZoneName,
207
+ storageHostname: storageHostname,
208
+ fileName: fileName,
209
+ data: fileContent,
210
+ password: storagePassword
211
+ })
212
+ })
213
+
214
+ it('uploadDirectory() to storage zone', async function () {
215
+ const { mkdtemp, writeFile, rm } = await import('fs/promises')
216
+ const { join } = await import('path')
217
+ const { tmpdir } = await import('os')
218
+
219
+ const tempDir = await mkdtemp(join(tmpdir(), 'bunny-upload-test-'))
220
+
221
+ await writeFile(join(tempDir, 'file1.txt'), 'Directory upload test file 1')
222
+ await writeFile(join(tempDir, 'file2.txt'), 'Directory upload test file 2')
223
+ await writeFile(join(tempDir, 'file3.html'), '<html><body>Test HTML</body></html>')
224
+
225
+ await storage.uploadDirectory({
226
+ sourceDirectory: tempDir,
227
+ destinationDirectory: 'test-dir',
228
+ storageZoneName: storageZoneName,
229
+ password: storagePassword,
230
+ region: 'la'
231
+ })
232
+
233
+ await rm(tempDir, { recursive: true, force: true })
234
+ })
235
+
236
+ it('verify directory files are accessible from pull zone', async function () {
237
+ const file1Url = `https://${pullZoneHostname}/test-dir/file1.txt`
238
+ const file2Url = `https://${pullZoneHostname}/test-dir/file2.txt`
239
+ const file3Url = `https://${pullZoneHostname}/test-dir/file3.html`
240
+
241
+ const response1 = await fetch(file1Url)
242
+ expect(response1.ok).toBe(true)
243
+ const content1 = await response1.text()
244
+ expect(content1).toBe('Directory upload test file 1')
245
+
246
+ const response2 = await fetch(file2Url)
247
+ expect(response2.ok).toBe(true)
248
+ const content2 = await response2.text()
249
+ expect(content2).toBe('Directory upload test file 2')
250
+
251
+ const response3 = await fetch(file3Url)
252
+ expect(response3.ok).toBe(true)
253
+ const content3 = await response3.text()
254
+ expect(content3).toBe('<html><body>Test HTML</body></html>')
255
+ })
256
+
257
+ it('purgeZone() to clear cache', async function () {
258
+ await pull.purgeZone(pullZoneId)
259
+
260
+ await new Promise(resolve => setTimeout(resolve, 2000))
261
+ })
262
+
263
+ it('fetch file again and verify it is updated', async function () {
264
+ const fileUrl = `https://${pullZoneHostname}/${fileName}`
265
+
266
+ const response = await fetch(fileUrl)
267
+ expect(response.ok).toBe(true)
268
+
269
+ const content = await response.text()
270
+ expect(content).toBe('Version 2: Updated content')
271
+ })
272
+
273
+ it('deleteFile() from storage zone', async function () {
274
+ await storage.deleteFile({
275
+ storageZoneName: storageZoneName,
276
+ storageHostname: storageHostname,
277
+ fileName: fileName,
278
+ password: storagePassword
279
+ })
280
+ })
281
+
282
+ it('deletePullZone()', async function () {
283
+ const deleteResult = await pull.deleteZone(pullZoneId)
284
+ expect(deleteResult).toBeDefined()
285
+ })
286
+
287
+ it('deleteStorageZone()', async function () {
288
+ const deleteResult = await storage.deleteZone(storageZoneId)
289
+ expect(deleteResult).toBeDefined()
290
+ })
291
+
292
+ it('cleanup - delete all test zones', async function () {
293
+ const pullResult = await pull.listZones()
294
+ const allPullZones = pullResult.Items || pullResult
295
+ expect(allPullZones).toBeArray()
296
+
297
+ const testPullZones = allPullZones.filter((zone: any) =>
298
+ zone.Name && zone.Name.match(/^test-t44-cdn-pull-\d+$/)
299
+ )
300
+
301
+ for (const zone of testPullZones) {
302
+ await pull.deleteZone(zone.Id)
303
+ }
304
+
305
+ const allStorageZones = await storage.listZones()
306
+ expect(allStorageZones).toBeArray()
307
+
308
+ const testStorageZones = allStorageZones.filter((zone: any) =>
309
+ zone.Name && zone.Name.match(/^test-t44-cdn-\d+$/)
310
+ )
311
+
312
+ for (const zone of testStorageZones) {
313
+ await storage.deleteZone(zone.Id)
314
+ }
315
+ })
316
+
317
+ })
318
+
319
+ })
@@ -0,0 +1,161 @@
1
+
2
+
3
+ export async function capsule({
4
+ encapsulate,
5
+ CapsulePropertyTypes,
6
+ makeImportStack
7
+ }: {
8
+ encapsulate: any
9
+ CapsulePropertyTypes: any
10
+ makeImportStack: any
11
+ }) {
12
+
13
+ // https://docs.bunny.net/api-reference/core/pull-zone/list-pull-zones
14
+ return encapsulate({
15
+ '#@stream44.studio/encapsulate/spine-contracts/CapsuleSpineContract.v0': {
16
+ '#@stream44.studio/encapsulate/structs/Capsule.v0': {},
17
+ '#t44/structs/providers/bunny.net/ProjectDeploymentFact.v0': {
18
+ as: '$ProjectDeploymentFact'
19
+ },
20
+ '#': {
21
+
22
+ api: {
23
+ type: CapsulePropertyTypes.Mapping,
24
+ value: './api.v0'
25
+ },
26
+
27
+ listZones: {
28
+ type: CapsulePropertyTypes.Function,
29
+ value: async function (this: any, options?: { page?: number; perPage?: number; search?: string; includeCertificate?: boolean }) {
30
+ const params: Record<string, string | number | boolean> = {};
31
+ if (options?.page !== undefined) {
32
+ params.page = options.page;
33
+ }
34
+ if (options?.perPage !== undefined) {
35
+ params.perPage = options.perPage;
36
+ }
37
+ if (options?.search) {
38
+ params.search = options.search;
39
+ }
40
+ if (options?.includeCertificate !== undefined) {
41
+ params.includeCertificate = options.includeCertificate;
42
+ }
43
+
44
+ const result = await this.api.call({
45
+ method: 'GET',
46
+ url: 'https://api.bunny.net/pullzone',
47
+ params,
48
+ operation: 'listZones'
49
+ });
50
+
51
+ await this.$ProjectDeploymentFact.set('pull-zones', 'list', 'PaginationListModelOfPullZoneModel', result);
52
+
53
+ return result;
54
+ }
55
+ },
56
+
57
+ createZone: {
58
+ type: CapsulePropertyTypes.Function,
59
+ value: async function (this: any, options: {
60
+ name: string;
61
+ originUrl: string;
62
+ storageZoneId?: number;
63
+ type?: 'Premium' | 'Standard';
64
+ }) {
65
+ const requestBody: any = {
66
+ Name: options.name,
67
+ OriginUrl: options.originUrl,
68
+ };
69
+
70
+ if (options.storageZoneId !== undefined) {
71
+ requestBody.StorageZoneId = options.storageZoneId;
72
+ }
73
+ if (options.type) {
74
+ requestBody.Type = options.type;
75
+ }
76
+
77
+ return await this.api.call({
78
+ method: 'POST',
79
+ url: 'https://api.bunny.net/pullzone',
80
+ data: requestBody,
81
+ operation: 'createZone'
82
+ });
83
+ }
84
+ },
85
+
86
+ ensureZone: {
87
+ type: CapsulePropertyTypes.Function,
88
+ value: async function (this: any, options: {
89
+ name: string;
90
+ originUrl: string;
91
+ storageZoneId?: number;
92
+ type?: 'Premium' | 'Standard';
93
+ }) {
94
+ const result = await this.listZones({ search: options.name });
95
+ const zones = result.Items || result;
96
+ const existingZone = zones.find((zone: any) => zone.Name === options.name);
97
+
98
+ if (existingZone) {
99
+ return await this.getZone(existingZone.Id);
100
+ }
101
+
102
+ return await this.createZone(options);
103
+ }
104
+ },
105
+
106
+ getZone: {
107
+ type: CapsulePropertyTypes.Function,
108
+ value: async function (this: any, id: number) {
109
+ const pullZone = await this.api.call({
110
+ method: 'GET',
111
+ url: `https://api.bunny.net/pullzone/${id}`,
112
+ operation: 'getZone'
113
+ });
114
+
115
+ if (pullZone && pullZone.Name) {
116
+ await this.$ProjectDeploymentFact.set('pull-zones', pullZone.Name, 'PullZoneModel', pullZone);
117
+ }
118
+
119
+ return pullZone;
120
+ }
121
+ },
122
+
123
+ deleteZone: {
124
+ type: CapsulePropertyTypes.Function,
125
+ value: async function (this: any, id: number) {
126
+ return await this.api.call({
127
+ method: 'DELETE',
128
+ url: `https://api.bunny.net/pullzone/${id}`,
129
+ operation: 'deleteZone'
130
+ });
131
+ }
132
+ },
133
+
134
+ purgeZone: {
135
+ type: CapsulePropertyTypes.Function,
136
+ value: async function (this: any, id: number, options?: { cacheTag?: string }) {
137
+ const requestBody: any = {};
138
+
139
+ if (options?.cacheTag) {
140
+ requestBody.CacheTag = options.cacheTag;
141
+ }
142
+
143
+ return await this.api.call({
144
+ method: 'POST',
145
+ url: `https://api.bunny.net/pullzone/${id}/purgeCache`,
146
+ data: requestBody,
147
+ operation: 'purgeZone'
148
+ });
149
+ }
150
+ }
151
+
152
+ }
153
+ }
154
+ }, {
155
+ importMeta: import.meta,
156
+ importStack: makeImportStack(),
157
+ capsuleName: capsule['#'],
158
+ })
159
+ }
160
+ capsule['#'] = 't44/caps/providers/bunny.net/api-pull.v0'
161
+
@@ -0,0 +1,168 @@
1
+ #!/usr/bin/env bun test
2
+
3
+ export const testConfig = {
4
+ group: 'vendor',
5
+ runOnAll: false,
6
+ }
7
+
8
+ import * as bunTest from 'bun:test'
9
+ import { run } from '../../../workspace-rt'
10
+
11
+ const {
12
+ test: { describe, it, expect },
13
+ storage
14
+ } = await run(async ({ encapsulate, CapsulePropertyTypes, makeImportStack }: any) => {
15
+ const spine = await encapsulate({
16
+ '#@stream44.studio/encapsulate/spine-contracts/CapsuleSpineContract.v0': {
17
+ '#@stream44.studio/encapsulate/structs/Capsule.v0': {},
18
+ '#': {
19
+ test: {
20
+ type: CapsulePropertyTypes.Mapping,
21
+ value: 't44/caps/WorkspaceTest.v0',
22
+ options: {
23
+ '#': {
24
+ bunTest,
25
+ env: {
26
+ BUNNY_API_KEY: { factReference: 't44/structs/providers/bunny.net/WorkspaceConnectionConfig.v0:apiKey' }
27
+ }
28
+ }
29
+ }
30
+ },
31
+ storage: {
32
+ type: CapsulePropertyTypes.Mapping,
33
+ value: './api-storage.v0'
34
+ },
35
+ }
36
+ }
37
+ }, {
38
+ importMeta: import.meta,
39
+ importStack: makeImportStack(),
40
+ capsuleName: 't44/caps/providers/bunny.net/api-storage.v0.test'
41
+ })
42
+ return { spine }
43
+ }, async ({ spine, apis }: any) => {
44
+ return apis[spine.capsuleSourceLineRef]
45
+ }, {
46
+ importMeta: import.meta
47
+ })
48
+
49
+ describe('Bunny Storage API', function () {
50
+
51
+ it('listZones()', async function () {
52
+
53
+ const result = await storage.listZones()
54
+
55
+ expect(result).toBeArray()
56
+ }, 15_000)
57
+
58
+ describe('Storage Zone Lifecycle', function () {
59
+
60
+ let zoneId: number
61
+ let zoneName: string
62
+ let password: string
63
+ let storageHostname: string
64
+ let fileName: string
65
+
66
+ it('ensureZone()', async function () {
67
+ const timestamp = Date.now()
68
+ zoneName = `test-t44-${timestamp}`
69
+
70
+ const zone = await storage.ensureZone({
71
+ name: zoneName,
72
+ region: 'LA'
73
+ })
74
+
75
+ expect(zone).toBeObject()
76
+ expect(zone.Id).toBeNumber()
77
+ expect(zone.Name).toBe(zoneName)
78
+ expect(zone.Password).toBeDefined()
79
+
80
+ zoneId = zone.Id
81
+ })
82
+
83
+ it('getZone()', async function () {
84
+ const retrievedZone = await storage.getZone(zoneId)
85
+
86
+ expect(retrievedZone).toBeObject()
87
+ expect(retrievedZone.Id).toBe(zoneId)
88
+ expect(retrievedZone.Name).toBe(zoneName)
89
+ expect(retrievedZone.Password).toBeDefined()
90
+
91
+ password = retrievedZone.Password
92
+ storageHostname = retrievedZone.StorageHostname
93
+
94
+ await new Promise(resolve => setTimeout(resolve, 2000))
95
+ })
96
+
97
+ it('uploadFile()', async function () {
98
+ const timestamp = Date.now()
99
+ fileName = `test-file-${timestamp}.txt`
100
+ const fileContent = 'Hello from Bunny.net Storage API test!'
101
+
102
+ await storage.uploadFile({
103
+ storageZoneName: zoneName,
104
+ storageHostname: storageHostname,
105
+ fileName: fileName,
106
+ data: fileContent,
107
+ password: password
108
+ })
109
+ })
110
+
111
+ it('listFiles() - verify upload', async function () {
112
+ const filesAfterUpload = await storage.listFiles({
113
+ storageZoneName: zoneName,
114
+ storageHostname: storageHostname,
115
+ password: password
116
+ })
117
+
118
+ expect(filesAfterUpload).toBeArray()
119
+ expect(filesAfterUpload.length).toBeGreaterThan(0)
120
+ const uploadedFile = filesAfterUpload.find((f: any) => f.ObjectName === fileName)
121
+ expect(uploadedFile).toBeDefined()
122
+ expect(uploadedFile.ObjectName).toBe(fileName)
123
+ expect(uploadedFile.IsDirectory).toBe(false)
124
+ })
125
+
126
+ it('deleteFile()', async function () {
127
+ await storage.deleteFile({
128
+ storageZoneName: zoneName,
129
+ storageHostname: storageHostname,
130
+ fileName: fileName,
131
+ password: password
132
+ })
133
+ })
134
+
135
+ it('listFiles() - verify deletion', async function () {
136
+ const filesAfterDelete = await storage.listFiles({
137
+ storageZoneName: zoneName,
138
+ storageHostname: storageHostname,
139
+ password: password
140
+ })
141
+
142
+ expect(filesAfterDelete).toBeArray()
143
+ const deletedFile = filesAfterDelete.find((f: any) => f.ObjectName === fileName)
144
+ expect(deletedFile).toBeUndefined()
145
+ })
146
+
147
+ it('deleteZone()', async function () {
148
+ const deleteResult = await storage.deleteZone(zoneId)
149
+
150
+ expect(deleteResult).toBeDefined()
151
+ })
152
+
153
+ it('cleanup - delete all test zones', async function () {
154
+ const allZones = await storage.listZones()
155
+ expect(allZones).toBeArray()
156
+
157
+ const testZones = allZones.filter((zone: any) =>
158
+ zone.Name && zone.Name.match(/^test-t44-\d+$/)
159
+ )
160
+
161
+ for (const zone of testZones) {
162
+ await storage.deleteZone(zone.Id)
163
+ }
164
+ })
165
+
166
+ })
167
+
168
+ })