t44 0.4.0-rc.3
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/.dco-signatures +9 -0
- package/.github/workflows/dco.yml +12 -0
- package/.o/GordianOpenIntegrity-CurrentLifehash.svg +1026 -0
- package/.o/GordianOpenIntegrity-InceptionLifehash.svg +1026 -0
- package/.o/GordianOpenIntegrity.yaml +25 -0
- package/.o/assets/Hero-Terminal44-v0.jpeg +0 -0
- package/DCO.md +34 -0
- package/LICENSE.md +203 -0
- package/README.md +183 -0
- package/bin/activate +36 -0
- package/bin/activate.ts +30 -0
- package/bin/postinstall.sh +19 -0
- package/bin/shell +27 -0
- package/bin/t44 +27 -0
- package/caps/ConfigSchemaStruct.ts +55 -0
- package/caps/Home.ts +51 -0
- package/caps/HomeRegistry.ts +313 -0
- package/caps/HomeRegistryFile.ts +144 -0
- package/caps/JsonSchemas.ts +220 -0
- package/caps/OpenApiSchema.ts +67 -0
- package/caps/PackageDescriptor.ts +88 -0
- package/caps/ProjectCatalogs.ts +153 -0
- package/caps/ProjectDeployment.ts +363 -0
- package/caps/ProjectDevelopment.ts +257 -0
- package/caps/ProjectPublishing.ts +522 -0
- package/caps/ProjectRack.ts +155 -0
- package/caps/ProjectRepository.ts +322 -0
- package/caps/RootKey.ts +219 -0
- package/caps/SigningKey.ts +243 -0
- package/caps/WorkspaceCli.ts +442 -0
- package/caps/WorkspaceConfig.ts +268 -0
- package/caps/WorkspaceConfig.yaml +71 -0
- package/caps/WorkspaceConfigFile.ts +799 -0
- package/caps/WorkspaceConnection.ts +249 -0
- package/caps/WorkspaceEntityConfig.ts +78 -0
- package/caps/WorkspaceEntityConfig.v0.ts +77 -0
- package/caps/WorkspaceEntityFact.ts +218 -0
- package/caps/WorkspaceInfo.ts +595 -0
- package/caps/WorkspaceInit.ts +30 -0
- package/caps/WorkspaceKey.ts +338 -0
- package/caps/WorkspaceModel.ts +373 -0
- package/caps/WorkspaceProjects.ts +636 -0
- package/caps/WorkspacePrompt.ts +406 -0
- package/caps/WorkspaceShell.sh +39 -0
- package/caps/WorkspaceShell.ts +104 -0
- package/caps/WorkspaceShell.yaml +64 -0
- package/caps/WorkspaceShellCli.ts +109 -0
- package/caps/WorkspaceTest.ts +167 -0
- package/caps/providers/README.md +2 -0
- package/caps/providers/bunny.net/ProjectDeployment.ts +327 -0
- package/caps/providers/bunny.net/api-pull.test.ts +319 -0
- package/caps/providers/bunny.net/api-pull.ts +164 -0
- package/caps/providers/bunny.net/api-storage.test.ts +168 -0
- package/caps/providers/bunny.net/api-storage.ts +248 -0
- package/caps/providers/bunny.net/api.ts +95 -0
- package/caps/providers/dynadot.com/ProjectDeployment.ts +202 -0
- package/caps/providers/dynadot.com/api-domains.test.ts +224 -0
- package/caps/providers/dynadot.com/api-domains.ts +169 -0
- package/caps/providers/dynadot.com/api-restful-v1.test.ts +190 -0
- package/caps/providers/dynadot.com/api-restful-v1.ts +94 -0
- package/caps/providers/dynadot.com/api-restful-v2.test.ts +200 -0
- package/caps/providers/dynadot.com/api-restful-v2.ts +94 -0
- package/caps/providers/git-scm.com/ProjectPublishing.ts +654 -0
- package/caps/providers/github.com/ProjectPublishing.ts +118 -0
- package/caps/providers/github.com/api.ts +115 -0
- package/caps/providers/npmjs.com/ProjectPublishing.ts +536 -0
- package/caps/providers/semver.org/ProjectPublishing.ts +286 -0
- package/caps/providers/vercel.com/ProjectDeployment.ts +326 -0
- package/caps/providers/vercel.com/api.test.ts +67 -0
- package/caps/providers/vercel.com/api.ts +132 -0
- package/caps/providers/vercel.com/bun.lock +194 -0
- package/caps/providers/vercel.com/package.json +10 -0
- package/caps/providers/vercel.com/project.test.ts +108 -0
- package/caps/providers/vercel.com/project.ts +150 -0
- package/caps/providers/vercel.com/tsconfig.json +28 -0
- package/docs/Overview.drawio +248 -0
- package/docs/Overview.svg +4 -0
- package/lib/crypto.ts +53 -0
- package/lib/key.ts +365 -0
- package/lib/schema-console-renderer.ts +181 -0
- package/lib/schema-resolver.ts +349 -0
- package/lib/ucan.ts +137 -0
- package/package.json +101 -0
- package/structs/HomeRegistry.ts +55 -0
- package/structs/HomeRegistryConfig.ts +56 -0
- package/structs/ProjectCatalogsConfig.ts +53 -0
- package/structs/ProjectDeploymentConfig.ts +56 -0
- package/structs/ProjectDeploymentFact.ts +106 -0
- package/structs/ProjectPublishingFact.ts +68 -0
- package/structs/ProjectRack.ts +51 -0
- package/structs/ProjectRackConfig.ts +56 -0
- package/structs/RepositoryOriginDescriptor.ts +51 -0
- package/structs/RootKeyConfig.ts +64 -0
- package/structs/SigningKeyConfig.ts +64 -0
- package/structs/Workspace.ts +56 -0
- package/structs/WorkspaceCatalogs.ts +56 -0
- package/structs/WorkspaceCliConfig.ts +53 -0
- package/structs/WorkspaceConfig.ts +64 -0
- package/structs/WorkspaceConfigFile.ts +50 -0
- package/structs/WorkspaceConfigFileMeta.ts +70 -0
- package/structs/WorkspaceKey.ts +55 -0
- package/structs/WorkspaceKeyConfig.ts +56 -0
- package/structs/WorkspaceMappingsConfig.ts +56 -0
- package/structs/WorkspaceProject.ts +104 -0
- package/structs/WorkspaceProjectsConfig.ts +67 -0
- package/structs/WorkspacePublishingConfig.ts +65 -0
- package/structs/WorkspaceShellConfig.ts +83 -0
- package/structs/providers/README.md +2 -0
- package/structs/providers/bunny.net/PullZoneFact.ts +55 -0
- package/structs/providers/bunny.net/PullZoneListFact.ts +55 -0
- package/structs/providers/bunny.net/StorageZoneFact.ts +55 -0
- package/structs/providers/bunny.net/StorageZoneListFact.ts +55 -0
- package/structs/providers/bunny.net/WorkspaceConnectionConfig.ts +43 -0
- package/structs/providers/dynadot.com/DomainFact.ts +46 -0
- package/structs/providers/dynadot.com/WorkspaceConnectionConfig.ts +54 -0
- package/structs/providers/git-scm.com/ProjectPublishingFact.ts +46 -0
- package/structs/providers/github.com/ProjectPublishingFact.ts +46 -0
- package/structs/providers/github.com/WorkspaceConnectionConfig.ts +43 -0
- package/structs/providers/npmjs.com/ProjectPublishingFact.ts +46 -0
- package/structs/providers/vercel.com/ProjectDeploymentFact.ts +55 -0
- package/structs/providers/vercel.com/WorkspaceConnectionConfig.ts +49 -0
- package/tests/01-Lifecycle/main.test.ts +173 -0
- package/tsconfig.json +28 -0
- package/workspace-rt.ts +134 -0
- package/workspace.yaml +3 -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 '@t44/t44/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': {},
|
|
19
|
+
'#': {
|
|
20
|
+
test: {
|
|
21
|
+
type: CapsulePropertyTypes.Mapping,
|
|
22
|
+
value: 't44/caps/WorkspaceTest',
|
|
23
|
+
options: {
|
|
24
|
+
'#': {
|
|
25
|
+
bunTest,
|
|
26
|
+
env: {
|
|
27
|
+
BUNNY_API_KEY: { factReference: 't44/structs/providers/bunny.net/WorkspaceConnectionConfig:apiKey' }
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
pull: {
|
|
33
|
+
type: CapsulePropertyTypes.Mapping,
|
|
34
|
+
value: './api-pull'
|
|
35
|
+
},
|
|
36
|
+
storage: {
|
|
37
|
+
type: CapsulePropertyTypes.Mapping,
|
|
38
|
+
value: './api-storage'
|
|
39
|
+
},
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}, {
|
|
43
|
+
importMeta: import.meta,
|
|
44
|
+
importStack: makeImportStack(),
|
|
45
|
+
capsuleName: 't44/caps/providers/bunny.net/api-pull.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,164 @@
|
|
|
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': {},
|
|
17
|
+
'#t44/structs/providers/bunny.net/PullZoneFact': {
|
|
18
|
+
as: '$PullZoneFact'
|
|
19
|
+
},
|
|
20
|
+
'#t44/structs/providers/bunny.net/PullZoneListFact': {
|
|
21
|
+
as: '$PullZoneListFact'
|
|
22
|
+
},
|
|
23
|
+
'#': {
|
|
24
|
+
|
|
25
|
+
api: {
|
|
26
|
+
type: CapsulePropertyTypes.Mapping,
|
|
27
|
+
value: './api'
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
listZones: {
|
|
31
|
+
type: CapsulePropertyTypes.Function,
|
|
32
|
+
value: async function (this: any, options?: { page?: number; perPage?: number; search?: string; includeCertificate?: boolean }) {
|
|
33
|
+
const params: Record<string, string | number | boolean> = {};
|
|
34
|
+
if (options?.page !== undefined) {
|
|
35
|
+
params.page = options.page;
|
|
36
|
+
}
|
|
37
|
+
if (options?.perPage !== undefined) {
|
|
38
|
+
params.perPage = options.perPage;
|
|
39
|
+
}
|
|
40
|
+
if (options?.search) {
|
|
41
|
+
params.search = options.search;
|
|
42
|
+
}
|
|
43
|
+
if (options?.includeCertificate !== undefined) {
|
|
44
|
+
params.includeCertificate = options.includeCertificate;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const result = await this.api.call({
|
|
48
|
+
method: 'GET',
|
|
49
|
+
url: 'https://api.bunny.net/pullzone',
|
|
50
|
+
params,
|
|
51
|
+
operation: 'listZones'
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
await this.$PullZoneListFact.set('list', result);
|
|
55
|
+
|
|
56
|
+
return result;
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
createZone: {
|
|
61
|
+
type: CapsulePropertyTypes.Function,
|
|
62
|
+
value: async function (this: any, options: {
|
|
63
|
+
name: string;
|
|
64
|
+
originUrl: string;
|
|
65
|
+
storageZoneId?: number;
|
|
66
|
+
type?: 'Premium' | 'Standard';
|
|
67
|
+
}) {
|
|
68
|
+
const requestBody: any = {
|
|
69
|
+
Name: options.name,
|
|
70
|
+
OriginUrl: options.originUrl,
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
if (options.storageZoneId !== undefined) {
|
|
74
|
+
requestBody.StorageZoneId = options.storageZoneId;
|
|
75
|
+
}
|
|
76
|
+
if (options.type) {
|
|
77
|
+
requestBody.Type = options.type;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return await this.api.call({
|
|
81
|
+
method: 'POST',
|
|
82
|
+
url: 'https://api.bunny.net/pullzone',
|
|
83
|
+
data: requestBody,
|
|
84
|
+
operation: 'createZone'
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
ensureZone: {
|
|
90
|
+
type: CapsulePropertyTypes.Function,
|
|
91
|
+
value: async function (this: any, options: {
|
|
92
|
+
name: string;
|
|
93
|
+
originUrl: string;
|
|
94
|
+
storageZoneId?: number;
|
|
95
|
+
type?: 'Premium' | 'Standard';
|
|
96
|
+
}) {
|
|
97
|
+
const result = await this.listZones({ search: options.name });
|
|
98
|
+
const zones = result.Items || result;
|
|
99
|
+
const existingZone = zones.find((zone: any) => zone.Name === options.name);
|
|
100
|
+
|
|
101
|
+
if (existingZone) {
|
|
102
|
+
return await this.getZone(existingZone.Id);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return await this.createZone(options);
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
getZone: {
|
|
110
|
+
type: CapsulePropertyTypes.Function,
|
|
111
|
+
value: async function (this: any, id: number) {
|
|
112
|
+
const pullZone = await this.api.call({
|
|
113
|
+
method: 'GET',
|
|
114
|
+
url: `https://api.bunny.net/pullzone/${id}`,
|
|
115
|
+
operation: 'getZone'
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
if (pullZone && pullZone.Name) {
|
|
119
|
+
await this.$PullZoneFact.set(pullZone.Name, pullZone);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return pullZone;
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
|
|
126
|
+
deleteZone: {
|
|
127
|
+
type: CapsulePropertyTypes.Function,
|
|
128
|
+
value: async function (this: any, id: number) {
|
|
129
|
+
return await this.api.call({
|
|
130
|
+
method: 'DELETE',
|
|
131
|
+
url: `https://api.bunny.net/pullzone/${id}`,
|
|
132
|
+
operation: 'deleteZone'
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
|
|
137
|
+
purgeZone: {
|
|
138
|
+
type: CapsulePropertyTypes.Function,
|
|
139
|
+
value: async function (this: any, id: number, options?: { cacheTag?: string }) {
|
|
140
|
+
const requestBody: any = {};
|
|
141
|
+
|
|
142
|
+
if (options?.cacheTag) {
|
|
143
|
+
requestBody.CacheTag = options.cacheTag;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return await this.api.call({
|
|
147
|
+
method: 'POST',
|
|
148
|
+
url: `https://api.bunny.net/pullzone/${id}/purgeCache`,
|
|
149
|
+
data: requestBody,
|
|
150
|
+
operation: 'purgeZone'
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}, {
|
|
158
|
+
importMeta: import.meta,
|
|
159
|
+
importStack: makeImportStack(),
|
|
160
|
+
capsuleName: capsule['#'],
|
|
161
|
+
})
|
|
162
|
+
}
|
|
163
|
+
capsule['#'] = 't44/caps/providers/bunny.net/api-pull'
|
|
164
|
+
|
|
@@ -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 '@t44/t44/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': {},
|
|
18
|
+
'#': {
|
|
19
|
+
test: {
|
|
20
|
+
type: CapsulePropertyTypes.Mapping,
|
|
21
|
+
value: 't44/caps/WorkspaceTest',
|
|
22
|
+
options: {
|
|
23
|
+
'#': {
|
|
24
|
+
bunTest,
|
|
25
|
+
env: {
|
|
26
|
+
BUNNY_API_KEY: { factReference: 't44/structs/providers/bunny.net/WorkspaceConnectionConfig:apiKey' }
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
storage: {
|
|
32
|
+
type: CapsulePropertyTypes.Mapping,
|
|
33
|
+
value: './api-storage'
|
|
34
|
+
},
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}, {
|
|
38
|
+
importMeta: import.meta,
|
|
39
|
+
importStack: makeImportStack(),
|
|
40
|
+
capsuleName: 't44/caps/providers/bunny.net/api-storage.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
|
+
})
|