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,248 @@
|
|
|
1
|
+
|
|
2
|
+
import uploadToBunny from 'upload-to-bunny'
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
export async function capsule({
|
|
6
|
+
encapsulate,
|
|
7
|
+
CapsulePropertyTypes,
|
|
8
|
+
makeImportStack
|
|
9
|
+
}: {
|
|
10
|
+
encapsulate: any
|
|
11
|
+
CapsulePropertyTypes: any
|
|
12
|
+
makeImportStack: any
|
|
13
|
+
}) {
|
|
14
|
+
|
|
15
|
+
// https://docs.bunny.net/api-reference/core/storage-zone/
|
|
16
|
+
return encapsulate({
|
|
17
|
+
'#@stream44.studio/encapsulate/spine-contracts/CapsuleSpineContract.v0': {
|
|
18
|
+
'#@stream44.studio/encapsulate/structs/Capsule': {},
|
|
19
|
+
'#t44/structs/providers/bunny.net/StorageZoneFact': {
|
|
20
|
+
as: '$StorageZoneFact'
|
|
21
|
+
},
|
|
22
|
+
'#t44/structs/providers/bunny.net/StorageZoneListFact': {
|
|
23
|
+
as: '$StorageZoneListFact'
|
|
24
|
+
},
|
|
25
|
+
'#': {
|
|
26
|
+
|
|
27
|
+
api: {
|
|
28
|
+
type: CapsulePropertyTypes.Mapping,
|
|
29
|
+
value: './api'
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
listZones: {
|
|
33
|
+
type: CapsulePropertyTypes.Function,
|
|
34
|
+
value: async function (this: any, options?: { page?: number; perPage?: number; search?: string }) {
|
|
35
|
+
const params: Record<string, string | number> = {};
|
|
36
|
+
if (options?.page !== undefined) {
|
|
37
|
+
params.page = options.page;
|
|
38
|
+
}
|
|
39
|
+
if (options?.perPage !== undefined) {
|
|
40
|
+
params.perPage = options.perPage;
|
|
41
|
+
}
|
|
42
|
+
if (options?.search) {
|
|
43
|
+
params.search = options.search;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const result = await this.api.call({
|
|
47
|
+
method: 'GET',
|
|
48
|
+
url: 'https://api.bunny.net/storagezone',
|
|
49
|
+
params,
|
|
50
|
+
operation: 'listZones'
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
await this.$StorageZoneListFact.set('list', result);
|
|
54
|
+
|
|
55
|
+
return result;
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
createZone: {
|
|
60
|
+
type: CapsulePropertyTypes.Function,
|
|
61
|
+
value: async function (this: any, options: {
|
|
62
|
+
name: string;
|
|
63
|
+
region: string;
|
|
64
|
+
replicationRegions?: string[];
|
|
65
|
+
zoneTier?: 'Standard' | 'Premium';
|
|
66
|
+
storageZoneType?: 'NotSupported' | 'Standard' | 'Premium';
|
|
67
|
+
}) {
|
|
68
|
+
return await this.api.call({
|
|
69
|
+
method: 'POST',
|
|
70
|
+
url: 'https://api.bunny.net/storagezone',
|
|
71
|
+
data: {
|
|
72
|
+
Name: options.name,
|
|
73
|
+
Region: options.region,
|
|
74
|
+
ReplicationRegions: options.replicationRegions || [],
|
|
75
|
+
ZoneTier: options.zoneTier || 'Standard',
|
|
76
|
+
StorageZoneType: options.storageZoneType || 'NotSupported'
|
|
77
|
+
},
|
|
78
|
+
operation: 'createZone'
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
ensureZone: {
|
|
84
|
+
type: CapsulePropertyTypes.Function,
|
|
85
|
+
value: async function (this: any, options: {
|
|
86
|
+
name: string;
|
|
87
|
+
region: string;
|
|
88
|
+
replicationRegions?: string[];
|
|
89
|
+
zoneTier?: 'Standard' | 'Premium';
|
|
90
|
+
storageZoneType?: 'NotSupported' | 'Standard' | 'Premium';
|
|
91
|
+
}) {
|
|
92
|
+
const zones = await this.listZones({ search: options.name });
|
|
93
|
+
const existingZone = zones.find((zone: any) => zone.Name === options.name);
|
|
94
|
+
|
|
95
|
+
if (existingZone) {
|
|
96
|
+
return await this.getZone(existingZone.Id);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return await this.createZone(options);
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
getZone: {
|
|
104
|
+
type: CapsulePropertyTypes.Function,
|
|
105
|
+
value: async function (this: any, id: number) {
|
|
106
|
+
const storageZone = await this.api.call({
|
|
107
|
+
method: 'GET',
|
|
108
|
+
url: `https://api.bunny.net/storagezone/${id}`,
|
|
109
|
+
operation: 'getZone'
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
if (storageZone && storageZone.Name) {
|
|
113
|
+
await this.$StorageZoneFact.set(storageZone.Name, storageZone);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return storageZone;
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
deleteZone: {
|
|
121
|
+
type: CapsulePropertyTypes.Function,
|
|
122
|
+
value: async function (this: any, id: number, deletePullZones: boolean = true) {
|
|
123
|
+
const params: Record<string, string | boolean> = {};
|
|
124
|
+
if (!deletePullZones) {
|
|
125
|
+
params.deletePullZones = false;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return await this.api.call({
|
|
129
|
+
method: 'DELETE',
|
|
130
|
+
url: `https://api.bunny.net/storagezone/${id}`,
|
|
131
|
+
params,
|
|
132
|
+
operation: 'deleteZone'
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
|
|
137
|
+
listFiles: {
|
|
138
|
+
type: CapsulePropertyTypes.Function,
|
|
139
|
+
value: async function (this: any, options: {
|
|
140
|
+
storageZoneName: string;
|
|
141
|
+
storageHostname: string;
|
|
142
|
+
path?: string;
|
|
143
|
+
password: string;
|
|
144
|
+
}) {
|
|
145
|
+
const path = options.path ? `/${options.path}` : '';
|
|
146
|
+
const headers = {
|
|
147
|
+
'AccessKey': options.password
|
|
148
|
+
};
|
|
149
|
+
return await this.api.call({
|
|
150
|
+
method: 'GET',
|
|
151
|
+
url: `https://${options.storageHostname}/${options.storageZoneName}${path}/`,
|
|
152
|
+
operation: 'listFiles',
|
|
153
|
+
headers: headers
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
|
|
158
|
+
uploadFile: {
|
|
159
|
+
type: CapsulePropertyTypes.Function,
|
|
160
|
+
value: async function (this: any, options: {
|
|
161
|
+
storageZoneName: string;
|
|
162
|
+
storageHostname: string;
|
|
163
|
+
path?: string;
|
|
164
|
+
fileName: string;
|
|
165
|
+
data: string | Buffer;
|
|
166
|
+
password: string;
|
|
167
|
+
}) {
|
|
168
|
+
const path = options.path ? `/${options.path}` : '';
|
|
169
|
+
const headers = {
|
|
170
|
+
'AccessKey': options.password,
|
|
171
|
+
'Content-Type': 'application/octet-stream'
|
|
172
|
+
};
|
|
173
|
+
return await this.api.call({
|
|
174
|
+
method: 'PUT',
|
|
175
|
+
url: `https://${options.storageHostname}/${options.storageZoneName}${path}/${options.fileName}`,
|
|
176
|
+
data: options.data,
|
|
177
|
+
operation: 'uploadFile',
|
|
178
|
+
headers: headers
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
|
|
183
|
+
deleteFile: {
|
|
184
|
+
type: CapsulePropertyTypes.Function,
|
|
185
|
+
value: async function (this: any, options: {
|
|
186
|
+
storageZoneName: string;
|
|
187
|
+
storageHostname: string;
|
|
188
|
+
path?: string;
|
|
189
|
+
fileName: string;
|
|
190
|
+
password: string;
|
|
191
|
+
}) {
|
|
192
|
+
const path = options.path ? `/${options.path}` : '';
|
|
193
|
+
const headers = {
|
|
194
|
+
'AccessKey': options.password
|
|
195
|
+
};
|
|
196
|
+
return await this.api.call({
|
|
197
|
+
method: 'DELETE',
|
|
198
|
+
url: `https://${options.storageHostname}/${options.storageZoneName}${path}/${options.fileName}`,
|
|
199
|
+
operation: 'deleteFile',
|
|
200
|
+
headers: headers
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
},
|
|
204
|
+
|
|
205
|
+
uploadDirectory: {
|
|
206
|
+
type: CapsulePropertyTypes.Function,
|
|
207
|
+
value: async function (this: any, options: {
|
|
208
|
+
sourceDirectory: string;
|
|
209
|
+
destinationDirectory?: string;
|
|
210
|
+
storageZoneName: string;
|
|
211
|
+
password: string;
|
|
212
|
+
region?: string;
|
|
213
|
+
cleanDestination?: 'simple' | 'avoid-deletes';
|
|
214
|
+
maxConcurrentUploads?: number;
|
|
215
|
+
}) {
|
|
216
|
+
|
|
217
|
+
const uploadOptions: any = {
|
|
218
|
+
storageZoneName: options.storageZoneName,
|
|
219
|
+
accessKey: options.password,
|
|
220
|
+
maxConcurrentUploads: options.maxConcurrentUploads || 10
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
if (options.region) {
|
|
224
|
+
uploadOptions.region = options.region;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (options.cleanDestination) {
|
|
228
|
+
uploadOptions.cleanDestination = options.cleanDestination;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return await uploadToBunny(
|
|
232
|
+
options.sourceDirectory,
|
|
233
|
+
options.destinationDirectory || '',
|
|
234
|
+
uploadOptions
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}, {
|
|
242
|
+
importMeta: import.meta,
|
|
243
|
+
importStack: makeImportStack(),
|
|
244
|
+
capsuleName: capsule['#'],
|
|
245
|
+
})
|
|
246
|
+
}
|
|
247
|
+
capsule['#'] = 't44/caps/providers/bunny.net/api-storage'
|
|
248
|
+
|
|
@@ -0,0 +1,95 @@
|
|
|
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
|
+
// https://docs.bunny.net/api-reference/core
|
|
14
|
+
return encapsulate({
|
|
15
|
+
'#@stream44.studio/encapsulate/spine-contracts/CapsuleSpineContract.v0': {
|
|
16
|
+
'#@stream44.studio/encapsulate/structs/Capsule': {},
|
|
17
|
+
'#t44/structs/providers/bunny.net/WorkspaceConnectionConfig': {
|
|
18
|
+
as: '$ConnectionConfig'
|
|
19
|
+
},
|
|
20
|
+
'#': {
|
|
21
|
+
|
|
22
|
+
call: {
|
|
23
|
+
type: CapsulePropertyTypes.Function,
|
|
24
|
+
value: async function (this: any, options: {
|
|
25
|
+
method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
|
|
26
|
+
url: string;
|
|
27
|
+
data?: any;
|
|
28
|
+
params?: Record<string, string | number | boolean>;
|
|
29
|
+
operation?: string;
|
|
30
|
+
headers?: Record<string, string>;
|
|
31
|
+
}) {
|
|
32
|
+
const apiKey = options.headers?.AccessKey || await this.$ConnectionConfig.getConfigValue('apiKey')
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
const config: any = {
|
|
36
|
+
method: options.method,
|
|
37
|
+
url: options.url,
|
|
38
|
+
headers: options.headers || {
|
|
39
|
+
'AccessKey': apiKey
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
if (options.data) {
|
|
44
|
+
if (!config.headers['Content-Type']) {
|
|
45
|
+
config.headers['Content-Type'] = 'application/json';
|
|
46
|
+
}
|
|
47
|
+
config.data = options.data;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (options.params) {
|
|
51
|
+
config.params = options.params;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const response = await axios(config);
|
|
55
|
+
return response.data;
|
|
56
|
+
} catch (error: any) {
|
|
57
|
+
if (error.response) {
|
|
58
|
+
const errorData = error.response.data;
|
|
59
|
+
const status = error.response.status;
|
|
60
|
+
const statusText = error.response.statusText;
|
|
61
|
+
|
|
62
|
+
const operationName = options.operation || options.method;
|
|
63
|
+
console.error(`Bunny API Error [${operationName}]:`);
|
|
64
|
+
console.error(` Status: ${status} ${statusText}`);
|
|
65
|
+
console.error(` Response:`, JSON.stringify(errorData, null, 2));
|
|
66
|
+
|
|
67
|
+
let errorMessage = `Bunny API ${operationName} failed: ${status} ${statusText}`;
|
|
68
|
+
|
|
69
|
+
if (errorData && typeof errorData === 'object') {
|
|
70
|
+
if (errorData.Message) {
|
|
71
|
+
errorMessage += ` - ${errorData.Message}`;
|
|
72
|
+
} else if (errorData.ErrorKey) {
|
|
73
|
+
errorMessage += ` - ${errorData.ErrorKey}`;
|
|
74
|
+
} else {
|
|
75
|
+
errorMessage += ` - ${JSON.stringify(errorData)}`;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
throw new Error(errorMessage);
|
|
80
|
+
}
|
|
81
|
+
throw error;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}, {
|
|
89
|
+
importMeta: import.meta,
|
|
90
|
+
importStack: makeImportStack(),
|
|
91
|
+
capsuleName: capsule['#'],
|
|
92
|
+
})
|
|
93
|
+
}
|
|
94
|
+
capsule['#'] = 't44/caps/providers/bunny.net/api'
|
|
95
|
+
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
|
|
2
|
+
export async function capsule({
|
|
3
|
+
encapsulate,
|
|
4
|
+
CapsulePropertyTypes,
|
|
5
|
+
makeImportStack
|
|
6
|
+
}: {
|
|
7
|
+
encapsulate: any
|
|
8
|
+
CapsulePropertyTypes: any
|
|
9
|
+
makeImportStack: any
|
|
10
|
+
}) {
|
|
11
|
+
return encapsulate({
|
|
12
|
+
'#@stream44.studio/encapsulate/spine-contracts/CapsuleSpineContract.v0': {
|
|
13
|
+
'#@stream44.studio/encapsulate/structs/Capsule': {},
|
|
14
|
+
'#t44/structs/ProjectDeploymentFact': {
|
|
15
|
+
as: '$StatusFact'
|
|
16
|
+
},
|
|
17
|
+
'#t44/structs/providers/dynadot.com/DomainFact': {
|
|
18
|
+
as: '$DomainFact'
|
|
19
|
+
},
|
|
20
|
+
'#t44/structs/ProjectDeploymentConfig': {
|
|
21
|
+
as: '$ProjectDeploymentConfig'
|
|
22
|
+
},
|
|
23
|
+
'#': {
|
|
24
|
+
domains: {
|
|
25
|
+
type: CapsulePropertyTypes.Mapping,
|
|
26
|
+
value: './api-domains'
|
|
27
|
+
},
|
|
28
|
+
deploy: {
|
|
29
|
+
type: CapsulePropertyTypes.Function,
|
|
30
|
+
value: async function (this: any, { projectionDir, alias, config, workspaceProjectName }: { projectionDir: string, alias: string, config: any, workspaceProjectName?: string }) {
|
|
31
|
+
const domainConfig = config.provider.config.Domain
|
|
32
|
+
const domainName = domainConfig.name
|
|
33
|
+
const zones = domainConfig.zones || []
|
|
34
|
+
|
|
35
|
+
console.log(`Deploying DNS for '${domainName}' via Dynadot ...`)
|
|
36
|
+
|
|
37
|
+
const unsupportedApiTypes: string[] = []
|
|
38
|
+
|
|
39
|
+
// Process only the zones we define — resolve jit() values
|
|
40
|
+
const mainRecords: Array<{ recordType: string; value: string }> = []
|
|
41
|
+
const subdomainRecords: Array<{ subdomain: string; record_type: string; value: string }> = []
|
|
42
|
+
|
|
43
|
+
for (const zone of zones) {
|
|
44
|
+
let value = zone.value
|
|
45
|
+
if (typeof value === 'function') {
|
|
46
|
+
value = await value()
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const recordType = zone.type || 'cname'
|
|
50
|
+
const subdomain = zone.subdomain || ''
|
|
51
|
+
|
|
52
|
+
if (unsupportedApiTypes.includes(recordType.toLowerCase())) {
|
|
53
|
+
console.log(` ⚠ ${domainName} -> ${value} (${recordType.toUpperCase()}) — not settable via API`)
|
|
54
|
+
console.log(` Set manually at: https://www.dynadot.com/account/domain/name/${domainName}`)
|
|
55
|
+
continue
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (subdomain === '' || subdomain === '@') {
|
|
59
|
+
mainRecords.push({ recordType, value })
|
|
60
|
+
console.log(` ${domainName} -> ${value} (${recordType.toUpperCase()})`)
|
|
61
|
+
} else {
|
|
62
|
+
subdomainRecords.push({ subdomain, record_type: recordType, value })
|
|
63
|
+
console.log(` ${subdomain}.${domainName} -> ${value} (${recordType.toUpperCase()})`)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (mainRecords.length === 0 && subdomainRecords.length === 0) {
|
|
68
|
+
console.log(` No DNS records to update.`)
|
|
69
|
+
} else {
|
|
70
|
+
console.log(` Setting DNS records (${mainRecords.length} main, ${subdomainRecords.length} sub) ...`)
|
|
71
|
+
const setResult = await this.domains.setDns({
|
|
72
|
+
name: domainName,
|
|
73
|
+
records: subdomainRecords,
|
|
74
|
+
mainDomains: mainRecords,
|
|
75
|
+
addToCurrent: true
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
// v2 REST API returns { code: 200, message: "Success" }
|
|
79
|
+
const code = setResult?.code
|
|
80
|
+
const message = setResult?.message
|
|
81
|
+
if (code !== 200) {
|
|
82
|
+
console.log(` DNS API response:`, JSON.stringify(setResult, null, 2))
|
|
83
|
+
throw new Error(`Failed to set DNS: ${message || JSON.stringify(setResult)}`)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
console.log(` DNS deployment complete: https://${domainName}`)
|
|
88
|
+
|
|
89
|
+
const statusResult = {
|
|
90
|
+
projectName: domainName,
|
|
91
|
+
provider: 'dynadot.com',
|
|
92
|
+
status: 'READY',
|
|
93
|
+
publicUrl: `https://${domainName}`
|
|
94
|
+
}
|
|
95
|
+
await this.$StatusFact.set(domainName, statusResult)
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
deprovision: {
|
|
99
|
+
type: CapsulePropertyTypes.Function,
|
|
100
|
+
value: async function (this: any, { config }: { config: any }) {
|
|
101
|
+
const domainName = config.provider.config.Domain.name
|
|
102
|
+
|
|
103
|
+
console.log(`Deprovisioning DNS for '${domainName}' from Dynadot ...`)
|
|
104
|
+
|
|
105
|
+
// Get current DNS records
|
|
106
|
+
const currentDns = await this.domains.getDns({ name: domainName })
|
|
107
|
+
const nsSettings = currentDns?.data?.domain_info?.[0]?.glue_info?.name_server_settings || {}
|
|
108
|
+
const existingRecords = nsSettings?.sub_domains || []
|
|
109
|
+
|
|
110
|
+
// Remove CNAME for root domain
|
|
111
|
+
const filteredRecords = existingRecords.filter((r: any) => {
|
|
112
|
+
return !(r.sub_host === '' && r.record_type === 'cname')
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
if (filteredRecords.length !== existingRecords.length) {
|
|
116
|
+
console.log(`Removing root CNAME record ...`)
|
|
117
|
+
await this.domains.setDns({
|
|
118
|
+
name: domainName,
|
|
119
|
+
records: filteredRecords
|
|
120
|
+
})
|
|
121
|
+
console.log(`Root CNAME record removed`)
|
|
122
|
+
} else {
|
|
123
|
+
console.log(`No root CNAME record found to remove`)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Delete fact files
|
|
127
|
+
console.log(`Deleting fact files ...`)
|
|
128
|
+
try {
|
|
129
|
+
await this.$DomainFact.delete(domainName)
|
|
130
|
+
await this.$StatusFact.delete(domainName)
|
|
131
|
+
console.log(`Fact files deleted`)
|
|
132
|
+
} catch (error: any) {
|
|
133
|
+
console.log(`Error deleting fact files: ${error.message}`)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
console.log(`Deprovision complete`)
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
status: {
|
|
140
|
+
type: CapsulePropertyTypes.Function,
|
|
141
|
+
value: async function (this: any, { config, now, passive, deploymentName }: { config: any; now?: boolean; passive?: boolean; deploymentName?: string }) {
|
|
142
|
+
const domainName = config.provider.config.Domain.name
|
|
143
|
+
const factName = deploymentName || domainName
|
|
144
|
+
|
|
145
|
+
if (!domainName) {
|
|
146
|
+
return {
|
|
147
|
+
projectName: factName || 'unknown',
|
|
148
|
+
provider: 'dynadot.com',
|
|
149
|
+
error: 'No domain name configured',
|
|
150
|
+
rawDefinitionFilepaths: []
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const rawFilepaths = [
|
|
155
|
+
this.$DomainFact.getRelativeFilepath(domainName)
|
|
156
|
+
]
|
|
157
|
+
|
|
158
|
+
// Try to get cached status if not forcing refresh
|
|
159
|
+
if (!now) {
|
|
160
|
+
const cached = await this.$StatusFact.get(factName, rawFilepaths)
|
|
161
|
+
if (cached) {
|
|
162
|
+
return cached.data
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// In passive mode, don't call the provider if no cache exists
|
|
167
|
+
if (passive) {
|
|
168
|
+
return null
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const dnsInfo = await this.domains.getDns({ name: domainName })
|
|
172
|
+
const nsSettings = dnsInfo?.data?.domain_info?.[0]?.glue_info?.name_server_settings || {}
|
|
173
|
+
const mainDomains = nsSettings.main_domains || []
|
|
174
|
+
const subDomains = nsSettings.sub_domains || []
|
|
175
|
+
|
|
176
|
+
// Check for main domain CNAME or ANAME record
|
|
177
|
+
const mainCname = mainDomains.find((r: any) => ['cname', 'aname'].includes(r.record_type?.toLowerCase()))
|
|
178
|
+
|
|
179
|
+
const result: Record<string, any> = {
|
|
180
|
+
projectName: factName,
|
|
181
|
+
provider: 'dynadot.com',
|
|
182
|
+
status: mainCname ? 'READY' : 'NOT_CONFIGURED',
|
|
183
|
+
publicUrl: mainCname ? `https://${domainName}` : undefined,
|
|
184
|
+
providerPortalUrl: `https://www.dynadot.com/account/domain/name/${domainName}`,
|
|
185
|
+
dnsRecords: { mainDomains, subDomains },
|
|
186
|
+
rawDefinitionFilepaths: rawFilepaths
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
await this.$StatusFact.set(factName, result)
|
|
190
|
+
|
|
191
|
+
return result
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}, {
|
|
197
|
+
importMeta: import.meta,
|
|
198
|
+
importStack: makeImportStack(),
|
|
199
|
+
capsuleName: capsule['#'],
|
|
200
|
+
})
|
|
201
|
+
}
|
|
202
|
+
capsule['#'] = 't44/caps/providers/dynadot.com/ProjectDeployment'
|