@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.
Files changed (99) hide show
  1. package/.dco-signatures +9 -0
  2. package/.github/workflows/dco.yaml +12 -0
  3. package/.github/workflows/gordian-open-integrity.yaml +13 -0
  4. package/.github/workflows/test.yaml +31 -0
  5. package/.o/GordianOpenIntegrity-CurrentLifehash.svg +1026 -0
  6. package/.o/GordianOpenIntegrity-InceptionLifehash.svg +1026 -0
  7. package/.o/GordianOpenIntegrity.yaml +21 -0
  8. package/.o/assets/Hero-Terminal44-v0.jpeg +0 -0
  9. package/.o/stream44.studio/assets/Icon-v1.svg +1170 -0
  10. package/.repo-identifier +1 -0
  11. package/DCO.md +34 -0
  12. package/LICENSE.txt +186 -0
  13. package/README.md +189 -0
  14. package/bin/activate +36 -0
  15. package/bin/activate.ts +30 -0
  16. package/bin/postinstall.sh +19 -0
  17. package/bin/shell +27 -0
  18. package/bin/t44 +27 -0
  19. package/caps/ConfigSchemaStruct.ts +55 -0
  20. package/caps/Home.ts +57 -0
  21. package/caps/HomeRegistry.ts +319 -0
  22. package/caps/HomeRegistryFile.ts +144 -0
  23. package/caps/JsonSchemas.ts +220 -0
  24. package/caps/OpenApiSchema.ts +67 -0
  25. package/caps/PackageDescriptor.ts +88 -0
  26. package/caps/ProjectCatalogs.ts +153 -0
  27. package/caps/ProjectDeployment.ts +426 -0
  28. package/caps/ProjectDevelopment.ts +257 -0
  29. package/caps/ProjectPublishing.ts +654 -0
  30. package/caps/ProjectPulling.ts +234 -0
  31. package/caps/ProjectRack.ts +155 -0
  32. package/caps/ProjectRepository.ts +332 -0
  33. package/caps/ProjectTest.ts +251 -0
  34. package/caps/ProjectTestLib.ts +257 -0
  35. package/caps/RootKey.ts +219 -0
  36. package/caps/SigningKey.ts +243 -0
  37. package/caps/TaskWorkflow.ts +192 -0
  38. package/caps/WorkspaceCli.ts +448 -0
  39. package/caps/WorkspaceConfig.ts +268 -0
  40. package/caps/WorkspaceConfig.yaml +87 -0
  41. package/caps/WorkspaceConfigFile.ts +902 -0
  42. package/caps/WorkspaceConnection.ts +329 -0
  43. package/caps/WorkspaceEntityConfig.ts +78 -0
  44. package/caps/WorkspaceEntityConfig.v0.ts +77 -0
  45. package/caps/WorkspaceEntityFact.ts +218 -0
  46. package/caps/WorkspaceInfo.ts +619 -0
  47. package/caps/WorkspaceInit.ts +30 -0
  48. package/caps/WorkspaceKey.ts +338 -0
  49. package/caps/WorkspaceModel.ts +373 -0
  50. package/caps/WorkspaceProjects.ts +636 -0
  51. package/caps/WorkspacePrompt.ts +430 -0
  52. package/caps/WorkspaceShell.sh +39 -0
  53. package/caps/WorkspaceShell.ts +104 -0
  54. package/caps/WorkspaceShell.yaml +64 -0
  55. package/caps/WorkspaceShellCli.ts +109 -0
  56. package/caps/patterns/README.md +2 -0
  57. package/caps/patterns/git-scm.com/ProjectPublishing.ts +507 -0
  58. package/caps/patterns/semver.org/ProjectPublishing.ts +458 -0
  59. package/docs/Overview.drawio +248 -0
  60. package/docs/Overview.svg +4 -0
  61. package/examples/01-Lifecycle/main.test.ts +223 -0
  62. package/lib/crypto.ts +53 -0
  63. package/lib/key.ts +381 -0
  64. package/lib/schema-console-renderer.ts +181 -0
  65. package/lib/schema-resolver.ts +349 -0
  66. package/lib/ucan.ts +137 -0
  67. package/package.json +91 -0
  68. package/standalone-rt.test.ts +150 -0
  69. package/standalone-rt.ts +140 -0
  70. package/structs/HomeRegistry.ts +55 -0
  71. package/structs/HomeRegistryConfig.ts +60 -0
  72. package/structs/ProjectCatalogsConfig.ts +53 -0
  73. package/structs/ProjectDeploymentConfig.ts +56 -0
  74. package/structs/ProjectDeploymentFact.ts +106 -0
  75. package/structs/ProjectPublishingConfig.ts +78 -0
  76. package/structs/ProjectPublishingFact.ts +68 -0
  77. package/structs/ProjectPullingConfig.ts +52 -0
  78. package/structs/ProjectRack.ts +51 -0
  79. package/structs/ProjectRackConfig.ts +56 -0
  80. package/structs/RepositoryOriginDescriptor.ts +51 -0
  81. package/structs/RootKeyConfig.ts +64 -0
  82. package/structs/SigningKeyConfig.ts +64 -0
  83. package/structs/Workspace.ts +56 -0
  84. package/structs/WorkspaceCatalogs.ts +56 -0
  85. package/structs/WorkspaceCliConfig.ts +53 -0
  86. package/structs/WorkspaceConfig.ts +64 -0
  87. package/structs/WorkspaceConfigFile.ts +50 -0
  88. package/structs/WorkspaceConfigFileMeta.ts +70 -0
  89. package/structs/WorkspaceKey.ts +55 -0
  90. package/structs/WorkspaceKeyConfig.ts +56 -0
  91. package/structs/WorkspaceMappingsConfig.ts +56 -0
  92. package/structs/WorkspaceProject.ts +104 -0
  93. package/structs/WorkspaceProjectsConfig.ts +67 -0
  94. package/structs/WorkspaceShellConfig.ts +83 -0
  95. package/structs/patterns/README.md +2 -0
  96. package/structs/patterns/git-scm.com/ProjectPublishingFact.ts +46 -0
  97. package/tsconfig.json +33 -0
  98. package/workspace-rt.ts +152 -0
  99. package/workspace.yaml +3 -0
