@idevconn/create-icore 0.9.3 → 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.
Files changed (37) hide show
  1. package/dist/cli.js +752 -190
  2. package/dist/index.cjs +775 -213
  3. package/dist/index.js +768 -206
  4. package/package.json +4 -4
  5. package/templates/.yarn/releases/{yarn-4.16.0.cjs → yarn-4.17.0.cjs} +326 -326
  6. package/templates/.yarnrc.yml +1 -1
  7. package/templates/apps/api/package.json +6 -6
  8. package/templates/apps/microservices/auth/package.json +4 -4
  9. package/templates/apps/microservices/jobs/package.json +5 -5
  10. package/templates/apps/microservices/notes/package.json +5 -5
  11. package/templates/apps/microservices/payment/package.json +4 -4
  12. package/templates/apps/microservices/upload/package.json +6 -6
  13. package/templates/apps/templates/client-antd/package.json +2 -2
  14. package/templates/apps/templates/client-mui/package.json +4 -4
  15. package/templates/apps/templates/client-shadcn/package.json +7 -7
  16. package/templates/apps/templates/client-shadcn/src/components/ui/button.tsx +1 -2
  17. package/templates/libs/auth-client/package.json +3 -3
  18. package/templates/libs/auth-strategies/firebase/package.json +4 -4
  19. package/templates/libs/auth-strategies/mongodb/package.json +4 -4
  20. package/templates/libs/auth-strategies/supabase/package.json +5 -5
  21. package/templates/libs/db-strategies/firestore/package.json +4 -4
  22. package/templates/libs/db-strategies/mongodb/package.json +3 -3
  23. package/templates/libs/db-strategies/supabase/package.json +5 -5
  24. package/templates/libs/firebase-admin/package.json +1 -1
  25. package/templates/libs/jobs-client/package.json +4 -4
  26. package/templates/libs/notes-client/package.json +3 -3
  27. package/templates/libs/payment-client/package.json +3 -3
  28. package/templates/libs/shared/package.json +2 -2
  29. package/templates/libs/storage-strategies/cloudinary/package.json +4 -4
  30. package/templates/libs/storage-strategies/firebase/package.json +4 -4
  31. package/templates/libs/storage-strategies/mongodb/package.json +3 -3
  32. package/templates/libs/storage-strategies/supabase/package.json +5 -5
  33. package/templates/libs/template-shared/package.json +8 -8
  34. package/templates/libs/upload-client/package.json +3 -3
  35. package/templates/libs/vite-plugins/package.json +3 -3
  36. package/templates/package.json +32 -32
  37. 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 mkdir2, readdir as readdir2, readFile as readFile6, rmdir as rmdir2, stat, writeFile as writeFile8, rm as rm4 } from "fs/promises";
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 join8 } from "path";
9
+ import { join as join9 } from "path";
10
10
  import { spawnSync } from "child_process";
11
11
 
12
12
  // src/lib/scaffold-env.ts
@@ -172,6 +172,9 @@ async function writeGatewayEnv(targetDir, opts) {
172
172
  for (const prefix of ["AUTH", "UPLOAD", "NOTES", "PAYMENT"]) {
173
173
  next = uncommentTransportEnv(next, prefix, opts.transport);
174
174
  }
175
+ if (opts.authProvider === "none") {
176
+ next = next.split("\n").filter((line) => !line.startsWith("AUTH_") && !line.startsWith("# AUTH_")).join("\n");
177
+ }
175
178
  await writeFile(join(targetDir, "apps/api/.env"), next);
176
179
  }
177
180
  async function writeRootEnv(targetDir, opts) {
@@ -257,6 +260,7 @@ async function removeStrategiesLib(targetDir) {
257
260
  await rm(join2(targetDir, "libs/shared/src/strategies"), { recursive: true, force: true });
258
261
  await rm(join2(targetDir, "libs/shared/src/testing.ts"), { force: true });
259
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 });
260
264
  const indexPath = join2(targetDir, "libs/shared/src/index.ts");
261
265
  try {
262
266
  const src = await readFile2(indexPath, "utf8");
@@ -275,122 +279,6 @@ async function removeStrategiesLib(targetDir) {
275
279
  } catch {
276
280
  }
277
281
  }
