t44 0.4.0-rc.10
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/.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 +185 -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 +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 +807 -0
- package/caps/WorkspaceConnection.ts +256 -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 +133 -0
- package/caps/providers/github.com/api.ts +130 -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/examples/01-Lifecycle/main.test.ts +228 -0
- package/lib/crypto.ts +53 -0
- package/lib/key.ts +369 -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 +102 -0
- package/standalone-rt.ts +121 -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/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/tsconfig.json +28 -0
- package/workspace-rt.ts +134 -0
- package/workspace.yaml +3 -0
|
@@ -0,0 +1,536 @@
|
|
|
1
|
+
|
|
2
|
+
import { join } from 'path'
|
|
3
|
+
import { $ } from 'bun'
|
|
4
|
+
import { mkdir, access, writeFile, readFile } from 'fs/promises'
|
|
5
|
+
import { constants } from 'fs'
|
|
6
|
+
import glob from 'fast-glob'
|
|
7
|
+
import chalk from 'chalk'
|
|
8
|
+
import { createHash } from 'crypto'
|
|
9
|
+
|
|
10
|
+
function detectIndent(content: string): number {
|
|
11
|
+
const match = content.match(/^\{\s*\n([ \t]+)/)
|
|
12
|
+
if (match) {
|
|
13
|
+
return match[1].length
|
|
14
|
+
}
|
|
15
|
+
return 2
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function capsule({
|
|
19
|
+
encapsulate,
|
|
20
|
+
CapsulePropertyTypes,
|
|
21
|
+
makeImportStack
|
|
22
|
+
}: {
|
|
23
|
+
encapsulate: any
|
|
24
|
+
CapsulePropertyTypes: any
|
|
25
|
+
makeImportStack: any
|
|
26
|
+
}) {
|
|
27
|
+
return encapsulate({
|
|
28
|
+
'#@stream44.studio/encapsulate/spine-contracts/CapsuleSpineContract.v0': {
|
|
29
|
+
'#@stream44.studio/encapsulate/structs/Capsule': {},
|
|
30
|
+
'#t44/structs/providers/npmjs.com/ProjectPublishingFact': {
|
|
31
|
+
as: '$NpmFact'
|
|
32
|
+
},
|
|
33
|
+
'#t44/structs/ProjectPublishingFact': {
|
|
34
|
+
as: '$StatusFact'
|
|
35
|
+
},
|
|
36
|
+
'#': {
|
|
37
|
+
WorkspacePrompt: {
|
|
38
|
+
type: CapsulePropertyTypes.Mapping,
|
|
39
|
+
value: 't44/caps/WorkspacePrompt'
|
|
40
|
+
},
|
|
41
|
+
ProjectCatalogs: {
|
|
42
|
+
type: CapsulePropertyTypes.Mapping,
|
|
43
|
+
value: 't44/caps/ProjectCatalogs'
|
|
44
|
+
},
|
|
45
|
+
prepare: {
|
|
46
|
+
type: CapsulePropertyTypes.Function,
|
|
47
|
+
value: async function (this: any, { projectionDir, config }: { projectionDir: string, config: any }) {
|
|
48
|
+
|
|
49
|
+
const name = config.provider.config.PackageSettings.name
|
|
50
|
+
const projectSourceDir = join(config.sourceDir)
|
|
51
|
+
const stageDir = join(projectionDir, 'stage', name.replace(/[\/]/g, '~'))
|
|
52
|
+
|
|
53
|
+
await mkdir(stageDir, { recursive: true })
|
|
54
|
+
|
|
55
|
+
const gitignorePath = join(projectSourceDir, '.gitignore')
|
|
56
|
+
const npmignorePath = join(projectSourceDir, '.npmignore')
|
|
57
|
+
|
|
58
|
+
let gitignoreExists = false
|
|
59
|
+
let npmignoreExists = false
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
await access(gitignorePath, constants.F_OK)
|
|
63
|
+
gitignoreExists = true
|
|
64
|
+
} catch { }
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
await access(npmignorePath, constants.F_OK)
|
|
68
|
+
npmignoreExists = true
|
|
69
|
+
} catch { }
|
|
70
|
+
|
|
71
|
+
const rsyncArgs = ['rsync', '-a', '--delete', '--delete-excluded', '--exclude', '.git']
|
|
72
|
+
if (gitignoreExists) rsyncArgs.push('--exclude-from=' + gitignorePath)
|
|
73
|
+
if (npmignoreExists) rsyncArgs.push('--exclude-from=' + npmignorePath)
|
|
74
|
+
rsyncArgs.push(projectSourceDir + '/', stageDir + '/')
|
|
75
|
+
await $`${rsyncArgs}`
|
|
76
|
+
|
|
77
|
+
const packageJsonPath = join(stageDir, 'package.json')
|
|
78
|
+
const packageJsonContent = await readFile(packageJsonPath, 'utf-8')
|
|
79
|
+
const indent = detectIndent(packageJsonContent)
|
|
80
|
+
const packageJson = JSON.parse(packageJsonContent)
|
|
81
|
+
|
|
82
|
+
// Replace package name with public npm name
|
|
83
|
+
packageJson.name = name
|
|
84
|
+
|
|
85
|
+
const modifiedPackageJsonContent = JSON.stringify(packageJson, null, indent) + '\n'
|
|
86
|
+
if (modifiedPackageJsonContent !== packageJsonContent) {
|
|
87
|
+
await writeFile(packageJsonPath, modifiedPackageJsonContent, 'utf-8')
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
const localVersion = packageJson.version
|
|
92
|
+
|
|
93
|
+
let publishedInfo: any = null
|
|
94
|
+
let publishedFiles: Map<string, string> = new Map()
|
|
95
|
+
let hasChanges = false
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
const viewResult = await $`npm view ${name} --json`.cwd(stageDir).quiet()
|
|
99
|
+
const result = viewResult.text()
|
|
100
|
+
|
|
101
|
+
if (result && result.trim()) {
|
|
102
|
+
publishedInfo = JSON.parse(result)
|
|
103
|
+
|
|
104
|
+
if (publishedInfo && publishedInfo['dist-tags']) {
|
|
105
|
+
try {
|
|
106
|
+
// Determine which tag to compare against based on local version
|
|
107
|
+
const isReleaseCandidate = localVersion.includes('-rc.')
|
|
108
|
+
const compareTag = isReleaseCandidate ? 'next' : 'latest'
|
|
109
|
+
const distTags = publishedInfo['dist-tags']
|
|
110
|
+
const compareVersion = distTags[compareTag]
|
|
111
|
+
|
|
112
|
+
if (compareVersion) {
|
|
113
|
+
// Include version in directory name for proper caching
|
|
114
|
+
const mirrorDir = join(projectionDir, 'mirror', `${name.replace(/[\/]/g, '~')}@${compareVersion}`)
|
|
115
|
+
const publishedPackageDir = join(mirrorDir, 'package')
|
|
116
|
+
|
|
117
|
+
// Check if already downloaded
|
|
118
|
+
let alreadyDownloaded = false
|
|
119
|
+
try {
|
|
120
|
+
await access(publishedPackageDir, constants.F_OK)
|
|
121
|
+
alreadyDownloaded = true
|
|
122
|
+
} catch { }
|
|
123
|
+
|
|
124
|
+
if (!alreadyDownloaded) {
|
|
125
|
+
await mkdir(mirrorDir, { recursive: true })
|
|
126
|
+
|
|
127
|
+
await $`npm pack ${name}@${compareVersion}`.cwd(mirrorDir).quiet()
|
|
128
|
+
|
|
129
|
+
const tarballFiles = await glob('*.tgz', { cwd: mirrorDir, absolute: false })
|
|
130
|
+
if (tarballFiles.length > 0) {
|
|
131
|
+
await $`tar -xzf ${tarballFiles[0]}`.cwd(mirrorDir).quiet()
|
|
132
|
+
await $`rm ${tarballFiles[0]}`.cwd(mirrorDir).quiet()
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Read file hashes from extracted package
|
|
137
|
+
const publishedFilePaths = await glob('**/*', {
|
|
138
|
+
cwd: publishedPackageDir,
|
|
139
|
+
absolute: false,
|
|
140
|
+
onlyFiles: true
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
for (const filePath of publishedFilePaths) {
|
|
144
|
+
const fullPath = join(publishedPackageDir, filePath)
|
|
145
|
+
const fileBuffer = await readFile(fullPath)
|
|
146
|
+
const fileHash = createHash('sha1').update(fileBuffer).digest('hex')
|
|
147
|
+
publishedFiles.set(filePath, fileHash)
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
} catch (e) { }
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
} catch (error: any) {
|
|
154
|
+
hasChanges = true
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const localPackResult = await $`npm pack`.cwd(stageDir).quiet()
|
|
158
|
+
const localTarballName = localPackResult.text().trim().split('\n').pop()?.trim()
|
|
159
|
+
const localTarballPath = join(stageDir, localTarballName || '')
|
|
160
|
+
|
|
161
|
+
const localTarballBuffer = await readFile(localTarballPath)
|
|
162
|
+
const localShasum = createHash('sha1').update(localTarballBuffer).digest('hex')
|
|
163
|
+
const localIntegrity = 'sha512-' + createHash('sha512').update(localTarballBuffer).digest('base64')
|
|
164
|
+
|
|
165
|
+
await $`rm ${localTarballPath}`.quiet()
|
|
166
|
+
|
|
167
|
+
let versionExistsOnNpm = false
|
|
168
|
+
let versionMatchesChecksum = false
|
|
169
|
+
let localMatchesAnyTag = false
|
|
170
|
+
|
|
171
|
+
if (publishedInfo) {
|
|
172
|
+
// Check if local version exists in any tag
|
|
173
|
+
const distTags = publishedInfo['dist-tags'] || {}
|
|
174
|
+
for (const [tag, version] of Object.entries(distTags)) {
|
|
175
|
+
if (version === localVersion) {
|
|
176
|
+
versionExistsOnNpm = true
|
|
177
|
+
|
|
178
|
+
// Fetch version-specific info to check shasum
|
|
179
|
+
try {
|
|
180
|
+
const versionResult = await $`npm view ${name}@${version as string} --json`.cwd(stageDir).quiet()
|
|
181
|
+
const versionInfo = JSON.parse(versionResult.text())
|
|
182
|
+
const publishedShasum = versionInfo.dist?.shasum || ''
|
|
183
|
+
const publishedIntegrity = versionInfo.dist?.integrity || ''
|
|
184
|
+
|
|
185
|
+
if (publishedShasum === localShasum || publishedIntegrity === localIntegrity) {
|
|
186
|
+
versionMatchesChecksum = true
|
|
187
|
+
localMatchesAnyTag = true
|
|
188
|
+
}
|
|
189
|
+
} catch (e) { }
|
|
190
|
+
|
|
191
|
+
break
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (!publishedInfo || !localMatchesAnyTag) {
|
|
197
|
+
hasChanges = true
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return {
|
|
201
|
+
name,
|
|
202
|
+
localVersion,
|
|
203
|
+
projectSourceDir,
|
|
204
|
+
stageDir,
|
|
205
|
+
publishedInfo,
|
|
206
|
+
localShasum,
|
|
207
|
+
localIntegrity,
|
|
208
|
+
versionExistsOnNpm,
|
|
209
|
+
versionMatchesChecksum,
|
|
210
|
+
localMatchesAnyTag,
|
|
211
|
+
hasChanges,
|
|
212
|
+
publishedFiles
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
},
|
|
216
|
+
push: {
|
|
217
|
+
type: CapsulePropertyTypes.Function,
|
|
218
|
+
value: async function (this: any, { projectionDir, config, metadata }: { projectionDir: string, config: any, metadata?: any }) {
|
|
219
|
+
|
|
220
|
+
if (!metadata) {
|
|
221
|
+
throw new Error('Push method requires metadata from prepare phase')
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Check if package.json has private: true
|
|
225
|
+
const stagePackageJsonPath = join(metadata.stageDir, 'package.json')
|
|
226
|
+
const stagePackageJson = JSON.parse(await readFile(stagePackageJsonPath, 'utf-8'))
|
|
227
|
+
const publicSetting = config.provider.config.PackageSettings?.public
|
|
228
|
+
|
|
229
|
+
if (stagePackageJson.private === true) {
|
|
230
|
+
if (publicSetting === undefined) {
|
|
231
|
+
const sourcePackageJsonPath = join(metadata.projectSourceDir, 'package.json')
|
|
232
|
+
console.log(chalk.yellow(`\n⚠️ Skipping npm publish — private: true in ${sourcePackageJsonPath}\n`))
|
|
233
|
+
} else {
|
|
234
|
+
console.log(chalk.yellow(`\n⚠️ Skipping npm publish — package is private\n`))
|
|
235
|
+
}
|
|
236
|
+
return
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const {
|
|
240
|
+
name,
|
|
241
|
+
localVersion,
|
|
242
|
+
projectSourceDir,
|
|
243
|
+
stageDir,
|
|
244
|
+
publishedInfo,
|
|
245
|
+
localShasum,
|
|
246
|
+
localIntegrity,
|
|
247
|
+
versionExistsOnNpm,
|
|
248
|
+
versionMatchesChecksum,
|
|
249
|
+
localMatchesAnyTag,
|
|
250
|
+
hasChanges,
|
|
251
|
+
publishedFiles
|
|
252
|
+
} = metadata
|
|
253
|
+
|
|
254
|
+
console.log(chalk.cyan(`\n📋 Package Details:`))
|
|
255
|
+
console.log(chalk.gray(` Package: ${chalk.white(name)}`))
|
|
256
|
+
console.log(chalk.gray(` Version: ${chalk.white(localVersion)}`))
|
|
257
|
+
console.log(chalk.gray(` Shasum: ${chalk.white(localShasum)}`))
|
|
258
|
+
console.log(chalk.gray(` Source: ${chalk.white(projectSourceDir)}`))
|
|
259
|
+
console.log(chalk.gray(` Build: ${chalk.white(stageDir)}`))
|
|
260
|
+
|
|
261
|
+
if (publishedInfo) {
|
|
262
|
+
const npmUrl = `https://www.npmjs.com/package/${name}`
|
|
263
|
+
const distTags = publishedInfo['dist-tags'] || {}
|
|
264
|
+
|
|
265
|
+
console.log(chalk.cyan(`\n📦 Published package: ${chalk.underline(npmUrl)}`))
|
|
266
|
+
|
|
267
|
+
// Fetch and display info for each tag
|
|
268
|
+
const tagInfos: Array<{ tag: string, version: string, shasum: string, matches: boolean }> = []
|
|
269
|
+
let anyTagMatches = false
|
|
270
|
+
|
|
271
|
+
for (const [tag, version] of Object.entries(distTags)) {
|
|
272
|
+
try {
|
|
273
|
+
const tagVersionResult = await $`npm view ${name}@${version as string} --json`.cwd(stageDir).quiet()
|
|
274
|
+
const tagVersionInfo = JSON.parse(tagVersionResult.text())
|
|
275
|
+
|
|
276
|
+
const publishedShasum = tagVersionInfo.dist?.shasum || ''
|
|
277
|
+
const publishedIntegrity = tagVersionInfo.dist?.integrity || ''
|
|
278
|
+
const matches = publishedShasum === localShasum || publishedIntegrity === localIntegrity
|
|
279
|
+
|
|
280
|
+
if (matches) anyTagMatches = true
|
|
281
|
+
|
|
282
|
+
tagInfos.push({ tag, version: version as string, shasum: publishedShasum, matches })
|
|
283
|
+
} catch (e) {
|
|
284
|
+
tagInfos.push({ tag, version: version as string, shasum: '', matches: false })
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
console.log(chalk.gray(` Versions:`))
|
|
289
|
+
for (const { tag, version, shasum } of tagInfos) {
|
|
290
|
+
console.log(chalk.white(` ${tag.padEnd(10)} v${version}`))
|
|
291
|
+
console.log(chalk.gray(` ${shasum}`))
|
|
292
|
+
}
|
|
293
|
+
console.log('')
|
|
294
|
+
|
|
295
|
+
// Store for later use
|
|
296
|
+
metadata.anyTagMatches = anyTagMatches
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Get anyTagMatches from the earlier section
|
|
300
|
+
const anyTagMatches = metadata.anyTagMatches || localMatchesAnyTag
|
|
301
|
+
|
|
302
|
+
if (anyTagMatches) {
|
|
303
|
+
console.log(chalk.green(`\n✓ Local package matches published version - no publish needed\n`))
|
|
304
|
+
return
|
|
305
|
+
} else if (versionExistsOnNpm && versionMatchesChecksum) {
|
|
306
|
+
console.log(chalk.green(`\n✓ Version ${localVersion} already published with matching content\n`))
|
|
307
|
+
return
|
|
308
|
+
} else if (versionExistsOnNpm && !versionMatchesChecksum) {
|
|
309
|
+
console.log(chalk.red(`\n✗ ERROR: Version ${localVersion} already exists on npm with different content!`))
|
|
310
|
+
console.log(chalk.magenta(` Run with --rc flag to bump the version and publish these changes`))
|
|
311
|
+
return
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Show file list only when publishing
|
|
315
|
+
const localFilePaths = await glob('**/*', {
|
|
316
|
+
cwd: stageDir,
|
|
317
|
+
absolute: false,
|
|
318
|
+
onlyFiles: true
|
|
319
|
+
})
|
|
320
|
+
|
|
321
|
+
const localFileHashes = new Map<string, string>()
|
|
322
|
+
for (const filePath of localFilePaths) {
|
|
323
|
+
const fullPath = join(stageDir, filePath)
|
|
324
|
+
const fileBuffer = await readFile(fullPath)
|
|
325
|
+
const localHash = createHash('sha1').update(fileBuffer).digest('hex')
|
|
326
|
+
localFileHashes.set(filePath, localHash)
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
console.log(chalk.cyan(`\n📄 Files to be published:`))
|
|
330
|
+
if (publishedInfo && publishedFiles.size > 0) {
|
|
331
|
+
console.log(chalk.gray(` Legend: ${chalk.green('● new')} ${chalk.yellow('● modified')} ${chalk.gray('● unchanged')}\n`))
|
|
332
|
+
} else {
|
|
333
|
+
console.log(chalk.gray(` Legend: ${chalk.green('● new')}\n`))
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
for (const filePath of localFilePaths) {
|
|
337
|
+
const localHash = localFileHashes.get(filePath)
|
|
338
|
+
const publishedHash = publishedFiles.get(filePath)
|
|
339
|
+
|
|
340
|
+
const stats = await readFile(join(stageDir, filePath))
|
|
341
|
+
const sizeKB = (stats.length / 1024).toFixed(1) + 'kB'
|
|
342
|
+
const sizeB = stats.length + 'B'
|
|
343
|
+
const displaySize = stats.length >= 1024 ? sizeKB : sizeB
|
|
344
|
+
|
|
345
|
+
let status = ''
|
|
346
|
+
let color = chalk.gray
|
|
347
|
+
|
|
348
|
+
if (!publishedHash) {
|
|
349
|
+
status = chalk.green('● ')
|
|
350
|
+
color = chalk.green
|
|
351
|
+
} else if (publishedHash !== localHash) {
|
|
352
|
+
status = chalk.yellow('● ')
|
|
353
|
+
color = chalk.yellow
|
|
354
|
+
} else {
|
|
355
|
+
status = chalk.gray('● ')
|
|
356
|
+
color = chalk.gray
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
console.log(` ${status}${color(displaySize.padEnd(8))} ${color(filePath)}`)
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
if (publishedInfo && publishedFiles.size > 0) {
|
|
363
|
+
const deletedFiles = Array.from(publishedFiles.keys()).filter(f => !localFilePaths.includes(f as any))
|
|
364
|
+
if (deletedFiles.length > 0) {
|
|
365
|
+
console.log(chalk.red(`\n Removed files from previous version:`))
|
|
366
|
+
for (const fileName of deletedFiles) {
|
|
367
|
+
console.log(chalk.red(` ✗ ${fileName}`))
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
console.log('\n')
|
|
372
|
+
|
|
373
|
+
// Display package.json diff comparing our code to published package for same tag
|
|
374
|
+
if (publishedInfo && publishedFiles.size > 0) {
|
|
375
|
+
// Determine which tag to compare against based on local version
|
|
376
|
+
const isReleaseCandidate = localVersion.includes('-rc.')
|
|
377
|
+
const compareTag = isReleaseCandidate ? 'next' : 'latest'
|
|
378
|
+
const distTags = publishedInfo['dist-tags'] || {}
|
|
379
|
+
const compareVersion = distTags[compareTag]
|
|
380
|
+
|
|
381
|
+
if (compareVersion) {
|
|
382
|
+
const mirrorDir = join(projectionDir, 'mirror', `${name.replace(/[\/]/g, '~')}@${compareVersion}`)
|
|
383
|
+
const publishedPackageDir = join(mirrorDir, 'package')
|
|
384
|
+
const publishedPackageJsonPath = join(publishedPackageDir, 'package.json')
|
|
385
|
+
|
|
386
|
+
// Check if published package.json exists
|
|
387
|
+
try {
|
|
388
|
+
await access(publishedPackageJsonPath, constants.F_OK)
|
|
389
|
+
|
|
390
|
+
// Read both package.json files
|
|
391
|
+
const publishedPackageJsonContent = await readFile(publishedPackageJsonPath, 'utf-8')
|
|
392
|
+
const localPackageJsonPath = join(projectSourceDir, 'package.json')
|
|
393
|
+
const localPackageJsonContent = await readFile(localPackageJsonPath, 'utf-8')
|
|
394
|
+
|
|
395
|
+
// Only show diff if they differ
|
|
396
|
+
if (publishedPackageJsonContent.trim() !== localPackageJsonContent.trim()) {
|
|
397
|
+
console.log(chalk.cyan(`\n📝 package.json changes (comparing to ${compareTag}@${compareVersion}):`))
|
|
398
|
+
console.log(chalk.gray('─'.repeat(80)))
|
|
399
|
+
|
|
400
|
+
try {
|
|
401
|
+
const diffResult = await $`diff -u ${publishedPackageJsonPath} ${localPackageJsonPath}`.quiet().nothrow()
|
|
402
|
+
const diffLines = diffResult.text().split('\n')
|
|
403
|
+
|
|
404
|
+
for (const line of diffLines) {
|
|
405
|
+
if (line.startsWith('---') || line.startsWith('+++')) {
|
|
406
|
+
console.log(chalk.gray(line))
|
|
407
|
+
} else if (line.startsWith('@@')) {
|
|
408
|
+
console.log(chalk.cyan(line))
|
|
409
|
+
} else if (line.startsWith('+')) {
|
|
410
|
+
console.log(chalk.green(line))
|
|
411
|
+
} else if (line.startsWith('-')) {
|
|
412
|
+
console.log(chalk.red(line))
|
|
413
|
+
} else {
|
|
414
|
+
console.log(chalk.gray(line))
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
} catch (e) {
|
|
418
|
+
// diff command failed
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
console.log(chalk.gray('─'.repeat(80)))
|
|
422
|
+
console.log('')
|
|
423
|
+
}
|
|
424
|
+
} catch (e) {
|
|
425
|
+
// Published package.json doesn't exist or can't be read
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Determine npm tag based on version
|
|
431
|
+
const isReleaseCandidate = localVersion.includes('-rc.')
|
|
432
|
+
const npmTag = isReleaseCandidate ? 'next' : 'latest'
|
|
433
|
+
|
|
434
|
+
if (publishedInfo && !versionExistsOnNpm) {
|
|
435
|
+
console.log(chalk.yellow(`\n⚠️ Ready to publish new package ${name} version ${localVersion} to npmjs.com`))
|
|
436
|
+
console.log(chalk.yellow(` Will be tagged as: ${chalk.bold(npmTag)}\n`))
|
|
437
|
+
} else {
|
|
438
|
+
console.log(chalk.yellow(`\n⚠️ Ready to publish new package ${name} version ${localVersion} to npmjs.com (first publish)`))
|
|
439
|
+
console.log(chalk.yellow(` Will be tagged as: ${chalk.bold(npmTag)}\n`))
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// Check if logged in to npm
|
|
443
|
+
try {
|
|
444
|
+
await $`npm whoami`.quiet()
|
|
445
|
+
} catch {
|
|
446
|
+
console.log(chalk.yellow(`\n⚠️ You must login to npmjs.com to proceed.\n`))
|
|
447
|
+
const loginProc = Bun.spawn(['npm', 'login'], {
|
|
448
|
+
stdin: 'inherit',
|
|
449
|
+
stdout: 'inherit',
|
|
450
|
+
stderr: 'inherit'
|
|
451
|
+
})
|
|
452
|
+
await loginProc.exited
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
try {
|
|
456
|
+
const otp = await this.WorkspacePrompt.input({
|
|
457
|
+
message: 'Enter your npmjs.com OTP (one-time password):',
|
|
458
|
+
defaultValue: '',
|
|
459
|
+
validate: (input: string) => {
|
|
460
|
+
if (!input || input.trim().length === 0) {
|
|
461
|
+
return 'OTP is required'
|
|
462
|
+
}
|
|
463
|
+
return true
|
|
464
|
+
}
|
|
465
|
+
})
|
|
466
|
+
|
|
467
|
+
console.log(chalk.cyan(`\n🚀 Publishing '${name}' version ${localVersion} to npm with tag '${npmTag}'...`))
|
|
468
|
+
await $`npm publish --access public --tag ${npmTag} --otp=${otp}`.cwd(stageDir)
|
|
469
|
+
console.log(chalk.green(`✅ Successfully published '${name}' version ${localVersion} to npm (tag: ${npmTag})\n`))
|
|
470
|
+
|
|
471
|
+
// Write fact files after successful publish
|
|
472
|
+
const npmFactName = name.replace(/[\/]/g, '~')
|
|
473
|
+
|
|
474
|
+
await this.$NpmFact.set(npmFactName, {
|
|
475
|
+
name,
|
|
476
|
+
version: localVersion,
|
|
477
|
+
private: false,
|
|
478
|
+
shasum: localShasum,
|
|
479
|
+
integrity: localIntegrity,
|
|
480
|
+
publishedAt: new Date().toISOString(),
|
|
481
|
+
npmUrl: `https://www.npmjs.com/package/${name}`
|
|
482
|
+
})
|
|
483
|
+
|
|
484
|
+
await this.$StatusFact.set(npmFactName, {
|
|
485
|
+
projectName: name,
|
|
486
|
+
provider: 'npmjs.com',
|
|
487
|
+
status: 'PUBLISHED',
|
|
488
|
+
publicUrl: `https://www.npmjs.com/package/${name}`
|
|
489
|
+
})
|
|
490
|
+
} catch (error: any) {
|
|
491
|
+
if (error.message?.includes('force closed') || error.message?.includes('SIGINT')) {
|
|
492
|
+
console.log(chalk.red(`\nABORTED\n`))
|
|
493
|
+
process.exit(1)
|
|
494
|
+
}
|
|
495
|
+
throw error
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
},
|
|
499
|
+
afterPush: {
|
|
500
|
+
type: CapsulePropertyTypes.Function,
|
|
501
|
+
value: async function (this: any, { repoName, metadata }: {
|
|
502
|
+
repoName: string
|
|
503
|
+
metadata?: any
|
|
504
|
+
}): Promise<void> {
|
|
505
|
+
if (!metadata) return
|
|
506
|
+
|
|
507
|
+
const isReleaseCandidate = metadata.localVersion?.includes('-rc.')
|
|
508
|
+
const npmTag = isReleaseCandidate ? 'next' : 'latest'
|
|
509
|
+
|
|
510
|
+
const npmData: Record<string, any> = {
|
|
511
|
+
distTags: {
|
|
512
|
+
[npmTag]: {
|
|
513
|
+
version: metadata.localVersion,
|
|
514
|
+
shasum: metadata.localShasum,
|
|
515
|
+
integrity: metadata.localIntegrity,
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
await this.ProjectCatalogs.updateCatalogRepository({
|
|
521
|
+
repoName,
|
|
522
|
+
providerKey: '#' + capsule['#'],
|
|
523
|
+
providerData: npmData,
|
|
524
|
+
})
|
|
525
|
+
}
|
|
526
|
+
},
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}, {
|
|
530
|
+
// @ts-ignore - import.meta is supported in Bun
|
|
531
|
+
importMeta: import.meta,
|
|
532
|
+
importStack: makeImportStack(),
|
|
533
|
+
capsuleName: capsule['#'],
|
|
534
|
+
})
|
|
535
|
+
}
|
|
536
|
+
capsule['#'] = 't44/caps/providers/npmjs.com/ProjectPublishing'
|