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