t44 0.2.0-rc.1

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.

Potentially problematic release.


This version of t44 might be problematic. Click here for more details.

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