278
- async function removeAuthStack(targetDir) {
279
- const rmPaths = [
280
- "apps/microservices/auth",
281
- "libs/auth-strategies",
282
- "libs/auth-client",
283
- "Dockerfile.ms-auth",
284
- "apps/api/src/app/auth",
285
- "apps/api/src/app/profile",
286
- "apps/api/src/app/abilities",
287
- "libs/shared/src/abilities",
288
- "apps/client/src/components/auth",
289
- "apps/client/src/routes/login.tsx",
290
- "apps/client/src/routes/auth.callback.tsx",
291
- "apps/client/src/routes/auth.oauth.callback.tsx",
292
- "apps/client/src/routes/_dashboard/profile.tsx"
293
- ];
294
- for (const p2 of rmPaths) {
295
- await rm(join2(targetDir, p2), { recursive: true, force: true });
296
- }
297
- const appModulePath = join2(targetDir, "apps/api/src/app/app.module.ts");
298
- try {
299
- const src = await readFile2(appModulePath, "utf8");
300
- const next = src.replace(/^import \{ AuthModule \} from '\.\/auth\/auth\.module';\n/m, "").replace(/^import \{ ProfileModule \} from '\.\/profile\/profile\.module';\n/m, "").replace(/^import \{ AbilitiesModule \} from '\.\/abilities\/abilities\.module';\n/m, "").replace(/\bAuthModule,\s*/g, "").replace(/,\s*AuthModule\b/g, "").replace(/\bProfileModule,\s*/g, "").replace(/,\s*ProfileModule\b/g, "").replace(/\bAbilitiesModule,\s*/g, "").replace(/,\s*AbilitiesModule\b/g, "");
301
- await writeFile2(appModulePath, next);
302
- } catch {
303
- }
304
- const dashboardPath = join2(targetDir, "apps/client/src/routes/_dashboard.tsx");
305
- try {
306
- const src = await readFile2(dashboardPath, "utf8");
307
- const next = src.replace(/^import \{ useAuthStore \} from '@icore\/template-shared';\n/m, "").replace(/, redirect/g, "").replace(/\n {2}beforeLoad: \(\) => \{[\s\S]*?\n {2}\},/m, "");
308
- await writeFile2(dashboardPath, next);
309
- } catch {
310
- }
311
- for (const alias of [
312
- "@icore/auth-client",
313
- "@icore/auth-supabase",
314
- "@icore/auth-firebase",
315
- "@icore/auth-mongodb"
316
- ]) {
317
- await stripTsconfigPath(targetDir, alias);
318
- }
319
- await stripDeps(join2(targetDir, "apps/api/package.json"), [
320
- "@icore/auth-client",
321
- "cookie-parser"
322
- ]);
323
- const gatewayMainPath = join2(targetDir, "apps/api/src/main.ts");
324
- try {
325
- const src = await readFile2(gatewayMainPath, "utf8");
326
- const next = src.replace(/^import cookieParser from 'cookie-parser';\n/m, "").replace(/^\s*app\.use\(cookieParser\(\)\);\n/m, "");
327
- await writeFile2(gatewayMainPath, next);
328
- } catch {
329
- }
330
- const gatewayEnv = join2(targetDir, "apps/api/.env");
331
- try {
332
- const env = await readFile2(gatewayEnv, "utf8");
333
- const next = env.split("\n").filter((line) => !line.startsWith("AUTH_") && !line.startsWith("# AUTH_")).join("\n");
334
- await writeFile2(gatewayEnv, next);
335
- } catch {
336
- }
337
- const composePath = join2(targetDir, "docker-compose.yml");
338
- try {
339
- const compose = await readFile2(composePath, "utf8");
340
- 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, "");
341
- await writeFile2(composePath, next);
342
- } catch {
343
- }
344
- for (const rel of ["libs/shared/src/index.ts", "libs/shared/src/client.ts"]) {
345
- const sharedIndexPath = join2(targetDir, rel);
346
- try {
347
- const src = await readFile2(sharedIndexPath, "utf8");
348
- await writeFile2(sharedIndexPath, src.replace(/^export \* from '\.\/abilities';\n?/m, ""));
349
- } catch {
350
- }
351
- }
352
- const routesIndexPath = join2(targetDir, "apps/client/src/routes/index.tsx");
353
- try {
354
- const src = await readFile2(routesIndexPath, "utf8");
355
- await writeFile2(
356
- routesIndexPath,
357
- src.replace(/ctaHref="\/login"/, 'ctaHref="/dashboard"').replace(/ctaLabel="Log in →"/, 'ctaLabel="Dashboard \u2192"')
358
- );
359
- } catch {
360
- }
361
- const mainTsxPath = join2(targetDir, "apps/client/src/main.tsx");
362
- try {
363
- const src = await readFile2(mainTsxPath, "utf8");
364
- const next = src.replace(/\n {2}onUnauthorized: \(\) => router\.navigate\(\{ to: '\/login' \}\),/, "").replace(/^\s*AbilityProvider,\n/m, "").replace(/^\s*<AbilityProvider>\n/m, "").replace(/^\s*<\/AbilityProvider>\n/m, "");
365
- await writeFile2(mainTsxPath, next);
366
- } catch {
367
- }
368
- await rm(join2(targetDir, "libs/template-shared/src/lib/abilities"), {
369
- recursive: true,
370
- force: true
371
- });
372
- const templateSharedIndexPath = join2(targetDir, "libs/template-shared/src/index.ts");
373
- try {
374
- const src = await readFile2(templateSharedIndexPath, "utf8");
375
- await writeFile2(
376
- templateSharedIndexPath,
377
- src.replace(/^export \* from '\.\/lib\/abilities\/ability-provider\.js';\n/m, "")
378
- );
379
- } catch {
380
- }
381
- const headerPath = join2(targetDir, "apps/client/src/components/layout/LayoutHeader.tsx");
382
- try {
383
- const src = await readFile2(headerPath, "utf8");
384
- await writeFile2(
385
- headerPath,
386
- src.replace(/^import \{ useTranslation \} from 'react-i18next';\n/m, "").replace(/^import \{ useNavigate \} from '@tanstack\/react-router';\n/m, "").replace(/^import \{ LogOut \} from 'lucide-react';\n/m, "").replace(/^import \{ Button \} from '\.\.\/ui\/button';\n/m, "").replace(
387
- /import \{ useAuthStore, setStoredLocale, type IcoreLocale \} from '@icore\/template-shared';/,
388
- "import { setStoredLocale, type IcoreLocale } from '@icore/template-shared';"
389
- ).replace(/^ {2}const \{ t \} = useTranslation\(\);\n/m, "").replace(/^ {2}const navigate = useNavigate\(\);\n/m, "").replace(/^ {2}const user = useAuthStore\(\(s\) => s\.user\);\n/m, "").replace(/^ {2}const logout = useAuthStore\(\(s\) => s\.logout\);\n/m, "").replace(/\n {2}function handleLogout\(\) \{[\s\S]*?\n {2}\}\n(?=\n {2}return)/m, "\n").replace(/\n {8}<div className="hidden sm:flex[\s\S]*?\n {8}<\/div>\n/m, "\n").replace(/\n {8}<Button[\s\S]*?sm:hidden[\s\S]*?\n {8}<\/Button>\n/m, "\n")
390
- );
391
- } catch {
392
- }
393
- }
394
282
  async function removeUploadStack(targetDir) {
395
283
  const paths = [
396
284
  "apps/microservices/upload",
@@ -432,9 +320,674 @@ async function removeUploadStack(targetDir) {
432
320
  }
433
321
  }
434
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
+ }
986
+ }
987
+
435
988
  // src/manifest/wire-features.ts
