@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.
Files changed (99) hide show
  1. package/.dco-signatures +9 -0
  2. package/.github/workflows/dco.yaml +12 -0
  3. package/.github/workflows/gordian-open-integrity.yaml +13 -0
  4. package/.github/workflows/test.yaml +31 -0
  5. package/.o/GordianOpenIntegrity-CurrentLifehash.svg +1026 -0
  6. package/.o/GordianOpenIntegrity-InceptionLifehash.svg +1026 -0
  7. package/.o/GordianOpenIntegrity.yaml +21 -0
  8. package/.o/assets/Hero-Terminal44-v0.jpeg +0 -0
  9. package/.o/stream44.studio/assets/Icon-v1.svg +1170 -0
  10. package/.repo-identifier +1 -0
  11. package/DCO.md +34 -0
  12. package/LICENSE.txt +186 -0
  13. package/README.md +189 -0
  14. package/bin/activate +36 -0
  15. package/bin/activate.ts +30 -0
  16. package/bin/postinstall.sh +19 -0
  17. package/bin/shell +27 -0
  18. package/bin/t44 +27 -0
  19. package/caps/ConfigSchemaStruct.ts +55 -0
  20. package/caps/Home.ts +57 -0
  21. package/caps/HomeRegistry.ts +319 -0
  22. package/caps/HomeRegistryFile.ts +144 -0
  23. package/caps/JsonSchemas.ts +220 -0
  24. package/caps/OpenApiSchema.ts +67 -0
  25. package/caps/PackageDescriptor.ts +88 -0
  26. package/caps/ProjectCatalogs.ts +153 -0
  27. package/caps/ProjectDeployment.ts +426 -0
  28. package/caps/ProjectDevelopment.ts +257 -0
  29. package/caps/ProjectPublishing.ts +654 -0
  30. package/caps/ProjectPulling.ts +234 -0
  31. package/caps/ProjectRack.ts +155 -0
  32. package/caps/ProjectRepository.ts +332 -0
  33. package/caps/ProjectTest.ts +251 -0
  34. package/caps/ProjectTestLib.ts +257 -0
  35. package/caps/RootKey.ts +219 -0
  36. package/caps/SigningKey.ts +243 -0
  37. package/caps/TaskWorkflow.ts +192 -0
  38. package/caps/WorkspaceCli.ts +448 -0
  39. package/caps/WorkspaceConfig.ts +268 -0
  40. package/caps/WorkspaceConfig.yaml +87 -0
  41. package/caps/WorkspaceConfigFile.ts +902 -0
  42. package/caps/WorkspaceConnection.ts +329 -0
  43. package/caps/WorkspaceEntityConfig.ts +78 -0
  44. package/caps/WorkspaceEntityConfig.v0.ts +77 -0
  45. package/caps/WorkspaceEntityFact.ts +218 -0
  46. package/caps/WorkspaceInfo.ts +619 -0
  47. package/caps/WorkspaceInit.ts +30 -0
  48. package/caps/WorkspaceKey.ts +338 -0
  49. package/caps/WorkspaceModel.ts +373 -0
  50. package/caps/WorkspaceProjects.ts +636 -0
  51. package/caps/WorkspacePrompt.ts +430 -0
  52. package/caps/WorkspaceShell.sh +39 -0
  53. package/caps/WorkspaceShell.ts +104 -0
  54. package/caps/WorkspaceShell.yaml +64 -0
  55. package/caps/WorkspaceShellCli.ts +109 -0
  56. package/caps/patterns/README.md +2 -0
  57. package/caps/patterns/git-scm.com/ProjectPublishing.ts +507 -0
  58. package/caps/patterns/semver.org/ProjectPublishing.ts +458 -0
  59. package/docs/Overview.drawio +248 -0
  60. package/docs/Overview.svg +4 -0
  61. package/examples/01-Lifecycle/main.test.ts +223 -0
  62. package/lib/crypto.ts +53 -0
  63. package/lib/key.ts +381 -0
  64. package/lib/schema-console-renderer.ts +181 -0
  65. package/lib/schema-resolver.ts +349 -0
  66. package/lib/ucan.ts +137 -0
  67. package/package.json +91 -0
  68. package/standalone-rt.test.ts +150 -0
  69. package/standalone-rt.ts +140 -0
  70. package/structs/HomeRegistry.ts +55 -0
  71. package/structs/HomeRegistryConfig.ts +60 -0
  72. package/structs/ProjectCatalogsConfig.ts +53 -0
  73. package/structs/ProjectDeploymentConfig.ts +56 -0
  74. package/structs/ProjectDeploymentFact.ts +106 -0
  75. package/structs/ProjectPublishingConfig.ts +78 -0
  76. package/structs/ProjectPublishingFact.ts +68 -0
  77. package/structs/ProjectPullingConfig.ts +52 -0
  78. package/structs/ProjectRack.ts +51 -0
  79. package/structs/ProjectRackConfig.ts +56 -0
  80. package/structs/RepositoryOriginDescriptor.ts +51 -0
  81. package/structs/RootKeyConfig.ts +64 -0
  82. package/structs/SigningKeyConfig.ts +64 -0
  83. package/structs/Workspace.ts +56 -0
  84. package/structs/WorkspaceCatalogs.ts +56 -0
  85. package/structs/WorkspaceCliConfig.ts +53 -0
  86. package/structs/WorkspaceConfig.ts +64 -0
  87. package/structs/WorkspaceConfigFile.ts +50 -0
  88. package/structs/WorkspaceConfigFileMeta.ts +70 -0
  89. package/structs/WorkspaceKey.ts +55 -0
  90. package/structs/WorkspaceKeyConfig.ts +56 -0
  91. package/structs/WorkspaceMappingsConfig.ts +56 -0
  92. package/structs/WorkspaceProject.ts +104 -0
  93. package/structs/WorkspaceProjectsConfig.ts +67 -0
  94. package/structs/WorkspaceShellConfig.ts +83 -0
  95. package/structs/patterns/README.md +2 -0
  96. package/structs/patterns/git-scm.com/ProjectPublishingFact.ts +46 -0
  97. package/tsconfig.json +33 -0
  98. package/workspace-rt.ts +152 -0
  99. package/workspace.yaml +3 -0
