@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.
Files changed (39) hide show
  1. package/dist/cli.js +832 -130
  2. package/dist/index.cjs +848 -149
  3. package/dist/index.d.cts +1 -1
  4. package/dist/index.d.ts +1 -1
  5. package/dist/index.js +841 -142
  6. package/package.json +4 -4
  7. package/templates/.yarn/releases/{yarn-4.16.0.cjs → yarn-4.17.0.cjs} +326 -326
  8. package/templates/.yarnrc.yml +1 -1
  9. package/templates/apps/api/package.json +6 -6
  10. package/templates/apps/microservices/auth/package.json +4 -4
  11. package/templates/apps/microservices/jobs/package.json +5 -5
  12. package/templates/apps/microservices/notes/package.json +5 -5
  13. package/templates/apps/microservices/payment/package.json +4 -4
  14. package/templates/apps/microservices/upload/package.json +6 -6
  15. package/templates/apps/templates/client-antd/package.json +2 -2
  16. package/templates/apps/templates/client-mui/package.json +4 -4
  17. package/templates/apps/templates/client-shadcn/package.json +7 -7
  18. package/templates/apps/templates/client-shadcn/src/components/ui/button.tsx +1 -2
  19. package/templates/libs/auth-client/package.json +3 -3
  20. package/templates/libs/auth-strategies/firebase/package.json +4 -4
  21. package/templates/libs/auth-strategies/mongodb/package.json +4 -4
  22. package/templates/libs/auth-strategies/supabase/package.json +5 -5
  23. package/templates/libs/db-strategies/firestore/package.json +4 -4
  24. package/templates/libs/db-strategies/mongodb/package.json +3 -3
  25. package/templates/libs/db-strategies/supabase/package.json +5 -5
  26. package/templates/libs/firebase-admin/package.json +1 -1
  27. package/templates/libs/jobs-client/package.json +4 -4
  28. package/templates/libs/notes-client/package.json +3 -3
  29. package/templates/libs/payment-client/package.json +3 -3
  30. package/templates/libs/shared/package.json +2 -2
  31. package/templates/libs/storage-strategies/cloudinary/package.json +4 -4
  32. package/templates/libs/storage-strategies/firebase/package.json +4 -4
  33. package/templates/libs/storage-strategies/mongodb/package.json +3 -3
  34. package/templates/libs/storage-strategies/supabase/package.json +5 -5
  35. package/templates/libs/template-shared/package.json +8 -8
  36. package/templates/libs/upload-client/package.json +3 -3
  37. package/templates/libs/vite-plugins/package.json +3 -3
  38. package/templates/package.json +32 -32
  39. package/templates/tools/create-icore/_template-shell/package.json +32 -32
package/dist/cli.js CHANGED
@@ -3,7 +3,7 @@
3
3
  // src/cli.ts
4
4
  import { existsSync } from "fs";
5
5
  import { fileURLToPath as fileURLToPath2 } from "url";
6
- import { dirname as dirname2, resolve as resolve2 } from "path";
6
+ import { dirname as dirname3, resolve as resolve2 } from "path";
7
7
  import kleur from "kleur";
8
8
  import * as p2 from "@clack/prompts";
9
9
 
@@ -336,9 +336,9 @@ Re-run with @latest to refresh:
336
336
  }
337
337
 
338
338
  // src/lib/scaffold.ts
339
- import { copyFile, mkdir as mkdir2, readdir as readdir2, readFile as readFile8, stat, writeFile as writeFile8, rm as rm4 } from "fs/promises";
339
+ import { copyFile, mkdir as mkdir3, readdir as readdir2, readFile as readFile9, rmdir as rmdir2, stat, writeFile as writeFile9, rm as rm5 } from "fs/promises";
340
340
  import { readFileSync } from "fs";
341
- import { join as join9 } from "path";
341
+ import { join as join10 } from "path";
342
342
  import { spawnSync } from "child_process";
343
343
 
344
344
  // src/lib/scaffold-env.ts
@@ -439,6 +439,30 @@ async function rewriteRootPackageJson(targetDir, opts) {
439
439
  }
440
440
  }
441
441
  delete pkg.pnpm;
442
+ const noMs = opts.authProvider === "none" && opts.upload === "none" && opts.payment === "none" && opts.jobs === "none" && opts.example === "none";
443
+ const ws = pkg.workspaces;
444
+ if (ws) {
445
+ pkg.workspaces = ws.filter((entry) => {
446
+ if (entry === "apps/templates/*") return false;
447
+ if (entry === "apps/microservices/*" && noMs) return false;
448
+ if (entry === "libs/auth-strategies/*" && opts.authProvider === "none") return false;
449
+ if (entry === "libs/storage-strategies/*" && opts.upload === "none") return false;
450
+ if (entry === "libs/db-strategies/*" && opts.dbProvider === "none") return false;
451
+ return true;
452
+ });
453
+ }
454
+ const rootDeps = pkg.dependencies ?? {};
455
+ const rootDevDeps = pkg.devDependencies ?? {};
456
+ if (opts.authProvider === "none") {
457
+ delete rootDeps["cookie-parser"];
458
+ delete rootDevDeps["@types/cookie-parser"];
459
+ }
460
+ if (opts.upload === "none") {
461
+ delete rootDevDeps["@types/multer"];
462
+ }
463
+ if (noMs) {
464
+ delete rootDeps["@nestjs/microservices"];
465
+ }
442
466
  await writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
443
467
  }
444
468
  async function writeAuthEnv(targetDir, opts) {
@@ -480,6 +504,9 @@ async function writeGatewayEnv(targetDir, opts) {
480
504
  for (const prefix of ["AUTH", "UPLOAD", "NOTES", "PAYMENT"]) {
481
505
  next = uncommentTransportEnv(next, prefix, opts.transport);
482
506
  }
507
+ if (opts.authProvider === "none") {
508
+ next = next.split("\n").filter((line) => !line.startsWith("AUTH_") && !line.startsWith("# AUTH_")).join("\n");
509
+ }
483
510
  await writeFile(join2(targetDir, "apps/api/.env"), next);
484
511
  }
485
512
  async function writeRootEnv(targetDir, opts) {
@@ -561,59 +588,26 @@ async function removeFirebaseAdminLib(targetDir) {
561
588
  "@icore/firebase-admin"
562
589
  ]);
563
590
  }
564
- async function removeAuthStack(targetDir) {
565
- const rmPaths = [
566
- "apps/microservices/auth",
567
- "libs/auth-strategies",
568
- "libs/auth-client",
569
- "Dockerfile.ms-auth",
570
- "apps/api/src/app/auth",
571
- "apps/api/src/app/profile",
572
- "apps/api/src/app/abilities",
573
- "apps/client/src/components/auth",
574
- "apps/client/src/routes/login.tsx",
575
- "apps/client/src/routes/auth.callback.tsx",
576
- "apps/client/src/routes/auth.oauth.callback.tsx",
577
- "apps/client/src/routes/_dashboard/profile.tsx"
578
- ];
579
- for (const p3 of rmPaths) {
580
- await rm(join3(targetDir, p3), { recursive: true, force: true });
581
- }
582
- const appModulePath = join3(targetDir, "apps/api/src/app/app.module.ts");
583
- try {
584
- const src = await readFile4(appModulePath, "utf8");
585
- 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, "");
586
- await writeFile2(appModulePath, next);
587
- } catch {
588
- }
589
- const dashboardPath = join3(targetDir, "apps/client/src/routes/_dashboard.tsx");
591
+ async function removeStrategiesLib(targetDir) {
592
+ await rm(join3(targetDir, "libs/shared/src/strategies"), { recursive: true, force: true });
593
+ await rm(join3(targetDir, "libs/shared/src/testing.ts"), { force: true });
594
+ await rm(join3(targetDir, "libs/shared/src/transport.ts"), { force: true });
595
+ await rm(join3(targetDir, "libs/shared/src/__tests__/transport.unit.test.ts"), { force: true });
596
+ const indexPath = join3(targetDir, "libs/shared/src/index.ts");
590
597
  try {
591
- const src = await readFile4(dashboardPath, "utf8");
592
- const next = src.replace(/^import \{ useAuthStore \} from '@icore\/template-shared';\n/m, "").replace(/, redirect/g, "").replace(/\n {2}beforeLoad: \(\) => \{[\s\S]*?\n {2}\},/m, "");
593
- await writeFile2(dashboardPath, next);
594
- } catch {
595
- }
596
- for (const alias of [
597
- "@icore/auth-client",
598
- "@icore/auth-supabase",
599
- "@icore/auth-firebase",
600
- "@icore/auth-mongodb"
601
- ]) {
602
- await stripTsconfigPath(targetDir, alias);
603
- }
604
- await stripDeps(join3(targetDir, "apps/api/package.json"), ["@icore/auth-client"]);
605
- const gatewayEnv = join3(targetDir, "apps/api/.env");
606
- try {
607
- const env = await readFile4(gatewayEnv, "utf8");
608
- const next = env.split("\n").filter((line) => !line.startsWith("AUTH_") && !line.startsWith("# AUTH_")).join("\n");
609
- await writeFile2(gatewayEnv, next);
598
+ const src = await readFile4(indexPath, "utf8");
599
+ await writeFile2(
600
+ indexPath,
601
+ src.replace(/^export \* from '\.\/strategies';\n/m, "").replace(/^export \* from '\.\/transport';\n?/m, "")
602
+ );
610
603
  } catch {
611
604
  }
612
- const composePath = join3(targetDir, "docker-compose.yml");
605
+ const pkgPath = join3(targetDir, "libs/shared/package.json");
613
606
  try {
614
- const compose = await readFile4(composePath, "utf8");
615
- 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, "");
616
- await writeFile2(composePath, next);
607
+ const pkg = JSON.parse(await readFile4(pkgPath, "utf8"));
608
+ if (pkg.exports) delete pkg.exports["./testing"];
609
+ if (pkg.dependencies) delete pkg.dependencies["@nestjs/microservices"];
610
+ await writeFile2(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
617
611
  } catch {
618
612
  }
619
613
  }
