@bagelink/workspace 1.10.7 → 1.10.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.
package/src/sdk.ts CHANGED
@@ -69,6 +69,9 @@ export async function generateSDK(
69
69
  try {
70
70
  // Dynamic import of @bagelink/sdk
71
71
  const { openAPI } = await import('@bagelink/sdk')
72
+ const { readFileSync } = await import('node:fs')
73
+ const { dirname, join } = await import('node:path')
74
+ const { fileURLToPath } = await import('node:url')
72
75
 
73
76
  const { types, code } = await openAPI(finalUrl, '/api')
74
77
 
@@ -86,14 +89,7 @@ export async function generateSDK(
86
89
  const apiPath = resolve(outputPath, 'api.ts')
87
90
  writeFileSync(apiPath, code)
88
91
  console.log('✅ Generated api.ts')
89
-
90
- // Write index
91
- const indexPath = resolve(outputPath, 'index.ts')
92
- writeFileSync(
93
- indexPath,
94
- 'export * from \'./api\'\nexport * from \'./types.d\'\n',
95
- )
96
- console.log('✅ Generated index.ts')
92
+ console.log('ℹ️ Stream utilities are imported from @bagelink/sdk')
97
93
 
98
94
  if (splitFiles) {
99
95
  console.log('\n🔀 Splitting into organized files...')
@@ -104,8 +100,9 @@ export async function generateSDK(
104
100
  }
105
101
 
106
102
  console.log('\n✅ SDK generated successfully!')
107
- console.log(`\nImport it in your code:`)
108
- console.log(` import { api } from '${outputDir.replace('./src/', './')}'`)
103
+ console.log(`\nImport directly in your code:`)
104
+ console.log(` import { agents } from '${outputDir.replace('./src/', './')}/api'`)
105
+ console.log(` import type { SendMessageRequest } from '${outputDir.replace('./src/', './')}/types.d'`)
109
106
  console.log('')
110
107
  }
111
108
  catch (error: unknown) {
@@ -128,48 +125,87 @@ export async function generateSDK(
128
125
  * Generate SDK for all projects in workspace
129
126
  */
130
127
  export async function generateSDKForWorkspace(root: string = process.cwd()): Promise<void> {
131
- console.log('\n🏢 Generating SDK for workspace projects...\n')
132
-
133
- // Find all projects
134
- const fs = await import('node:fs')
135
- const items = fs.readdirSync(root, { withFileTypes: true })
136
- const projects = items
137
- .filter(
138
- item => item.isDirectory()
139
- && item.name !== 'node_modules'
140
- && item.name !== 'shared'
141
- && item.name !== '.git'
142
- && !item.name.startsWith('.'),
143
- )
144
- .map(item => item.name)
145
-
146
- if (projects.length === 0) {
147
- console.log('No projects found in workspace')
148
- return
128
+ console.log('\n🏢 Generating SDK for workspace...\n')
129
+
130
+ // Try to load config
131
+ let config: WorkspaceConfig | null = null
132
+ let openApiUrl: string | undefined
133
+
134
+ try {
135
+ const configPath = resolve(root, 'bgl.config.ts')
136
+ if (existsSync(configPath)) {
137
+ const module = await import(`file://${configPath}`)
138
+ const workspace = module.default
139
+ if (typeof workspace === 'function') {
140
+ config = workspace('development')
141
+ if (config?.openapi_url) {
142
+ openApiUrl = config.openapi_url
143
+ }
144
+ }
145
+ }
146
+ }
147
+ catch {
148
+ // Ignore config load errors
149
149
  }
150
150
 
151
+ // Prompt for OpenAPI URL if not in config
151
152
  const response = await prompts({
152
- type: 'multiselect',
153
- name: 'selectedProjects',
154
- message: 'Select projects to generate SDK for:',
155
- choices: projects.map(p => ({ title: p, value: p, selected: true })),
153
+ type: openApiUrl !== undefined ? null : 'text',
154
+ name: 'openApiUrl',
155
+ message: 'OpenAPI spec URL:',
156
+ initial: openApiUrl ?? 'http://localhost:8000/openapi.json',
156
157
  })
157
158
 
158
- if (!response || !response.selectedProjects || response.selectedProjects.length === 0) {
159
- console.log('\n❌ No projects selected.\n')
160
- return
159
+ if (response === undefined || (openApiUrl === undefined && !response.openApiUrl)) {
160
+ console.log('\n❌ SDK generation cancelled.\n')
161
+ process.exit(1)
161
162
  }
162
163
 
163
- for (const project of response.selectedProjects) {
164
- console.log(`\n📦 Generating SDK for: ${project}`)
165
- const projectPath = resolve(root, project)
166
- try {
167
- await generateSDK(projectPath)
164
+ const finalUrl = openApiUrl ?? response.openApiUrl
165
+ const outputDir = './shared'
166
+
167
+ console.log(`\n📡 Fetching OpenAPI spec from: ${finalUrl}`)
168
+ console.log(`📁 Output directory: ${outputDir}\n`)
169
+
170
+ try {
171
+ // Dynamic import of @bagelink/sdk
172
+ const { openAPI } = await import('@bagelink/sdk')
173
+
174
+ const { types, code } = await openAPI(finalUrl, '/api')
175
+
176
+ const outputPath = resolve(root, outputDir)
177
+ if (!existsSync(outputPath)) {
178
+ mkdirSync(outputPath, { recursive: true })
168
179
  }
169
- catch {
170
- console.error(`Failed to generate SDK for ${project}`)
180
+
181
+ // Write types
182
+ const typesPath = resolve(outputPath, 'types.d.ts')
183
+ writeFileSync(typesPath, types)
184
+ console.log('✅ Generated types.d.ts')
185
+
186
+ // Write API client
187
+ const apiPath = resolve(outputPath, 'api.ts')
188
+ writeFileSync(apiPath, code)
189
+ console.log('✅ Generated api.ts')
190
+
191
+ console.log('\n✅ SDK generated successfully in shared folder!')
192
+ console.log(`\nImport from shared in your projects:`)
193
+ console.log(` import { agents } from '../shared/api'`)
194
+ console.log(` import type { SendMessageRequest } from '../shared/types'`)
195
+ console.log('')
196
+ }
197
+ catch (error: unknown) {
198
+ console.error('\n❌ Failed to generate SDK:')
199
+ if (error instanceof Error) {
200
+ console.error(error.message)
171
201
  }
202
+ else {
203
+ console.error(error)
204
+ }
205
+ console.log('\nMake sure:')
206
+ console.log(' 1. @bagelink/sdk is installed: bun add -D @bagelink/sdk')
207
+ console.log(' 2. OpenAPI URL is accessible')
208
+ console.log(' 3. API server is running (if using localhost)')
209
+ process.exit(1)
172
210
  }
173
-
174
- console.log('\n✅ All SDKs generated!')
175
211
  }
package/src/types.ts CHANGED
@@ -1,4 +1,4 @@
1
- export type WorkspaceEnvironment = 'localhost' | 'development' | 'production'
1
+ export type WorkspaceEnvironment = string
2
2
 
3
3
  export interface WorkspaceConfig {
4
4
  /**
@@ -8,10 +8,11 @@ export interface WorkspaceConfig {
8
8
  host: string
9
9
 
10
10
  /**
11
- * The proxy path to use for API requests
12
- * @default '/api'
11
+ * Optional proxy path to use for API requests
12
+ * If not set, no proxy will be configured for this environment
13
+ * @example '/api'
13
14
  */
14
- proxy: string
15
+ proxy?: string
15
16
 
16
17
  /**
17
18
  * Optional OpenAPI specification URL for SDK generation
@@ -45,5 +46,10 @@ export interface ProxyConfig {
45
46
  changeOrigin: boolean
46
47
  rewrite?: (path: string) => string
47
48
  secure: boolean
49
+ ws?: boolean
50
+ followRedirects?: boolean
51
+ autoRewrite?: boolean
52
+ protocolRewrite?: string
53
+ configure?: (proxy: any, options: any) => void
48
54
  }
49
55
  }
package/src/vite.ts ADDED
@@ -0,0 +1,166 @@
1
+ import type { Plugin, ResolvedConfig } from 'vite'
2
+ import type { WorkspaceConfig, WorkspaceEnvironment } from './types'
3
+ import { basename } from 'node:path'
4
+ import process from 'node:process'
5
+ import { fileURLToPath } from 'node:url'
6
+ import { createViteProxy } from './proxy'
7
+ import { listProjects } from './workspace.js'
8
+
9
+ export { generateNetlifyConfig, setBuildEnvVars, writeNetlifyConfig } from './netlify'
10
+ // Re-export proxy utilities for convenience
11
+ export { createCustomProxy, createViteProxy } from './proxy'
12
+
13
+ export interface BagelinkPluginOptions {
14
+ /**
15
+ * Path to shared package relative to project
16
+ * @default '../shared'
17
+ */
18
+ sharedPath?: string
19
+
20
+ /**
21
+ * Whether to include @shared alias
22
+ * @default true
23
+ */
24
+ includeSharedAlias?: boolean
25
+
26
+ /**
27
+ * Additional path aliases beyond @ and @shared
28
+ */
29
+ additionalAliases?: Record<string, string>
30
+
31
+ /**
32
+ * Whether to auto-configure proxy
33
+ * @default true
34
+ */
35
+ configureProxy?: boolean
36
+ }
37
+
38
+ /**
39
+ * Vite plugin for Bagelink workspace integration
40
+ * Automatically configures proxy and path aliases based on bgl.config.ts
41
+ *
42
+ * @example
43
+ * ```ts
44
+ * import { defineConfig } from 'vite'
45
+ * import vue from '@vitejs/plugin-vue'
46
+ * import { bagelink } from '@bagelink/workspace/vite'
47
+ * import workspace from './bgl.config'
48
+ *
49
+ * export default defineConfig({
50
+ * plugins: [
51
+ * vue(),
52
+ * bagelink({ workspace })
53
+ * ]
54
+ * })
55
+ * ```
56
+ *
57
+ * @example With custom options
58
+ * ```ts
59
+ * import { defineConfig } from 'vite'
60
+ * import vue from '@vitejs/plugin-vue'
61
+ * import { bagelink } from '@bagelink/workspace/vite'
62
+ * import workspace from './bgl.config'
63
+ *
64
+ * export default defineConfig({
65
+ * plugins: [
66
+ * vue(),
67
+ * bagelink({
68
+ * workspace,
69
+ * sharedPath: '../packages/shared',
70
+ * additionalAliases: {
71
+ * '@utils': fileURLToPath(new URL('./src/utils', import.meta.url))
72
+ * }
73
+ * })
74
+ * ]
75
+ * })
76
+ * ```
77
+ */
78
+ export function bagelink(options: {
79
+ workspace: (mode: WorkspaceEnvironment) => WorkspaceConfig
80
+ config?: BagelinkPluginOptions
81
+ }): Plugin {
82
+ const { workspace, config = {} } = options
83
+ let resolvedConfig: ResolvedConfig
84
+ let workspaceConfig: WorkspaceConfig
85
+
86
+ return {
87
+ name: 'vite-plugin-bagelink',
88
+ enforce: 'pre',
89
+
90
+ configResolved(resolved) {
91
+ resolvedConfig = resolved
92
+ workspaceConfig = workspace(resolved.mode as WorkspaceEnvironment)
93
+ },
94
+
95
+ config(userConfig, { mode }) {
96
+ workspaceConfig = workspace(mode as WorkspaceEnvironment)
97
+
98
+ // Build alias configuration
99
+ const alias: Record<string, string> = {}
100
+
101
+ // Add @shared alias if enabled
102
+ if (config.includeSharedAlias !== false) {
103
+ const sharedPath = config.sharedPath ?? '../shared'
104
+ // Resolve relative to the project root
105
+ alias['@shared'] = fileURLToPath(new URL(sharedPath, `file://${process.cwd()}/`))
106
+ }
107
+
108
+ // Add @ alias pointing to src
109
+ alias['@'] = fileURLToPath(new URL('./src', `file://${process.cwd()}/`))
110
+
111
+ // Add any additional aliases
112
+ if (config.additionalAliases) {
113
+ Object.assign(alias, config.additionalAliases)
114
+ }
115
+
116
+ // Compute port based on alphabetical order in workspace
117
+ let port: number | undefined
118
+ try {
119
+ const currentDir = process.cwd()
120
+ const projectName = basename(currentDir)
121
+ const parentDir = fileURLToPath(new URL('..', `file://${currentDir}/`))
122
+ const allProjects = listProjects(parentDir) // Already sorted alphabetically
123
+ const projectIndex = allProjects.indexOf(projectName)
124
+
125
+ if (projectIndex !== -1) {
126
+ port = 5173 + projectIndex
127
+ }
128
+ } catch {
129
+ // If we can't determine the workspace, don't set a port
130
+ }
131
+
132
+ // Build server config with proxy and consistent port
133
+ const server = {
134
+ ...(config.configureProxy !== false && {
135
+ proxy: createViteProxy(workspaceConfig),
136
+ }),
137
+ ...(port !== undefined && {
138
+ port,
139
+ strictPort: false, // use next available port if preferred is taken
140
+ }),
141
+ }
142
+
143
+ // Inject workspace config as environment variables
144
+ const define = {
145
+ ...(workspaceConfig.proxy && {
146
+ 'import.meta.env.VITE_BGL_PROXY': JSON.stringify(workspaceConfig.proxy),
147
+ }),
148
+ 'import.meta.env.VITE_BGL_HOST': JSON.stringify(workspaceConfig.host),
149
+ ...(workspaceConfig.openapi_url && {
150
+ 'import.meta.env.VITE_BGL_OPENAPI_URL': JSON.stringify(workspaceConfig.openapi_url),
151
+ }),
152
+ }
153
+
154
+ return {
155
+ resolve: {
156
+ alias,
157
+ },
158
+ define,
159
+ server,
160
+ optimizeDeps: {
161
+ exclude: ['@bagelink/workspace'],
162
+ },
163
+ }
164
+ },
165
+ }
166
+ }
package/src/workspace.ts CHANGED
@@ -87,12 +87,27 @@ function createWorkspaceRoot(root: string, name: string, projectId: string): voi
87
87
  private: true,
88
88
  workspaces: ['*', '!node_modules'],
89
89
  scripts: {
90
- dev: 'bun run --filter \'./[!shared]*\' dev',
91
- build: 'bun run --filter \'./[!shared]*\' build',
92
- typecheck: 'tsc --noEmit',
90
+ 'dev': 'bgl dev',
91
+ 'dev:local': 'bgl dev --mode localhost',
92
+ 'dev:verbose': 'bun run --filter \'./!shared*\' dev',
93
+ 'build': 'bgl build',
94
+ 'typecheck': 'tsc --noEmit',
95
+ 'lint': 'eslint . --cache',
96
+ 'lint:fix': 'eslint . --cache --fix',
97
+ },
98
+ dependencies: {
99
+ '@bagelink/auth': 'latest',
100
+ '@bagelink/sdk': 'latest',
101
+ '@bagelink/vue': 'latest',
102
+ 'pinia': 'latest',
103
+ 'vue': 'latest',
104
+ 'vue-router': 'latest',
93
105
  },
94
106
  devDependencies: {
107
+ '@bagelink/lint-config': 'latest',
95
108
  '@bagelink/workspace': 'latest',
109
+ '@vitejs/plugin-vue': 'latest',
110
+ 'eslint': 'latest',
96
111
  'typescript': '^5.0.0',
97
112
  'vite': 'latest',
98
113
  },
@@ -106,10 +121,15 @@ function createWorkspaceRoot(root: string, name: string, projectId: string): voi
106
121
  // bgl.config.ts (shared)
107
122
  const bglConfig = `import { defineWorkspace } from '@bagelink/workspace'
108
123
 
124
+ /**
125
+ * Define your workspace environments
126
+ * You can add as many custom environments as needed (e.g., 'staging', 'preview')
127
+ * Use --mode flag to switch: bgl dev --mode <env_name>
128
+ */
109
129
  export default defineWorkspace({
110
130
  localhost: {
111
131
  host: 'http://localhost:8000',
112
- proxy: '/api',
132
+ proxy: '/api', // Optional: remove to skip proxy setup
113
133
  openapi_url: 'http://localhost:8000/openapi.json',
114
134
  },
115
135
  development: {
@@ -168,6 +188,77 @@ dist
168
188
 
169
189
  writeFileSync(resolve(workspaceDir, '.gitignore'), gitignore)
170
190
 
191
+ // Create scripts directory
192
+ const scriptsDir = resolve(workspaceDir, 'scripts')
193
+ mkdirSync(scriptsDir, { recursive: true })
194
+
195
+ // Copy dev runner script
196
+ const devRunnerContent = `#!/usr/bin/env bun
197
+ import { spawn } from 'bun'
198
+ import { readdir } from 'fs/promises'
199
+ import { resolve } from 'path'
200
+
201
+ const projectsRoot = process.cwd()
202
+ const projects = (await readdir(projectsRoot, { withFileTypes: true }))
203
+ .filter(
204
+ item =>
205
+ item.isDirectory()
206
+ && item.name !== 'node_modules'
207
+ && item.name !== 'shared'
208
+ && item.name !== 'scripts'
209
+ && item.name !== '.git'
210
+ && !item.name.startsWith('.'),
211
+ )
212
+ .map(item => item.name)
213
+
214
+ console.log(\`\\n🚀 Starting \${projects.length} project\${projects.length > 1 ? 's' : ''}...\\n\`)
215
+
216
+ const urlPattern = /Local:\\s+(http:\\/\\/localhost:\\d+)/
217
+
218
+ projects.forEach((project) => {
219
+ const proc = spawn({
220
+ cmd: ['bun', 'run', 'dev'],
221
+ cwd: resolve(projectsRoot, project),
222
+ stdout: 'pipe',
223
+ stderr: 'pipe',
224
+ })
225
+
226
+ const decoder = new TextDecoder()
227
+
228
+ proc.stdout.pipeTo(
229
+ new WritableStream({
230
+ write(chunk) {
231
+ const text = decoder.decode(chunk)
232
+ const match = text.match(urlPattern)
233
+ if (match) {
234
+ console.log(\` ✓ \${project.padEnd(15)} → \${match[1]}\`)
235
+ }
236
+ },
237
+ }),
238
+ )
239
+
240
+ proc.stderr.pipeTo(
241
+ new WritableStream({
242
+ write(chunk) {
243
+ const text = decoder.decode(chunk)
244
+ if (text.includes('error') || text.includes('Error')) {
245
+ console.error(\` ✗ \${project}: \${text.trim()}\`)
246
+ }
247
+ },
248
+ }),
249
+ )
250
+ })
251
+
252
+ console.log('\\n💡 Press Ctrl+C to stop all servers\\n')
253
+
254
+ process.on('SIGINT', () => {
255
+ console.log('\\n\\n👋 Stopping all servers...\\n')
256
+ process.exit()
257
+ })
258
+ `
259
+
260
+ writeFileSync(resolve(scriptsDir, 'dev.ts'), devRunnerContent)
261
+
171
262
  console.log(`✅ Created workspace: ${name}`)
172
263
  }
173
264
 
@@ -300,18 +391,14 @@ export default defineWorkspace({
300
391
  // vite.config.ts
301
392
  const viteConfig = `import { defineConfig } from 'vite'
302
393
  import vue from '@vitejs/plugin-vue'
303
- import { createViteProxy } from '@bagelink/workspace'
394
+ import { bagelink } from '@bagelink/workspace/vite'
304
395
  import workspace from './bgl.config'
305
396
 
306
- export default defineConfig(({ mode }) => {
307
- const config = workspace(mode as 'localhost' | 'development' | 'production')
308
-
309
- return {
310
- plugins: [vue()],
311
- server: {
312
- proxy: createViteProxy(config),
313
- },
314
- }
397
+ export default defineConfig({
398
+ plugins: [
399
+ vue(),
400
+ bagelink({ workspace }),
401
+ ],
315
402
  })
316
403
  `
317
404
 
@@ -340,9 +427,12 @@ export default defineConfig(({ mode }) => {
340
427
 
341
428
  // main.ts
342
429
  const mainTs = `import { createApp } from 'vue'
430
+ import { BagelVue } from '@bagelink/vue'
343
431
  import App from './App.vue'
344
432
 
345
- createApp(App).mount('#app')
433
+ createApp(App)
434
+ .use(BagelVue)
435
+ .mount('#app')
346
436
  `
347
437
 
348
438
  writeFileSync(resolve(srcDir, 'main.ts'), mainTs)
@@ -412,7 +502,7 @@ function updateWorkspaceScripts(root: string, projectName: string): void {
412
502
  }
413
503
 
414
504
  /**
415
- * List all projects in workspace
505
+ * List all projects in workspace (sorted alphabetically for consistent ordering)
416
506
  */
417
507
  export function listProjects(root: string = process.cwd()): string[] {
418
508
  try {
@@ -422,10 +512,25 @@ export function listProjects(root: string = process.cwd()): string[] {
422
512
  item => item.isDirectory()
423
513
  && item.name !== 'node_modules'
424
514
  && item.name !== 'shared'
515
+ && item.name !== 'scripts'
425
516
  && item.name !== '.git'
426
517
  && !item.name.startsWith('.'),
427
518
  )
519
+ .filter((item) => {
520
+ // Only include directories with package.json that has a dev script
521
+ const packageJsonPath = resolve(root, item.name, 'package.json')
522
+ if (!existsSync(packageJsonPath)) return false
523
+
524
+ try {
525
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'))
526
+ return packageJson.scripts?.dev !== undefined
527
+ }
528
+ catch {
529
+ return false
530
+ }
531
+ })
428
532
  .map(item => item.name)
533
+ .sort() // Sort alphabetically for consistent port assignment
429
534
  }
430
535
  catch {
431
536
  return []
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/env bun
2
+ import { spawn } from 'bun'
3
+ import { readdir } from 'fs/promises'
4
+ import { resolve } from 'path'
5
+
6
+ const projectsRoot = process.cwd()
7
+ const projects = (await readdir(projectsRoot, { withFileTypes: true }))
8
+ .filter(
9
+ item =>
10
+ item.isDirectory()
11
+ && item.name !== 'node_modules'
12
+ && item.name !== 'shared'
13
+ && item.name !== '.git'
14
+ && !item.name.startsWith('.'),
15
+ )
16
+ .map(item => item.name)
17
+
18
+ console.log(`\n🚀 Starting ${projects.length} project${projects.length > 1 ? 's' : ''}...\n`)
19
+
20
+ const urlPattern = /Local:\s+(http:\/\/localhost:\d+)/
21
+
22
+ projects.forEach((project) => {
23
+ const proc = spawn({
24
+ cmd: ['bun', 'run', 'dev'],
25
+ cwd: resolve(projectsRoot, project),
26
+ stdout: 'pipe',
27
+ stderr: 'pipe',
28
+ })
29
+
30
+ const decoder = new TextDecoder()
31
+
32
+ proc.stdout.pipeTo(
33
+ new WritableStream({
34
+ write(chunk) {
35
+ const text = decoder.decode(chunk)
36
+ const match = text.match(urlPattern)
37
+ if (match) {
38
+ console.log(` ✓ ${project.padEnd(15)} → ${match[1]}`)
39
+ }
40
+ },
41
+ }),
42
+ )
43
+
44
+ proc.stderr.pipeTo(
45
+ new WritableStream({
46
+ write(chunk) {
47
+ const text = decoder.decode(chunk)
48
+ if (text.includes('error') || text.includes('Error')) {
49
+ console.error(` ✗ ${project}: ${text.trim()}`)
50
+ }
51
+ },
52
+ }),
53
+ )
54
+ })
55
+
56
+ console.log('\n💡 Press Ctrl+C to stop all servers\n')
57
+
58
+ process.on('SIGINT', () => {
59
+ console.log('\n\n👋 Stopping all servers...\n')
60
+ process.exit()
61
+ })
@@ -0,0 +1,23 @@
1
+ {
2
+ "extends": "../tsconfig.json",
3
+ "compilerOptions": {
4
+ "composite": true,
5
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
6
+ "baseUrl": ".",
7
+ "paths": {
8
+ "@/*": [
9
+ "./src/*"
10
+ ],
11
+ "@shared/*": [
12
+ "../shared/*"
13
+ ]
14
+ }
15
+ },
16
+ "include": [
17
+ "src/**/*",
18
+ "src/**/*.vue"
19
+ ],
20
+ "exclude": [
21
+ "node_modules"
22
+ ]
23
+ }