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
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'
|