@@ -623,7 +617,8 @@ async function removeUploadStack(targetDir) {
623
617
  "apps/microservices/upload-e2e",
624
618
  "libs/storage-strategies",
625
619
  "libs/upload-client",
626
- "apps/api/src/app/storage"
620
+ "apps/api/src/app/storage",
621
+ "Dockerfile.ms-upload"
627
622
  ];
628
623
  for (const p3 of paths) {
629
624
  await rm(join3(targetDir, p3), { recursive: true, force: true });
@@ -648,11 +643,683 @@ async function removeUploadStack(targetDir) {
648
643
  "@icore/upload-client",
649
644
  "@types/multer"
650
645
  ]);
646
+ const uploadComposePath = join3(targetDir, "docker-compose.yml");
647
+ try {
648
+ const compose = await readFile4(uploadComposePath, "utf8");
649
+ 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, "");
650
+ await writeFile2(uploadComposePath, next);
651
+ } catch {
652
+ }
653
+ }
654
+
655
+ // src/lib/scaffold-auth-none.ts
656
+ import { mkdir, rm as rm2, readFile as readFile5, writeFile as writeFile3 } from "fs/promises";
657
+ import { dirname as dirname2, join as join4 } from "path";
658
+ async function stripDeps2(pkgPath, names) {
659
+ try {
660
+ const raw = await readFile5(pkgPath, "utf8");
661
+ const pkg = JSON.parse(raw);
662
+ for (const n of names) {
663
+ if (pkg.dependencies) delete pkg.dependencies[n];
664
+ if (pkg.devDependencies) delete pkg.devDependencies[n];
665
+ }
666
+ await writeFile3(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
667
+ } catch {
668
+ }
669
+ }
670
+ var AUTH_ONLY_PATHS = [
671
+ "apps/microservices/auth",
672
+ "libs/auth-strategies",
673
+ "libs/auth-client",
674
+ "Dockerfile.ms-auth",
675
+ "apps/api/src/app/auth",
676
+ "apps/api/src/app/profile",
677
+ "apps/api/src/app/abilities",
678
+ "libs/shared/src/abilities",
679
+ "apps/client/src/components/auth",
680
+ "apps/client/src/routes/login.tsx",
681
+ "apps/client/src/routes/auth.callback.tsx",
682
+ "apps/client/src/routes/auth.oauth.callback.tsx",
683
+ "apps/client/src/routes/_dashboard/profile.tsx",
684
+ "libs/template-shared/src/lib/abilities"
685
+ ];
686
+ async function removeAuthOnlyPaths(targetDir) {
687
+ for (const p3 of AUTH_ONLY_PATHS) {
688
+ await rm2(join4(targetDir, p3), { recursive: true, force: true });
689
+ }
690
+ await stripDeps2(join4(targetDir, "apps/api/package.json"), [
691
+ "@icore/auth-client",
692
+ "cookie-parser"
693
+ ]);
694
+ }
695
+ async function stripTsconfigPath2(targetDir, alias) {
696
+ const tsconfigPath = join4(targetDir, "tsconfig.base.json");
697
+ try {
698
+ const src = await readFile5(tsconfigPath, "utf8");
699
+ const escaped = alias.replace(/[@/]/g, (c) => c === "@" ? "@" : "\\/");
700
+ const pretty = src.replace(new RegExp(`^\\s*"${escaped}": \\[[^\\]]*\\],?\\n`, "m"), "");
701
+ if (pretty !== src) {
702
+ await writeFile3(tsconfigPath, pretty);
703
+ return;
704
+ }
705
+ const parsed = JSON.parse(src);
706
+ if (parsed.compilerOptions?.paths) delete parsed.compilerOptions.paths[alias];
707
+ await writeFile3(tsconfigPath, JSON.stringify(parsed));
708
+ } catch {
709
+ }
710
+ }
711
+ async function removeAuthTsconfigPaths(targetDir) {
712
+ for (const alias of [
713
+ "@icore/auth-client",
714
+ "@icore/auth-supabase",
715
+ "@icore/auth-firebase",
716
+ "@icore/auth-mongodb"
717
+ ]) {
718
+ await stripTsconfigPath2(targetDir, alias);
719
+ }
720
+ }
721
+ async function removeDockerComposeAuthService(targetDir) {
722
+ const composePath = join4(targetDir, "docker-compose.yml");
723
+ try {
724
+ const compose = await readFile5(composePath, "utf8");
725
+ 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, "");
726
+ await writeFile3(composePath, next);
727
+ } catch {
728
+ }
729
+ }
730
+ var GATEWAY_MAIN_TS = `import { Logger } from '@nestjs/common';
731
+ import { NestFactory } from '@nestjs/core';
732
+ import { NestExpressApplication } from '@nestjs/platform-express';
733
+ import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
734
+ import { formatGatewayBanner } from '@icore/shared';
735
+ import { AppModule } from './app/app.module';
736
+ import { GATEWAY_SERVICES } from './app/gateway-services';
737
+ import pkg from '@icore/package.json';
738
+
739
+ const DEFAULT_PORT = 3001;
740
+
741
+ async function bootstrap() {
742
+ const app = await NestFactory.create<NestExpressApplication>(AppModule);
743
+ app.setGlobalPrefix('api');
744
+
745
+ const swaggerConfig = new DocumentBuilder()
746
+ .setTitle('iCore API')
747
+ .setDescription('iCore Gateway HTTP surface')
748
+ .setVersion(pkg.version)
749
+ .addBearerAuth()
750
+ .build();
751
+ const document = SwaggerModule.createDocument(app, swaggerConfig);
752
+ SwaggerModule.setup('api/docs', app, document);
753
+
754
+ const port = Number(process.env.API_PORT ?? DEFAULT_PORT);
755
+ await app.listen(port);
756
+ }
757
+
758
+ bootstrap()
759
+ .then(() => {
760
+ const origin = process.env.API_ORIGIN ?? 'http://localhost';
761
+ const port = Number(process.env.API_PORT ?? DEFAULT_PORT);
762
+ new Logger('API-Bootstrap').log(
763
+ formatGatewayBanner({ port, origin, services: GATEWAY_SERVICES }),
764
+ );
765
+ })
766
+ .catch((err) => {
767
+ new Logger('API-Bootstrap').error(
768
+ 'Gateway bootstrap failed',
769
+ err instanceof Error ? err.stack : err,
770
+ );
771
+ process.exit(1);
772
+ });
773
+ `;
774
+ var GATEWAY_APP_MODULE_TS = `import { join } from 'node:path';
775
+ import { Module } from '@nestjs/common';
776
+ import { ConfigModule } from '@nestjs/config';
777
+ import { ThrottlerModule, seconds } from '@nestjs/throttler';
778
+ import { StorageModule } from './storage/storage.module';
779
+ import { FeaturesModule } from './features.module';
780
+
781
+ @Module({
782
+ imports: [
783
+ ConfigModule.forRoot({
784
+ isGlobal: true,
785
+ envFilePath: [join(process.cwd(), 'apps/api/.env'), join(process.cwd(), '.env')],
786
+ }),
787
+ ThrottlerModule.forRoot([{ name: 'auth-burst', ttl: seconds(60), limit: 10 }]),
788
+ StorageModule,
789
+ FeaturesModule,
790
+ ],
791
+ })
792
+ export class AppModule {}
793
+ `;
794
+ var SHARED_CLIENT_TS = `// Browser-safe subset of @icore/shared.
795
+ // Import from '@icore/shared/client' in client-side code to avoid pulling
796
+ // in NestJS / Node.js-only modules (transport, strategies, contracts).
797
+ export * from './types';
798
+ `;
799
+ var SHARED_INDEX_TS = `export * from './env';
800
+ export * from './bootstrap';
801
+ export * from './jobs';
802
+ export * from './strategies';
803
+ export * from './transport';
804
+ export * from './types';
805
+ `;
806
+ var TEMPLATE_SHARED_INDEX_TS = `export * from './lib/api/create-api.js';
807
+ export * from './lib/stores/auth.store.js';
808
+ export * from './lib/stores/loading.store.js';
809
+ export * from './lib/i18n/create-i18n.js';
810
+ export * from './lib/i18n/keys.js';
811
+ export * from './lib/notify/use-notify.js';
812
+ export * from './lib/draft/index.js';
813
+ export * from './lib/landing/LandingPage.js';
814
+ export * from './lib/stores/theme.store.js';
815
+ `;
816
+ var SHADCN_MAIN_TSX = `import './globals.css';
817
+ import { StrictMode } from 'react';
818
+ import { createRoot } from 'react-dom/client';
819
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
820
+ import { RouterProvider, createRouter } from '@tanstack/react-router';
821
+ import {
822
+ createIcoreApi,
823
+ createIcoreI18n,
824
+ ICORE_LOCALES,
825
+ useThemeStore,
826
+ } from '@icore/template-shared';
827
+ import { I18nextProvider } from 'react-i18next';
828
+ import { Toaster } from 'sonner';
829
+ import { routeTree } from './routeTree.gen';
830
+ import { wireShadcnNotifier } from './lib/notify';
831
+
832
+ const queryClient = new QueryClient({
833
+ defaultOptions: { queries: { retry: false, refetchOnWindowFocus: false } },
834
+ });
835
+
836
+ const router = createRouter({ routeTree, context: { queryClient } });
837
+
838
+ declare module '@tanstack/react-router' {
839
+ interface Register {
840
+ router: typeof router;
841
+ }
842
+ }
843
+
844
+ const i18n = createIcoreI18n({ resources: ICORE_LOCALES });
845
+
846
+ // Single shared API instance \u2014 used by every query in src/queries/
847
+ export const api = createIcoreApi({
848
+ baseUrl: import.meta.env.VITE_API_URL ?? '/api',
849
+ });
850
+
851
+ wireShadcnNotifier();
852
+
853
+ // Apply the theme class before React mounts so the first paint is correct
854
+ const applyTheme = (mode: 'light' | 'dark') => {
855
+ document.documentElement.classList.toggle('dark', mode === 'dark');
856
+ };
857
+ applyTheme(useThemeStore.getState().mode);
858
+ useThemeStore.subscribe((s) => applyTheme(s.mode));
859
+
860
+ createRoot(document.getElementById('root')!).render(
861
+ <StrictMode>
862
+ <I18nextProvider i18n={i18n}>
863
+ <QueryClientProvider client={queryClient}>
864
+ <RouterProvider router={router} />
865
+ <Toaster richColors />
866
+ </QueryClientProvider>
867
+ </I18nextProvider>
868
+ </StrictMode>,
869
+ );
870
+ `;
871
+ var SHADCN_DASHBOARD_TSX = `import { createFileRoute, Outlet } from '@tanstack/react-router';
872
+ import { MainLayout } from '../layouts/MainLayout';
873
+
874
+ export const Route = createFileRoute('/_dashboard')({
875
+ component: () => (
876
+ <MainLayout>
877
+ <Outlet />
878
+ </MainLayout>
879
+ ),
880
+ });
881
+ `;
882
+ var SHADCN_INDEX_TSX = `import { createFileRoute } from '@tanstack/react-router';
883
+ import { LandingPage } from '@icore/template-shared';
884
+
885
+ // All version strings are injected at build time by vite.config.mts
886
+ // (reads root package.json via fs.readFileSync so they stay accurate
887
+ // even when workspace packages are bumped independently).
888
+ const APP_VERSION = (import.meta.env.VITE_APP_VERSION as string | undefined) ?? '0.0.0-dev';
889
+
890
+ export const Route = createFileRoute('/')({
891
+ component: () => (
892
+ <LandingPage
893
+ coreVersion={APP_VERSION}
894
+ uiLibrary="shadcn"
895
+ deps={[
896
+ { name: 'react', version: (import.meta.env.VITE_DEP_REACT as string) ?? '?' },
897
+ { name: 'vite', version: (import.meta.env.VITE_DEP_VITE as string) ?? '?' },
898
+ { name: 'tailwindcss', version: (import.meta.env.VITE_DEP_TAILWINDCSS as string) ?? '?' },
899
+ {
900
+ name: '@tanstack/react-router',
901
+ version: (import.meta.env.VITE_DEP_TANSTACK_ROUTER as string) ?? '?',
902
+ },
903
+ {
904
+ name: '@tanstack/react-query',
905
+ version: (import.meta.env.VITE_DEP_TANSTACK_QUERY as string) ?? '?',
906
+ },
907
+ { name: 'zustand', version: (import.meta.env.VITE_DEP_ZUSTAND as string) ?? '?' },
908
+ { name: '@casl/ability', version: (import.meta.env.VITE_DEP_CASL as string) ?? '?' },
909
+ ]}
910
+ ctaHref="/dashboard"
911
+ ctaLabel="Dashboard \u2192"
912
+ />
913
+ ),
914
+ });
915
+ `;
916
+ var SHADCN_LAYOUT_HEADER_TSX = `import { setStoredLocale, type IcoreLocale } from '@icore/template-shared';
917
+ import { ThemeToggle } from '../ThemeToggle';
918
+
919
+ const LOCALES: { code: IcoreLocale; label: string }[] = [
920
+ { code: 'en', label: 'EN' },
921
+ { code: 'ru', label: 'RU' },
922
+ { code: 'he', label: 'HE' },
923
+ ];
924
+
925
+ export function LayoutHeader() {
926
+ function handleLocale(code: IcoreLocale) {
927
+ setStoredLocale(code);
928
+ window.location.reload();
929
+ }
930
+
931
+ return (
932
+ <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">
933
+ <div className="flex items-center gap-2">
934
+ <div className="flex h-6 w-6 items-center justify-center rounded bg-[--color-primary]">
935
+ <span className="text-xs font-bold text-[--color-primary-foreground]">i</span>
936
+ </div>
937
+ <span className="text-sm font-semibold tracking-tight">iCore</span>
938
+ </div>
939
+
940
+ <div className="flex items-center gap-1">
941
+ <div className="flex items-center rounded-md border border-[--color-border] overflow-hidden mr-2">
942
+ {LOCALES.map(({ code, label }) => (
943
+ <button
944
+ key={code}
945
+ type="button"
946
+ onClick={() => handleLocale(code)}
947
+ className="px-2.5 py-1 text-xs text-[--color-muted-foreground] hover:bg-[--color-muted] hover:text-[--color-foreground] transition-colors cursor-pointer"
948
+ >
949
+ {label}
950
+ </button>
951
+ ))}
952
+ </div>
953
+
954
+ <ThemeToggle />
955
+ </div>
956
+ </header>
957
+ );
958
+ }
959
+ `;
960
+ var ANTD_MAIN_TSX = `import './globals.less';
961
+ import { StrictMode } from 'react';
962
+ import { createRoot } from 'react-dom/client';
963
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
964
+ import { RouterProvider, createRouter } from '@tanstack/react-router';
965
+ import { ConfigProvider, App as AntApp, theme } from 'antd';
966
+ import { I18nextProvider } from 'react-i18next';
967
+ import {
968
+ createIcoreApi,
969
+ createIcoreI18n,
970
+ ICORE_LOCALES,
971
+ useThemeStore,
972
+ } from '@icore/template-shared';
973
+ import { routeTree } from './routeTree.gen';
974
+
975
+ const queryClient = new QueryClient({
976
+ defaultOptions: { queries: { retry: false, refetchOnWindowFocus: false } },
977
+ });
978
+
979
+ const router = createRouter({ routeTree, context: { queryClient } });
980
+
981
+ declare module '@tanstack/react-router' {
982
+ interface Register {
983
+ router: typeof router;
984
+ }
985
+ }
986
+
987
+ const i18n = createIcoreI18n({ resources: ICORE_LOCALES });
988
+
989
+ // Single shared API instance \u2014 used by every query in src/queries/
990
+ export const api = createIcoreApi({
991
+ baseUrl: import.meta.env.VITE_API_URL ?? '/api',
992
+ });
993
+
994
+ function Root() {
995
+ const mode = useThemeStore((s) => s.mode);
996
+ const algorithm = mode === 'dark' ? theme.darkAlgorithm : theme.defaultAlgorithm;
997
+ return (
998
+ <ConfigProvider theme={{ algorithm, token: { colorPrimary: '#22c55e', colorLink: '#22c55e' } }}>
999
+ <AntApp>
1000
+ <QueryClientProvider client={queryClient}>
1001
+ <RouterProvider router={router} />
1002
+ </QueryClientProvider>
1003
+ </AntApp>
1004
+ </ConfigProvider>
1005
+ );
1006
+ }
1007
+
1008
+ createRoot(document.getElementById('root')!).render(
1009
+ <StrictMode>
1010
+ <I18nextProvider i18n={i18n}>
1011
+ <Root />
1012
+ </I18nextProvider>
1013
+ </StrictMode>,
1014
+ );
1015
+ `;
1016
+ var ANTD_DASHBOARD_TSX = `import { createFileRoute, Outlet } from '@tanstack/react-router';
1017
+ import { MainLayout } from '../layouts/MainLayout';
1018
+
1019
+ export const Route = createFileRoute('/_dashboard')({
1020
+ component: () => (
1021
+ <MainLayout>
1022
+ <Outlet />
1023
+ </MainLayout>
1024
+ ),
1025
+ });
1026
+ `;
1027
+ var ANTD_INDEX_TSX = `import { createFileRoute, Link } from '@tanstack/react-router';
1028
+ import { LandingPage } from '@icore/template-shared';
1029
+
1030
+ // All version strings are injected at build time by vite.config.mts
1031
+ // (reads root package.json via fs.readFileSync so they stay accurate
1032
+ // even when workspace packages are bumped independently).
1033
+ const APP_VERSION = (import.meta.env.VITE_APP_VERSION as string | undefined) ?? '0.0.0-dev';
1034
+
1035
+ export const Route = createFileRoute('/')({
1036
+ component: () => (
1037
+ <LandingPage
1038
+ coreVersion={APP_VERSION}
1039
+ uiLibrary="antd"
1040
+ deps={[
1041
+ { name: 'react', version: (import.meta.env.VITE_DEP_REACT as string) ?? '?' },
1042
+ { name: 'antd', version: (import.meta.env.VITE_DEP_ANTD as string) ?? '?' },
1043
+ { name: 'vite', version: (import.meta.env.VITE_DEP_VITE as string) ?? '?' },
1044
+ {
1045
+ name: '@tanstack/react-router',
1046
+ version: (import.meta.env.VITE_DEP_TANSTACK_ROUTER as string) ?? '?',
1047
+ },
1048
+ {
1049
+ name: '@tanstack/react-query',
1050
+ version: (import.meta.env.VITE_DEP_TANSTACK_QUERY as string) ?? '?',
1051
+ },
1052
+ { name: 'zustand', version: (import.meta.env.VITE_DEP_ZUSTAND as string) ?? '?' },
1053
+ { name: '@casl/ability', version: (import.meta.env.VITE_DEP_CASL as string) ?? '?' },
1054
+ ]}
1055
+ ctaHref="/dashboard"
1056
+ ctaLabel={<Link to="/dashboard">Dashboard \u2192</Link>}
1057
+ />
1058
+ ),
1059
+ });
1060
+ `;
1061
+ var ANTD_LAYOUT_HEADER_TSX = `import { Button, Layout, Space } from 'antd';
1062
+ import { setStoredLocale, type IcoreLocale } from '@icore/template-shared';
1063
+ import { ThemeToggle } from '../ThemeToggle';
1064
+
1065
+ const APP_VERSION = (import.meta.env.VITE_APP_VERSION as string | undefined) ?? '0.0.0-dev';
1066
+
1067
+ const LOCALES: { code: IcoreLocale; label: string }[] = [
1068
+ { code: 'en', label: 'EN' },
1069
+ { code: 'ru', label: 'RU' },
1070
+ { code: 'he', label: 'HE' },
1071
+ ];
1072
+
1073
+ export function LayoutHeader() {
1074
+ function handleLocale(code: IcoreLocale) {
1075
+ setStoredLocale(code);
1076
+ window.location.reload();
1077
+ }
1078
+
1079
+ return (
1080
+ <Layout.Header
1081
+ style={{
1082
+ display: 'flex',
1083
+ alignItems: 'center',
1084
+ justifyContent: 'space-between',
1085
+ padding: '0 24px',
1086
+ }}
1087
+ >
1088
+ <Space>
1089
+ <span style={{ color: '#fff', fontWeight: 600, fontSize: 16 }}>iCore</span>
1090
+ <span
1091
+ style={{
1092
+ color: 'rgba(255,255,255,0.45)',
1093
+ fontSize: 11,
1094
+ background: 'rgba(255,255,255,0.1)',
1095
+ padding: '1px 6px',
1096
+ borderRadius: 4,
1097
+ }}
1098
+ >
1099
+ v{APP_VERSION}
1100
+ </span>
1101
+ </Space>
1102
+
1103
+ <Space size="middle">
1104
+ <Space size={4}>
1105
+ {LOCALES.map(({ code, label }) => (
1106
+ <Button
1107
+ key={code}
1108
+ size="small"
1109
+ type="text"
1110
+ style={{ color: 'rgba(255,255,255,0.65)' }}
1111
+ onClick={() => handleLocale(code)}
1112
+ >
1113
+ {label}
1114
+ </Button>
1115
+ ))}
1116
+ </Space>
1117
+
1118
+ <ThemeToggle />
1119
+ </Space>
1120
+ </Layout.Header>
1121
+ );
1122
+ }
1123
+ `;
1124
+ var MUI_MAIN_TSX = `import './globals.css';
1125
+ import { StrictMode, useMemo } from 'react';
1126
+ import { createRoot } from 'react-dom/client';
1127
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
1128
+ import { RouterProvider, createRouter } from '@tanstack/react-router';
1129
+ import { CssBaseline, ThemeProvider, createTheme } from '@mui/material';
1130
+ import { I18nextProvider } from 'react-i18next';
1131
+ import {
1132
+ createIcoreApi,
1133
+ createIcoreI18n,
1134
+ ICORE_LOCALES,
1135
+ useThemeStore,
1136
+ } from '@icore/template-shared';
1137
+ import { routeTree } from './routeTree.gen';
1138
+ import { wireMuiNotifier } from './lib/notify';
1139
+
1140
+ const queryClient = new QueryClient({
1141
+ defaultOptions: { queries: { retry: false, refetchOnWindowFocus: false } },
1142
+ });
1143
+
1144
+ const router = createRouter({ routeTree, context: { queryClient } });
1145
+
1146
+ declare module '@tanstack/react-router' {
1147
+ interface Register {
1148
+ router: typeof router;
1149
+ }
1150
+ }
1151
+
1152
+ const i18n = createIcoreI18n({ resources: ICORE_LOCALES });
1153
+
1154
+ export const api = createIcoreApi({
1155
+ baseUrl: import.meta.env.VITE_API_URL ?? '/api',
1156
+ });
1157
+
1158
+ wireMuiNotifier();
1159
+
1160
+ function Root() {
1161
+ const mode = useThemeStore((s) => s.mode);
1162
+ const theme = useMemo(
1163
+ () => createTheme({ palette: { mode, primary: { main: '#22c55e' } } }),
1164
+ [mode],
1165
+ );
1166
+ return (
1167
+ <ThemeProvider theme={theme}>
1168
+ <CssBaseline />
1169
+ <QueryClientProvider client={queryClient}>
1170
+ <RouterProvider router={router} />
1171
+ </QueryClientProvider>
1172
+ </ThemeProvider>
1173
+ );
1174
+ }
1175
+
1176
+ createRoot(document.getElementById('root')!).render(
1177
+ <StrictMode>
1178
+ <I18nextProvider i18n={i18n}>
1179
+ <Root />
1180
+ </I18nextProvider>
1181
+ </StrictMode>,
1182
+ );
1183
+ `;
1184
+ var MUI_DASHBOARD_TSX = `import { createFileRoute, Outlet } from '@tanstack/react-router';
1185
+ import { MainLayout } from '../layouts/MainLayout';
1186
+
1187
+ export const Route = createFileRoute('/_dashboard')({
1188
+ component: () => (
1189
+ <MainLayout>
1190
+ <Outlet />
1191
+ </MainLayout>
1192
+ ),
1193
+ });
1194
+ `;
1195
+ var MUI_INDEX_TSX = `import { createFileRoute, Link } from '@tanstack/react-router';
1196
+ import { LandingPage } from '@icore/template-shared';
1197
+
1198
+ // All version strings are injected at build time by vite.config.mts
1199
+ // (reads root package.json via fs.readFileSync so they stay accurate
1200
+ // even when workspace packages are bumped independently).
1201
+ const APP_VERSION = (import.meta.env.VITE_APP_VERSION as string | undefined) ?? '0.0.0-dev';
1202
+
1203
+ export const Route = createFileRoute('/')({
1204
+ component: () => (
1205
+ <LandingPage
1206
+ coreVersion={APP_VERSION}
1207
+ uiLibrary="mui"
1208
+ deps={[
1209
+ { name: 'react', version: (import.meta.env.VITE_DEP_REACT as string) ?? '?' },
1210
+ { name: '@mui/material', version: (import.meta.env.VITE_DEP_MUI as string) ?? '?' },
1211
+ { name: 'vite', version: (import.meta.env.VITE_DEP_VITE as string) ?? '?' },
1212
+ {
1213
+ name: '@tanstack/react-router',
1214
+ version: (import.meta.env.VITE_DEP_TANSTACK_ROUTER as string) ?? '?',
1215
+ },
1216
+ {
1217
+ name: '@tanstack/react-query',
1218
+ version: (import.meta.env.VITE_DEP_TANSTACK_QUERY as string) ?? '?',
1219
+ },
1220
+ { name: 'zustand', version: (import.meta.env.VITE_DEP_ZUSTAND as string) ?? '?' },
1221
+ { name: '@casl/ability', version: (import.meta.env.VITE_DEP_CASL as string) ?? '?' },
1222
+ ]}
1223
+ ctaHref="/dashboard"
1224
+ ctaLabel={<Link to="/dashboard">Dashboard \u2192</Link>}
1225
+ />
1226
+ ),
1227
+ });
1228
+ `;
1229
+ var MUI_LAYOUT_HEADER_TSX = `import { AppBar, Box, Button, Toolbar, Typography } from '@mui/material';
1230
+ import { useTranslation } from 'react-i18next';
1231
+ import { setStoredLocale, type IcoreLocale } from '@icore/template-shared';
1232
+ import { ThemeToggle } from '../ThemeToggle';
1233
+
1234
+ const APP_VERSION = (import.meta.env.VITE_APP_VERSION as string | undefined) ?? '0.0.0-dev';
1235
+
1236
+ const LOCALES: { code: IcoreLocale; label: string }[] = [
1237
+ { code: 'en', label: 'EN' },
1238
+ { code: 'ru', label: 'RU' },
1239
+ { code: 'he', label: 'HE' },
1240
+ ];
1241
+
1242
+ export function LayoutHeader() {
1243
+ const { i18n } = useTranslation();
1244
+ const currentLocale = i18n.language as IcoreLocale;
1245
+
1246
+ function handleLocale(code: IcoreLocale) {
1247
+ setStoredLocale(code);
1248
+ window.location.reload();
1249
+ }
1250
+
1251
+ return (
1252
+ <AppBar position="sticky" color="default" elevation={1}>
1253
+ <Toolbar sx={{ justifyContent: 'space-between' }}>
1254
+ <Typography variant="h6" component="div" fontWeight={600}>
1255
+ iCore{' '}
1256
+ <span style={{ opacity: 0.6, fontSize: '0.75em', fontWeight: 400 }}>v{APP_VERSION}</span>
1257
+ </Typography>
1258
+
1259
+ <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
1260
+ <Box sx={{ display: 'flex', gap: 0.5 }}>
1261
+ {LOCALES.map(({ code, label }) => (
1262
+ <Button
1263
+ key={code}
1264
+ size="small"
1265
+ variant={currentLocale === code ? 'contained' : 'text'}
1266
+ onClick={() => handleLocale(code)}
1267
+ >
1268
+ {label}
1269
+ </Button>
1270
+ ))}
1271
+ </Box>
1272
+
1273
+ <ThemeToggle />
1274
+ </Box>
1275
+ </Toolbar>
1276
+ </AppBar>
1277
+ );
1278
+ }
1279
+ `;
1280
+ var COMMON_VARIANTS = {
1281
+ "apps/api/src/main.ts": GATEWAY_MAIN_TS,
1282
+ "apps/api/src/app/app.module.ts": GATEWAY_APP_MODULE_TS,
1283
+ "libs/shared/src/client.ts": SHARED_CLIENT_TS,
1284
+ "libs/shared/src/index.ts": SHARED_INDEX_TS,
1285
+ "libs/template-shared/src/index.ts": TEMPLATE_SHARED_INDEX_TS
1286
+ };
1287
+ var UI_VARIANTS = {
1288
+ shadcn: {
1289
+ "apps/client/src/main.tsx": SHADCN_MAIN_TSX,
1290
+ "apps/client/src/routes/_dashboard.tsx": SHADCN_DASHBOARD_TSX,
1291
+ "apps/client/src/routes/index.tsx": SHADCN_INDEX_TSX,
1292
+ "apps/client/src/components/layout/LayoutHeader.tsx": SHADCN_LAYOUT_HEADER_TSX
1293
+ },
1294
+ antd: {
1295
+ "apps/client/src/main.tsx": ANTD_MAIN_TSX,
1296
+ "apps/client/src/routes/_dashboard.tsx": ANTD_DASHBOARD_TSX,
1297
+ "apps/client/src/routes/index.tsx": ANTD_INDEX_TSX,
1298
+ "apps/client/src/components/layout/LayoutHeader.tsx": ANTD_LAYOUT_HEADER_TSX
1299
+ },
1300
+ mui: {
1301
+ "apps/client/src/main.tsx": MUI_MAIN_TSX,
1302
+ "apps/client/src/routes/_dashboard.tsx": MUI_DASHBOARD_TSX,
1303
+ "apps/client/src/routes/index.tsx": MUI_INDEX_TSX,
1304
+ "apps/client/src/components/layout/LayoutHeader.tsx": MUI_LAYOUT_HEADER_TSX
1305
+ }
1306
+ };
1307
+ async function applyAuthNoneVariants(targetDir, ui) {
1308
+ const uiFiles = UI_VARIANTS[ui] ?? UI_VARIANTS["shadcn"];
1309
+ const all = { ...COMMON_VARIANTS, ...uiFiles };
1310
+ for (const [rel, content] of Object.entries(all)) {
1311
+ const dest = join4(targetDir, rel);
1312
+ try {
1313
+ await mkdir(dirname2(dest), { recursive: true });
1314
+ await writeFile3(dest, content);
1315
+ } catch {
1316
+ }
1317
+ }
651
1318
  }
