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
package/caps/Home.ts ADDED
@@ -0,0 +1,51 @@
1
+
2
+ import { join } from 'path'
3
+ import { homedir } from 'os'
4
+
5
+ export async function capsule({
6
+ encapsulate,
7
+ CapsulePropertyTypes,
8
+ makeImportStack
9
+ }: {
10
+ encapsulate: any
11
+ CapsulePropertyTypes: any
12
+ makeImportStack: any
13
+ }) {
14
+ return encapsulate({
15
+ '#@stream44.studio/encapsulate/spine-contracts/CapsuleSpineContract.v0': {
16
+ '#@stream44.studio/encapsulate/structs/Capsule': {},
17
+ '#': {
18
+ homeDir: {
19
+ type: CapsulePropertyTypes.GetterFunction,
20
+ value: async function (this: any): Promise<string> {
21
+ return process.env.T44_HOME_DIR || homedir()
22
+ }
23
+ },
24
+ sshDir: {
25
+ type: CapsulePropertyTypes.GetterFunction,
26
+ value: async function (this: any): Promise<string> {
27
+ return join(await this.homeDir, '.ssh')
28
+ }
29
+ },
30
+ registryDir: {
31
+ type: CapsulePropertyTypes.GetterFunction,
32
+ value: async function (this: any): Promise<string> {
33
+ return join(await this.homeDir, '.o/workspace.foundation')
34
+ }
35
+ },
36
+ relativePath: {
37
+ type: CapsulePropertyTypes.Function,
38
+ value: async function (this: any, fullPath: string): Promise<string> {
39
+ const home = await this.homeDir
40
+ return fullPath.startsWith(home + '/') ? fullPath.slice(home.length + 1) : fullPath
41
+ }
42
+ },
43
+ }
44
+ }
45
+ }, {
46
+ importMeta: import.meta,
47
+ importStack: makeImportStack(),
48
+ capsuleName: capsule['#'],
49
+ })
50
+ }
51
+ capsule['#'] = 't44/caps/Home'
@@ -0,0 +1,313 @@
1
+ export async function capsule({
2
+ encapsulate,
3
+ CapsulePropertyTypes,
4
+ makeImportStack
5
+ }: {
6
+ encapsulate: any
7
+ CapsulePropertyTypes: any
8
+ makeImportStack: any
9
+ }) {
10
+ return encapsulate({
11
+ '#@stream44.studio/encapsulate/spine-contracts/CapsuleSpineContract.v0': {
12
+ '#@stream44.studio/encapsulate/structs/Capsule': {},
13
+ '#t44/structs/HomeRegistryConfig': {
14
+ as: '$HomeRegistryConfig'
15
+ },
16
+ '#t44/structs/HomeRegistry': {
17
+ as: '$HomeRegistry'
18
+ },
19
+ '#t44/structs/Workspace': {
20
+ as: '$Workspace'
21
+ },
22
+ '#t44/structs/WorkspaceKey': {
23
+ as: '$WorkspaceKey'
24
+ },
25
+ '#t44/structs/ProjectRack': {
26
+ as: '$ProjectRack'
27
+ },
28
+ '#t44/structs/WorkspaceCatalogs': {
29
+ as: '$WorkspaceCatalogs'
30
+ },
31
+ '#': {
32
+ Home: {
33
+ type: CapsulePropertyTypes.Mapping,
34
+ value: 't44/caps/Home'
35
+ },
36
+ WorkspacePrompt: {
37
+ type: CapsulePropertyTypes.Mapping,
38
+ value: 't44/caps/WorkspacePrompt'
39
+ },
40
+ rootDir: {
41
+ type: CapsulePropertyTypes.GetterFunction,
42
+ value: async function (this: any): Promise<string> {
43
+ const config = await this.$HomeRegistryConfig.config
44
+ if (!config?.rootDir) {
45
+ throw new Error('Home registry rootDir is not configured. Run ensureRootDir() first.')
46
+ }
47
+ return config.rootDir
48
+ }
49
+ },
50
+ ensureRootDir: {
51
+ type: CapsulePropertyTypes.Function,
52
+ value: async function (this: any): Promise<string> {
53
+ const { stat, mkdir } = await import('fs/promises')
54
+ const chalk = (await import('chalk')).default
55
+
56
+ const config = await this.$HomeRegistryConfig.config
57
+ const defaultDir = await this.Home.registryDir
58
+
59
+ let chosenDir: string
60
+
61
+ if (config?.rootDir) {
62
+ // Validate that the directory exists
63
+ try {
64
+ const s = await stat(config.rootDir)
65
+ if (!s.isDirectory()) {
66
+ throw new Error(`Home registry path '${config.rootDir}' exists but is not a directory.`)
67
+ }
68
+ } catch (error: any) {
69
+ if (error.code === 'ENOENT') {
70
+ throw new Error(`Home registry directory '${config.rootDir}' does not exist. Please create it or reconfigure.`)
71
+ }
72
+ throw error
73
+ }
74
+ chosenDir = config.rootDir
75
+ } else {
76
+ // rootDir not set — prompt user
77
+ console.log(chalk.cyan(`\n🏠 Home Registry Setup\n`))
78
+ console.log(chalk.gray(' The home registry is the place in your home directory that keeps'))
79
+ console.log(chalk.gray(' details about your workspaces and projects.'))
80
+ console.log('')
81
+
82
+ chosenDir = await this.WorkspacePrompt.input({
83
+ message: 'Enter the home registry directory:',
84
+ defaultValue: defaultDir,
85
+ validate: (input: string) => {
86
+ if (!input || input.trim().length === 0) {
87
+ return 'Directory path cannot be empty'
88
+ }
89
+ return true
90
+ }
91
+ })
92
+
93
+ // Check if directory exists
94
+ let dirExists = false
95
+ try {
96
+ const s = await stat(chosenDir)
97
+ dirExists = s.isDirectory()
98
+ } catch {
99
+ // Does not exist
100
+ }
101
+
102
+ if (dirExists) {
103
+ const confirmed = await this.WorkspacePrompt.confirm({
104
+ message: `Directory '${chosenDir}' already exists. Use this existing registry as the home registry for your workspace?`,
105
+ defaultValue: true
106
+ })
107
+
108
+ if (!confirmed) {
109
+ console.log(chalk.red('\n\nABORTED\n'))
110
+ process.exit(0)
111
+ }
112
+ } else {
113
+ // Create the directory
114
+ await mkdir(chosenDir, { recursive: true })
115
+ console.log(chalk.green(`\n ✓ Created home registry directory: ${chosenDir}\n`))
116
+ }
117
+
118
+ await this.$HomeRegistryConfig.setConfigValue(['rootDir'], chosenDir)
119
+ }
120
+
121
+ // Ensure registry.json exists via the HomeRegistry struct
122
+ let registryData = await this.$HomeRegistry.get('registry')
123
+
124
+ if (!registryData) {
125
+ // registry.json does not exist — generate identity
126
+ console.log(chalk.cyan(`\n Generating home registry identity...\n`))
127
+
128
+ const { generateKeypair } = await import('../lib/ucan.js')
129
+ const { did, privateKey } = await generateKeypair()
130
+
131
+ registryData = {
132
+ did,
133
+ privateKey,
134
+ createdAt: new Date().toISOString(),
135
+ rootDir: chosenDir
136
+ }
137
+
138
+ const registryFilePath = await this.$HomeRegistry.set('registry', registryData)
139
+
140
+ console.log(chalk.green(` ✓ Registry identity saved to:`))
141
+ console.log(chalk.green(` ${registryFilePath}`))
142
+ console.log(chalk.green(` ✓ DID: ${registryData.did}\n`))
143
+ }
144
+
145
+ // Ensure rootDir is set in registry data
146
+ if (!registryData.rootDir || registryData.rootDir !== chosenDir) {
147
+ registryData.rootDir = chosenDir
148
+ await this.$HomeRegistry.set('registry', registryData)
149
+ }
150
+
151
+ // Ensure identifier is set in config
152
+ if (!config?.identifier || config.identifier !== registryData.did) {
153
+ await this.$HomeRegistryConfig.setConfigValue(['identifier'], registryData.did)
154
+ }
155
+
156
+ return chosenDir
157
+ }
158
+ },
159
+ getRegistryPath: {
160
+ type: CapsulePropertyTypes.Function,
161
+ value: async function (this: any): Promise<string> {
162
+ return this.$HomeRegistry.getPath('registry')
163
+ }
164
+ },
165
+ getRegistry: {
166
+ type: CapsulePropertyTypes.Function,
167
+ value: async function (this: any): Promise<any | null> {
168
+ return this.$HomeRegistry.get('registry')
169
+ }
170
+ },
171
+ getWorkspace: {
172
+ type: CapsulePropertyTypes.Function,
173
+ value: async function (this: any, name: string): Promise<any | null> {
174
+ return this.$Workspace.get(name)
175
+ }
176
+ },
177
+ setWorkspace: {
178
+ type: CapsulePropertyTypes.Function,
179
+ value: async function (this: any, name: string, data: { did: string; privateKey: string; createdAt: string; workspaceRootDir: string }): Promise<string> {
180
+ return this.$Workspace.set(name, data)
181
+ }
182
+ },
183
+ getWorkspacePath: {
184
+ type: CapsulePropertyTypes.Function,
185
+ value: async function (this: any, name: string): Promise<string> {
186
+ return this.$Workspace.getPath(name)
187
+ }
188
+ },
189
+ listKeys: {
190
+ type: CapsulePropertyTypes.Function,
191
+ value: async function (this: any): Promise<Array<{ name: string; did: string; createdAt?: string }>> {
192
+ const { readdir, readFile } = await import('fs/promises')
193
+ const { join } = await import('path')
194
+ const rootDir = await this.rootDir
195
+ const keyDir = join(rootDir, '@t44.sh~t44~structs~WorkspaceKey')
196
+ try {
197
+ const files = await readdir(keyDir)
198
+ const keys: Array<{ name: string; did: string; createdAt?: string }> = []
199
+ for (const file of files) {
200
+ if (file.endsWith('.json')) {
201
+ try {
202
+ const data = JSON.parse(await readFile(join(keyDir, file), 'utf-8'))
203
+ keys.push({
204
+ name: file.replace(/\.json$/, ''),
205
+ did: data.did || '',
206
+ createdAt: data.createdAt,
207
+ })
208
+ } catch { }
209
+ }
210
+ }
211
+ return keys
212
+ } catch {
213
+ return []
214
+ }
215
+ }
216
+ },
217
+ getKey: {
218
+ type: CapsulePropertyTypes.Function,
219
+ value: async function (this: any, name: string): Promise<any | null> {
220
+ return this.$WorkspaceKey.get(name)
221
+ }
222
+ },
223
+ setKey: {
224
+ type: CapsulePropertyTypes.Function,
225
+ value: async function (this: any, name: string, data: { did: string; privateKey: string; createdAt: string }): Promise<string> {
226
+ return this.$WorkspaceKey.set(name, data)
227
+ }
228
+ },
229
+ getKeyPath: {
230
+ type: CapsulePropertyTypes.Function,
231
+ value: async function (this: any, name: string): Promise<string> {
232
+ return this.$WorkspaceKey.getPath(name)
233
+ }
234
+ },
235
+ keyExists: {
236
+ type: CapsulePropertyTypes.Function,
237
+ value: async function (this: any, name: string): Promise<boolean> {
238
+ return this.$WorkspaceKey.exists(name)
239
+ }
240
+ },
241
+ listRacks: {
242
+ type: CapsulePropertyTypes.Function,
243
+ value: async function (this: any): Promise<Array<{ name: string; did: string; createdAt?: string }>> {
244
+ const { readdir, readFile } = await import('fs/promises')
245
+ const { join } = await import('path')
246
+ const rootDir = await this.rootDir
247
+ const rackDir = join(rootDir, '@t44.sh~t44~structs~ProjectRack')
248
+ try {
249
+ const files = await readdir(rackDir)
250
+ const racks: Array<{ name: string; did: string; createdAt?: string }> = []
251
+ for (const file of files) {
252
+ if (file.endsWith('.json')) {
253
+ try {
254
+ const data = JSON.parse(await readFile(join(rackDir, file), 'utf-8'))
255
+ racks.push({
256
+ name: file.replace(/\.json$/, ''),
257
+ did: data.did || '',
258
+ createdAt: data.createdAt,
259
+ })
260
+ } catch { }
261
+ }
262
+ }
263
+ return racks
264
+ } catch {
265
+ return []
266
+ }
267
+ }
268
+ },
269
+ getRack: {
270
+ type: CapsulePropertyTypes.Function,
271
+ value: async function (this: any, name: string): Promise<any | null> {
272
+ return this.$ProjectRack.get(name)
273
+ }
274
+ },
275
+ setRack: {
276
+ type: CapsulePropertyTypes.Function,
277
+ value: async function (this: any, name: string, data: { did: string; privateKey: string; createdAt: string }): Promise<string> {
278
+ return this.$ProjectRack.set(name, data)
279
+ }
280
+ },
281
+ getRackPath: {
282
+ type: CapsulePropertyTypes.Function,
283
+ value: async function (this: any, name: string): Promise<string> {
284
+ return this.$ProjectRack.getPath(name)
285
+ }
286
+ },
287
+ getCatalog: {
288
+ type: CapsulePropertyTypes.Function,
289
+ value: async function (this: any, name: string): Promise<any | null> {
290
+ return this.$WorkspaceCatalogs.get(name)
291
+ }
292
+ },
293
+ setCatalog: {
294
+ type: CapsulePropertyTypes.Function,
295
+ value: async function (this: any, name: string, data: any): Promise<string> {
296
+ return this.$WorkspaceCatalogs.set(name, data)
297
+ }
298
+ },
299
+ getCatalogPath: {
300
+ type: CapsulePropertyTypes.Function,
301
+ value: async function (this: any, name: string): Promise<string> {
302
+ return this.$WorkspaceCatalogs.getPath(name)
303
+ }
304
+ }
305
+ }
306
+ }
307
+ }, {
308
+ importMeta: import.meta,
309
+ importStack: makeImportStack(),
310
+ capsuleName: capsule['#'],
311
+ })
312
+ }
313
+ capsule['#'] = 't44/caps/HomeRegistry'
@@ -0,0 +1,144 @@
1
+
2
+ import { join, relative, dirname } from 'path'
3
+ import { readFile, writeFile, mkdir, access } from 'fs/promises'
4
+
5
+ export async function capsule({
6
+ encapsulate,
7
+ CapsulePropertyTypes,
8
+ makeImportStack
9
+ }: {
10
+ encapsulate: any
11
+ CapsulePropertyTypes: any
12
+ makeImportStack: any
13
+ }) {
14
+ return encapsulate({
15
+ '#@stream44.studio/encapsulate/spine-contracts/CapsuleSpineContract.v0': {
16
+ '#@stream44.studio/encapsulate/structs/Capsule': {},
17
+ '#t44/structs/HomeRegistryConfig': {
18
+ as: '$HomeRegistryConfig'
19
+ },
20
+ '#': {
21
+ JsonSchema: {
22
+ type: CapsulePropertyTypes.Mapping,
23
+ value: 't44/caps/JsonSchemas'
24
+ },
25
+ RegisterSchemas: {
26
+ type: CapsulePropertyTypes.StructInit,
27
+ value: async function (this: any): Promise<void> {
28
+ if (this.schema?.schema) {
29
+ const version = this.schemaMinorVersion || '0'
30
+ await this.JsonSchema.registerSchema(this.capsuleName, this.schema.schema, version)
31
+ }
32
+ }
33
+ },
34
+ _resolveDir: {
35
+ type: CapsulePropertyTypes.GetterFunction,
36
+ value: async function (this: any): Promise<string> {
37
+ const config = await this.$HomeRegistryConfig.config
38
+ if (!config?.rootDir) {
39
+ throw new Error('Home registry rootDir is not configured. Run ensureRootDir() first.')
40
+ }
41
+ const dirName = this.capsuleName.replace(/\//g, '~')
42
+ return join(config.rootDir, dirName)
43
+ }
44
+ },
45
+ get: {
46
+ type: CapsulePropertyTypes.Function,
47
+ value: async function (this: any, name: string): Promise<any | null> {
48
+ const filePath = join(await this._resolveDir, `${name}.json`)
49
+ let parsed: any
50
+ try {
51
+ parsed = JSON.parse(await readFile(filePath, 'utf-8'))
52
+ } catch {
53
+ return null
54
+ }
55
+
56
+ // Migrate old wrapper format: { $schema, $defs?, SchemaName: { ...data }, ... }
57
+ // to flat format: { $schema, $id, ...data }
58
+ if (parsed && parsed.$schema) {
59
+ const schemaName = this.capsuleName.split('/').pop()?.replace(/\.v\d+$/, '')
60
+ if (schemaName && parsed[schemaName] && typeof parsed[schemaName] === 'object') {
61
+ const innerData = parsed[schemaName]
62
+ // Re-write in new format with $schema and $id
63
+ const version = this.schemaMinorVersion || '0'
64
+ const output = this.schema.wrapWithSchema(innerData, this.capsuleName, version)
65
+ await writeFile(filePath, JSON.stringify(output, null, 2), { mode: 0o600 })
66
+ return innerData
67
+ }
68
+ }
69
+
70
+ // Migrate old relative $schema format to new $schema + $id format
71
+ if (parsed && parsed.$schema && !parsed.$id && typeof parsed.$schema === 'string' && parsed.$schema.includes('/')) {
72
+ const data = { ...parsed }
73
+ delete data.$schema
74
+ // Re-write in new format with $schema and $id
75
+ const version = this.schemaMinorVersion || '0'
76
+ const output = this.schema.wrapWithSchema(data, this.capsuleName, version)
77
+ await writeFile(filePath, JSON.stringify(output, null, 2), { mode: 0o600 })
78
+ return data
79
+ }
80
+
81
+ // Already schema-wrapped (new format) — return data (strip $schema and $id)
82
+ if (parsed && (parsed.$schema || parsed.$id)) {
83
+ const data = { ...parsed }
84
+ delete data.$schema
85
+ delete data.$id
86
+ return data
87
+ }
88
+
89
+ // Raw data — wrap with schema and re-write
90
+ const data = parsed
91
+ if (this.schema?.wrapWithSchema) {
92
+ const version = this.schemaMinorVersion || '0'
93
+ const output = this.schema.wrapWithSchema(data, this.capsuleName, version)
94
+ await writeFile(filePath, JSON.stringify(output, null, 2), { mode: 0o600 })
95
+ }
96
+
97
+ return data
98
+ }
99
+ },
100
+ set: {
101
+ type: CapsulePropertyTypes.Function,
102
+ value: async function (this: any, name: string, data: any): Promise<string> {
103
+ const dir = await this._resolveDir
104
+ const filePath = join(dir, `${name}.json`)
105
+ await mkdir(dir, { recursive: true })
106
+
107
+ if (this.schema?.wrapWithSchema) {
108
+ const version = this.schemaMinorVersion || '0'
109
+ const output = this.schema.wrapWithSchema(data, this.capsuleName, version)
110
+ await writeFile(filePath, JSON.stringify(output, null, 2), { mode: 0o600 })
111
+ } else {
112
+ await writeFile(filePath, JSON.stringify(data, null, 2), { mode: 0o600 })
113
+ }
114
+
115
+ return filePath
116
+ }
117
+ },
118
+ getPath: {
119
+ type: CapsulePropertyTypes.Function,
120
+ value: async function (this: any, name: string): Promise<string> {
121
+ return join(await this._resolveDir, `${name}.json`)
122
+ }
123
+ },
124
+ exists: {
125
+ type: CapsulePropertyTypes.Function,
126
+ value: async function (this: any, name: string): Promise<boolean> {
127
+ const filePath = join(await this._resolveDir, `${name}.json`)
128
+ try {
129
+ await access(filePath)
130
+ return true
131
+ } catch {
132
+ return false
133
+ }
134
+ }
135
+ },
136
+ }
137
+ }
138
+ }, {
139
+ importMeta: import.meta,
140
+ importStack: makeImportStack(),
141
+ capsuleName: capsule['#'],
142
+ })
143
+ }
144
+ capsule['#'] = 't44/caps/HomeRegistryFile'