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,200 @@
|
|
|
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/workspace-rt'
|
|
10
|
+
|
|
11
|
+
const {
|
|
12
|
+
test: { describe, it, expect },
|
|
13
|
+
api
|
|
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
|
+
DYNADOT_API_KEY: { factReference: 't44/structs/providers/dynadot.com/WorkspaceConnectionConfig:apiKey' },
|
|
27
|
+
DYNADOT_API_SECRET: { factReference: 't44/structs/providers/dynadot.com/WorkspaceConnectionConfig:apiSecret' },
|
|
28
|
+
DYNADOT_API_KEY_TRANSACTION_SECRET: { factReference: 't44/structs/providers/dynadot.com/WorkspaceConnectionConfig:apiKeyTransactionSecret' }
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
api: {
|
|
34
|
+
type: CapsulePropertyTypes.Mapping,
|
|
35
|
+
value: './api-restful-v2'
|
|
36
|
+
},
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}, {
|
|
40
|
+
importMeta: import.meta,
|
|
41
|
+
importStack: makeImportStack(),
|
|
42
|
+
capsuleName: 't44/caps/providers/dynadot.com/api-restful-v2.test'
|
|
43
|
+
})
|
|
44
|
+
return { spine }
|
|
45
|
+
}, async ({ spine, apis }: any) => {
|
|
46
|
+
return apis[spine.capsuleSourceLineRef]
|
|
47
|
+
}, {
|
|
48
|
+
importMeta: import.meta
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
describe('Dynadot REST API v2', function () {
|
|
52
|
+
|
|
53
|
+
let domainName: string
|
|
54
|
+
|
|
55
|
+
it('should list domains', async function () {
|
|
56
|
+
|
|
57
|
+
const result = await api.call({
|
|
58
|
+
method: 'GET',
|
|
59
|
+
path: 'domains',
|
|
60
|
+
operation: 'list'
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
expect(result).toBeObject()
|
|
64
|
+
expect(result.code).toBe(200)
|
|
65
|
+
expect(result.data).toBeObject()
|
|
66
|
+
expect(result.data.domain_info).toBeArray()
|
|
67
|
+
expect(result.data.domain_info.length).toBeGreaterThan(0)
|
|
68
|
+
|
|
69
|
+
domainName = result.data.domain_info[0].domain_name
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it('should get domain info for first domain', async function () {
|
|
73
|
+
|
|
74
|
+
const result = await api.call({
|
|
75
|
+
method: 'GET',
|
|
76
|
+
path: `domains/${domainName}`,
|
|
77
|
+
operation: 'info'
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
expect(result).toBeObject()
|
|
81
|
+
expect(result.code).toBe(200)
|
|
82
|
+
expect(result.data).toBeObject()
|
|
83
|
+
expect(result.data.domain_info).toBeArray()
|
|
84
|
+
expect(result.data.domain_info[0].domain_name).toBe(domainName)
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
it('should set a TXT test record, verify, remove, and verify removal', async function () {
|
|
88
|
+
|
|
89
|
+
// 1. Get current DNS to snapshot existing records
|
|
90
|
+
const beforeDns = await api.call({
|
|
91
|
+
method: 'GET',
|
|
92
|
+
path: `domains/${domainName}`,
|
|
93
|
+
operation: 'getDns'
|
|
94
|
+
})
|
|
95
|
+
expect(beforeDns.code).toBe(200)
|
|
96
|
+
const nsBefore = beforeDns.data.domain_info[0].glue_info?.name_server_settings || {}
|
|
97
|
+
const existingSubs = nsBefore.sub_domains || []
|
|
98
|
+
|
|
99
|
+
// 2. Add a TXT test record using add_dns_to_current_setting to preserve all existing records
|
|
100
|
+
// v2 requires at least one dns_main_list entry
|
|
101
|
+
const existingMainDomains = nsBefore.main_domains || []
|
|
102
|
+
const mainListForAdd = existingMainDomains.length > 0
|
|
103
|
+
? existingMainDomains.map((r: any) => ({
|
|
104
|
+
record_type: r.record_type,
|
|
105
|
+
record_value1: r.value || r.record_value1,
|
|
106
|
+
...(r.record_value2 ? { record_value2: r.record_value2 } : {}),
|
|
107
|
+
}))
|
|
108
|
+
: [{ record_type: 'a', record_value1: '127.0.0.1' }]
|
|
109
|
+
|
|
110
|
+
const setResult = await api.call({
|
|
111
|
+
method: 'POST',
|
|
112
|
+
path: `domains/${domainName}/records`,
|
|
113
|
+
body: {
|
|
114
|
+
add_dns_to_current_setting: true,
|
|
115
|
+
dns_main_list: mainListForAdd,
|
|
116
|
+
sub_list: [{
|
|
117
|
+
sub_host: 't44-v2-api-test',
|
|
118
|
+
record_type: 'txt',
|
|
119
|
+
record_value1: 'v2-test-value'
|
|
120
|
+
}]
|
|
121
|
+
},
|
|
122
|
+
operation: 'setDns'
|
|
123
|
+
})
|
|
124
|
+
expect(setResult).toBeObject()
|
|
125
|
+
expect(setResult.code).toBe(200)
|
|
126
|
+
|
|
127
|
+
// 3. Get DNS again — verify TXT record was added and existing records preserved
|
|
128
|
+
const afterSet = await api.call({
|
|
129
|
+
method: 'GET',
|
|
130
|
+
path: `domains/${domainName}`,
|
|
131
|
+
operation: 'getDns'
|
|
132
|
+
})
|
|
133
|
+
expect(afterSet.code).toBe(200)
|
|
134
|
+
const nsAfterSet = afterSet.data.domain_info[0].glue_info?.name_server_settings || {}
|
|
135
|
+
const subsAfterSet = nsAfterSet.sub_domains || []
|
|
136
|
+
|
|
137
|
+
const txtRecord = subsAfterSet.find((r: any) => r.sub_host === 't44-v2-api-test')
|
|
138
|
+
expect(txtRecord).toBeDefined()
|
|
139
|
+
expect(txtRecord.record_type).toBe('txt')
|
|
140
|
+
expect(txtRecord.value).toBe('v2-test-value')
|
|
141
|
+
|
|
142
|
+
// Verify existing subdomain records are still present
|
|
143
|
+
for (const orig of existingSubs) {
|
|
144
|
+
const found = subsAfterSet.find((r: any) => r.sub_host === orig.sub_host && r.record_type === orig.record_type)
|
|
145
|
+
expect(found).toBeDefined()
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// 4. Remove the TXT record by re-setting all records WITHOUT the test record
|
|
149
|
+
const subsWithoutTest = subsAfterSet
|
|
150
|
+
.filter((r: any) => r.sub_host !== 't44-v2-api-test')
|
|
151
|
+
.map((r: any) => ({
|
|
152
|
+
sub_host: r.sub_host,
|
|
153
|
+
record_type: r.record_type,
|
|
154
|
+
record_value1: r.value,
|
|
155
|
+
}))
|
|
156
|
+
|
|
157
|
+
const mainRecordsForRestore = (nsAfterSet.main_domains || []).map((r: any) => ({
|
|
158
|
+
record_type: r.record_type,
|
|
159
|
+
record_value1: r.value || r.record_value1,
|
|
160
|
+
...(r.record_value2 ? { record_value2: r.record_value2 } : {}),
|
|
161
|
+
}))
|
|
162
|
+
|
|
163
|
+
// Need at least one dns_main_list entry
|
|
164
|
+
const dnsMainList = mainRecordsForRestore.length > 0
|
|
165
|
+
? mainRecordsForRestore
|
|
166
|
+
: [{ record_type: 'a', record_value1: '127.0.0.1' }]
|
|
167
|
+
|
|
168
|
+
const removeResult = await api.call({
|
|
169
|
+
method: 'POST',
|
|
170
|
+
path: `domains/${domainName}/records`,
|
|
171
|
+
body: {
|
|
172
|
+
dns_main_list: dnsMainList,
|
|
173
|
+
sub_list: subsWithoutTest,
|
|
174
|
+
},
|
|
175
|
+
operation: 'setDns'
|
|
176
|
+
})
|
|
177
|
+
expect(removeResult).toBeObject()
|
|
178
|
+
expect(removeResult.code).toBe(200)
|
|
179
|
+
|
|
180
|
+
// 5. Get DNS again — verify TXT record is gone and original records restored
|
|
181
|
+
const afterRemove = await api.call({
|
|
182
|
+
method: 'GET',
|
|
183
|
+
path: `domains/${domainName}`,
|
|
184
|
+
operation: 'getDns'
|
|
185
|
+
})
|
|
186
|
+
expect(afterRemove.code).toBe(200)
|
|
187
|
+
const nsAfterRemove = afterRemove.data.domain_info[0].glue_info?.name_server_settings || {}
|
|
188
|
+
const subsAfterRemove = nsAfterRemove.sub_domains || []
|
|
189
|
+
|
|
190
|
+
const removedRecord = subsAfterRemove.find((r: any) => r.sub_host === 't44-v2-api-test')
|
|
191
|
+
expect(removedRecord).toBeUndefined()
|
|
192
|
+
|
|
193
|
+
// Verify original subdomain records are still present
|
|
194
|
+
for (const orig of existingSubs) {
|
|
195
|
+
const found = subsAfterRemove.find((r: any) => r.sub_host === orig.sub_host && r.record_type === orig.record_type)
|
|
196
|
+
expect(found).toBeDefined()
|
|
197
|
+
}
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
})
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
|
|
2
|
+
import axios from 'axios';
|
|
3
|
+
|
|
4
|
+
export async function capsule({
|
|
5
|
+
encapsulate,
|
|
6
|
+
CapsulePropertyTypes,
|
|
7
|
+
makeImportStack
|
|
8
|
+
}: {
|
|
9
|
+
encapsulate: any
|
|
10
|
+
CapsulePropertyTypes: any
|
|
11
|
+
makeImportStack: any
|
|
12
|
+
}) {
|
|
13
|
+
// Dynadot REST API v2
|
|
14
|
+
// Docs: https://www.dynadot.com/domain/api-document
|
|
15
|
+
// URL format: https://api.dynadot.com/restful/v2/{resource}/{resource_identify}/{action}
|
|
16
|
+
// Auth: Bearer apiKey, HMAC-SHA256 signed with apiSecret
|
|
17
|
+
// Signing: stringToSign uses full path /restful/v2/... (per v2 docs)
|
|
18
|
+
return encapsulate({
|
|
19
|
+
'#@stream44.studio/encapsulate/spine-contracts/CapsuleSpineContract.v0': {
|
|
20
|
+
'#@stream44.studio/encapsulate/structs/Capsule': {},
|
|
21
|
+
'#t44/structs/providers/dynadot.com/WorkspaceConnectionConfig': {
|
|
22
|
+
as: '$ConnectionConfig'
|
|
23
|
+
},
|
|
24
|
+
'#': {
|
|
25
|
+
|
|
26
|
+
call: {
|
|
27
|
+
type: CapsulePropertyTypes.Function,
|
|
28
|
+
value: async function (this: any, options: {
|
|
29
|
+
method: 'GET' | 'POST' | 'PUT' | 'DELETE';
|
|
30
|
+
path: string;
|
|
31
|
+
query?: Record<string, string>;
|
|
32
|
+
body?: any;
|
|
33
|
+
operation?: string;
|
|
34
|
+
}) {
|
|
35
|
+
const { createHmac, randomUUID } = await import('crypto')
|
|
36
|
+
const apiKey = await this.$ConnectionConfig.getConfigValue('apiKey')
|
|
37
|
+
const apiSecret = await this.$ConnectionConfig.getConfigValue('apiSecret')
|
|
38
|
+
|
|
39
|
+
const baseUrl = 'https://api.dynadot.com/restful/v2/'
|
|
40
|
+
const relativePath = options.path.replace(/^\//, '')
|
|
41
|
+
const requestId = randomUUID()
|
|
42
|
+
|
|
43
|
+
// Build request
|
|
44
|
+
const axiosOpts: any = {
|
|
45
|
+
method: options.method,
|
|
46
|
+
url: `${baseUrl}${relativePath}`,
|
|
47
|
+
headers: {
|
|
48
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
49
|
+
'X-Request-Id': requestId,
|
|
50
|
+
'Accept': 'application/json',
|
|
51
|
+
'Content-Type': 'application/json',
|
|
52
|
+
},
|
|
53
|
+
validateStatus: () => true,
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// For GET: params as query string, empty payload
|
|
57
|
+
// For POST/PUT: JSON body as payload
|
|
58
|
+
// Send pre-serialized string to ensure signature matches body exactly
|
|
59
|
+
let payloadJson = ''
|
|
60
|
+
if (options.method === 'GET' && options.query) {
|
|
61
|
+
axiosOpts.params = options.query
|
|
62
|
+
} else if (options.body) {
|
|
63
|
+
payloadJson = JSON.stringify(options.body)
|
|
64
|
+
axiosOpts.data = payloadJson
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// HMAC-SHA256 signature: full path + apiSecret + base64 encoding
|
|
68
|
+
const fullPath = `/restful/v2/${relativePath}`
|
|
69
|
+
const stringToSign = `${apiKey}\n${fullPath}\n${requestId}\n${payloadJson}`
|
|
70
|
+
const signature = createHmac('sha256', apiSecret).update(stringToSign).digest('base64')
|
|
71
|
+
axiosOpts.headers['X-Signature'] = signature
|
|
72
|
+
|
|
73
|
+
const response = await axios(axiosOpts)
|
|
74
|
+
|
|
75
|
+
if (response.status >= 400 || (response.data?.code && response.data.code >= 400)) {
|
|
76
|
+
const errorData = response.data
|
|
77
|
+
const operationName = options.operation || options.path
|
|
78
|
+
const desc = errorData?.error?.description || errorData?.message || JSON.stringify(errorData)
|
|
79
|
+
throw new Error(`Dynadot API v2 ${operationName} failed: ${response.status} - ${desc}`)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return response.data
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}, {
|
|
89
|
+
importMeta: import.meta,
|
|
90
|
+
importStack: makeImportStack(),
|
|
91
|
+
capsuleName: capsule['#'],
|
|
92
|
+
})
|
|
93
|
+
}
|
|
94
|
+
capsule['#'] = 't44/caps/providers/dynadot.com/api-restful-v2'
|