@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/build.ts ADDED
@@ -0,0 +1,45 @@
1
+ import { spawn } from 'node:child_process'
2
+ import process from 'node:process'
3
+ import { listProjects } from './workspace.js'
4
+
5
+ export async function runBuild(
6
+ filter?: string,
7
+ additionalArgs: string[] = [],
8
+ ) {
9
+ const argsStr = additionalArgs.length > 0 ? ` -- ${additionalArgs.join(' ')}` : ''
10
+ const resolvedFilters = resolveFilters(filter)
11
+ if (!resolvedFilters || resolvedFilters.length === 0) return 1
12
+
13
+ const filterArgs = resolvedFilters.map(f => `--filter '${f}'`).join(' ')
14
+ const command = `bun run ${filterArgs} build${argsStr}`
15
+
16
+ const proc = spawn(command, {
17
+ cwd: process.cwd(),
18
+ stdio: 'inherit',
19
+ shell: true,
20
+ })
21
+
22
+ proc.on('error', (error) => {
23
+ console.error('Failed to start build:', error.message)
24
+ })
25
+
26
+ return new Promise<number>((resolve, reject) => {
27
+ proc.on('exit', (code) => {
28
+ resolve(code || 0)
29
+ })
30
+ proc.on('error', reject)
31
+ })
32
+ }
33
+
34
+ function resolveFilters(filter?: string): string[] | null {
35
+ if (filter) return [filter]
36
+
37
+ const projects = listProjects()
38
+ if (projects.length === 0) {
39
+ console.error('No projects found')
40
+ return null
41
+ }
42
+
43
+ // Return all projects as individual filters
44
+ return projects.map(p => `./${p}`)
45
+ }
@@ -0,0 +1,70 @@
1
+ /// <reference types="vite/client" />
2
+
3
+ /**
4
+ * Runtime workspace configuration
5
+ * Provides access to workspace config injected at build time
6
+ */
7
+ export interface RuntimeWorkspaceConfig {
8
+ /** API proxy path (e.g., '/api') */
9
+ proxy?: string
10
+ /** API host URL (e.g., 'https://project.bagel.to') */
11
+ host: string
12
+ /** Base URL for API requests (proxy if available, otherwise host) */
13
+ baseURL: string
14
+ /** OpenAPI specification URL (if configured) */
15
+ openapiUrl?: string
16
+ /** Current environment mode */
17
+ mode: string
18
+ }
19
+
20
+ /**
21
+ * Get workspace configuration at runtime
22
+ * Config is injected as environment variables during build
23
+ *
24
+ * @example
25
+ * ```ts
26
+ * import { useWorkspace } from '@bagelink/workspace'
27
+ *
28
+ * const { proxy, host } = useWorkspace()
29
+ * const auth = createAuth({ baseURL: proxy })
30
+ * ```
31
+ *
32
+ * @example In Vue component
33
+ * ```vue
34
+ * <script setup>
35
+ * import { useWorkspace } from '@bagelink/workspace'
36
+ *
37
+ * const { proxy, host, mode } = useWorkspace()
38
+ * </script>
39
+ * ```
40
+ */
41
+ export function useWorkspace(): RuntimeWorkspaceConfig {
42
+ // Access env variables injected by the Vite plugin
43
+ const proxy = import.meta.env.VITE_BGL_PROXY || undefined
44
+ const host = import.meta.env.VITE_BGL_HOST || ''
45
+ const openapiUrl = import.meta.env.VITE_BGL_OPENAPI_URL
46
+ const mode = (import.meta.env.MODE as string) || 'development'
47
+
48
+ return {
49
+ proxy,
50
+ host,
51
+ baseURL: proxy ?? host,
52
+ openapiUrl,
53
+ mode,
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Get the full API URL by combining host and proxy
59
+ *
60
+ * @example
61
+ * ```ts
62
+ * import { getApiUrl } from '@bagelink/workspace'
63
+ *
64
+ * const apiUrl = getApiUrl() // 'https://project.bagel.to/api'
65
+ * ```
66
+ */
67
+ export function getApiUrl(): string {
68
+ const { host, proxy } = useWorkspace()
69
+ return `${host}${proxy}`
70
+ }
package/src/dev.ts ADDED
@@ -0,0 +1,171 @@
1
+ import { spawn } from 'node:child_process'
2
+ import process from 'node:process'
3
+ import { listProjects } from './workspace.js'
4
+
5
+ interface ServerInfo {
6
+ name: string
7
+ assignedPort: number
8
+ actualPort?: number
9
+ status: 'starting' | 'ready'
10
+ order: number
11
+ }
12
+
13
+ const servers = new Map<string, ServerInfo>()
14
+ const projectToPort = new Map<string, number>()
15
+ const seenLines = new Set<string>()
16
+ const START_PORT = 5173
17
+
18
+ // ANSI colors
19
+ const colors = {
20
+ reset: '\x1B[0m',
21
+ cyan: '\x1B[36m',
22
+ green: '\x1B[32m',
23
+ yellow: '\x1B[33m',
24
+ dim: '\x1B[2m',
25
+ red: '\x1B[31m',
26
+ }
27
+
28
+ function clearAndPrintServers() {
29
+ // Clear previous output
30
+ if (servers.size > 0) {
31
+ process.stdout.write('\x1B[2J\x1B[H') // Clear screen and move to top
32
+ }
33
+
34
+ console.log(`${colors.cyan}šŸš€ Development Servers${colors.reset}\n`)
35
+
36
+ // Sort by order to maintain consistent display
37
+ const sortedServers = Array.from(servers.entries()).sort((a, b) => a[1].order - b[1].order)
38
+
39
+ for (const [name, info] of sortedServers) {
40
+ const url = `http://localhost:${info.actualPort ?? info.assignedPort}`
41
+ if (info.status === 'ready') {
42
+ console.log(`${colors.green}ā—${colors.reset} ${colors.cyan}${name}${colors.reset} ${colors.dim}→${colors.reset} ${url}`)
43
+ }
44
+ else {
45
+ console.log(`${colors.yellow}ā—‹${colors.reset} ${colors.dim}${name} (starting...)${colors.reset} ${colors.dim}${url}${colors.reset}`)
46
+ }
47
+ }
48
+
49
+ console.log('')
50
+ }
51
+
52
+ export async function runDev(
53
+ filter?: string,
54
+ additionalArgs: string[] = [],
55
+ ) {
56
+ const argsStr = additionalArgs.length > 0 ? ` -- ${additionalArgs.join(' ')}` : ''
57
+ const resolvedFilters = resolveFilters(filter)
58
+ if (!resolvedFilters || resolvedFilters.length === 0) return 1
59
+
60
+ // Pre-assign ports based on alphabetical order
61
+ const projectNames = resolvedFilters.map(f => f.replace('./', '')).sort()
62
+ projectNames.forEach((name, index) => {
63
+ const assignedPort = START_PORT + index
64
+ servers.set(name, {
65
+ name,
66
+ assignedPort,
67
+ status: 'starting',
68
+ order: index,
69
+ })
70
+ projectToPort.set(name, assignedPort)
71
+ })
72
+
73
+ clearAndPrintServers()
74
+
75
+ const filterArgs = resolvedFilters.map(f => `--filter '${f}'`).join(' ')
76
+ const projectNamesDisplay = projectNames.join(', ')
77
+
78
+ const command = `bun run ${filterArgs} dev${argsStr}`
79
+ const proc = spawn(command, {
80
+ cwd: process.cwd(),
81
+ stdio: ['inherit', 'pipe', 'pipe'],
82
+ shell: true,
83
+ })
84
+
85
+ let stdoutBuffer = ''
86
+ let stderrBuffer = ''
87
+
88
+ function processLine(line: string) {
89
+ if (!line.trim()) return
90
+
91
+ // Skip duplicate lines
92
+ if (seenLines.has(line)) return
93
+ seenLines.add(line)
94
+
95
+ // Extract port from "project dev: āžœ Local: http://localhost:5173/"
96
+ const portMatch = line.match(/Local:\s+http:\/\/localhost:(\d+)/)
97
+ if (portMatch) {
98
+ const port = Number.parseInt(portMatch[1], 10)
99
+
100
+ // Extract project name from this line
101
+ const projectInLine = line.match(/^([\w-]+)\s+dev:/)
102
+ if (projectInLine) {
103
+ const name = projectInLine[1]
104
+ const info = servers.get(name)
105
+ if (info && !info.actualPort) {
106
+ info.actualPort = port
107
+ info.status = 'ready'
108
+ clearAndPrintServers()
109
+ }
110
+ }
111
+ }
112
+ }
113
+
114
+ proc.stdout?.setEncoding('utf8')
115
+ proc.stderr?.setEncoding('utf8')
116
+
117
+ proc.stdout?.on('data', (data: string) => {
118
+ // Debug: show we're receiving data
119
+ if (servers.size === 0) {
120
+ console.log(`${colors.dim}Receiving output...${colors.reset}`)
121
+ }
122
+
123
+ stdoutBuffer += data
124
+ const lines = stdoutBuffer.split('\n')
125
+ stdoutBuffer = lines.pop() || ''
126
+
127
+ for (const line of lines) {
128
+ processLine(line)
129
+ }
130
+ })
131
+
132
+ proc.stderr?.on('data', (data: string) => {
133
+ stderrBuffer += data
134
+ const lines = stderrBuffer.split('\n')
135
+ stderrBuffer = lines.pop() || ''
136
+
137
+ for (const line of lines) {
138
+ processLine(line)
139
+ }
140
+ })
141
+
142
+ proc.on('error', (error) => {
143
+ console.error(`${colors.red}Failed to start dev servers:${colors.reset}`, error.message)
144
+ })
145
+
146
+ // Handle Ctrl+C gracefully
147
+ process.on('SIGINT', () => {
148
+ proc.kill('SIGINT')
149
+ process.exit(0)
150
+ })
151
+
152
+ return new Promise<number>((resolve, reject) => {
153
+ proc.on('exit', (code) => {
154
+ resolve(code || 0)
155
+ })
156
+ proc.on('error', reject)
157
+ })
158
+ }
159
+
160
+ function resolveFilters(filter?: string): string[] | null {
161
+ if (filter) return [filter]
162
+
163
+ const projects = listProjects()
164
+ if (projects.length === 0) {
165
+ console.error('No projects found')
166
+ return null
167
+ }
168
+
169
+ // Return all projects as individual filters
170
+ return projects.map(p => `./${p}`)
171
+ }
package/src/index.ts CHANGED
@@ -4,16 +4,8 @@ import type {
4
4
  WorkspaceOptions,
5
5
  ProxyConfig,
6
6
  } from './types'
7
- import { resolveConfig, mergeConfigs } from './config'
8
- import { generateWorkspaceConfig, generateWorkspaceConfigSync } from './init'
9
- import {
10
- generateNetlifyConfig,
11
- generateNetlifyRedirect,
12
- writeNetlifyConfig,
13
- setBuildEnvVars,
14
- } from './netlify'
15
- import { createViteProxy, createCustomProxy } from './proxy'
16
7
 
8
+ // Export types (no runtime code)
17
9
  export type {
18
10
  ProxyConfig,
19
11
  WorkspaceConfig,
@@ -21,23 +13,9 @@ export type {
21
13
  WorkspaceOptions,
22
14
  }
23
15
 
24
- export {
25
- createCustomProxy,
26
- createViteProxy,
27
- generateNetlifyConfig,
28
- generateNetlifyRedirect,
29
- generateWorkspaceConfig,
30
- generateWorkspaceConfigSync,
31
- mergeConfigs,
32
- resolveConfig,
33
- setBuildEnvVars,
34
- writeNetlifyConfig,
35
- }
36
-
37
- export { setupLint } from './lint'
38
- export { generateSDK, generateSDKForWorkspace } from './sdk'
39
- export { addProject, initWorkspace, listProjects } from './workspace'
40
- export { getWorkspaceInfo, isWorkspace } from './detect'
16
+ // Runtime composables (browser-safe, no Node.js dependencies)
17
+ export { getApiUrl, useWorkspace } from './composable'
18
+ export type { RuntimeWorkspaceConfig } from './composable'
41
19
 
42
20
  /**
43
21
  * Define workspace configuration
@@ -50,55 +28,3 @@ export function defineWorkspace(
50
28
  return configs[mode] || configs.development
51
29
  }
52
30
  }
53
-
54
- /**
55
- * Create a workspace instance for managing project configuration
56
- * Supports both single project and monorepo setups
57
- */
58
- export function createWorkspace(options: WorkspaceOptions = {}) {
59
- let cachedConfig: WorkspaceConfig | null = null
60
-
61
- return {
62
- /**
63
- * Get resolved config for the specified environment
64
- */
65
- async getConfig(mode: WorkspaceEnvironment = 'development'): Promise<WorkspaceConfig> {
66
- if (!cachedConfig) {
67
- cachedConfig = await resolveConfig(mode, options)
68
- }
69
- return cachedConfig
70
- },
71
-
72
- /**
73
- * Create Vite proxy configuration
74
- */
75
- createProxy(config: WorkspaceConfig): ProxyConfig {
76
- return createViteProxy(config)
77
- },
78
-
79
- /**
80
- * Generate Netlify configuration file
81
- */
82
- generateNetlify(
83
- config: WorkspaceConfig,
84
- outPath: string = './netlify.toml',
85
- additionalConfig?: string
86
- ): void {
87
- writeNetlifyConfig(config, outPath, additionalConfig)
88
- },
89
-
90
- /**
91
- * Set build environment variables
92
- */
93
- setBuildEnv(config: WorkspaceConfig): void {
94
- setBuildEnvVars(config)
95
- },
96
-
97
- /**
98
- * Clear cached configuration
99
- */
100
- clearCache(): void {
101
- cachedConfig = null
102
- },
103
- }
104
- }
package/src/init.ts CHANGED
@@ -49,10 +49,15 @@ export async function generateWorkspaceConfig(
49
49
  const configContent = `import { defineWorkspace } from '@bagelink/workspace'
50
50
  import type { WorkspaceConfig, WorkspaceEnvironment } from '@bagelink/workspace'
51
51
 
52
+ /**
53
+ * Define your workspace environments
54
+ * You can add as many custom environments as needed (e.g., 'staging', 'preview')
55
+ * Use --mode flag to switch: bgl dev --mode <env_name>
56
+ */
52
57
  const configs: Record<WorkspaceEnvironment, WorkspaceConfig> = {
53
58
  localhost: {
54
59
  host: 'http://localhost:8000',
55
- proxy: '/api',
60
+ proxy: '/api', // Optional: remove to skip proxy setup
56
61
  openapi_url: 'http://localhost:8000/openapi.json',
57
62
  },
58
63
  development: {
@@ -91,6 +96,12 @@ export default defineWorkspace(configs)
91
96
  message: 'Create/update vite.config.ts?',
92
97
  initial: true,
93
98
  },
99
+ {
100
+ type: 'confirm',
101
+ name: 'createTsConfig',
102
+ message: 'Create tsconfig.app.json with path aliases?',
103
+ initial: true,
104
+ },
94
105
  {
95
106
  type: 'confirm',
96
107
  name: 'generateNetlify',
@@ -107,6 +118,10 @@ export default defineWorkspace(configs)
107
118
  updateViteConfig(root)
108
119
  }
109
120
 
121
+ if (setupResponse.createTsConfig) {
122
+ createTsConfig(root)
123
+ }
124
+
110
125
  if (setupResponse.generateNetlify) {
111
126
  const prodConfig: WorkspaceConfig = {
112
127
  host: productionHost,
@@ -132,10 +147,15 @@ export function generateWorkspaceConfigSync(
132
147
  const configContent = `import { defineWorkspace } from '@bagelink/workspace'
133
148
  import type { WorkspaceConfig, WorkspaceEnvironment } from '@bagelink/workspace'
134
149
 
150
+ /**
151
+ * Define your workspace environments
152
+ * You can add as many custom environments as needed (e.g., 'staging', 'preview')
153
+ * Use --mode flag to switch: bgl dev --mode <env_name>
154
+ */
135
155
  const configs: Record<WorkspaceEnvironment, WorkspaceConfig> = {
136
156
  localhost: {
137
157
  host: 'http://localhost:8000',
138
- proxy: '/api',
158
+ proxy: '/api', // Optional: remove to skip proxy setup
139
159
  openapi_url: 'http://localhost:8000/openapi.json',
140
160
  },
141
161
  development: {
@@ -216,6 +236,38 @@ function updatePackageJsonScripts(root: string): void {
216
236
  }
217
237
  }
218
238
 
239
+ /**
240
+ * Create tsconfig.app.json with path aliases
241
+ */
242
+ function createTsConfig(root: string): void {
243
+ const tsConfigPath = resolve(root, 'tsconfig.app.json')
244
+ const tsConfigExists = existsSync(tsConfigPath)
245
+
246
+ if (tsConfigExists) {
247
+ console.log('āš ļø tsconfig.app.json already exists, skipping')
248
+ return
249
+ }
250
+
251
+ const tsConfigContent = `{
252
+ "extends": "../tsconfig.json",
253
+ "compilerOptions": {
254
+ "composite": true,
255
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
256
+ "baseUrl": ".",
257
+ "paths": {
258
+ "@/*": ["./src/*"],
259
+ "@shared/*": ["../shared/*"]
260
+ }
261
+ },
262
+ "include": ["src/**/*", "src/**/*.vue"],
263
+ "exclude": ["node_modules"]
264
+ }
265
+ `
266
+
267
+ writeFileSync(tsConfigPath, tsConfigContent, 'utf-8')
268
+ console.log('āœ… Created tsconfig.app.json')
269
+ }
270
+
219
271
  /**
220
272
  * Create or update vite.config.ts
221
273
  */
@@ -233,25 +285,31 @@ function updateViteConfig(root: string): void {
233
285
  }
234
286
 
235
287
  // Ask to overwrite
236
- console.log('āš ļø vite.config.ts exists. Please manually add the workspace configuration.')
237
- console.log(' See: https://github.com/bageldb/bagelink/tree/master/packages/workspace#readme')
288
+ console.log('āš ļø vite.config.ts exists. Please manually add the bagelink plugin:')
289
+ console.log('')
290
+ console.log(' import { bagelink } from \'@bagelink/workspace/vite\'')
291
+ console.log(' import workspace from \'./bgl.config\'')
292
+ console.log('')
293
+ console.log(' plugins: [')
294
+ console.log(' vue(),')
295
+ console.log(' bagelink({ workspace }),')
296
+ console.log(' ]')
297
+ console.log('')
238
298
  return
239
299
  }
240
300
 
241
- // Create new vite.config.ts
301
+ // Create new vite.config.ts with plugin
242
302
  const viteConfigContent = `import { defineConfig } from 'vite'
243
- import { createViteProxy } from '@bagelink/workspace'
303
+ import vue from '@vitejs/plugin-vue'
304
+ import { bagelink } from '@bagelink/workspace/vite'
244
305
  import workspace from './bgl.config'
245
306
 
246
307
  // https://vitejs.dev/config/
247
- export default defineConfig(({ mode }) => {
248
- const config = workspace(mode as 'localhost' | 'development' | 'production')
249
-
250
- return {
251
- server: {
252
- proxy: createViteProxy(config),
253
- },
254
- }
308
+ export default defineConfig({
309
+ plugins: [
310
+ vue(),
311
+ bagelink({ workspace }),
312
+ ],
255
313
  })
256
314
  `
257
315
 
package/src/lint.ts CHANGED
@@ -1,8 +1,35 @@
1
- import { existsSync, readFileSync, writeFileSync } from 'node:fs'
1
+ import { existsSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs'
2
2
  import { resolve } from 'node:path'
3
3
  import process from 'node:process'
4
4
  import prompts from 'prompts'
5
5
 
6
+ /**
7
+ * List of redundant lint config files that can be safely removed
8
+ * after setting up the new lint configuration
9
+ */
10
+ const REDUNDANT_FILES = [
11
+ // Old ESLint configs
12
+ '.eslintrc',
13
+ '.eslintrc.json',
14
+ '.eslintrc.js',
15
+ '.eslintrc.cjs',
16
+ '.eslintrc.yaml',
17
+ '.eslintrc.yml',
18
+ // Oxlint
19
+ 'oxlint.json',
20
+ // Old Prettier configs (we create .prettierrc)
21
+ 'prettier.config.js',
22
+ 'prettier.config.cjs',
23
+ 'prettier.config.mjs',
24
+ '.prettierrc.json',
25
+ '.prettierrc.yaml',
26
+ '.prettierrc.yml',
27
+ '.prettierrc.js',
28
+ '.prettierrc.cjs',
29
+ '.prettierrc.mjs',
30
+ '.prettierrc.toml',
31
+ ]
32
+
6
33
  /**
7
34
  * Set up linting in a project
8
35
  */
@@ -24,6 +51,12 @@ export async function setupLint(
24
51
  { title: 'Git Hooks', value: 'githooks', selected: false },
25
52
  ],
26
53
  },
54
+ {
55
+ type: 'confirm',
56
+ name: 'cleanRedundant',
57
+ message: 'Clean up redundant lint config files?',
58
+ initial: true,
59
+ },
27
60
  {
28
61
  type: 'confirm',
29
62
  name: 'installDeps',
@@ -37,7 +70,12 @@ export async function setupLint(
37
70
  process.exit(1)
38
71
  }
39
72
 
40
- const { configs, installDeps } = response
73
+ const { configs, cleanRedundant, installDeps } = response
74
+
75
+ // Clean up redundant files first
76
+ if (cleanRedundant) {
77
+ await cleanRedundantFiles(root)
78
+ }
41
79
 
42
80
  // Create config files
43
81
  if (configs.includes('eslint')) {
@@ -182,6 +220,56 @@ function createGitHooks(root: string): void {
182
220
  console.log(' Then run: npx simple-git-hooks')
183
221
  }
184
222
 
223
+ /**
224
+ * Clean up redundant lint configuration files
225
+ */
226
+ async function cleanRedundantFiles(root: string): Promise<void> {
227
+ const foundFiles: string[] = []
228
+
229
+ // Check which redundant files exist
230
+ for (const file of REDUNDANT_FILES) {
231
+ const filePath = resolve(root, file)
232
+ if (existsSync(filePath)) {
233
+ foundFiles.push(file)
234
+ }
235
+ }
236
+
237
+ if (foundFiles.length === 0) {
238
+ console.log('✨ No redundant files found')
239
+ return
240
+ }
241
+
242
+ console.log('\nšŸ“‹ Found redundant files:')
243
+ foundFiles.forEach((file) => { console.log(` - ${file}`) })
244
+
245
+ const confirmResponse = await prompts({
246
+ type: 'confirm',
247
+ name: 'confirm',
248
+ message: `Delete ${foundFiles.length} redundant file${foundFiles.length > 1 ? 's' : ''}?`,
249
+ initial: true,
250
+ })
251
+
252
+ if (!confirmResponse.confirm) {
253
+ console.log('ā­ļø Skipped cleaning redundant files')
254
+ return
255
+ }
256
+
257
+ // Delete confirmed files
258
+ let deleted = 0
259
+ for (const file of foundFiles) {
260
+ try {
261
+ unlinkSync(resolve(root, file))
262
+ console.log(`šŸ—‘ļø Deleted ${file}`)
263
+ deleted++
264
+ }
265
+ catch (error) {
266
+ console.error(`āŒ Failed to delete ${file}:`, error)
267
+ }
268
+ }
269
+
270
+ console.log(`āœ… Cleaned up ${deleted} file${deleted > 1 ? 's' : ''}`)
271
+ }
272
+
185
273
  /**
186
274
  * Update package.json with lint scripts
187
275
  */