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,298 @@
1
+ export async function capsule({
2
+ encapsulate,
3
+ CapsulePropertyTypes,
4
+ makeImportStack
5
+ }: {
6
+ encapsulate: any
7
+ CapsulePropertyTypes: any
8
+ makeImportStack: any
9
+ }) {
10
+ return encapsulate({
11
+ '#@stream44.studio/encapsulate/spine-contracts/CapsuleSpineContract.v0': {
12
+ '#@stream44.studio/encapsulate/structs/Capsule.v0': {},
13
+ '#t44/structs/HomeRegistryConfig.v0': {
14
+ as: '$HomeRegistryConfig'
15
+ },
16
+ '#': {
17
+ WorkspacePrompt: {
18
+ type: CapsulePropertyTypes.Mapping,
19
+ value: 't44/caps/WorkspacePrompt.v0'
20
+ },
21
+ rootDir: {
22
+ type: CapsulePropertyTypes.GetterFunction,
23
+ value: async function (this: any): Promise<string> {
24
+ const config = await this.$HomeRegistryConfig.config
25
+ if (!config?.rootDir) {
26
+ throw new Error('Home registry rootDir is not configured. Run ensureRootDir() first.')
27
+ }
28
+ return config.rootDir
29
+ }
30
+ },
31
+ ensureRootDir: {
32
+ type: CapsulePropertyTypes.Function,
33
+ value: async function (this: any): Promise<string> {
34
+ const { join } = await import('path')
35
+ const { stat, mkdir, readFile, writeFile } = await import('fs/promises')
36
+ const { homedir } = await import('os')
37
+ const chalk = (await import('chalk')).default
38
+
39
+ const config = await this.$HomeRegistryConfig.config
40
+ const defaultDir = join(homedir(), '.o/workspace.foundation')
41
+
42
+ let chosenDir: string
43
+
44
+ if (config?.rootDir) {
45
+ // Validate that the directory exists
46
+ try {
47
+ const s = await stat(config.rootDir)
48
+ if (!s.isDirectory()) {
49
+ throw new Error(`Home registry path '${config.rootDir}' exists but is not a directory.`)
50
+ }
51
+ } catch (error: any) {
52
+ if (error.code === 'ENOENT') {
53
+ throw new Error(`Home registry directory '${config.rootDir}' does not exist. Please create it or reconfigure.`)
54
+ }
55
+ throw error
56
+ }
57
+ chosenDir = config.rootDir
58
+ } else {
59
+ // rootDir not set — prompt user
60
+ console.log(chalk.cyan(`\n🏠 Home Registry Setup\n`))
61
+ console.log(chalk.gray(' The home registry is the place in your home directory that keeps'))
62
+ console.log(chalk.gray(' details about your workspaces and projects.'))
63
+ console.log('')
64
+
65
+ chosenDir = await this.WorkspacePrompt.input({
66
+ message: 'Enter the home registry directory:',
67
+ defaultValue: defaultDir,
68
+ validate: (input: string) => {
69
+ if (!input || input.trim().length === 0) {
70
+ return 'Directory path cannot be empty'
71
+ }
72
+ return true
73
+ }
74
+ })
75
+
76
+ // Check if directory exists
77
+ let dirExists = false
78
+ try {
79
+ const s = await stat(chosenDir)
80
+ dirExists = s.isDirectory()
81
+ } catch {
82
+ // Does not exist
83
+ }
84
+
85
+ if (dirExists) {
86
+ const confirmed = await this.WorkspacePrompt.confirm({
87
+ message: `Directory '${chosenDir}' already exists. Use this existing registry as the home registry for your workspace?`,
88
+ defaultValue: true
89
+ })
90
+
91
+ if (!confirmed) {
92
+ console.log(chalk.red('\n\nABORTED\n'))
93
+ process.exit(0)
94
+ }
95
+ } else {
96
+ // Create the directory
97
+ await mkdir(chosenDir, { recursive: true })
98
+ console.log(chalk.green(`\n ✓ Created home registry directory: ${chosenDir}\n`))
99
+ }
100
+
101
+ await this.$HomeRegistryConfig.setConfigValue(['rootDir'], chosenDir)
102
+ }
103
+
104
+ // Ensure registry.json exists at the root of the home registry
105
+ const registryFilePath = join(chosenDir, 'registry.json')
106
+ let registryData: any
107
+
108
+ try {
109
+ registryData = JSON.parse(await readFile(registryFilePath, 'utf-8'))
110
+ } catch {
111
+ // registry.json does not exist — generate identity
112
+ console.log(chalk.cyan(`\n Generating home registry identity...\n`))
113
+
114
+ const { generateKeypair } = await import('../lib/ucan.js')
115
+ const { did, privateKey } = await generateKeypair()
116
+
117
+ registryData = {
118
+ did,
119
+ privateKey,
120
+ createdAt: new Date().toISOString()
121
+ }
122
+
123
+ await writeFile(registryFilePath, JSON.stringify(registryData, null, 2), { mode: 0o600 })
124
+
125
+ console.log(chalk.green(` ✓ Registry identity saved to:`))
126
+ console.log(chalk.green(` ${registryFilePath}`))
127
+ console.log(chalk.green(` ✓ DID: ${registryData.did}\n`))
128
+ }
129
+
130
+ // Ensure identifier is set in config
131
+ if (!config?.identifier || config.identifier !== registryData.did) {
132
+ await this.$HomeRegistryConfig.setConfigValue(['identifier'], registryData.did)
133
+ }
134
+
135
+ return chosenDir
136
+ }
137
+ },
138
+ getRegistryPath: {
139
+ type: CapsulePropertyTypes.Function,
140
+ value: async function (this: any): Promise<string> {
141
+ const { join } = await import('path')
142
+ const rootDir = await this.rootDir
143
+ return join(rootDir, 'registry.json')
144
+ }
145
+ },
146
+ getRegistry: {
147
+ type: CapsulePropertyTypes.Function,
148
+ value: async function (this: any): Promise<any | null> {
149
+ const { join } = await import('path')
150
+ const { readFile } = await import('fs/promises')
151
+ const rootDir = await this.rootDir
152
+ const filePath = join(rootDir, 'registry.json')
153
+ try {
154
+ return JSON.parse(await readFile(filePath, 'utf-8'))
155
+ } catch {
156
+ return null
157
+ }
158
+ }
159
+ },
160
+ getWorkspace: {
161
+ type: CapsulePropertyTypes.Function,
162
+ value: async function (this: any, name: string): Promise<any | null> {
163
+ const { join } = await import('path')
164
+ const { readFile } = await import('fs/promises')
165
+
166
+ const rootDir = await this.rootDir
167
+ const filePath = join(rootDir, 'workspaces', `${name}.json`)
168
+ try {
169
+ return JSON.parse(await readFile(filePath, 'utf-8'))
170
+ } catch {
171
+ return null
172
+ }
173
+ }
174
+ },
175
+ setWorkspace: {
176
+ type: CapsulePropertyTypes.Function,
177
+ value: async function (this: any, name: string, data: { did: string; privateKey: string; createdAt: string; workspaceRootDir: string }): Promise<string> {
178
+ const { join } = await import('path')
179
+ const { writeFile, mkdir } = await import('fs/promises')
180
+
181
+ const rootDir = await this.rootDir
182
+ const dir = join(rootDir, 'workspaces')
183
+ const filePath = join(dir, `${name}.json`)
184
+ await mkdir(dir, { recursive: true })
185
+ await writeFile(filePath, JSON.stringify(data, null, 2), { mode: 0o600 })
186
+ return filePath
187
+ }
188
+ },
189
+ getWorkspacePath: {
190
+ type: CapsulePropertyTypes.Function,
191
+ value: async function (this: any, name: string): Promise<string> {
192
+ const { join } = await import('path')
193
+
194
+ const rootDir = await this.rootDir
195
+ return join(rootDir, 'workspaces', `${name}.json`)
196
+ }
197
+ },
198
+ getKey: {
199
+ type: CapsulePropertyTypes.Function,
200
+ value: async function (this: any, name: string): Promise<any | null> {
201
+ const { join } = await import('path')
202
+ const { readFile } = await import('fs/promises')
203
+
204
+ const rootDir = await this.rootDir
205
+ const filePath = join(rootDir, 'workspace-keys', `${name}.json`)
206
+ try {
207
+ return JSON.parse(await readFile(filePath, 'utf-8'))
208
+ } catch {
209
+ return null
210
+ }
211
+ }
212
+ },
213
+ setKey: {
214
+ type: CapsulePropertyTypes.Function,
215
+ value: async function (this: any, name: string, data: { did: string; privateKey: string; createdAt: string }): Promise<string> {
216
+ const { join } = await import('path')
217
+ const { writeFile, mkdir } = await import('fs/promises')
218
+
219
+ const rootDir = await this.rootDir
220
+ const dir = join(rootDir, 'workspace-keys')
221
+ const filePath = join(dir, `${name}.json`)
222
+ await mkdir(dir, { recursive: true })
223
+ await writeFile(filePath, JSON.stringify(data, null, 2), { mode: 0o600 })
224
+ return filePath
225
+ }
226
+ },
227
+ getKeyPath: {
228
+ type: CapsulePropertyTypes.Function,
229
+ value: async function (this: any, name: string): Promise<string> {
230
+ const { join } = await import('path')
231
+
232
+ const rootDir = await this.rootDir
233
+ return join(rootDir, 'workspace-keys', `${name}.json`)
234
+ }
235
+ },
236
+ keyExists: {
237
+ type: CapsulePropertyTypes.Function,
238
+ value: async function (this: any, name: string): Promise<boolean> {
239
+ const { join } = await import('path')
240
+ const { access } = await import('fs/promises')
241
+
242
+ const rootDir = await this.rootDir
243
+ const filePath = join(rootDir, 'workspace-keys', `${name}.json`)
244
+ try {
245
+ await access(filePath)
246
+ return true
247
+ } catch {
248
+ return false
249
+ }
250
+ }
251
+ },
252
+ getRack: {
253
+ type: CapsulePropertyTypes.Function,
254
+ value: async function (this: any, name: string): Promise<any | null> {
255
+ const { join } = await import('path')
256
+ const { readFile } = await import('fs/promises')
257
+
258
+ const rootDir = await this.rootDir
259
+ const filePath = join(rootDir, 'project-racks', `${name}.json`)
260
+ try {
261
+ return JSON.parse(await readFile(filePath, 'utf-8'))
262
+ } catch {
263
+ return null
264
+ }
265
+ }
266
+ },
267
+ setRack: {
268
+ type: CapsulePropertyTypes.Function,
269
+ value: async function (this: any, name: string, data: { did: string; privateKey: string; createdAt: string }): Promise<string> {
270
+ const { join } = await import('path')
271
+ const { writeFile, mkdir } = await import('fs/promises')
272
+
273
+ const rootDir = await this.rootDir
274
+ const dir = join(rootDir, 'project-racks')
275
+ const filePath = join(dir, `${name}.json`)
276
+ await mkdir(dir, { recursive: true })
277
+ await writeFile(filePath, JSON.stringify(data, null, 2), { mode: 0o600 })
278
+ return filePath
279
+ }
280
+ },
281
+ getRackPath: {
282
+ type: CapsulePropertyTypes.Function,
283
+ value: async function (this: any, name: string): Promise<string> {
284
+ const { join } = await import('path')
285
+
286
+ const rootDir = await this.rootDir
287
+ return join(rootDir, 'project-racks', `${name}.json`)
288
+ }
289
+ }
290
+ }
291
+ }
292
+ }, {
293
+ importMeta: import.meta,
294
+ importStack: makeImportStack(),
295
+ capsuleName: capsule['#'],
296
+ })
297
+ }
298
+ capsule['#'] = 't44/caps/HomeRegistry.v0'
@@ -0,0 +1,192 @@
1
+ import { join } from 'node:path'
2
+ import { mkdir, readFile, writeFile } from 'node:fs/promises'
3
+ import { convertOpenApiToJsonSchema, createAjvInstance } from '../lib/openapi.js'
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
+ '#': {
18
+ WorkspaceConfig: {
19
+ type: CapsulePropertyTypes.Mapping,
20
+ value: 't44/caps/WorkspaceConfig.v0'
21
+ },
22
+ url: {
23
+ type: CapsulePropertyTypes.Literal,
24
+ value: undefined,
25
+ },
26
+ definitions: {
27
+ type: CapsulePropertyTypes.Literal,
28
+ value: {}
29
+ },
30
+ resolveDefinition: {
31
+ type: CapsulePropertyTypes.Function,
32
+ value: async function (this: any, defName: string): Promise<any | null> {
33
+ const defValue = this.definitions[defName]
34
+ if (!defValue) return null
35
+
36
+ if (typeof defValue === 'object') {
37
+ return defValue
38
+ }
39
+
40
+ if (typeof defValue === 'string' && this.url) {
41
+ const cacheDir = join(
42
+ this.WorkspaceConfig.workspaceRootDir,
43
+ '.~o',
44
+ 'workspace.foundation',
45
+ 'WorkspaceEntityFacts',
46
+ 'OpenApiSchemas',
47
+ )
48
+
49
+ await mkdir(cacheDir, { recursive: true })
50
+
51
+ const schemaFile = join(cacheDir, this.url.replace(/\//g, '~'))
52
+
53
+ let openApiSpec: any
54
+ try {
55
+ const cached = await readFile(schemaFile, 'utf-8')
56
+ openApiSpec = JSON.parse(cached)
57
+ } catch (error) {
58
+ const response = await fetch(this.url)
59
+ if (!response.ok) {
60
+ return null
61
+ }
62
+ openApiSpec = await response.json()
63
+ await writeFile(schemaFile, JSON.stringify(openApiSpec, null, 2))
64
+ }
65
+
66
+ const refParts = defValue.replace(/^#\//, '').split('/')
67
+ let schema = openApiSpec
68
+ for (const part of refParts) {
69
+ const decodedPart = part.replace(/~1/g, '/').replace(/~0/g, '~')
70
+ schema = schema[decodedPart]
71
+ if (!schema) return null
72
+ }
73
+ return schema
74
+ }
75
+
76
+ return null
77
+ }
78
+ },
79
+ validate: {
80
+ type: CapsulePropertyTypes.Function,
81
+ value: async function (this: any, definitionName: string, data: any): Promise<{ warnings: any[], errors: any[] }> {
82
+ const warnings: any[] = []
83
+ const errors: any[] = []
84
+
85
+ const schemaRef = this.definitions[definitionName]
86
+ if (!schemaRef) {
87
+ errors.push({
88
+ type: 'schema_not_found',
89
+ message: `Definition "${definitionName}" not found in schema definitions`,
90
+ definitionName
91
+ })
92
+ return { warnings, errors }
93
+ }
94
+
95
+ let schema: any
96
+ let openApiSpec: any
97
+
98
+ if (typeof schemaRef === 'object') {
99
+ schema = schemaRef
100
+ openApiSpec = { definitions: this.definitions }
101
+ } else {
102
+ const cacheDir = join(
103
+ this.WorkspaceConfig.workspaceRootDir,
104
+ '.~o',
105
+ 'workspace.foundation',
106
+ 'WorkspaceEntityFacts',
107
+ 'OpenApiSchemas',
108
+ )
109
+
110
+ await mkdir(cacheDir, { recursive: true })
111
+
112
+ const schemaFile = join(cacheDir, this.url.replace(/\//g, '~'))
113
+
114
+ try {
115
+ const cached = await readFile(schemaFile, 'utf-8')
116
+ openApiSpec = JSON.parse(cached)
117
+ } catch (error) {
118
+ const response = await fetch(this.url)
119
+ if (!response.ok) {
120
+ errors.push({
121
+ type: 'fetch_failed',
122
+ message: `Failed to fetch OpenAPI spec from ${this.url}: ${response.statusText}`,
123
+ url: this.url
124
+ })
125
+ return { warnings, errors }
126
+ }
127
+ openApiSpec = await response.json()
128
+ await writeFile(schemaFile, JSON.stringify(openApiSpec, null, 2))
129
+ }
130
+
131
+ const refParts = schemaRef.replace(/^#\//, '').split('/')
132
+ schema = openApiSpec
133
+ for (const part of refParts) {
134
+ const decodedPart = part.replace(/~1/g, '/').replace(/~0/g, '~')
135
+ schema = schema[decodedPart]
136
+ if (!schema) {
137
+ errors.push({
138
+ type: 'schema_reference_not_found',
139
+ message: `Schema reference "${schemaRef}" not found in OpenAPI spec`,
140
+ schemaRef
141
+ })
142
+ return { warnings, errors }
143
+ }
144
+ }
145
+ }
146
+
147
+ const ajv = createAjvInstance()
148
+
149
+ const jsonSchema = convertOpenApiToJsonSchema(schema, openApiSpec, warnings)
150
+
151
+ let validate
152
+ try {
153
+ validate = ajv.compile(jsonSchema)
154
+ } catch (error: any) {
155
+ warnings.push({
156
+ type: 'schema_compilation_failed',
157
+ message: `Schema compilation failed for "${definitionName}": ${error.message}`,
158
+ definitionName,
159
+ error: error.message
160
+ })
161
+ return { warnings, errors }
162
+ }
163
+
164
+ if (!validate(data)) {
165
+ const validationErrors = validate.errors?.map(err => ({
166
+ path: err.instancePath || '/',
167
+ message: err.message,
168
+ params: err.params,
169
+ keyword: err.keyword,
170
+ schemaPath: err.schemaPath
171
+ })) || []
172
+
173
+ errors.push({
174
+ type: 'validation_failed',
175
+ message: `Data does not conform to schema "${definitionName}"`,
176
+ definitionName,
177
+ validationErrors
178
+ })
179
+ }
180
+
181
+ return { warnings, errors }
182
+ }
183
+ }
184
+ }
185
+ }
186
+ }, {
187
+ importMeta: import.meta,
188
+ importStack: makeImportStack(),
189
+ capsuleName: capsule['#'],
190
+ })
191
+ }
192
+ capsule['#'] = 't44/caps/OpenApiSchema.v0'