@stream44.studio/t44 0.4.0-rc.24
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.yaml +12 -0
- package/.github/workflows/gordian-open-integrity.yaml +13 -0
- package/.github/workflows/test.yaml +31 -0
- package/.o/GordianOpenIntegrity-CurrentLifehash.svg +1026 -0
- package/.o/GordianOpenIntegrity-InceptionLifehash.svg +1026 -0
- package/.o/GordianOpenIntegrity.yaml +21 -0
- package/.o/assets/Hero-Terminal44-v0.jpeg +0 -0
- package/.o/stream44.studio/assets/Icon-v1.svg +1170 -0
- package/.repo-identifier +1 -0
- package/DCO.md +34 -0
- package/LICENSE.txt +186 -0
- package/README.md +189 -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 +57 -0
- package/caps/HomeRegistry.ts +319 -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 +426 -0
- package/caps/ProjectDevelopment.ts +257 -0
- package/caps/ProjectPublishing.ts +654 -0
- package/caps/ProjectPulling.ts +234 -0
- package/caps/ProjectRack.ts +155 -0
- package/caps/ProjectRepository.ts +332 -0
- package/caps/ProjectTest.ts +251 -0
- package/caps/ProjectTestLib.ts +257 -0
- package/caps/RootKey.ts +219 -0
- package/caps/SigningKey.ts +243 -0
- package/caps/TaskWorkflow.ts +192 -0
- package/caps/WorkspaceCli.ts +448 -0
- package/caps/WorkspaceConfig.ts +268 -0
- package/caps/WorkspaceConfig.yaml +87 -0
- package/caps/WorkspaceConfigFile.ts +902 -0
- package/caps/WorkspaceConnection.ts +329 -0
- package/caps/WorkspaceEntityConfig.ts +78 -0
- package/caps/WorkspaceEntityConfig.v0.ts +77 -0
- package/caps/WorkspaceEntityFact.ts +218 -0
- package/caps/WorkspaceInfo.ts +619 -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 +430 -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/patterns/README.md +2 -0
- package/caps/patterns/git-scm.com/ProjectPublishing.ts +507 -0
- package/caps/patterns/semver.org/ProjectPublishing.ts +458 -0
- package/docs/Overview.drawio +248 -0
- package/docs/Overview.svg +4 -0
- package/examples/01-Lifecycle/main.test.ts +223 -0
- package/lib/crypto.ts +53 -0
- package/lib/key.ts +381 -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 +91 -0
- package/standalone-rt.test.ts +150 -0
- package/standalone-rt.ts +140 -0
- package/structs/HomeRegistry.ts +55 -0
- package/structs/HomeRegistryConfig.ts +60 -0
- package/structs/ProjectCatalogsConfig.ts +53 -0
- package/structs/ProjectDeploymentConfig.ts +56 -0
- package/structs/ProjectDeploymentFact.ts +106 -0
- package/structs/ProjectPublishingConfig.ts +78 -0
- package/structs/ProjectPublishingFact.ts +68 -0
- package/structs/ProjectPullingConfig.ts +52 -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/WorkspaceShellConfig.ts +83 -0
- package/structs/patterns/README.md +2 -0
- package/structs/patterns/git-scm.com/ProjectPublishingFact.ts +46 -0
- package/tsconfig.json +33 -0
- package/workspace-rt.ts +152 -0
- package/workspace.yaml +3 -0
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
|
|
2
|
+
import { join } from 'path'
|
|
3
|
+
|
|
4
|
+
// IMPORTANT: Connection config files contain encrypted credentials.
|
|
5
|
+
// NEVER delete these files programmatically. If decryption fails,
|
|
6
|
+
// log a clear error and exit so the user can investigate. The user must manually delete and re-enter
|
|
7
|
+
// credentials if the workspace key has changed.
|
|
8
|
+
|
|
9
|
+
// Track which connection setup titles and descriptions have been shown
|
|
10
|
+
const shownConnectionTitles = new Set<string>()
|
|
11
|
+
const shownDescriptions = new Set<string>()
|
|
12
|
+
|
|
13
|
+
// Cache for in-flight getStoredConfig promises to prevent parallel decryption race conditions
|
|
14
|
+
const storedConfigCache = new Map<string, Promise<Record<string, any> | null>>()
|
|
15
|
+
|
|
16
|
+
// TODO: Remove after all workspaces have been migrated.
|
|
17
|
+
// Maps new capsule name prefixes to old capsule name prefixes for credential file migration.
|
|
18
|
+
const structRelocations: Record<string, string> = {
|
|
19
|
+
'@stream44.studio/t44-bunny.net/structs/': '@stream44.studio/t44/structs/providers/bunny.net/',
|
|
20
|
+
'@stream44.studio/t44-vercel.com/structs/': '@stream44.studio/t44/structs/providers/vercel.com/',
|
|
21
|
+
'@stream44.studio/t44-github.com/structs/': '@stream44.studio/t44/structs/providers/github.com/',
|
|
22
|
+
'@stream44.studio/t44-dynadot.com/structs/': '@stream44.studio/t44/structs/providers/dynadot.com/',
|
|
23
|
+
'@stream44.studio/t44-npmjs.com/structs/': '@stream44.studio/t44/structs/providers/npmjs.com/',
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function capsule({
|
|
27
|
+
encapsulate,
|
|
28
|
+
CapsulePropertyTypes,
|
|
29
|
+
makeImportStack
|
|
30
|
+
}: {
|
|
31
|
+
encapsulate: any
|
|
32
|
+
CapsulePropertyTypes: any
|
|
33
|
+
makeImportStack: any
|
|
34
|
+
}) {
|
|
35
|
+
return encapsulate({
|
|
36
|
+
'#@stream44.studio/encapsulate/spine-contracts/CapsuleSpineContract.v0': {
|
|
37
|
+
'#@stream44.studio/encapsulate/structs/Capsule': {},
|
|
38
|
+
'#': {
|
|
39
|
+
Home: {
|
|
40
|
+
type: CapsulePropertyTypes.Mapping,
|
|
41
|
+
value: '@stream44.studio/t44/caps/Home'
|
|
42
|
+
},
|
|
43
|
+
WorkspaceConfig: {
|
|
44
|
+
type: CapsulePropertyTypes.Mapping,
|
|
45
|
+
value: '@stream44.studio/t44/caps/WorkspaceConfig'
|
|
46
|
+
},
|
|
47
|
+
WorkspacePrompt: {
|
|
48
|
+
type: CapsulePropertyTypes.Mapping,
|
|
49
|
+
value: '@stream44.studio/t44/caps/WorkspacePrompt'
|
|
50
|
+
},
|
|
51
|
+
WorkspaceKey: {
|
|
52
|
+
type: CapsulePropertyTypes.Mapping,
|
|
53
|
+
value: '@stream44.studio/t44/caps/WorkspaceKey'
|
|
54
|
+
},
|
|
55
|
+
HomeRegistry: {
|
|
56
|
+
type: CapsulePropertyTypes.Mapping,
|
|
57
|
+
value: '@stream44.studio/t44/caps/HomeRegistry'
|
|
58
|
+
},
|
|
59
|
+
JsonSchema: {
|
|
60
|
+
type: CapsulePropertyTypes.Mapping,
|
|
61
|
+
value: '@stream44.studio/t44/caps/JsonSchemas'
|
|
62
|
+
},
|
|
63
|
+
RegisterSchemas: {
|
|
64
|
+
type: CapsulePropertyTypes.StructInit,
|
|
65
|
+
value: async function (this: any): Promise<void> {
|
|
66
|
+
if (this.schema?.schema) {
|
|
67
|
+
const version = this.schemaMinorVersion || '0'
|
|
68
|
+
await this.JsonSchema.registerSchema(this.capsuleName, this.schema.schema, version)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
getFilepath: {
|
|
73
|
+
type: CapsulePropertyTypes.Function,
|
|
74
|
+
value: async function (this: any): Promise<string> {
|
|
75
|
+
const registryDir = await this.Home.registryDir
|
|
76
|
+
const workspaceConfig = await this.WorkspaceConfig.config
|
|
77
|
+
const workspaceConfigStruct = workspaceConfig?.['#@stream44.studio/t44/structs/WorkspaceConfig'] || {}
|
|
78
|
+
const workspaceName = workspaceConfigStruct.name
|
|
79
|
+
const connectionType = this.capsuleName.replace(/\//g, '~')
|
|
80
|
+
return join(registryDir, '@t44.sh~t44~caps~WorkspaceConnection', workspaceName, `${connectionType}.json`)
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
getRelativeFilepath: {
|
|
84
|
+
type: CapsulePropertyTypes.Function,
|
|
85
|
+
value: async function (this: any): Promise<string> {
|
|
86
|
+
const fullPath = await this.getFilepath()
|
|
87
|
+
return this.Home.relativePath(fullPath)
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
getStoredConfig: {
|
|
91
|
+
type: CapsulePropertyTypes.Function,
|
|
92
|
+
value: async function (this: any): Promise<Record<string, any> | null> {
|
|
93
|
+
const filepath = await this.getFilepath()
|
|
94
|
+
|
|
95
|
+
// TODO: Remove after all workspaces have been migrated.
|
|
96
|
+
// Migrate credential files from old struct paths to new package paths.
|
|
97
|
+
await this._migrateCredentialFile(filepath)
|
|
98
|
+
|
|
99
|
+
// Use cached promise if already in-flight to prevent parallel decryption race conditions
|
|
100
|
+
if (storedConfigCache.has(filepath)) {
|
|
101
|
+
return storedConfigCache.get(filepath)!
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const promise = this._getStoredConfigImpl(filepath)
|
|
105
|
+
storedConfigCache.set(filepath, promise)
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
return await promise
|
|
109
|
+
} finally {
|
|
110
|
+
// Clear cache after completion so next call gets fresh data
|
|
111
|
+
storedConfigCache.delete(filepath)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
// TODO: Remove after all workspaces have been migrated.
|
|
116
|
+
_migrateCredentialFile: {
|
|
117
|
+
type: CapsulePropertyTypes.Function,
|
|
118
|
+
value: async function (this: any, newFilepath: string): Promise<void> {
|
|
119
|
+
const { access, rename, mkdir } = await import('fs/promises')
|
|
120
|
+
const { dirname } = await import('path')
|
|
121
|
+
|
|
122
|
+
// Check if the new file already exists — nothing to migrate
|
|
123
|
+
try {
|
|
124
|
+
await access(newFilepath)
|
|
125
|
+
return
|
|
126
|
+
} catch { }
|
|
127
|
+
|
|
128
|
+
// Determine if this capsule name matches a known relocation
|
|
129
|
+
let oldCapsuleName: string | null = null
|
|
130
|
+
for (const [newPrefix, oldPrefix] of Object.entries(structRelocations)) {
|
|
131
|
+
if (this.capsuleName.startsWith(newPrefix)) {
|
|
132
|
+
oldCapsuleName = oldPrefix + this.capsuleName.slice(newPrefix.length)
|
|
133
|
+
break
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
if (!oldCapsuleName) return
|
|
137
|
+
|
|
138
|
+
// Build old filepath using the same pattern as getFilepath
|
|
139
|
+
const oldConnectionType = oldCapsuleName.replace(/\//g, '~')
|
|
140
|
+
const dir = dirname(newFilepath)
|
|
141
|
+
const oldFilepath = join(dir, `${oldConnectionType}.json`)
|
|
142
|
+
|
|
143
|
+
// Check if old file exists and move it
|
|
144
|
+
try {
|
|
145
|
+
await access(oldFilepath)
|
|
146
|
+
await mkdir(dirname(newFilepath), { recursive: true })
|
|
147
|
+
await rename(oldFilepath, newFilepath)
|
|
148
|
+
} catch { }
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
_getStoredConfigImpl: {
|
|
152
|
+
type: CapsulePropertyTypes.Function,
|
|
153
|
+
value: async function (this: any, filepath: string): Promise<Record<string, any> | null> {
|
|
154
|
+
const { readFile } = await import('fs/promises')
|
|
155
|
+
|
|
156
|
+
try {
|
|
157
|
+
const content = await readFile(filepath, 'utf-8')
|
|
158
|
+
const parsed = JSON.parse(content)
|
|
159
|
+
const config = parsed.config || {}
|
|
160
|
+
|
|
161
|
+
// Handle legacy encryptedConfig format (migrate to per-value encryption)
|
|
162
|
+
if (parsed.encryptedConfig) {
|
|
163
|
+
const decrypted = await this.WorkspaceKey.decryptString(parsed.encryptedConfig)
|
|
164
|
+
const legacyConfig = JSON.parse(decrypted)
|
|
165
|
+
// Re-save with per-value encryption
|
|
166
|
+
await this.setStoredConfig(legacyConfig)
|
|
167
|
+
return legacyConfig
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const result: Record<string, any> = {}
|
|
171
|
+
let needsMigration = false
|
|
172
|
+
|
|
173
|
+
for (const [key, value] of Object.entries(config)) {
|
|
174
|
+
if (typeof value === 'string' && value.startsWith('aes-256-gcm:')) {
|
|
175
|
+
// Encrypted value: <algo>:<keyName>-<did>:<enc value>
|
|
176
|
+
// Also supports legacy format: <algo>:<keyName>:<enc value>
|
|
177
|
+
// Use lastIndexOf since DID contains colons but base64 does not
|
|
178
|
+
const lastColon = value.lastIndexOf(':')
|
|
179
|
+
if (lastColon > 'aes-256-gcm:'.length) {
|
|
180
|
+
const encryptedValue = value.substring(lastColon + 1)
|
|
181
|
+
const keyIdentifier = value.substring('aes-256-gcm:'.length, lastColon)
|
|
182
|
+
try {
|
|
183
|
+
const decrypted = await this.WorkspaceKey.decryptString(encryptedValue)
|
|
184
|
+
result[key] = JSON.parse(decrypted)
|
|
185
|
+
} catch (decryptErr: any) {
|
|
186
|
+
const chalk = (await import('chalk')).default
|
|
187
|
+
let currentKeyId = 'unknown'
|
|
188
|
+
try {
|
|
189
|
+
const keyConfig = await this.WorkspaceKey.ensureKey()
|
|
190
|
+
const did = await this.WorkspaceKey.getDid()
|
|
191
|
+
currentKeyId = `${keyConfig.keyName}-${did}`
|
|
192
|
+
} catch { }
|
|
193
|
+
console.error(chalk.red(`\n┌─────────────────────────────────────────────────────────────────┐`))
|
|
194
|
+
console.error(chalk.red(`│ ✗ Connection Credential Decryption Failed │`))
|
|
195
|
+
console.error(chalk.red(`├─────────────────────────────────────────────────────────────────┤`))
|
|
196
|
+
console.error(chalk.red(`│ │`))
|
|
197
|
+
console.error(chalk.red(`│ Cannot decrypt '${key}' in ${this.capsuleName}`))
|
|
198
|
+
console.error(chalk.red(`│ │`))
|
|
199
|
+
console.error(chalk.red(`│ This credential was encrypted with a different workspace key. │`))
|
|
200
|
+
console.error(chalk.red(`│ Encrypted with: ${keyIdentifier}`))
|
|
201
|
+
console.error(chalk.red(`│ Current key: ${currentKeyId}`))
|
|
202
|
+
console.error(chalk.red(`│ │`))
|
|
203
|
+
console.error(chalk.red(`├─────────────────────────────────────────────────────────────────┤`))
|
|
204
|
+
console.error(chalk.red(`│ To fix, delete the connection config and re-enter credentials: │`))
|
|
205
|
+
console.error(chalk.red(`│ │`))
|
|
206
|
+
console.error(chalk.red(`│ rm ${filepath}`))
|
|
207
|
+
console.error(chalk.red(`│ │`))
|
|
208
|
+
console.error(chalk.red(`└─────────────────────────────────────────────────────────────────┘\n`))
|
|
209
|
+
process.exit(1)
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
} else {
|
|
213
|
+
// Plain text value - needs migration
|
|
214
|
+
result[key] = value
|
|
215
|
+
needsMigration = true
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Auto-migrate plain text values to encrypted
|
|
220
|
+
if (needsMigration) {
|
|
221
|
+
await this.setStoredConfig(result)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return Object.keys(result).length > 0 ? result : null
|
|
225
|
+
} catch (err: any) {
|
|
226
|
+
// If file doesn't exist, that's normal - user hasn't configured this provider yet
|
|
227
|
+
if (err?.code === 'ENOENT') {
|
|
228
|
+
return null
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// For other errors (permission issues, malformed JSON, etc.), log and exit
|
|
232
|
+
const chalk = (await import('chalk')).default
|
|
233
|
+
console.error(chalk.red(`\n\u2717 Failed to read connection config for '${this.capsuleName}'\n`))
|
|
234
|
+
console.error(chalk.red(` File: ${filepath}`))
|
|
235
|
+
console.error(chalk.red(` Error: ${err?.message || err}\n`))
|
|
236
|
+
process.exit(1)
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
},
|
|
240
|
+
setStoredConfig: {
|
|
241
|
+
type: CapsulePropertyTypes.Function,
|
|
242
|
+
value: async function (this: any, config: Record<string, any>): Promise<void> {
|
|
243
|
+
const { mkdir, writeFile } = await import('fs/promises')
|
|
244
|
+
const { dirname } = await import('path')
|
|
245
|
+
const filepath = await this.getFilepath()
|
|
246
|
+
const dir = dirname(filepath)
|
|
247
|
+
|
|
248
|
+
await mkdir(dir, { recursive: true })
|
|
249
|
+
|
|
250
|
+
// Ensure workspace key exists and get key name + DID
|
|
251
|
+
const { keyName } = await this.WorkspaceKey.ensureKey()
|
|
252
|
+
const did = await this.WorkspaceKey.getDid()
|
|
253
|
+
|
|
254
|
+
// Encrypt each value separately with prefix format
|
|
255
|
+
const encryptedConfig: Record<string, string> = {}
|
|
256
|
+
for (const [key, value] of Object.entries(config)) {
|
|
257
|
+
const valueJson = JSON.stringify(value)
|
|
258
|
+
const encrypted = await this.WorkspaceKey.encryptString(valueJson)
|
|
259
|
+
// Format: <algo>:<keyName>-<did>:<enc value>
|
|
260
|
+
encryptedConfig[key] = `aes-256-gcm:${keyName}-${did}:${encrypted}`
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const output = {
|
|
264
|
+
config: encryptedConfig
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
await writeFile(filepath, JSON.stringify(output, null, 4), { mode: 0o600 })
|
|
268
|
+
}
|
|
269
|
+
},
|
|
270
|
+
getConfigValue: {
|
|
271
|
+
type: CapsulePropertyTypes.Function,
|
|
272
|
+
value: async function (this: any, key: string): Promise<any> {
|
|
273
|
+
const storedConfig = await this.getStoredConfig() || {}
|
|
274
|
+
|
|
275
|
+
if (storedConfig[key] !== undefined) {
|
|
276
|
+
return storedConfig[key]
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Value not set, need to prompt user
|
|
280
|
+
const propertySchema = this.schema?.schema?.properties?.[key]
|
|
281
|
+
if (!propertySchema) {
|
|
282
|
+
throw new Error(`No schema defined for config key "${key}" in ${this.capsuleName} connection config`)
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Create promptFactId for deduplication
|
|
286
|
+
const promptFactId = `${this.capsuleName}:${key}`
|
|
287
|
+
|
|
288
|
+
// Show title once per capsuleName
|
|
289
|
+
const chalk = (await import('chalk')).default
|
|
290
|
+
if (!shownConnectionTitles.has(this.capsuleName)) {
|
|
291
|
+
console.log(chalk.cyan(`\n🔑 ${this.capsuleName} Connection Setup\n`))
|
|
292
|
+
shownConnectionTitles.add(this.capsuleName)
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Show description once per promptFactId
|
|
296
|
+
if (propertySchema.description && !shownDescriptions.has(promptFactId)) {
|
|
297
|
+
console.log(chalk.gray(` ${propertySchema.description}\n`))
|
|
298
|
+
shownDescriptions.add(promptFactId)
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const value = await this.WorkspacePrompt.input({
|
|
302
|
+
message: `${propertySchema.title || key}:`,
|
|
303
|
+
validate: (input: string) => {
|
|
304
|
+
if (!input || input.trim().length === 0) {
|
|
305
|
+
return `${propertySchema.title || key} cannot be empty`
|
|
306
|
+
}
|
|
307
|
+
return true
|
|
308
|
+
},
|
|
309
|
+
promptFactId
|
|
310
|
+
})
|
|
311
|
+
|
|
312
|
+
// Store the value
|
|
313
|
+
storedConfig[key] = value
|
|
314
|
+
await this.setStoredConfig(storedConfig)
|
|
315
|
+
|
|
316
|
+
console.log(chalk.green(`\n ✓ ${propertySchema.title || key} saved to connection config\n`))
|
|
317
|
+
|
|
318
|
+
return value
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}, {
|
|
324
|
+
importMeta: import.meta,
|
|
325
|
+
importStack: makeImportStack(),
|
|
326
|
+
capsuleName: capsule['#'],
|
|
327
|
+
})
|
|
328
|
+
}
|
|
329
|
+
capsule['#'] = '@stream44.studio/t44/caps/WorkspaceConnection'
|
|
@@ -0,0 +1,78 @@
|
|
|
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
|
+
'#': {
|
|
15
|
+
WorkspaceConfig: {
|
|
16
|
+
type: CapsulePropertyTypes.Mapping,
|
|
17
|
+
value: '@stream44.studio/t44/caps/WorkspaceConfig'
|
|
18
|
+
},
|
|
19
|
+
JsonSchema: {
|
|
20
|
+
type: CapsulePropertyTypes.Mapping,
|
|
21
|
+
value: '@stream44.studio/t44/caps/JsonSchemas'
|
|
22
|
+
},
|
|
23
|
+
config: {
|
|
24
|
+
type: CapsulePropertyTypes.GetterFunction,
|
|
25
|
+
value: async function (this: any): Promise<void> {
|
|
26
|
+
|
|
27
|
+
const config = await this.WorkspaceConfig.config
|
|
28
|
+
|
|
29
|
+
const configKey = '#' + this.capsuleName
|
|
30
|
+
|
|
31
|
+
const entityConfig = config[configKey] || undefined
|
|
32
|
+
|
|
33
|
+
return entityConfig
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
setConfigValue: {
|
|
37
|
+
type: CapsulePropertyTypes.Function,
|
|
38
|
+
value: async function (this: any, path: string[], value: any): Promise<void> {
|
|
39
|
+
|
|
40
|
+
const configKey = '#' + this.capsuleName
|
|
41
|
+
|
|
42
|
+
const now = new Date().toISOString()
|
|
43
|
+
await this.WorkspaceConfig.ensureEntityTimestamps(
|
|
44
|
+
{ entityName: this.capsuleName },
|
|
45
|
+
configKey, now
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
const changed = await this.WorkspaceConfig.setConfigValueForEntity(
|
|
49
|
+
{ entityName: this.capsuleName, schema: this.schema },
|
|
50
|
+
[configKey, ...path], value
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
if (changed) {
|
|
54
|
+
await this.WorkspaceConfig.setConfigValueForEntity(
|
|
55
|
+
{ entityName: this.capsuleName, schema: this.schema },
|
|
56
|
+
[configKey, 'updatedAt'], new Date().toISOString()
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
RegisterSchemas: {
|
|
62
|
+
type: CapsulePropertyTypes.StructInit,
|
|
63
|
+
value: async function (this: any): Promise<void> {
|
|
64
|
+
if (this.schema?.schema) {
|
|
65
|
+
const version = this.schemaMinorVersion || '0'
|
|
66
|
+
await this.JsonSchema.registerSchema(this.capsuleName, this.schema.schema, version)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}, {
|
|
73
|
+
importMeta: import.meta,
|
|
74
|
+
importStack: makeImportStack(),
|
|
75
|
+
capsuleName: capsule['#'],
|
|
76
|
+
})
|
|
77
|
+
}
|
|
78
|
+
capsule['#'] = '@stream44.studio/t44/caps/WorkspaceEntityConfig'
|
|
@@ -0,0 +1,77 @@
|
|
|
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
|
+
'#': {
|
|
15
|
+
WorkspaceConfig: {
|
|
16
|
+
type: CapsulePropertyTypes.Mapping,
|
|
17
|
+
value: '@stream44.studio/t44/caps/WorkspaceConfig.v0'
|
|
18
|
+
},
|
|
19
|
+
JsonSchema: {
|
|
20
|
+
type: CapsulePropertyTypes.Mapping,
|
|
21
|
+
value: '@stream44.studio/t44/caps/JsonSchemas.v0'
|
|
22
|
+
},
|
|
23
|
+
config: {
|
|
24
|
+
type: CapsulePropertyTypes.GetterFunction,
|
|
25
|
+
value: async function (this: any): Promise<void> {
|
|
26
|
+
|
|
27
|
+
const config = await this.WorkspaceConfig.config
|
|
28
|
+
|
|
29
|
+
const configKey = '#' + this.capsuleName
|
|
30
|
+
|
|
31
|
+
const entityConfig = config[configKey] || undefined
|
|
32
|
+
|
|
33
|
+
return entityConfig
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
setConfigValue: {
|
|
37
|
+
type: CapsulePropertyTypes.Function,
|
|
38
|
+
value: async function (this: any, path: string[], value: any): Promise<void> {
|
|
39
|
+
|
|
40
|
+
const configKey = '#' + this.capsuleName
|
|
41
|
+
|
|
42
|
+
await this.WorkspaceConfig.setConfigValueForEntity(
|
|
43
|
+
{ entityName: this.capsuleName, schema: this.schema },
|
|
44
|
+
[configKey, 'createdAt'], new Date().toISOString(), { ifAbsent: true }
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
const changed = await this.WorkspaceConfig.setConfigValueForEntity(
|
|
48
|
+
{ entityName: this.capsuleName, schema: this.schema },
|
|
49
|
+
[configKey, ...path], value
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
if (changed) {
|
|
53
|
+
await this.WorkspaceConfig.setConfigValueForEntity(
|
|
54
|
+
{ entityName: this.capsuleName, schema: this.schema },
|
|
55
|
+
[configKey, 'updatedAt'], new Date().toISOString()
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
RegisterSchemas: {
|
|
61
|
+
type: CapsulePropertyTypes.StructInit,
|
|
62
|
+
value: async function (this: any): Promise<void> {
|
|
63
|
+
if (this.schema?.schema) {
|
|
64
|
+
const version = this.schemaMinorVersion || '0'
|
|
65
|
+
await this.JsonSchema.registerSchema(this.capsuleName, this.schema.schema, version)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}, {
|
|
72
|
+
importMeta: import.meta,
|
|
73
|
+
importStack: makeImportStack(),
|
|
74
|
+
capsuleName: capsule['#'],
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
capsule['#'] = '@stream44.studio/t44/caps/WorkspaceEntityConfig.v0'
|