652
1319
 
653
1320
  // src/manifest/wire-features.ts
654
- import { readFile as readFile6, writeFile as writeFile4, rm as rm3 } from "fs/promises";
655
- import { join as join5 } from "path";
1321
+ import { readFile as readFile7, writeFile as writeFile5, rm as rm4, rmdir, unlink } from "fs/promises";
1322
+ import { join as join6 } from "path";
656
1323
 
657
1324
  // src/manifest/index.ts
658
1325
  var EMPTY = { libDirs: [], deps: {}, tsPaths: {} };
@@ -812,8 +1479,8 @@ var MANIFEST = {
812
1479
  };
813
1480
 
814
1481
  // src/manifest/wire-provider.ts
815
- import { readFile as readFile5, writeFile as writeFile3, rm as rm2 } from "fs/promises";
816
- import { join as join4 } from "path";
1482
+ import { readFile as readFile6, writeFile as writeFile4, rm as rm3 } from "fs/promises";
1483
+ import { join as join5 } from "path";
817
1484
  async function writeProvider(targetDir, axis, provider) {
818
1485
  const nestModule = axis.section[provider]?.nestModule;
819
1486
  if (!nestModule) throw new Error(`provider "${provider}" has no nestModule in the manifest`);
@@ -824,27 +1491,27 @@ const ENV_PATH = '${axis.envPath}';
824
1491
 
825
1492
  export const ${axis.exportConst} = ${symbol}.forRoot(ENV_PATH);
826
1493
  `;
827
- await writeFile3(join4(targetDir, axis.providerFile), content);
1494
+ await writeFile4(join5(targetDir, axis.providerFile), content);
828
1495
  }
829
1496
  async function stripJsonKeys(path, drop) {
830
1497
  try {
831
- const pkg = JSON.parse(await readFile5(path, "utf8"));
1498
+ const pkg = JSON.parse(await readFile6(path, "utf8"));
832
1499
  for (const field of ["dependencies", "devDependencies"]) {
833
1500
  const deps = pkg[field];
834
1501
  if (!deps) continue;
835
1502
  for (const k of Object.keys(deps)) if (drop(k)) delete deps[k];
836
1503
  }
837
- await writeFile3(path, JSON.stringify(pkg, null, 2) + "\n");
1504
+ await writeFile4(path, JSON.stringify(pkg, null, 2) + "\n");
838
1505
  } catch {
839
1506
  }
840
1507
  }
841
1508
  async function stripTsconfigKeys(targetDir, aliases) {
842
- const path = join4(targetDir, "tsconfig.base.json");
1509
+ const path = join5(targetDir, "tsconfig.base.json");
843
1510
  try {
844
- const parsed = JSON.parse(await readFile5(path, "utf8"));
1511
+ const parsed = JSON.parse(await readFile6(path, "utf8"));
845
1512
  const paths = parsed.compilerOptions?.paths;
846
1513
  if (paths) for (const a of aliases) delete paths[a];
847
- await writeFile3(path, JSON.stringify(parsed, null, 2) + "\n");
1514
+ await writeFile4(path, JSON.stringify(parsed, null, 2) + "\n");
848
1515
  } catch {
849
1516
  }
850
1517
  }
@@ -853,10 +1520,10 @@ async function cleanupUnusedAxis(targetDir, axis, chosen) {
853
1520
  if (provider === chosen) continue;
854
1521
  const unit = axis.section[provider];
855
1522
  for (const dir of unit.libDirs)
856
- await rm2(join4(targetDir, dir), { recursive: true, force: true });
857
- for (const t of unit.appTests ?? []) await rm2(join4(targetDir, t), { force: true });
1523
+ await rm3(join5(targetDir, dir), { recursive: true, force: true });
1524
+ for (const t of unit.appTests ?? []) await rm3(join5(targetDir, t), { force: true });
858
1525
  const dropKeys = /* @__PURE__ */ new Set([...Object.keys(unit.tsPaths), ...Object.keys(unit.deps)]);
859
- await stripJsonKeys(join4(targetDir, axis.msPackageJson), (k) => dropKeys.has(k));
1526
+ await stripJsonKeys(join5(targetDir, axis.msPackageJson), (k) => dropKeys.has(k));
860
1527
  await stripTsconfigKeys(targetDir, Object.keys(unit.tsPaths));
861
1528
  }
862
1529
  }
@@ -885,7 +1552,7 @@ async function writeFeaturesWiring(targetDir, opts) {
885
1552
  })
886
1553
  export class FeaturesModule {}
887
1554
  `;
888
- await writeFile4(join5(targetDir, FEATURES_MODULE), featuresModule);
1555
+ await writeFile5(join6(targetDir, FEATURES_MODULE), featuresModule);
889
1556
  const services = [];
890
1557
  if (opts.authProvider !== "none") services.push({ name: "auth", prefix: "AUTH" });
891
1558
  if (opts.upload !== "none") services.push({ name: "upload", prefix: "UPLOAD" });
@@ -899,14 +1566,14 @@ export const GATEWAY_SERVICES = [
899
1566
  ${entries}
900
1567
  ];
901
1568
  `;
902
- await writeFile4(join5(targetDir, GATEWAY_SERVICES), gatewayServices);
1569
+ await writeFile5(join6(targetDir, GATEWAY_SERVICES), gatewayServices);
903
1570
  }
904
1571
  async function stripJobsDockerCompose(targetDir) {
905
- const composePath = join5(targetDir, "docker-compose.yml");
1572
+ const composePath = join6(targetDir, "docker-compose.yml");
906
1573
  try {
907
- const compose = await readFile6(composePath, "utf8");
1574
+ const compose = await readFile7(composePath, "utf8");
908
1575
  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, "");
909
- await writeFile4(composePath, next);
1576
+ await writeFile5(composePath, next);
910
1577
  } catch {
911
1578
  }
912
1579
  }
@@ -916,18 +1583,38 @@ async function cleanupUnusedFeatures(targetDir, opts) {
916
1583
  if (chosen.has(key)) continue;
917
1584
  const unit = FEATURES[key];
918
1585
  for (const dir of unit.libDirs)
919
- await rm3(join5(targetDir, dir), { recursive: true, force: true });
1586
+ await rm4(join6(targetDir, dir), { recursive: true, force: true });
920
1587
  const dropKeys = /* @__PURE__ */ new Set([...Object.keys(unit.tsPaths), ...Object.keys(unit.deps)]);
921
- await stripJsonKeys(join5(targetDir, API_PKG), (k) => dropKeys.has(k));
1588
+ await stripJsonKeys(join6(targetDir, API_PKG), (k) => dropKeys.has(k));
922
1589
  await stripTsconfigKeys(targetDir, Object.keys(unit.tsPaths));
923
1590
  if (unit.gatewayService) await stripGatewayTransport(targetDir, unit.gatewayService.prefix);
924
1591
  if (unit.dockerService === "jobs") await stripJobsDockerCompose(targetDir);
1592
+ if (key === "jobs") {
1593
+ try {
1594
+ await unlink(join6(targetDir, "libs/shared/src/jobs.ts"));
1595
+ } catch {
1596
+ }
1597
+ try {
1598
+ await unlink(join6(targetDir, "libs/shared/src/__tests__/jobs.unit.test.ts"));
1599
+ } catch {
1600
+ }
1601
+ const sharedIdx = join6(targetDir, "libs/shared/src/index.ts");
1602
+ try {
1603
+ const src = await readFile7(sharedIdx, "utf8");
1604
+ await writeFile5(sharedIdx, src.replace(/^export \* from '\.\/jobs';\n/m, ""));
1605
+ } catch {
1606
+ }
1607
+ }
1608
+ }
1609
+ try {
1610
+ await rmdir(join6(targetDir, "apps/client/src/queries"));
1611
+ } catch {
925
1612
  }
926
1613
  }
927
1614
 
928
1615
  // src/manifest/wire-client.ts
929
- import { writeFile as writeFile5 } from "fs/promises";
930
- import { join as join6 } from "path";
1616
+ import { writeFile as writeFile6 } from "fs/promises";
1617
+ import { join as join7 } from "path";
931
1618
  var NAV_CONFIG_FILE = "apps/client/src/nav.config.ts";
932
1619
  var BASE_NAV = [
933
1620
  { to: "/dashboard", labelKey: "nav.dashboard", iconName: "dashboard", exact: true }
@@ -965,12 +1652,12 @@ export const NAV_CONFIG: NavItem[] = [
965
1652
  ` + entries.map(renderEntry).join("\n") + `
966
1653
  ];
967
1654
  `;
968
- await writeFile5(join6(targetDir, NAV_CONFIG_FILE), content);
1655
+ await writeFile6(join7(targetDir, NAV_CONFIG_FILE), content);
969
1656
  }
