@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,448 @@
|
|
|
1
|
+
|
|
2
|
+
import { Command } from 'commander'
|
|
3
|
+
import { $ } from 'bun'
|
|
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
|
+
'#@stream44.studio/t44/structs/WorkspaceCliConfig': {
|
|
18
|
+
as: '$config'
|
|
19
|
+
},
|
|
20
|
+
'#': {
|
|
21
|
+
WorkspaceConfig: {
|
|
22
|
+
type: CapsulePropertyTypes.Mapping,
|
|
23
|
+
value: '@stream44.studio/t44/caps/WorkspaceConfig'
|
|
24
|
+
},
|
|
25
|
+
WorkspaceKey: {
|
|
26
|
+
type: CapsulePropertyTypes.Mapping,
|
|
27
|
+
value: '@stream44.studio/t44/caps/WorkspaceKey'
|
|
28
|
+
},
|
|
29
|
+
ProjectRack: {
|
|
30
|
+
type: CapsulePropertyTypes.Mapping,
|
|
31
|
+
value: '@stream44.studio/t44/caps/ProjectRack'
|
|
32
|
+
},
|
|
33
|
+
WorkspaceShell: {
|
|
34
|
+
type: CapsulePropertyTypes.Mapping,
|
|
35
|
+
value: '@stream44.studio/t44/caps/WorkspaceShell'
|
|
36
|
+
},
|
|
37
|
+
ProjectDeployment: {
|
|
38
|
+
type: CapsulePropertyTypes.Mapping,
|
|
39
|
+
value: '@stream44.studio/t44/caps/ProjectDeployment'
|
|
40
|
+
},
|
|
41
|
+
ProjectPublishing: {
|
|
42
|
+
type: CapsulePropertyTypes.Mapping,
|
|
43
|
+
value: '@stream44.studio/t44/caps/ProjectPublishing'
|
|
44
|
+
},
|
|
45
|
+
ProjectPulling: {
|
|
46
|
+
type: CapsulePropertyTypes.Mapping,
|
|
47
|
+
value: '@stream44.studio/t44/caps/ProjectPulling'
|
|
48
|
+
},
|
|
49
|
+
ProjectDevelopment: {
|
|
50
|
+
type: CapsulePropertyTypes.Mapping,
|
|
51
|
+
value: '@stream44.studio/t44/caps/ProjectDevelopment'
|
|
52
|
+
},
|
|
53
|
+
WorkspaceInit: {
|
|
54
|
+
type: CapsulePropertyTypes.Mapping,
|
|
55
|
+
value: '@stream44.studio/t44/caps/WorkspaceInit'
|
|
56
|
+
},
|
|
57
|
+
WorkspaceInfo: {
|
|
58
|
+
type: CapsulePropertyTypes.Mapping,
|
|
59
|
+
value: '@stream44.studio/t44/caps/WorkspaceInfo'
|
|
60
|
+
},
|
|
61
|
+
WorkspaceModel: {
|
|
62
|
+
type: CapsulePropertyTypes.Mapping,
|
|
63
|
+
value: '@stream44.studio/t44/caps/WorkspaceModel'
|
|
64
|
+
},
|
|
65
|
+
WorkspacePrompt: {
|
|
66
|
+
type: CapsulePropertyTypes.Mapping,
|
|
67
|
+
value: '@stream44.studio/t44/caps/WorkspacePrompt'
|
|
68
|
+
},
|
|
69
|
+
HomeRegistry: {
|
|
70
|
+
type: CapsulePropertyTypes.Mapping,
|
|
71
|
+
value: '@stream44.studio/t44/caps/HomeRegistry'
|
|
72
|
+
},
|
|
73
|
+
RootKey: {
|
|
74
|
+
type: CapsulePropertyTypes.Mapping,
|
|
75
|
+
value: '@stream44.studio/t44/caps/RootKey'
|
|
76
|
+
},
|
|
77
|
+
SigningKey: {
|
|
78
|
+
type: CapsulePropertyTypes.Mapping,
|
|
79
|
+
value: '@stream44.studio/t44/caps/SigningKey'
|
|
80
|
+
},
|
|
81
|
+
WorkspaceProjects: {
|
|
82
|
+
type: CapsulePropertyTypes.Mapping,
|
|
83
|
+
value: '@stream44.studio/t44/caps/WorkspaceProjects'
|
|
84
|
+
},
|
|
85
|
+
ProjectCatalogs: {
|
|
86
|
+
type: CapsulePropertyTypes.Mapping,
|
|
87
|
+
value: '@stream44.studio/t44/caps/ProjectCatalogs'
|
|
88
|
+
},
|
|
89
|
+
cliOptions: {
|
|
90
|
+
type: CapsulePropertyTypes.Literal,
|
|
91
|
+
value: { yes: false }
|
|
92
|
+
},
|
|
93
|
+
jsApi: {
|
|
94
|
+
type: CapsulePropertyTypes.GetterFunction,
|
|
95
|
+
value: async function (this: any): Promise<object> {
|
|
96
|
+
|
|
97
|
+
const config = await this.WorkspaceConfig.config as any
|
|
98
|
+
|
|
99
|
+
const api: Record<string, any> = {}
|
|
100
|
+
for (const propertyName in config?.javascript?.api) {
|
|
101
|
+
api[propertyName] = config.javascript.api[propertyName]
|
|
102
|
+
}
|
|
103
|
+
return api
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
cliCommands: {
|
|
107
|
+
type: CapsulePropertyTypes.GetterFunction,
|
|
108
|
+
value: async function (this: any): Promise<object> {
|
|
109
|
+
|
|
110
|
+
const cliConfig = await this.$config.config
|
|
111
|
+
const self = this
|
|
112
|
+
|
|
113
|
+
const commands: Record<string, (commandArgs?: any) => Promise<void>> = {}
|
|
114
|
+
for (const commandName in cliConfig?.cli?.commands) {
|
|
115
|
+
const commandConfig = cliConfig.cli.commands[commandName]
|
|
116
|
+
|
|
117
|
+
commands[commandName] = async function (commandArgs?: any) {
|
|
118
|
+
|
|
119
|
+
const { cmd, capsule } = commandConfig
|
|
120
|
+
|
|
121
|
+
if (capsule) {
|
|
122
|
+
// TODO: Dynamically load capsule
|
|
123
|
+
if (capsule === '@stream44.studio/t44/caps/ProjectDeployment') {
|
|
124
|
+
await self.ProjectDeployment.run({ args: commandArgs })
|
|
125
|
+
} else if (capsule === '@stream44.studio/t44/caps/ProjectPublishing') {
|
|
126
|
+
await self.ProjectPublishing.run({ args: commandArgs })
|
|
127
|
+
} else if (capsule === '@stream44.studio/t44/caps/ProjectPulling') {
|
|
128
|
+
await self.ProjectPulling.run({ args: commandArgs })
|
|
129
|
+
} else if (capsule === '@stream44.studio/t44/caps/WorkspaceShell') {
|
|
130
|
+
await self.WorkspaceShell.run({ args: commandArgs })
|
|
131
|
+
} else if (capsule === '@stream44.studio/t44/caps/ProjectDevelopment') {
|
|
132
|
+
await self.ProjectDevelopment.run({ args: commandArgs })
|
|
133
|
+
} else if (capsule === '@stream44.studio/t44/caps/WorkspaceInit') {
|
|
134
|
+
await self.WorkspaceInit.run({ args: commandArgs })
|
|
135
|
+
} else if (capsule === '@stream44.studio/t44/caps/WorkspaceInfo') {
|
|
136
|
+
await self.WorkspaceInfo.run({ args: commandArgs })
|
|
137
|
+
} else if (capsule === '@stream44.studio/t44/caps/WorkspaceModel') {
|
|
138
|
+
const full = commandArgs?.full || false
|
|
139
|
+
const entitySelector = commandArgs?.entitySelector
|
|
140
|
+
await self.WorkspaceModel.run({ full, entitySelector })
|
|
141
|
+
} else {
|
|
142
|
+
throw new Error(`Unsupported capsule '${capsule}'!`)
|
|
143
|
+
}
|
|
144
|
+
} else if (cmd) {
|
|
145
|
+
await $`sh -c ${cmd}`.cwd(self.WorkspaceConfig.workspaceRootDir)
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return commands
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
validateIdentities: {
|
|
153
|
+
type: CapsulePropertyTypes.Function,
|
|
154
|
+
value: async function (this: any): Promise<boolean> {
|
|
155
|
+
const chalk = (await import('chalk')).default
|
|
156
|
+
|
|
157
|
+
const fullConfig = await this.WorkspaceConfig.config
|
|
158
|
+
const wsConfigStructKey = '#@stream44.studio/t44/structs/WorkspaceConfig'
|
|
159
|
+
const keyConfigStructKey = '#@stream44.studio/t44/structs/WorkspaceKeyConfig'
|
|
160
|
+
const rackConfigStructKey = '#@stream44.studio/t44/structs/ProjectRackConfig'
|
|
161
|
+
const homeRegistryConfigStructKey = '#@stream44.studio/t44/structs/HomeRegistryConfig'
|
|
162
|
+
|
|
163
|
+
const ws = fullConfig?.[wsConfigStructKey]
|
|
164
|
+
const keyConfig = fullConfig?.[keyConfigStructKey]
|
|
165
|
+
const rackConfig = fullConfig?.[rackConfigStructKey]
|
|
166
|
+
const homeRegistryConfig = fullConfig?.[homeRegistryConfigStructKey]
|
|
167
|
+
|
|
168
|
+
if (!ws) return true
|
|
169
|
+
|
|
170
|
+
// --- Home Registry identity ---
|
|
171
|
+
if (homeRegistryConfig?.rootDir) {
|
|
172
|
+
const registryData = await this.HomeRegistry.getRegistry()
|
|
173
|
+
const registryPath = await this.HomeRegistry.getRegistryPath()
|
|
174
|
+
|
|
175
|
+
if (registryData) {
|
|
176
|
+
if (homeRegistryConfig.identifier) {
|
|
177
|
+
if (registryData.did !== homeRegistryConfig.identifier) {
|
|
178
|
+
console.log(chalk.red(`\n✗ Home Registry Identity Mismatch\n`))
|
|
179
|
+
console.log(chalk.red(` The home registry identifier in your config does not match the registry.\n`))
|
|
180
|
+
console.log(chalk.white(` Config identifier:`))
|
|
181
|
+
console.log(chalk.white(` ${homeRegistryConfig.identifier}`))
|
|
182
|
+
console.log(chalk.white(` Registry identifier (${registryPath}):`))
|
|
183
|
+
console.log(chalk.white(` ${registryData.did}\n`))
|
|
184
|
+
console.log(chalk.red(` To fix this, either:`))
|
|
185
|
+
console.log(chalk.red(` • Update the identifier in your workspace config to match the registry`))
|
|
186
|
+
console.log(chalk.red(` • Or delete the relevant config fields and re-run to set up fresh\n`))
|
|
187
|
+
return false
|
|
188
|
+
}
|
|
189
|
+
} else {
|
|
190
|
+
// rootDir set but no identifier — adopt from registry
|
|
191
|
+
await this.WorkspaceConfig.setConfigValue([homeRegistryConfigStructKey, 'identifier'], registryData.did)
|
|
192
|
+
console.log(chalk.green(` ✓ Adopted home registry identity from registry`))
|
|
193
|
+
console.log(chalk.green(` DID: ${registryData.did}\n`))
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// --- Workspace identity ---
|
|
199
|
+
if (ws.name) {
|
|
200
|
+
const registryData = await this.HomeRegistry.getWorkspace(ws.name)
|
|
201
|
+
const registryPath = await this.HomeRegistry.getWorkspacePath(ws.name)
|
|
202
|
+
|
|
203
|
+
if (registryData) {
|
|
204
|
+
if (ws.identifier) {
|
|
205
|
+
// Validate: config identifier must match registry
|
|
206
|
+
if (registryData.did !== ws.identifier) {
|
|
207
|
+
console.log(chalk.red(`\n✗ Workspace Identity Mismatch\n`))
|
|
208
|
+
console.log(chalk.red(` The identifier in your workspace config does not match the registry.\n`))
|
|
209
|
+
console.log(chalk.white(` Config identifier:`))
|
|
210
|
+
console.log(chalk.white(` ${ws.identifier}`))
|
|
211
|
+
console.log(chalk.white(` Registry identifier (${registryPath}):`))
|
|
212
|
+
console.log(chalk.white(` ${registryData.did}\n`))
|
|
213
|
+
console.log(chalk.red(` This can happen if the registry file was regenerated or the config was manually edited.`))
|
|
214
|
+
console.log(chalk.red(` To fix this, either:`))
|
|
215
|
+
console.log(chalk.red(` • Update the identifier in your workspace config to match the registry`))
|
|
216
|
+
console.log(chalk.red(` • Or delete the relevant config fields and re-run to set up fresh\n`))
|
|
217
|
+
return false
|
|
218
|
+
}
|
|
219
|
+
// Validate: rootDir must match
|
|
220
|
+
if (registryData.workspaceRootDir && registryData.workspaceRootDir !== ws.rootDir) {
|
|
221
|
+
console.log(chalk.red(`\n✗ Workspace Root Directory Mismatch\n`))
|
|
222
|
+
console.log(chalk.red(` The workspace "${ws.name}" is registered to a different directory.\n`))
|
|
223
|
+
console.log(chalk.white(` Config rootDir:`))
|
|
224
|
+
console.log(chalk.white(` ${ws.rootDir}`))
|
|
225
|
+
console.log(chalk.white(` Registry rootDir (${registryPath}):`))
|
|
226
|
+
console.log(chalk.white(` ${registryData.workspaceRootDir}\n`))
|
|
227
|
+
console.log(chalk.red(` A workspace can only be connected to one directory.\n`))
|
|
228
|
+
return false
|
|
229
|
+
}
|
|
230
|
+
} else {
|
|
231
|
+
// Name set but no identifier — check rootDir and adopt if matching
|
|
232
|
+
if (registryData.workspaceRootDir && registryData.workspaceRootDir !== ws.rootDir) {
|
|
233
|
+
console.log(chalk.red(`\n✗ Workspace "${ws.name}" Cannot Be Adopted\n`))
|
|
234
|
+
console.log(chalk.red(` The workspace "${ws.name}" is registered to a different directory.\n`))
|
|
235
|
+
console.log(chalk.white(` Your rootDir:`))
|
|
236
|
+
console.log(chalk.white(` ${ws.rootDir}`))
|
|
237
|
+
console.log(chalk.white(` Registry rootDir (${registryPath}):`))
|
|
238
|
+
console.log(chalk.white(` ${registryData.workspaceRootDir}\n`))
|
|
239
|
+
console.log(chalk.red(` A workspace can only be connected to one directory.`))
|
|
240
|
+
console.log(chalk.red(` Choose a different workspace name or update the registry.\n`))
|
|
241
|
+
return false
|
|
242
|
+
}
|
|
243
|
+
// rootDir matches — adopt identity
|
|
244
|
+
await this.WorkspaceConfig.setConfigValue([wsConfigStructKey, 'identifier'], registryData.did)
|
|
245
|
+
console.log(chalk.green(` ✓ Adopted workspace identity for "${ws.name}" from registry`))
|
|
246
|
+
console.log(chalk.green(` DID: ${registryData.did}\n`))
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// --- Key identity ---
|
|
252
|
+
if (keyConfig?.name) {
|
|
253
|
+
const registryData = await this.HomeRegistry.getKey(keyConfig.name)
|
|
254
|
+
const registryPath = await this.HomeRegistry.getKeyPath(keyConfig.name)
|
|
255
|
+
|
|
256
|
+
if (registryData) {
|
|
257
|
+
if (keyConfig.identifier) {
|
|
258
|
+
if (registryData.did !== keyConfig.identifier) {
|
|
259
|
+
console.log(chalk.red(`\n✗ Workspace Key Identity Mismatch\n`))
|
|
260
|
+
console.log(chalk.red(` The key identifier in your config does not match the registry.\n`))
|
|
261
|
+
console.log(chalk.white(` Config identifier:`))
|
|
262
|
+
console.log(chalk.white(` ${keyConfig.identifier}`))
|
|
263
|
+
console.log(chalk.white(` Registry identifier (${registryPath}):`))
|
|
264
|
+
console.log(chalk.white(` ${registryData.did}\n`))
|
|
265
|
+
console.log(chalk.red(` To fix this, either:`))
|
|
266
|
+
console.log(chalk.red(` • Update the identifier in your workspace config to match the registry`))
|
|
267
|
+
console.log(chalk.red(` • Or delete the relevant config fields and re-run to set up fresh\n`))
|
|
268
|
+
return false
|
|
269
|
+
}
|
|
270
|
+
} else {
|
|
271
|
+
// Name set but no identifier — adopt from registry
|
|
272
|
+
await this.WorkspaceConfig.setConfigValue([keyConfigStructKey, 'identifier'], registryData.did)
|
|
273
|
+
console.log(chalk.green(` ✓ Adopted key identity for "${keyConfig.name}" from registry`))
|
|
274
|
+
console.log(chalk.green(` DID: ${registryData.did}\n`))
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// --- Project Rack identity ---
|
|
280
|
+
if (rackConfig?.name) {
|
|
281
|
+
const registryData = await this.HomeRegistry.getRack(rackConfig.name)
|
|
282
|
+
const registryPath = await this.HomeRegistry.getRackPath(rackConfig.name)
|
|
283
|
+
|
|
284
|
+
if (registryData) {
|
|
285
|
+
if (rackConfig.identifier) {
|
|
286
|
+
if (registryData.did !== rackConfig.identifier) {
|
|
287
|
+
console.log(chalk.red(`\n✗ Project Rack Identity Mismatch\n`))
|
|
288
|
+
console.log(chalk.red(` The rack identifier in your config does not match the registry.\n`))
|
|
289
|
+
console.log(chalk.white(` Config identifier:`))
|
|
290
|
+
console.log(chalk.white(` ${rackConfig.identifier}`))
|
|
291
|
+
console.log(chalk.white(` Registry identifier (${registryPath}):`))
|
|
292
|
+
console.log(chalk.white(` ${registryData.did}\n`))
|
|
293
|
+
console.log(chalk.red(` To fix this, either:`))
|
|
294
|
+
console.log(chalk.red(` • Update the identifier in your workspace config to match the registry`))
|
|
295
|
+
console.log(chalk.red(` • Or delete the relevant config fields and re-run to set up fresh\n`))
|
|
296
|
+
return false
|
|
297
|
+
}
|
|
298
|
+
} else {
|
|
299
|
+
// Name set but no identifier — adopt from registry
|
|
300
|
+
await this.WorkspaceConfig.setConfigValue([rackConfigStructKey, 'identifier'], registryData.did)
|
|
301
|
+
console.log(chalk.green(` ✓ Adopted project rack identity for "${rackConfig.name}" from registry`))
|
|
302
|
+
console.log(chalk.green(` DID: ${registryData.did}\n`))
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return true
|
|
308
|
+
}
|
|
309
|
+
},
|
|
310
|
+
runCli: {
|
|
311
|
+
type: CapsulePropertyTypes.Function,
|
|
312
|
+
value: async function (this: any, argv: string[]): Promise<void> {
|
|
313
|
+
|
|
314
|
+
const program = new Command()
|
|
315
|
+
.option('--trace', 'Detailed logging for debugging and performance tuning.')
|
|
316
|
+
.option('--yes', 'Confirm all questions with default values.')
|
|
317
|
+
.option('--now', 'Fetch fresh data instead of using cached values.')
|
|
318
|
+
|
|
319
|
+
// Check for flags without parsing (to avoid consuming argv)
|
|
320
|
+
const hasYesFlag = argv.includes('--yes')
|
|
321
|
+
const hasNowFlag = argv.includes('--now')
|
|
322
|
+
|
|
323
|
+
// Set cliOptions for use by other capsules
|
|
324
|
+
this.cliOptions = { yes: hasYesFlag, now: hasNowFlag }
|
|
325
|
+
this.WorkspacePrompt.cliOptions = { yes: hasYesFlag }
|
|
326
|
+
|
|
327
|
+
// Ensure workspace config base fields (rootDir, rootConfigFilepath)
|
|
328
|
+
await this.WorkspaceConfig.ensureConfigBase()
|
|
329
|
+
|
|
330
|
+
// Ensure home registry directory is configured
|
|
331
|
+
await this.HomeRegistry.ensureRootDir()
|
|
332
|
+
|
|
333
|
+
// Ensure workspace identity fields (name, identifier) — requires registry
|
|
334
|
+
await this.WorkspaceConfig.ensureConfigIdentity()
|
|
335
|
+
|
|
336
|
+
// Validate identities: adopt from registry if only name is set, halt on mismatch
|
|
337
|
+
const identityValid = await this.validateIdentities()
|
|
338
|
+
if (!identityValid) return
|
|
339
|
+
|
|
340
|
+
// Ensure root key is configured and valid
|
|
341
|
+
const rootKey = await this.RootKey.ensureKey()
|
|
342
|
+
if (!rootKey) return
|
|
343
|
+
|
|
344
|
+
// Ensure signing key is configured and valid
|
|
345
|
+
const signingKey = await this.SigningKey.ensureKey()
|
|
346
|
+
if (!signingKey) return
|
|
347
|
+
|
|
348
|
+
// Ensure workspace key is configured
|
|
349
|
+
await this.WorkspaceKey.ensureKey()
|
|
350
|
+
|
|
351
|
+
// Ensure project rack is configured
|
|
352
|
+
await this.ProjectRack.ensureRack()
|
|
353
|
+
|
|
354
|
+
// Ensure project identifiers exist in package.json descriptors
|
|
355
|
+
await this.WorkspaceProjects.ensureIdentifiers()
|
|
356
|
+
|
|
357
|
+
// Validate project catalogs configuration
|
|
358
|
+
const catalogsValid = await this.ProjectCatalogs.validate()
|
|
359
|
+
if (!catalogsValid) return
|
|
360
|
+
|
|
361
|
+
const cliConfig = await this.$config.config
|
|
362
|
+
const cliCommands = await this.cliCommands as Record<string, (args?: any) => Promise<void>>
|
|
363
|
+
|
|
364
|
+
for (const commandName in cliConfig?.cli?.commands) {
|
|
365
|
+
const commandConfig = cliConfig.cli.commands[commandName]
|
|
366
|
+
const { description, arguments: commandArgs } = commandConfig
|
|
367
|
+
|
|
368
|
+
const cmd = program
|
|
369
|
+
.command(commandName)
|
|
370
|
+
.description(description || '')
|
|
371
|
+
|
|
372
|
+
// Add arguments if defined
|
|
373
|
+
if (commandArgs) {
|
|
374
|
+
for (const argName in commandArgs) {
|
|
375
|
+
const argConfig = commandArgs[argName]
|
|
376
|
+
const argSyntax = argConfig.optional ? `[${argName}]` : `<${argName}>`
|
|
377
|
+
cmd.argument(argSyntax, argConfig.description || '')
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Add options if defined
|
|
382
|
+
const commandOptions = commandConfig.options
|
|
383
|
+
if (commandOptions) {
|
|
384
|
+
for (const optionName in commandOptions) {
|
|
385
|
+
const optionConfig = commandOptions[optionName]
|
|
386
|
+
if (optionConfig.value === 'optional') {
|
|
387
|
+
cmd.option(`--${optionName} [value]`, optionConfig.description || '')
|
|
388
|
+
} else if (optionConfig.value === 'required') {
|
|
389
|
+
cmd.option(`--${optionName} <value>`, optionConfig.description || '')
|
|
390
|
+
} else {
|
|
391
|
+
cmd.option(`--${optionName}`, optionConfig.description || '')
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
cmd.action(async function (...actionArgs) {
|
|
397
|
+
// Helper to convert hyphenated names to camelCase (e.g., 'dangerously-squash' -> 'dangerouslySquash')
|
|
398
|
+
const toCamelCase = (str: string) => {
|
|
399
|
+
return str.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase())
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Extract argument values (last arg is the command object)
|
|
403
|
+
const commandObj = actionArgs[actionArgs.length - 1]
|
|
404
|
+
const argValues: any = {}
|
|
405
|
+
|
|
406
|
+
if (commandArgs) {
|
|
407
|
+
const argNames = Object.keys(commandArgs)
|
|
408
|
+
argNames.forEach((name, index) => {
|
|
409
|
+
argValues[name] = actionArgs[index]
|
|
410
|
+
})
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// Extract option values
|
|
414
|
+
// Commander.js converts hyphenated options to camelCase in opts()
|
|
415
|
+
// Store them as camelCase in argValues for consistency
|
|
416
|
+
if (commandOptions) {
|
|
417
|
+
const opts = commandObj.opts()
|
|
418
|
+
for (const optionName in commandOptions) {
|
|
419
|
+
const camelCaseKey = toCamelCase(optionName)
|
|
420
|
+
argValues[camelCaseKey] = opts[camelCaseKey] || false
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Pass global options (like --yes, --now) from program to command args
|
|
425
|
+
const globalOpts = program.opts()
|
|
426
|
+
if (globalOpts.yes) {
|
|
427
|
+
argValues.yes = true
|
|
428
|
+
}
|
|
429
|
+
if (globalOpts.now) {
|
|
430
|
+
argValues.now = true
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
await cliCommands[commandName](argValues)
|
|
434
|
+
})
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
await program.parseAsync(argv)
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}, {
|
|
443
|
+
importMeta: import.meta,
|
|
444
|
+
importStack: makeImportStack(),
|
|
445
|
+
capsuleName: capsule['#'],
|
|
446
|
+
})
|
|
447
|
+
}
|
|
448
|
+
capsule['#'] = '@stream44.studio/t44/caps/WorkspaceCli'
|