@idevconn/create-icore 0.9.2 → 0.10.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/dist/cli.js +832 -130
- package/dist/index.cjs +848 -149
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +841 -142
- package/package.json +4 -4
- package/templates/.yarn/releases/{yarn-4.16.0.cjs → yarn-4.17.0.cjs} +326 -326
- package/templates/.yarnrc.yml +1 -1
- package/templates/apps/api/package.json +6 -6
- package/templates/apps/microservices/auth/package.json +4 -4
- package/templates/apps/microservices/jobs/package.json +5 -5
- package/templates/apps/microservices/notes/package.json +5 -5
- package/templates/apps/microservices/payment/package.json +4 -4
- package/templates/apps/microservices/upload/package.json +6 -6
- package/templates/apps/templates/client-antd/package.json +2 -2
- package/templates/apps/templates/client-mui/package.json +4 -4
- package/templates/apps/templates/client-shadcn/package.json +7 -7
- package/templates/apps/templates/client-shadcn/src/components/ui/button.tsx +1 -2
- package/templates/libs/auth-client/package.json +3 -3
- package/templates/libs/auth-strategies/firebase/package.json +4 -4
- package/templates/libs/auth-strategies/mongodb/package.json +4 -4
- package/templates/libs/auth-strategies/supabase/package.json +5 -5
- package/templates/libs/db-strategies/firestore/package.json +4 -4
- package/templates/libs/db-strategies/mongodb/package.json +3 -3
- package/templates/libs/db-strategies/supabase/package.json +5 -5
- package/templates/libs/firebase-admin/package.json +1 -1
- package/templates/libs/jobs-client/package.json +4 -4
- package/templates/libs/notes-client/package.json +3 -3
- package/templates/libs/payment-client/package.json +3 -3
- package/templates/libs/shared/package.json +2 -2
- package/templates/libs/storage-strategies/cloudinary/package.json +4 -4
- package/templates/libs/storage-strategies/firebase/package.json +4 -4
- package/templates/libs/storage-strategies/mongodb/package.json +3 -3
- package/templates/libs/storage-strategies/supabase/package.json +5 -5
- package/templates/libs/template-shared/package.json +8 -8
- package/templates/libs/upload-client/package.json +3 -3
- package/templates/libs/vite-plugins/package.json +3 -3
- package/templates/package.json +32 -32
- package/templates/tools/create-icore/_template-shell/package.json +32 -32
package/dist/index.js
CHANGED
|
@@ -4,9 +4,9 @@ function pmRun(pm, script) {
|
|
|
4
4
|
}
|
|
5
5
|
|
|
6
6
|
// src/lib/scaffold.ts
|
|
7
|
-
import { copyFile, mkdir as
|
|
7
|
+
import { copyFile, mkdir as mkdir3, readdir as readdir2, readFile as readFile7, rmdir as rmdir2, stat, writeFile as writeFile9, rm as rm5 } from "fs/promises";
|
|
8
8
|
import { readFileSync } from "fs";
|
|
9
|
-
import { join as
|
|
9
|
+
import { join as join9 } from "path";
|
|
10
10
|
import { spawnSync } from "child_process";
|
|
11
11
|
|
|
12
12
|
// src/lib/scaffold-env.ts
|
|
@@ -107,6 +107,30 @@ async function rewriteRootPackageJson(targetDir, opts) {
|
|
|
107
107
|
}
|
|
108
108
|
}
|
|
109
109
|
delete pkg.pnpm;
|
|
110
|
+
const noMs = opts.authProvider === "none" && opts.upload === "none" && opts.payment === "none" && opts.jobs === "none" && opts.example === "none";
|
|
111
|
+
const ws = pkg.workspaces;
|
|
112
|
+
if (ws) {
|
|
113
|
+
pkg.workspaces = ws.filter((entry) => {
|
|
114
|
+
if (entry === "apps/templates/*") return false;
|
|
115
|
+
if (entry === "apps/microservices/*" && noMs) return false;
|
|
116
|
+
if (entry === "libs/auth-strategies/*" && opts.authProvider === "none") return false;
|
|
117
|
+
if (entry === "libs/storage-strategies/*" && opts.upload === "none") return false;
|
|
118
|
+
if (entry === "libs/db-strategies/*" && opts.dbProvider === "none") return false;
|
|
119
|
+
return true;
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
const rootDeps = pkg.dependencies ?? {};
|
|
123
|
+
const rootDevDeps = pkg.devDependencies ?? {};
|
|
124
|
+
if (opts.authProvider === "none") {
|
|
125
|
+
delete rootDeps["cookie-parser"];
|
|
126
|
+
delete rootDevDeps["@types/cookie-parser"];
|
|
127
|
+
}
|
|
128
|
+
if (opts.upload === "none") {
|
|
129
|
+
delete rootDevDeps["@types/multer"];
|
|
130
|
+
}
|
|
131
|
+
if (noMs) {
|
|
132
|
+
delete rootDeps["@nestjs/microservices"];
|
|
133
|
+
}
|
|
110
134
|
await writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
111
135
|
}
|
|
112
136
|
async function writeAuthEnv(targetDir, opts) {
|
|
@@ -148,6 +172,9 @@ async function writeGatewayEnv(targetDir, opts) {
|
|
|
148
172
|
for (const prefix of ["AUTH", "UPLOAD", "NOTES", "PAYMENT"]) {
|
|
149
173
|
next = uncommentTransportEnv(next, prefix, opts.transport);
|
|
150
174
|
}
|
|
175
|
+
if (opts.authProvider === "none") {
|
|
176
|
+
next = next.split("\n").filter((line) => !line.startsWith("AUTH_") && !line.startsWith("# AUTH_")).join("\n");
|
|
177
|
+
}
|
|
151
178
|
await writeFile(join(targetDir, "apps/api/.env"), next);
|
|
152
179
|
}
|
|
153
180
|
async function writeRootEnv(targetDir, opts) {
|
|
@@ -229,59 +256,26 @@ async function removeFirebaseAdminLib(targetDir) {
|
|
|
229
256
|
"@icore/firebase-admin"
|
|
230
257
|
]);
|
|
231
258
|
}
|
|
232
|
-
async function
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
"apps/api/src/app/auth",
|
|
239
|
-
"apps/api/src/app/profile",
|
|
240
|
-
"apps/api/src/app/abilities",
|
|
241
|
-
"apps/client/src/components/auth",
|
|
242
|
-
"apps/client/src/routes/login.tsx",
|
|
243
|
-
"apps/client/src/routes/auth.callback.tsx",
|
|
244
|
-
"apps/client/src/routes/auth.oauth.callback.tsx",
|
|
245
|
-
"apps/client/src/routes/_dashboard/profile.tsx"
|
|
246
|
-
];
|
|
247
|
-
for (const p2 of rmPaths) {
|
|
248
|
-
await rm(join2(targetDir, p2), { recursive: true, force: true });
|
|
249
|
-
}
|
|
250
|
-
const appModulePath = join2(targetDir, "apps/api/src/app/app.module.ts");
|
|
259
|
+
async function removeStrategiesLib(targetDir) {
|
|
260
|
+
await rm(join2(targetDir, "libs/shared/src/strategies"), { recursive: true, force: true });
|
|
261
|
+
await rm(join2(targetDir, "libs/shared/src/testing.ts"), { force: true });
|
|
262
|
+
await rm(join2(targetDir, "libs/shared/src/transport.ts"), { force: true });
|
|
263
|
+
await rm(join2(targetDir, "libs/shared/src/__tests__/transport.unit.test.ts"), { force: true });
|
|
264
|
+
const indexPath = join2(targetDir, "libs/shared/src/index.ts");
|
|
251
265
|
try {
|
|
252
|
-
const src = await readFile2(
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
const dashboardPath = join2(targetDir, "apps/client/src/routes/_dashboard.tsx");
|
|
258
|
-
try {
|
|
259
|
-
const src = await readFile2(dashboardPath, "utf8");
|
|
260
|
-
const next = src.replace(/^import \{ useAuthStore \} from '@icore\/template-shared';\n/m, "").replace(/, redirect/g, "").replace(/\n {2}beforeLoad: \(\) => \{[\s\S]*?\n {2}\},/m, "");
|
|
261
|
-
await writeFile2(dashboardPath, next);
|
|
262
|
-
} catch {
|
|
263
|
-
}
|
|
264
|
-
for (const alias of [
|
|
265
|
-
"@icore/auth-client",
|
|
266
|
-
"@icore/auth-supabase",
|
|
267
|
-
"@icore/auth-firebase",
|
|
268
|
-
"@icore/auth-mongodb"
|
|
269
|
-
]) {
|
|
270
|
-
await stripTsconfigPath(targetDir, alias);
|
|
271
|
-
}
|
|
272
|
-
await stripDeps(join2(targetDir, "apps/api/package.json"), ["@icore/auth-client"]);
|
|
273
|
-
const gatewayEnv = join2(targetDir, "apps/api/.env");
|
|
274
|
-
try {
|
|
275
|
-
const env = await readFile2(gatewayEnv, "utf8");
|
|
276
|
-
const next = env.split("\n").filter((line) => !line.startsWith("AUTH_") && !line.startsWith("# AUTH_")).join("\n");
|
|
277
|
-
await writeFile2(gatewayEnv, next);
|
|
266
|
+
const src = await readFile2(indexPath, "utf8");
|
|
267
|
+
await writeFile2(
|
|
268
|
+
indexPath,
|
|
269
|
+
src.replace(/^export \* from '\.\/strategies';\n/m, "").replace(/^export \* from '\.\/transport';\n?/m, "")
|
|
270
|
+
);
|
|
278
271
|
} catch {
|
|
279
272
|
}
|
|
280
|
-
const
|
|
273
|
+
const pkgPath = join2(targetDir, "libs/shared/package.json");
|
|
281
274
|
try {
|
|
282
|
-
const
|
|
283
|
-
|
|
284
|
-
|
|
275
|
+
const pkg = JSON.parse(await readFile2(pkgPath, "utf8"));
|
|
276
|
+
if (pkg.exports) delete pkg.exports["./testing"];
|
|
277
|
+
if (pkg.dependencies) delete pkg.dependencies["@nestjs/microservices"];
|
|
278
|
+
await writeFile2(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
285
279
|
} catch {
|
|
286
280
|
}
|
|
287
281
|
}
|
|
@@ -291,7 +285,8 @@ async function removeUploadStack(targetDir) {
|
|
|
291
285
|
"apps/microservices/upload-e2e",
|
|
292
286
|
"libs/storage-strategies",
|
|
293
287
|
"libs/upload-client",
|
|
294
|
-
"apps/api/src/app/storage"
|
|
288
|
+
"apps/api/src/app/storage",
|
|
289
|
+
"Dockerfile.ms-upload"
|
|
295
290
|
];
|
|
296
291
|
for (const p2 of paths) {
|
|
297
292
|
await rm(join2(targetDir, p2), { recursive: true, force: true });
|
|
@@ -316,11 +311,683 @@ async function removeUploadStack(targetDir) {
|
|
|
316
311
|
"@icore/upload-client",
|
|
317
312
|
"@types/multer"
|
|
318
313
|
]);
|
|
314
|
+
const uploadComposePath = join2(targetDir, "docker-compose.yml");
|
|
315
|
+
try {
|
|
316
|
+
const compose = await readFile2(uploadComposePath, "utf8");
|
|
317
|
+
const next = compose.replace(/\n {2}upload:[\s\S]+?(?=\n {2}\w|\nnetworks:)/m, "\n").replace(/\n {6}upload:\n {8}condition: service_started/g, "").replace(/\n {6}UPLOAD_TRANSPORT:[^\n]*/g, "").replace(/\n {6}UPLOAD_REDIS_URL:[^\n]*/g, "");
|
|
318
|
+
await writeFile2(uploadComposePath, next);
|
|
319
|
+
} catch {
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// src/lib/scaffold-auth-none.ts
|
|
324
|
+
import { mkdir, rm as rm2, readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
|
|
325
|
+
import { dirname, join as join3 } from "path";
|
|
326
|
+
async function stripDeps2(pkgPath, names) {
|
|
327
|
+
try {
|
|
328
|
+
const raw = await readFile3(pkgPath, "utf8");
|
|
329
|
+
const pkg = JSON.parse(raw);
|
|
330
|
+
for (const n of names) {
|
|
331
|
+
if (pkg.dependencies) delete pkg.dependencies[n];
|
|
332
|
+
if (pkg.devDependencies) delete pkg.devDependencies[n];
|
|
333
|
+
}
|
|
334
|
+
await writeFile3(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
335
|
+
} catch {
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
var AUTH_ONLY_PATHS = [
|
|
339
|
+
"apps/microservices/auth",
|
|
340
|
+
"libs/auth-strategies",
|
|
341
|
+
"libs/auth-client",
|
|
342
|
+
"Dockerfile.ms-auth",
|
|
343
|
+
"apps/api/src/app/auth",
|
|
344
|
+
"apps/api/src/app/profile",
|
|
345
|
+
"apps/api/src/app/abilities",
|
|
346
|
+
"libs/shared/src/abilities",
|
|
347
|
+
"apps/client/src/components/auth",
|
|
348
|
+
"apps/client/src/routes/login.tsx",
|
|
349
|
+
"apps/client/src/routes/auth.callback.tsx",
|
|
350
|
+
"apps/client/src/routes/auth.oauth.callback.tsx",
|
|
351
|
+
"apps/client/src/routes/_dashboard/profile.tsx",
|
|
352
|
+
"libs/template-shared/src/lib/abilities"
|
|
353
|
+
];
|
|
354
|
+
async function removeAuthOnlyPaths(targetDir) {
|
|
355
|
+
for (const p2 of AUTH_ONLY_PATHS) {
|
|
356
|
+
await rm2(join3(targetDir, p2), { recursive: true, force: true });
|
|
357
|
+
}
|
|
358
|
+
await stripDeps2(join3(targetDir, "apps/api/package.json"), [
|
|
359
|
+
"@icore/auth-client",
|
|
360
|
+
"cookie-parser"
|
|
361
|
+
]);
|
|
362
|
+
}
|
|
363
|
+
async function stripTsconfigPath2(targetDir, alias) {
|
|
364
|
+
const tsconfigPath = join3(targetDir, "tsconfig.base.json");
|
|
365
|
+
try {
|
|
366
|
+
const src = await readFile3(tsconfigPath, "utf8");
|
|
367
|
+
const escaped = alias.replace(/[@/]/g, (c) => c === "@" ? "@" : "\\/");
|
|
368
|
+
const pretty = src.replace(new RegExp(`^\\s*"${escaped}": \\[[^\\]]*\\],?\\n`, "m"), "");
|
|
369
|
+
if (pretty !== src) {
|
|
370
|
+
await writeFile3(tsconfigPath, pretty);
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
const parsed = JSON.parse(src);
|
|
374
|
+
if (parsed.compilerOptions?.paths) delete parsed.compilerOptions.paths[alias];
|
|
375
|
+
await writeFile3(tsconfigPath, JSON.stringify(parsed));
|
|
376
|
+
} catch {
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
async function removeAuthTsconfigPaths(targetDir) {
|
|
380
|
+
for (const alias of [
|
|
381
|
+
"@icore/auth-client",
|
|
382
|
+
"@icore/auth-supabase",
|
|
383
|
+
"@icore/auth-firebase",
|
|
384
|
+
"@icore/auth-mongodb"
|
|
385
|
+
]) {
|
|
386
|
+
await stripTsconfigPath2(targetDir, alias);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
async function removeDockerComposeAuthService(targetDir) {
|
|
390
|
+
const composePath = join3(targetDir, "docker-compose.yml");
|
|
391
|
+
try {
|
|
392
|
+
const compose = await readFile3(composePath, "utf8");
|
|
393
|
+
const next = compose.replace(/\n {2}auth:[\s\S]+?(?=\n {2}\w|\nnetworks:)/m, "\n").replace(/\n {6}auth:\n {8}condition: service_started/g, "").replace(/\n {6}AUTH_TRANSPORT:[^\n]*/g, "").replace(/\n {6}AUTH_REDIS_URL:[^\n]*/g, "");
|
|
394
|
+
await writeFile3(composePath, next);
|
|
395
|
+
} catch {
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
var GATEWAY_MAIN_TS = `import { Logger } from '@nestjs/common';
|
|
399
|
+
import { NestFactory } from '@nestjs/core';
|
|
400
|
+
import { NestExpressApplication } from '@nestjs/platform-express';
|
|
401
|
+
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
|
|
402
|
+
import { formatGatewayBanner } from '@icore/shared';
|
|
403
|
+
import { AppModule } from './app/app.module';
|
|
404
|
+
import { GATEWAY_SERVICES } from './app/gateway-services';
|
|
405
|
+
import pkg from '@icore/package.json';
|
|
406
|
+
|
|
407
|
+
const DEFAULT_PORT = 3001;
|
|
408
|
+
|
|
409
|
+
async function bootstrap() {
|
|
410
|
+
const app = await NestFactory.create<NestExpressApplication>(AppModule);
|
|
411
|
+
app.setGlobalPrefix('api');
|
|
412
|
+
|
|
413
|
+
const swaggerConfig = new DocumentBuilder()
|
|
414
|
+
.setTitle('iCore API')
|
|
415
|
+
.setDescription('iCore Gateway HTTP surface')
|
|
416
|
+
.setVersion(pkg.version)
|
|
417
|
+
.addBearerAuth()
|
|
418
|
+
.build();
|
|
419
|
+
const document = SwaggerModule.createDocument(app, swaggerConfig);
|
|
420
|
+
SwaggerModule.setup('api/docs', app, document);
|
|
421
|
+
|
|
422
|
+
const port = Number(process.env.API_PORT ?? DEFAULT_PORT);
|
|
423
|
+
await app.listen(port);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
bootstrap()
|
|
427
|
+
.then(() => {
|
|
428
|
+
const origin = process.env.API_ORIGIN ?? 'http://localhost';
|
|
429
|
+
const port = Number(process.env.API_PORT ?? DEFAULT_PORT);
|
|
430
|
+
new Logger('API-Bootstrap').log(
|
|
431
|
+
formatGatewayBanner({ port, origin, services: GATEWAY_SERVICES }),
|
|
432
|
+
);
|
|
433
|
+
})
|
|
434
|
+
.catch((err) => {
|
|
435
|
+
new Logger('API-Bootstrap').error(
|
|
436
|
+
'Gateway bootstrap failed',
|
|
437
|
+
err instanceof Error ? err.stack : err,
|
|
438
|
+
);
|
|
439
|
+
process.exit(1);
|
|
440
|
+
});
|
|
441
|
+
`;
|
|
442
|
+
var GATEWAY_APP_MODULE_TS = `import { join } from 'node:path';
|
|
443
|
+
import { Module } from '@nestjs/common';
|
|
444
|
+
import { ConfigModule } from '@nestjs/config';
|
|
445
|
+
import { ThrottlerModule, seconds } from '@nestjs/throttler';
|
|
446
|
+
import { StorageModule } from './storage/storage.module';
|
|
447
|
+
import { FeaturesModule } from './features.module';
|
|
448
|
+
|
|
449
|
+
@Module({
|
|
450
|
+
imports: [
|
|
451
|
+
ConfigModule.forRoot({
|
|
452
|
+
isGlobal: true,
|
|
453
|
+
envFilePath: [join(process.cwd(), 'apps/api/.env'), join(process.cwd(), '.env')],
|
|
454
|
+
}),
|
|
455
|
+
ThrottlerModule.forRoot([{ name: 'auth-burst', ttl: seconds(60), limit: 10 }]),
|
|
456
|
+
StorageModule,
|
|
457
|
+
FeaturesModule,
|
|
458
|
+
],
|
|
459
|
+
})
|
|
460
|
+
export class AppModule {}
|
|
461
|
+
`;
|
|
462
|
+
var SHARED_CLIENT_TS = `// Browser-safe subset of @icore/shared.
|
|
463
|
+
// Import from '@icore/shared/client' in client-side code to avoid pulling
|
|
464
|
+
// in NestJS / Node.js-only modules (transport, strategies, contracts).
|
|
465
|
+
export * from './types';
|
|
466
|
+
`;
|
|
467
|
+
var SHARED_INDEX_TS = `export * from './env';
|
|
468
|
+
export * from './bootstrap';
|
|
469
|
+
export * from './jobs';
|
|
470
|
+
export * from './strategies';
|
|
471
|
+
export * from './transport';
|
|
472
|
+
export * from './types';
|
|
473
|
+
`;
|
|
474
|
+
var TEMPLATE_SHARED_INDEX_TS = `export * from './lib/api/create-api.js';
|
|
475
|
+
export * from './lib/stores/auth.store.js';
|
|
476
|
+
export * from './lib/stores/loading.store.js';
|
|
477
|
+
export * from './lib/i18n/create-i18n.js';
|
|
478
|
+
export * from './lib/i18n/keys.js';
|
|
479
|
+
export * from './lib/notify/use-notify.js';
|
|
480
|
+
export * from './lib/draft/index.js';
|
|
481
|
+
export * from './lib/landing/LandingPage.js';
|
|
482
|
+
export * from './lib/stores/theme.store.js';
|
|
483
|
+
`;
|
|
484
|
+
var SHADCN_MAIN_TSX = `import './globals.css';
|
|
485
|
+
import { StrictMode } from 'react';
|
|
486
|
+
import { createRoot } from 'react-dom/client';
|
|
487
|
+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
488
|
+
import { RouterProvider, createRouter } from '@tanstack/react-router';
|
|
489
|
+
import {
|
|
490
|
+
createIcoreApi,
|
|
491
|
+
createIcoreI18n,
|
|
492
|
+
ICORE_LOCALES,
|
|
493
|
+
useThemeStore,
|
|
494
|
+
} from '@icore/template-shared';
|
|
495
|
+
import { I18nextProvider } from 'react-i18next';
|
|
496
|
+
import { Toaster } from 'sonner';
|
|
497
|
+
import { routeTree } from './routeTree.gen';
|
|
498
|
+
import { wireShadcnNotifier } from './lib/notify';
|
|
499
|
+
|
|
500
|
+
const queryClient = new QueryClient({
|
|
501
|
+
defaultOptions: { queries: { retry: false, refetchOnWindowFocus: false } },
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
const router = createRouter({ routeTree, context: { queryClient } });
|
|
505
|
+
|
|
506
|
+
declare module '@tanstack/react-router' {
|
|
507
|
+
interface Register {
|
|
508
|
+
router: typeof router;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
const i18n = createIcoreI18n({ resources: ICORE_LOCALES });
|
|
513
|
+
|
|
514
|
+
// Single shared API instance \u2014 used by every query in src/queries/
|
|
515
|
+
export const api = createIcoreApi({
|
|
516
|
+
baseUrl: import.meta.env.VITE_API_URL ?? '/api',
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
wireShadcnNotifier();
|
|
520
|
+
|
|
521
|
+
// Apply the theme class before React mounts so the first paint is correct
|
|
522
|
+
const applyTheme = (mode: 'light' | 'dark') => {
|
|
523
|
+
document.documentElement.classList.toggle('dark', mode === 'dark');
|
|
524
|
+
};
|
|
525
|
+
applyTheme(useThemeStore.getState().mode);
|
|
526
|
+
useThemeStore.subscribe((s) => applyTheme(s.mode));
|
|
527
|
+
|
|
528
|
+
createRoot(document.getElementById('root')!).render(
|
|
529
|
+
<StrictMode>
|
|
530
|
+
<I18nextProvider i18n={i18n}>
|
|
531
|
+
<QueryClientProvider client={queryClient}>
|
|
532
|
+
<RouterProvider router={router} />
|
|
533
|
+
<Toaster richColors />
|
|
534
|
+
</QueryClientProvider>
|
|
535
|
+
</I18nextProvider>
|
|
536
|
+
</StrictMode>,
|
|
537
|
+
);
|
|
538
|
+
`;
|
|
539
|
+
var SHADCN_DASHBOARD_TSX = `import { createFileRoute, Outlet } from '@tanstack/react-router';
|
|
540
|
+
import { MainLayout } from '../layouts/MainLayout';
|
|
541
|
+
|
|
542
|
+
export const Route = createFileRoute('/_dashboard')({
|
|
543
|
+
component: () => (
|
|
544
|
+
<MainLayout>
|
|
545
|
+
<Outlet />
|
|
546
|
+
</MainLayout>
|
|
547
|
+
),
|
|
548
|
+
});
|
|
549
|
+
`;
|
|
550
|
+
var SHADCN_INDEX_TSX = `import { createFileRoute } from '@tanstack/react-router';
|
|
551
|
+
import { LandingPage } from '@icore/template-shared';
|
|
552
|
+
|
|
553
|
+
// All version strings are injected at build time by vite.config.mts
|
|
554
|
+
// (reads root package.json via fs.readFileSync so they stay accurate
|
|
555
|
+
// even when workspace packages are bumped independently).
|
|
556
|
+
const APP_VERSION = (import.meta.env.VITE_APP_VERSION as string | undefined) ?? '0.0.0-dev';
|
|
557
|
+
|
|
558
|
+
export const Route = createFileRoute('/')({
|
|
559
|
+
component: () => (
|
|
560
|
+
<LandingPage
|
|
561
|
+
coreVersion={APP_VERSION}
|
|
562
|
+
uiLibrary="shadcn"
|
|
563
|
+
deps={[
|
|
564
|
+
{ name: 'react', version: (import.meta.env.VITE_DEP_REACT as string) ?? '?' },
|
|
565
|
+
{ name: 'vite', version: (import.meta.env.VITE_DEP_VITE as string) ?? '?' },
|
|
566
|
+
{ name: 'tailwindcss', version: (import.meta.env.VITE_DEP_TAILWINDCSS as string) ?? '?' },
|
|
567
|
+
{
|
|
568
|
+
name: '@tanstack/react-router',
|
|
569
|
+
version: (import.meta.env.VITE_DEP_TANSTACK_ROUTER as string) ?? '?',
|
|
570
|
+
},
|
|
571
|
+
{
|
|
572
|
+
name: '@tanstack/react-query',
|
|
573
|
+
version: (import.meta.env.VITE_DEP_TANSTACK_QUERY as string) ?? '?',
|
|
574
|
+
},
|
|
575
|
+
{ name: 'zustand', version: (import.meta.env.VITE_DEP_ZUSTAND as string) ?? '?' },
|
|
576
|
+
{ name: '@casl/ability', version: (import.meta.env.VITE_DEP_CASL as string) ?? '?' },
|
|
577
|
+
]}
|
|
578
|
+
ctaHref="/dashboard"
|
|
579
|
+
ctaLabel="Dashboard \u2192"
|
|
580
|
+
/>
|
|
581
|
+
),
|
|
582
|
+
});
|
|
583
|
+
`;
|
|
584
|
+
var SHADCN_LAYOUT_HEADER_TSX = `import { setStoredLocale, type IcoreLocale } from '@icore/template-shared';
|
|
585
|
+
import { ThemeToggle } from '../ThemeToggle';
|
|
586
|
+
|
|
587
|
+
const LOCALES: { code: IcoreLocale; label: string }[] = [
|
|
588
|
+
{ code: 'en', label: 'EN' },
|
|
589
|
+
{ code: 'ru', label: 'RU' },
|
|
590
|
+
{ code: 'he', label: 'HE' },
|
|
591
|
+
];
|
|
592
|
+
|
|
593
|
+
export function LayoutHeader() {
|
|
594
|
+
function handleLocale(code: IcoreLocale) {
|
|
595
|
+
setStoredLocale(code);
|
|
596
|
+
window.location.reload();
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
return (
|
|
600
|
+
<header className="sticky top-0 z-30 flex h-14 items-center justify-between border-b border-[--color-border] bg-[--color-background]/80 px-4 backdrop-blur-sm">
|
|
601
|
+
<div className="flex items-center gap-2">
|
|
602
|
+
<div className="flex h-6 w-6 items-center justify-center rounded bg-[--color-primary]">
|
|
603
|
+
<span className="text-xs font-bold text-[--color-primary-foreground]">i</span>
|
|
604
|
+
</div>
|
|
605
|
+
<span className="text-sm font-semibold tracking-tight">iCore</span>
|
|
606
|
+
</div>
|
|
607
|
+
|
|
608
|
+
<div className="flex items-center gap-1">
|
|
609
|
+
<div className="flex items-center rounded-md border border-[--color-border] overflow-hidden mr-2">
|
|
610
|
+
{LOCALES.map(({ code, label }) => (
|
|
611
|
+
<button
|
|
612
|
+
key={code}
|
|
613
|
+
type="button"
|
|
614
|
+
onClick={() => handleLocale(code)}
|
|
615
|
+
className="px-2.5 py-1 text-xs text-[--color-muted-foreground] hover:bg-[--color-muted] hover:text-[--color-foreground] transition-colors cursor-pointer"
|
|
616
|
+
>
|
|
617
|
+
{label}
|
|
618
|
+
</button>
|
|
619
|
+
))}
|
|
620
|
+
</div>
|
|
621
|
+
|
|
622
|
+
<ThemeToggle />
|
|
623
|
+
</div>
|
|
624
|
+
</header>
|
|
625
|
+
);
|
|
626
|
+
}
|
|
627
|
+
`;
|
|
628
|
+
var ANTD_MAIN_TSX = `import './globals.less';
|
|
629
|
+
import { StrictMode } from 'react';
|
|
630
|
+
import { createRoot } from 'react-dom/client';
|
|
631
|
+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
632
|
+
import { RouterProvider, createRouter } from '@tanstack/react-router';
|
|
633
|
+
import { ConfigProvider, App as AntApp, theme } from 'antd';
|
|
634
|
+
import { I18nextProvider } from 'react-i18next';
|
|
635
|
+
import {
|
|
636
|
+
createIcoreApi,
|
|
637
|
+
createIcoreI18n,
|
|
638
|
+
ICORE_LOCALES,
|
|
639
|
+
useThemeStore,
|
|
640
|
+
} from '@icore/template-shared';
|
|
641
|
+
import { routeTree } from './routeTree.gen';
|
|
642
|
+
|
|
643
|
+
const queryClient = new QueryClient({
|
|
644
|
+
defaultOptions: { queries: { retry: false, refetchOnWindowFocus: false } },
|
|
645
|
+
});
|
|
646
|
+
|
|
647
|
+
const router = createRouter({ routeTree, context: { queryClient } });
|
|
648
|
+
|
|
649
|
+
declare module '@tanstack/react-router' {
|
|
650
|
+
interface Register {
|
|
651
|
+
router: typeof router;
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
const i18n = createIcoreI18n({ resources: ICORE_LOCALES });
|
|
656
|
+
|
|
657
|
+
// Single shared API instance \u2014 used by every query in src/queries/
|
|
658
|
+
export const api = createIcoreApi({
|
|
659
|
+
baseUrl: import.meta.env.VITE_API_URL ?? '/api',
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
function Root() {
|
|
663
|
+
const mode = useThemeStore((s) => s.mode);
|
|
664
|
+
const algorithm = mode === 'dark' ? theme.darkAlgorithm : theme.defaultAlgorithm;
|
|
665
|
+
return (
|
|
666
|
+
<ConfigProvider theme={{ algorithm, token: { colorPrimary: '#22c55e', colorLink: '#22c55e' } }}>
|
|
667
|
+
<AntApp>
|
|
668
|
+
<QueryClientProvider client={queryClient}>
|
|
669
|
+
<RouterProvider router={router} />
|
|
670
|
+
</QueryClientProvider>
|
|
671
|
+
</AntApp>
|
|
672
|
+
</ConfigProvider>
|
|
673
|
+
);
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
createRoot(document.getElementById('root')!).render(
|
|
677
|
+
<StrictMode>
|
|
678
|
+
<I18nextProvider i18n={i18n}>
|
|
679
|
+
<Root />
|
|
680
|
+
</I18nextProvider>
|
|
681
|
+
</StrictMode>,
|
|
682
|
+
);
|
|
683
|
+
`;
|
|
684
|
+
var ANTD_DASHBOARD_TSX = `import { createFileRoute, Outlet } from '@tanstack/react-router';
|
|
685
|
+
import { MainLayout } from '../layouts/MainLayout';
|
|
686
|
+
|
|
687
|
+
export const Route = createFileRoute('/_dashboard')({
|
|
688
|
+
component: () => (
|
|
689
|
+
<MainLayout>
|
|
690
|
+
<Outlet />
|
|
691
|
+
</MainLayout>
|
|
692
|
+
),
|
|
693
|
+
});
|
|
694
|
+
`;
|
|
695
|
+
var ANTD_INDEX_TSX = `import { createFileRoute, Link } from '@tanstack/react-router';
|
|
696
|
+
import { LandingPage } from '@icore/template-shared';
|
|
697
|
+
|
|
698
|
+
// All version strings are injected at build time by vite.config.mts
|
|
699
|
+
// (reads root package.json via fs.readFileSync so they stay accurate
|
|
700
|
+
// even when workspace packages are bumped independently).
|
|
701
|
+
const APP_VERSION = (import.meta.env.VITE_APP_VERSION as string | undefined) ?? '0.0.0-dev';
|
|
702
|
+
|
|
703
|
+
export const Route = createFileRoute('/')({
|
|
704
|
+
component: () => (
|
|
705
|
+
<LandingPage
|
|
706
|
+
coreVersion={APP_VERSION}
|
|
707
|
+
uiLibrary="antd"
|
|
708
|
+
deps={[
|
|
709
|
+
{ name: 'react', version: (import.meta.env.VITE_DEP_REACT as string) ?? '?' },
|
|
710
|
+
{ name: 'antd', version: (import.meta.env.VITE_DEP_ANTD as string) ?? '?' },
|
|
711
|
+
{ name: 'vite', version: (import.meta.env.VITE_DEP_VITE as string) ?? '?' },
|
|
712
|
+
{
|
|
713
|
+
name: '@tanstack/react-router',
|
|
714
|
+
version: (import.meta.env.VITE_DEP_TANSTACK_ROUTER as string) ?? '?',
|
|
715
|
+
},
|
|
716
|
+
{
|
|
717
|
+
name: '@tanstack/react-query',
|
|
718
|
+
version: (import.meta.env.VITE_DEP_TANSTACK_QUERY as string) ?? '?',
|
|
719
|
+
},
|
|
720
|
+
{ name: 'zustand', version: (import.meta.env.VITE_DEP_ZUSTAND as string) ?? '?' },
|
|
721
|
+
{ name: '@casl/ability', version: (import.meta.env.VITE_DEP_CASL as string) ?? '?' },
|
|
722
|
+
]}
|
|
723
|
+
ctaHref="/dashboard"
|
|
724
|
+
ctaLabel={<Link to="/dashboard">Dashboard \u2192</Link>}
|
|
725
|
+
/>
|
|
726
|
+
),
|
|
727
|
+
});
|
|
728
|
+
`;
|
|
729
|
+
var ANTD_LAYOUT_HEADER_TSX = `import { Button, Layout, Space } from 'antd';
|
|
730
|
+
import { setStoredLocale, type IcoreLocale } from '@icore/template-shared';
|
|
731
|
+
import { ThemeToggle } from '../ThemeToggle';
|
|
732
|
+
|
|
733
|
+
const APP_VERSION = (import.meta.env.VITE_APP_VERSION as string | undefined) ?? '0.0.0-dev';
|
|
734
|
+
|
|
735
|
+
const LOCALES: { code: IcoreLocale; label: string }[] = [
|
|
736
|
+
{ code: 'en', label: 'EN' },
|
|
737
|
+
{ code: 'ru', label: 'RU' },
|
|
738
|
+
{ code: 'he', label: 'HE' },
|
|
739
|
+
];
|
|
740
|
+
|
|
741
|
+
export function LayoutHeader() {
|
|
742
|
+
function handleLocale(code: IcoreLocale) {
|
|
743
|
+
setStoredLocale(code);
|
|
744
|
+
window.location.reload();
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
return (
|
|
748
|
+
<Layout.Header
|
|
749
|
+
style={{
|
|
750
|
+
display: 'flex',
|
|
751
|
+
alignItems: 'center',
|
|
752
|
+
justifyContent: 'space-between',
|
|
753
|
+
padding: '0 24px',
|
|
754
|
+
}}
|
|
755
|
+
>
|
|
756
|
+
<Space>
|
|
757
|
+
<span style={{ color: '#fff', fontWeight: 600, fontSize: 16 }}>iCore</span>
|
|
758
|
+
<span
|
|
759
|
+
style={{
|
|
760
|
+
color: 'rgba(255,255,255,0.45)',
|
|
761
|
+
fontSize: 11,
|
|
762
|
+
background: 'rgba(255,255,255,0.1)',
|
|
763
|
+
padding: '1px 6px',
|
|
764
|
+
borderRadius: 4,
|
|
765
|
+
}}
|
|
766
|
+
>
|
|
767
|
+
v{APP_VERSION}
|
|
768
|
+
</span>
|
|
769
|
+
</Space>
|
|
770
|
+
|
|
771
|
+
<Space size="middle">
|
|
772
|
+
<Space size={4}>
|
|
773
|
+
{LOCALES.map(({ code, label }) => (
|
|
774
|
+
<Button
|
|
775
|
+
key={code}
|
|
776
|
+
size="small"
|
|
777
|
+
type="text"
|
|
778
|
+
style={{ color: 'rgba(255,255,255,0.65)' }}
|
|
779
|
+
onClick={() => handleLocale(code)}
|
|
780
|
+
>
|
|
781
|
+
{label}
|
|
782
|
+
</Button>
|
|
783
|
+
))}
|
|
784
|
+
</Space>
|
|
785
|
+
|
|
786
|
+
<ThemeToggle />
|
|
787
|
+
</Space>
|
|
788
|
+
</Layout.Header>
|
|
789
|
+
);
|
|
790
|
+
}
|
|
791
|
+
`;
|
|
792
|
+
var MUI_MAIN_TSX = `import './globals.css';
|
|
793
|
+
import { StrictMode, useMemo } from 'react';
|
|
794
|
+
import { createRoot } from 'react-dom/client';
|
|
795
|
+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
796
|
+
import { RouterProvider, createRouter } from '@tanstack/react-router';
|
|
797
|
+
import { CssBaseline, ThemeProvider, createTheme } from '@mui/material';
|
|
798
|
+
import { I18nextProvider } from 'react-i18next';
|
|
799
|
+
import {
|
|
800
|
+
createIcoreApi,
|
|
801
|
+
createIcoreI18n,
|
|
802
|
+
ICORE_LOCALES,
|
|
803
|
+
useThemeStore,
|
|
804
|
+
} from '@icore/template-shared';
|
|
805
|
+
import { routeTree } from './routeTree.gen';
|
|
806
|
+
import { wireMuiNotifier } from './lib/notify';
|
|
807
|
+
|
|
808
|
+
const queryClient = new QueryClient({
|
|
809
|
+
defaultOptions: { queries: { retry: false, refetchOnWindowFocus: false } },
|
|
810
|
+
});
|
|
811
|
+
|
|
812
|
+
const router = createRouter({ routeTree, context: { queryClient } });
|
|
813
|
+
|
|
814
|
+
declare module '@tanstack/react-router' {
|
|
815
|
+
interface Register {
|
|
816
|
+
router: typeof router;
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
const i18n = createIcoreI18n({ resources: ICORE_LOCALES });
|
|
821
|
+
|
|
822
|
+
export const api = createIcoreApi({
|
|
823
|
+
baseUrl: import.meta.env.VITE_API_URL ?? '/api',
|
|
824
|
+
});
|
|
825
|
+
|
|
826
|
+
wireMuiNotifier();
|
|
827
|
+
|
|
828
|
+
function Root() {
|
|
829
|
+
const mode = useThemeStore((s) => s.mode);
|
|
830
|
+
const theme = useMemo(
|
|
831
|
+
() => createTheme({ palette: { mode, primary: { main: '#22c55e' } } }),
|
|
832
|
+
[mode],
|
|
833
|
+
);
|
|
834
|
+
return (
|
|
835
|
+
<ThemeProvider theme={theme}>
|
|
836
|
+
<CssBaseline />
|
|
837
|
+
<QueryClientProvider client={queryClient}>
|
|
838
|
+
<RouterProvider router={router} />
|
|
839
|
+
</QueryClientProvider>
|
|
840
|
+
</ThemeProvider>
|
|
841
|
+
);
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
createRoot(document.getElementById('root')!).render(
|
|
845
|
+
<StrictMode>
|
|
846
|
+
<I18nextProvider i18n={i18n}>
|
|
847
|
+
<Root />
|
|
848
|
+
</I18nextProvider>
|
|
849
|
+
</StrictMode>,
|
|
850
|
+
);
|
|
851
|
+
`;
|
|
852
|
+
var MUI_DASHBOARD_TSX = `import { createFileRoute, Outlet } from '@tanstack/react-router';
|
|
853
|
+
import { MainLayout } from '../layouts/MainLayout';
|
|
854
|
+
|
|
855
|
+
export const Route = createFileRoute('/_dashboard')({
|
|
856
|
+
component: () => (
|
|
857
|
+
<MainLayout>
|
|
858
|
+
<Outlet />
|
|
859
|
+
</MainLayout>
|
|
860
|
+
),
|
|
861
|
+
});
|
|
862
|
+
`;
|
|
863
|
+
var MUI_INDEX_TSX = `import { createFileRoute, Link } from '@tanstack/react-router';
|
|
864
|
+
import { LandingPage } from '@icore/template-shared';
|
|
865
|
+
|
|
866
|
+
// All version strings are injected at build time by vite.config.mts
|
|
867
|
+
// (reads root package.json via fs.readFileSync so they stay accurate
|
|
868
|
+
// even when workspace packages are bumped independently).
|
|
869
|
+
const APP_VERSION = (import.meta.env.VITE_APP_VERSION as string | undefined) ?? '0.0.0-dev';
|
|
870
|
+
|
|
871
|
+
export const Route = createFileRoute('/')({
|
|
872
|
+
component: () => (
|
|
873
|
+
<LandingPage
|
|
874
|
+
coreVersion={APP_VERSION}
|
|
875
|
+
uiLibrary="mui"
|
|
876
|
+
deps={[
|
|
877
|
+
{ name: 'react', version: (import.meta.env.VITE_DEP_REACT as string) ?? '?' },
|
|
878
|
+
{ name: '@mui/material', version: (import.meta.env.VITE_DEP_MUI as string) ?? '?' },
|
|
879
|
+
{ name: 'vite', version: (import.meta.env.VITE_DEP_VITE as string) ?? '?' },
|
|
880
|
+
{
|
|
881
|
+
name: '@tanstack/react-router',
|
|
882
|
+
version: (import.meta.env.VITE_DEP_TANSTACK_ROUTER as string) ?? '?',
|
|
883
|
+
},
|
|
884
|
+
{
|
|
885
|
+
name: '@tanstack/react-query',
|
|
886
|
+
version: (import.meta.env.VITE_DEP_TANSTACK_QUERY as string) ?? '?',
|
|
887
|
+
},
|
|
888
|
+
{ name: 'zustand', version: (import.meta.env.VITE_DEP_ZUSTAND as string) ?? '?' },
|
|
889
|
+
{ name: '@casl/ability', version: (import.meta.env.VITE_DEP_CASL as string) ?? '?' },
|
|
890
|
+
]}
|
|
891
|
+
ctaHref="/dashboard"
|
|
892
|
+
ctaLabel={<Link to="/dashboard">Dashboard \u2192</Link>}
|
|
893
|
+
/>
|
|
894
|
+
),
|
|
895
|
+
});
|
|
896
|
+
`;
|
|
897
|
+
var MUI_LAYOUT_HEADER_TSX = `import { AppBar, Box, Button, Toolbar, Typography } from '@mui/material';
|
|
898
|
+
import { useTranslation } from 'react-i18next';
|
|
899
|
+
import { setStoredLocale, type IcoreLocale } from '@icore/template-shared';
|
|
900
|
+
import { ThemeToggle } from '../ThemeToggle';
|
|
901
|
+
|
|
902
|
+
const APP_VERSION = (import.meta.env.VITE_APP_VERSION as string | undefined) ?? '0.0.0-dev';
|
|
903
|
+
|
|
904
|
+
const LOCALES: { code: IcoreLocale; label: string }[] = [
|
|
905
|
+
{ code: 'en', label: 'EN' },
|
|
906
|
+
{ code: 'ru', label: 'RU' },
|
|
907
|
+
{ code: 'he', label: 'HE' },
|
|
908
|
+
];
|
|
909
|
+
|
|
910
|
+
export function LayoutHeader() {
|
|
911
|
+
const { i18n } = useTranslation();
|
|
912
|
+
const currentLocale = i18n.language as IcoreLocale;
|
|
913
|
+
|
|
914
|
+
function handleLocale(code: IcoreLocale) {
|
|
915
|
+
setStoredLocale(code);
|
|
916
|
+
window.location.reload();
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
return (
|
|
920
|
+
<AppBar position="sticky" color="default" elevation={1}>
|
|
921
|
+
<Toolbar sx={{ justifyContent: 'space-between' }}>
|
|
922
|
+
<Typography variant="h6" component="div" fontWeight={600}>
|
|
923
|
+
iCore{' '}
|
|
924
|
+
<span style={{ opacity: 0.6, fontSize: '0.75em', fontWeight: 400 }}>v{APP_VERSION}</span>
|
|
925
|
+
</Typography>
|
|
926
|
+
|
|
927
|
+
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
|
928
|
+
<Box sx={{ display: 'flex', gap: 0.5 }}>
|
|
929
|
+
{LOCALES.map(({ code, label }) => (
|
|
930
|
+
<Button
|
|
931
|
+
key={code}
|
|
932
|
+
size="small"
|
|
933
|
+
variant={currentLocale === code ? 'contained' : 'text'}
|
|
934
|
+
onClick={() => handleLocale(code)}
|
|
935
|
+
>
|
|
936
|
+
{label}
|
|
937
|
+
</Button>
|
|
938
|
+
))}
|
|
939
|
+
</Box>
|
|
940
|
+
|
|
941
|
+
<ThemeToggle />
|
|
942
|
+
</Box>
|
|
943
|
+
</Toolbar>
|
|
944
|
+
</AppBar>
|
|
945
|
+
);
|
|
946
|
+
}
|
|
947
|
+
`;
|
|
948
|
+
var COMMON_VARIANTS = {
|
|
949
|
+
"apps/api/src/main.ts": GATEWAY_MAIN_TS,
|
|
950
|
+
"apps/api/src/app/app.module.ts": GATEWAY_APP_MODULE_TS,
|
|
951
|
+
"libs/shared/src/client.ts": SHARED_CLIENT_TS,
|
|
952
|
+
"libs/shared/src/index.ts": SHARED_INDEX_TS,
|
|
953
|
+
"libs/template-shared/src/index.ts": TEMPLATE_SHARED_INDEX_TS
|
|
954
|
+
};
|
|
955
|
+
var UI_VARIANTS = {
|
|
956
|
+
shadcn: {
|
|
957
|
+
"apps/client/src/main.tsx": SHADCN_MAIN_TSX,
|
|
958
|
+
"apps/client/src/routes/_dashboard.tsx": SHADCN_DASHBOARD_TSX,
|
|
959
|
+
"apps/client/src/routes/index.tsx": SHADCN_INDEX_TSX,
|
|
960
|
+
"apps/client/src/components/layout/LayoutHeader.tsx": SHADCN_LAYOUT_HEADER_TSX
|
|
961
|
+
},
|
|
962
|
+
antd: {
|
|
963
|
+
"apps/client/src/main.tsx": ANTD_MAIN_TSX,
|
|
964
|
+
"apps/client/src/routes/_dashboard.tsx": ANTD_DASHBOARD_TSX,
|
|
965
|
+
"apps/client/src/routes/index.tsx": ANTD_INDEX_TSX,
|
|
966
|
+
"apps/client/src/components/layout/LayoutHeader.tsx": ANTD_LAYOUT_HEADER_TSX
|
|
967
|
+
},
|
|
968
|
+
mui: {
|
|
969
|
+
"apps/client/src/main.tsx": MUI_MAIN_TSX,
|
|
970
|
+
"apps/client/src/routes/_dashboard.tsx": MUI_DASHBOARD_TSX,
|
|
971
|
+
"apps/client/src/routes/index.tsx": MUI_INDEX_TSX,
|
|
972
|
+
"apps/client/src/components/layout/LayoutHeader.tsx": MUI_LAYOUT_HEADER_TSX
|
|
973
|
+
}
|
|
974
|
+
};
|
|
975
|
+
async function applyAuthNoneVariants(targetDir, ui) {
|
|
976
|
+
const uiFiles = UI_VARIANTS[ui] ?? UI_VARIANTS["shadcn"];
|
|
977
|
+
const all = { ...COMMON_VARIANTS, ...uiFiles };
|
|
978
|
+
for (const [rel, content] of Object.entries(all)) {
|
|
979
|
+
const dest = join3(targetDir, rel);
|
|
980
|
+
try {
|
|
981
|
+
await mkdir(dirname(dest), { recursive: true });
|
|
982
|
+
await writeFile3(dest, content);
|
|
983
|
+
} catch {
|
|
984
|
+
}
|
|
985
|
+
}
|
|
319
986
|
}
|
|
320
987
|
|
|
321
988
|
// src/manifest/wire-features.ts
|
|
322
|
-
import { readFile as
|
|
323
|
-
import { join as
|
|
989
|
+
import { readFile as readFile5, writeFile as writeFile5, rm as rm4, rmdir, unlink } from "fs/promises";
|
|
990
|
+
import { join as join5 } from "path";
|
|
324
991
|
|
|
325
992
|
// src/manifest/index.ts
|
|
326
993
|
var EMPTY = { libDirs: [], deps: {}, tsPaths: {} };
|
|
@@ -480,8 +1147,8 @@ var MANIFEST = {
|
|
|
480
1147
|
};
|
|
481
1148
|
|
|
482
1149
|
// src/manifest/wire-provider.ts
|
|
483
|
-
import { readFile as
|
|
484
|
-
import { join as
|
|
1150
|
+
import { readFile as readFile4, writeFile as writeFile4, rm as rm3 } from "fs/promises";
|
|
1151
|
+
import { join as join4 } from "path";
|
|
485
1152
|
async function writeProvider(targetDir, axis, provider) {
|
|
486
1153
|
const nestModule = axis.section[provider]?.nestModule;
|
|
487
1154
|
if (!nestModule) throw new Error(`provider "${provider}" has no nestModule in the manifest`);
|
|
@@ -492,27 +1159,27 @@ const ENV_PATH = '${axis.envPath}';
|
|
|
492
1159
|
|
|
493
1160
|
export const ${axis.exportConst} = ${symbol}.forRoot(ENV_PATH);
|
|
494
1161
|
`;
|
|
495
|
-
await
|
|
1162
|
+
await writeFile4(join4(targetDir, axis.providerFile), content);
|
|
496
1163
|
}
|
|
497
1164
|
async function stripJsonKeys(path, drop) {
|
|
498
1165
|
try {
|
|
499
|
-
const pkg = JSON.parse(await
|
|
1166
|
+
const pkg = JSON.parse(await readFile4(path, "utf8"));
|
|
500
1167
|
for (const field of ["dependencies", "devDependencies"]) {
|
|
501
1168
|
const deps = pkg[field];
|
|
502
1169
|
if (!deps) continue;
|
|
503
1170
|
for (const k of Object.keys(deps)) if (drop(k)) delete deps[k];
|
|
504
1171
|
}
|
|
505
|
-
await
|
|
1172
|
+
await writeFile4(path, JSON.stringify(pkg, null, 2) + "\n");
|
|
506
1173
|
} catch {
|
|
507
1174
|
}
|
|
508
1175
|
}
|
|
509
1176
|
async function stripTsconfigKeys(targetDir, aliases) {
|
|
510
|
-
const path =
|
|
1177
|
+
const path = join4(targetDir, "tsconfig.base.json");
|
|
511
1178
|
try {
|
|
512
|
-
const parsed = JSON.parse(await
|
|
1179
|
+
const parsed = JSON.parse(await readFile4(path, "utf8"));
|
|
513
1180
|
const paths = parsed.compilerOptions?.paths;
|
|
514
1181
|
if (paths) for (const a of aliases) delete paths[a];
|
|
515
|
-
await
|
|
1182
|
+
await writeFile4(path, JSON.stringify(parsed, null, 2) + "\n");
|
|
516
1183
|
} catch {
|
|
517
1184
|
}
|
|
518
1185
|
}
|
|
@@ -521,10 +1188,10 @@ async function cleanupUnusedAxis(targetDir, axis, chosen) {
|
|
|
521
1188
|
if (provider === chosen) continue;
|
|
522
1189
|
const unit = axis.section[provider];
|
|
523
1190
|
for (const dir of unit.libDirs)
|
|
524
|
-
await
|
|
525
|
-
for (const t of unit.appTests ?? []) await
|
|
1191
|
+
await rm3(join4(targetDir, dir), { recursive: true, force: true });
|
|
1192
|
+
for (const t of unit.appTests ?? []) await rm3(join4(targetDir, t), { force: true });
|
|
526
1193
|
const dropKeys = /* @__PURE__ */ new Set([...Object.keys(unit.tsPaths), ...Object.keys(unit.deps)]);
|
|
527
|
-
await stripJsonKeys(
|
|
1194
|
+
await stripJsonKeys(join4(targetDir, axis.msPackageJson), (k) => dropKeys.has(k));
|
|
528
1195
|
await stripTsconfigKeys(targetDir, Object.keys(unit.tsPaths));
|
|
529
1196
|
}
|
|
530
1197
|
}
|
|
@@ -553,7 +1220,7 @@ async function writeFeaturesWiring(targetDir, opts) {
|
|
|
553
1220
|
})
|
|
554
1221
|
export class FeaturesModule {}
|
|
555
1222
|
`;
|
|
556
|
-
await
|
|
1223
|
+
await writeFile5(join5(targetDir, FEATURES_MODULE), featuresModule);
|
|
557
1224
|
const services = [];
|
|
558
1225
|
if (opts.authProvider !== "none") services.push({ name: "auth", prefix: "AUTH" });
|
|
559
1226
|
if (opts.upload !== "none") services.push({ name: "upload", prefix: "UPLOAD" });
|
|
@@ -567,14 +1234,14 @@ export const GATEWAY_SERVICES = [
|
|
|
567
1234
|
${entries}
|
|
568
1235
|
];
|
|
569
1236
|
`;
|
|
570
|
-
await
|
|
1237
|
+
await writeFile5(join5(targetDir, GATEWAY_SERVICES), gatewayServices);
|
|
571
1238
|
}
|
|
572
1239
|
async function stripJobsDockerCompose(targetDir) {
|
|
573
|
-
const composePath =
|
|
1240
|
+
const composePath = join5(targetDir, "docker-compose.yml");
|
|
574
1241
|
try {
|
|
575
|
-
const compose = await
|
|
1242
|
+
const compose = await readFile5(composePath, "utf8");
|
|
576
1243
|
const next = compose.replace(/\n {2}jobs:[\s\S]+?(?=\n {2}\w+:|\nnetworks:)/m, "\n").replace(/\n {6}jobs:\n {8}condition: service_started/g, "").replace(/\n {6}JOBS_REDIS_URL:[^\n]*/g, "");
|
|
577
|
-
await
|
|
1244
|
+
await writeFile5(composePath, next);
|
|
578
1245
|
} catch {
|
|
579
1246
|
}
|
|
580
1247
|
}
|
|
@@ -584,18 +1251,38 @@ async function cleanupUnusedFeatures(targetDir, opts) {
|
|
|
584
1251
|
if (chosen.has(key)) continue;
|
|
585
1252
|
const unit = FEATURES[key];
|
|
586
1253
|
for (const dir of unit.libDirs)
|
|
587
|
-
await
|
|
1254
|
+
await rm4(join5(targetDir, dir), { recursive: true, force: true });
|
|
588
1255
|
const dropKeys = /* @__PURE__ */ new Set([...Object.keys(unit.tsPaths), ...Object.keys(unit.deps)]);
|
|
589
|
-
await stripJsonKeys(
|
|
1256
|
+
await stripJsonKeys(join5(targetDir, API_PKG), (k) => dropKeys.has(k));
|
|
590
1257
|
await stripTsconfigKeys(targetDir, Object.keys(unit.tsPaths));
|
|
591
1258
|
if (unit.gatewayService) await stripGatewayTransport(targetDir, unit.gatewayService.prefix);
|
|
592
1259
|
if (unit.dockerService === "jobs") await stripJobsDockerCompose(targetDir);
|
|
1260
|
+
if (key === "jobs") {
|
|
1261
|
+
try {
|
|
1262
|
+
await unlink(join5(targetDir, "libs/shared/src/jobs.ts"));
|
|
1263
|
+
} catch {
|
|
1264
|
+
}
|
|
1265
|
+
try {
|
|
1266
|
+
await unlink(join5(targetDir, "libs/shared/src/__tests__/jobs.unit.test.ts"));
|
|
1267
|
+
} catch {
|
|
1268
|
+
}
|
|
1269
|
+
const sharedIdx = join5(targetDir, "libs/shared/src/index.ts");
|
|
1270
|
+
try {
|
|
1271
|
+
const src = await readFile5(sharedIdx, "utf8");
|
|
1272
|
+
await writeFile5(sharedIdx, src.replace(/^export \* from '\.\/jobs';\n/m, ""));
|
|
1273
|
+
} catch {
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
try {
|
|
1278
|
+
await rmdir(join5(targetDir, "apps/client/src/queries"));
|
|
1279
|
+
} catch {
|
|
593
1280
|
}
|
|
594
1281
|
}
|
|
595
1282
|
|
|
596
1283
|
// src/manifest/wire-client.ts
|
|
597
|
-
import { writeFile as
|
|
598
|
-
import { join as
|
|
1284
|
+
import { writeFile as writeFile6 } from "fs/promises";
|
|
1285
|
+
import { join as join6 } from "path";
|
|
599
1286
|
var NAV_CONFIG_FILE = "apps/client/src/nav.config.ts";
|
|
600
1287
|
var BASE_NAV = [
|
|
601
1288
|
{ to: "/dashboard", labelKey: "nav.dashboard", iconName: "dashboard", exact: true }
|
|
@@ -633,12 +1320,12 @@ export const NAV_CONFIG: NavItem[] = [
|
|
|
633
1320
|
` + entries.map(renderEntry).join("\n") + `
|
|
634
1321
|
];
|
|
635
1322
|
`;
|
|
636
|
-
await
|
|
1323
|
+
await writeFile6(join6(targetDir, NAV_CONFIG_FILE), content);
|
|
637
1324
|
}
|
|
638
1325
|
|
|
639
1326
|
// src/manifest/blueprint.ts
|
|
640
|
-
import { writeFile as
|
|
641
|
-
import { join as
|
|
1327
|
+
import { writeFile as writeFile7 } from "fs/promises";
|
|
1328
|
+
import { join as join7 } from "path";
|
|
642
1329
|
async function writeBlueprintJson(targetDir, opts) {
|
|
643
1330
|
const blueprint = {
|
|
644
1331
|
schemaVersion: 1,
|
|
@@ -653,10 +1340,10 @@ async function writeBlueprintJson(targetDir, opts) {
|
|
|
653
1340
|
transport: opts.transport,
|
|
654
1341
|
packageManager: opts.packageManager
|
|
655
1342
|
};
|
|
656
|
-
await
|
|
1343
|
+
await writeFile7(join7(targetDir, "blueprint.json"), JSON.stringify(blueprint, null, 2) + "\n");
|
|
657
1344
|
}
|
|
658
1345
|
async function writeJson(targetDir, rel, data) {
|
|
659
|
-
await
|
|
1346
|
+
await writeFile7(join7(targetDir, rel, "blueprint.json"), JSON.stringify(data, null, 2) + "\n");
|
|
660
1347
|
}
|
|
661
1348
|
async function writeServiceBlueprints(targetDir, opts) {
|
|
662
1349
|
const t = opts.transport;
|
|
@@ -750,11 +1437,11 @@ var writeDbProvider = (targetDir, provider) => writeProvider(targetDir, DB, prov
|
|
|
750
1437
|
var cleanupUnusedDb = (targetDir, chosen) => cleanupUnusedAxis(targetDir, DB, chosen);
|
|
751
1438
|
|
|
752
1439
|
// src/lib/scaffold-pkg.ts
|
|
753
|
-
import { readFile as
|
|
754
|
-
import { join as
|
|
1440
|
+
import { readFile as readFile6, writeFile as writeFile8, mkdir as mkdir2, readdir } from "fs/promises";
|
|
1441
|
+
import { join as join8 } from "path";
|
|
755
1442
|
async function writePnpmWorkspace(targetDir) {
|
|
756
|
-
const pkgPath =
|
|
757
|
-
const pkg = JSON.parse(await
|
|
1443
|
+
const pkgPath = join8(targetDir, "package.json");
|
|
1444
|
+
const pkg = JSON.parse(await readFile6(pkgPath, "utf8"));
|
|
758
1445
|
const workspaces = pkg.workspaces ?? [];
|
|
759
1446
|
const packagesBlock = workspaces.map((p2) => ` - '${p2}'`).join("\n");
|
|
760
1447
|
const allowBuilds = [
|
|
@@ -777,7 +1464,7 @@ ${packagesBlock}
|
|
|
777
1464
|
allowBuilds:
|
|
778
1465
|
${allowBuilds}
|
|
779
1466
|
`;
|
|
780
|
-
await
|
|
1467
|
+
await writeFile8(join8(targetDir, "pnpm-workspace.yaml"), content);
|
|
781
1468
|
}
|
|
782
1469
|
async function rewritePnpmWorkspaceDeps(targetDir) {
|
|
783
1470
|
async function walk2(dir) {
|
|
@@ -790,9 +1477,9 @@ async function rewritePnpmWorkspaceDeps(targetDir) {
|
|
|
790
1477
|
}
|
|
791
1478
|
for (const e of entries) {
|
|
792
1479
|
if (e.isDirectory() && e.name !== "node_modules") {
|
|
793
|
-
found.push(...await walk2(
|
|
1480
|
+
found.push(...await walk2(join8(dir, e.name)));
|
|
794
1481
|
} else if (e.isFile() && e.name === "package.json") {
|
|
795
|
-
found.push(
|
|
1482
|
+
found.push(join8(dir, e.name));
|
|
796
1483
|
}
|
|
797
1484
|
}
|
|
798
1485
|
return found;
|
|
@@ -800,17 +1487,17 @@ async function rewritePnpmWorkspaceDeps(targetDir) {
|
|
|
800
1487
|
const pkgFiles = await walk2(targetDir);
|
|
801
1488
|
for (const f of pkgFiles) {
|
|
802
1489
|
try {
|
|
803
|
-
const raw = await
|
|
1490
|
+
const raw = await readFile6(f, "utf8");
|
|
804
1491
|
const next = raw.replace(/"(@icore\/[^"]+)":\s*"\*"/g, '"$1": "workspace:*"');
|
|
805
|
-
if (next !== raw) await
|
|
1492
|
+
if (next !== raw) await writeFile8(f, next);
|
|
806
1493
|
} catch {
|
|
807
1494
|
}
|
|
808
1495
|
}
|
|
809
1496
|
}
|
|
810
1497
|
async function patchGitignoreForPm(targetDir, pm) {
|
|
811
|
-
const giPath =
|
|
1498
|
+
const giPath = join8(targetDir, ".gitignore");
|
|
812
1499
|
try {
|
|
813
|
-
let src = await
|
|
1500
|
+
let src = await readFile6(giPath, "utf8");
|
|
814
1501
|
src = src.replace(/^# Build artifacts.*\ntools\/create-icore\/templates\/\s*\n/m, "");
|
|
815
1502
|
if (pm !== "yarn") {
|
|
816
1503
|
src = src.replace(/^\.yarn\/\*\s*\n/m, "").replace(/^!\.yarn\/patches\s*\n/m, "").replace(/^!\.yarn\/plugins\s*\n/m, "").replace(/^!\.yarn\/releases\s*\n/m, "").replace(/^!\.yarn\/sdks\s*\n/m, "").replace(/^!\.yarn\/versions\s*\n/m, "").replace(/^\.pnp\.\*\s*\n/m, "");
|
|
@@ -825,7 +1512,7 @@ async function patchGitignoreForPm(targetDir, pm) {
|
|
|
825
1512
|
src += "\n# npm\nnpm-debug.log*\n";
|
|
826
1513
|
}
|
|
827
1514
|
}
|
|
828
|
-
await
|
|
1515
|
+
await writeFile8(giPath, src);
|
|
829
1516
|
} catch {
|
|
830
1517
|
}
|
|
831
1518
|
}
|
|
@@ -833,14 +1520,15 @@ async function writeAiFiles(targetDir, opts) {
|
|
|
833
1520
|
const pm = opts.packageManager;
|
|
834
1521
|
const nx = pm === "npm" ? "npx nx" : `${pm} nx`;
|
|
835
1522
|
const devCmd = pmRun(pm, "dev");
|
|
836
|
-
const activeMSes = [
|
|
1523
|
+
const activeMSes = [];
|
|
1524
|
+
if (opts.authProvider !== "none") activeMSes.push("auth (port 4001)");
|
|
837
1525
|
if (opts.upload !== "none") activeMSes.push(`upload (port 4002)`);
|
|
838
1526
|
if (opts.payment !== "none") activeMSes.push(`payment (port 4003)`);
|
|
839
1527
|
if (opts.example !== "none") activeMSes.push(`notes (port 4004)`);
|
|
840
1528
|
if (opts.jobs !== "none") activeMSes.push(`jobs (standalone)`);
|
|
841
1529
|
const usesSupabase = opts.authProvider === "supabase" || opts.dbProvider === "supabase" || opts.upload === "supabase";
|
|
842
1530
|
const usesFirebase = opts.authProvider === "firebase" || opts.dbProvider === "firebase" || opts.upload === "firebase";
|
|
843
|
-
await
|
|
1531
|
+
await writeFile8(join8(targetDir, "CLAUDE.md"), "@AGENTS.md\n");
|
|
844
1532
|
const uiLabel = { shadcn: "shadcn/ui + Tailwind", antd: "Ant Design 6", mui: "MUI 6" }[opts.ui];
|
|
845
1533
|
const readme = `# ${opts.projectName}
|
|
846
1534
|
|
|
@@ -862,9 +1550,7 @@ async function writeAiFiles(targetDir, opts) {
|
|
|
862
1550
|
|
|
863
1551
|
\`\`\`bash
|
|
864
1552
|
# 1. Fill in provider credentials
|
|
865
|
-
# apps/microservices/auth/.env
|
|
866
|
-
# apps/microservices/upload/.env (if upload is enabled)
|
|
867
|
-
# apps/client/.env (VITE_API_URL \u2014 already defaults to /api)
|
|
1553
|
+
${opts.authProvider !== "none" ? "# apps/microservices/auth/.env\n" : ""}${opts.upload !== "none" ? "# apps/microservices/upload/.env\n" : ""}# apps/client/.env (VITE_API_URL \u2014 already defaults to /api)
|
|
868
1554
|
|
|
869
1555
|
# 2. Start everything
|
|
870
1556
|
${devCmd}
|
|
@@ -890,7 +1576,7 @@ ${pm === "yarn" ? "yarn remove-notes" : pm === "pnpm" ? "pnpm remove-notes" : "n
|
|
|
890
1576
|
|
|
891
1577
|
Apache-2.0
|
|
892
1578
|
`;
|
|
893
|
-
await
|
|
1579
|
+
await writeFile8(join8(targetDir, "README.md"), readme);
|
|
894
1580
|
const agents = `# ${opts.projectName} \u2014 Agent Instructions
|
|
895
1581
|
|
|
896
1582
|
## Stack snapshot
|
|
@@ -960,10 +1646,10 @@ ${nx} g @nx/nest:resource # generate NestJS resource
|
|
|
960
1646
|
|
|
961
1647
|
| File | Key vars |
|
|
962
1648
|
|------|----------|
|
|
963
|
-
|
|
964
|
-
${opts.upload !== "none" ? `| \`apps/microservices/upload/.env\` | \`STORAGE_PROVIDER=${opts.upload}\`, provider creds |
|
|
965
|
-
` : ""}
|
|
966
|
-
| \`apps/client/.env\` | \`VITE_API_URL=/api\` (proxied to :3001 in dev) |
|
|
1649
|
+
${opts.authProvider !== "none" ? `| \`apps/microservices/auth/.env\` | \`AUTH_PROVIDER=${opts.authProvider}\`, ${opts.authProvider === "supabase" ? "`SUPABASE_URL`, `SUPABASE_SERVICE_ROLE_KEY`" : "`FB_ADMIN_*`, `FIREBASE_WEB_API_KEY`"} |
|
|
1650
|
+
` : ""}${opts.upload !== "none" ? `| \`apps/microservices/upload/.env\` | \`STORAGE_PROVIDER=${opts.upload}\`, provider creds |
|
|
1651
|
+
` : ""}${opts.example !== "none" ? `| \`apps/microservices/notes/.env\` | \`DB_PROVIDER=${opts.dbProvider}\`, DB creds |
|
|
1652
|
+
` : ""}| \`apps/client/.env\` | \`VITE_API_URL=/api\` (proxied to :3001 in dev) |
|
|
967
1653
|
|
|
968
1654
|
## Testing
|
|
969
1655
|
|
|
@@ -971,8 +1657,8 @@ ${opts.upload !== "none" ? `| \`apps/microservices/upload/.env\` | \`STORAGE_PRO
|
|
|
971
1657
|
- Test behaviour, not implementation. Fake strategies from \`@icore/shared\` (FakeAuthStrategy etc.) serve as test doubles.
|
|
972
1658
|
- Run: \`${nx} test <project>\`
|
|
973
1659
|
`;
|
|
974
|
-
await
|
|
975
|
-
await
|
|
1660
|
+
await writeFile8(join8(targetDir, "AGENTS.md"), agents);
|
|
1661
|
+
await mkdir2(join8(targetDir, ".claude"), { recursive: true });
|
|
976
1662
|
const mcpServers = {
|
|
977
1663
|
nx: {
|
|
978
1664
|
command: "npx",
|
|
@@ -1013,8 +1699,8 @@ ${opts.upload !== "none" ? `| \`apps/microservices/upload/.env\` | \`STORAGE_PRO
|
|
|
1013
1699
|
]
|
|
1014
1700
|
}
|
|
1015
1701
|
};
|
|
1016
|
-
await
|
|
1017
|
-
|
|
1702
|
+
await writeFile8(
|
|
1703
|
+
join8(targetDir, ".claude", "settings.json"),
|
|
1018
1704
|
JSON.stringify(settings, null, 2) + "\n"
|
|
1019
1705
|
);
|
|
1020
1706
|
}
|
|
@@ -1034,20 +1720,20 @@ var IGNORE_TOP = /* @__PURE__ */ new Set([
|
|
|
1034
1720
|
".vscode"
|
|
1035
1721
|
]);
|
|
1036
1722
|
async function copyTree(src, dest) {
|
|
1037
|
-
await
|
|
1723
|
+
await mkdir3(dest, { recursive: true });
|
|
1038
1724
|
const entries = await readdir2(src, { withFileTypes: true });
|
|
1039
1725
|
for (const entry of entries) {
|
|
1040
1726
|
if (IGNORE_TOP.has(entry.name)) continue;
|
|
1041
|
-
const s =
|
|
1042
|
-
const d =
|
|
1727
|
+
const s = join9(src, entry.name);
|
|
1728
|
+
const d = join9(dest, entry.name);
|
|
1043
1729
|
if (entry.isDirectory()) await copyTree(s, d);
|
|
1044
1730
|
else if (entry.isFile()) await copyFile(s, d);
|
|
1045
1731
|
}
|
|
1046
1732
|
}
|
|
1047
1733
|
async function selectClientTemplate(targetDir, opts) {
|
|
1048
|
-
const templatesRoot =
|
|
1049
|
-
const chosen =
|
|
1050
|
-
const destClient =
|
|
1734
|
+
const templatesRoot = join9(targetDir, "apps/templates");
|
|
1735
|
+
const chosen = join9(templatesRoot, `client-${opts.ui}`);
|
|
1736
|
+
const destClient = join9(targetDir, "apps/client");
|
|
1051
1737
|
let chosenUi = opts.ui;
|
|
1052
1738
|
try {
|
|
1053
1739
|
const s = await stat(chosen);
|
|
@@ -1055,9 +1741,9 @@ async function selectClientTemplate(targetDir, opts) {
|
|
|
1055
1741
|
await copyTree(chosen, destClient);
|
|
1056
1742
|
} catch {
|
|
1057
1743
|
chosenUi = "shadcn";
|
|
1058
|
-
await copyTree(
|
|
1744
|
+
await copyTree(join9(templatesRoot, "client-shadcn"), destClient);
|
|
1059
1745
|
}
|
|
1060
|
-
await
|
|
1746
|
+
await rm5(templatesRoot, { recursive: true, force: true });
|
|
1061
1747
|
await rewriteClientPaths(destClient, chosenUi);
|
|
1062
1748
|
}
|
|
1063
1749
|
async function rewriteClientPaths(clientDir, ui) {
|
|
@@ -1070,11 +1756,11 @@ async function rewriteClientPaths(clientDir, ui) {
|
|
|
1070
1756
|
"eslint.config.mjs"
|
|
1071
1757
|
];
|
|
1072
1758
|
for (const rel of candidates) {
|
|
1073
|
-
const path =
|
|
1759
|
+
const path = join9(clientDir, rel);
|
|
1074
1760
|
try {
|
|
1075
|
-
const raw = await
|
|
1761
|
+
const raw = await readFile7(path, "utf8");
|
|
1076
1762
|
const next = raw.replaceAll("../../../", "../../").replaceAll(`apps/templates/client-${ui}`, "apps/client").replaceAll(`client-${ui}`, "client");
|
|
1077
|
-
if (next !== raw) await
|
|
1763
|
+
if (next !== raw) await writeFile9(path, next);
|
|
1078
1764
|
} catch {
|
|
1079
1765
|
}
|
|
1080
1766
|
}
|
|
@@ -1090,12 +1776,12 @@ function gitInit(cwd, projectName) {
|
|
|
1090
1776
|
}
|
|
1091
1777
|
function resolveYarnBin(cwd) {
|
|
1092
1778
|
try {
|
|
1093
|
-
const yarnrc = readFileSync(
|
|
1779
|
+
const yarnrc = readFileSync(join9(cwd, ".yarnrc.yml"), "utf8");
|
|
1094
1780
|
const match = yarnrc.match(/^yarnPath:\s*(.+)$/m);
|
|
1095
|
-
if (match?.[1]) return
|
|
1781
|
+
if (match?.[1]) return join9(cwd, match[1].trim());
|
|
1096
1782
|
} catch {
|
|
1097
1783
|
}
|
|
1098
|
-
return
|
|
1784
|
+
return join9(cwd, ".yarn", "releases", "yarn-4.5.0.cjs");
|
|
1099
1785
|
}
|
|
1100
1786
|
function runInstall(cwd, pm) {
|
|
1101
1787
|
if (pm === "yarn") {
|
|
@@ -1106,7 +1792,8 @@ function runInstall(cwd, pm) {
|
|
|
1106
1792
|
spawnSync("pnpm", ["install"], { cwd, stdio: "inherit" });
|
|
1107
1793
|
}
|
|
1108
1794
|
}
|
|
1109
|
-
async function scaffold(
|
|
1795
|
+
async function scaffold(rawOpts, templatesDir) {
|
|
1796
|
+
const opts = rawOpts.authProvider === "none" && rawOpts.example !== "none" ? { ...rawOpts, example: "none" } : rawOpts;
|
|
1110
1797
|
await copyTree(templatesDir, opts.targetDir);
|
|
1111
1798
|
await rewriteRootPackageJson(opts.targetDir, opts);
|
|
1112
1799
|
if (opts.authProvider !== "none") await writeAuthEnv(opts.targetDir, opts);
|
|
@@ -1117,6 +1804,10 @@ async function scaffold(opts, templatesDir) {
|
|
|
1117
1804
|
await writeRootEnv(opts.targetDir, opts);
|
|
1118
1805
|
await selectClientTemplate(opts.targetDir, opts);
|
|
1119
1806
|
await writeClientEnv(opts.targetDir);
|
|
1807
|
+
if (opts.authProvider === "none") {
|
|
1808
|
+
await removeAuthOnlyPaths(opts.targetDir);
|
|
1809
|
+
await applyAuthNoneVariants(opts.targetDir, opts.ui);
|
|
1810
|
+
}
|
|
1120
1811
|
if (opts.upload === "none") await removeUploadStack(opts.targetDir);
|
|
1121
1812
|
await cleanupUnusedFeatures(opts.targetDir, opts);
|
|
1122
1813
|
await writeFeaturesWiring(opts.targetDir, opts);
|
|
@@ -1125,7 +1816,8 @@ async function scaffold(opts, templatesDir) {
|
|
|
1125
1816
|
await cleanupUnusedAuth(opts.targetDir, opts.authProvider);
|
|
1126
1817
|
await writeAuthProvider(opts.targetDir, opts.authProvider);
|
|
1127
1818
|
} else {
|
|
1128
|
-
await
|
|
1819
|
+
await removeAuthTsconfigPaths(opts.targetDir);
|
|
1820
|
+
await removeDockerComposeAuthService(opts.targetDir);
|
|
1129
1821
|
}
|
|
1130
1822
|
if (opts.upload !== "none") {
|
|
1131
1823
|
await cleanupUnusedStorage(opts.targetDir, opts.upload);
|
|
@@ -1138,13 +1830,20 @@ async function scaffold(opts, templatesDir) {
|
|
|
1138
1830
|
await writeDbProvider(opts.targetDir, opts.dbProvider);
|
|
1139
1831
|
}
|
|
1140
1832
|
await pruneRootProviderDeps(opts.targetDir, opts);
|
|
1833
|
+
if (opts.authProvider === "none" && opts.upload === "none" && opts.dbProvider === "none" && opts.payment === "none") {
|
|
1834
|
+
await removeStrategiesLib(opts.targetDir);
|
|
1835
|
+
}
|
|
1836
|
+
try {
|
|
1837
|
+
await rmdir2(join9(opts.targetDir, "apps/microservices"));
|
|
1838
|
+
} catch {
|
|
1839
|
+
}
|
|
1141
1840
|
await writeBlueprintJson(opts.targetDir, opts);
|
|
1142
1841
|
await writeServiceBlueprints(opts.targetDir, opts);
|
|
1143
1842
|
if (opts.packageManager === "yarn") {
|
|
1144
|
-
await
|
|
1843
|
+
await writeFile9(join9(opts.targetDir, "yarn.lock"), "");
|
|
1145
1844
|
} else {
|
|
1146
|
-
await
|
|
1147
|
-
await
|
|
1845
|
+
await rm5(join9(opts.targetDir, ".yarn"), { recursive: true, force: true });
|
|
1846
|
+
await rm5(join9(opts.targetDir, ".yarnrc.yml"), { force: true });
|
|
1148
1847
|
}
|
|
1149
1848
|
if (opts.packageManager === "pnpm") {
|
|
1150
1849
|
await writePnpmWorkspace(opts.targetDir);
|
|
@@ -1159,12 +1858,12 @@ async function scaffold(opts, templatesDir) {
|
|
|
1159
1858
|
// src/lib/prompts.ts
|
|
1160
1859
|
import * as p from "@clack/prompts";
|
|
1161
1860
|
import { resolve } from "path";
|
|
1162
|
-
import { readFile as
|
|
1163
|
-
import { dirname, join as
|
|
1861
|
+
import { readFile as readFile9 } from "fs/promises";
|
|
1862
|
+
import { dirname as dirname2, join as join10 } from "path";
|
|
1164
1863
|
import { fileURLToPath } from "url";
|
|
1165
1864
|
|
|
1166
1865
|
// src/lib/config.ts
|
|
1167
|
-
import { readFile as
|
|
1866
|
+
import { readFile as readFile8 } from "fs/promises";
|
|
1168
1867
|
var ConfigFileError = class extends Error {
|
|
1169
1868
|
constructor(message) {
|
|
1170
1869
|
super(message);
|
|
@@ -1235,7 +1934,7 @@ function validateConfig(raw) {
|
|
|
1235
1934
|
async function loadConfig(filePath) {
|
|
1236
1935
|
let raw;
|
|
1237
1936
|
try {
|
|
1238
|
-
raw = await
|
|
1937
|
+
raw = await readFile8(filePath, "utf8");
|
|
1239
1938
|
} catch {
|
|
1240
1939
|
throw new ConfigFileError(`config file not found: ${filePath}`);
|
|
1241
1940
|
}
|
|
@@ -1260,8 +1959,8 @@ function detectPackageManager() {
|
|
|
1260
1959
|
}
|
|
1261
1960
|
async function readSelfVersion() {
|
|
1262
1961
|
try {
|
|
1263
|
-
const here =
|
|
1264
|
-
const pkgRaw = await
|
|
1962
|
+
const here = dirname2(fileURLToPath(import.meta.url));
|
|
1963
|
+
const pkgRaw = await readFile9(join10(here, "..", "package.json"), "utf8");
|
|
1265
1964
|
const pkg = JSON.parse(pkgRaw);
|
|
1266
1965
|
return pkg.version ?? null;
|
|
1267
1966
|
} catch {
|
|
@@ -1485,23 +2184,23 @@ Re-run with @latest to refresh:
|
|
|
1485
2184
|
}
|
|
1486
2185
|
|
|
1487
2186
|
// src/manifest/audit.ts
|
|
1488
|
-
import { readFile as
|
|
1489
|
-
import { join as
|
|
2187
|
+
import { readFile as readFile10, readdir as readdir3 } from "fs/promises";
|
|
2188
|
+
import { join as join11 } from "path";
|
|
1490
2189
|
var IGNORE_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", ".nx"]);
|
|
1491
2190
|
async function walk(dir, out = []) {
|
|
1492
2191
|
const entries = await readdir3(dir, { withFileTypes: true });
|
|
1493
2192
|
for (const e of entries) {
|
|
1494
2193
|
if (e.isDirectory()) {
|
|
1495
|
-
if (!IGNORE_DIRS.has(e.name)) await walk(
|
|
2194
|
+
if (!IGNORE_DIRS.has(e.name)) await walk(join11(dir, e.name), out);
|
|
1496
2195
|
} else if (/\.(ts|tsx|mjs)$/.test(e.name)) {
|
|
1497
|
-
out.push(
|
|
2196
|
+
out.push(join11(dir, e.name));
|
|
1498
2197
|
}
|
|
1499
2198
|
}
|
|
1500
2199
|
return out;
|
|
1501
2200
|
}
|
|
1502
2201
|
async function tsconfigAliases(dir) {
|
|
1503
2202
|
try {
|
|
1504
|
-
const raw = await
|
|
2203
|
+
const raw = await readFile10(join11(dir, "tsconfig.base.json"), "utf8");
|
|
1505
2204
|
const aliases = /* @__PURE__ */ new Set();
|
|
1506
2205
|
for (const m of raw.matchAll(/"(@icore\/[a-z0-9.-]+)"\s*:/g)) {
|
|
1507
2206
|
if (m[1]) aliases.add(m[1]);
|
|
@@ -1519,7 +2218,7 @@ var PROVIDER_SDKS = {
|
|
|
1519
2218
|
};
|
|
1520
2219
|
async function readBlueprint(dir) {
|
|
1521
2220
|
try {
|
|
1522
|
-
return JSON.parse(await
|
|
2221
|
+
return JSON.parse(await readFile10(join11(dir, "blueprint.json"), "utf8"));
|
|
1523
2222
|
} catch {
|
|
1524
2223
|
return null;
|
|
1525
2224
|
}
|
|
@@ -1536,7 +2235,7 @@ function forbiddenFromBlueprint(bp) {
|
|
|
1536
2235
|
}
|
|
1537
2236
|
async function allPackageJsons(dir) {
|
|
1538
2237
|
const out = [];
|
|
1539
|
-
const root =
|
|
2238
|
+
const root = join11(dir, "package.json");
|
|
1540
2239
|
out.push(root);
|
|
1541
2240
|
async function walk2(d) {
|
|
1542
2241
|
let entries;
|
|
@@ -1547,18 +2246,18 @@ async function allPackageJsons(dir) {
|
|
|
1547
2246
|
}
|
|
1548
2247
|
for (const e of entries) {
|
|
1549
2248
|
if (e.isDirectory()) {
|
|
1550
|
-
if (!IGNORE_DIRS.has(e.name)) await walk2(
|
|
2249
|
+
if (!IGNORE_DIRS.has(e.name)) await walk2(join11(d, e.name));
|
|
1551
2250
|
} else if (e.name === "package.json") {
|
|
1552
|
-
out.push(
|
|
2251
|
+
out.push(join11(d, e.name));
|
|
1553
2252
|
}
|
|
1554
2253
|
}
|
|
1555
2254
|
}
|
|
1556
|
-
await walk2(
|
|
2255
|
+
await walk2(join11(dir, "apps"));
|
|
1557
2256
|
return out;
|
|
1558
2257
|
}
|
|
1559
2258
|
async function depKeys(pkgPath) {
|
|
1560
2259
|
try {
|
|
1561
|
-
const pkg = JSON.parse(await
|
|
2260
|
+
const pkg = JSON.parse(await readFile10(pkgPath, "utf8"));
|
|
1562
2261
|
return /* @__PURE__ */ new Set([
|
|
1563
2262
|
...Object.keys(pkg.dependencies ?? {}),
|
|
1564
2263
|
...Object.keys(pkg.devDependencies ?? {})
|
|
@@ -1572,7 +2271,7 @@ async function auditProject(dir, opts = {}) {
|
|
|
1572
2271
|
const violations = [];
|
|
1573
2272
|
const aliases = await tsconfigAliases(dir);
|
|
1574
2273
|
for (const file of await walk(dir)) {
|
|
1575
|
-
const src = await
|
|
2274
|
+
const src = await readFile10(file, "utf8");
|
|
1576
2275
|
for (const m of src.matchAll(ICORE_IMPORT)) {
|
|
1577
2276
|
const alias = m[1];
|
|
1578
2277
|
if (alias && !aliases.has(alias)) {
|