@bagelink/workspace 1.11.0 → 1.11.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/src/netlify.ts CHANGED
@@ -5,9 +5,13 @@ import process from 'node:process'
5
5
 
6
6
  /**
7
7
  * Generate netlify.toml redirect configuration
8
+ * Uses environment variables for flexibility across environments
8
9
  */
9
10
  export function generateNetlifyRedirect(config: WorkspaceConfig): string {
10
- const redirect = `[[redirects]]
11
+ const redirect = `# API Proxy Configuration
12
+ # Environment variables are set in Netlify UI or netlify.toml [build.environment] section
13
+
14
+ [[redirects]]
11
15
  from = "${config.proxy}/*"
12
16
  to = "${config.host}/:splat"
13
17
  status = 200
@@ -18,13 +22,49 @@ export function generateNetlifyRedirect(config: WorkspaceConfig): string {
18
22
  return redirect
19
23
  }
20
24
 
25
+ /**
26
+ * Generate netlify.toml with environment variable template
27
+ * Provides a standard config that can be reused across projects
28
+ */
29
+ export function generateNetlifyConfigTemplate(): string {
30
+ return `# Standard Netlify configuration for Bagelink projects
31
+ # Uses environment variables for proxy configuration
32
+
33
+ [build.environment]
34
+ # Set these in Netlify UI or override here
35
+ # BGL_PROXY_PATH = "/api"
36
+ # BGL_API_HOST = "https://your-project.bagel.to"
37
+
38
+ [[redirects]]
39
+ # Proxy API requests to backend
40
+ # Uses BGL_PROXY_PATH and BGL_API_HOST from environment
41
+ from = "/api/*"
42
+ to = "https://your-project.bagel.to/:splat"
43
+ status = 200
44
+ force = true
45
+ headers = {X-From = "Netlify"}
46
+
47
+ # Example: Multiple API backends
48
+ # [[redirects]]
49
+ # from = "/api/v2/*"
50
+ # to = "https://api-v2.example.com/:splat"
51
+ # status = 200
52
+ # force = true
53
+ `
54
+ }
55
+
21
56
  /**
22
57
  * Generate complete netlify.toml file
23
58
  */
24
59
  export function generateNetlifyConfig(
25
60
  config: WorkspaceConfig,
26
61
  additionalConfig?: string,
62
+ useTemplate: boolean = false,
27
63
  ): string {
64
+ if (useTemplate) {
65
+ return generateNetlifyConfigTemplate()
66
+ }
67
+
28
68
  const redirect = generateNetlifyRedirect(config)
29
69
 
30
70
  if (additionalConfig !== undefined && additionalConfig !== '') {
@@ -40,13 +80,24 @@ export function generateNetlifyConfig(
40
80
  export function writeNetlifyConfig(
41
81
  config: WorkspaceConfig,
42
82
  outPath: string = './netlify.toml',
43
- additionalConfig?: string
83
+ additionalConfig?: string,
84
+ useTemplate: boolean = false,
44
85
  ): void {
45
- const content = generateNetlifyConfig(config, additionalConfig)
86
+ const content = generateNetlifyConfig(config, additionalConfig, useTemplate)
46
87
  const resolvedPath = resolve(outPath)
47
88
 
48
89
  writeFileSync(resolvedPath, content, 'utf-8')
49
90
  console.log(`✓ Generated netlify.toml at ${resolvedPath}`)
91
+
92
+ if (!useTemplate) {
93
+ console.log('\n💡 Tip: For environment-based config, set these in Netlify UI:')
94
+ console.log(` BGL_PROXY_PATH = "${config.proxy}"`)
95
+ console.log(` BGL_API_HOST = "${config.host}"`)
96
+ if (config.openapi_url) {
97
+ console.log(` BGL_OPENAPI_URL = "${config.openapi_url}"`)
98
+ }
99
+ console.log('')
100
+ }
50
101
  }
51
102
 
52
103
  /**
package/src/proxy.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import type { IncomingMessage, ServerResponse, ClientRequest } from 'node:http'
1
2
  import type { WorkspaceConfig, ProxyConfig } from './types'
2
3
 
3
4
  /**
@@ -12,7 +13,22 @@ export function createViteProxy(config: WorkspaceConfig): ProxyConfig {
12
13
  target: config.host,
13
14
  changeOrigin: true,
14
15
  rewrite: (path: string) => path.replace(new RegExp(`^${config.proxy}`), ''),
15
- secure: true,
16
+ secure: false,
17
+ ws: true,
18
+ followRedirects: true,
19
+ autoRewrite: true,
20
+ protocolRewrite: 'http',
21
+ configure: (proxy: any, _options: any) => {
22
+ proxy.on('proxyReq', (proxyReq: ClientRequest, req: IncomingMessage, _res: ServerResponse) => {
23
+ // Ensure proper headers are forwarded
24
+ if (req.headers.origin) {
25
+ proxyReq.setHeader('origin', config.host)
26
+ }
27
+ })
28
+ proxy.on('error', (err: Error, _req: IncomingMessage, _res: ServerResponse) => {
29
+ console.log('proxy error', err)
30
+ })
31
+ },
16
32
  }
17
33
  }
18
34
 
@@ -21,7 +37,9 @@ export function createViteProxy(config: WorkspaceConfig): ProxyConfig {
21
37
  proxy['/files'] = {
22
38
  target: config.host,
23
39
  changeOrigin: true,
24
- secure: true,
40
+ secure: false,
41
+ ws: true,
42
+ followRedirects: true,
25
43
  }
26
44
  }
27
45
 
@@ -38,6 +56,7 @@ export function createCustomProxy(
38
56
  changeOrigin?: boolean
39
57
  rewrite?: boolean
40
58
  secure?: boolean
59
+ ws?: boolean
41
60
  } = {}
42
61
  ): ProxyConfig {
43
62
  const proxy: ProxyConfig = {}
@@ -46,7 +65,8 @@ export function createCustomProxy(
46
65
  proxy[path] = {
47
66
  target,
48
67
  changeOrigin: options.changeOrigin ?? true,
49
- secure: options.secure ?? true,
68
+ secure: options.secure ?? false,
69
+ ws: options.ws ?? true,
50
70
  ...(options.rewrite === true && {
51
71
  rewrite: (p: string) => p.replace(new RegExp(`^${path}`), ''),
52
72
  }),
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
+ }