t44 0.4.0-rc.3
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.yml +12 -0
- package/.o/GordianOpenIntegrity-CurrentLifehash.svg +1026 -0
- package/.o/GordianOpenIntegrity-InceptionLifehash.svg +1026 -0
- package/.o/GordianOpenIntegrity.yaml +25 -0
- package/.o/assets/Hero-Terminal44-v0.jpeg +0 -0
- package/DCO.md +34 -0
- package/LICENSE.md +203 -0
- package/README.md +183 -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 +51 -0
- package/caps/HomeRegistry.ts +313 -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 +363 -0
- package/caps/ProjectDevelopment.ts +257 -0
- package/caps/ProjectPublishing.ts +522 -0
- package/caps/ProjectRack.ts +155 -0
- package/caps/ProjectRepository.ts +322 -0
- package/caps/RootKey.ts +219 -0
- package/caps/SigningKey.ts +243 -0
- package/caps/WorkspaceCli.ts +442 -0
- package/caps/WorkspaceConfig.ts +268 -0
- package/caps/WorkspaceConfig.yaml +71 -0
- package/caps/WorkspaceConfigFile.ts +799 -0
- package/caps/WorkspaceConnection.ts +249 -0
- package/caps/WorkspaceEntityConfig.ts +78 -0
- package/caps/WorkspaceEntityConfig.v0.ts +77 -0
- package/caps/WorkspaceEntityFact.ts +218 -0
- package/caps/WorkspaceInfo.ts +595 -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 +406 -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/WorkspaceTest.ts +167 -0
- package/caps/providers/README.md +2 -0
- package/caps/providers/bunny.net/ProjectDeployment.ts +327 -0
- package/caps/providers/bunny.net/api-pull.test.ts +319 -0
- package/caps/providers/bunny.net/api-pull.ts +164 -0
- package/caps/providers/bunny.net/api-storage.test.ts +168 -0
- package/caps/providers/bunny.net/api-storage.ts +248 -0
- package/caps/providers/bunny.net/api.ts +95 -0
- package/caps/providers/dynadot.com/ProjectDeployment.ts +202 -0
- package/caps/providers/dynadot.com/api-domains.test.ts +224 -0
- package/caps/providers/dynadot.com/api-domains.ts +169 -0
- package/caps/providers/dynadot.com/api-restful-v1.test.ts +190 -0
- package/caps/providers/dynadot.com/api-restful-v1.ts +94 -0
- package/caps/providers/dynadot.com/api-restful-v2.test.ts +200 -0
- package/caps/providers/dynadot.com/api-restful-v2.ts +94 -0
- package/caps/providers/git-scm.com/ProjectPublishing.ts +654 -0
- package/caps/providers/github.com/ProjectPublishing.ts +118 -0
- package/caps/providers/github.com/api.ts +115 -0
- package/caps/providers/npmjs.com/ProjectPublishing.ts +536 -0
- package/caps/providers/semver.org/ProjectPublishing.ts +286 -0
- package/caps/providers/vercel.com/ProjectDeployment.ts +326 -0
- package/caps/providers/vercel.com/api.test.ts +67 -0
- package/caps/providers/vercel.com/api.ts +132 -0
- package/caps/providers/vercel.com/bun.lock +194 -0
- package/caps/providers/vercel.com/package.json +10 -0
- package/caps/providers/vercel.com/project.test.ts +108 -0
- package/caps/providers/vercel.com/project.ts +150 -0
- package/caps/providers/vercel.com/tsconfig.json +28 -0
- package/docs/Overview.drawio +248 -0
- package/docs/Overview.svg +4 -0
- package/lib/crypto.ts +53 -0
- package/lib/key.ts +365 -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 +101 -0
- package/structs/HomeRegistry.ts +55 -0
- package/structs/HomeRegistryConfig.ts +56 -0
- package/structs/ProjectCatalogsConfig.ts +53 -0
- package/structs/ProjectDeploymentConfig.ts +56 -0
- package/structs/ProjectDeploymentFact.ts +106 -0
- package/structs/ProjectPublishingFact.ts +68 -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/WorkspacePublishingConfig.ts +65 -0
- package/structs/WorkspaceShellConfig.ts +83 -0
- package/structs/providers/README.md +2 -0
- package/structs/providers/bunny.net/PullZoneFact.ts +55 -0
- package/structs/providers/bunny.net/PullZoneListFact.ts +55 -0
- package/structs/providers/bunny.net/StorageZoneFact.ts +55 -0
- package/structs/providers/bunny.net/StorageZoneListFact.ts +55 -0
- package/structs/providers/bunny.net/WorkspaceConnectionConfig.ts +43 -0
- package/structs/providers/dynadot.com/DomainFact.ts +46 -0
- package/structs/providers/dynadot.com/WorkspaceConnectionConfig.ts +54 -0
- package/structs/providers/git-scm.com/ProjectPublishingFact.ts +46 -0
- package/structs/providers/github.com/ProjectPublishingFact.ts +46 -0
- package/structs/providers/github.com/WorkspaceConnectionConfig.ts +43 -0
- package/structs/providers/npmjs.com/ProjectPublishingFact.ts +46 -0
- package/structs/providers/vercel.com/ProjectDeploymentFact.ts +55 -0
- package/structs/providers/vercel.com/WorkspaceConnectionConfig.ts +49 -0
- package/tests/01-Lifecycle/main.test.ts +173 -0
- package/tsconfig.json +28 -0
- package/workspace-rt.ts +134 -0
- package/workspace.yaml +3 -0
|
@@ -0,0 +1,522 @@
|
|
|
1
|
+
|
|
2
|
+
import { join, resolve } from 'path'
|
|
3
|
+
import { $ } from 'bun'
|
|
4
|
+
import { mkdir, access, readFile, writeFile } from 'fs/promises'
|
|
5
|
+
import { constants } from 'fs'
|
|
6
|
+
import chalk from 'chalk'
|
|
7
|
+
|
|
8
|
+
export async function capsule({
|
|
9
|
+
encapsulate,
|
|
10
|
+
CapsulePropertyTypes,
|
|
11
|
+
makeImportStack
|
|
12
|
+
}: {
|
|
13
|
+
encapsulate: any
|
|
14
|
+
CapsulePropertyTypes: any
|
|
15
|
+
makeImportStack: any
|
|
16
|
+
}) {
|
|
17
|
+
return encapsulate({
|
|
18
|
+
'#@stream44.studio/encapsulate/spine-contracts/CapsuleSpineContract.v0': {
|
|
19
|
+
'#@stream44.studio/encapsulate/structs/Capsule': {},
|
|
20
|
+
'#t44/structs/WorkspaceConfig': {
|
|
21
|
+
as: '$WorkspaceConfig'
|
|
22
|
+
},
|
|
23
|
+
'#t44/structs/WorkspacePublishingConfig': {
|
|
24
|
+
as: '$WorkspaceRepositories'
|
|
25
|
+
},
|
|
26
|
+
'#t44/structs/WorkspaceProjectsConfig': {
|
|
27
|
+
as: '$WorkspaceProjectsConfig'
|
|
28
|
+
},
|
|
29
|
+
'#': {
|
|
30
|
+
WorkspaceConfig: {
|
|
31
|
+
type: CapsulePropertyTypes.Mapping,
|
|
32
|
+
value: 't44/caps/WorkspaceConfig'
|
|
33
|
+
},
|
|
34
|
+
WorkspaceProjects: {
|
|
35
|
+
type: CapsulePropertyTypes.Mapping,
|
|
36
|
+
value: 't44/caps/WorkspaceProjects'
|
|
37
|
+
},
|
|
38
|
+
ProjectRepository: {
|
|
39
|
+
type: CapsulePropertyTypes.Mapping,
|
|
40
|
+
value: 't44/caps/ProjectRepository'
|
|
41
|
+
},
|
|
42
|
+
SemverProvider: {
|
|
43
|
+
type: CapsulePropertyTypes.Mapping,
|
|
44
|
+
value: 't44/caps/providers/semver.org/ProjectPublishing'
|
|
45
|
+
},
|
|
46
|
+
GitRepository: {
|
|
47
|
+
type: CapsulePropertyTypes.Mapping,
|
|
48
|
+
value: 't44/caps/providers/git-scm.com/ProjectPublishing'
|
|
49
|
+
},
|
|
50
|
+
NpmRegistry: {
|
|
51
|
+
type: CapsulePropertyTypes.Mapping,
|
|
52
|
+
value: 't44/caps/providers/npmjs.com/ProjectPublishing'
|
|
53
|
+
},
|
|
54
|
+
GitHubRepository: {
|
|
55
|
+
type: CapsulePropertyTypes.Mapping,
|
|
56
|
+
value: 't44/caps/providers/github.com/ProjectPublishing'
|
|
57
|
+
},
|
|
58
|
+
ProjectRack: {
|
|
59
|
+
type: CapsulePropertyTypes.Mapping,
|
|
60
|
+
value: 't44/caps/ProjectRack'
|
|
61
|
+
},
|
|
62
|
+
HomeRegistry: {
|
|
63
|
+
type: CapsulePropertyTypes.Mapping,
|
|
64
|
+
value: 't44/caps/HomeRegistry'
|
|
65
|
+
},
|
|
66
|
+
ProjectCatalogs: {
|
|
67
|
+
type: CapsulePropertyTypes.Mapping,
|
|
68
|
+
value: 't44/caps/ProjectCatalogs'
|
|
69
|
+
},
|
|
70
|
+
run: {
|
|
71
|
+
type: CapsulePropertyTypes.Function,
|
|
72
|
+
value: async function (this: any, { args }: any): Promise<void> {
|
|
73
|
+
|
|
74
|
+
const { projectSelector, rc, release, bump, publish, dangerouslyResetMain, yesSignoff } = args
|
|
75
|
+
|
|
76
|
+
// Determine if this is a dry-run (default) or actual publish
|
|
77
|
+
const isDryRun = !rc && !release && !bump && !publish
|
|
78
|
+
const shouldBumpVersions = rc || release || bump
|
|
79
|
+
|
|
80
|
+
// Provider filter: when --publish <filter> is given, only matching providers run
|
|
81
|
+
const publishFilter = typeof publish === 'string' ? publish : null
|
|
82
|
+
const PROVIDER_FILTERS: Record<string, string[]> = {
|
|
83
|
+
git: [
|
|
84
|
+
't44/caps/providers/git-scm.com/ProjectPublishing',
|
|
85
|
+
't44/caps/providers/github.com/ProjectPublishing',
|
|
86
|
+
],
|
|
87
|
+
}
|
|
88
|
+
const isProviderIncluded = (capsuleName: string): boolean => {
|
|
89
|
+
if (!publishFilter) return true
|
|
90
|
+
const allowed = PROVIDER_FILTERS[publishFilter]
|
|
91
|
+
if (!allowed) {
|
|
92
|
+
console.log(`[t44] WARNING: Unknown provider filter '${publishFilter}', running all providers\n`)
|
|
93
|
+
return true
|
|
94
|
+
}
|
|
95
|
+
return allowed.includes(capsuleName)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const repositoriesConfig = await this.$WorkspaceRepositories.config
|
|
99
|
+
|
|
100
|
+
if (!repositoriesConfig?.repositories) {
|
|
101
|
+
throw new Error('No repositories configuration found')
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (dangerouslyResetMain && !projectSelector) {
|
|
105
|
+
throw new Error('--dangerously-reset-main flag requires a projectSelector or FORCE_FOR_ALL to be specified')
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
let matchingRepositories: Record<string, any>
|
|
109
|
+
|
|
110
|
+
if (!projectSelector || projectSelector === 'FORCE_FOR_ALL') {
|
|
111
|
+
matchingRepositories = repositoriesConfig.repositories
|
|
112
|
+
} else {
|
|
113
|
+
matchingRepositories = await this.WorkspaceProjects.resolveMatchingRepositories({
|
|
114
|
+
workspaceProject: projectSelector,
|
|
115
|
+
repositories: repositoriesConfig.repositories
|
|
116
|
+
})
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Show mode indicator
|
|
120
|
+
if (isDryRun) {
|
|
121
|
+
console.log('[t44] DRY-RUN MODE: Going through all motions without irreversible operations\n')
|
|
122
|
+
console.log('[t44] Use --rc, --release, or --bump to perform actual operations\n')
|
|
123
|
+
} else if (bump) {
|
|
124
|
+
console.log('[t44] BUMP MODE: Will bump versions but skip tagging and publishing\n')
|
|
125
|
+
} else if (publish) {
|
|
126
|
+
if (publishFilter) {
|
|
127
|
+
console.log(`[t44] PUBLISH MODE: Pushing current state to '${publishFilter}' providers only (no version bump or tagging)\n`)
|
|
128
|
+
} else {
|
|
129
|
+
console.log('[t44] PUBLISH MODE: Pushing current state to all providers (no version bump or tagging)\n')
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Phase 1: Copy source directories to stage location (git-tracked)
|
|
134
|
+
console.log('[t44] Syncing source directories to stage repos ...\n')
|
|
135
|
+
const stageSourceDirs: Map<string, string> = new Map()
|
|
136
|
+
|
|
137
|
+
const syncToStage = async (repoName: string, repoConfig: any) => {
|
|
138
|
+
const projectSourceDir = join((repoConfig as any).sourceDir)
|
|
139
|
+
const repoSourceDir = await this.ProjectRepository.getStagePath({ repoUri: repoName })
|
|
140
|
+
|
|
141
|
+
// Init git repo if not already
|
|
142
|
+
await this.ProjectRepository.init({ rootDir: repoSourceDir })
|
|
143
|
+
|
|
144
|
+
// Reset working tree to last commit before copying
|
|
145
|
+
await this.ProjectRepository.reset({ rootDir: repoSourceDir })
|
|
146
|
+
|
|
147
|
+
// Sync files from source to stage repo
|
|
148
|
+
const gitignorePath = join(projectSourceDir, '.gitignore')
|
|
149
|
+
await this.ProjectRepository.sync({
|
|
150
|
+
rootDir: repoSourceDir,
|
|
151
|
+
sourceDir: projectSourceDir,
|
|
152
|
+
gitignorePath
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
stageSourceDirs.set(repoName, repoSourceDir)
|
|
156
|
+
return repoSourceDir
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Update source package.json private field based on npm provider public config
|
|
160
|
+
for (const [, repoConfig] of Object.entries(matchingRepositories)) {
|
|
161
|
+
const providers = Array.isArray((repoConfig as any).providers)
|
|
162
|
+
? (repoConfig as any).providers
|
|
163
|
+
: (repoConfig as any).provider
|
|
164
|
+
? [(repoConfig as any).provider]
|
|
165
|
+
: []
|
|
166
|
+
|
|
167
|
+
for (const providerConfig of providers) {
|
|
168
|
+
if (providerConfig.capsule === 't44/caps/providers/npmjs.com/ProjectPublishing') {
|
|
169
|
+
const publicSetting = providerConfig.config?.PackageSettings?.public
|
|
170
|
+
if (publicSetting !== undefined) {
|
|
171
|
+
const projectSourceDir = join((repoConfig as any).sourceDir)
|
|
172
|
+
const sourcePackageJsonPath = join(projectSourceDir, 'package.json')
|
|
173
|
+
try {
|
|
174
|
+
const content = await readFile(sourcePackageJsonPath, 'utf-8')
|
|
175
|
+
const packageJson = JSON.parse(content)
|
|
176
|
+
const desiredPrivate = !publicSetting
|
|
177
|
+
if (packageJson.private !== desiredPrivate) {
|
|
178
|
+
packageJson.private = desiredPrivate
|
|
179
|
+
const indent = content.match(/^\{\s*\n([ \t]+)/)
|
|
180
|
+
const indentSize = indent ? indent[1].length : 2
|
|
181
|
+
await writeFile(sourcePackageJsonPath, JSON.stringify(packageJson, null, indentSize) + '\n', 'utf-8')
|
|
182
|
+
console.log(` ✓ Updated ${sourcePackageJsonPath} private: ${desiredPrivate}`)
|
|
183
|
+
}
|
|
184
|
+
} catch { }
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
for (const [repoName, repoConfig] of Object.entries(matchingRepositories)) {
|
|
191
|
+
console.log(`=> Syncing '${repoName}' ...`)
|
|
192
|
+
const repoSourceDir = await syncToStage(repoName, repoConfig)
|
|
193
|
+
console.log(` Synced to: ${repoSourceDir}\n`)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Helper to apply renames and resolve workspace dependencies on stage source dirs
|
|
197
|
+
const applyRenamesAndFinalize = async () => {
|
|
198
|
+
const matchingDirs = new Map(
|
|
199
|
+
Object.keys(matchingRepositories)
|
|
200
|
+
.filter(name => stageSourceDirs.has(name))
|
|
201
|
+
.map(name => [name, stageSourceDirs.get(name)!])
|
|
202
|
+
)
|
|
203
|
+
await this.SemverProvider.rename({
|
|
204
|
+
dirs: matchingDirs.values(),
|
|
205
|
+
repos: Object.fromEntries(matchingDirs)
|
|
206
|
+
})
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Phase 2: Detect source changes and bump versions
|
|
210
|
+
const bumpedRepos = new Set<string>()
|
|
211
|
+
|
|
212
|
+
if (shouldBumpVersions) {
|
|
213
|
+
if (rc) console.log('[t44] Release candidate mode enabled\n')
|
|
214
|
+
if (release) console.log('[t44] Release mode enabled\n')
|
|
215
|
+
if (bump) console.log('[t44] Bump mode enabled\n')
|
|
216
|
+
|
|
217
|
+
console.log('[t44] Bumping versions ...\n')
|
|
218
|
+
|
|
219
|
+
for (const [repoName, repoConfig] of Object.entries(matchingRepositories)) {
|
|
220
|
+
const repoSourceDir = stageSourceDirs.get(repoName)!
|
|
221
|
+
|
|
222
|
+
// Check if there are changes since last committed state
|
|
223
|
+
const hasChanges = await this.ProjectRepository.hasChanges({ rootDir: repoSourceDir })
|
|
224
|
+
|
|
225
|
+
if (!hasChanges) {
|
|
226
|
+
console.log(`=> Skipping bump for '${repoName}' (no changes)\n`)
|
|
227
|
+
continue
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
console.log(`=> Bumping version for '${repoName}' ...\n`)
|
|
231
|
+
|
|
232
|
+
const result = await this.SemverProvider.bump({
|
|
233
|
+
config: repoConfig,
|
|
234
|
+
options: { rc, release, bump }
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
if (result?.newVersion) {
|
|
238
|
+
bumpedRepos.add(repoName)
|
|
239
|
+
|
|
240
|
+
// Update version in stage repo's package.json too
|
|
241
|
+
const stagePackageJsonPath = join(repoSourceDir, 'package.json')
|
|
242
|
+
const stageContent = await readFile(stagePackageJsonPath, 'utf-8')
|
|
243
|
+
const stagePackageJson = JSON.parse(stageContent)
|
|
244
|
+
stagePackageJson.version = result.newVersion
|
|
245
|
+
const indent = stageContent.match(/^\{\s*\n([ \t]+)/)
|
|
246
|
+
const indentSize = indent ? indent[1].length : 2
|
|
247
|
+
await writeFile(stagePackageJsonPath, JSON.stringify(stagePackageJson, null, indentSize) + '\n', 'utf-8')
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
console.log('[t44] Version bump complete!\n')
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Phase 3: Apply renames and resolve workspace dependencies
|
|
255
|
+
await applyRenamesAndFinalize()
|
|
256
|
+
|
|
257
|
+
// Phase 4: Commit the final state for all repos that have changes
|
|
258
|
+
if (shouldBumpVersions && !bump) {
|
|
259
|
+
for (const [repoName] of Object.entries(matchingRepositories)) {
|
|
260
|
+
const repoSourceDir = stageSourceDirs.get(repoName)!
|
|
261
|
+
await this.ProjectRepository.commit({ rootDir: repoSourceDir, message: 'bump' })
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Helper to iterate providers with custom callback
|
|
266
|
+
const forEachProvider = async (callback: (params: {
|
|
267
|
+
repoName: string,
|
|
268
|
+
repoConfig: any,
|
|
269
|
+
providerConfig: any,
|
|
270
|
+
capsuleName: string,
|
|
271
|
+
repoSourceDir: string
|
|
272
|
+
}) => Promise<void>) => {
|
|
273
|
+
for (const [repoName, repoConfig] of Object.entries(matchingRepositories)) {
|
|
274
|
+
const providers = Array.isArray((repoConfig as any).providers)
|
|
275
|
+
? (repoConfig as any).providers
|
|
276
|
+
: (repoConfig as any).provider
|
|
277
|
+
? [(repoConfig as any).provider]
|
|
278
|
+
: []
|
|
279
|
+
|
|
280
|
+
const repoSourceDir = stageSourceDirs.get(repoName)!
|
|
281
|
+
for (const providerConfig of providers) {
|
|
282
|
+
const capsuleName = providerConfig.capsule
|
|
283
|
+
await callback({ repoName, repoConfig, providerConfig, capsuleName, repoSourceDir })
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Phase 4: Prepare all providers (copy from stage source to projection dirs)
|
|
289
|
+
console.log('[t44] Preparing providers ...\n')
|
|
290
|
+
const packageMetadata: Map<string, any> = new Map()
|
|
291
|
+
const gitMetadata: Map<string, any> = new Map()
|
|
292
|
+
|
|
293
|
+
await forEachProvider(async ({ repoName, repoConfig, providerConfig, capsuleName, repoSourceDir }) => {
|
|
294
|
+
if (!isProviderIncluded(capsuleName)) return
|
|
295
|
+
if (capsuleName === 't44/caps/providers/npmjs.com/ProjectPublishing') {
|
|
296
|
+
const metadata = await this.NpmRegistry.prepare({
|
|
297
|
+
config: { ...repoConfig, provider: providerConfig, sourceDir: repoSourceDir },
|
|
298
|
+
projectionDir: join(
|
|
299
|
+
this.WorkspaceConfig.workspaceRootDir,
|
|
300
|
+
'.~o/workspace.foundation/@t44.sh~t44~caps~ProjectPublishing/@t44.sh~t44~caps~providers~npmjs.com~ProjectPublishing'
|
|
301
|
+
),
|
|
302
|
+
repoSourceDir
|
|
303
|
+
})
|
|
304
|
+
packageMetadata.set(repoName, metadata)
|
|
305
|
+
} else if (capsuleName === 't44/caps/providers/github.com/ProjectPublishing' && !isDryRun) {
|
|
306
|
+
// Ensure GitHub repo exists before git-scm.com tries to clone from it
|
|
307
|
+
await this.GitHubRepository.push({ config: { ...repoConfig, provider: providerConfig, sourceDir: repoSourceDir } })
|
|
308
|
+
} else if (capsuleName === 't44/caps/providers/git-scm.com/ProjectPublishing') {
|
|
309
|
+
const metadata = await this.GitRepository.prepare({
|
|
310
|
+
config: { ...repoConfig, provider: providerConfig, sourceDir: repoSourceDir },
|
|
311
|
+
projectionDir: join(
|
|
312
|
+
this.WorkspaceConfig.workspaceRootDir,
|
|
313
|
+
'.~o/workspace.foundation/@t44.sh~t44~caps~ProjectPublishing/@t44.sh~t44~caps~providers~git-scm.com~ProjectPublishing'
|
|
314
|
+
)
|
|
315
|
+
})
|
|
316
|
+
gitMetadata.set(repoName, metadata)
|
|
317
|
+
}
|
|
318
|
+
})
|
|
319
|
+
|
|
320
|
+
// Phase 5: Tag git repos with version (only bumped repos)
|
|
321
|
+
if ((rc || release) && !isDryRun && !publish) {
|
|
322
|
+
const taggedRepos = new Set<string>()
|
|
323
|
+
await forEachProvider(async ({ repoName, repoConfig, providerConfig, capsuleName, repoSourceDir }) => {
|
|
324
|
+
if (capsuleName === 't44/caps/providers/git-scm.com/ProjectPublishing' && !taggedRepos.has(repoName)) {
|
|
325
|
+
if (!bumpedRepos.has(repoName)) {
|
|
326
|
+
console.log(` ○ Skipping tag for '${repoName}' (not bumped)\n`)
|
|
327
|
+
taggedRepos.add(repoName)
|
|
328
|
+
return
|
|
329
|
+
}
|
|
330
|
+
const metadata = gitMetadata.get(repoName)
|
|
331
|
+
if (!metadata?.stageDir) return
|
|
332
|
+
|
|
333
|
+
await this.GitRepository.tag({ metadata, repoSourceDir })
|
|
334
|
+
taggedRepos.add(repoName)
|
|
335
|
+
}
|
|
336
|
+
})
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Phase 5.5: Sync selected project repos to project rack registry
|
|
340
|
+
const rackName = await this.ProjectRack.getRackName()
|
|
341
|
+
if (rackName) {
|
|
342
|
+
const registryRootDir = await this.HomeRegistry.rootDir
|
|
343
|
+
const rackStructDir = 't44/structs/ProjectRack'.replace(/\//g, '~')
|
|
344
|
+
const rackCapsuleDir = 't44/caps/ProjectRepository'.replace(/\//g, '~')
|
|
345
|
+
const workspaceConfig = await this.$WorkspaceConfig.config
|
|
346
|
+
const workspaceRootDir = workspaceConfig?.rootDir
|
|
347
|
+
const projects = await this.WorkspaceProjects.list
|
|
348
|
+
|
|
349
|
+
// Determine which projects have matching repositories
|
|
350
|
+
const matchingProjectNames = new Set<string>()
|
|
351
|
+
if (workspaceRootDir) {
|
|
352
|
+
const { resolve, relative } = await import('path')
|
|
353
|
+
for (const [, repoConfig] of Object.entries(matchingRepositories)) {
|
|
354
|
+
const typedConfig = repoConfig as any
|
|
355
|
+
if (typedConfig.sourceDir) {
|
|
356
|
+
const resolvedSourceDir = resolve(typedConfig.sourceDir)
|
|
357
|
+
const relPath = relative(workspaceRootDir, resolvedSourceDir)
|
|
358
|
+
const topDir = relPath.split('/')[0]
|
|
359
|
+
matchingProjectNames.add(topDir)
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
console.log(`[t44] Syncing project repos to project rack '${rackName}' ...\n`)
|
|
365
|
+
|
|
366
|
+
for (const [projectName, projectData] of Object.entries(projects)) {
|
|
367
|
+
if (matchingProjectNames.size > 0 && !matchingProjectNames.has(projectName)) {
|
|
368
|
+
continue
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const project = projectData as any
|
|
372
|
+
const projectDid = project.identifier?.did
|
|
373
|
+
if (!projectDid) {
|
|
374
|
+
console.log(` ○ Skipping '${projectName}' (no project identifier)`)
|
|
375
|
+
continue
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const projectSourceDir = project.sourceDir
|
|
379
|
+
const rackRepoDir = join(registryRootDir, rackStructDir, rackName, rackCapsuleDir, projectDid)
|
|
380
|
+
try {
|
|
381
|
+
// Init bare repo in rack registry if needed
|
|
382
|
+
await this.ProjectRepository.initBare({ rootDir: rackRepoDir })
|
|
383
|
+
|
|
384
|
+
// Add remote to source repo if not present, or update URL
|
|
385
|
+
const remoteName = 't44/caps/ProjectRack'
|
|
386
|
+
const hasRemote = await this.ProjectRepository.hasRemote({ rootDir: projectSourceDir, name: remoteName })
|
|
387
|
+
if (!hasRemote) {
|
|
388
|
+
await this.ProjectRepository.addRemote({ rootDir: projectSourceDir, name: remoteName, url: rackRepoDir })
|
|
389
|
+
} else {
|
|
390
|
+
await this.ProjectRepository.setRemoteUrl({ rootDir: projectSourceDir, name: remoteName, url: rackRepoDir })
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// Push source repo to rack registry
|
|
394
|
+
const branch = await this.ProjectRepository.getBranch({ rootDir: projectSourceDir })
|
|
395
|
+
await this.ProjectRepository.pushToRemote({ rootDir: projectSourceDir, remote: remoteName, branch, force: true })
|
|
396
|
+
|
|
397
|
+
console.log(` ✓ Synced '${projectName}' to rack`)
|
|
398
|
+
} catch (error: any) {
|
|
399
|
+
const chalk = (await import('chalk')).default
|
|
400
|
+
console.log(chalk.red(`\n ✗ Failed to sync '${projectName}' to project rack '${rackName}'`))
|
|
401
|
+
console.log(chalk.red(` ${error.message || error}`))
|
|
402
|
+
console.log(chalk.red(`[t44] ABORT: Rack sync failed. Not pushing to external providers.\n`))
|
|
403
|
+
return
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
console.log(`[t44] Rack sync complete.\n`)
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// Phase 6a: Push all providers
|
|
411
|
+
if (isDryRun) {
|
|
412
|
+
console.log('[t44] DRY-RUN: Skipping publishing (would publish packages here)\n')
|
|
413
|
+
} else {
|
|
414
|
+
console.log('[t44] Publishing packages ...\n')
|
|
415
|
+
}
|
|
416
|
+
const processedRepos = new Set<string>()
|
|
417
|
+
await forEachProvider(async ({ repoName, repoConfig, providerConfig, capsuleName, repoSourceDir }) => {
|
|
418
|
+
if (!isProviderIncluded(capsuleName)) return
|
|
419
|
+
if (!processedRepos.has(repoName)) {
|
|
420
|
+
console.log(`\n=> Processing repository '${repoName}' ...\n`)
|
|
421
|
+
processedRepos.add(repoName)
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
if (isDryRun) {
|
|
425
|
+
console.log(` -> DRY-RUN: Skipping provider '${capsuleName}'\n`)
|
|
426
|
+
} else {
|
|
427
|
+
console.log(` -> Running provider '${capsuleName}' ...\n`)
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
if (capsuleName === 't44/caps/providers/github.com/ProjectPublishing' && !isDryRun) {
|
|
431
|
+
await this.GitHubRepository.push({
|
|
432
|
+
config: { ...repoConfig, provider: providerConfig, sourceDir: repoSourceDir }
|
|
433
|
+
})
|
|
434
|
+
} else if (capsuleName === 't44/caps/providers/git-scm.com/ProjectPublishing' && !isDryRun) {
|
|
435
|
+
await this.GitRepository.push({
|
|
436
|
+
config: { ...repoConfig, provider: providerConfig, sourceDir: repoSourceDir },
|
|
437
|
+
dangerouslyResetMain,
|
|
438
|
+
yesSignoff,
|
|
439
|
+
metadata: gitMetadata.get(repoName),
|
|
440
|
+
projectSourceDir: (repoConfig as any).sourceDir
|
|
441
|
+
})
|
|
442
|
+
} else if (capsuleName === 't44/caps/providers/npmjs.com/ProjectPublishing' && !isDryRun) {
|
|
443
|
+
await this.NpmRegistry.push({
|
|
444
|
+
config: { ...repoConfig, provider: providerConfig, sourceDir: repoSourceDir },
|
|
445
|
+
projectionDir: join(
|
|
446
|
+
this.WorkspaceConfig.workspaceRootDir,
|
|
447
|
+
'.~o/workspace.foundation/@t44.sh~t44~caps~ProjectPublishing/@t44.sh~t44~caps~providers~npmjs.com~ProjectPublishing'
|
|
448
|
+
),
|
|
449
|
+
metadata: packageMetadata.get(repoName)
|
|
450
|
+
})
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
if (!isDryRun) {
|
|
454
|
+
console.log(` <- Provider '${capsuleName}' complete.\n`)
|
|
455
|
+
}
|
|
456
|
+
})
|
|
457
|
+
|
|
458
|
+
for (const repoName of processedRepos) {
|
|
459
|
+
console.log(`<= Repository '${repoName}' processing complete.\n`)
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// Phase 6b: Update catalogs after all pushes complete
|
|
463
|
+
if (!isDryRun) {
|
|
464
|
+
const catalogRepos = new Set<string>()
|
|
465
|
+
await forEachProvider(async ({ repoName, repoConfig, providerConfig, capsuleName }) => {
|
|
466
|
+
if (!isProviderIncluded(capsuleName)) return
|
|
467
|
+
|
|
468
|
+
if (!catalogRepos.has(repoName)) {
|
|
469
|
+
catalogRepos.add(repoName)
|
|
470
|
+
const repoSourceDir_ = resolve((repoConfig as any).sourceDir)
|
|
471
|
+
const workspaceProjectName = await this.WorkspaceProjects.findProjectForPath({ targetPath: repoSourceDir_ }) || ''
|
|
472
|
+
await this.ProjectCatalogs.updateCatalogRepository({
|
|
473
|
+
repoName,
|
|
474
|
+
providerKey: '#' + capsule['#'],
|
|
475
|
+
providerData: {
|
|
476
|
+
sourceDir: repoSourceDir_,
|
|
477
|
+
workspaceProjectName,
|
|
478
|
+
},
|
|
479
|
+
})
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
if (capsuleName === 't44/caps/providers/github.com/ProjectPublishing') {
|
|
483
|
+
await this.GitHubRepository.afterPush({
|
|
484
|
+
repoName,
|
|
485
|
+
config: { ...repoConfig, provider: providerConfig, sourceDir: (repoConfig as any).sourceDir },
|
|
486
|
+
})
|
|
487
|
+
} else if (capsuleName === 't44/caps/providers/git-scm.com/ProjectPublishing') {
|
|
488
|
+
await this.GitRepository.afterPush({
|
|
489
|
+
repoName,
|
|
490
|
+
config: { ...repoConfig, provider: providerConfig, sourceDir: (repoConfig as any).sourceDir },
|
|
491
|
+
metadata: gitMetadata.get(repoName),
|
|
492
|
+
})
|
|
493
|
+
} else if (capsuleName === 't44/caps/providers/npmjs.com/ProjectPublishing') {
|
|
494
|
+
await this.NpmRegistry.afterPush({
|
|
495
|
+
repoName,
|
|
496
|
+
metadata: packageMetadata.get(repoName),
|
|
497
|
+
})
|
|
498
|
+
}
|
|
499
|
+
})
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
if (isDryRun) {
|
|
503
|
+
console.log('[t44] DRY-RUN complete! No irreversible operations were performed.')
|
|
504
|
+
console.log('[t44] To actually publish, use: t44 push --rc (for release candidate) or t44 push --release')
|
|
505
|
+
console.log('[t44] To bump versions only: t44 push --bump')
|
|
506
|
+
} else if (bump) {
|
|
507
|
+
console.log('[t44] Version bump complete! Versions updated in package.json files.')
|
|
508
|
+
console.log('[t44] To tag and publish, use: t44 push --rc or t44 push --release')
|
|
509
|
+
} else {
|
|
510
|
+
console.log('[t44] Project repositories pushed OK!')
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
}, {
|
|
517
|
+
importMeta: import.meta,
|
|
518
|
+
importStack: makeImportStack(),
|
|
519
|
+
capsuleName: capsule['#'],
|
|
520
|
+
})
|
|
521
|
+
}
|
|
522
|
+
capsule['#'] = 't44/caps/ProjectPublishing'
|