Skip to content

Commit d50e1e4

Browse files
committed
Published using @Stream44 Studio
Signed-off-by: Christoph <christoph@christoph.diy>
1 parent e1311c1 commit d50e1e4

9 files changed

Lines changed: 223 additions & 31 deletions

File tree

.github/workflows/test.yaml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
name: Tests
2+
3+
on:
4+
push:
5+
branches: [ "main" ]
6+
pull_request:
7+
branches: [ "main" ]
8+
9+
jobs:
10+
test:
11+
name: Run Tests
12+
runs-on: ubuntu-latest
13+
steps:
14+
- name: Checkout code
15+
uses: actions/checkout@v4
16+
17+
- name: Setup Bun
18+
uses: oven-sh/setup-bun@v2
19+
with:
20+
bun-version: latest
21+
22+
- name: Install dependencies
23+
run: bun install
24+
25+
- name: Run tests
26+
run: bun test

caps/ProjectPublishing.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ export async function capsule({
7171
type: CapsulePropertyTypes.Function,
7272
value: async function (this: any, { args }: any): Promise<void> {
7373

74-
const { projectSelector, rc, release, bump, publish, dangerouslyResetMain, yesSignoff } = args
74+
const { projectSelector, rc, release, bump, publish, dangerouslyResetMain, dangerouslyResetGordianOpenIntegrity, yesSignoff } = args
7575

7676
// Determine if this is a dry-run (default) or actual publish
7777
const isDryRun = !rc && !release && !bump && !publish
@@ -149,7 +149,8 @@ export async function capsule({
149149
await this.ProjectRepository.sync({
150150
rootDir: repoSourceDir,
151151
sourceDir: projectSourceDir,
152-
gitignorePath
152+
gitignorePath,
153+
excludePatterns: repositoriesConfig.alwaysIgnore || []
153154
})
154155

155156
stageSourceDirs.set(repoName, repoSourceDir)
@@ -433,8 +434,9 @@ export async function capsule({
433434
})
434435
} else if (capsuleName === 't44/caps/providers/git-scm.com/ProjectPublishing' && !isDryRun) {
435436
await this.GitRepository.push({
436-
config: { ...repoConfig, provider: providerConfig, sourceDir: repoSourceDir },
437+
config: { ...repoConfig, provider: providerConfig, sourceDir: repoSourceDir, alwaysIgnore: repositoriesConfig.alwaysIgnore },
437438
dangerouslyResetMain,
439+
dangerouslyResetGordianOpenIntegrity,
438440
yesSignoff,
439441
metadata: gitMetadata.get(repoName),
440442
projectSourceDir: (repoConfig as any).sourceDir

caps/ProjectRepository.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,11 @@ export async function capsule({
6262
},
6363
sync: {
6464
type: CapsulePropertyTypes.Function,
65-
value: async function (this: any, { rootDir, sourceDir, gitignorePath }: {
65+
value: async function (this: any, { rootDir, sourceDir, gitignorePath, excludePatterns }: {
6666
rootDir: string
6767
sourceDir: string
6868
gitignorePath?: string
69+
excludePatterns?: string[]
6970
}): Promise<void> {
7071
let gitignoreExists = false
7172
if (gitignorePath) {
@@ -79,6 +80,12 @@ export async function capsule({
7980
if (gitignoreExists && gitignorePath) {
8081
rsyncArgs.push('--exclude-from=' + gitignorePath)
8182
}
83+
// Add additional exclude patterns from alwaysIgnore config
84+
if (excludePatterns && excludePatterns.length > 0) {
85+
for (const pattern of excludePatterns) {
86+
rsyncArgs.push('--exclude', pattern)
87+
}
88+
}
8289
rsyncArgs.push(sourceDir + '/', rootDir + '/')
8390
await $`${rsyncArgs}`
8491
}

caps/WorkspaceConfig.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ extends:
5555
value: optional
5656
dangerously-reset-main:
5757
description: Reset the git repository and force push to remote.
58+
dangerously-reset-gordian-open-integrity:
59+
description: Reset the Gordian Open Integrity trust root.
5860
yes-signoff:
5961
description: Automatically agree to DCO sign-off without prompting.
6062
deploy:

caps/WorkspaceConnection.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ import { join } from 'path'
1010
const shownConnectionTitles = new Set<string>()
1111
const shownDescriptions = new Set<string>()
1212

13+
// Cache for in-flight getStoredConfig promises to prevent parallel decryption race conditions
14+
const storedConfigCache = new Map<string, Promise<Record<string, any> | null>>()
15+
1316
export async function capsule({
1417
encapsulate,
1518
CapsulePropertyTypes,
@@ -77,9 +80,29 @@ export async function capsule({
7780
getStoredConfig: {
7881
type: CapsulePropertyTypes.Function,
7982
value: async function (this: any): Promise<Record<string, any> | null> {
80-
const { readFile } = await import('fs/promises')
8183
const filepath = await this.getFilepath()
8284

85+
// Use cached promise if already in-flight to prevent parallel decryption race conditions
86+
if (storedConfigCache.has(filepath)) {
87+
return storedConfigCache.get(filepath)!
88+
}
89+
90+
const promise = this._getStoredConfigImpl(filepath)
91+
storedConfigCache.set(filepath, promise)
92+
93+
try {
94+
return await promise
95+
} finally {
96+
// Clear cache after completion so next call gets fresh data
97+
storedConfigCache.delete(filepath)
98+
}
99+
}
100+
},
101+
_getStoredConfigImpl: {
102+
type: CapsulePropertyTypes.Function,
103+
value: async function (this: any, filepath: string): Promise<Record<string, any> | null> {
104+
const { readFile } = await import('fs/promises')
105+
83106
try {
84107
const content = await readFile(filepath, 'utf-8')
85108
const parsed = JSON.parse(content)

caps/WorkspaceTest.ts

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
21
import type * as BunTest from 'bun:test'
32
import { config as loadDotenv } from 'dotenv'
4-
import { join } from 'path'
3+
import { join, dirname, basename } from 'path'
4+
import { mkdir } from 'fs/promises'
55

66
// Global cache for loaded env files (this is fine as a cache)
77
const loadedEnvFiles = new Set<string>()
@@ -67,6 +67,35 @@ export async function capsule({
6767
return process.env[envVarName]
6868
}
6969
},
70+
workbenchDir: {
71+
type: CapsulePropertyTypes.GetterFunction,
72+
value: function (this: any): string {
73+
74+
const moduleFilepath = this['#@stream44.studio/encapsulate/structs/Capsule'].rootCapsule.moduleFilepath
75+
const dir = join(dirname(moduleFilepath), '.~o/workspace.foundation/workbenches', basename(moduleFilepath).replace(/\.[^\.]+$/, ''))
76+
77+
return dir
78+
}
79+
},
80+
emptyWorkbenchDir: {
81+
type: CapsulePropertyTypes.Function,
82+
value: async function (this: any): Promise<void> {
83+
const dir = this.workbenchDir
84+
85+
// Ensure the directory exists first
86+
await mkdir(dir, { recursive: true })
87+
88+
// Remove directory contents (not the directory itself) including dotfiles
89+
// Use shell with proper globbing to handle both regular files and dotfiles
90+
await Bun.$`sh -c 'rm -rf ${dir}/* ${dir}/.[!.]* ${dir}/..?* 2>/dev/null || true'`.quiet()
91+
}
92+
},
93+
EnsureEmptyWorkbenchDir: {
94+
type: CapsulePropertyTypes.StructInit,
95+
value: async function (this: any) {
96+
await this.emptyWorkbenchDir()
97+
}
98+
},
7099
describe: {
71100
type: CapsulePropertyTypes.GetterFunction,
72101
value: function (this: any) {
@@ -161,7 +190,7 @@ export async function capsule({
161190
}, {
162191
importMeta: import.meta,
163192
importStack: makeImportStack(),
164-
capsuleName: capsule['#'],
193+
capsuleName: capsule['#']
165194
})
166195
}
167196
capsule['#'] = 't44/caps/WorkspaceTest'

0 commit comments

Comments
 (0)