@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,430 @@
|
|
|
1
|
+
|
|
2
|
+
// Error prefix for missing credentials in non-interactive mode
|
|
3
|
+
// Format: "MISSING_CREDENTIALS:provider:credentialName:message"
|
|
4
|
+
export const MISSING_CREDENTIALS_PREFIX = 'MISSING_CREDENTIALS:'
|
|
5
|
+
|
|
6
|
+
export function isMissingCredentialsError(error: any): { provider: string; credentialName: string; message: string } | null {
|
|
7
|
+
if (error?.message?.startsWith(MISSING_CREDENTIALS_PREFIX)) {
|
|
8
|
+
const parts = error.message.slice(MISSING_CREDENTIALS_PREFIX.length).split(':')
|
|
9
|
+
if (parts.length >= 3) {
|
|
10
|
+
return {
|
|
11
|
+
provider: parts[0],
|
|
12
|
+
credentialName: parts[1],
|
|
13
|
+
message: parts.slice(2).join(':')
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return null
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Global prompt queue to ensure sequential prompting and deduplication
|
|
21
|
+
const globalPromptQueue: Array<() => Promise<any>> = []
|
|
22
|
+
const globalActivePrompts: Record<string, Promise<any>> = {}
|
|
23
|
+
const globalShownTitles: Set<string> = new Set()
|
|
24
|
+
let globalIsProcessing = false
|
|
25
|
+
|
|
26
|
+
async function processGlobalQueue() {
|
|
27
|
+
if (globalIsProcessing || globalPromptQueue.length === 0) return
|
|
28
|
+
globalIsProcessing = true
|
|
29
|
+
|
|
30
|
+
while (globalPromptQueue.length > 0) {
|
|
31
|
+
const task = globalPromptQueue.shift()!
|
|
32
|
+
await task()
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
globalIsProcessing = false
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function capsule({
|
|
39
|
+
encapsulate,
|
|
40
|
+
CapsulePropertyTypes,
|
|
41
|
+
makeImportStack
|
|
42
|
+
}: {
|
|
43
|
+
encapsulate: any
|
|
44
|
+
CapsulePropertyTypes: any
|
|
45
|
+
makeImportStack: any
|
|
46
|
+
}) {
|
|
47
|
+
return encapsulate({
|
|
48
|
+
'#@stream44.studio/encapsulate/spine-contracts/CapsuleSpineContract.v0': {
|
|
49
|
+
'#@stream44.studio/encapsulate/structs/Capsule': {},
|
|
50
|
+
'#': {
|
|
51
|
+
WorkspaceCli: {
|
|
52
|
+
type: CapsulePropertyTypes.Mapping,
|
|
53
|
+
value: '@stream44.studio/t44/caps/WorkspaceCli'
|
|
54
|
+
},
|
|
55
|
+
WorkspaceTest: {
|
|
56
|
+
type: CapsulePropertyTypes.Mapping,
|
|
57
|
+
value: '@stream44.studio/t44/caps/ProjectTest'
|
|
58
|
+
},
|
|
59
|
+
prompt: {
|
|
60
|
+
type: CapsulePropertyTypes.Function,
|
|
61
|
+
value: async function (this: any, questions: any[]): Promise<any> {
|
|
62
|
+
// Check if --yes flag is set
|
|
63
|
+
const cliOptions = await this.WorkspaceCli.cliOptions
|
|
64
|
+
const yes = cliOptions?.yes || false
|
|
65
|
+
|
|
66
|
+
// If --yes is set, return default values
|
|
67
|
+
if (yes) {
|
|
68
|
+
const result: Record<string, any> = {}
|
|
69
|
+
for (const question of questions) {
|
|
70
|
+
result[question.name] = question.default
|
|
71
|
+
}
|
|
72
|
+
return result
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Otherwise, load inquirer and prompt user
|
|
76
|
+
const inquirer = await import('inquirer')
|
|
77
|
+
try {
|
|
78
|
+
return await inquirer.default.prompt(questions)
|
|
79
|
+
} catch (error: any) {
|
|
80
|
+
if (error.message?.includes('SIGINT') || error.message?.includes('force closed')) {
|
|
81
|
+
const chalk = (await import('chalk')).default
|
|
82
|
+
console.log(chalk.red('\n\nABORTED\n'))
|
|
83
|
+
process.exit(0)
|
|
84
|
+
}
|
|
85
|
+
throw error
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
confirm: {
|
|
90
|
+
type: CapsulePropertyTypes.Function,
|
|
91
|
+
value: async function (this: any, {
|
|
92
|
+
title,
|
|
93
|
+
description,
|
|
94
|
+
message,
|
|
95
|
+
defaultValue = false,
|
|
96
|
+
onSuccess,
|
|
97
|
+
onAbort
|
|
98
|
+
}: {
|
|
99
|
+
title?: string
|
|
100
|
+
description?: string | string[]
|
|
101
|
+
message: string
|
|
102
|
+
defaultValue?: boolean
|
|
103
|
+
onSuccess?: (confirmed: boolean) => Promise<void> | void
|
|
104
|
+
onAbort?: () => Promise<void> | void
|
|
105
|
+
}): Promise<boolean> {
|
|
106
|
+
const chalk = (await import('chalk')).default
|
|
107
|
+
|
|
108
|
+
if (title) {
|
|
109
|
+
console.log(chalk.cyan(`\n${title}\n`))
|
|
110
|
+
}
|
|
111
|
+
if (description) {
|
|
112
|
+
const lines = Array.isArray(description) ? description : [description]
|
|
113
|
+
for (const line of lines) {
|
|
114
|
+
console.log(chalk.gray(` ${line}`))
|
|
115
|
+
}
|
|
116
|
+
console.log('')
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Check if --yes flag is set
|
|
120
|
+
const cliOptions = await this.WorkspaceCli.cliOptions
|
|
121
|
+
const yes = cliOptions?.yes || false
|
|
122
|
+
|
|
123
|
+
let confirmed: boolean
|
|
124
|
+
|
|
125
|
+
// If --yes is set, return default value
|
|
126
|
+
if (yes) {
|
|
127
|
+
confirmed = defaultValue
|
|
128
|
+
} else {
|
|
129
|
+
try {
|
|
130
|
+
// Otherwise, load inquirer and prompt user
|
|
131
|
+
const inquirer = await import('inquirer')
|
|
132
|
+
const result = await inquirer.default.prompt([
|
|
133
|
+
{
|
|
134
|
+
type: 'confirm',
|
|
135
|
+
name: 'confirmed',
|
|
136
|
+
message,
|
|
137
|
+
default: defaultValue
|
|
138
|
+
}
|
|
139
|
+
])
|
|
140
|
+
confirmed = result.confirmed
|
|
141
|
+
} catch (error: any) {
|
|
142
|
+
if (error.message?.includes('SIGINT') || error.message?.includes('force closed')) {
|
|
143
|
+
console.log(chalk.red('\n\nABORTED\n'))
|
|
144
|
+
if (onAbort) {
|
|
145
|
+
await onAbort()
|
|
146
|
+
}
|
|
147
|
+
process.exit(0)
|
|
148
|
+
}
|
|
149
|
+
throw error
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (onSuccess) {
|
|
154
|
+
await onSuccess(confirmed)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return confirmed
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
input: {
|
|
161
|
+
type: CapsulePropertyTypes.Function,
|
|
162
|
+
value: async function (this: any, {
|
|
163
|
+
message,
|
|
164
|
+
defaultValue,
|
|
165
|
+
validate,
|
|
166
|
+
promptFactId
|
|
167
|
+
}: {
|
|
168
|
+
message: string
|
|
169
|
+
defaultValue?: string
|
|
170
|
+
validate?: (input: string) => boolean | string
|
|
171
|
+
promptFactId?: string
|
|
172
|
+
}): Promise<string> {
|
|
173
|
+
// Try to get value from env first (works in both interactive and non-interactive modes)
|
|
174
|
+
if (promptFactId) {
|
|
175
|
+
const envValue = this.getEnvValueForFactReference(promptFactId)
|
|
176
|
+
if (envValue) {
|
|
177
|
+
return envValue
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// In non-interactive mode, throw error with MISSING_CREDENTIALS prefix
|
|
182
|
+
if (!process.stdin.isTTY && promptFactId) {
|
|
183
|
+
// Extract provider from promptFactId
|
|
184
|
+
const providerMatch = promptFactId.match(/patterns\/([^/]+)\//)
|
|
185
|
+
const provider = providerMatch ? providerMatch[1] : 'unknown'
|
|
186
|
+
const credentialName = message.replace(/:$/, '')
|
|
187
|
+
|
|
188
|
+
throw new Error(
|
|
189
|
+
`MISSING_CREDENTIALS:${provider}:${credentialName}:` +
|
|
190
|
+
`Cannot prompt for "${message}" in non-interactive mode. ` +
|
|
191
|
+
`Please run interactively first to configure credentials, or set the corresponding env variable.`
|
|
192
|
+
)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Check for existing prompt with same ID
|
|
196
|
+
if (promptFactId && promptFactId in globalActivePrompts) {
|
|
197
|
+
return globalActivePrompts[promptFactId]
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Create the prompt task
|
|
201
|
+
const promptTask = async () => {
|
|
202
|
+
try {
|
|
203
|
+
// Check if --yes flag is set
|
|
204
|
+
const cliOptions = await this.WorkspaceCli.cliOptions
|
|
205
|
+
const yes = cliOptions?.yes || false
|
|
206
|
+
|
|
207
|
+
// If --yes is set, return default value
|
|
208
|
+
if (yes) {
|
|
209
|
+
if (!defaultValue) {
|
|
210
|
+
throw new Error(`Cannot use --yes flag without a default value for prompt: ${message}`)
|
|
211
|
+
}
|
|
212
|
+
return defaultValue
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Otherwise, load inquirer and prompt user
|
|
216
|
+
const inquirer = await import('inquirer')
|
|
217
|
+
const question: any = {
|
|
218
|
+
type: 'input',
|
|
219
|
+
name: 'value',
|
|
220
|
+
message,
|
|
221
|
+
validate
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Only add default if explicitly provided
|
|
225
|
+
if (defaultValue !== undefined) {
|
|
226
|
+
question.default = defaultValue
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const { value } = await inquirer.default.prompt([question])
|
|
230
|
+
return value
|
|
231
|
+
} catch (error: any) {
|
|
232
|
+
if (error.message?.includes('SIGINT') || error.message?.includes('force closed')) {
|
|
233
|
+
const chalk = (await import('chalk')).default
|
|
234
|
+
console.log(chalk.red('\n\nABORTED\n'))
|
|
235
|
+
process.exit(0)
|
|
236
|
+
}
|
|
237
|
+
throw error
|
|
238
|
+
} finally {
|
|
239
|
+
// Remove from active prompts when done
|
|
240
|
+
if (promptFactId) {
|
|
241
|
+
delete globalActivePrompts[promptFactId]
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Create promise and add to queue
|
|
247
|
+
const promise = new Promise<string>((resolve, reject) => {
|
|
248
|
+
globalPromptQueue.push(async () => {
|
|
249
|
+
try {
|
|
250
|
+
const result = await promptTask()
|
|
251
|
+
resolve(result)
|
|
252
|
+
} catch (error) {
|
|
253
|
+
reject(error)
|
|
254
|
+
}
|
|
255
|
+
})
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
// Track active prompt
|
|
259
|
+
if (promptFactId) {
|
|
260
|
+
globalActivePrompts[promptFactId] = promise
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Start processing queue (inlined to avoid ambient reference issues)
|
|
264
|
+
if (!globalIsProcessing && globalPromptQueue.length > 0) {
|
|
265
|
+
globalIsProcessing = true
|
|
266
|
+
|
|
267
|
+
// Process queue asynchronously
|
|
268
|
+
; (async () => {
|
|
269
|
+
while (globalPromptQueue.length > 0) {
|
|
270
|
+
const task = globalPromptQueue.shift()!
|
|
271
|
+
await task()
|
|
272
|
+
}
|
|
273
|
+
globalIsProcessing = false
|
|
274
|
+
})()
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return promise
|
|
278
|
+
}
|
|
279
|
+
},
|
|
280
|
+
getEnvValueForFactReference: {
|
|
281
|
+
type: CapsulePropertyTypes.Function,
|
|
282
|
+
value: function (this: any, factReference: string): string | undefined {
|
|
283
|
+
const workspaceTest = this.WorkspaceTest
|
|
284
|
+
if (!workspaceTest) return undefined
|
|
285
|
+
|
|
286
|
+
const envConfig = workspaceTest.env || {}
|
|
287
|
+
|
|
288
|
+
for (const [envVarName, envDef] of Object.entries(envConfig)) {
|
|
289
|
+
if ((envDef as any)?.factReference === factReference) {
|
|
290
|
+
return workspaceTest.getEnvValue(envVarName)
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
return undefined
|
|
294
|
+
}
|
|
295
|
+
},
|
|
296
|
+
select: {
|
|
297
|
+
type: CapsulePropertyTypes.Function,
|
|
298
|
+
value: async function (this: any, {
|
|
299
|
+
message,
|
|
300
|
+
choices,
|
|
301
|
+
defaultValue,
|
|
302
|
+
pageSize
|
|
303
|
+
}: {
|
|
304
|
+
message: string
|
|
305
|
+
choices: Array<{ name: string; value: any; disabled?: boolean | string }>
|
|
306
|
+
defaultValue?: any
|
|
307
|
+
pageSize?: number
|
|
308
|
+
}): Promise<any> {
|
|
309
|
+
// Check if --yes flag is set
|
|
310
|
+
const cliOptions = await this.WorkspaceCli.cliOptions
|
|
311
|
+
const yes = cliOptions?.yes || false
|
|
312
|
+
|
|
313
|
+
// If --yes is set, return default value
|
|
314
|
+
if (yes) {
|
|
315
|
+
if (defaultValue === undefined) {
|
|
316
|
+
throw new Error(`Cannot use --yes flag without a default value for select: ${message}`)
|
|
317
|
+
}
|
|
318
|
+
return defaultValue
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Otherwise, load inquirer and prompt user
|
|
322
|
+
const inquirer = await import('inquirer')
|
|
323
|
+
try {
|
|
324
|
+
// Resolve defaultValue to the actual choice reference for inquirer v12
|
|
325
|
+
// inquirer v12 matches default by === on choice.value, so we need
|
|
326
|
+
// the exact reference from the choices array, not a separate object.
|
|
327
|
+
let resolvedDefault: any = undefined
|
|
328
|
+
if (defaultValue !== undefined) {
|
|
329
|
+
const match = choices.find(c =>
|
|
330
|
+
JSON.stringify(c.value) === JSON.stringify(defaultValue)
|
|
331
|
+
)
|
|
332
|
+
if (match) resolvedDefault = match.value
|
|
333
|
+
}
|
|
334
|
+
const { value } = await inquirer.default.prompt([
|
|
335
|
+
{
|
|
336
|
+
type: 'list',
|
|
337
|
+
name: 'value',
|
|
338
|
+
message,
|
|
339
|
+
choices,
|
|
340
|
+
default: resolvedDefault,
|
|
341
|
+
pageSize: pageSize || 10
|
|
342
|
+
}
|
|
343
|
+
])
|
|
344
|
+
return value
|
|
345
|
+
} catch (error: any) {
|
|
346
|
+
if (error.message?.includes('SIGINT') || error.message?.includes('force closed')) {
|
|
347
|
+
const chalk = (await import('chalk')).default
|
|
348
|
+
console.log(chalk.red('\n\nABORTED\n'))
|
|
349
|
+
process.exit(0)
|
|
350
|
+
}
|
|
351
|
+
throw error
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
},
|
|
355
|
+
setupPrompt: {
|
|
356
|
+
type: CapsulePropertyTypes.Function,
|
|
357
|
+
value: async function (this: any, {
|
|
358
|
+
title,
|
|
359
|
+
description,
|
|
360
|
+
message,
|
|
361
|
+
defaultValue,
|
|
362
|
+
validate,
|
|
363
|
+
configPath,
|
|
364
|
+
configKey,
|
|
365
|
+
onSuccess,
|
|
366
|
+
onAbort
|
|
367
|
+
}: {
|
|
368
|
+
title: string
|
|
369
|
+
description?: string | string[]
|
|
370
|
+
message: string
|
|
371
|
+
defaultValue: string
|
|
372
|
+
validate?: (input: string) => boolean | string
|
|
373
|
+
configPath: string[]
|
|
374
|
+
configKey?: string
|
|
375
|
+
onSuccess?: (value: string) => Promise<void> | void
|
|
376
|
+
onAbort?: () => Promise<void> | void
|
|
377
|
+
}): Promise<string> {
|
|
378
|
+
const chalk = (await import('chalk')).default
|
|
379
|
+
|
|
380
|
+
console.log(chalk.cyan(`\n${title}\n`))
|
|
381
|
+
if (description) {
|
|
382
|
+
const lines = Array.isArray(description) ? description : [description]
|
|
383
|
+
for (const line of lines) {
|
|
384
|
+
console.log(chalk.gray(` ${line}`))
|
|
385
|
+
}
|
|
386
|
+
console.log('')
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
try {
|
|
390
|
+
const value = await this.input({
|
|
391
|
+
message,
|
|
392
|
+
defaultValue,
|
|
393
|
+
validate
|
|
394
|
+
})
|
|
395
|
+
|
|
396
|
+
if (onSuccess) {
|
|
397
|
+
await onSuccess(value)
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
const displayKey = configKey || configPath.join('.')
|
|
401
|
+
console.log(chalk.green(`\n ✓ Updated config with ${displayKey}: ${value}\n`))
|
|
402
|
+
|
|
403
|
+
return value
|
|
404
|
+
} catch (error: any) {
|
|
405
|
+
if (error.message?.includes('SIGINT') || error.message?.includes('force closed')) {
|
|
406
|
+
console.log(chalk.red('\n\nABORTED\n'))
|
|
407
|
+
if (onAbort) {
|
|
408
|
+
await onAbort()
|
|
409
|
+
}
|
|
410
|
+
process.exit(0)
|
|
411
|
+
}
|
|
412
|
+
throw error
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}, {
|
|
419
|
+
importMeta: import.meta,
|
|
420
|
+
importStack: makeImportStack(),
|
|
421
|
+
capsuleName: capsule['#'],
|
|
422
|
+
ambientReferences: {
|
|
423
|
+
globalPromptQueue,
|
|
424
|
+
globalActivePrompts,
|
|
425
|
+
globalShownTitles,
|
|
426
|
+
globalIsProcessing
|
|
427
|
+
}
|
|
428
|
+
})
|
|
429
|
+
}
|
|
430
|
+
capsule['#'] = '@stream44.studio/t44/caps/WorkspacePrompt'
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
|
|
3
|
+
set -e
|
|
4
|
+
|
|
5
|
+
function w_log {
|
|
6
|
+
if [[ "$1" == "var" ]]; then
|
|
7
|
+
echo "\033[1;37m[$F_WORKSPACE_BASENAME] $2\033[0m"
|
|
8
|
+
elif [[ "$1" == "info" ]]; then
|
|
9
|
+
echo "\033[0;36m[$F_WORKSPACE_BASENAME] $2\033[0m"
|
|
10
|
+
elif [[ "$1" == "start" ]]; then
|
|
11
|
+
echo "\033[0;36m[$F_WORKSPACE_BASENAME] $2 ...\033[0m"
|
|
12
|
+
elif [[ "$1" == "notice" ]]; then
|
|
13
|
+
echo "\n\033[0;36m[$F_WORKSPACE_BASENAME] NOTICE: $2 ...\033[0m\n"
|
|
14
|
+
elif [[ "$1" == "success" ]]; then
|
|
15
|
+
echo "\033[0;32m[$F_WORKSPACE_BASENAME] $2\033[0m"
|
|
16
|
+
elif [[ "$1" == "error" ]]; then
|
|
17
|
+
echo "\033[0;31m[$F_WORKSPACE_BASENAME] $2\033[0m" >&2
|
|
18
|
+
fi
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function initWorkspace {
|
|
22
|
+
|
|
23
|
+
#${COMMANDS}
|
|
24
|
+
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
if [ -z "$F_WORKSPACE_DIR" ]; then
|
|
29
|
+
w_log "error" "ERROR: 'F_WORKSPACE_DIR' env var not set!"
|
|
30
|
+
else
|
|
31
|
+
|
|
32
|
+
if [ -z "$F_WORKSPACE_BASENAME" ]; then
|
|
33
|
+
export F_WORKSPACE_BASENAME="$(basename "${F_WORKSPACE_DIR}")"
|
|
34
|
+
fi
|
|
35
|
+
|
|
36
|
+
initWorkspace ${@:1}
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
set +e
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
|
|
2
|
+
import { join, resolve, relative } from 'path'
|
|
3
|
+
import { readFile } from 'fs/promises'
|
|
4
|
+
import { $ } from 'bun'
|
|
5
|
+
|
|
6
|
+
export async function capsule({
|
|
7
|
+
encapsulate,
|
|
8
|
+
CapsulePropertyTypes,
|
|
9
|
+
makeImportStack
|
|
10
|
+
}: {
|
|
11
|
+
encapsulate: any
|
|
12
|
+
CapsulePropertyTypes: any
|
|
13
|
+
makeImportStack: any
|
|
14
|
+
}) {
|
|
15
|
+
return encapsulate({
|
|
16
|
+
'#@stream44.studio/encapsulate/spine-contracts/CapsuleSpineContract.v0': {
|
|
17
|
+
'#@stream44.studio/encapsulate/structs/Capsule': {},
|
|
18
|
+
'#@stream44.studio/t44/structs/WorkspaceCliConfig': {
|
|
19
|
+
as: '$WorkspaceCliConfig'
|
|
20
|
+
},
|
|
21
|
+
'#@stream44.studio/t44/structs/WorkspaceShellConfig': {
|
|
22
|
+
as: '$ShellConfig'
|
|
23
|
+
},
|
|
24
|
+
'#': {
|
|
25
|
+
WorkspaceConfig: {
|
|
26
|
+
type: CapsulePropertyTypes.Mapping,
|
|
27
|
+
value: '@stream44.studio/t44/caps/WorkspaceConfig'
|
|
28
|
+
},
|
|
29
|
+
run: {
|
|
30
|
+
type: CapsulePropertyTypes.Function,
|
|
31
|
+
value: async function (this: any, { args }: any): Promise<void> {
|
|
32
|
+
|
|
33
|
+
const env = await this.$ShellConfig.env
|
|
34
|
+
const config = await this.WorkspaceConfig.config as any
|
|
35
|
+
const cliConfig = await this.$WorkspaceCliConfig.config
|
|
36
|
+
|
|
37
|
+
for (const name in env) {
|
|
38
|
+
process.stdout.write(`export ${name}="${env[name]}"\n`)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
process.stdout.write('\n')
|
|
42
|
+
|
|
43
|
+
// Load the shell script template
|
|
44
|
+
const shellScriptModule = import.meta.resolve('./WorkspaceShell.sh')
|
|
45
|
+
const shellScriptPath = shellScriptModule.replace('file://', '')
|
|
46
|
+
let shellScript = await readFile(shellScriptPath, 'utf-8')
|
|
47
|
+
|
|
48
|
+
// Generate commands dynamically from shell.commands
|
|
49
|
+
const shellConfig = await this.$ShellConfig.config
|
|
50
|
+
const shellCommands = shellConfig?.shell?.commands || {}
|
|
51
|
+
const cliCommands = cliConfig?.cli?.commands || {}
|
|
52
|
+
const commandNames = Object.keys(shellCommands).sort()
|
|
53
|
+
|
|
54
|
+
// Generate shell functions
|
|
55
|
+
const helpLines: string[] = []
|
|
56
|
+
for (const cmdName of commandNames) {
|
|
57
|
+
const cmdConfig = shellCommands[cmdName]
|
|
58
|
+
|
|
59
|
+
let cmdBody = ''
|
|
60
|
+
|
|
61
|
+
// Check if this is a cliCommand reference
|
|
62
|
+
if (cmdConfig.cliCommand) {
|
|
63
|
+
const cliCommandName = cmdConfig.cliCommand
|
|
64
|
+
// Auto-generate the shell code to call the CLI command
|
|
65
|
+
cmdBody = `(
|
|
66
|
+
cd "$F_WORKSPACE_DIR" &&
|
|
67
|
+
"\${F_WORKSPACE_IMPL_DIR}/bin/workspace" ${cliCommandName} \$@
|
|
68
|
+
)`
|
|
69
|
+
} else {
|
|
70
|
+
cmdBody = cmdConfig.cmd || ''
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
helpLines.push(` function ${cmdName} {`)
|
|
74
|
+
for (const line of cmdBody.split('\n')) {
|
|
75
|
+
helpLines.push(` ${line}`)
|
|
76
|
+
}
|
|
77
|
+
helpLines.push(` }`)
|
|
78
|
+
helpLines.push(``)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Generate alias for h if it exists
|
|
82
|
+
if (shellCommands.h) {
|
|
83
|
+
helpLines.push(` alias h='cd "\${F_WORKSPACE_DIR}"'`)
|
|
84
|
+
helpLines.push(``)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const commandsBlock = helpLines.join('\n')
|
|
88
|
+
|
|
89
|
+
// Replace the placeholder in the shell script
|
|
90
|
+
shellScript = shellScript.replace('#${COMMANDS}', commandsBlock)
|
|
91
|
+
|
|
92
|
+
// Remove the shebang line and output
|
|
93
|
+
process.stdout.write(shellScript.split('\n').slice(1).join('\n'))
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}, {
|
|
99
|
+
importMeta: import.meta,
|
|
100
|
+
importStack: makeImportStack(),
|
|
101
|
+
capsuleName: capsule['#'],
|
|
102
|
+
})
|
|
103
|
+
}
|
|
104
|
+
capsule['#'] = '@stream44.studio/t44/caps/WorkspaceShell'
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
$schema: ../../../../../../../.~o/workspace.foundation/@t44.sh~t44~caps~JsonSchemas/@t44.sh~t44~structs~WorkspaceConfigFile.json
|
|
2
|
+
"#@stream44.studio/t44/structs/WorkspaceShellConfig":
|
|
3
|
+
env:
|
|
4
|
+
default: null
|
|
5
|
+
force:
|
|
6
|
+
F_WORKSPACE_DIR: resolve('${__dirname}/..')
|
|
7
|
+
shell:
|
|
8
|
+
commands:
|
|
9
|
+
"44":
|
|
10
|
+
description: Run workspace CLI commands.
|
|
11
|
+
cmd: |
|
|
12
|
+
"${F_WORKSPACE_IMPL_DIR}/bin/t44" $@
|
|
13
|
+
help:
|
|
14
|
+
description: Display help for shell commands.
|
|
15
|
+
cmd: |
|
|
16
|
+
"${F_WORKSPACE_IMPL_DIR}/bin/shell" --help
|
|
17
|
+
a:
|
|
18
|
+
description: Re-activate workspace environment.
|
|
19
|
+
cmd: |
|
|
20
|
+
w_log "start" "Re-activating workspace"
|
|
21
|
+
|
|
22
|
+
source "$F_WORKSPACE_ACTIVATE_BIN_PATH"
|
|
23
|
+
|
|
24
|
+
w_log "success" "Workspace Activated OK!"
|
|
25
|
+
i:
|
|
26
|
+
description: Re-install workspace dependencies (removes node_modules and runs bun install).
|
|
27
|
+
cmd: |
|
|
28
|
+
w_log "start" "Re-installing workspace"
|
|
29
|
+
|
|
30
|
+
(
|
|
31
|
+
cd "${F_WORKSPACE_DIR}" &&
|
|
32
|
+
find . -name 'node_modules' -type d -prune -exec rm -rf {} + 2>/dev/null || true &&
|
|
33
|
+
bun install
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
w_log "success" "Workspace Re-installed OK!"
|
|
37
|
+
info:
|
|
38
|
+
description: Display workspace information.
|
|
39
|
+
cmd: |
|
|
40
|
+
(
|
|
41
|
+
cd "${F_WORKSPACE_DIR}" &&
|
|
42
|
+
"${F_WORKSPACE_IMPL_DIR}/bin/t44" info ${@:2}
|
|
43
|
+
)
|
|
44
|
+
p:
|
|
45
|
+
description: Push project repositories.
|
|
46
|
+
cmd: |
|
|
47
|
+
w_log "start" "Push project repositories"
|
|
48
|
+
|
|
49
|
+
(
|
|
50
|
+
cd "${F_WORKSPACE_DIR}" &&
|
|
51
|
+
"${F_WORKSPACE_IMPL_DIR}/bin/t44" push $@
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
w_log "success" "Project repositories pushed OK!"
|
|
55
|
+
h:
|
|
56
|
+
description: Navigate to workspace home directory.
|
|
57
|
+
cmd: |
|
|
58
|
+
cd "${F_WORKSPACE_DIR}"
|
|
59
|
+
dev:
|
|
60
|
+
cliCommand: dev
|
|
61
|
+
deploy:
|
|
62
|
+
cliCommand: deploy
|
|
63
|
+
createdAt: "2026-02-11T01:01:49.799Z"
|
|
64
|
+
updatedAt: "2026-02-11T01:01:49.799Z"
|