@soederpop/luca 0.0.10 → 0.0.11

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.
@@ -21,10 +21,10 @@ export const argsSchema = CommandOptionsSchema.extend({
21
21
  async function release(options: z.infer<typeof argsSchema>, context: ContainerContext) {
22
22
  const container = context.container as any
23
23
  const proc = container.feature('proc')
24
- const fs = container.feature('fs')
24
+ const fileSystem = container.feature('fs')
25
25
  const ui = container.feature('ui')
26
26
 
27
- const pkg = JSON.parse(await fs.readFileAsync('package.json'))
27
+ const pkg = JSON.parse(await fileSystem.readFileAsync('package.json'))
28
28
  const version = pkg.version
29
29
  const tag = `v${version}`
30
30
  const distDir = 'dist/release'
@@ -45,7 +45,7 @@ async function release(options: z.infer<typeof argsSchema>, context: ContainerCo
45
45
  // 1. Run tests
46
46
  if (!options.skipTests) {
47
47
  console.log('\n→ Running tests...')
48
- const testResult = await proc.exec('bun test test/*.test.ts', { silent: false })
48
+ const testResult = await proc.execAndCapture('bun test test/*.test.ts', { silent: false })
49
49
  if (testResult.exitCode !== 0) {
50
50
  console.error('Tests failed. Fix them before releasing.')
51
51
  return
@@ -62,7 +62,7 @@ async function release(options: z.infer<typeof argsSchema>, context: ContainerCo
62
62
  ]
63
63
  for (const [label, cmd] of steps) {
64
64
  console.log(` ${label}...`)
65
- const r = await proc.exec(cmd, { silent: true })
65
+ const r = await proc.execAndCapture(cmd, { silent: true })
66
66
  if (r.exitCode !== 0) {
67
67
  console.error(`${label} failed:\n${r.stderr}`)
68
68
  return
@@ -71,7 +71,7 @@ async function release(options: z.infer<typeof argsSchema>, context: ContainerCo
71
71
  }
72
72
 
73
73
  // 3. Cross-compile for all targets
74
- fs.ensureFolder(distDir)
74
+ fileSystem.ensureFolder(distDir)
75
75
 
76
76
  console.log(`\n→ Compiling for ${selectedTargets.length} targets...`)
77
77
  for (const target of selectedTargets) {
@@ -80,14 +80,14 @@ async function release(options: z.infer<typeof argsSchema>, context: ContainerCo
80
80
  const cmd = `bun build ./src/cli/cli.ts --compile --target=${target.bunTarget} --outfile ${outfile} --external node-llama-cpp`
81
81
 
82
82
  console.log(` ${target.name}...`)
83
- const result = await proc.exec(cmd, { silent: true })
83
+ const result = await proc.execAndCapture(cmd, { silent: true })
84
84
  if (result.exitCode !== 0) {
85
85
  console.error(` Failed to compile for ${target.name}:\n${result.stderr}`)
86
86
  return
87
87
  }
88
88
 
89
- const stat = fs.statSync(outfile)
90
- const sizeMB = (stat.size / 1024 / 1024).toFixed(1)
89
+ const sizeBytes = proc.exec(`stat -f%z ${container.paths.resolve(outfile)}`)
90
+ const sizeMB = (parseInt(sizeBytes, 10) / 1024 / 1024).toFixed(1)
91
91
  console.log(` ✓ ${outfile} (${sizeMB} MB)`)
92
92
  }
93
93
 
@@ -98,14 +98,14 @@ async function release(options: z.infer<typeof argsSchema>, context: ContainerCo
98
98
  }
99
99
 
100
100
  // 4. Check if tag already exists
101
- const tagCheck = await proc.exec(`git tag -l "${tag}"`, { silent: true })
101
+ const tagCheck = await proc.execAndCapture(`git tag -l "${tag}"`, { silent: true })
102
102
  if (tagCheck.stdout.trim() === tag) {
103
103
  console.error(`\nTag ${tag} already exists. Bump the version in package.json first.`)
104
104
  return
105
105
  }
106
106
 
107
107
  // 5. Check for clean working tree (allow untracked)
108
- const statusCheck = await proc.exec('git status --porcelain', { silent: true })
108
+ const statusCheck = await proc.execAndCapture('git status --porcelain', { silent: true })
109
109
  const dirtyFiles = statusCheck.stdout.trim().split('\n').filter((l: string) => l && !l.startsWith('??'))
110
110
  if (dirtyFiles.length > 0) {
111
111
  console.error('\nWorking tree has uncommitted changes. Commit or stash them first.')
@@ -115,7 +115,7 @@ async function release(options: z.infer<typeof argsSchema>, context: ContainerCo
115
115
 
116
116
  // 6. Create git tag
117
117
  console.log(`\n→ Creating tag ${tag}...`)
118
- const tagResult = await proc.exec(`git tag -a "${tag}" -m "Release ${tag}"`, { silent: true })
118
+ const tagResult = await proc.execAndCapture(`git tag -a "${tag}" -m "Release ${tag}"`, { silent: true })
119
119
  if (tagResult.exitCode !== 0) {
120
120
  console.error(`Failed to create tag:\n${tagResult.stderr}`)
121
121
  return
@@ -123,29 +123,37 @@ async function release(options: z.infer<typeof argsSchema>, context: ContainerCo
123
123
 
124
124
  // 7. Push tag
125
125
  console.log(`→ Pushing tag ${tag}...`)
126
- const pushResult = await proc.exec(`git push origin "${tag}"`, { silent: true })
126
+ const pushResult = await proc.execAndCapture(`git push origin "${tag}"`, { silent: true })
127
127
  if (pushResult.exitCode !== 0) {
128
128
  console.error(`Failed to push tag:\n${pushResult.stderr}`)
129
129
  return
130
130
  }
131
131
 
132
132
  // 8. Create GitHub release and upload binaries
133
- const draftFlag = options.draft ? '--draft' : ''
134
- const assets = selectedTargets
135
- .map(t => `${distDir}/luca-${t.suffix}${t.ext || ''}`)
136
- .join(' ')
133
+ const assetPaths = selectedTargets
134
+ .map(t => container.paths.resolve(`${distDir}/luca-${t.suffix}${t.ext || ''}`))
137
135
 
138
136
  const releaseTitle = `Luca ${tag}`
139
137
  const releaseNotes = await generateReleaseNotes(proc, tag)
140
138
 
139
+ // Write notes to a temp file so gh doesn't need shell quoting
140
+ const notesFile = container.paths.resolve(distDir, 'release-notes.md')
141
+ await fileSystem.writeFileAsync(notesFile, releaseNotes)
142
+
141
143
  console.log(`\n→ Creating GitHub release ${tag}...`)
142
- const ghCmd = `gh release create "${tag}" ${assets} --title "${releaseTitle}" --notes ${JSON.stringify(releaseNotes)} ${draftFlag}`
143
- const ghResult = await proc.exec(ghCmd, { silent: false })
144
+ const ghArgs = [
145
+ 'release', 'create', tag,
146
+ ...assetPaths,
147
+ '--title', releaseTitle,
148
+ '--notes-file', notesFile,
149
+ ...(options.draft ? ['--draft'] : []),
150
+ ]
151
+ const ghResult = await proc.spawnAndCapture('gh', ghArgs)
144
152
 
145
153
  if (ghResult.exitCode !== 0) {
146
154
  console.error(`Failed to create GitHub release:\n${ghResult.stderr}`)
147
155
  console.log('The tag was pushed. You can manually create the release with:')
148
- console.log(` gh release create ${tag} ${assets}`)
156
+ console.log(` gh release create ${tag} ${assetPaths.join(' ')}`)
149
157
  return
150
158
  }
151
159
 
@@ -155,7 +163,7 @@ async function release(options: z.infer<typeof argsSchema>, context: ContainerCo
155
163
 
156
164
  async function generateReleaseNotes(proc: any, tag: string): Promise<string> {
157
165
  // Get commits since last tag
158
- const lastTag = await proc.exec('git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo ""', { silent: true })
166
+ const lastTag = await proc.execAndCapture('git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo ""', { silent: true })
159
167
  const since = lastTag.stdout.trim()
160
168
 
161
169
  let logCmd: string
@@ -165,7 +173,7 @@ async function generateReleaseNotes(proc: any, tag: string): Promise<string> {
165
173
  logCmd = 'git log --oneline --no-decorate -20'
166
174
  }
167
175
 
168
- const log = await proc.exec(logCmd, { silent: true })
176
+ const log = await proc.execAndCapture(logCmd, { silent: true })
169
177
  const commits = log.stdout.trim()
170
178
 
171
179
  return `## What's Changed\n\n${commits ? commits.split('\n').map((c: string) => `- ${c}`).join('\n') : 'Initial release'}\n\n## Platforms\n\n- Linux x64\n- Linux ARM64\n- macOS x64 (Intel)\n- macOS ARM64 (Apple Silicon)\n- Windows x64`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@soederpop/luca",
3
- "version": "0.0.10",
3
+ "version": "0.0.11",
4
4
  "website": "https://luca.soederpop.com",
5
5
  "description": "lightweight universal conversational architecture AKA Le Ultimate Component Architecture AKA Last Universal Common Ancestor, part AI part Human",
6
6
  "author": "jon soeder aka the people's champ <jon@soederpop.com>",
@@ -102,7 +102,7 @@
102
102
  "chokidar": "^3.5.3",
103
103
  "cli-markdown": "^3.5.0",
104
104
  "compromise": "^14.14.5",
105
- "contentbase": "^0.1.1",
105
+ "contentbase": "^0.1.4",
106
106
  "cors": "^2.8.5",
107
107
  "detect-port": "^1.5.1",
108
108
  "dotenv": "^17.2.4",
@@ -1,5 +1,5 @@
1
1
  // Auto-generated bootstrap content
2
- // Generated at: 2026-03-19T18:52:32.906Z
2
+ // Generated at: 2026-03-20T00:15:40.841Z
3
3
  // Source: docs/bootstrap/*.md, docs/bootstrap/templates/*
4
4
  //
5
5
  // Do not edit manually. Run: luca build-bootstrap