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.
Files changed (125) hide show
  1. package/.dco-signatures +9 -0
  2. package/.github/workflows/dco.yml +12 -0
  3. package/.o/GordianOpenIntegrity-CurrentLifehash.svg +1026 -0
  4. package/.o/GordianOpenIntegrity-InceptionLifehash.svg +1026 -0
  5. package/.o/GordianOpenIntegrity.yaml +25 -0
  6. package/.o/assets/Hero-Terminal44-v0.jpeg +0 -0
  7. package/DCO.md +34 -0
  8. package/LICENSE.md +203 -0
  9. package/README.md +183 -0
  10. package/bin/activate +36 -0
  11. package/bin/activate.ts +30 -0
  12. package/bin/postinstall.sh +19 -0
  13. package/bin/shell +27 -0
  14. package/bin/t44 +27 -0
  15. package/caps/ConfigSchemaStruct.ts +55 -0
  16. package/caps/Home.ts +51 -0
  17. package/caps/HomeRegistry.ts +313 -0
  18. package/caps/HomeRegistryFile.ts +144 -0
  19. package/caps/JsonSchemas.ts +220 -0
  20. package/caps/OpenApiSchema.ts +67 -0
  21. package/caps/PackageDescriptor.ts +88 -0
  22. package/caps/ProjectCatalogs.ts +153 -0
  23. package/caps/ProjectDeployment.ts +363 -0
  24. package/caps/ProjectDevelopment.ts +257 -0
  25. package/caps/ProjectPublishing.ts +522 -0
  26. package/caps/ProjectRack.ts +155 -0
  27. package/caps/ProjectRepository.ts +322 -0
  28. package/caps/RootKey.ts +219 -0
  29. package/caps/SigningKey.ts +243 -0
  30. package/caps/WorkspaceCli.ts +442 -0
  31. package/caps/WorkspaceConfig.ts +268 -0
  32. package/caps/WorkspaceConfig.yaml +71 -0
  33. package/caps/WorkspaceConfigFile.ts +799 -0
  34. package/caps/WorkspaceConnection.ts +249 -0
  35. package/caps/WorkspaceEntityConfig.ts +78 -0
  36. package/caps/WorkspaceEntityConfig.v0.ts +77 -0
  37. package/caps/WorkspaceEntityFact.ts +218 -0
  38. package/caps/WorkspaceInfo.ts +595 -0
  39. package/caps/WorkspaceInit.ts +30 -0
  40. package/caps/WorkspaceKey.ts +338 -0
  41. package/caps/WorkspaceModel.ts +373 -0
  42. package/caps/WorkspaceProjects.ts +636 -0
  43. package/caps/WorkspacePrompt.ts +406 -0
  44. package/caps/WorkspaceShell.sh +39 -0
  45. package/caps/WorkspaceShell.ts +104 -0
  46. package/caps/WorkspaceShell.yaml +64 -0
  47. package/caps/WorkspaceShellCli.ts +109 -0
  48. package/caps/WorkspaceTest.ts +167 -0
  49. package/caps/providers/README.md +2 -0
  50. package/caps/providers/bunny.net/ProjectDeployment.ts +327 -0
  51. package/caps/providers/bunny.net/api-pull.test.ts +319 -0
  52. package/caps/providers/bunny.net/api-pull.ts +164 -0
  53. package/caps/providers/bunny.net/api-storage.test.ts +168 -0
  54. package/caps/providers/bunny.net/api-storage.ts +248 -0
  55. package/caps/providers/bunny.net/api.ts +95 -0
  56. package/caps/providers/dynadot.com/ProjectDeployment.ts +202 -0
  57. package/caps/providers/dynadot.com/api-domains.test.ts +224 -0
  58. package/caps/providers/dynadot.com/api-domains.ts +169 -0
  59. package/caps/providers/dynadot.com/api-restful-v1.test.ts +190 -0
  60. package/caps/providers/dynadot.com/api-restful-v1.ts +94 -0
  61. package/caps/providers/dynadot.com/api-restful-v2.test.ts +200 -0
  62. package/caps/providers/dynadot.com/api-restful-v2.ts +94 -0
  63. package/caps/providers/git-scm.com/ProjectPublishing.ts +654 -0
  64. package/caps/providers/github.com/ProjectPublishing.ts +118 -0
  65. package/caps/providers/github.com/api.ts +115 -0
  66. package/caps/providers/npmjs.com/ProjectPublishing.ts +536 -0
  67. package/caps/providers/semver.org/ProjectPublishing.ts +286 -0
  68. package/caps/providers/vercel.com/ProjectDeployment.ts +326 -0
  69. package/caps/providers/vercel.com/api.test.ts +67 -0
  70. package/caps/providers/vercel.com/api.ts +132 -0
  71. package/caps/providers/vercel.com/bun.lock +194 -0
  72. package/caps/providers/vercel.com/package.json +10 -0
  73. package/caps/providers/vercel.com/project.test.ts +108 -0
  74. package/caps/providers/vercel.com/project.ts +150 -0
  75. package/caps/providers/vercel.com/tsconfig.json +28 -0
  76. package/docs/Overview.drawio +248 -0
  77. package/docs/Overview.svg +4 -0
  78. package/lib/crypto.ts +53 -0
  79. package/lib/key.ts +365 -0
  80. package/lib/schema-console-renderer.ts +181 -0
  81. package/lib/schema-resolver.ts +349 -0
  82. package/lib/ucan.ts +137 -0
  83. package/package.json +101 -0
  84. package/structs/HomeRegistry.ts +55 -0
  85. package/structs/HomeRegistryConfig.ts +56 -0
  86. package/structs/ProjectCatalogsConfig.ts +53 -0
  87. package/structs/ProjectDeploymentConfig.ts +56 -0
  88. package/structs/ProjectDeploymentFact.ts +106 -0
  89. package/structs/ProjectPublishingFact.ts +68 -0
  90. package/structs/ProjectRack.ts +51 -0
  91. package/structs/ProjectRackConfig.ts +56 -0
  92. package/structs/RepositoryOriginDescriptor.ts +51 -0
  93. package/structs/RootKeyConfig.ts +64 -0
  94. package/structs/SigningKeyConfig.ts +64 -0
  95. package/structs/Workspace.ts +56 -0
  96. package/structs/WorkspaceCatalogs.ts +56 -0
  97. package/structs/WorkspaceCliConfig.ts +53 -0
  98. package/structs/WorkspaceConfig.ts +64 -0
  99. package/structs/WorkspaceConfigFile.ts +50 -0
  100. package/structs/WorkspaceConfigFileMeta.ts +70 -0
  101. package/structs/WorkspaceKey.ts +55 -0
  102. package/structs/WorkspaceKeyConfig.ts +56 -0
  103. package/structs/WorkspaceMappingsConfig.ts +56 -0
  104. package/structs/WorkspaceProject.ts +104 -0
  105. package/structs/WorkspaceProjectsConfig.ts +67 -0
  106. package/structs/WorkspacePublishingConfig.ts +65 -0
  107. package/structs/WorkspaceShellConfig.ts +83 -0
  108. package/structs/providers/README.md +2 -0
  109. package/structs/providers/bunny.net/PullZoneFact.ts +55 -0
  110. package/structs/providers/bunny.net/PullZoneListFact.ts +55 -0
  111. package/structs/providers/bunny.net/StorageZoneFact.ts +55 -0
  112. package/structs/providers/bunny.net/StorageZoneListFact.ts +55 -0
  113. package/structs/providers/bunny.net/WorkspaceConnectionConfig.ts +43 -0
  114. package/structs/providers/dynadot.com/DomainFact.ts +46 -0
  115. package/structs/providers/dynadot.com/WorkspaceConnectionConfig.ts +54 -0
  116. package/structs/providers/git-scm.com/ProjectPublishingFact.ts +46 -0
  117. package/structs/providers/github.com/ProjectPublishingFact.ts +46 -0
  118. package/structs/providers/github.com/WorkspaceConnectionConfig.ts +43 -0
  119. package/structs/providers/npmjs.com/ProjectPublishingFact.ts +46 -0
  120. package/structs/providers/vercel.com/ProjectDeploymentFact.ts +55 -0
  121. package/structs/providers/vercel.com/WorkspaceConnectionConfig.ts +49 -0
  122. package/tests/01-Lifecycle/main.test.ts +173 -0
  123. package/tsconfig.json +28 -0
  124. package/workspace-rt.ts +134 -0
  125. 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'