970
1657
 
971
1658
  // src/manifest/blueprint.ts
972
- import { writeFile as writeFile6 } from "fs/promises";
973
- import { join as join7 } from "path";
1659
+ import { writeFile as writeFile7 } from "fs/promises";
1660
+ import { join as join8 } from "path";
974
1661
  async function writeBlueprintJson(targetDir, opts) {
975
1662
  const blueprint = {
976
1663
  schemaVersion: 1,
@@ -985,10 +1672,10 @@ async function writeBlueprintJson(targetDir, opts) {
985
1672
  transport: opts.transport,
986
1673
  packageManager: opts.packageManager
987
1674
  };
988
- await writeFile6(join7(targetDir, "blueprint.json"), JSON.stringify(blueprint, null, 2) + "\n");
1675
+ await writeFile7(join8(targetDir, "blueprint.json"), JSON.stringify(blueprint, null, 2) + "\n");
989
1676
  }
990
1677
  async function writeJson(targetDir, rel, data) {
991
- await writeFile6(join7(targetDir, rel, "blueprint.json"), JSON.stringify(data, null, 2) + "\n");
1678
+ await writeFile7(join8(targetDir, rel, "blueprint.json"), JSON.stringify(data, null, 2) + "\n");
992
1679
  }
993
1680
  async function writeServiceBlueprints(targetDir, opts) {
994
1681
  const t = opts.transport;
@@ -1082,8 +1769,8 @@ var writeDbProvider = (targetDir, provider) => writeProvider(targetDir, DB, prov
1082
1769
  var cleanupUnusedDb = (targetDir, chosen) => cleanupUnusedAxis(targetDir, DB, chosen);
1083
1770
 
1084
1771
  // src/lib/scaffold-pkg.ts
1085
- import { readFile as readFile7, writeFile as writeFile7, mkdir, readdir } from "fs/promises";
1086
- import { join as join8 } from "path";
1772
+ import { readFile as readFile8, writeFile as writeFile8, mkdir as mkdir2, readdir } from "fs/promises";
1773
+ import { join as join9 } from "path";
1087
1774
 
1088
1775
  // src/lib/options.ts
1089
1776
  function pmRun(pm, script) {
@@ -1092,8 +1779,8 @@ function pmRun(pm, script) {
1092
1779
 
1093
1780
  // src/lib/scaffold-pkg.ts
1094
1781
  async function writePnpmWorkspace(targetDir) {
1095
- const pkgPath = join8(targetDir, "package.json");
1096
- const pkg = JSON.parse(await readFile7(pkgPath, "utf8"));
1782
+ const pkgPath = join9(targetDir, "package.json");
1783
+ const pkg = JSON.parse(await readFile8(pkgPath, "utf8"));
1097
1784
  const workspaces = pkg.workspaces ?? [];
1098
1785
  const packagesBlock = workspaces.map((p3) => ` - '${p3}'`).join("\n");
1099
1786
  const allowBuilds = [
@@ -1116,7 +1803,7 @@ ${packagesBlock}
1116
1803
  allowBuilds:
1117
1804
  ${allowBuilds}
1118
1805
  `;
1119
- await writeFile7(join8(targetDir, "pnpm-workspace.yaml"), content);
1806
+ await writeFile8(join9(targetDir, "pnpm-workspace.yaml"), content);
1120
1807
  }
1121
1808
  async function rewritePnpmWorkspaceDeps(targetDir) {
1122
1809
  async function walk(dir) {
@@ -1129,9 +1816,9 @@ async function rewritePnpmWorkspaceDeps(targetDir) {
1129
1816
  }
1130
1817
  for (const e of entries) {
1131
1818
  if (e.isDirectory() && e.name !== "node_modules") {
1132
- found.push(...await walk(join8(dir, e.name)));
1819
+ found.push(...await walk(join9(dir, e.name)));
1133
1820
  } else if (e.isFile() && e.name === "package.json") {
1134
- found.push(join8(dir, e.name));
1821
+ found.push(join9(dir, e.name));
1135
1822
  }
1136
1823
  }
1137
1824
  return found;
@@ -1139,17 +1826,17 @@ async function rewritePnpmWorkspaceDeps(targetDir) {
1139
1826
  const pkgFiles = await walk(targetDir);
1140
1827
  for (const f of pkgFiles) {
1141
1828
  try {
1142
- const raw = await readFile7(f, "utf8");
1829
+ const raw = await readFile8(f, "utf8");
1143
1830
  const next = raw.replace(/"(@icore\/[^"]+)":\s*"\*"/g, '"$1": "workspace:*"');
1144
- if (next !== raw) await writeFile7(f, next);
1831
+ if (next !== raw) await writeFile8(f, next);
1145
1832
  } catch {
1146
1833
  }
1147
1834
  }
1148
1835
  }
1149
1836
  async function patchGitignoreForPm(targetDir, pm) {
1150
- const giPath = join8(targetDir, ".gitignore");
1837
+ const giPath = join9(targetDir, ".gitignore");
1151
1838
  try {
1152
- let src = await readFile7(giPath, "utf8");
1839
+ let src = await readFile8(giPath, "utf8");
1153
1840
  src = src.replace(/^# Build artifacts.*\ntools\/create-icore\/templates\/\s*\n/m, "");
1154
1841
  if (pm !== "yarn") {
1155
1842
  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, "");
@@ -1164,7 +1851,7 @@ async function patchGitignoreForPm(targetDir, pm) {
1164
1851
  src += "\n# npm\nnpm-debug.log*\n";
1165
1852
  }
1166
1853
  }
1167
- await writeFile7(giPath, src);
1854
+ await writeFile8(giPath, src);
1168
1855
  } catch {
1169
1856
  }
1170
1857
  }
@@ -1172,14 +1859,15 @@ async function writeAiFiles(targetDir, opts) {
1172
1859
  const pm = opts.packageManager;
1173
1860
  const nx = pm === "npm" ? "npx nx" : `${pm} nx`;
1174
1861
  const devCmd = pmRun(pm, "dev");
1175
- const activeMSes = ["auth (port 4001)"];
1862
+ const activeMSes = [];
1863
+ if (opts.authProvider !== "none") activeMSes.push("auth (port 4001)");
1176
1864
  if (opts.upload !== "none") activeMSes.push(`upload (port 4002)`);
1177
1865
  if (opts.payment !== "none") activeMSes.push(`payment (port 4003)`);
1178
1866
  if (opts.example !== "none") activeMSes.push(`notes (port 4004)`);
1179
1867
  if (opts.jobs !== "none") activeMSes.push(`jobs (standalone)`);
1180
1868
  const usesSupabase = opts.authProvider === "supabase" || opts.dbProvider === "supabase" || opts.upload === "supabase";
1181
1869
  const usesFirebase = opts.authProvider === "firebase" || opts.dbProvider === "firebase" || opts.upload === "firebase";
1182
- await writeFile7(join8(targetDir, "CLAUDE.md"), "@AGENTS.md\n");
1870
+ await writeFile8(join9(targetDir, "CLAUDE.md"), "@AGENTS.md\n");
1183
1871
  const uiLabel = { shadcn: "shadcn/ui + Tailwind", antd: "Ant Design 6", mui: "MUI 6" }[opts.ui];
1184
1872
  const readme = `# ${opts.projectName}
1185
1873
 
@@ -1201,9 +1889,7 @@ async function writeAiFiles(targetDir, opts) {
1201
1889
 
1202
1890
  \`\`\`bash
1203
1891
  # 1. Fill in provider credentials
1204
- # apps/microservices/auth/.env
1205
- # apps/microservices/upload/.env (if upload is enabled)
1206
- # apps/client/.env (VITE_API_URL \u2014 already defaults to /api)
1892
+ ${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)
1207
1893
 
1208
1894
  # 2. Start everything
1209
1895
  ${devCmd}
@@ -1229,7 +1915,7 @@ ${pm === "yarn" ? "yarn remove-notes" : pm === "pnpm" ? "pnpm remove-notes" : "n
1229
1915
 
1230
1916
  Apache-2.0
1231
1917
  `;
1232
- await writeFile7(join8(targetDir, "README.md"), readme);
1918
+ await writeFile8(join9(targetDir, "README.md"), readme);
1233
1919
  const agents = `# ${opts.projectName} \u2014 Agent Instructions
1234
1920
 
1235
1921
  ## Stack snapshot
@@ -1299,10 +1985,10 @@ ${nx} g @nx/nest:resource # generate NestJS resource
1299
1985
 
1300
1986
  | File | Key vars |
1301
1987
  |------|----------|
1302
- | \`apps/microservices/auth/.env\` | \`AUTH_PROVIDER=${opts.authProvider}\`, ${opts.authProvider === "supabase" ? "`SUPABASE_URL`, `SUPABASE_SERVICE_ROLE_KEY`" : "`FB_ADMIN_*`, `FIREBASE_WEB_API_KEY`"} |
1303
- ${opts.upload !== "none" ? `| \`apps/microservices/upload/.env\` | \`STORAGE_PROVIDER=${opts.upload}\`, provider creds |
1304
- ` : ""}| \`apps/microservices/notes/.env\` | \`DB_PROVIDER=${opts.dbProvider}\`, DB creds |
1305
- | \`apps/client/.env\` | \`VITE_API_URL=/api\` (proxied to :3001 in dev) |
1988
+ ${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`"} |
1989
+ ` : ""}${opts.upload !== "none" ? `| \`apps/microservices/upload/.env\` | \`STORAGE_PROVIDER=${opts.upload}\`, provider creds |
1990
+ ` : ""}${opts.example !== "none" ? `| \`apps/microservices/notes/.env\` | \`DB_PROVIDER=${opts.dbProvider}\`, DB creds |
1991
+ ` : ""}| \`apps/client/.env\` | \`VITE_API_URL=/api\` (proxied to :3001 in dev) |
1306
1992
 
1307
1993
  ## Testing
1308
1994
 
@@ -1310,8 +1996,8 @@ ${opts.upload !== "none" ? `| \`apps/microservices/upload/.env\` | \`STORAGE_PRO
1310
1996
  - Test behaviour, not implementation. Fake strategies from \`@icore/shared\` (FakeAuthStrategy etc.) serve as test doubles.
1311
1997
  - Run: \`${nx} test <project>\`
1312
1998
  `;
1313
- await writeFile7(join8(targetDir, "AGENTS.md"), agents);
1314
- await mkdir(join8(targetDir, ".claude"), { recursive: true });
1999
+ await writeFile8(join9(targetDir, "AGENTS.md"), agents);
2000
+ await mkdir2(join9(targetDir, ".claude"), { recursive: true });
1315
2001
  const mcpServers = {
1316
2002
  nx: {
1317
2003
  command: "npx",
@@ -1352,8 +2038,8 @@ ${opts.upload !== "none" ? `| \`apps/microservices/upload/.env\` | \`STORAGE_PRO
1352
2038
  ]
1353
2039
  }
1354
2040
  };
1355
- await writeFile7(
1356
- join8(targetDir, ".claude", "settings.json"),
2041
+ await writeFile8(
2042
+ join9(targetDir, ".claude", "settings.json"),
1357
2043
  JSON.stringify(settings, null, 2) + "\n"
1358
2044
  );
1359
2045
  }
@@ -1373,20 +2059,20 @@ var IGNORE_TOP = /* @__PURE__ */ new Set([
1373
2059
  ".vscode"
1374
2060
  ]);
1375
2061
  async function copyTree(src, dest) {
1376
- await mkdir2(dest, { recursive: true });
2062
+ await mkdir3(dest, { recursive: true });
1377
2063
  const entries = await readdir2(src, { withFileTypes: true });
1378
2064
  for (const entry of entries) {
1379
2065
  if (IGNORE_TOP.has(entry.name)) continue;
1380
- const s = join9(src, entry.name);
1381
- const d = join9(dest, entry.name);
2066
+ const s = join10(src, entry.name);
2067
+ const d = join10(dest, entry.name);
1382
2068
  if (entry.isDirectory()) await copyTree(s, d);
1383
2069
  else if (entry.isFile()) await copyFile(s, d);
1384
2070
  }
1385
2071
  }
1386
2072
  async function selectClientTemplate(targetDir, opts) {
1387
- const templatesRoot = join9(targetDir, "apps/templates");
1388
- const chosen = join9(templatesRoot, `client-${opts.ui}`);
1389
- const destClient = join9(targetDir, "apps/client");
2073
+ const templatesRoot = join10(targetDir, "apps/templates");
2074
+ const chosen = join10(templatesRoot, `client-${opts.ui}`);
2075
+ const destClient = join10(targetDir, "apps/client");
1390
2076
  let chosenUi = opts.ui;
1391
2077
  try {
1392
2078
  const s = await stat(chosen);
@@ -1394,9 +2080,9 @@ async function selectClientTemplate(targetDir, opts) {
1394
2080
  await copyTree(chosen, destClient);
1395
2081
  } catch {
1396
2082
  chosenUi = "shadcn";
1397
- await copyTree(join9(templatesRoot, "client-shadcn"), destClient);
2083
+ await copyTree(join10(templatesRoot, "client-shadcn"), destClient);
1398
2084
  }
1399
- await rm4(templatesRoot, { recursive: true, force: true });
2085
+ await rm5(templatesRoot, { recursive: true, force: true });
1400
2086
  await rewriteClientPaths(destClient, chosenUi);
1401
2087
  }
1402
2088
  async function rewriteClientPaths(clientDir, ui) {
@@ -1409,11 +2095,11 @@ async function rewriteClientPaths(clientDir, ui) {
1409
2095
  "eslint.config.mjs"
1410
2096
  ];
1411
2097
  for (const rel of candidates) {
1412
- const path = join9(clientDir, rel);
2098
+ const path = join10(clientDir, rel);
1413
2099
  try {
1414
- const raw = await readFile8(path, "utf8");
2100
+ const raw = await readFile9(path, "utf8");
1415
2101
  const next = raw.replaceAll("../../../", "../../").replaceAll(`apps/templates/client-${ui}`, "apps/client").replaceAll(`client-${ui}`, "client");
1416
- if (next !== raw) await writeFile8(path, next);
2102
+ if (next !== raw) await writeFile9(path, next);
1417
2103
  } catch {
1418
2104
  }
1419
2105
  }
@@ -1429,12 +2115,12 @@ function gitInit(cwd, projectName) {
1429
2115
  }
1430
2116
  function resolveYarnBin(cwd) {
1431
2117
  try {
1432
- const yarnrc = readFileSync(join9(cwd, ".yarnrc.yml"), "utf8");
2118
+ const yarnrc = readFileSync(join10(cwd, ".yarnrc.yml"), "utf8");
1433
2119
  const match = yarnrc.match(/^yarnPath:\s*(.+)$/m);
1434
- if (match?.[1]) return join9(cwd, match[1].trim());
2120
+ if (match?.[1]) return join10(cwd, match[1].trim());
1435
2121
  } catch {
1436
2122
  }
1437
- return join9(cwd, ".yarn", "releases", "yarn-4.5.0.cjs");
2123
+ return join10(cwd, ".yarn", "releases", "yarn-4.5.0.cjs");
1438
2124
  }
1439
2125
  function runInstall(cwd, pm) {
1440
2126
  if (pm === "yarn") {
@@ -1445,7 +2131,8 @@ function runInstall(cwd, pm) {
1445
2131
  spawnSync("pnpm", ["install"], { cwd, stdio: "inherit" });
1446
2132
  }
1447
2133
  }
1448
- async function scaffold(opts, templatesDir2) {
2134
+ async function scaffold(rawOpts, templatesDir2) {
2135
+ const opts = rawOpts.authProvider === "none" && rawOpts.example !== "none" ? { ...rawOpts, example: "none" } : rawOpts;
1449
2136
  await copyTree(templatesDir2, opts.targetDir);
1450
2137
  await rewriteRootPackageJson(opts.targetDir, opts);
1451
2138
  if (opts.authProvider !== "none") await writeAuthEnv(opts.targetDir, opts);
@@ -1456,6 +2143,10 @@ async function scaffold(opts, templatesDir2) {
1456
2143
  await writeRootEnv(opts.targetDir, opts);
1457
2144
  await selectClientTemplate(opts.targetDir, opts);
1458
2145
  await writeClientEnv(opts.targetDir);
2146
+ if (opts.authProvider === "none") {
2147
+ await removeAuthOnlyPaths(opts.targetDir);
2148
+ await applyAuthNoneVariants(opts.targetDir, opts.ui);
2149
+ }
1459
2150
  if (opts.upload === "none") await removeUploadStack(opts.targetDir);
1460
2151
  await cleanupUnusedFeatures(opts.targetDir, opts);
1461
2152
  await writeFeaturesWiring(opts.targetDir, opts);
@@ -1464,7 +2155,8 @@ async function scaffold(opts, templatesDir2) {
1464
2155
  await cleanupUnusedAuth(opts.targetDir, opts.authProvider);
1465
2156
  await writeAuthProvider(opts.targetDir, opts.authProvider);
1466
2157
  } else {
1467
- await removeAuthStack(opts.targetDir);
2158
+ await removeAuthTsconfigPaths(opts.targetDir);
2159
+ await removeDockerComposeAuthService(opts.targetDir);
1468
2160
  }
1469
2161
  if (opts.upload !== "none") {
1470
2162
  await cleanupUnusedStorage(opts.targetDir, opts.upload);
@@ -1477,13 +2169,20 @@ async function scaffold(opts, templatesDir2) {
1477
2169
  await writeDbProvider(opts.targetDir, opts.dbProvider);
1478
2170
  }
1479
2171
  await pruneRootProviderDeps(opts.targetDir, opts);
2172
+ if (opts.authProvider === "none" && opts.upload === "none" && opts.dbProvider === "none" && opts.payment === "none") {
2173
+ await removeStrategiesLib(opts.targetDir);
2174
+ }
2175
+ try {
2176
+ await rmdir2(join10(opts.targetDir, "apps/microservices"));
2177
+ } catch {
2178
+ }
1480
2179
  await writeBlueprintJson(opts.targetDir, opts);
1481
2180
  await writeServiceBlueprints(opts.targetDir, opts);
1482
2181
  if (opts.packageManager === "yarn") {
1483
- await writeFile8(join9(opts.targetDir, "yarn.lock"), "");
2182
+ await writeFile9(join10(opts.targetDir, "yarn.lock"), "");
1484
2183
  } else {
1485
- await rm4(join9(opts.targetDir, ".yarn"), { recursive: true, force: true });
1486
- await rm4(join9(opts.targetDir, ".yarnrc.yml"), { force: true });
2184
+ await rm5(join10(opts.targetDir, ".yarn"), { recursive: true, force: true });
2185
+ await rm5(join10(opts.targetDir, ".yarnrc.yml"), { force: true });
1487
2186
  }
1488
2187
  if (opts.packageManager === "pnpm") {
1489
2188
  await writePnpmWorkspace(opts.targetDir);
@@ -1505,7 +2204,7 @@ Upgrade: https://nodejs.org
1505
2204
  );
1506
2205
  process.exit(1);
1507
2206
  }
1508
- var here = dirname2(fileURLToPath2(import.meta.url));
2207
+ var here = dirname3(fileURLToPath2(import.meta.url));
1509
2208
  var templatesDir = resolve2(here, "..", "templates");
1510
2209
  async function main() {
1511
2210
  if (!existsSync(templatesDir)) {
@@ -1525,11 +2224,14 @@ async function main() {
1525
2224
  p2.log.info(`Next:`);
1526
2225
  p2.log.info(` cd ${opts.projectName}`);
1527
2226
  if (!opts.install) p2.log.info(` ${opts.packageManager} install`);
1528
- p2.log.info(
1529
- ` ${pmRun(opts.packageManager, "dev")} # gateway + auth MS + upload MS + client`
1530
- );
2227
+ const services = ["gateway", "client"];
2228
+ if (opts.authProvider !== "none") services.splice(1, 0, "auth MS");
2229
+ if (opts.upload !== "none") services.splice(-1, 0, "upload MS");
2230
+ p2.log.info(` ${pmRun(opts.packageManager, "dev")} # ${services.join(" + ")}`);
1531
2231
  p2.log.info(` open http://localhost:4200`);
1532
- p2.log.info(` edit apps/microservices/auth/.env to plug in real ${opts.authProvider} creds`);
2232
+ if (opts.authProvider !== "none") {
2233
+ p2.log.info(` edit apps/microservices/auth/.env to plug in real ${opts.authProvider} creds`);
2234
+ }
1533
2235
  }
1534
2236
  main().catch((err) => {
1535
2237
  p2.log.error(err instanceof Error ? err.message : String(err));