@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,234 @@
|
|
|
1
|
+
|
|
2
|
+
import { join, resolve } from 'path'
|
|
3
|
+
import { $ } from 'bun'
|
|
4
|
+
import chalk from 'chalk'
|
|
5
|
+
|
|
6
|
+
// āā Provider Lifecycle Steps āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
7
|
+
// Each pull provider capsule can implement these methods:
|
|
8
|
+
//
|
|
9
|
+
// 1. parseUrl ā parse a URL and return { owner, repo, branch } or null
|
|
10
|
+
// 2. prepare ā clone/fetch the mirror repo, checkout the branch
|
|
11
|
+
// 3. pull ā generate diff and apply to sourceDir
|
|
12
|
+
//
|
|
13
|
+
// Every method receives { config, ctx } where:
|
|
14
|
+
// config = the provider's own config entry (with .capsule and .config)
|
|
15
|
+
// ctx = shared pullingContext for the current operation
|
|
16
|
+
|
|
17
|
+
export async function capsule({
|
|
18
|
+
encapsulate,
|
|
19
|
+
CapsulePropertyTypes,
|
|
20
|
+
makeImportStack
|
|
21
|
+
}: {
|
|
22
|
+
encapsulate: any
|
|
23
|
+
CapsulePropertyTypes: any
|
|
24
|
+
makeImportStack: any
|
|
25
|
+
}) {
|
|
26
|
+
return encapsulate({
|
|
27
|
+
'#@stream44.studio/encapsulate/spine-contracts/CapsuleSpineContract.v0': {
|
|
28
|
+
'#@stream44.studio/encapsulate/structs/Capsule': {},
|
|
29
|
+
'#@stream44.studio/t44/structs/WorkspaceConfig': {
|
|
30
|
+
as: '$WorkspaceConfig'
|
|
31
|
+
},
|
|
32
|
+
'#@stream44.studio/t44/structs/ProjectPullingConfig': {
|
|
33
|
+
as: '$WorkspacePulling'
|
|
34
|
+
},
|
|
35
|
+
'#@stream44.studio/t44/structs/ProjectPublishingConfig': {
|
|
36
|
+
as: '$WorkspaceRepositories'
|
|
37
|
+
},
|
|
38
|
+
'#': {
|
|
39
|
+
WorkspaceConfig: {
|
|
40
|
+
type: CapsulePropertyTypes.Mapping,
|
|
41
|
+
value: '@stream44.studio/t44/caps/WorkspaceConfig'
|
|
42
|
+
},
|
|
43
|
+
ProjectRepository: {
|
|
44
|
+
type: CapsulePropertyTypes.Mapping,
|
|
45
|
+
value: '@stream44.studio/t44/caps/ProjectRepository'
|
|
46
|
+
},
|
|
47
|
+
run: {
|
|
48
|
+
type: CapsulePropertyTypes.Function,
|
|
49
|
+
value: async function (this: any, { args }: any): Promise<void> {
|
|
50
|
+
|
|
51
|
+
const { url } = args
|
|
52
|
+
|
|
53
|
+
if (!url) {
|
|
54
|
+
console.error(chalk.red('\n[t44] ERROR: A URL argument is required.'))
|
|
55
|
+
console.error(chalk.red(' Usage: t44 pull <url>'))
|
|
56
|
+
console.error(chalk.red(' Example: t44 pull https://github.com/Stream44/FramespaceGenesis/tree/fixes\n'))
|
|
57
|
+
process.exit(1)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// āā Dynamic provider loader āāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
61
|
+
const providerCache = new Map<string, any>()
|
|
62
|
+
const getProvider = async (uri: string) => {
|
|
63
|
+
const cleanUri = uri.startsWith('#') ? uri.substring(1) : uri
|
|
64
|
+
if (!providerCache.has(cleanUri)) {
|
|
65
|
+
const { api } = await this.self.importCapsule({ uri: cleanUri })
|
|
66
|
+
providerCache.set(cleanUri, api)
|
|
67
|
+
}
|
|
68
|
+
return providerCache.get(cleanUri)!
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// āā Load pulling config āāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
72
|
+
const pullingConfig = await this.$WorkspacePulling.config
|
|
73
|
+
|
|
74
|
+
const globalProviders: any[] = Array.isArray(pullingConfig?.providers)
|
|
75
|
+
? pullingConfig.providers
|
|
76
|
+
: []
|
|
77
|
+
|
|
78
|
+
if (globalProviders.length === 0) {
|
|
79
|
+
console.error(chalk.red('\n[t44] ERROR: No pulling providers configured.'))
|
|
80
|
+
console.error(chalk.red(' Add providers under "#@stream44.studio/t44/structs/ProjectPullingConfig" in workspace.yaml\n'))
|
|
81
|
+
process.exit(1)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// āā STEP 1: parseUrl ā ask each provider to parse the URL āā
|
|
85
|
+
console.log(`\n[t44] Parsing URL: ${chalk.cyan(url)}\n`)
|
|
86
|
+
|
|
87
|
+
let parsedUrl: any = null
|
|
88
|
+
let matchedProviderConfig: any = null
|
|
89
|
+
|
|
90
|
+
for (const providerConfig of globalProviders) {
|
|
91
|
+
if (providerConfig.enabled === false) continue
|
|
92
|
+
|
|
93
|
+
const provider = await getProvider(providerConfig.capsule)
|
|
94
|
+
if (typeof provider.parseUrl !== 'function') continue
|
|
95
|
+
|
|
96
|
+
const result = await provider.parseUrl({ config: providerConfig, url })
|
|
97
|
+
if (result) {
|
|
98
|
+
parsedUrl = result
|
|
99
|
+
matchedProviderConfig = providerConfig
|
|
100
|
+
console.log(chalk.green(` ā URL parsed by '${providerConfig.capsule}'`))
|
|
101
|
+
console.log(chalk.gray(` owner: ${result.owner}, repo: ${result.repo}, branch: ${result.branch}\n`))
|
|
102
|
+
break
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (!parsedUrl || !matchedProviderConfig) {
|
|
107
|
+
console.error(chalk.red('\n[t44] ERROR: No pull provider could parse the URL.'))
|
|
108
|
+
console.error(chalk.red(` URL: ${url}`))
|
|
109
|
+
console.error(chalk.red(` Configured providers:`))
|
|
110
|
+
for (const p of globalProviders) {
|
|
111
|
+
console.error(chalk.red(` - ${p.capsule} (enabled: ${p.enabled !== false})`))
|
|
112
|
+
}
|
|
113
|
+
console.error(chalk.red(`\n Make sure a provider for this URL type is configured.\n`))
|
|
114
|
+
process.exit(1)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// āā STEP 2: Find matching publishing repository āāāāāā
|
|
118
|
+
const publishingConfig = await this.$WorkspaceRepositories.config
|
|
119
|
+
|
|
120
|
+
if (!publishingConfig?.repositories) {
|
|
121
|
+
console.error(chalk.red('\n[t44] ERROR: No publishing repositories configured.'))
|
|
122
|
+
console.error(chalk.red(' Cannot match URL to a local project without publishing config.\n'))
|
|
123
|
+
process.exit(1)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
let matchedRepoName: string | null = null
|
|
127
|
+
let matchedRepoConfig: any = null
|
|
128
|
+
|
|
129
|
+
for (const [repoName, repoConfig] of Object.entries(publishingConfig.repositories)) {
|
|
130
|
+
const typedConfig = repoConfig as any
|
|
131
|
+
const providers: any[] = Array.isArray(typedConfig.providers) ? typedConfig.providers : []
|
|
132
|
+
|
|
133
|
+
for (const provider of providers) {
|
|
134
|
+
// Match against GitHub publishing provider config
|
|
135
|
+
if (provider.capsule === '@stream44.studio/t44-github.com/caps/ProjectPublishing') {
|
|
136
|
+
const settings = provider.config?.RepositorySettings
|
|
137
|
+
if (settings?.owner === parsedUrl.owner && settings?.repo === parsedUrl.repo) {
|
|
138
|
+
matchedRepoName = repoName
|
|
139
|
+
matchedRepoConfig = typedConfig
|
|
140
|
+
break
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (matchedRepoName) break
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (!matchedRepoName || !matchedRepoConfig) {
|
|
148
|
+
console.error(chalk.red(`\n[t44] ERROR: No matching publishing repository found for ${parsedUrl.owner}/${parsedUrl.repo}.`))
|
|
149
|
+
console.error(chalk.red(` The URL points to '${parsedUrl.owner}/${parsedUrl.repo}' but no repository in`))
|
|
150
|
+
console.error(chalk.red(` "#@stream44.studio/t44/structs/ProjectPublishingConfig" has a GitHub provider with matching owner/repo.`))
|
|
151
|
+
console.error(chalk.red(`\n Configured repositories:`))
|
|
152
|
+
for (const rn of Object.keys(publishingConfig.repositories)) {
|
|
153
|
+
console.error(chalk.red(` - ${rn}`))
|
|
154
|
+
}
|
|
155
|
+
console.error('')
|
|
156
|
+
process.exit(1)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const sourceDir = resolve(matchedRepoConfig.sourceDir)
|
|
160
|
+
console.log(chalk.green(` ā Matched repository: '${matchedRepoName}'`))
|
|
161
|
+
console.log(chalk.gray(` sourceDir: ${sourceDir}\n`))
|
|
162
|
+
|
|
163
|
+
// āā STEP 3: prepare ā ensure sourceDir has no uncommitted changes āā
|
|
164
|
+
console.log('[t44] Preparing: checking for uncommitted changes ...\n')
|
|
165
|
+
|
|
166
|
+
const statusResult = await $`git status --porcelain`.cwd(sourceDir).quiet().nothrow()
|
|
167
|
+
const statusOutput = statusResult.text().trim()
|
|
168
|
+
|
|
169
|
+
if (statusOutput.length > 0) {
|
|
170
|
+
console.error(chalk.red(`\n[t44] ERROR: sourceDir has uncommitted changes. Commit or stash them first.`))
|
|
171
|
+
console.error(chalk.red(` sourceDir: ${sourceDir}`))
|
|
172
|
+
console.error(chalk.red(` Changes:\n`))
|
|
173
|
+
for (const line of statusOutput.split('\n').slice(0, 20)) {
|
|
174
|
+
console.error(chalk.red(` ${line}`))
|
|
175
|
+
}
|
|
176
|
+
if (statusOutput.split('\n').length > 20) {
|
|
177
|
+
console.error(chalk.red(` ... and ${statusOutput.split('\n').length - 20} more`))
|
|
178
|
+
}
|
|
179
|
+
console.error('')
|
|
180
|
+
process.exit(1)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
console.log(chalk.green(` ā sourceDir is clean\n`))
|
|
184
|
+
|
|
185
|
+
// āā STEP 4: Set up mirror repo āāāāāāāāāāāāāāāāāāāāāāā
|
|
186
|
+
// Reuse the publishing stage repo as a fast clone source if it exists.
|
|
187
|
+
// The mirror lives in its own ProjectPulling projection dir.
|
|
188
|
+
|
|
189
|
+
const pullingApi = {
|
|
190
|
+
getProjectionDir: (capsuleName: string) => join(
|
|
191
|
+
this.WorkspaceConfig.workspaceRootDir,
|
|
192
|
+
'.~o/workspace.foundation/@t44.sh~t44~caps~ProjectPulling',
|
|
193
|
+
capsuleName.replace(/\//g, '~')
|
|
194
|
+
),
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const ctx = {
|
|
198
|
+
parsedUrl,
|
|
199
|
+
matchedRepoName,
|
|
200
|
+
matchedRepoConfig,
|
|
201
|
+
sourceDir,
|
|
202
|
+
publishingConfig,
|
|
203
|
+
pullingApi,
|
|
204
|
+
metadata: {} as Record<string, any>,
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// āā STEP 5: Call provider prepare + pull āāāāāāāāāāāāā
|
|
208
|
+
const provider = await getProvider(matchedProviderConfig.capsule)
|
|
209
|
+
|
|
210
|
+
if (typeof provider.prepare === 'function') {
|
|
211
|
+
console.log('[t44] Preparing mirror repository ...\n')
|
|
212
|
+
await provider.prepare({ config: matchedProviderConfig, ctx })
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (typeof provider.pull !== 'function') {
|
|
216
|
+
console.error(chalk.red(`\n[t44] ERROR: Provider '${matchedProviderConfig.capsule}' does not implement 'pull'.`))
|
|
217
|
+
process.exit(1)
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
console.log('[t44] Pulling changes ...\n')
|
|
221
|
+
await provider.pull({ config: matchedProviderConfig, ctx })
|
|
222
|
+
|
|
223
|
+
console.log(chalk.green('\n[t44] Pull complete!'))
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}, {
|
|
229
|
+
importMeta: import.meta,
|
|
230
|
+
importStack: makeImportStack(),
|
|
231
|
+
capsuleName: capsule['#'],
|
|
232
|
+
})
|
|
233
|
+
}
|
|
234
|
+
capsule['#'] = '@stream44.studio/t44/caps/ProjectPulling'
|
|
@@ -0,0 +1,155 @@
|
|
|
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': {},
|
|
13
|
+
'#@stream44.studio/t44/structs/WorkspaceConfig': {
|
|
14
|
+
as: '$WorkspaceConfig'
|
|
15
|
+
},
|
|
16
|
+
'#@stream44.studio/t44/structs/ProjectRackConfig': {
|
|
17
|
+
as: '$ProjectRackConfig'
|
|
18
|
+
},
|
|
19
|
+
'#': {
|
|
20
|
+
WorkspacePrompt: {
|
|
21
|
+
type: CapsulePropertyTypes.Mapping,
|
|
22
|
+
value: '@stream44.studio/t44/caps/WorkspacePrompt'
|
|
23
|
+
},
|
|
24
|
+
HomeRegistry: {
|
|
25
|
+
type: CapsulePropertyTypes.Mapping,
|
|
26
|
+
value: '@stream44.studio/t44/caps/HomeRegistry'
|
|
27
|
+
},
|
|
28
|
+
ensureRack: {
|
|
29
|
+
type: CapsulePropertyTypes.Function,
|
|
30
|
+
value: async function (this: any): Promise<{ rackName: string }> {
|
|
31
|
+
const workspaceConfig = await this.$WorkspaceConfig.config
|
|
32
|
+
const rackConfig = await this.$ProjectRackConfig.config
|
|
33
|
+
|
|
34
|
+
// Check if projectRack is already set in config (object format: { name, identifier })
|
|
35
|
+
if (rackConfig?.name && rackConfig?.identifier) {
|
|
36
|
+
return { rackName: rackConfig.name }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
let rackName: string
|
|
40
|
+
|
|
41
|
+
const rackConfigStructKey = '#@stream44.studio/t44/structs/ProjectRackConfig'
|
|
42
|
+
if (!rackConfig?.name) {
|
|
43
|
+
const chalk = (await import('chalk')).default
|
|
44
|
+
|
|
45
|
+
console.log(chalk.cyan(`\nš¦ Project Rack Setup\n`))
|
|
46
|
+
console.log(chalk.gray(` Workspace: ${workspaceConfig?.name || 'unknown'}`))
|
|
47
|
+
console.log(chalk.gray(` Root: ${workspaceConfig?.rootDir || 'unknown'}`))
|
|
48
|
+
console.log(chalk.gray(''))
|
|
49
|
+
console.log(chalk.gray(' The project rack holds an integrated set of projects which can be'))
|
|
50
|
+
console.log(chalk.gray(' pulled into one or more workspaces.'))
|
|
51
|
+
console.log(chalk.gray(' A workspace attached to a rack has access to all projects in the rack'))
|
|
52
|
+
console.log(chalk.gray(' and is able to add more projects to the rack.'))
|
|
53
|
+
console.log(chalk.gray(' All workspaces attached to a rack automatically sync their projects'))
|
|
54
|
+
console.log(chalk.gray(' to the rack.'))
|
|
55
|
+
console.log(chalk.gray(''))
|
|
56
|
+
|
|
57
|
+
// List existing project racks from registry
|
|
58
|
+
const existingRacks = await this.HomeRegistry.listRacks()
|
|
59
|
+
|
|
60
|
+
// Build choices
|
|
61
|
+
const choices: Array<{ name: string; value: any }> = []
|
|
62
|
+
|
|
63
|
+
for (const rack of existingRacks) {
|
|
64
|
+
choices.push({
|
|
65
|
+
name: `${rack.name} ${chalk.gray(rack.did ? rack.did.substring(0, 50) + '...' : '')}`,
|
|
66
|
+
value: { type: 'existing', name: rack.name }
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
choices.push({
|
|
71
|
+
name: chalk.yellow('+ Create a new project rack'),
|
|
72
|
+
value: { type: 'create' }
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
const selected = await this.WorkspacePrompt.select({
|
|
76
|
+
message: 'Select a project rack:',
|
|
77
|
+
choices,
|
|
78
|
+
defaultValue: { type: 'create' },
|
|
79
|
+
pageSize: 15
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
if (selected.type === 'existing') {
|
|
83
|
+
rackName = selected.name
|
|
84
|
+
} else {
|
|
85
|
+
// Prompt for rack name
|
|
86
|
+
rackName = await this.WorkspacePrompt.input({
|
|
87
|
+
message: 'Enter a name for the new project rack:',
|
|
88
|
+
defaultValue: workspaceConfig?.name || 'genesis',
|
|
89
|
+
validate: (input: string) => {
|
|
90
|
+
if (!input || input.trim().length === 0) {
|
|
91
|
+
return 'Project rack name cannot be empty'
|
|
92
|
+
}
|
|
93
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(input)) {
|
|
94
|
+
return 'Project rack name can only contain letters, numbers, underscores, and hyphens'
|
|
95
|
+
}
|
|
96
|
+
return true
|
|
97
|
+
}
|
|
98
|
+
})
|
|
99
|
+
}
|
|
100
|
+
} else {
|
|
101
|
+
rackName = rackConfig.name
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Check if rack already exists in registry
|
|
105
|
+
let rackData = await this.HomeRegistry.getRack(rackName)
|
|
106
|
+
|
|
107
|
+
if (!rackData) {
|
|
108
|
+
const chalk = (await import('chalk')).default
|
|
109
|
+
console.log(chalk.cyan(`\n Registering project rack '${rackName}'...\n`))
|
|
110
|
+
|
|
111
|
+
const { generateKeypair } = await import('../lib/ucan.js')
|
|
112
|
+
const { did, privateKey } = await generateKeypair()
|
|
113
|
+
|
|
114
|
+
rackData = {
|
|
115
|
+
did,
|
|
116
|
+
privateKey,
|
|
117
|
+
createdAt: new Date().toISOString()
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const rackPath = await this.HomeRegistry.setRack(rackName, rackData)
|
|
121
|
+
|
|
122
|
+
console.log(chalk.green(` ā Project rack registered at:`))
|
|
123
|
+
console.log(chalk.green(` ${rackPath}`))
|
|
124
|
+
console.log(chalk.green(` ā DID: ${rackData.did}\n`))
|
|
125
|
+
} else {
|
|
126
|
+
const chalk = (await import('chalk')).default
|
|
127
|
+
const rackPath = await this.HomeRegistry.getRackPath(rackName)
|
|
128
|
+
console.log(chalk.green(`\n ā Using existing project rack at:`))
|
|
129
|
+
console.log(chalk.green(` ${rackPath}`))
|
|
130
|
+
console.log(chalk.green(` ā DID: ${rackData.did}\n`))
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Store rack as object { name, identifier } in rack config struct
|
|
134
|
+
await this.$ProjectRackConfig.setConfigValue(['name'], rackName)
|
|
135
|
+
await this.$ProjectRackConfig.setConfigValue(['identifier'], rackData.did)
|
|
136
|
+
|
|
137
|
+
return { rackName }
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
getRackName: {
|
|
141
|
+
type: CapsulePropertyTypes.Function,
|
|
142
|
+
value: async function (this: any): Promise<string | null> {
|
|
143
|
+
const rackConfig = await this.$ProjectRackConfig.config
|
|
144
|
+
return rackConfig?.name || null
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}, {
|
|
150
|
+
importMeta: import.meta,
|
|
151
|
+
importStack: makeImportStack(),
|
|
152
|
+
capsuleName: capsule['#'],
|
|
153
|
+
})
|
|
154
|
+
}
|
|
155
|
+
capsule['#'] = '@stream44.studio/t44/caps/ProjectRack'
|