@@ -0,0 +1,373 @@
1
+
2
+ import { join, relative } from 'path'
3
+ import { readdir } from 'fs/promises'
4
+ import chalk from 'chalk'
5
+ import { Resolver } from '../lib/schema-resolver.js'
6
+ import type { ResolvedEntity } from '../lib/schema-resolver.js'
7
+ import { SchemaConsoleRenderer } from '../lib/schema-console-renderer.js'
8
+
9
+ export async function capsule({
10
+ encapsulate,
11
+ CapsulePropertyTypes,
12
+ makeImportStack
13
+ }: {
14
+ encapsulate: any
15
+ CapsulePropertyTypes: any
16
+ makeImportStack: any
17
+ }) {
18
+ return encapsulate({
19
+ '#@stream44.studio/encapsulate/spine-contracts/CapsuleSpineContract.v0': {
20
+ '#@stream44.studio/encapsulate/structs/Capsule': {},
21
+ '#@stream44.studio/t44/structs/WorkspaceConfig': {
22
+ as: '$Config'
23
+ },
24
+ '#': {
25
+ Home: {
26
+ type: CapsulePropertyTypes.Mapping,
27
+ value: '@stream44.studio/t44/caps/Home'
28
+ },
29
+ WorkspaceConfig: {
30
+ type: CapsulePropertyTypes.Mapping,
31
+ value: '@stream44.studio/t44/caps/WorkspaceConfig'
32
+ },
33
+ HomeRegistry: {
34
+ type: CapsulePropertyTypes.Mapping,
35
+ value: '@stream44.studio/t44/caps/HomeRegistry'
36
+ },
37
+ run: {
38
+ type: CapsulePropertyTypes.Function,
39
+ value: async function (this: any, options?: { full?: boolean; entitySelector?: string }): Promise<void> {
40
+ const showFull = options?.full || false
41
+ const entitySelector = options?.entitySelector
42
+
43
+ const workspaceConfig = await this.$Config.config
44
+ const workspaceRootDir = workspaceConfig?.rootDir
45
+ const workspaceName = workspaceConfig?.name || 'default'
46
+
47
+ // Build resolver context with all required paths
48
+ const registryDir = await this.Home.registryDir
49
+ const foundationDir = join(workspaceRootDir, '.~o', 'workspace.foundation')
50
+ const homeRegistryConnectionsDir = join(registryDir, '@t44.sh~t44~caps~WorkspaceConnection', workspaceName)
51
+
52
+ const resolver = Resolver({
53
+ workspaceRootDir,
54
+ workspaceName,
55
+ schemasDir: join(foundationDir, '@t44.sh~t44~caps~JsonSchemas'),
56
+ factsDir: join(foundationDir, '@t44.sh~t44~caps~WorkspaceEntityFact'),
57
+ metaCacheDir: join(foundationDir, '@t44.sh~t44~caps~WorkspaceEntityFact', '@t44.sh~t44~structs~WorkspaceConfigFileMeta'),
58
+ homeRegistryConnectionsDir
59
+ })
60
+
61
+ // Load all entity types in parallel
62
+ const [schemas, configResult, factResult, connectionResult, registryEntities] = await Promise.all([
63
+ resolver.loadSchemas(),
64
+ resolver.loadConfigEntities(),
65
+ resolver.loadFactEntities(),
66
+ resolver.loadConnectionEntities(),
67
+ (async () => {
68
+ const registryRootDir = await this.HomeRegistry.rootDir
69
+ try {
70
+ const entries = await readdir(registryRootDir)
71
+ return {
72
+ rootDir: registryRootDir,
73
+ entities: entries
74
+ .filter((e: string) => e.startsWith('@'))
75
+ .map((e: string) => e.replace(/~/g, '/'))
76
+ }
77
+ } catch {
78
+ return { rootDir: registryRootDir, entities: [] }
79
+ }
80
+ })()
81
+ ])
82
+
83
+ // Merge entity maps from individual loaders
84
+ const entities = new Map<string, ResolvedEntity[]>()
85
+ for (const [key, value] of configResult.entities) entities.set(key, value)
86
+ for (const [key, value] of factResult.entities) {
87
+ if (entities.has(key)) entities.get(key)!.push(...value)
88
+ else entities.set(key, value)
89
+ }
90
+ for (const [key, value] of connectionResult.entities) {
91
+ if (entities.has(key)) entities.get(key)!.push(...value)
92
+ else entities.set(key, value)
93
+ }
94
+
95
+ const configEntities = configResult.configEntities
96
+ const factEntities = factResult.factEntities
97
+ const connectionEntities = connectionResult.connectionEntities
98
+ const registryEntitySet = new Set(registryEntities.entities)
99
+ const registryRootDir = registryEntities.rootDir
100
+
101
+ // Build schema lookup: entity name (without #) → schema file path
102
+ const schemaMap = new Map<string, string>()
103
+ const schemasDir = join(workspaceRootDir, '.~o', 'workspace.foundation', '@t44.sh~t44~caps~JsonSchemas')
104
+ for (const [schemaId] of schemas) {
105
+ const entityName = schemaId.replace(/\.v\d+$/, '')
106
+ // Schema files don't include version in filename, only in $id
107
+ const schemaFileName = entityName.replace(/\//g, '~') + '.json'
108
+ const schemaPath = relative(workspaceRootDir, join(schemasDir, schemaFileName))
109
+ schemaMap.set(entityName, schemaPath)
110
+ }
111
+
112
+ // Build unified entity list from all sources
113
+ const allEntities = new Set<string>()
114
+ for (const key of entities.keys()) allEntities.add(key)
115
+ for (const name of registryEntitySet) {
116
+ const key = name.startsWith('#') ? name : '#' + name
117
+ allEntities.add(key)
118
+ }
119
+ // Add schema-only entities (schemas without data)
120
+ // Skip if entity already has config/fact/connection/registry data
121
+ for (const entityName of schemaMap.keys()) {
122
+ const key = '#' + entityName
123
+ if (!entities.has(key) && !registryEntitySet.has(entityName)) {
124
+ allEntities.add(key)
125
+ }
126
+ }
127
+
128
+ // Build entity matching function
129
+ const matchesSelector = (entity: string): boolean => {
130
+ if (!entitySelector) return true
131
+
132
+ const entityName = entity.startsWith('#') ? entity.substring(1) : entity
133
+ const schemaId = schemaMap.has(entityName) ? entityName : null
134
+
135
+ // Path-based matching (starts with '.')
136
+ if (entitySelector.startsWith('.')) {
137
+ const selectorPath = join(process.cwd(), entitySelector)
138
+
139
+ // Check config entities
140
+ const configInstances = entities.get(entity) || []
141
+ for (const instance of configInstances) {
142
+ if (instance.filePath.includes(selectorPath) || instance.relPath.includes(entitySelector)) {
143
+ return true
144
+ }
145
+ }
146
+
147
+ // Check fact entities
148
+ const facts = factEntities.get(entityName) || []
149
+ for (const fact of facts) {
150
+ if (fact.filePath.includes(selectorPath) || fact.relPath.includes(entitySelector)) {
151
+ return true
152
+ }
153
+ }
154
+
155
+ // Check connection entities
156
+ const connections = connectionEntities.get(entityName) || []
157
+ for (const conn of connections) {
158
+ if (conn.filePath.includes(selectorPath) || conn.relPath.includes(entitySelector)) {
159
+ return true
160
+ }
161
+ }
162
+
163
+ return false
164
+ }
165
+
166
+ // Entity name matching (indexOf)
167
+ if (entityName.indexOf(entitySelector) !== -1) {
168
+ return true
169
+ }
170
+
171
+ // Schema ID matching (indexOf)
172
+ if (schemaId && schemaId.indexOf(entitySelector) !== -1) {
173
+ return true
174
+ }
175
+
176
+ return false
177
+ }
178
+
179
+ // Filter entities based on selector
180
+ const filteredEntities = Array.from(allEntities).filter(matchesSelector).sort()
181
+
182
+ console.log('')
183
+
184
+ for (const entity of filteredEntities) {
185
+ const entityName = entity.startsWith('#') ? entity.substring(1) : entity
186
+ const isRegistry = registryEntitySet.has(entityName)
187
+ const isConfig = configEntities.has(entity)
188
+ const isFact = factEntities.has(entityName)
189
+ const isConnection = connectionEntities.has(entityName)
190
+ const hasSchema = schemaMap.has(entityName)
191
+
192
+ const schemaSuffix = hasSchema ? chalk.gray(' - ') + chalk.gray(schemaMap.get(entityName)!) : ''
193
+
194
+ // Collect validation status
195
+ const entityInstances = entities.get(entity) || []
196
+ const invalidInstances = entityInstances.filter(e => !e.valid)
197
+ const validationSuffix = invalidInstances.length > 0
198
+ ? chalk.red(` ✗ ${invalidInstances.length} validation error(s)`)
199
+ : ''
200
+
201
+ // Skip schema-only display if entity also has registry/config/fact/connection data
202
+ // This prevents duplicate lines for entities like HomeRegistry
203
+ if (!isConfig && !isRegistry && !isFact && !isConnection) {
204
+ // Schema-only entity
205
+ if (hasSchema) {
206
+ console.log(chalk.gray(entityName) + schemaSuffix)
207
+ }
208
+ continue
209
+ }
210
+
211
+ if (isRegistry && !isConfig && !isFact && !isConnection) {
212
+ const dirName = entity.replace(/\//g, '~')
213
+ const registryPath = `${registryRootDir}/${dirName}`
214
+ console.log(chalk.bold.white(entityName) + ' ' + chalk.magenta('[registry]') + chalk.gray(' - ') + chalk.yellow(`${registryPath}/`) + schemaSuffix + validationSuffix)
215
+ } else if (isConfig) {
216
+ const configInstances = entityInstances.filter(e => e.filePath.endsWith('.yaml'))
217
+ if (configInstances.length > 0) {
218
+ const first = configInstances[0]
219
+ const lineInfo = first.line ? `:${first.line}` : ''
220
+ console.log(chalk.bold.white(entityName) + ' ' + chalk.cyan('[config]') + chalk.gray(' - ') + chalk.yellow(first.relPath + lineInfo) + schemaSuffix + validationSuffix)
221
+ } else {
222
+ console.log(chalk.bold.white(entityName) + ' ' + chalk.cyan('[config]') + schemaSuffix + validationSuffix)
223
+ }
224
+
225
+ if (isRegistry) {
226
+ const dirName = entity.replace(/\//g, '~')
227
+ const registryPath = `${registryRootDir}/${dirName}`
228
+ console.log(chalk.magenta(' [registry]') + chalk.gray(' - ') + chalk.yellow(`${registryPath}/`))
229
+ }
230
+ }
231
+
232
+ // Show fact files
233
+ if (isFact) {
234
+ const facts = factEntities.get(entityName)!
235
+ if (!isConfig && !isRegistry && !isConnection) {
236
+ console.log(chalk.bold.white(entityName) + ' ' + chalk.green('[fact]') + schemaSuffix + validationSuffix)
237
+ } else {
238
+ console.log(chalk.green(' [fact]'))
239
+ }
240
+ for (let i = 0; i < facts.length; i++) {
241
+ const fact = facts[i]
242
+ const connector = i === facts.length - 1 ? '└── ' : '├── '
243
+ const indent = (isConfig || isRegistry || isConnection) ? ' ' : ' '
244
+ const validMark = fact.valid ? '' : chalk.red(' ✗')
245
+ console.log(chalk.gray(indent + connector) + chalk.green(fact.relPath) + chalk.gray(` (${fact.name})`) + validMark)
246
+
247
+ // Show full details for this specific file if --full flag is set
248
+ if (showFull) {
249
+ const schema = schemas.get(fact.schemaId)
250
+ const detailIndent = indent + ' '
251
+
252
+ if (schema) {
253
+ const rendered = SchemaConsoleRenderer.renderEntity(fact.data, schema, {
254
+ indent: detailIndent.length / 2,
255
+ maxDepth: -1,
256
+ showTypes: false
257
+ })
258
+ console.log(rendered)
259
+ } else {
260
+ const jsonLines = JSON.stringify(fact.data, null, 2).split('\n')
261
+ for (const line of jsonLines) {
262
+ console.log(chalk.gray(detailIndent + line))
263
+ }
264
+ }
265
+
266
+ if (fact.errors.length > 0) {
267
+ const errorLines = SchemaConsoleRenderer.renderErrors(fact.errors).split('\n')
268
+ for (const line of errorLines) {
269
+ console.log(detailIndent + line)
270
+ }
271
+ }
272
+ }
273
+ }
274
+ }
275
+
276
+ // Show connection files
277
+ if (isConnection) {
278
+ const connections = connectionEntities.get(entityName)!
279
+ if (!isConfig && !isRegistry && !isFact) {
280
+ console.log(chalk.bold.white(entityName) + ' ' + chalk.magenta('[registry]') + schemaSuffix + validationSuffix)
281
+ } else {
282
+ console.log(chalk.magenta(' [registry]'))
283
+ }
284
+ for (let i = 0; i < connections.length; i++) {
285
+ const conn = connections[i]
286
+ const connector = i === connections.length - 1 ? '└── ' : '├── '
287
+ const indent = (isConfig || isRegistry || isFact) ? ' ' : ' '
288
+ const validMark = conn.valid ? '' : chalk.red(' ✗')
289
+ console.log(chalk.gray(indent + connector) + chalk.yellow(conn.relPath) + chalk.gray(` (${conn.name})`) + validMark)
290
+
291
+ // Show full details for this specific file if --full flag is set
292
+ if (showFull) {
293
+ const schema = schemas.get(conn.schemaId)
294
+ const detailIndent = indent + ' '
295
+
296
+ if (schema) {
297
+ // Connection data is wrapped in 'config' object, unwrap it for schema validation
298
+ const dataToRender = conn.data.config || conn.data
299
+ const rendered = SchemaConsoleRenderer.renderEntity(dataToRender, schema, {
300
+ indent: detailIndent.length / 2,
301
+ maxDepth: -1,
302
+ showTypes: false
303
+ })
304
+ console.log(rendered)
305
+ } else {
306
+ const jsonLines = JSON.stringify(conn.data, null, 2).split('\n')
307
+ for (const line of jsonLines) {
308
+ console.log(chalk.gray(detailIndent + line))
309
+ }
310
+ }
311
+
312
+ if (conn.errors.length > 0) {
313
+ const errorLines = SchemaConsoleRenderer.renderErrors(conn.errors).split('\n')
314
+ for (const line of errorLines) {
315
+ console.log(detailIndent + line)
316
+ }
317
+ }
318
+ }
319
+ }
320
+ }
321
+
322
+ // Show validation errors in detail (for compact mode)
323
+ if (!showFull) {
324
+ for (const instance of invalidInstances) {
325
+ for (const err of instance.errors) {
326
+ console.log(chalk.red(` ${err.path}: ${err.message}`))
327
+ }
328
+ }
329
+ }
330
+
331
+ // Show full entity details for config entities if --full flag is set
332
+ if (showFull && isConfig && !isFact && !isConnection) {
333
+ const configInstances = entityInstances.filter(e => e.filePath.endsWith('.yaml'))
334
+ for (const instance of configInstances) {
335
+ const schema = schemas.get(instance.schemaId)
336
+ const baseIndent = ' '
337
+
338
+ if (schema) {
339
+ const rendered = SchemaConsoleRenderer.renderEntity(instance.data, schema, {
340
+ indent: baseIndent.length / 2 + 1,
341
+ maxDepth: -1,
342
+ showTypes: false
343
+ })
344
+ console.log(rendered)
345
+ } else {
346
+ const jsonLines = JSON.stringify(instance.data, null, 2).split('\n')
347
+ for (const line of jsonLines) {
348
+ console.log(chalk.gray(baseIndent + line))
349
+ }
350
+ }
351
+
352
+ if (instance.errors.length > 0) {
353
+ const errorLines = SchemaConsoleRenderer.renderErrors(instance.errors).split('\n')
354
+ for (const line of errorLines) {
355
+ console.log(baseIndent + line)
356
+ }
357
+ }
358
+ }
359
+ }
360
+ }
361
+
362
+ console.log('')
363
+ }
364
+ }
365
+ }
366
+ }
367
+ }, {
368
+ importMeta: import.meta,
369
+ importStack: makeImportStack(),
370
+ capsuleName: capsule['#'],
371
+ })
372
+ }
373
+ capsule['#'] = '@stream44.studio/t44/caps/WorkspaceModel'