@bagelink/workspace 1.7.4 → 1.9.0

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@bagelink/workspace",
3
3
  "type": "module",
4
- "version": "1.7.4",
4
+ "version": "1.9.0",
5
5
  "description": "Monorepo workspace tooling for Bagel projects with proxy and config management",
6
6
  "author": {
7
7
  "name": "Bagel Studio",
@@ -52,6 +52,14 @@
52
52
  "dependencies": {
53
53
  "prompts": "^2.4.2"
54
54
  },
55
+ "peerDependencies": {
56
+ "vite": ">=5.0.0"
57
+ },
58
+ "peerDependenciesMeta": {
59
+ "@bagelink/lint-config": {
60
+ "optional": true
61
+ }
62
+ },
55
63
  "devDependencies": {
56
64
  "@types/node": "^24.0.0",
57
65
  "@types/prompts": "^2.4.9",
@@ -59,9 +67,6 @@
59
67
  "typescript": "^5.8.3",
60
68
  "unbuild": "^3.5.0"
61
69
  },
62
- "peerDependencies": {
63
- "vite": ">=5.0.0"
64
- },
65
70
  "scripts": {
66
71
  "dev": "unbuild --stub",
67
72
  "build": "unbuild",
package/src/index.ts CHANGED
@@ -34,6 +34,9 @@ export {
34
34
  writeNetlifyConfig,
35
35
  }
36
36
 
37
+ export { addProject, initWorkspace, listProjects } from './workspace'
38
+ export { setupLint } from './lint'
39
+
37
40
  /**
38
41
  * Define workspace configuration
39
42
  * Simple helper to get config from a config map
package/src/lint.ts ADDED
@@ -0,0 +1,235 @@
1
+ import { existsSync, writeFileSync } from 'node:fs'
2
+ import { resolve } from 'node:path'
3
+ import process from 'node:process'
4
+ import prompts from 'prompts'
5
+
6
+ /**
7
+ * Set up linting in a project
8
+ */
9
+ export async function setupLint(
10
+ root: string = process.cwd(),
11
+ isWorkspace: boolean = false,
12
+ ): Promise<void> {
13
+ console.log('\nšŸ” Setting up linting...\n')
14
+
15
+ const response = await prompts([
16
+ {
17
+ type: 'multiselect',
18
+ name: 'configs',
19
+ message: 'Select configurations to set up:',
20
+ choices: [
21
+ { title: 'ESLint', value: 'eslint', selected: true },
22
+ { title: 'Prettier', value: 'prettier', selected: true },
23
+ { title: 'EditorConfig', value: 'editorconfig', selected: true },
24
+ { title: 'Git Hooks', value: 'githooks', selected: false },
25
+ ],
26
+ },
27
+ {
28
+ type: 'confirm',
29
+ name: 'installDeps',
30
+ message: 'Install dependencies?',
31
+ initial: true,
32
+ },
33
+ ])
34
+
35
+ if (!response || !response.configs) {
36
+ console.log('\nāŒ Setup cancelled.\n')
37
+ process.exit(1)
38
+ }
39
+
40
+ const { configs, installDeps } = response
41
+
42
+ // Create config files
43
+ if (configs.includes('eslint')) {
44
+ createEslintConfig(root, isWorkspace)
45
+ }
46
+
47
+ if (configs.includes('prettier')) {
48
+ createPrettierConfig(root)
49
+ }
50
+
51
+ if (configs.includes('editorconfig')) {
52
+ createEditorConfig(root)
53
+ }
54
+
55
+ if (configs.includes('githooks')) {
56
+ createGitHooks(root)
57
+ }
58
+
59
+ // Update package.json
60
+ updatePackageJsonLint(root, configs)
61
+
62
+ if (installDeps) {
63
+ console.log('\nšŸ“¦ Installing dependencies...')
64
+ console.log('Run: bun add -D @bagelink/lint-config eslint prettier typescript')
65
+ }
66
+
67
+ console.log('\nāœ… Linting setup complete!')
68
+ console.log('\nAvailable commands:')
69
+ console.log(' bun run lint - Run linter')
70
+ console.log(' bun run lint:fix - Fix linting issues')
71
+ console.log(' bun run format - Format code with Prettier')
72
+ console.log('')
73
+ }
74
+
75
+ /**
76
+ * Create ESLint config
77
+ */
78
+ function createEslintConfig(root: string, isWorkspace: boolean): void {
79
+ const configPath = resolve(root, 'eslint.config.js')
80
+
81
+ const config = isWorkspace
82
+ ? `import { defineConfig } from '@bagelink/lint-config/eslint'
83
+
84
+ export default defineConfig({
85
+ // Workspace-level ESLint config
86
+ ignores: ['**/dist/**', '**/node_modules/**', '**/.bun-cache/**'],
87
+ })
88
+ `
89
+ : `import vue3Config from '@bagelink/lint-config/eslint/vue3'
90
+
91
+ export default vue3Config
92
+ `
93
+
94
+ writeFileSync(configPath, config)
95
+ console.log('āœ… Created eslint.config.js')
96
+ }
97
+
98
+ /**
99
+ * Create Prettier config
100
+ */
101
+ function createPrettierConfig(root: string): void {
102
+ const configPath = resolve(root, '.prettierrc')
103
+
104
+ const config = {
105
+ semi: false,
106
+ singleQuote: true,
107
+ tabWidth: 2,
108
+ useTabs: true,
109
+ trailingComma: 'all',
110
+ printWidth: 100,
111
+ arrowParens: 'avoid',
112
+ }
113
+
114
+ writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`)
115
+ console.log('āœ… Created .prettierrc')
116
+
117
+ // .prettierignore
118
+ const ignorePath = resolve(root, '.prettierignore')
119
+ const ignore = `dist
120
+ node_modules
121
+ .bun-cache
122
+ *.min.js
123
+ *.min.css
124
+ `
125
+
126
+ writeFileSync(ignorePath, ignore)
127
+ console.log('āœ… Created .prettierignore')
128
+ }
129
+
130
+ /**
131
+ * Create EditorConfig
132
+ */
133
+ function createEditorConfig(root: string): void {
134
+ const configPath = resolve(root, '.editorconfig')
135
+
136
+ const config = `root = true
137
+
138
+ [*]
139
+ charset = utf-8
140
+ indent_style = tab
141
+ indent_size = 2
142
+ end_of_line = lf
143
+ insert_final_newline = true
144
+ trim_trailing_whitespace = true
145
+
146
+ [*.md]
147
+ trim_trailing_whitespace = false
148
+
149
+ [*.{json,yml,yaml}]
150
+ indent_style = space
151
+ indent_size = 2
152
+ `
153
+
154
+ writeFileSync(configPath, config)
155
+ console.log('āœ… Created .editorconfig')
156
+ }
157
+
158
+ /**
159
+ * Create Git Hooks
160
+ */
161
+ function createGitHooks(root: string): void {
162
+ const packageJsonPath = resolve(root, 'package.json')
163
+
164
+ if (!existsSync(packageJsonPath)) {
165
+ console.warn('āš ļø No package.json found, skipping git hooks')
166
+ return
167
+ }
168
+
169
+ // .lintstagedrc
170
+ const lintStagedConfig = {
171
+ '*.{js,jsx,ts,tsx,vue}': ['eslint --fix'],
172
+ '*.{json,md,yml,yaml}': ['prettier --write'],
173
+ }
174
+
175
+ writeFileSync(
176
+ resolve(root, '.lintstagedrc'),
177
+ `${JSON.stringify(lintStagedConfig, null, 2)}\n`,
178
+ )
179
+
180
+ console.log('āœ… Created .lintstagedrc')
181
+ console.log('ā„¹ļø Add simple-git-hooks and lint-staged to devDependencies')
182
+ console.log(' Then run: npx simple-git-hooks')
183
+ }
184
+
185
+ /**
186
+ * Update package.json with lint scripts
187
+ */
188
+ function updatePackageJsonLint(root: string, configs: string[]): void {
189
+ const packageJsonPath = resolve(root, 'package.json')
190
+
191
+ if (!existsSync(packageJsonPath)) {
192
+ console.warn('āš ļø No package.json found')
193
+ return
194
+ }
195
+
196
+ try {
197
+ const packageJson = JSON.parse(
198
+ require('fs').readFileSync(packageJsonPath, 'utf-8'),
199
+ )
200
+
201
+ if (!packageJson.scripts) {
202
+ packageJson.scripts = {}
203
+ }
204
+
205
+ // Add lint scripts
206
+ if (configs.includes('eslint')) {
207
+ if (!packageJson.scripts.lint) {
208
+ packageJson.scripts.lint = 'eslint .'
209
+ }
210
+ if (!packageJson.scripts['lint:fix']) {
211
+ packageJson.scripts['lint:fix'] = 'eslint . --fix'
212
+ }
213
+ }
214
+
215
+ // Add format scripts
216
+ if (configs.includes('prettier')) {
217
+ if (!packageJson.scripts.format) {
218
+ packageJson.scripts.format = 'prettier --write .'
219
+ }
220
+ if (!packageJson.scripts['format:check']) {
221
+ packageJson.scripts['format:check'] = 'prettier --check .'
222
+ }
223
+ }
224
+
225
+ writeFileSync(
226
+ packageJsonPath,
227
+ `${JSON.stringify(packageJson, null, 2)}\n`,
228
+ )
229
+
230
+ console.log('āœ… Updated package.json with lint scripts')
231
+ }
232
+ catch (error) {
233
+ console.error('āŒ Failed to update package.json:', error)
234
+ }
235
+ }
@@ -0,0 +1,432 @@
1
+ import { existsSync, mkdirSync, readdirSync, writeFileSync } from 'node:fs'
2
+ import { resolve } from 'node:path'
3
+ import process from 'node:process'
4
+ import prompts from 'prompts'
5
+
6
+ /**
7
+ * Initialize a new workspace with flat structure
8
+ */
9
+ export async function initWorkspace(root: string = process.cwd()): Promise<void> {
10
+ console.log('\nšŸš€ Creating Bagel workspace...\n')
11
+
12
+ const response = await prompts([
13
+ {
14
+ type: 'text',
15
+ name: 'workspaceName',
16
+ message: 'Workspace name:',
17
+ initial: 'my-workspace',
18
+ },
19
+ {
20
+ type: 'text',
21
+ name: 'projectId',
22
+ message: 'Bagel project ID:',
23
+ initial: 'my-project',
24
+ },
25
+ {
26
+ type: 'confirm',
27
+ name: 'createFirstProject',
28
+ message: 'Create first project?',
29
+ initial: true,
30
+ },
31
+ {
32
+ type: (prev: boolean) => (prev ? 'text' : null),
33
+ name: 'firstProjectName',
34
+ message: 'First project name:',
35
+ initial: 'web',
36
+ },
37
+ ])
38
+
39
+ if (!response || !response.workspaceName) {
40
+ console.log('\nāŒ Workspace creation cancelled.\n')
41
+ process.exit(1)
42
+ }
43
+
44
+ const { workspaceName, projectId, createFirstProject, firstProjectName } = response
45
+
46
+ // Create workspace structure
47
+ createWorkspaceRoot(root, workspaceName, projectId)
48
+
49
+ // Create shared package
50
+ createSharedPackage(root)
51
+
52
+ if (createFirstProject && firstProjectName) {
53
+ await addProject(firstProjectName, root)
54
+ }
55
+
56
+ console.log('\nāœ… Workspace created successfully!')
57
+ console.log('\nNext steps:')
58
+ console.log(` cd ${workspaceName}`)
59
+ console.log(' bun install')
60
+ if (createFirstProject) {
61
+ console.log(` bun run dev:${firstProjectName}`)
62
+ }
63
+ else {
64
+ console.log(' bgl add <project-name> # Add a project')
65
+ }
66
+ console.log('')
67
+ }
68
+
69
+ /**
70
+ * Create workspace root files
71
+ */
72
+ function createWorkspaceRoot(root: string, name: string, projectId: string): void {
73
+ const workspaceDir = resolve(root, name)
74
+
75
+ if (existsSync(workspaceDir)) {
76
+ console.error(`āŒ Directory ${name} already exists`)
77
+ process.exit(1)
78
+ }
79
+
80
+ mkdirSync(workspaceDir, { recursive: true })
81
+
82
+ // package.json
83
+ const packageJson = {
84
+ name,
85
+ private: true,
86
+ workspaces: ['*', '!node_modules'],
87
+ scripts: {
88
+ dev: 'bun run --filter \'./[!shared]*\' dev',
89
+ build: 'bun run --filter \'./[!shared]*\' build',
90
+ typecheck: 'tsc --noEmit',
91
+ },
92
+ devDependencies: {
93
+ '@bagelink/workspace': 'latest',
94
+ typescript: '^5.0.0',
95
+ vite: 'latest',
96
+ },
97
+ }
98
+
99
+ writeFileSync(
100
+ resolve(workspaceDir, 'package.json'),
101
+ `${JSON.stringify(packageJson, null, 2)}\n`,
102
+ )
103
+
104
+ // bgl.config.ts (shared)
105
+ const bglConfig = `import { defineWorkspace } from '@bagelink/workspace'
106
+
107
+ export default defineWorkspace({
108
+ localhost: {
109
+ host: 'http://localhost:8000',
110
+ proxy: '/api',
111
+ openapi_url: 'http://localhost:8000/openapi.json',
112
+ },
113
+ development: {
114
+ host: 'https://${projectId}.bagel.to',
115
+ proxy: '/api',
116
+ openapi_url: 'https://${projectId}.bagel.to/openapi.json',
117
+ },
118
+ production: {
119
+ host: 'https://${projectId}.bagel.to',
120
+ proxy: '/api',
121
+ openapi_url: 'https://${projectId}.bagel.to/openapi.json',
122
+ },
123
+ })
124
+ `
125
+
126
+ writeFileSync(resolve(workspaceDir, 'bgl.config.ts'), bglConfig)
127
+
128
+ // tsconfig.json
129
+ const tsConfig = {
130
+ compilerOptions: {
131
+ target: 'ES2020',
132
+ useDefineForClassFields: true,
133
+ module: 'ESNext',
134
+ lib: ['ES2020', 'DOM', 'DOM.Iterable'],
135
+ skipLibCheck: true,
136
+ moduleResolution: 'bundler',
137
+ allowImportingTsExtensions: true,
138
+ resolveJsonModule: true,
139
+ isolatedModules: true,
140
+ noEmit: true,
141
+ jsx: 'preserve',
142
+ strict: true,
143
+ noUnusedLocals: true,
144
+ noUnusedParameters: true,
145
+ noFallthroughCasesInSwitch: true,
146
+ baseUrl: '.',
147
+ paths: {
148
+ 'shared/*': ['./shared/*'],
149
+ },
150
+ },
151
+ }
152
+
153
+ writeFileSync(
154
+ resolve(workspaceDir, 'tsconfig.json'),
155
+ `${JSON.stringify(tsConfig, null, 2)}\n`,
156
+ )
157
+
158
+ // .gitignore
159
+ const gitignore = `node_modules
160
+ dist
161
+ .DS_Store
162
+ *.local
163
+ .env.local
164
+ .vite
165
+ `
166
+
167
+ writeFileSync(resolve(workspaceDir, '.gitignore'), gitignore)
168
+
169
+ console.log(`āœ… Created workspace: ${name}`)
170
+ }
171
+
172
+ /**
173
+ * Create shared package
174
+ */
175
+ function createSharedPackage(root: string): void {
176
+ const sharedDir = resolve(root, 'shared')
177
+ mkdirSync(sharedDir, { recursive: true })
178
+
179
+ // package.json
180
+ const packageJson = {
181
+ name: 'shared',
182
+ version: '1.0.0',
183
+ type: 'module',
184
+ exports: {
185
+ '.': './index.ts',
186
+ './utils': './utils/index.ts',
187
+ './types': './types/index.ts',
188
+ },
189
+ }
190
+
191
+ writeFileSync(
192
+ resolve(sharedDir, 'package.json'),
193
+ `${JSON.stringify(packageJson, null, 2)}\n`,
194
+ )
195
+
196
+ // index.ts
197
+ writeFileSync(
198
+ resolve(sharedDir, 'index.ts'),
199
+ '// Shared utilities and exports\nexport * from \'./utils\'\n',
200
+ )
201
+
202
+ // utils/index.ts
203
+ mkdirSync(resolve(sharedDir, 'utils'), { recursive: true })
204
+ writeFileSync(
205
+ resolve(sharedDir, 'utils', 'index.ts'),
206
+ '// Shared utility functions\nexport function formatDate(date: Date): string {\n return date.toISOString()\n}\n',
207
+ )
208
+
209
+ // types/index.ts
210
+ mkdirSync(resolve(sharedDir, 'types'), { recursive: true })
211
+ writeFileSync(
212
+ resolve(sharedDir, 'types', 'index.ts'),
213
+ '// Shared types\nexport interface User {\n id: string\n name: string\n}\n',
214
+ )
215
+
216
+ console.log('āœ… Created shared package')
217
+ }
218
+
219
+ /**
220
+ * Add a new project to the workspace
221
+ */
222
+ export async function addProject(
223
+ name: string,
224
+ root: string = process.cwd(),
225
+ ): Promise<void> {
226
+ const projectDir = resolve(root, name)
227
+
228
+ if (existsSync(projectDir)) {
229
+ console.error(`āŒ Project ${name} already exists`)
230
+ process.exit(1)
231
+ }
232
+
233
+ console.log(`\nšŸ“¦ Creating project: ${name}\n`)
234
+
235
+ mkdirSync(projectDir, { recursive: true })
236
+
237
+ // Determine if this is part of a workspace
238
+ const isWorkspace = existsSync(resolve(root, 'bgl.config.ts'))
239
+
240
+ // package.json
241
+ const packageJson: Record<string, unknown> = {
242
+ name,
243
+ type: 'module',
244
+ scripts: {
245
+ dev: 'vite',
246
+ build: 'vite build',
247
+ preview: 'vite preview',
248
+ },
249
+ dependencies: {} as Record<string, string>,
250
+ devDependencies: {
251
+ '@vitejs/plugin-vue': 'latest',
252
+ vite: 'latest',
253
+ vue: 'latest',
254
+ },
255
+ }
256
+
257
+ // Add shared dependency if in workspace
258
+ if (isWorkspace) {
259
+ (packageJson.dependencies as Record<string, string>).shared = 'workspace:*'
260
+ }
261
+
262
+ writeFileSync(
263
+ resolve(projectDir, 'package.json'),
264
+ `${JSON.stringify(packageJson, null, 2)}\n`,
265
+ )
266
+
267
+ // bgl.config.ts
268
+ const bglConfigContent = isWorkspace
269
+ ? `import { defineWorkspace } from '@bagelink/workspace'
270
+ import rootWorkspace from '../bgl.config'
271
+
272
+ export default defineWorkspace({
273
+ localhost: rootWorkspace('localhost'),
274
+ development: rootWorkspace('development'),
275
+ production: rootWorkspace('production'),
276
+ })
277
+ `
278
+ : `import { defineWorkspace } from '@bagelink/workspace'
279
+
280
+ export default defineWorkspace({
281
+ localhost: {
282
+ host: 'http://localhost:8000',
283
+ proxy: '/api',
284
+ },
285
+ development: {
286
+ host: 'https://my-project.bagel.to',
287
+ proxy: '/api',
288
+ },
289
+ production: {
290
+ host: 'https://my-project.bagel.to',
291
+ proxy: '/api',
292
+ },
293
+ })
294
+ `
295
+
296
+ writeFileSync(resolve(projectDir, 'bgl.config.ts'), bglConfigContent)
297
+
298
+ // vite.config.ts
299
+ const viteConfig = `import { defineConfig } from 'vite'
300
+ import vue from '@vitejs/plugin-vue'
301
+ import { createViteProxy } from '@bagelink/workspace'
302
+ import workspace from './bgl.config'
303
+
304
+ export default defineConfig(({ mode }) => {
305
+ const config = workspace(mode as 'localhost' | 'development' | 'production')
306
+
307
+ return {
308
+ plugins: [vue()],
309
+ server: {
310
+ proxy: createViteProxy(config),
311
+ },
312
+ }
313
+ })
314
+ `
315
+
316
+ writeFileSync(resolve(projectDir, 'vite.config.ts'), viteConfig)
317
+
318
+ // Create basic src structure
319
+ const srcDir = resolve(projectDir, 'src')
320
+ mkdirSync(srcDir, { recursive: true })
321
+
322
+ // index.html
323
+ const indexHtml = `<!DOCTYPE html>
324
+ <html lang="en">
325
+ <head>
326
+ <meta charset="UTF-8">
327
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
328
+ <title>${name}</title>
329
+ </head>
330
+ <body>
331
+ <div id="app"></div>
332
+ <script type="module" src="/src/main.ts"></script>
333
+ </body>
334
+ </html>
335
+ `
336
+
337
+ writeFileSync(resolve(projectDir, 'index.html'), indexHtml)
338
+
339
+ // main.ts
340
+ const mainTs = `import { createApp } from 'vue'
341
+ import App from './App.vue'
342
+
343
+ createApp(App).mount('#app')
344
+ `
345
+
346
+ writeFileSync(resolve(srcDir, 'main.ts'), mainTs)
347
+
348
+ // App.vue
349
+ const appVue = `<script setup lang="ts">
350
+ import { ref } from 'vue'
351
+ ${isWorkspace ? 'import { formatDate } from \'shared/utils\'\n' : ''}
352
+ const count = ref(0)
353
+ </script>
354
+
355
+ <template>
356
+ <div>
357
+ <h1>${name}</h1>
358
+ <button @click="count++">Count: {{ count }}</button>
359
+ ${isWorkspace ? '<p>{{ formatDate(new Date()) }}</p>' : ''}
360
+ </div>
361
+ </template>
362
+ `
363
+
364
+ writeFileSync(resolve(srcDir, 'App.vue'), appVue)
365
+
366
+ console.log(`āœ… Created project: ${name}`)
367
+
368
+ // Update workspace package.json if needed
369
+ if (isWorkspace) {
370
+ updateWorkspaceScripts(root, name)
371
+ }
372
+
373
+ console.log('\nNext steps:')
374
+ console.log(` cd ${name}`)
375
+ console.log(' bun install')
376
+ console.log(' bun run dev')
377
+ console.log('')
378
+ }
379
+
380
+ /**
381
+ * Update workspace root package.json with new project scripts
382
+ */
383
+ function updateWorkspaceScripts(root: string, projectName: string): void {
384
+ const packageJsonPath = resolve(root, 'package.json')
385
+ if (!existsSync(packageJsonPath)) return
386
+
387
+ try {
388
+ const packageJson = JSON.parse(
389
+ require('fs').readFileSync(packageJsonPath, 'utf-8'),
390
+ )
391
+
392
+ if (!packageJson.scripts) {
393
+ packageJson.scripts = {}
394
+ }
395
+
396
+ // Add project-specific scripts
397
+ packageJson.scripts[`dev:${projectName}`] = `bun --filter ${projectName} dev`
398
+ packageJson.scripts[`build:${projectName}`] = `bun --filter ${projectName} build`
399
+
400
+ writeFileSync(
401
+ packageJsonPath,
402
+ `${JSON.stringify(packageJson, null, 2)}\n`,
403
+ )
404
+
405
+ console.log(`āœ… Added scripts: dev:${projectName}, build:${projectName}`)
406
+ }
407
+ catch (error) {
408
+ console.warn('āš ļø Could not update workspace scripts')
409
+ }
410
+ }
411
+
412
+ /**
413
+ * List all projects in workspace
414
+ */
415
+ export function listProjects(root: string = process.cwd()): string[] {
416
+ try {
417
+ const items = readdirSync(root, { withFileTypes: true })
418
+ return items
419
+ .filter(
420
+ item =>
421
+ item.isDirectory()
422
+ && item.name !== 'node_modules'
423
+ && item.name !== 'shared'
424
+ && item.name !== '.git'
425
+ && !item.name.startsWith('.'),
426
+ )
427
+ .map(item => item.name)
428
+ }
429
+ catch {
430
+ return []
431
+ }
432
+ }