@@ -0,0 +1,243 @@
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/SigningKeyConfig': {
17
+ as: '$SigningKeyConfig'
18
+ },
19
+ '#': {
20
+ Home: {
21
+ type: CapsulePropertyTypes.Mapping,
22
+ value: '@stream44.studio/t44/caps/Home'
23
+ },
24
+ WorkspacePrompt: {
25
+ type: CapsulePropertyTypes.Mapping,
26
+ value: '@stream44.studio/t44/caps/WorkspacePrompt'
27
+ },
28
+ ensureKey: {
29
+ type: CapsulePropertyTypes.Function,
30
+ value: async function (this: any): Promise<{ keyName: string; privateKeyPath: string; publicKey: string; keyFingerprint: string } | null> {
31
+ const { join } = await import('path')
32
+ const { readFile, stat: statFile } = await import('fs/promises')
33
+ const chalk = (await import('chalk')).default
34
+ const {
35
+ discoverEd25519Keys,
36
+ validateConfiguredKey,
37
+ ensurePassphrase,
38
+ ensureKeyInAgent,
39
+ computeFingerprint,
40
+ promptPassphrase,
41
+ generateEd25519Key
42
+ } = await import('../lib/key.js')
43
+
44
+ const workspaceConfig = await this.$WorkspaceConfig.config
45
+ const keyConfig = await this.$SigningKeyConfig.config
46
+ const sshDir = await this.Home.sshDir
47
+
48
+ // --- Already configured: validate ---
49
+ if (keyConfig?.name && keyConfig?.privateKeyPath && keyConfig?.publicKey && keyConfig?.keyFingerprint) {
50
+ const valid = await validateConfiguredKey(keyConfig, 'Signing Key')
51
+ if (!valid) {
52
+ return null
53
+ }
54
+ return {
55
+ keyName: keyConfig.name,
56
+ privateKeyPath: keyConfig.privateKeyPath,
57
+ publicKey: keyConfig.publicKey,
58
+ keyFingerprint: keyConfig.keyFingerprint
59
+ }
60
+ }
61
+
62
+ // --- Not configured: discover or create ---
63
+ console.log(chalk.cyan(`\n🔏 Signing Key Setup\n`))
64
+ console.log(chalk.gray(` Workspace: ${workspaceConfig?.name || 'unknown'}`))
65
+ console.log(chalk.gray(` Root: ${workspaceConfig?.rootDir || 'unknown'}`))
66
+ console.log(chalk.gray(''))
67
+ console.log(chalk.gray(' The signing key is an Ed25519 SSH key used for code and artifact signing.'))
68
+ console.log(chalk.gray(` You can select an existing key from ${sshDir} or create a new one.`))
69
+ console.log(chalk.gray(''))
70
+
71
+ // Discover existing Ed25519 keys in ~/.ssh
72
+ const existingKeys = await discoverEd25519Keys(sshDir)
73
+
74
+ // Build choices
75
+ const choices: Array<{ name: string; value: any }> = []
76
+
77
+ for (const key of existingKeys) {
78
+ choices.push({
79
+ name: `${key.name} ${chalk.gray(key.publicKey.substring(0, 60) + '...')}`,
80
+ value: { type: 'existing', ...key }
81
+ })
82
+ }
83
+
84
+ choices.push({
85
+ name: chalk.yellow('+ Create a new Ed25519 key'),
86
+ value: { type: 'create' }
87
+ })
88
+
89
+ const selected = await this.WorkspacePrompt.select({
90
+ message: 'Select an Ed25519 key for signing:',
91
+ choices,
92
+ defaultValue: { type: 'create' },
93
+ pageSize: 15
94
+ })
95
+
96
+ let keyName: string
97
+ let privateKeyPath: string
98
+ let publicKey: string
99
+ let keyFingerprint: string
100
+
101
+ if (selected.type === 'existing') {
102
+ keyName = selected.name
103
+ privateKeyPath = selected.privateKeyPath
104
+ publicKey = selected.publicKey
105
+ keyFingerprint = computeFingerprint(selected.privateKeyPath)
106
+
107
+ // Ensure selected existing key has a passphrase
108
+ const passphraseOk = await ensurePassphrase(privateKeyPath, keyName, 'Signing key')
109
+ if (!passphraseOk) {
110
+ return null
111
+ }
112
+
113
+ // Add to ssh-agent
114
+ await ensureKeyInAgent(privateKeyPath, keyName, 'Signing key')
115
+ } else {
116
+ // Prompt for key name
117
+ keyName = await this.WorkspacePrompt.input({
118
+ message: 'Enter a name for the new signing key:',
119
+ defaultValue: 'id_t44_signing_ed25519',
120
+ validate: (input: string) => {
121
+ if (!input || input.trim().length === 0) {
122
+ return 'Key name cannot be empty'
123
+ }
124
+ if (!/^[a-zA-Z0-9_.-]+$/.test(input)) {
125
+ return 'Key name can only contain letters, numbers, underscores, dots, and hyphens'
126
+ }
127
+ return true
128
+ }
129
+ })
130
+
131
+ privateKeyPath = join(sshDir, keyName)
132
+
133
+ // Check if file already exists
134
+ let exists = false
135
+ try {
136
+ await statFile(privateKeyPath)
137
+ exists = true
138
+ } catch { }
139
+
140
+ if (exists) {
141
+ console.log(chalk.red(`\n ✗ File already exists: ${privateKeyPath}`))
142
+ console.log(chalk.red(` Choose a different name or select the existing key.\n`))
143
+ return null
144
+ }
145
+
146
+ // Prompt for passphrase
147
+ console.log(chalk.cyan(`\n Generating Ed25519 signing key '${keyName}'...`))
148
+ console.log(chalk.gray(` The key will be protected with a passphrase and added to the macOS Keychain.\n`))
149
+
150
+ const envPassphrase = process.env.T44_KEYS_PASSPHRASE
151
+ const passphrase = envPassphrase || await promptPassphrase()
152
+ if (!passphrase) {
153
+ console.log(chalk.red(`\n ✗ A passphrase is required for the signing key.\n`))
154
+ return null
155
+ }
156
+
157
+ const result = await generateEd25519Key(privateKeyPath, passphrase, 't44-signing-key')
158
+ if (!result) {
159
+ return null
160
+ }
161
+
162
+ publicKey = result.publicKey
163
+ keyFingerprint = result.keyFingerprint
164
+
165
+ console.log(chalk.green(` ✓ Signing key generated:`))
166
+ console.log(chalk.green(` ${privateKeyPath}`))
167
+ console.log(chalk.green(` ${privateKeyPath}.pub\n`))
168
+
169
+ // Add the new key to the ssh-agent with Keychain storage
170
+ if (!envPassphrase) {
171
+ await ensureKeyInAgent(privateKeyPath, keyName, 'Signing key')
172
+ }
173
+ }
174
+
175
+ // Store in config
176
+ await this.$SigningKeyConfig.setConfigValue(['name'], keyName)
177
+ await this.$SigningKeyConfig.setConfigValue(['privateKeyPath'], privateKeyPath)
178
+ await this.$SigningKeyConfig.setConfigValue(['publicKey'], publicKey)
179
+ await this.$SigningKeyConfig.setConfigValue(['keyFingerprint'], keyFingerprint)
180
+
181
+ console.log(chalk.green(` ✓ Signing key configured: ${keyName}`))
182
+ console.log(chalk.green(` ${keyFingerprint}\n`))
183
+
184
+ return { keyName, privateKeyPath, publicKey, keyFingerprint }
185
+ }
186
+ },
187
+ getKeyPath: {
188
+ type: CapsulePropertyTypes.Function,
189
+ value: async function (this: any): Promise<string | null> {
190
+ const keyConfig = await this.$SigningKeyConfig.config
191
+
192
+ if (!keyConfig?.privateKeyPath) {
193
+ return null
194
+ }
195
+
196
+ return keyConfig.privateKeyPath
197
+ }
198
+ },
199
+ getPublicKey: {
200
+ type: CapsulePropertyTypes.Function,
201
+ value: async function (this: any): Promise<string | null> {
202
+ const keyConfig = await this.$SigningKeyConfig.config
203
+
204
+ if (!keyConfig?.publicKey) {
205
+ return null
206
+ }
207
+
208
+ return keyConfig.publicKey
209
+ }
210
+ },
211
+ getFingerprint: {
212
+ type: CapsulePropertyTypes.Function,
213
+ value: async function (this: any): Promise<string | null> {
214
+ const keyConfig = await this.$SigningKeyConfig.config
215
+
216
+ if (!keyConfig?.keyFingerprint) {
217
+ return null
218
+ }
219
+
220
+ return keyConfig.keyFingerprint
221
+ }
222
+ },
223
+ getKeyName: {
224
+ type: CapsulePropertyTypes.Function,
225
+ value: async function (this: any): Promise<string | null> {
226
+ const keyConfig = await this.$SigningKeyConfig.config
227
+
228
+ if (!keyConfig?.name) {
229
+ return null
230
+ }
231
+
232
+ return keyConfig.name
233
+ }
234
+ }
235
+ }
236
+ }
237
+ }, {
238
+ importMeta: import.meta,
239
+ importStack: makeImportStack(),
240
+ capsuleName: capsule['#'],
241
+ })
242
+ }
243
+ capsule['#'] = '@stream44.studio/t44/caps/SigningKey'
@@ -0,0 +1,192 @@
1
+
2
+ type TaskType = 'serial' | 'parallel' | 'step';
3
+
4
+ type Task = {
5
+ type: TaskType;
6
+ name: string;
7
+ fn: () => void | Promise<void>;
8
+ children: Task[];
9
+ timeout?: number;
10
+ };
11
+
12
+ export async function capsule({
13
+ encapsulate,
14
+ CapsulePropertyTypes,
15
+ makeImportStack
16
+ }: {
17
+ encapsulate: any
18
+ CapsulePropertyTypes: any
19
+ makeImportStack: any
20
+ }) {
21
+ return encapsulate({
22
+ '#@stream44.studio/encapsulate/spine-contracts/CapsuleSpineContract.v0': {
23
+ '#@stream44.studio/encapsulate/structs/Capsule': {},
24
+ '#': {
25
+
26
+ lib: {
27
+ type: CapsulePropertyTypes.Mapping,
28
+ value: '@stream44.studio/t44/caps/ProjectTestLib',
29
+ },
30
+
31
+ _rootTasks: {
32
+ type: CapsulePropertyTypes.Literal,
33
+ value: [] as Task[],
34
+ },
35
+
36
+ _currentParent: {
37
+ type: CapsulePropertyTypes.Literal,
38
+ value: null as Task | null,
39
+ },
40
+
41
+ _breadcrumb: {
42
+ type: CapsulePropertyTypes.Literal,
43
+ value: [] as string[],
44
+ },
45
+
46
+ exitOnComplete: {
47
+ type: CapsulePropertyTypes.Literal,
48
+ value: true,
49
+ },
50
+
51
+ serial: {
52
+ type: CapsulePropertyTypes.Function,
53
+ value: function (this: any, name: string, fn: () => void | Promise<void>) {
54
+ const task: Task = {
55
+ type: 'serial',
56
+ name,
57
+ fn,
58
+ children: []
59
+ };
60
+
61
+ if (this._currentParent) {
62
+ this._currentParent.children.push(task);
63
+ } else {
64
+ this._rootTasks.push(task);
65
+ }
66
+
67
+ const previousParent = this._currentParent;
68
+ this._currentParent = task;
69
+ fn();
70
+ this._currentParent = previousParent;
71
+ }
72
+ },
73
+
74
+ parallel: {
75
+ type: CapsulePropertyTypes.Function,
76
+ value: function (this: any, name: string, fn: () => void | Promise<void>) {
77
+ const task: Task = {
78
+ type: 'parallel',
79
+ name,
80
+ fn,
81
+ children: []
82
+ };
83
+
84
+ if (this._currentParent) {
85
+ this._currentParent.children.push(task);
86
+ } else {
87
+ this._rootTasks.push(task);
88
+ }
89
+
90
+ const previousParent = this._currentParent;
91
+ this._currentParent = task;
92
+ fn();
93
+ this._currentParent = previousParent;
94
+ }
95
+ },
96
+
97
+ step: {
98
+ type: CapsulePropertyTypes.Function,
99
+ value: function (this: any, name: string, fn: () => void | Promise<void>, timeout?: number) {
100
+ const task: Task = {
101
+ type: 'step',
102
+ name,
103
+ fn,
104
+ children: [],
105
+ timeout,
106
+ };
107
+
108
+ if (this._currentParent) {
109
+ this._currentParent.children.push(task);
110
+ } else {
111
+ this._rootTasks.push(task);
112
+ }
113
+ }
114
+ },
115
+
116
+ _executeTask: {
117
+ type: CapsulePropertyTypes.Function,
118
+ value: async function (this: any, task: Task): Promise<void> {
119
+ const previousBreadcrumb = [...this._breadcrumb];
120
+ this._breadcrumb.push(task.name);
121
+ const trail = this._breadcrumb.join(' -> ');
122
+
123
+ const colors = {
124
+ reset: '\x1b[0m',
125
+ blue: '\x1b[34m',
126
+ cyan: '\x1b[36m',
127
+ magenta: '\x1b[35m',
128
+ green: '\x1b[32m',
129
+ red: '\x1b[31m',
130
+ };
131
+
132
+ let startSymbol = '▶';
133
+ let color = colors.reset;
134
+
135
+ if (task.type === 'serial') {
136
+ startSymbol = '⚡';
137
+ color = colors.blue;
138
+ } else if (task.type === 'parallel') {
139
+ startSymbol = '⚙';
140
+ color = colors.magenta;
141
+ } else {
142
+ startSymbol = '▸';
143
+ color = colors.cyan;
144
+ }
145
+
146
+ console.log(`${color}${startSymbol} ${trail}${colors.reset}`);
147
+
148
+ try {
149
+ if (task.type === 'serial') {
150
+ for (const child of task.children) {
151
+ await this._executeTask(child);
152
+ }
153
+ } else if (task.type === 'parallel') {
154
+ await Promise.all(task.children.map((child: Task) => this._executeTask(child)));
155
+ } else {
156
+ await task.fn();
157
+ }
158
+ console.log(`${colors.green}✓ ${trail}${colors.reset}`);
159
+ } catch (error) {
160
+ console.log(`${colors.red}✗ ${trail}${colors.reset}`);
161
+ throw error;
162
+ } finally {
163
+ this._breadcrumb = previousBreadcrumb;
164
+ }
165
+ }
166
+ },
167
+
168
+ run: {
169
+ type: CapsulePropertyTypes.Function,
170
+ value: async function (this: any): Promise<void> {
171
+ try {
172
+ for (const task of this._rootTasks) {
173
+ await this._executeTask(task);
174
+ }
175
+ } finally {
176
+ this._rootTasks = [];
177
+ this._breadcrumb = [];
178
+ this._currentParent = null;
179
+ }
180
+
181
+ if (this.exitOnComplete) process.exit(0);
182
+ }
183
+ },
184
+ }
185
+ }
186
+ }, {
187
+ importMeta: import.meta,
188
+ importStack: makeImportStack(),
189
+ capsuleName: capsule['#'],
190
+ })
191
+ }
192
+ capsule['#'] = '@stream44.studio/t44/caps/TaskWorkflow'