create-turbo-mono 1.0.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/.claude/settings.local.json +14 -0
- package/README.md +182 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +118 -0
- package/dist/index.js.map +1 -0
- package/dist/scaffold.d.ts +8 -0
- package/dist/scaffold.d.ts.map +1 -0
- package/dist/scaffold.js +42 -0
- package/dist/scaffold.js.map +1 -0
- package/dist/templates/backend.d.ts +2 -0
- package/dist/templates/backend.d.ts.map +1 -0
- package/dist/templates/backend.js +424 -0
- package/dist/templates/backend.js.map +1 -0
- package/dist/templates/cicd.d.ts +3 -0
- package/dist/templates/cicd.d.ts.map +1 -0
- package/dist/templates/cicd.js +307 -0
- package/dist/templates/cicd.js.map +1 -0
- package/dist/templates/docker.d.ts +3 -0
- package/dist/templates/docker.d.ts.map +1 -0
- package/dist/templates/docker.js +458 -0
- package/dist/templates/docker.js.map +1 -0
- package/dist/templates/docs.d.ts +3 -0
- package/dist/templates/docs.d.ts.map +1 -0
- package/dist/templates/docs.js +71 -0
- package/dist/templates/docs.js.map +1 -0
- package/dist/templates/frontend.d.ts +2 -0
- package/dist/templates/frontend.d.ts.map +1 -0
- package/dist/templates/frontend.js +441 -0
- package/dist/templates/frontend.js.map +1 -0
- package/dist/templates/root.d.ts +3 -0
- package/dist/templates/root.d.ts.map +1 -0
- package/dist/templates/root.js +210 -0
- package/dist/templates/root.js.map +1 -0
- package/dist/templates/shared.d.ts +2 -0
- package/dist/templates/shared.d.ts.map +1 -0
- package/dist/templates/shared.js +696 -0
- package/dist/templates/shared.js.map +1 -0
- package/dist/utils.d.ts +5 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +34 -0
- package/dist/utils.js.map +1 -0
- package/package.json +40 -0
- package/src/index.ts +138 -0
- package/src/scaffold.ts +51 -0
- package/src/templates/backend.ts +460 -0
- package/src/templates/cicd.ts +334 -0
- package/src/templates/docker.ts +503 -0
- package/src/templates/docs.ts +74 -0
- package/src/templates/frontend.ts +469 -0
- package/src/templates/root.ts +216 -0
- package/src/templates/shared.ts +820 -0
- package/src/utils.ts +31 -0
- package/tsconfig.json +20 -0
|
@@ -0,0 +1,820 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
export async function createSharedPackages(targetDir: string): Promise<void> {
|
|
5
|
+
await createTsConfigPackage(targetDir);
|
|
6
|
+
await createDatabasePackage(targetDir);
|
|
7
|
+
await createSharedBackendPackage(targetDir);
|
|
8
|
+
await createSharedFrontendPackage(targetDir);
|
|
9
|
+
await createSharedCommonPackage(targetDir);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async function createTsConfigPackage(targetDir: string): Promise<void> {
|
|
13
|
+
const packageDir = path.join(targetDir, 'packages', 'tsconfig');
|
|
14
|
+
await fs.ensureDir(packageDir);
|
|
15
|
+
|
|
16
|
+
// package.json
|
|
17
|
+
const packageJson = {
|
|
18
|
+
name: '@repo/tsconfig',
|
|
19
|
+
version: '1.0.0',
|
|
20
|
+
private: true,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
await fs.writeJson(path.join(packageDir, 'package.json'), packageJson, {
|
|
24
|
+
spaces: 2,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// base.json
|
|
28
|
+
const baseConfig = {
|
|
29
|
+
compilerOptions: {
|
|
30
|
+
target: 'ES2020',
|
|
31
|
+
lib: ['ES2020'],
|
|
32
|
+
module: 'commonjs',
|
|
33
|
+
moduleResolution: 'node',
|
|
34
|
+
esModuleInterop: true,
|
|
35
|
+
skipLibCheck: true,
|
|
36
|
+
forceConsistentCasingInFileNames: true,
|
|
37
|
+
strict: true,
|
|
38
|
+
resolveJsonModule: true,
|
|
39
|
+
declaration: true,
|
|
40
|
+
declarationMap: true,
|
|
41
|
+
sourceMap: true,
|
|
42
|
+
incremental: true,
|
|
43
|
+
noUnusedLocals: true,
|
|
44
|
+
noUnusedParameters: true,
|
|
45
|
+
noImplicitReturns: true,
|
|
46
|
+
noFallthroughCasesInSwitch: true,
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
await fs.writeJson(path.join(packageDir, 'base.json'), baseConfig, {
|
|
51
|
+
spaces: 2,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// nestjs.json
|
|
55
|
+
const nestjsConfig = {
|
|
56
|
+
extends: './base.json',
|
|
57
|
+
compilerOptions: {
|
|
58
|
+
module: 'commonjs',
|
|
59
|
+
declaration: true,
|
|
60
|
+
removeComments: true,
|
|
61
|
+
emitDecoratorMetadata: true,
|
|
62
|
+
experimentalDecorators: true,
|
|
63
|
+
allowSyntheticDefaultImports: true,
|
|
64
|
+
target: 'ES2021',
|
|
65
|
+
sourceMap: true,
|
|
66
|
+
outDir: './dist',
|
|
67
|
+
baseUrl: './',
|
|
68
|
+
incremental: true,
|
|
69
|
+
skipLibCheck: true,
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
await fs.writeJson(path.join(packageDir, 'nestjs.json'), nestjsConfig, {
|
|
74
|
+
spaces: 2,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// nextjs.json
|
|
78
|
+
const nextjsConfig = {
|
|
79
|
+
extends: './base.json',
|
|
80
|
+
compilerOptions: {
|
|
81
|
+
target: 'ES2020',
|
|
82
|
+
lib: ['dom', 'dom.iterable', 'esnext'],
|
|
83
|
+
allowJs: true,
|
|
84
|
+
skipLibCheck: true,
|
|
85
|
+
strict: true,
|
|
86
|
+
forceConsistentCasingInFileNames: true,
|
|
87
|
+
noEmit: true,
|
|
88
|
+
esModuleInterop: true,
|
|
89
|
+
module: 'esnext',
|
|
90
|
+
moduleResolution: 'bundler',
|
|
91
|
+
resolveJsonModule: true,
|
|
92
|
+
isolatedModules: true,
|
|
93
|
+
jsx: 'preserve',
|
|
94
|
+
incremental: true,
|
|
95
|
+
plugins: [
|
|
96
|
+
{
|
|
97
|
+
name: 'next',
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
await fs.writeJson(path.join(packageDir, 'nextjs.json'), nextjsConfig, {
|
|
104
|
+
spaces: 2,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// react-library.json
|
|
108
|
+
const reactLibConfig = {
|
|
109
|
+
extends: './base.json',
|
|
110
|
+
compilerOptions: {
|
|
111
|
+
target: 'ES2020',
|
|
112
|
+
lib: ['dom', 'dom.iterable', 'esnext'],
|
|
113
|
+
jsx: 'react-jsx',
|
|
114
|
+
module: 'esnext',
|
|
115
|
+
moduleResolution: 'bundler',
|
|
116
|
+
noEmit: false,
|
|
117
|
+
outDir: './dist',
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
await fs.writeJson(
|
|
122
|
+
path.join(packageDir, 'react-library.json'),
|
|
123
|
+
reactLibConfig,
|
|
124
|
+
{ spaces: 2 }
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async function createDatabasePackage(targetDir: string): Promise<void> {
|
|
129
|
+
const packageDir = path.join(targetDir, 'packages', 'database');
|
|
130
|
+
await fs.ensureDir(path.join(packageDir, 'prisma'));
|
|
131
|
+
await fs.ensureDir(path.join(packageDir, 'src'));
|
|
132
|
+
|
|
133
|
+
// package.json
|
|
134
|
+
const packageJson = {
|
|
135
|
+
name: '@repo/database',
|
|
136
|
+
version: '1.0.0',
|
|
137
|
+
private: true,
|
|
138
|
+
main: './src/index.ts',
|
|
139
|
+
types: './src/index.ts',
|
|
140
|
+
scripts: {
|
|
141
|
+
'db:generate': 'prisma generate',
|
|
142
|
+
'db:push': 'prisma db push',
|
|
143
|
+
'db:migrate': 'prisma migrate dev',
|
|
144
|
+
'db:studio': 'prisma studio',
|
|
145
|
+
build: 'tsc',
|
|
146
|
+
clean: 'rm -rf dist',
|
|
147
|
+
'type-check': 'tsc --noEmit',
|
|
148
|
+
},
|
|
149
|
+
dependencies: {
|
|
150
|
+
'@prisma/client': '^5.7.1',
|
|
151
|
+
},
|
|
152
|
+
devDependencies: {
|
|
153
|
+
'@repo/tsconfig': 'workspace:*',
|
|
154
|
+
prisma: '^5.7.1',
|
|
155
|
+
typescript: '^5.3.3',
|
|
156
|
+
},
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
await fs.writeJson(path.join(packageDir, 'package.json'), packageJson, {
|
|
160
|
+
spaces: 2,
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// tsconfig.json
|
|
164
|
+
const tsConfig = {
|
|
165
|
+
extends: '@repo/tsconfig/base.json',
|
|
166
|
+
compilerOptions: {
|
|
167
|
+
outDir: './dist',
|
|
168
|
+
rootDir: './src',
|
|
169
|
+
},
|
|
170
|
+
include: ['src/**/*'],
|
|
171
|
+
exclude: ['node_modules', 'dist'],
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
await fs.writeJson(path.join(packageDir, 'tsconfig.json'), tsConfig, {
|
|
175
|
+
spaces: 2,
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
// prisma/schema.prisma
|
|
179
|
+
const prismaSchema = `// This is your Prisma schema file,
|
|
180
|
+
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
|
181
|
+
|
|
182
|
+
generator client {
|
|
183
|
+
provider = "prisma-client-js"
|
|
184
|
+
output = "../node_modules/.prisma/client"
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
datasource db {
|
|
188
|
+
provider = "postgresql"
|
|
189
|
+
url = env("DATABASE_URL")
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
model User {
|
|
193
|
+
id String @id @default(cuid())
|
|
194
|
+
email String @unique
|
|
195
|
+
name String?
|
|
196
|
+
createdAt DateTime @default(now())
|
|
197
|
+
updatedAt DateTime @updatedAt
|
|
198
|
+
|
|
199
|
+
@@map("users")
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
model Post {
|
|
203
|
+
id String @id @default(cuid())
|
|
204
|
+
title String
|
|
205
|
+
content String?
|
|
206
|
+
published Boolean @default(false)
|
|
207
|
+
authorId String?
|
|
208
|
+
createdAt DateTime @default(now())
|
|
209
|
+
updatedAt DateTime @updatedAt
|
|
210
|
+
|
|
211
|
+
@@map("posts")
|
|
212
|
+
}
|
|
213
|
+
`;
|
|
214
|
+
|
|
215
|
+
await fs.writeFile(
|
|
216
|
+
path.join(packageDir, 'prisma', 'schema.prisma'),
|
|
217
|
+
prismaSchema
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
// src/index.ts
|
|
221
|
+
const indexTs = `import { PrismaClient } from '@prisma/client';
|
|
222
|
+
|
|
223
|
+
declare global {
|
|
224
|
+
// eslint-disable-next-line no-var
|
|
225
|
+
var prisma: PrismaClient | undefined;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export const prisma =
|
|
229
|
+
global.prisma ||
|
|
230
|
+
new PrismaClient({
|
|
231
|
+
log: process.env.NODE_ENV === 'development' ? ['query', 'error', 'warn'] : ['error'],
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
235
|
+
global.prisma = prisma;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export * from '@prisma/client';
|
|
239
|
+
`;
|
|
240
|
+
|
|
241
|
+
await fs.writeFile(path.join(packageDir, 'src', 'index.ts'), indexTs);
|
|
242
|
+
|
|
243
|
+
// .env.example
|
|
244
|
+
const envExample = `DATABASE_URL="postgresql://user:password@localhost:5432/mydb?schema=public"
|
|
245
|
+
`;
|
|
246
|
+
|
|
247
|
+
await fs.writeFile(path.join(packageDir, '.env.example'), envExample);
|
|
248
|
+
|
|
249
|
+
// README.md
|
|
250
|
+
const readme = `# @repo/database
|
|
251
|
+
|
|
252
|
+
Shared Prisma database client and schema.
|
|
253
|
+
|
|
254
|
+
## Usage
|
|
255
|
+
|
|
256
|
+
\`\`\`typescript
|
|
257
|
+
import { prisma } from '@repo/database';
|
|
258
|
+
|
|
259
|
+
// Use prisma client
|
|
260
|
+
const users = await prisma.user.findMany();
|
|
261
|
+
\`\`\`
|
|
262
|
+
|
|
263
|
+
## Commands
|
|
264
|
+
|
|
265
|
+
\`\`\`bash
|
|
266
|
+
# Generate Prisma Client
|
|
267
|
+
pnpm db:generate
|
|
268
|
+
|
|
269
|
+
# Create and apply migrations
|
|
270
|
+
pnpm db:migrate
|
|
271
|
+
|
|
272
|
+
# Push schema changes without migration
|
|
273
|
+
pnpm db:push
|
|
274
|
+
|
|
275
|
+
# Open Prisma Studio
|
|
276
|
+
pnpm db:studio
|
|
277
|
+
\`\`\`
|
|
278
|
+
`;
|
|
279
|
+
|
|
280
|
+
await fs.writeFile(path.join(packageDir, 'README.md'), readme);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
async function createSharedBackendPackage(targetDir: string): Promise<void> {
|
|
284
|
+
const packageDir = path.join(targetDir, 'packages', 'shared-backend');
|
|
285
|
+
await fs.ensureDir(path.join(packageDir, 'src'));
|
|
286
|
+
await fs.ensureDir(path.join(packageDir, 'src', 'utils'));
|
|
287
|
+
await fs.ensureDir(path.join(packageDir, 'src', 'middleware'));
|
|
288
|
+
await fs.ensureDir(path.join(packageDir, 'src', 'types'));
|
|
289
|
+
|
|
290
|
+
// package.json
|
|
291
|
+
const packageJson = {
|
|
292
|
+
name: '@repo/shared-backend',
|
|
293
|
+
version: '1.0.0',
|
|
294
|
+
private: true,
|
|
295
|
+
main: './src/index.ts',
|
|
296
|
+
types: './src/index.ts',
|
|
297
|
+
scripts: {
|
|
298
|
+
build: 'tsc',
|
|
299
|
+
clean: 'rm -rf dist',
|
|
300
|
+
'type-check': 'tsc --noEmit',
|
|
301
|
+
},
|
|
302
|
+
dependencies: {
|
|
303
|
+
'@repo/shared-common': 'workspace:*',
|
|
304
|
+
},
|
|
305
|
+
devDependencies: {
|
|
306
|
+
'@repo/tsconfig': 'workspace:*',
|
|
307
|
+
'@types/node': '^20.10.6',
|
|
308
|
+
typescript: '^5.3.3',
|
|
309
|
+
},
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
await fs.writeJson(path.join(packageDir, 'package.json'), packageJson, {
|
|
313
|
+
spaces: 2,
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
// tsconfig.json
|
|
317
|
+
const tsConfig = {
|
|
318
|
+
extends: '@repo/tsconfig/base.json',
|
|
319
|
+
compilerOptions: {
|
|
320
|
+
outDir: './dist',
|
|
321
|
+
rootDir: './src',
|
|
322
|
+
},
|
|
323
|
+
include: ['src/**/*'],
|
|
324
|
+
exclude: ['node_modules', 'dist'],
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
await fs.writeJson(path.join(packageDir, 'tsconfig.json'), tsConfig, {
|
|
328
|
+
spaces: 2,
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
// src/index.ts
|
|
332
|
+
const indexTs = `export * from './utils';
|
|
333
|
+
export * from './types';
|
|
334
|
+
`;
|
|
335
|
+
|
|
336
|
+
await fs.writeFile(path.join(packageDir, 'src', 'index.ts'), indexTs);
|
|
337
|
+
|
|
338
|
+
// src/utils/index.ts
|
|
339
|
+
const utilsIndexTs = `export * from './logger';
|
|
340
|
+
export * from './response';
|
|
341
|
+
`;
|
|
342
|
+
|
|
343
|
+
await fs.writeFile(
|
|
344
|
+
path.join(packageDir, 'src', 'utils', 'index.ts'),
|
|
345
|
+
utilsIndexTs
|
|
346
|
+
);
|
|
347
|
+
|
|
348
|
+
// src/utils/logger.ts
|
|
349
|
+
const loggerTs = `export class Logger {
|
|
350
|
+
private context: string;
|
|
351
|
+
|
|
352
|
+
constructor(context: string) {
|
|
353
|
+
this.context = context;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
log(message: string, ...args: any[]) {
|
|
357
|
+
console.log(\`[\${this.context}] \${message}\`, ...args);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
error(message: string, ...args: any[]) {
|
|
361
|
+
console.error(\`[\${this.context}] ERROR: \${message}\`, ...args);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
warn(message: string, ...args: any[]) {
|
|
365
|
+
console.warn(\`[\${this.context}] WARN: \${message}\`, ...args);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
debug(message: string, ...args: any[]) {
|
|
369
|
+
if (process.env.NODE_ENV === 'development') {
|
|
370
|
+
console.debug(\`[\${this.context}] DEBUG: \${message}\`, ...args);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
`;
|
|
375
|
+
|
|
376
|
+
await fs.writeFile(
|
|
377
|
+
path.join(packageDir, 'src', 'utils', 'logger.ts'),
|
|
378
|
+
loggerTs
|
|
379
|
+
);
|
|
380
|
+
|
|
381
|
+
// src/utils/response.ts
|
|
382
|
+
const responseTs = `export interface ApiResponse<T = any> {
|
|
383
|
+
success: boolean;
|
|
384
|
+
data?: T;
|
|
385
|
+
message?: string;
|
|
386
|
+
error?: string;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
export function successResponse<T>(data: T, message?: string): ApiResponse<T> {
|
|
390
|
+
return {
|
|
391
|
+
success: true,
|
|
392
|
+
data,
|
|
393
|
+
message,
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
export function errorResponse(error: string, message?: string): ApiResponse {
|
|
398
|
+
return {
|
|
399
|
+
success: false,
|
|
400
|
+
error,
|
|
401
|
+
message,
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
`;
|
|
405
|
+
|
|
406
|
+
await fs.writeFile(
|
|
407
|
+
path.join(packageDir, 'src', 'utils', 'response.ts'),
|
|
408
|
+
responseTs
|
|
409
|
+
);
|
|
410
|
+
|
|
411
|
+
// src/types/index.ts
|
|
412
|
+
const typesIndexTs = `export interface PaginationParams {
|
|
413
|
+
page?: number;
|
|
414
|
+
limit?: number;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
export interface PaginatedResponse<T> {
|
|
418
|
+
data: T[];
|
|
419
|
+
total: number;
|
|
420
|
+
page: number;
|
|
421
|
+
limit: number;
|
|
422
|
+
totalPages: number;
|
|
423
|
+
}
|
|
424
|
+
`;
|
|
425
|
+
|
|
426
|
+
await fs.writeFile(
|
|
427
|
+
path.join(packageDir, 'src', 'types', 'index.ts'),
|
|
428
|
+
typesIndexTs
|
|
429
|
+
);
|
|
430
|
+
|
|
431
|
+
// README.md
|
|
432
|
+
const readme = `# @repo/shared-backend
|
|
433
|
+
|
|
434
|
+
Shared backend utilities, types, and middleware.
|
|
435
|
+
|
|
436
|
+
## Usage
|
|
437
|
+
|
|
438
|
+
\`\`\`typescript
|
|
439
|
+
import { Logger, successResponse } from '@repo/shared-backend';
|
|
440
|
+
|
|
441
|
+
const logger = new Logger('MyService');
|
|
442
|
+
logger.log('Hello from shared backend');
|
|
443
|
+
|
|
444
|
+
const response = successResponse({ id: 1 }, 'User created');
|
|
445
|
+
\`\`\`
|
|
446
|
+
`;
|
|
447
|
+
|
|
448
|
+
await fs.writeFile(path.join(packageDir, 'README.md'), readme);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
async function createSharedFrontendPackage(targetDir: string): Promise<void> {
|
|
452
|
+
const packageDir = path.join(targetDir, 'packages', 'shared-frontend');
|
|
453
|
+
await fs.ensureDir(path.join(packageDir, 'src'));
|
|
454
|
+
await fs.ensureDir(path.join(packageDir, 'src', 'components'));
|
|
455
|
+
await fs.ensureDir(path.join(packageDir, 'src', 'hooks'));
|
|
456
|
+
await fs.ensureDir(path.join(packageDir, 'src', 'utils'));
|
|
457
|
+
|
|
458
|
+
// package.json
|
|
459
|
+
const packageJson = {
|
|
460
|
+
name: '@repo/shared-frontend',
|
|
461
|
+
version: '1.0.0',
|
|
462
|
+
private: true,
|
|
463
|
+
main: './src/index.ts',
|
|
464
|
+
types: './src/index.ts',
|
|
465
|
+
scripts: {
|
|
466
|
+
build: 'tsc',
|
|
467
|
+
clean: 'rm -rf dist',
|
|
468
|
+
'type-check': 'tsc --noEmit',
|
|
469
|
+
lint: 'eslint "src/**/*.{ts,tsx}"',
|
|
470
|
+
},
|
|
471
|
+
dependencies: {
|
|
472
|
+
'@repo/shared-common': 'workspace:*',
|
|
473
|
+
react: '^18.2.0',
|
|
474
|
+
'react-dom': '^18.2.0',
|
|
475
|
+
},
|
|
476
|
+
devDependencies: {
|
|
477
|
+
'@repo/tsconfig': 'workspace:*',
|
|
478
|
+
'@types/react': '^18.2.46',
|
|
479
|
+
'@types/react-dom': '^18.2.18',
|
|
480
|
+
typescript: '^5.3.3',
|
|
481
|
+
},
|
|
482
|
+
peerDependencies: {
|
|
483
|
+
react: '^18.2.0',
|
|
484
|
+
'react-dom': '^18.2.0',
|
|
485
|
+
},
|
|
486
|
+
};
|
|
487
|
+
|
|
488
|
+
await fs.writeJson(path.join(packageDir, 'package.json'), packageJson, {
|
|
489
|
+
spaces: 2,
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
// tsconfig.json
|
|
493
|
+
const tsConfig = {
|
|
494
|
+
extends: '@repo/tsconfig/react-library.json',
|
|
495
|
+
compilerOptions: {
|
|
496
|
+
outDir: './dist',
|
|
497
|
+
rootDir: './src',
|
|
498
|
+
},
|
|
499
|
+
include: ['src/**/*'],
|
|
500
|
+
exclude: ['node_modules', 'dist'],
|
|
501
|
+
};
|
|
502
|
+
|
|
503
|
+
await fs.writeJson(path.join(packageDir, 'tsconfig.json'), tsConfig, {
|
|
504
|
+
spaces: 2,
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
// src/index.ts
|
|
508
|
+
const indexTs = `export * from './components';
|
|
509
|
+
export * from './hooks';
|
|
510
|
+
export * from './utils';
|
|
511
|
+
`;
|
|
512
|
+
|
|
513
|
+
await fs.writeFile(path.join(packageDir, 'src', 'index.ts'), indexTs);
|
|
514
|
+
|
|
515
|
+
// src/components/index.ts
|
|
516
|
+
const componentsIndexTs = `export * from './Button';
|
|
517
|
+
`;
|
|
518
|
+
|
|
519
|
+
await fs.writeFile(
|
|
520
|
+
path.join(packageDir, 'src', 'components', 'index.ts'),
|
|
521
|
+
componentsIndexTs
|
|
522
|
+
);
|
|
523
|
+
|
|
524
|
+
// src/components/Button.tsx
|
|
525
|
+
const buttonTsx = `import React from 'react';
|
|
526
|
+
|
|
527
|
+
export interface ButtonProps
|
|
528
|
+
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
|
529
|
+
variant?: 'primary' | 'secondary' | 'danger';
|
|
530
|
+
size?: 'sm' | 'md' | 'lg';
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
export function Button({
|
|
534
|
+
children,
|
|
535
|
+
variant = 'primary',
|
|
536
|
+
size = 'md',
|
|
537
|
+
className = '',
|
|
538
|
+
...props
|
|
539
|
+
}: ButtonProps) {
|
|
540
|
+
const baseStyles = 'font-semibold rounded focus:outline-none focus:ring-2';
|
|
541
|
+
|
|
542
|
+
const variantStyles = {
|
|
543
|
+
primary: 'bg-blue-600 text-white hover:bg-blue-700 focus:ring-blue-500',
|
|
544
|
+
secondary: 'bg-gray-600 text-white hover:bg-gray-700 focus:ring-gray-500',
|
|
545
|
+
danger: 'bg-red-600 text-white hover:bg-red-700 focus:ring-red-500',
|
|
546
|
+
};
|
|
547
|
+
|
|
548
|
+
const sizeStyles = {
|
|
549
|
+
sm: 'px-3 py-1.5 text-sm',
|
|
550
|
+
md: 'px-4 py-2 text-base',
|
|
551
|
+
lg: 'px-6 py-3 text-lg',
|
|
552
|
+
};
|
|
553
|
+
|
|
554
|
+
const combinedClassName = \`\${baseStyles} \${variantStyles[variant]} \${sizeStyles[size]} \${className}\`;
|
|
555
|
+
|
|
556
|
+
return (
|
|
557
|
+
<button className={combinedClassName} {...props}>
|
|
558
|
+
{children}
|
|
559
|
+
</button>
|
|
560
|
+
);
|
|
561
|
+
}
|
|
562
|
+
`;
|
|
563
|
+
|
|
564
|
+
await fs.writeFile(
|
|
565
|
+
path.join(packageDir, 'src', 'components', 'Button.tsx'),
|
|
566
|
+
buttonTsx
|
|
567
|
+
);
|
|
568
|
+
|
|
569
|
+
// src/hooks/index.ts
|
|
570
|
+
const hooksIndexTs = `export * from './useLocalStorage';
|
|
571
|
+
`;
|
|
572
|
+
|
|
573
|
+
await fs.writeFile(
|
|
574
|
+
path.join(packageDir, 'src', 'hooks', 'index.ts'),
|
|
575
|
+
hooksIndexTs
|
|
576
|
+
);
|
|
577
|
+
|
|
578
|
+
// src/hooks/useLocalStorage.ts
|
|
579
|
+
const useLocalStorageTs = `import { useState, useEffect } from 'react';
|
|
580
|
+
|
|
581
|
+
export function useLocalStorage<T>(
|
|
582
|
+
key: string,
|
|
583
|
+
initialValue: T
|
|
584
|
+
): [T, (value: T) => void] {
|
|
585
|
+
const [storedValue, setStoredValue] = useState<T>(() => {
|
|
586
|
+
if (typeof window === 'undefined') {
|
|
587
|
+
return initialValue;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
try {
|
|
591
|
+
const item = window.localStorage.getItem(key);
|
|
592
|
+
return item ? JSON.parse(item) : initialValue;
|
|
593
|
+
} catch (error) {
|
|
594
|
+
console.error(error);
|
|
595
|
+
return initialValue;
|
|
596
|
+
}
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
const setValue = (value: T) => {
|
|
600
|
+
try {
|
|
601
|
+
setStoredValue(value);
|
|
602
|
+
if (typeof window !== 'undefined') {
|
|
603
|
+
window.localStorage.setItem(key, JSON.stringify(value));
|
|
604
|
+
}
|
|
605
|
+
} catch (error) {
|
|
606
|
+
console.error(error);
|
|
607
|
+
}
|
|
608
|
+
};
|
|
609
|
+
|
|
610
|
+
return [storedValue, setValue];
|
|
611
|
+
}
|
|
612
|
+
`;
|
|
613
|
+
|
|
614
|
+
await fs.writeFile(
|
|
615
|
+
path.join(packageDir, 'src', 'hooks', 'useLocalStorage.ts'),
|
|
616
|
+
useLocalStorageTs
|
|
617
|
+
);
|
|
618
|
+
|
|
619
|
+
// src/utils/index.ts
|
|
620
|
+
const utilsIndexTs = `export * from './cn';
|
|
621
|
+
`;
|
|
622
|
+
|
|
623
|
+
await fs.writeFile(
|
|
624
|
+
path.join(packageDir, 'src', 'utils', 'index.ts'),
|
|
625
|
+
utilsIndexTs
|
|
626
|
+
);
|
|
627
|
+
|
|
628
|
+
// src/utils/cn.ts
|
|
629
|
+
const cnTs = `export function cn(...classes: (string | undefined | null | false)[]): string {
|
|
630
|
+
return classes.filter(Boolean).join(' ');
|
|
631
|
+
}
|
|
632
|
+
`;
|
|
633
|
+
|
|
634
|
+
await fs.writeFile(path.join(packageDir, 'src', 'utils', 'cn.ts'), cnTs);
|
|
635
|
+
|
|
636
|
+
// README.md
|
|
637
|
+
const readme = `# @repo/shared-frontend
|
|
638
|
+
|
|
639
|
+
Shared frontend components, hooks, and utilities.
|
|
640
|
+
|
|
641
|
+
## Usage
|
|
642
|
+
|
|
643
|
+
\`\`\`typescript
|
|
644
|
+
import { Button, useLocalStorage } from '@repo/shared-frontend';
|
|
645
|
+
|
|
646
|
+
function MyComponent() {
|
|
647
|
+
const [value, setValue] = useLocalStorage('myKey', 'default');
|
|
648
|
+
|
|
649
|
+
return <Button onClick={() => setValue('new value')}>Click me</Button>;
|
|
650
|
+
}
|
|
651
|
+
\`\`\`
|
|
652
|
+
`;
|
|
653
|
+
|
|
654
|
+
await fs.writeFile(path.join(packageDir, 'README.md'), readme);
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
async function createSharedCommonPackage(targetDir: string): Promise<void> {
|
|
658
|
+
const packageDir = path.join(targetDir, 'packages', 'shared-common');
|
|
659
|
+
await fs.ensureDir(path.join(packageDir, 'src'));
|
|
660
|
+
await fs.ensureDir(path.join(packageDir, 'src', 'utils'));
|
|
661
|
+
await fs.ensureDir(path.join(packageDir, 'src', 'constants'));
|
|
662
|
+
await fs.ensureDir(path.join(packageDir, 'src', 'types'));
|
|
663
|
+
|
|
664
|
+
// package.json
|
|
665
|
+
const packageJson = {
|
|
666
|
+
name: '@repo/shared-common',
|
|
667
|
+
version: '1.0.0',
|
|
668
|
+
private: true,
|
|
669
|
+
main: './src/index.ts',
|
|
670
|
+
types: './src/index.ts',
|
|
671
|
+
scripts: {
|
|
672
|
+
build: 'tsc',
|
|
673
|
+
clean: 'rm -rf dist',
|
|
674
|
+
'type-check': 'tsc --noEmit',
|
|
675
|
+
},
|
|
676
|
+
devDependencies: {
|
|
677
|
+
'@repo/tsconfig': 'workspace:*',
|
|
678
|
+
typescript: '^5.3.3',
|
|
679
|
+
},
|
|
680
|
+
};
|
|
681
|
+
|
|
682
|
+
await fs.writeJson(path.join(packageDir, 'package.json'), packageJson, {
|
|
683
|
+
spaces: 2,
|
|
684
|
+
});
|
|
685
|
+
|
|
686
|
+
// tsconfig.json
|
|
687
|
+
const tsConfig = {
|
|
688
|
+
extends: '@repo/tsconfig/base.json',
|
|
689
|
+
compilerOptions: {
|
|
690
|
+
outDir: './dist',
|
|
691
|
+
rootDir: './src',
|
|
692
|
+
},
|
|
693
|
+
include: ['src/**/*'],
|
|
694
|
+
exclude: ['node_modules', 'dist'],
|
|
695
|
+
};
|
|
696
|
+
|
|
697
|
+
await fs.writeJson(path.join(packageDir, 'tsconfig.json'), tsConfig, {
|
|
698
|
+
spaces: 2,
|
|
699
|
+
});
|
|
700
|
+
|
|
701
|
+
// src/index.ts
|
|
702
|
+
const indexTs = `export * from './utils';
|
|
703
|
+
export * from './constants';
|
|
704
|
+
export * from './types';
|
|
705
|
+
`;
|
|
706
|
+
|
|
707
|
+
await fs.writeFile(path.join(packageDir, 'src', 'index.ts'), indexTs);
|
|
708
|
+
|
|
709
|
+
// src/utils/index.ts
|
|
710
|
+
const utilsIndexTs = `export * from './validation';
|
|
711
|
+
export * from './formatting';
|
|
712
|
+
`;
|
|
713
|
+
|
|
714
|
+
await fs.writeFile(
|
|
715
|
+
path.join(packageDir, 'src', 'utils', 'index.ts'),
|
|
716
|
+
utilsIndexTs
|
|
717
|
+
);
|
|
718
|
+
|
|
719
|
+
// src/utils/validation.ts
|
|
720
|
+
const validationTs = `export function isEmail(email: string): boolean {
|
|
721
|
+
const emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;
|
|
722
|
+
return emailRegex.test(email);
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
export function isValidUrl(url: string): boolean {
|
|
726
|
+
try {
|
|
727
|
+
new URL(url);
|
|
728
|
+
return true;
|
|
729
|
+
} catch {
|
|
730
|
+
return false;
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
export function isEmpty(value: any): boolean {
|
|
735
|
+
if (value == null) return true;
|
|
736
|
+
if (typeof value === 'string') return value.trim().length === 0;
|
|
737
|
+
if (Array.isArray(value)) return value.length === 0;
|
|
738
|
+
if (typeof value === 'object') return Object.keys(value).length === 0;
|
|
739
|
+
return false;
|
|
740
|
+
}
|
|
741
|
+
`;
|
|
742
|
+
|
|
743
|
+
await fs.writeFile(
|
|
744
|
+
path.join(packageDir, 'src', 'utils', 'validation.ts'),
|
|
745
|
+
validationTs
|
|
746
|
+
);
|
|
747
|
+
|
|
748
|
+
// src/utils/formatting.ts
|
|
749
|
+
const formattingTs = `export function formatDate(date: Date | string): string {
|
|
750
|
+
const d = typeof date === 'string' ? new Date(date) : date;
|
|
751
|
+
return d.toLocaleDateString('en-US', {
|
|
752
|
+
year: 'numeric',
|
|
753
|
+
month: 'long',
|
|
754
|
+
day: 'numeric',
|
|
755
|
+
});
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
export function capitalize(str: string): string {
|
|
759
|
+
if (!str) return '';
|
|
760
|
+
return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
export function truncate(str: string, maxLength: number): string {
|
|
764
|
+
if (str.length <= maxLength) return str;
|
|
765
|
+
return str.slice(0, maxLength - 3) + '...';
|
|
766
|
+
}
|
|
767
|
+
`;
|
|
768
|
+
|
|
769
|
+
await fs.writeFile(
|
|
770
|
+
path.join(packageDir, 'src', 'utils', 'formatting.ts'),
|
|
771
|
+
formattingTs
|
|
772
|
+
);
|
|
773
|
+
|
|
774
|
+
// src/constants/index.ts
|
|
775
|
+
const constantsIndexTs = `export const APP_NAME = 'My App';
|
|
776
|
+
export const API_VERSION = 'v1';
|
|
777
|
+
export const DEFAULT_PAGE_SIZE = 20;
|
|
778
|
+
export const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB
|
|
779
|
+
`;
|
|
780
|
+
|
|
781
|
+
await fs.writeFile(
|
|
782
|
+
path.join(packageDir, 'src', 'constants', 'index.ts'),
|
|
783
|
+
constantsIndexTs
|
|
784
|
+
);
|
|
785
|
+
|
|
786
|
+
// src/types/index.ts
|
|
787
|
+
const typesIndexTs = `export interface BaseEntity {
|
|
788
|
+
id: string;
|
|
789
|
+
createdAt: Date;
|
|
790
|
+
updatedAt: Date;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
export type Nullable<T> = T | null;
|
|
794
|
+
|
|
795
|
+
export type Optional<T> = T | undefined;
|
|
796
|
+
`;
|
|
797
|
+
|
|
798
|
+
await fs.writeFile(
|
|
799
|
+
path.join(packageDir, 'src', 'types', 'index.ts'),
|
|
800
|
+
typesIndexTs
|
|
801
|
+
);
|
|
802
|
+
|
|
803
|
+
// README.md
|
|
804
|
+
const readme = `# @repo/shared-common
|
|
805
|
+
|
|
806
|
+
Universal utilities and types shared across all packages.
|
|
807
|
+
|
|
808
|
+
## Usage
|
|
809
|
+
|
|
810
|
+
\`\`\`typescript
|
|
811
|
+
import { isEmail, formatDate, APP_NAME } from '@repo/shared-common';
|
|
812
|
+
|
|
813
|
+
console.log(isEmail('test@example.com')); // true
|
|
814
|
+
console.log(formatDate(new Date())); // "January 1, 2024"
|
|
815
|
+
console.log(APP_NAME); // "My App"
|
|
816
|
+
\`\`\`
|
|
817
|
+
`;
|
|
818
|
+
|
|
819
|
+
await fs.writeFile(path.join(packageDir, 'README.md'), readme);
|
|
820
|
+
}
|