436
- import { readFile as readFile4, writeFile as writeFile4, rm as rm3, rmdir, unlink } from "fs/promises";
437
- import { join as join4 } from "path";
989
+ import { readFile as readFile5, writeFile as writeFile5, rm as rm4, rmdir, unlink } from "fs/promises";
990
+ import { join as join5 } from "path";
438
991
 
439
992
  // src/manifest/index.ts
440
993
  var EMPTY = { libDirs: [], deps: {}, tsPaths: {} };
@@ -594,8 +1147,8 @@ var MANIFEST = {
594
1147
  };
595
1148
 
596
1149
  // src/manifest/wire-provider.ts
597
- import { readFile as readFile3, writeFile as writeFile3, rm as rm2 } from "fs/promises";
598
- import { join as join3 } from "path";
1150
+ import { readFile as readFile4, writeFile as writeFile4, rm as rm3 } from "fs/promises";
1151
+ import { join as join4 } from "path";
599
1152
  async function writeProvider(targetDir, axis, provider) {
600
1153
  const nestModule = axis.section[provider]?.nestModule;
601
1154
  if (!nestModule) throw new Error(`provider "${provider}" has no nestModule in the manifest`);
@@ -606,27 +1159,27 @@ const ENV_PATH = '${axis.envPath}';
606
1159
 
607
1160
  export const ${axis.exportConst} = ${symbol}.forRoot(ENV_PATH);
608
1161
  `;
609
- await writeFile3(join3(targetDir, axis.providerFile), content);
1162
+ await writeFile4(join4(targetDir, axis.providerFile), content);
610
1163
  }
611
1164
  async function stripJsonKeys(path, drop) {
612
1165
  try {
613
- const pkg = JSON.parse(await readFile3(path, "utf8"));
1166
+ const pkg = JSON.parse(await readFile4(path, "utf8"));
614
1167
  for (const field of ["dependencies", "devDependencies"]) {
615
1168
  const deps = pkg[field];
616
1169
  if (!deps) continue;
617
1170
  for (const k of Object.keys(deps)) if (drop(k)) delete deps[k];
618
1171
  }
619
- await writeFile3(path, JSON.stringify(pkg, null, 2) + "\n");
1172
+ await writeFile4(path, JSON.stringify(pkg, null, 2) + "\n");
620
1173
  } catch {
621
1174
  }
622
1175
  }
623
1176
  async function stripTsconfigKeys(targetDir, aliases) {
624
- const path = join3(targetDir, "tsconfig.base.json");
1177
+ const path = join4(targetDir, "tsconfig.base.json");
625
1178
  try {
626
- const parsed = JSON.parse(await readFile3(path, "utf8"));
1179
+ const parsed = JSON.parse(await readFile4(path, "utf8"));
627
1180
  const paths = parsed.compilerOptions?.paths;
628
1181
  if (paths) for (const a of aliases) delete paths[a];
629
- await writeFile3(path, JSON.stringify(parsed, null, 2) + "\n");
1182
+ await writeFile4(path, JSON.stringify(parsed, null, 2) + "\n");
630
1183
  } catch {
631
1184
  }
632
1185
  }
@@ -635,10 +1188,10 @@ async function cleanupUnusedAxis(targetDir, axis, chosen) {
635
1188
  if (provider === chosen) continue;
636
1189
  const unit = axis.section[provider];
637
1190
  for (const dir of unit.libDirs)
638
- await rm2(join3(targetDir, dir), { recursive: true, force: true });
639
- for (const t of unit.appTests ?? []) await rm2(join3(targetDir, t), { force: true });
1191
+ await rm3(join4(targetDir, dir), { recursive: true, force: true });
1192
+ for (const t of unit.appTests ?? []) await rm3(join4(targetDir, t), { force: true });
640
1193
  const dropKeys = /* @__PURE__ */ new Set([...Object.keys(unit.tsPaths), ...Object.keys(unit.deps)]);
641
- await stripJsonKeys(join3(targetDir, axis.msPackageJson), (k) => dropKeys.has(k));
1194
+ await stripJsonKeys(join4(targetDir, axis.msPackageJson), (k) => dropKeys.has(k));
642
1195
  await stripTsconfigKeys(targetDir, Object.keys(unit.tsPaths));
643
1196
  }
644
1197
  }
@@ -667,7 +1220,7 @@ async function writeFeaturesWiring(targetDir, opts) {
667
1220
  })
668
1221
  export class FeaturesModule {}
669
1222
  `;
670
- await writeFile4(join4(targetDir, FEATURES_MODULE), featuresModule);
1223
+ await writeFile5(join5(targetDir, FEATURES_MODULE), featuresModule);
671
1224
  const services = [];
672
1225
  if (opts.authProvider !== "none") services.push({ name: "auth", prefix: "AUTH" });
673
1226
  if (opts.upload !== "none") services.push({ name: "upload", prefix: "UPLOAD" });
@@ -681,14 +1234,14 @@ export const GATEWAY_SERVICES = [
681
1234
  ${entries}
682
1235
  ];
683
1236
  `;
684
- await writeFile4(join4(targetDir, GATEWAY_SERVICES), gatewayServices);
1237
+ await writeFile5(join5(targetDir, GATEWAY_SERVICES), gatewayServices);
685
1238
  }
686
1239
  async function stripJobsDockerCompose(targetDir) {
687
- const composePath = join4(targetDir, "docker-compose.yml");
1240
+ const composePath = join5(targetDir, "docker-compose.yml");
688
1241
  try {
689
- const compose = await readFile4(composePath, "utf8");
1242
+ const compose = await readFile5(composePath, "utf8");
690
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, "");
691
- await writeFile4(composePath, next);
1244
+ await writeFile5(composePath, next);
692
1245
  } catch {
693
1246
  }
694
1247
  }
@@ -698,34 +1251,38 @@ async function cleanupUnusedFeatures(targetDir, opts) {
698
1251
  if (chosen.has(key)) continue;
699
1252
  const unit = FEATURES[key];
700
1253
  for (const dir of unit.libDirs)
701
- await rm3(join4(targetDir, dir), { recursive: true, force: true });
1254
+ await rm4(join5(targetDir, dir), { recursive: true, force: true });
702
1255
  const dropKeys = /* @__PURE__ */ new Set([...Object.keys(unit.tsPaths), ...Object.keys(unit.deps)]);
703
- await stripJsonKeys(join4(targetDir, API_PKG), (k) => dropKeys.has(k));
1256
+ await stripJsonKeys(join5(targetDir, API_PKG), (k) => dropKeys.has(k));
704
1257
  await stripTsconfigKeys(targetDir, Object.keys(unit.tsPaths));
705
1258
  if (unit.gatewayService) await stripGatewayTransport(targetDir, unit.gatewayService.prefix);
706
1259
  if (unit.dockerService === "jobs") await stripJobsDockerCompose(targetDir);
707
1260
  if (key === "jobs") {
708
1261
  try {
709
- await unlink(join4(targetDir, "libs/shared/src/jobs.ts"));
1262
+ await unlink(join5(targetDir, "libs/shared/src/jobs.ts"));
710
1263
  } catch {
711
1264
  }
712
- const sharedIdx = join4(targetDir, "libs/shared/src/index.ts");
713
1265
  try {
714
- const src = await readFile4(sharedIdx, "utf8");
715
- await writeFile4(sharedIdx, src.replace(/^export \* from '\.\/jobs';\n/m, ""));
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, ""));
716
1273
  } catch {
717
1274
  }
718
1275
  }
719
1276
  }
720
1277
  try {
721
- await rmdir(join4(targetDir, "apps/client/src/queries"));
1278
+ await rmdir(join5(targetDir, "apps/client/src/queries"));
722
1279
  } catch {
723
1280
  }
724
1281
  }
725
1282
 
726
1283
  // src/manifest/wire-client.ts
727
- import { writeFile as writeFile5 } from "fs/promises";
728
- import { join as join5 } from "path";
1284
+ import { writeFile as writeFile6 } from "fs/promises";
1285
+ import { join as join6 } from "path";
729
1286
  var NAV_CONFIG_FILE = "apps/client/src/nav.config.ts";
730
1287
  var BASE_NAV = [
731
1288
  { to: "/dashboard", labelKey: "nav.dashboard", iconName: "dashboard", exact: true }
@@ -763,12 +1320,12 @@ export const NAV_CONFIG: NavItem[] = [
763
1320
  ` + entries.map(renderEntry).join("\n") + `
764
1321
  ];
765
1322
  `;
766
- await writeFile5(join5(targetDir, NAV_CONFIG_FILE), content);
1323
+ await writeFile6(join6(targetDir, NAV_CONFIG_FILE), content);
767
1324
  }
