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,155 @@
|
|
|
1
|
+
export async function capsule({
|
|
2
|
+
encapsulate,
|
|
3
|
+
CapsulePropertyTypes,
|
|
4
|
+
makeImportStack
|
|
5
|
+
}: {
|
|
6
|
+
encapsulate: any
|
|
7
|
+
CapsulePropertyTypes: any
|
|
8
|
+
makeImportStack: any
|
|
9
|
+
}) {
|
|
10
|
+
return encapsulate({
|
|
11
|
+
'#@stream44.studio/encapsulate/spine-contracts/CapsuleSpineContract.v0': {
|
|
12
|
+
'#@stream44.studio/encapsulate/structs/Capsule': {},
|
|
13
|
+
'#t44/structs/WorkspaceConfig': {
|
|
14
|
+
as: '$WorkspaceConfig'
|
|
15
|
+
},
|
|
16
|
+
'#t44/structs/ProjectRackConfig': {
|
|
17
|
+
as: '$ProjectRackConfig'
|
|
18
|
+
},
|
|
19
|
+
'#': {
|
|
20
|
+
WorkspacePrompt: {
|
|
21
|
+
type: CapsulePropertyTypes.Mapping,
|
|
22
|
+
value: 't44/caps/WorkspacePrompt'
|
|
23
|
+
},
|
|
24
|
+
HomeRegistry: {
|
|
25
|
+
type: CapsulePropertyTypes.Mapping,
|
|
26
|
+
value: 't44/caps/HomeRegistry'
|
|
27
|
+
},
|
|
28
|
+
ensureRack: {
|
|
29
|
+
type: CapsulePropertyTypes.Function,
|
|
30
|
+
value: async function (this: any): Promise<{ rackName: string }> {
|
|
31
|
+
const workspaceConfig = await this.$WorkspaceConfig.config
|
|
32
|
+
const rackConfig = await this.$ProjectRackConfig.config
|
|
33
|
+
|
|
34
|
+
// Check if projectRack is already set in config (object format: { name, identifier })
|
|
35
|
+
if (rackConfig?.name && rackConfig?.identifier) {
|
|
36
|
+
return { rackName: rackConfig.name }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
let rackName: string
|
|
40
|
+
|
|
41
|
+
const rackConfigStructKey = '#t44/structs/ProjectRackConfig'
|
|
42
|
+
if (!rackConfig?.name) {
|
|
43
|
+
const chalk = (await import('chalk')).default
|
|
44
|
+
|
|
45
|
+
console.log(chalk.cyan(`\n📦 Project Rack Setup\n`))
|
|
46
|
+
console.log(chalk.gray(` Workspace: ${workspaceConfig?.name || 'unknown'}`))
|
|
47
|
+
console.log(chalk.gray(` Root: ${workspaceConfig?.rootDir || 'unknown'}`))
|
|
48
|
+
console.log(chalk.gray(''))
|
|
49
|
+
console.log(chalk.gray(' The project rack holds an integrated set of projects which can be'))
|
|
50
|
+
console.log(chalk.gray(' pulled into one or more workspaces.'))
|
|
51
|
+
console.log(chalk.gray(' A workspace attached to a rack has access to all projects in the rack'))
|
|
52
|
+
console.log(chalk.gray(' and is able to add more projects to the rack.'))
|
|
53
|
+
console.log(chalk.gray(' All workspaces attached to a rack automatically sync their projects'))
|
|
54
|
+
console.log(chalk.gray(' to the rack.'))
|
|
55
|
+
console.log(chalk.gray(''))
|
|
56
|
+
|
|
57
|
+
// List existing project racks from registry
|
|
58
|
+
const existingRacks = await this.HomeRegistry.listRacks()
|
|
59
|
+
|
|
60
|
+
// Build choices
|
|
61
|
+
const choices: Array<{ name: string; value: any }> = []
|
|
62
|
+
|
|
63
|
+
for (const rack of existingRacks) {
|
|
64
|
+
choices.push({
|
|
65
|
+
name: `${rack.name} ${chalk.gray(rack.did ? rack.did.substring(0, 50) + '...' : '')}`,
|
|
66
|
+
value: { type: 'existing', name: rack.name }
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
choices.push({
|
|
71
|
+
name: chalk.yellow('+ Create a new project rack'),
|
|
72
|
+
value: { type: 'create' }
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
const selected = await this.WorkspacePrompt.select({
|
|
76
|
+
message: 'Select a project rack:',
|
|
77
|
+
choices,
|
|
78
|
+
defaultValue: { type: 'create' },
|
|
79
|
+
pageSize: 15
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
if (selected.type === 'existing') {
|
|
83
|
+
rackName = selected.name
|
|
84
|
+
} else {
|
|
85
|
+
// Prompt for rack name
|
|
86
|
+
rackName = await this.WorkspacePrompt.input({
|
|
87
|
+
message: 'Enter a name for the new project rack:',
|
|
88
|
+
defaultValue: workspaceConfig?.name || 'genesis',
|
|
89
|
+
validate: (input: string) => {
|
|
90
|
+
if (!input || input.trim().length === 0) {
|
|
91
|
+
return 'Project rack name cannot be empty'
|
|
92
|
+
}
|
|
93
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(input)) {
|
|
94
|
+
return 'Project rack name can only contain letters, numbers, underscores, and hyphens'
|
|
95
|
+
}
|
|
96
|
+
return true
|
|
97
|
+
}
|
|
98
|
+
})
|
|
99
|
+
}
|
|
100
|
+
} else {
|
|
101
|
+
rackName = rackConfig.name
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Check if rack already exists in registry
|
|
105
|
+
let rackData = await this.HomeRegistry.getRack(rackName)
|
|
106
|
+
|
|
107
|
+
if (!rackData) {
|
|
108
|
+
const chalk = (await import('chalk')).default
|
|
109
|
+
console.log(chalk.cyan(`\n Registering project rack '${rackName}'...\n`))
|
|
110
|
+
|
|
111
|
+
const { generateKeypair } = await import('../lib/ucan.js')
|
|
112
|
+
const { did, privateKey } = await generateKeypair()
|
|
113
|
+
|
|
114
|
+
rackData = {
|
|
115
|
+
did,
|
|
116
|
+
privateKey,
|
|
117
|
+
createdAt: new Date().toISOString()
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const rackPath = await this.HomeRegistry.setRack(rackName, rackData)
|
|
121
|
+
|
|
122
|
+
console.log(chalk.green(` ✓ Project rack registered at:`))
|
|
123
|
+
console.log(chalk.green(` ${rackPath}`))
|
|
124
|
+
console.log(chalk.green(` ✓ DID: ${rackData.did}\n`))
|
|
125
|
+
} else {
|
|
126
|
+
const chalk = (await import('chalk')).default
|
|
127
|
+
const rackPath = await this.HomeRegistry.getRackPath(rackName)
|
|
128
|
+
console.log(chalk.green(`\n ✓ Using existing project rack at:`))
|
|
129
|
+
console.log(chalk.green(` ${rackPath}`))
|
|
130
|
+
console.log(chalk.green(` ✓ DID: ${rackData.did}\n`))
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Store rack as object { name, identifier } in rack config struct
|
|
134
|
+
await this.$ProjectRackConfig.setConfigValue(['name'], rackName)
|
|
135
|
+
await this.$ProjectRackConfig.setConfigValue(['identifier'], rackData.did)
|
|
136
|
+
|
|
137
|
+
return { rackName }
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
getRackName: {
|
|
141
|
+
type: CapsulePropertyTypes.Function,
|
|
142
|
+
value: async function (this: any): Promise<string | null> {
|
|
143
|
+
const rackConfig = await this.$ProjectRackConfig.config
|
|
144
|
+
return rackConfig?.name || null
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}, {
|
|
150
|
+
importMeta: import.meta,
|
|
151
|
+
importStack: makeImportStack(),
|
|
152
|
+
capsuleName: capsule['#'],
|
|
153
|
+
})
|
|
154
|
+
}
|
|
155
|
+
capsule['#'] = 't44/caps/ProjectRack'
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
|
|
2
|
+
import { join } from 'path'
|
|
3
|
+
import { $ } from 'bun'
|
|
4
|
+
import { mkdir, access, readFile, writeFile } from 'fs/promises'
|
|
5
|
+
import { constants } from 'fs'
|
|
6
|
+
|
|
7
|
+
export async function capsule({
|
|
8
|
+
encapsulate,
|
|
9
|
+
CapsulePropertyTypes,
|
|
10
|
+
makeImportStack
|
|
11
|
+
}: {
|
|
12
|
+
encapsulate: any
|
|
13
|
+
CapsulePropertyTypes: any
|
|
14
|
+
makeImportStack: any
|
|
15
|
+
}) {
|
|
16
|
+
return encapsulate({
|
|
17
|
+
'#@stream44.studio/encapsulate/spine-contracts/CapsuleSpineContract.v0': {
|
|
18
|
+
'#@stream44.studio/encapsulate/structs/Capsule': {},
|
|
19
|
+
'#t44/structs/WorkspaceConfig': {
|
|
20
|
+
as: '$WorkspaceConfig'
|
|
21
|
+
},
|
|
22
|
+
'#': {
|
|
23
|
+
WorkspaceConfig: {
|
|
24
|
+
type: CapsulePropertyTypes.Mapping,
|
|
25
|
+
value: 't44/caps/WorkspaceConfig'
|
|
26
|
+
},
|
|
27
|
+
getStagePath: {
|
|
28
|
+
type: CapsulePropertyTypes.Function,
|
|
29
|
+
value: async function (this: any, { repoUri }: { repoUri: string }): Promise<string> {
|
|
30
|
+
const normalizedUri = repoUri.replace(/[\/]/g, '~')
|
|
31
|
+
return join(
|
|
32
|
+
this.WorkspaceConfig.workspaceRootDir,
|
|
33
|
+
'.~o/workspace.foundation/@t44.sh~t44~caps~ProjectRepository/stage',
|
|
34
|
+
normalizedUri
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
init: {
|
|
39
|
+
type: CapsulePropertyTypes.Function,
|
|
40
|
+
value: async function (this: any, { rootDir }: { rootDir: string }): Promise<void> {
|
|
41
|
+
await mkdir(rootDir, { recursive: true })
|
|
42
|
+
|
|
43
|
+
const gitDir = join(rootDir, '.git')
|
|
44
|
+
let isGitRepo = false
|
|
45
|
+
try {
|
|
46
|
+
await access(gitDir, constants.F_OK)
|
|
47
|
+
isGitRepo = true
|
|
48
|
+
} catch { }
|
|
49
|
+
|
|
50
|
+
if (!isGitRepo) {
|
|
51
|
+
await $`git init`.cwd(rootDir).quiet()
|
|
52
|
+
await $`git commit --allow-empty -m init`.cwd(rootDir).quiet()
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
reset: {
|
|
57
|
+
type: CapsulePropertyTypes.Function,
|
|
58
|
+
value: async function (this: any, { rootDir }: { rootDir: string }): Promise<void> {
|
|
59
|
+
await $`git checkout -- .`.cwd(rootDir).quiet().nothrow()
|
|
60
|
+
await $`git clean -fd`.cwd(rootDir).quiet().nothrow()
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
sync: {
|
|
64
|
+
type: CapsulePropertyTypes.Function,
|
|
65
|
+
value: async function (this: any, { rootDir, sourceDir, gitignorePath }: {
|
|
66
|
+
rootDir: string
|
|
67
|
+
sourceDir: string
|
|
68
|
+
gitignorePath?: string
|
|
69
|
+
}): Promise<void> {
|
|
70
|
+
let gitignoreExists = false
|
|
71
|
+
if (gitignorePath) {
|
|
72
|
+
try {
|
|
73
|
+
await access(gitignorePath, constants.F_OK)
|
|
74
|
+
gitignoreExists = true
|
|
75
|
+
} catch { }
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const rsyncArgs = ['rsync', '-a', '--delete', '--exclude', '.git']
|
|
79
|
+
if (gitignoreExists && gitignorePath) {
|
|
80
|
+
rsyncArgs.push('--exclude-from=' + gitignorePath)
|
|
81
|
+
}
|
|
82
|
+
rsyncArgs.push(sourceDir + '/', rootDir + '/')
|
|
83
|
+
await $`${rsyncArgs}`
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
hasChanges: {
|
|
87
|
+
type: CapsulePropertyTypes.Function,
|
|
88
|
+
value: async function (this: any, { rootDir }: { rootDir: string }): Promise<boolean> {
|
|
89
|
+
await $`git add -A`.cwd(rootDir).quiet()
|
|
90
|
+
const diff = await $`git diff --cached --stat`.cwd(rootDir).quiet().nothrow()
|
|
91
|
+
const hasChanges = diff.text().trim().length > 0
|
|
92
|
+
await $`git reset`.cwd(rootDir).quiet().nothrow()
|
|
93
|
+
return hasChanges
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
commit: {
|
|
97
|
+
type: CapsulePropertyTypes.Function,
|
|
98
|
+
value: async function (this: any, { rootDir, message }: {
|
|
99
|
+
rootDir: string
|
|
100
|
+
message: string
|
|
101
|
+
}): Promise<void> {
|
|
102
|
+
await $`git add -A`.cwd(rootDir).quiet()
|
|
103
|
+
await $`git commit -m ${message}`.cwd(rootDir).quiet().nothrow()
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
getHeadCommit: {
|
|
107
|
+
type: CapsulePropertyTypes.Function,
|
|
108
|
+
value: async function (this: any, { rootDir }: { rootDir: string }): Promise<string> {
|
|
109
|
+
const result = await $`git rev-parse HEAD`.cwd(rootDir).quiet().nothrow()
|
|
110
|
+
if (result.exitCode !== 0) return ''
|
|
111
|
+
return result.text().trim()
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
getBranch: {
|
|
115
|
+
type: CapsulePropertyTypes.Function,
|
|
116
|
+
value: async function (this: any, { rootDir }: { rootDir: string }): Promise<string> {
|
|
117
|
+
const result = await $`git rev-parse --abbrev-ref HEAD`.cwd(rootDir).quiet()
|
|
118
|
+
return result.text().trim()
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
getLastCommitMessage: {
|
|
122
|
+
type: CapsulePropertyTypes.Function,
|
|
123
|
+
value: async function (this: any, { rootDir }: { rootDir: string }): Promise<string> {
|
|
124
|
+
const result = await $`git log -1 --pretty=%B`.cwd(rootDir).quiet()
|
|
125
|
+
return result.text().trim()
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
clone: {
|
|
129
|
+
type: CapsulePropertyTypes.Function,
|
|
130
|
+
value: async function (this: any, { originUri, targetDir }: {
|
|
131
|
+
originUri: string
|
|
132
|
+
targetDir: string
|
|
133
|
+
}): Promise<{ isNewEmptyRepo: boolean }> {
|
|
134
|
+
const parentDir = join(targetDir, '..')
|
|
135
|
+
await mkdir(parentDir, { recursive: true })
|
|
136
|
+
await $`git clone ${originUri} ${targetDir}`.cwd(parentDir)
|
|
137
|
+
|
|
138
|
+
const headCheck = await $`git rev-parse HEAD`.cwd(targetDir).quiet().nothrow()
|
|
139
|
+
return { isNewEmptyRepo: headCheck.exitCode !== 0 }
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
addAll: {
|
|
143
|
+
type: CapsulePropertyTypes.Function,
|
|
144
|
+
value: async function (this: any, { rootDir }: { rootDir: string }): Promise<boolean> {
|
|
145
|
+
await $`git add .`.cwd(rootDir).quiet()
|
|
146
|
+
const statusResult = await $`git status --porcelain`.cwd(rootDir).quiet()
|
|
147
|
+
return statusResult.text().trim().length > 0
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
isAheadOfRemote: {
|
|
151
|
+
type: CapsulePropertyTypes.Function,
|
|
152
|
+
value: async function (this: any, { rootDir }: { rootDir: string }): Promise<boolean> {
|
|
153
|
+
const lsRemoteResult = await $`git ls-remote origin`.cwd(rootDir).quiet().nothrow()
|
|
154
|
+
const lsRemoteOutput = lsRemoteResult.text().trim()
|
|
155
|
+
|
|
156
|
+
if (!lsRemoteOutput) {
|
|
157
|
+
return true
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const localHead = (await $`git rev-parse HEAD`.cwd(rootDir).quiet()).text().trim()
|
|
161
|
+
const remoteHeadLine = lsRemoteOutput.split('\n').find((l: string) => l.includes('refs/heads/main'))
|
|
162
|
+
const remoteHead = remoteHeadLine ? remoteHeadLine.split('\t')[0] : null
|
|
163
|
+
|
|
164
|
+
return !remoteHead || remoteHead !== localHead
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
push: {
|
|
168
|
+
type: CapsulePropertyTypes.Function,
|
|
169
|
+
value: async function (this: any, { rootDir }: { rootDir: string }): Promise<void> {
|
|
170
|
+
await $`git push -u origin main --tags`.cwd(rootDir)
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
forcePush: {
|
|
174
|
+
type: CapsulePropertyTypes.Function,
|
|
175
|
+
value: async function (this: any, { rootDir }: { rootDir: string }): Promise<void> {
|
|
176
|
+
await $`git push --force --tags`.cwd(rootDir)
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
squashAllCommits: {
|
|
180
|
+
type: CapsulePropertyTypes.Function,
|
|
181
|
+
value: async function (this: any, { rootDir, message }: {
|
|
182
|
+
rootDir: string
|
|
183
|
+
message: string
|
|
184
|
+
}): Promise<void> {
|
|
185
|
+
const rootCommit = await $`git rev-list --max-parents=0 HEAD`.cwd(rootDir).text()
|
|
186
|
+
await $`git reset --soft ${rootCommit.trim()}`.cwd(rootDir)
|
|
187
|
+
await $`git commit --amend -m ${message}`.cwd(rootDir)
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
tag: {
|
|
191
|
+
type: CapsulePropertyTypes.Function,
|
|
192
|
+
value: async function (this: any, { rootDir, tag }: {
|
|
193
|
+
rootDir: string
|
|
194
|
+
tag: string
|
|
195
|
+
}): Promise<void> {
|
|
196
|
+
await $`git tag ${tag}`.cwd(rootDir)
|
|
197
|
+
}
|
|
198
|
+
},
|
|
199
|
+
hasTag: {
|
|
200
|
+
type: CapsulePropertyTypes.Function,
|
|
201
|
+
value: async function (this: any, { rootDir, tag }: {
|
|
202
|
+
rootDir: string
|
|
203
|
+
tag: string
|
|
204
|
+
}): Promise<{ exists: boolean, commit?: string }> {
|
|
205
|
+
const localTagCheck = await $`git tag -l ${tag}`.cwd(rootDir).quiet().nothrow()
|
|
206
|
+
if (localTagCheck.text().trim() === tag) {
|
|
207
|
+
const tagCommit = (await $`git rev-parse ${tag}^{}`.cwd(rootDir).quiet().nothrow()).text().trim()
|
|
208
|
+
return { exists: true, commit: tagCommit }
|
|
209
|
+
}
|
|
210
|
+
return { exists: false }
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
hasRemoteTag: {
|
|
214
|
+
type: CapsulePropertyTypes.Function,
|
|
215
|
+
value: async function (this: any, { rootDir, tag }: {
|
|
216
|
+
rootDir: string
|
|
217
|
+
tag: string
|
|
218
|
+
}): Promise<{ exists: boolean, commit?: string }> {
|
|
219
|
+
const remoteTagCheck = await $`git ls-remote --tags origin ${tag}`.cwd(rootDir).quiet().nothrow()
|
|
220
|
+
const output = remoteTagCheck.text().trim()
|
|
221
|
+
if (output.length > 0) {
|
|
222
|
+
const commit = output.split(/\s+/)[0]
|
|
223
|
+
return { exists: true, commit }
|
|
224
|
+
}
|
|
225
|
+
return { exists: false }
|
|
226
|
+
}
|
|
227
|
+
},
|
|
228
|
+
diff: {
|
|
229
|
+
type: CapsulePropertyTypes.Function,
|
|
230
|
+
value: async function (this: any, { rootDir, from, to }: {
|
|
231
|
+
rootDir: string
|
|
232
|
+
from: string
|
|
233
|
+
to?: string
|
|
234
|
+
}): Promise<string> {
|
|
235
|
+
const toRef = to || 'HEAD'
|
|
236
|
+
const result = await $`git diff ${from} ${toRef}`.cwd(rootDir).quiet().nothrow()
|
|
237
|
+
return result.text().trim()
|
|
238
|
+
}
|
|
239
|
+
},
|
|
240
|
+
exists: {
|
|
241
|
+
type: CapsulePropertyTypes.Function,
|
|
242
|
+
value: async function (this: any, { rootDir }: { rootDir: string }): Promise<boolean> {
|
|
243
|
+
try {
|
|
244
|
+
await access(join(rootDir, '.git'), constants.F_OK)
|
|
245
|
+
return true
|
|
246
|
+
} catch {
|
|
247
|
+
return false
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
initBare: {
|
|
252
|
+
type: CapsulePropertyTypes.Function,
|
|
253
|
+
value: async function (this: any, { rootDir }: { rootDir: string }): Promise<void> {
|
|
254
|
+
await mkdir(rootDir, { recursive: true })
|
|
255
|
+
|
|
256
|
+
let isBareRepo = false
|
|
257
|
+
try {
|
|
258
|
+
await access(join(rootDir, 'HEAD'), constants.F_OK)
|
|
259
|
+
isBareRepo = true
|
|
260
|
+
} catch { }
|
|
261
|
+
|
|
262
|
+
if (!isBareRepo) {
|
|
263
|
+
await $`git init --bare`.cwd(rootDir).quiet()
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
},
|
|
267
|
+
hasRemote: {
|
|
268
|
+
type: CapsulePropertyTypes.Function,
|
|
269
|
+
value: async function (this: any, { rootDir, name }: {
|
|
270
|
+
rootDir: string
|
|
271
|
+
name: string
|
|
272
|
+
}): Promise<boolean> {
|
|
273
|
+
const result = await $`git remote`.cwd(rootDir).quiet().nothrow()
|
|
274
|
+
const remotes = result.text().trim().split('\n').filter(Boolean)
|
|
275
|
+
return remotes.includes(name)
|
|
276
|
+
}
|
|
277
|
+
},
|
|
278
|
+
addRemote: {
|
|
279
|
+
type: CapsulePropertyTypes.Function,
|
|
280
|
+
value: async function (this: any, { rootDir, name, url }: {
|
|
281
|
+
rootDir: string
|
|
282
|
+
name: string
|
|
283
|
+
url: string
|
|
284
|
+
}): Promise<void> {
|
|
285
|
+
await $`git remote add ${name} ${url}`.cwd(rootDir).quiet()
|
|
286
|
+
}
|
|
287
|
+
},
|
|
288
|
+
setRemoteUrl: {
|
|
289
|
+
type: CapsulePropertyTypes.Function,
|
|
290
|
+
value: async function (this: any, { rootDir, name, url }: {
|
|
291
|
+
rootDir: string
|
|
292
|
+
name: string
|
|
293
|
+
url: string
|
|
294
|
+
}): Promise<void> {
|
|
295
|
+
await $`git remote set-url ${name} ${url}`.cwd(rootDir).quiet()
|
|
296
|
+
}
|
|
297
|
+
},
|
|
298
|
+
pushToRemote: {
|
|
299
|
+
type: CapsulePropertyTypes.Function,
|
|
300
|
+
value: async function (this: any, { rootDir, remote, branch, force }: {
|
|
301
|
+
rootDir: string
|
|
302
|
+
remote: string
|
|
303
|
+
branch?: string
|
|
304
|
+
force?: boolean
|
|
305
|
+
}): Promise<void> {
|
|
306
|
+
const branchName = branch || 'main'
|
|
307
|
+
if (force) {
|
|
308
|
+
await $`git push --force ${remote} ${branchName}`.cwd(rootDir).quiet()
|
|
309
|
+
} else {
|
|
310
|
+
await $`git push ${remote} ${branchName}`.cwd(rootDir).quiet()
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}, {
|
|
317
|
+
importMeta: import.meta,
|
|
318
|
+
importStack: makeImportStack(),
|
|
319
|
+
capsuleName: capsule['#'],
|
|
320
|
+
})
|
|
321
|
+
}
|
|
322
|
+
capsule['#'] = 't44/caps/ProjectRepository'
|