@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,268 @@
1
+
2
+ import { join, resolve } from 'path'
3
+
4
+ export async function capsule({
5
+ encapsulate,
6
+ CapsulePropertyTypes,
7
+ makeImportStack
8
+ }: {
9
+ encapsulate: any
10
+ CapsulePropertyTypes: any
11
+ makeImportStack: any
12
+ }) {
13
+ return encapsulate({
14
+ '#@stream44.studio/encapsulate/spine-contracts/CapsuleSpineContract.v0': {
15
+ '#@stream44.studio/encapsulate/structs/Capsule': {},
16
+ '#@stream44.studio/t44/structs/WorkspaceConfig': {
17
+ as: '$WorkspaceConfig'
18
+ },
19
+ '#': {
20
+ workspaceRootDir: {
21
+ type: CapsulePropertyTypes.Literal,
22
+ value: undefined
23
+ },
24
+ workspaceConfigFilepath: {
25
+ type: CapsulePropertyTypes.Literal,
26
+ value: '.workspace/workspace.yaml'
27
+ },
28
+ WorkspacePrompt: {
29
+ type: CapsulePropertyTypes.Mapping,
30
+ value: '@stream44.studio/t44/caps/WorkspacePrompt'
31
+ },
32
+ HomeRegistry: {
33
+ type: CapsulePropertyTypes.Mapping,
34
+ value: '@stream44.studio/t44/caps/HomeRegistry'
35
+ },
36
+ WorkspaceConfigFile: {
37
+ type: CapsulePropertyTypes.Mapping,
38
+ value: '@stream44.studio/t44/caps/WorkspaceConfigFile'
39
+ },
40
+ config: {
41
+ type: CapsulePropertyTypes.GetterFunction,
42
+ value: async function (this: any): Promise<object> {
43
+
44
+ const configPath = join(this.workspaceRootDir, this.workspaceConfigFilepath);
45
+
46
+ const { config } = await this.WorkspaceConfigFile.loadConfig(configPath, this.workspaceRootDir)
47
+
48
+ // Validate javascript.api.workspaceDir from CLI config struct
49
+ const cliConfigStructKey = '#@stream44.studio/t44/structs/WorkspaceCliConfig'
50
+ const cliConfigStruct = config[cliConfigStructKey] || {}
51
+ if (resolve(cliConfigStruct?.javascript?.api?.workspaceDir) !== this.workspaceRootDir) {
52
+ throw new Error(`javascript.api.workspaceDir '${cliConfigStruct?.javascript?.api?.workspaceDir}' in '${configPath}' does not match expected this.workspaceRootDir '${this.workspaceRootDir}'!`)
53
+ }
54
+
55
+ // Validate rootDir if set
56
+ const workspaceConfigStructKey = '#@stream44.studio/t44/structs/WorkspaceConfig'
57
+ const workspaceConfigStruct = config[workspaceConfigStructKey] || {}
58
+ if (workspaceConfigStruct.rootDir && workspaceConfigStruct.rootDir !== this.workspaceRootDir) {
59
+ throw new Error(`rootDir '${workspaceConfigStruct.rootDir}' does not match expected '${this.workspaceRootDir}'!`)
60
+ }
61
+ if (workspaceConfigStruct.rootConfigFilepath && workspaceConfigStruct.rootConfigFilepath !== this.workspaceConfigFilepath) {
62
+ throw new Error(`rootConfigFilepath '${workspaceConfigStruct.rootConfigFilepath}' does not match expected '${this.workspaceConfigFilepath}'!`)
63
+ }
64
+
65
+ return config
66
+ }
67
+ },
68
+ ensureConfigBase: {
69
+ type: CapsulePropertyTypes.Function,
70
+ value: async function (this: any): Promise<void> {
71
+
72
+ const workspaceConfigStructKey = '#@stream44.studio/t44/structs/WorkspaceConfig'
73
+
74
+ const config = await this.config
75
+ const workspaceConfigStruct = config[workspaceConfigStructKey] || {}
76
+
77
+ if (!workspaceConfigStruct.rootDir) {
78
+ await this.$WorkspaceConfig.setConfigValue(['rootDir'], this.workspaceRootDir)
79
+ }
80
+
81
+ if (!workspaceConfigStruct.rootConfigFilepath) {
82
+ await this.$WorkspaceConfig.setConfigValue(['rootConfigFilepath'], this.workspaceConfigFilepath)
83
+ }
84
+ }
85
+ },
86
+ ensureConfigIdentity: {
87
+ type: CapsulePropertyTypes.Function,
88
+ value: async function (this: any): Promise<void> {
89
+
90
+ const workspaceConfigStructKey = '#@stream44.studio/t44/structs/WorkspaceConfig'
91
+
92
+ const config = await this.config
93
+ const workspaceConfigStruct = config[workspaceConfigStructKey] || {}
94
+
95
+ // Ensure workspace name
96
+ if (!workspaceConfigStruct.name) {
97
+ const { basename } = await import('path')
98
+
99
+ let workspaceName: string | undefined
100
+
101
+ while (!workspaceName) {
102
+ const candidateName = await this.WorkspacePrompt.setupPrompt({
103
+ title: '🏢 Workspace Name Setup',
104
+ description: 'A workspace holds some or all projects registered in a Project Rack.',
105
+ message: 'Enter a name for this workspace:',
106
+ defaultValue: basename(this.workspaceRootDir),
107
+ validate: (input: string) => {
108
+ if (!input || input.trim().length === 0) {
109
+ return 'Workspace name cannot be empty'
110
+ }
111
+ if (!/^[a-zA-Z0-9_-]+$/.test(input)) {
112
+ return 'Workspace name can only contain letters, numbers, underscores, and hyphens'
113
+ }
114
+ return true
115
+ },
116
+ configPath: [workspaceConfigStructKey, 'name'],
117
+ onSuccess: async () => {
118
+ // Don't write to config yet — we need to validate first
119
+ }
120
+ })
121
+
122
+ // Check if a workspace with this name already exists in the registry
123
+ const existingData = await this.HomeRegistry.getWorkspace(candidateName)
124
+
125
+ if (existingData) {
126
+ if (existingData.workspaceRootDir === this.workspaceRootDir) {
127
+ // Same directory — adopt existing identity
128
+ const chalk = (await import('chalk')).default
129
+ console.log(chalk.green(`\n ✓ Found existing workspace identity for "${candidateName}" in this directory.`))
130
+ console.log(chalk.green(` Adopting existing identity.\n`))
131
+
132
+ await this.$WorkspaceConfig.setConfigValue(['name'], candidateName)
133
+ workspaceConfigStruct.name = candidateName
134
+
135
+ // Adopt the existing identifier
136
+ await this.$WorkspaceConfig.setConfigValue(['identifier'], existingData.did)
137
+ workspaceConfigStruct.identifier = existingData.did
138
+
139
+ console.log(chalk.green(` ✓ DID: ${existingData.did}\n`))
140
+ workspaceName = candidateName
141
+ } else {
142
+ // Different directory — warn and prompt
143
+ const chalk = (await import('chalk')).default
144
+ const registryPath = await this.HomeRegistry.getWorkspacePath(candidateName)
145
+ console.log(chalk.yellow(`\n ⚠ A workspace named "${candidateName}" already exists at:`))
146
+ console.log(chalk.white(` ${registryPath}`))
147
+ console.log('')
148
+ console.log(chalk.yellow(` It is currently connected to:`))
149
+ console.log(chalk.white(` ${existingData.workspaceRootDir}`))
150
+ console.log('')
151
+ console.log(chalk.yellow(` You are trying to set up a workspace with the same name in:`))
152
+ console.log(chalk.white(` ${this.workspaceRootDir}\n`))
153
+ console.log(chalk.yellow(` A workspace can only be connected to one directory.`))
154
+ console.log('')
155
+
156
+ const confirmed = await this.WorkspacePrompt.confirm({
157
+ message: `Disconnect "${candidateName}" from "${existingData.workspaceRootDir}" and connect it to "${this.workspaceRootDir}" instead?`,
158
+ defaultValue: false
159
+ })
160
+
161
+ if (confirmed) {
162
+ // Update registry with new rootDir
163
+ existingData.workspaceRootDir = this.workspaceRootDir
164
+ await this.HomeRegistry.setWorkspace(candidateName, existingData)
165
+
166
+ await this.$WorkspaceConfig.setConfigValue(['name'], candidateName)
167
+ workspaceConfigStruct.name = candidateName
168
+
169
+ // Adopt the existing identifier
170
+ await this.$WorkspaceConfig.setConfigValue(['identifier'], existingData.did)
171
+ workspaceConfigStruct.identifier = existingData.did
172
+
173
+ console.log(chalk.green(`\n ✓ Workspace "${candidateName}" reconnected to this directory.`))
174
+ console.log(chalk.green(` ✓ DID: ${existingData.did}\n`))
175
+ workspaceName = candidateName
176
+ } else {
177
+ console.log(chalk.gray(`\n Please choose a different workspace name.\n`))
178
+ // Loop again to re-prompt
179
+ }
180
+ }
181
+ } else {
182
+ // Name is available — commit it
183
+ await this.$WorkspaceConfig.setConfigValue(['name'], candidateName)
184
+ workspaceConfigStruct.name = candidateName
185
+ workspaceName = candidateName
186
+ }
187
+ }
188
+ }
189
+
190
+ // Ensure workspace identifier
191
+ if (!workspaceConfigStruct.identifier) {
192
+ const chalk = (await import('chalk')).default
193
+
194
+ const workspaceName = workspaceConfigStruct.name
195
+
196
+ console.log(chalk.cyan('\n🔑 Workspace Identifier Setup\n'))
197
+ console.log(chalk.gray(' Generating unique workspace identifier...\n'))
198
+
199
+ // Generate Ed25519 key pair for workspace identifier
200
+ const { generateKeypair } = await import('../lib/ucan.js')
201
+ const { did, privateKey } = await generateKeypair()
202
+
203
+ // Store in registry
204
+ const identifierData = {
205
+ did,
206
+ privateKey,
207
+ createdAt: new Date().toISOString(),
208
+ workspaceRootDir: this.workspaceRootDir,
209
+ }
210
+
211
+ const identifierPath = await this.HomeRegistry.setWorkspace(workspaceName, identifierData)
212
+
213
+ // Update config with workspace identifier (DID)
214
+ await this.$WorkspaceConfig.setConfigValue(['identifier'], did)
215
+
216
+ console.log(chalk.green(` ✓ Workspace identifier generated and saved to:`))
217
+ console.log(chalk.green(` ${identifierPath}`))
218
+ console.log(chalk.green(` ✓ DID: ${did}\n`))
219
+ }
220
+ }
221
+ },
222
+ configTree: {
223
+ type: CapsulePropertyTypes.GetterFunction,
224
+ value: async function (this: any): Promise<any> {
225
+ const configPath = join(this.workspaceRootDir, this.workspaceConfigFilepath);
226
+ const { configTree } = await this.WorkspaceConfigFile.loadConfig(configPath, this.workspaceRootDir)
227
+ return configTree
228
+ }
229
+ },
230
+ entitySources: {
231
+ type: CapsulePropertyTypes.GetterFunction,
232
+ value: async function (this: any): Promise<Map<string, { path: string, line: number }[]>> {
233
+ const configPath = join(this.workspaceRootDir, this.workspaceConfigFilepath);
234
+ const { entitySources } = await this.WorkspaceConfigFile.loadConfig(configPath, this.workspaceRootDir)
235
+ return entitySources
236
+ }
237
+ },
238
+ setConfigValue: {
239
+ type: CapsulePropertyTypes.Function,
240
+ value: async function (this: any, path: string[], value: any, options?: { ifAbsent?: boolean }): Promise<boolean> {
241
+ const configPath = join(this.workspaceRootDir, this.workspaceConfigFilepath)
242
+ return this.WorkspaceConfigFile.setConfigValue(configPath, path, value, options)
243
+ }
244
+ },
245
+ setConfigValueForEntity: {
246
+ type: CapsulePropertyTypes.Function,
247
+ value: async function (this: any, entity: { entityName: string, schema: any }, path: string[], value: any, options?: { ifAbsent?: boolean }): Promise<boolean> {
248
+ const configPath = join(this.workspaceRootDir, this.workspaceConfigFilepath)
249
+ return this.WorkspaceConfigFile.setConfigValueForEntity(configPath, entity, path, value, options)
250
+ }
251
+ },
252
+ ensureEntityTimestamps: {
253
+ type: CapsulePropertyTypes.Function,
254
+ value: async function (this: any, entity: { entityName: string }, configKey: string, now: string): Promise<void> {
255
+ const configPath = join(this.workspaceRootDir, this.workspaceConfigFilepath)
256
+ await this.WorkspaceConfigFile.setConfigValue(configPath, [configKey, 'createdAt'], now, { ifAbsent: true })
257
+ await this.WorkspaceConfigFile.setConfigValue(configPath, [configKey, 'updatedAt'], now, { ifAbsent: true })
258
+ }
259
+ },
260
+ }
261
+ }
262
+ }, {
263
+ importMeta: import.meta,
264
+ importStack: makeImportStack(),
265
+ capsuleName: capsule['#'],
266
+ })
267
+ }
268
+ capsule['#'] = '@stream44.studio/t44/caps/WorkspaceConfig'
@@ -0,0 +1,87 @@
1
+ $schema: ../../../../.~o/workspace.foundation/@t44.sh~t44~caps~JsonSchemas/@t44.sh~t44~structs~WorkspaceConfigFile.json
2
+ extends:
3
+ - ./WorkspaceShell.yaml
4
+ "#@stream44.studio/t44/structs/WorkspaceCliConfig":
5
+ javascript:
6
+ api:
7
+ workspaceDir: resolve('${__dirname}/..')
8
+ cli:
9
+ commands:
10
+ init:
11
+ capsule: "@stream44.studio/t44/caps/WorkspaceInit"
12
+ description: Initialize a new Terminal 44 workspace.
13
+ activate:
14
+ capsule: "@stream44.studio/t44/caps/WorkspaceShell"
15
+ description: Generate environment variables for sourcing into shells.
16
+ info:
17
+ capsule: "@stream44.studio/t44/caps/WorkspaceInfo"
18
+ description: Display information about the workspace.
19
+ options:
20
+ full:
21
+ description: Show full details instead of compact view
22
+ query:
23
+ capsule: "@stream44.studio/t44/caps/WorkspaceModel"
24
+ description: Query the workspace model.
25
+ arguments:
26
+ entitySelector:
27
+ optional: true
28
+ description: Filter entities by path, name, or schema ID
29
+ options:
30
+ full:
31
+ description: Show full details instead of compact view
32
+ dev:
33
+ capsule: "@stream44.studio/t44/caps/ProjectDevelopment"
34
+ description: Run a project or package dev server.
35
+ arguments:
36
+ projectSelector:
37
+ optional: true
38
+ description: Name of a project or package to run the dev script for.
39
+ push:
40
+ capsule: "@stream44.studio/t44/caps/ProjectPublishing"
41
+ description: Publish local committed code to a remote service.
42
+ arguments:
43
+ projectSelector:
44
+ optional: true
45
+ description: Name of a top-level project directory in the workspace. If not specified, pushes all repositories.
46
+ options:
47
+ branch:
48
+ description: Push to a specific branch instead of main. Creates the branch if it doesn't exist.
49
+ value: required
50
+ bump:
51
+ description: Bump package.json versions without tagging or publishing (dry-run mode)
52
+ rc:
53
+ description: Release candidate mode - bump versions, tag, and publish
54
+ release:
55
+ description: Remove release candidate suffix and publish release version
56
+ git:
57
+ description: Push current state to git providers only (no version bump or tagging).
58
+ pkg:
59
+ description: Push current state to package providers only (no version bump or tagging).
60
+ dangerously-reset-main:
61
+ description: Reset the git repository and force push to remote.
62
+ dangerously-reset-gordian-open-integrity:
63
+ description: Reset the Gordian Open Integrity trust root.
64
+ dangerously-squash-to-commit:
65
+ description: Soft-reset the repo to the specified commit, squashing all subsequent commits into the working tree, then re-commit and force push. Accepts full or short hash.
66
+ value: required
67
+ yes-signoff:
68
+ description: Automatically agree to DCO sign-off without prompting.
69
+ pull:
70
+ capsule: "@stream44.studio/t44/caps/ProjectPulling"
71
+ description: Sync changes from a remote branch URL into the local source directory.
72
+ arguments:
73
+ url:
74
+ optional: false
75
+ description: URL of a remote branch to pull from (e.g. https://github.com/owner/repo/tree/branch)
76
+ deploy:
77
+ capsule: "@stream44.studio/t44/caps/ProjectDeployment"
78
+ description: Deploy a project to a provider.
79
+ arguments:
80
+ projectSelector:
81
+ optional: true
82
+ description: Name of a top-level project directory in the workspace. If not specified, deploys all projects.
83
+ options:
84
+ deprovision:
85
+ description: Delete the project from the provider instead of deploying it
86
+ createdAt: "2026-02-11T01:01:49.797Z"
87
+ updatedAt: "2026-02-11T01:01:49.797Z"