768
1325
 
769
1326
  // src/manifest/blueprint.ts
770
- import { writeFile as writeFile6 } from "fs/promises";
771
- import { join as join6 } from "path";
1327
+ import { writeFile as writeFile7 } from "fs/promises";
1328
+ import { join as join7 } from "path";
772
1329
  async function writeBlueprintJson(targetDir, opts) {
773
1330
  const blueprint = {
774
1331
  schemaVersion: 1,
@@ -783,10 +1340,10 @@ async function writeBlueprintJson(targetDir, opts) {
783
1340
  transport: opts.transport,
784
1341
  packageManager: opts.packageManager
785
1342
  };
786
- await writeFile6(join6(targetDir, "blueprint.json"), JSON.stringify(blueprint, null, 2) + "\n");
1343
+ await writeFile7(join7(targetDir, "blueprint.json"), JSON.stringify(blueprint, null, 2) + "\n");
787
1344
  }
788
1345
  async function writeJson(targetDir, rel, data) {
789
- await writeFile6(join6(targetDir, rel, "blueprint.json"), JSON.stringify(data, null, 2) + "\n");
1346
+ await writeFile7(join7(targetDir, rel, "blueprint.json"), JSON.stringify(data, null, 2) + "\n");
790
1347
  }
791
1348
  async function writeServiceBlueprints(targetDir, opts) {
792
1349
  const t = opts.transport;
@@ -880,11 +1437,11 @@ var writeDbProvider = (targetDir, provider) => writeProvider(targetDir, DB, prov
880
1437
  var cleanupUnusedDb = (targetDir, chosen) => cleanupUnusedAxis(targetDir, DB, chosen);
881
1438
 
882
1439
  // src/lib/scaffold-pkg.ts
883
- import { readFile as readFile5, writeFile as writeFile7, mkdir, readdir } from "fs/promises";
884
- import { join as join7 } from "path";
1440
+ import { readFile as readFile6, writeFile as writeFile8, mkdir as mkdir2, readdir } from "fs/promises";
1441
+ import { join as join8 } from "path";
885
1442
  async function writePnpmWorkspace(targetDir) {
886
- const pkgPath = join7(targetDir, "package.json");
887
- const pkg = JSON.parse(await readFile5(pkgPath, "utf8"));
1443
+ const pkgPath = join8(targetDir, "package.json");
1444
+ const pkg = JSON.parse(await readFile6(pkgPath, "utf8"));
888
1445
  const workspaces = pkg.workspaces ?? [];
889
1446
  const packagesBlock = workspaces.map((p2) => ` - '${p2}'`).join("\n");
890
1447
  const allowBuilds = [
@@ -907,7 +1464,7 @@ ${packagesBlock}
907
1464
  allowBuilds:
908
1465
  ${allowBuilds}
909
1466
  `;
910
- await writeFile7(join7(targetDir, "pnpm-workspace.yaml"), content);
1467
+ await writeFile8(join8(targetDir, "pnpm-workspace.yaml"), content);
911
1468
  }
912
1469
  async function rewritePnpmWorkspaceDeps(targetDir) {
913
1470
  async function walk2(dir) {
@@ -920,9 +1477,9 @@ async function rewritePnpmWorkspaceDeps(targetDir) {
920
1477
  }
921
1478
  for (const e of entries) {
922
1479
  if (e.isDirectory() && e.name !== "node_modules") {
923
- found.push(...await walk2(join7(dir, e.name)));
1480
+ found.push(...await walk2(join8(dir, e.name)));
924
1481
  } else if (e.isFile() && e.name === "package.json") {
925
- found.push(join7(dir, e.name));
1482
+ found.push(join8(dir, e.name));
926
1483
  }
927
1484
  }
928
1485
  return found;
@@ -930,17 +1487,17 @@ async function rewritePnpmWorkspaceDeps(targetDir) {
930
1487
  const pkgFiles = await walk2(targetDir);
931
1488
  for (const f of pkgFiles) {
932
1489
  try {
933
- const raw = await readFile5(f, "utf8");
1490
+ const raw = await readFile6(f, "utf8");
934
1491
  const next = raw.replace(/"(@icore\/[^"]+)":\s*"\*"/g, '"$1": "workspace:*"');
935
- if (next !== raw) await writeFile7(f, next);
1492
+ if (next !== raw) await writeFile8(f, next);
936
1493
  } catch {
937
1494
  }
938
1495
  }
939
1496
  }
940
1497
  async function patchGitignoreForPm(targetDir, pm) {
941
- const giPath = join7(targetDir, ".gitignore");
1498
+ const giPath = join8(targetDir, ".gitignore");
942
1499
  try {
943
- let src = await readFile5(giPath, "utf8");
1500
+ let src = await readFile6(giPath, "utf8");
944
1501
  src = src.replace(/^# Build artifacts.*\ntools\/create-icore\/templates\/\s*\n/m, "");
945
1502
  if (pm !== "yarn") {
946
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, "");
@@ -955,7 +1512,7 @@ async function patchGitignoreForPm(targetDir, pm) {
955
1512
  src += "\n# npm\nnpm-debug.log*\n";
956
1513
  }
957
1514
  }
958
- await writeFile7(giPath, src);
1515
+ await writeFile8(giPath, src);
959
1516
  } catch {
960
1517
  }
961
1518
  }
@@ -971,7 +1528,7 @@ async function writeAiFiles(targetDir, opts) {
971
1528
  if (opts.jobs !== "none") activeMSes.push(`jobs (standalone)`);
972
1529
  const usesSupabase = opts.authProvider === "supabase" || opts.dbProvider === "supabase" || opts.upload === "supabase";
973
1530
  const usesFirebase = opts.authProvider === "firebase" || opts.dbProvider === "firebase" || opts.upload === "firebase";
974
- await writeFile7(join7(targetDir, "CLAUDE.md"), "@AGENTS.md\n");
1531
+ await writeFile8(join8(targetDir, "CLAUDE.md"), "@AGENTS.md\n");
975
1532
  const uiLabel = { shadcn: "shadcn/ui + Tailwind", antd: "Ant Design 6", mui: "MUI 6" }[opts.ui];
976
1533
  const readme = `# ${opts.projectName}
977
1534
 
@@ -1019,7 +1576,7 @@ ${pm === "yarn" ? "yarn remove-notes" : pm === "pnpm" ? "pnpm remove-notes" : "n
1019
1576
 
1020
1577
  Apache-2.0
1021
1578
  `;
1022
- await writeFile7(join7(targetDir, "README.md"), readme);
1579
+ await writeFile8(join8(targetDir, "README.md"), readme);
1023
1580
  const agents = `# ${opts.projectName} \u2014 Agent Instructions
1024
1581
 
1025
1582
  ## Stack snapshot
@@ -1100,8 +1657,8 @@ ${opts.authProvider !== "none" ? `| \`apps/microservices/auth/.env\` | \`AUTH_PR
1100
1657
  - Test behaviour, not implementation. Fake strategies from \`@icore/shared\` (FakeAuthStrategy etc.) serve as test doubles.
1101
1658
  - Run: \`${nx} test <project>\`
1102
1659
  `;
1103
- await writeFile7(join7(targetDir, "AGENTS.md"), agents);
1104
- await mkdir(join7(targetDir, ".claude"), { recursive: true });
1660
+ await writeFile8(join8(targetDir, "AGENTS.md"), agents);
1661
+ await mkdir2(join8(targetDir, ".claude"), { recursive: true });
1105
1662
  const mcpServers = {
1106
1663
  nx: {
1107
1664
  command: "npx",
@@ -1142,8 +1699,8 @@ ${opts.authProvider !== "none" ? `| \`apps/microservices/auth/.env\` | \`AUTH_PR
1142
1699
  ]
1143
1700
  }
1144
1701
  };
1145
- await writeFile7(
1146
- join7(targetDir, ".claude", "settings.json"),
1702
+ await writeFile8(
1703
+ join8(targetDir, ".claude", "settings.json"),
1147
1704
  JSON.stringify(settings, null, 2) + "\n"
1148
1705
  );
1149
1706
  }
@@ -1163,20 +1720,20 @@ var IGNORE_TOP = /* @__PURE__ */ new Set([
1163
1720
  ".vscode"
1164
1721
  ]);
1165
1722
  async function copyTree(src, dest) {
1166
- await mkdir2(dest, { recursive: true });
1723
+ await mkdir3(dest, { recursive: true });
1167
1724
  const entries = await readdir2(src, { withFileTypes: true });
1168
1725
  for (const entry of entries) {
1169
1726
  if (IGNORE_TOP.has(entry.name)) continue;
1170
- const s = join8(src, entry.name);
1171
- const d = join8(dest, entry.name);
1727
+ const s = join9(src, entry.name);
1728
+ const d = join9(dest, entry.name);
1172
1729
  if (entry.isDirectory()) await copyTree(s, d);
1173
1730
  else if (entry.isFile()) await copyFile(s, d);
1174
1731
  }
1175
1732
  }
1176
1733
  async function selectClientTemplate(targetDir, opts) {
1177
- const templatesRoot = join8(targetDir, "apps/templates");
1178
- const chosen = join8(templatesRoot, `client-${opts.ui}`);
1179
- const destClient = join8(targetDir, "apps/client");
1734
+ const templatesRoot = join9(targetDir, "apps/templates");
1735
+ const chosen = join9(templatesRoot, `client-${opts.ui}`);
1736
+ const destClient = join9(targetDir, "apps/client");
1180
1737
  let chosenUi = opts.ui;
1181
1738
  try {
1182
1739
  const s = await stat(chosen);
@@ -1184,9 +1741,9 @@ async function selectClientTemplate(targetDir, opts) {
1184
1741
  await copyTree(chosen, destClient);
1185
1742
  } catch {
1186
1743
  chosenUi = "shadcn";
1187
- await copyTree(join8(templatesRoot, "client-shadcn"), destClient);
1744
+ await copyTree(join9(templatesRoot, "client-shadcn"), destClient);
1188
1745
  }
1189
- await rm4(templatesRoot, { recursive: true, force: true });
1746
+ await rm5(templatesRoot, { recursive: true, force: true });
1190
1747
  await rewriteClientPaths(destClient, chosenUi);
1191
1748
  }
1192
1749
  async function rewriteClientPaths(clientDir, ui) {
@@ -1199,11 +1756,11 @@ async function rewriteClientPaths(clientDir, ui) {
1199
1756
  "eslint.config.mjs"
1200
1757
  ];
1201
1758
  for (const rel of candidates) {
1202
- const path = join8(clientDir, rel);
1759
+ const path = join9(clientDir, rel);
1203
1760
  try {
1204
- const raw = await readFile6(path, "utf8");
1761
+ const raw = await readFile7(path, "utf8");
1205
1762
  const next = raw.replaceAll("../../../", "../../").replaceAll(`apps/templates/client-${ui}`, "apps/client").replaceAll(`client-${ui}`, "client");
1206
- if (next !== raw) await writeFile8(path, next);
1763
+ if (next !== raw) await writeFile9(path, next);
1207
1764
  } catch {
1208
1765
  }
1209
1766
  }
@@ -1219,12 +1776,12 @@ function gitInit(cwd, projectName) {
1219
1776
  }
1220
1777
  function resolveYarnBin(cwd) {
1221
1778
  try {
1222
- const yarnrc = readFileSync(join8(cwd, ".yarnrc.yml"), "utf8");
1779
+ const yarnrc = readFileSync(join9(cwd, ".yarnrc.yml"), "utf8");
1223
1780
  const match = yarnrc.match(/^yarnPath:\s*(.+)$/m);
1224
- if (match?.[1]) return join8(cwd, match[1].trim());
1781
+ if (match?.[1]) return join9(cwd, match[1].trim());
1225
1782
  } catch {
1226
1783
  }
1227
- return join8(cwd, ".yarn", "releases", "yarn-4.5.0.cjs");
1784
+ return join9(cwd, ".yarn", "releases", "yarn-4.5.0.cjs");
1228
1785
  }
1229
1786
  function runInstall(cwd, pm) {
1230
1787
  if (pm === "yarn") {
@@ -1247,6 +1804,10 @@ async function scaffold(rawOpts, templatesDir) {
1247
1804
  await writeRootEnv(opts.targetDir, opts);
1248
1805
  await selectClientTemplate(opts.targetDir, opts);
1249
1806
  await writeClientEnv(opts.targetDir);
1807
+ if (opts.authProvider === "none") {
1808
+ await removeAuthOnlyPaths(opts.targetDir);
1809
+ await applyAuthNoneVariants(opts.targetDir, opts.ui);
1810
+ }
1250
1811
  if (opts.upload === "none") await removeUploadStack(opts.targetDir);
1251
1812
  await cleanupUnusedFeatures(opts.targetDir, opts);
1252
1813
  await writeFeaturesWiring(opts.targetDir, opts);
@@ -1255,7 +1816,8 @@ async function scaffold(rawOpts, templatesDir) {
1255
1816
  await cleanupUnusedAuth(opts.targetDir, opts.authProvider);
1256
1817
  await writeAuthProvider(opts.targetDir, opts.authProvider);
1257
1818
  } else {
1258
- await removeAuthStack(opts.targetDir);
1819
+ await removeAuthTsconfigPaths(opts.targetDir);
1820
+ await removeDockerComposeAuthService(opts.targetDir);
1259
1821
  }
1260
1822
  if (opts.upload !== "none") {
1261
1823
  await cleanupUnusedStorage(opts.targetDir, opts.upload);
@@ -1268,20 +1830,20 @@ async function scaffold(rawOpts, templatesDir) {
1268
1830
  await writeDbProvider(opts.targetDir, opts.dbProvider);
1269
1831
  }
1270
1832
  await pruneRootProviderDeps(opts.targetDir, opts);
1271
- if (opts.authProvider === "none" && opts.upload === "none" && opts.dbProvider === "none") {
1833
+ if (opts.authProvider === "none" && opts.upload === "none" && opts.dbProvider === "none" && opts.payment === "none") {
1272
1834
  await removeStrategiesLib(opts.targetDir);
1273
1835
  }
1274
1836
  try {
1275
- await rmdir2(join8(opts.targetDir, "apps/microservices"));
1837
+ await rmdir2(join9(opts.targetDir, "apps/microservices"));
1276
1838
  } catch {
1277
1839
  }
1278
1840
  await writeBlueprintJson(opts.targetDir, opts);
1279
1841
  await writeServiceBlueprints(opts.targetDir, opts);
1280
1842
  if (opts.packageManager === "yarn") {
1281
- await writeFile8(join8(opts.targetDir, "yarn.lock"), "");
1843
+ await writeFile9(join9(opts.targetDir, "yarn.lock"), "");
1282
1844
  } else {
1283
- await rm4(join8(opts.targetDir, ".yarn"), { recursive: true, force: true });
1284
- await rm4(join8(opts.targetDir, ".yarnrc.yml"), { force: true });
1845
+ await rm5(join9(opts.targetDir, ".yarn"), { recursive: true, force: true });
1846
+ await rm5(join9(opts.targetDir, ".yarnrc.yml"), { force: true });
1285
1847
  }
1286
1848
  if (opts.packageManager === "pnpm") {
1287
1849
  await writePnpmWorkspace(opts.targetDir);
@@ -1296,12 +1858,12 @@ async function scaffold(rawOpts, templatesDir) {
1296
1858
  // src/lib/prompts.ts
1297
1859
  import * as p from "@clack/prompts";
1298
1860
  import { resolve } from "path";
1299
- import { readFile as readFile8 } from "fs/promises";
1300
- import { dirname, join as join9 } from "path";
1861
+ import { readFile as readFile9 } from "fs/promises";
1862
+ import { dirname as dirname2, join as join10 } from "path";
1301
1863
  import { fileURLToPath } from "url";
1302
1864
 
1303
1865
  // src/lib/config.ts
1304
- import { readFile as readFile7 } from "fs/promises";
1866
+ import { readFile as readFile8 } from "fs/promises";
1305
1867
  var ConfigFileError = class extends Error {
1306
1868
  constructor(message) {
1307
1869
  super(message);
@@ -1372,7 +1934,7 @@ function validateConfig(raw) {
1372
1934
  async function loadConfig(filePath) {
1373
1935
  let raw;
1374
1936
  try {
1375
- raw = await readFile7(filePath, "utf8");
1937
+ raw = await readFile8(filePath, "utf8");
1376
1938
  } catch {
1377
1939
  throw new ConfigFileError(`config file not found: ${filePath}`);
1378
1940
  }
@@ -1397,8 +1959,8 @@ function detectPackageManager() {
1397
1959
  }
1398
1960
  async function readSelfVersion() {
1399
1961
  try {
1400
- const here = dirname(fileURLToPath(import.meta.url));
1401
- const pkgRaw = await readFile8(join9(here, "..", "package.json"), "utf8");
1962
+ const here = dirname2(fileURLToPath(import.meta.url));
1963
+ const pkgRaw = await readFile9(join10(here, "..", "package.json"), "utf8");
1402
1964
  const pkg = JSON.parse(pkgRaw);
1403
1965
  return pkg.version ?? null;
1404
1966
  } catch {
@@ -1622,23 +2184,23 @@ Re-run with @latest to refresh:
1622
2184
  }
1623
2185
 
1624
2186
  // src/manifest/audit.ts
1625
- import { readFile as readFile9, readdir as readdir3 } from "fs/promises";
1626
- import { join as join10 } from "path";
2187
+ import { readFile as readFile10, readdir as readdir3 } from "fs/promises";
2188
+ import { join as join11 } from "path";
1627
2189
  var IGNORE_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", ".nx"]);
1628
2190
  async function walk(dir, out = []) {
1629
2191
  const entries = await readdir3(dir, { withFileTypes: true });
1630
2192
  for (const e of entries) {
1631
2193
  if (e.isDirectory()) {
1632
- if (!IGNORE_DIRS.has(e.name)) await walk(join10(dir, e.name), out);
2194
+ if (!IGNORE_DIRS.has(e.name)) await walk(join11(dir, e.name), out);
1633
2195
  } else if (/\.(ts|tsx|mjs)$/.test(e.name)) {
1634
- out.push(join10(dir, e.name));
2196
+ out.push(join11(dir, e.name));
1635
2197
  }
1636
2198
  }
1637
2199
  return out;
1638
2200
  }
1639
2201
  async function tsconfigAliases(dir) {
1640
2202
  try {
1641
- const raw = await readFile9(join10(dir, "tsconfig.base.json"), "utf8");
2203
+ const raw = await readFile10(join11(dir, "tsconfig.base.json"), "utf8");
1642
2204
  const aliases = /* @__PURE__ */ new Set();
1643
2205
  for (const m of raw.matchAll(/"(@icore\/[a-z0-9.-]+)"\s*:/g)) {
1644
2206
  if (m[1]) aliases.add(m[1]);
@@ -1656,7 +2218,7 @@ var PROVIDER_SDKS = {
1656
2218
  };
1657
2219
  async function readBlueprint(dir) {
1658
2220
  try {
1659
- return JSON.parse(await readFile9(join10(dir, "blueprint.json"), "utf8"));
2221
+ return JSON.parse(await readFile10(join11(dir, "blueprint.json"), "utf8"));
1660
2222
  } catch {
1661
2223
  return null;
1662
2224
  }
@@ -1673,7 +2235,7 @@ function forbiddenFromBlueprint(bp) {
1673
2235
  }
1674
2236
  async function allPackageJsons(dir) {
1675
2237
  const out = [];
1676
- const root = join10(dir, "package.json");
2238
+ const root = join11(dir, "package.json");
1677
2239
  out.push(root);
1678
2240
  async function walk2(d) {
1679
2241
  let entries;
@@ -1684,18 +2246,18 @@ async function allPackageJsons(dir) {
1684
2246
  }
1685
2247
  for (const e of entries) {
1686
2248
  if (e.isDirectory()) {
1687
- if (!IGNORE_DIRS.has(e.name)) await walk2(join10(d, e.name));
2249
+ if (!IGNORE_DIRS.has(e.name)) await walk2(join11(d, e.name));
1688
2250
  } else if (e.name === "package.json") {
1689
- out.push(join10(d, e.name));
2251
+ out.push(join11(d, e.name));
1690
2252
  }
1691
2253
  }
1692
2254
  }
1693
- await walk2(join10(dir, "apps"));
2255
+ await walk2(join11(dir, "apps"));
1694
2256
  return out;
1695
2257
  }
1696
2258
  async function depKeys(pkgPath) {
1697
2259
  try {
1698
- const pkg = JSON.parse(await readFile9(pkgPath, "utf8"));
2260
+ const pkg = JSON.parse(await readFile10(pkgPath, "utf8"));
1699
2261
  return /* @__PURE__ */ new Set([
1700
2262
  ...Object.keys(pkg.dependencies ?? {}),
1701
2263
  ...Object.keys(pkg.devDependencies ?? {})
@@ -1709,7 +2271,7 @@ async function auditProject(dir, opts = {}) {
1709
2271
  const violations = [];
1710
2272
  const aliases = await tsconfigAliases(dir);
1711
2273
  for (const file of await walk(dir)) {
1712
- const src = await readFile9(file, "utf8");
2274
+ const src = await readFile10(file, "utf8");
1713
2275
  for (const m of src.matchAll(ICORE_IMPORT)) {
1714
2276
  const alias = m[1];
1715
2277
  if (alias && !aliases.has(alias)) {