@idevconn/create-icore 0.9.3 → 0.10.1

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 (42) hide show
  1. package/dist/cli.js +755 -190
  2. package/dist/index.cjs +778 -213
  3. package/dist/index.js +771 -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 +5 -4
  19. package/templates/libs/auth-strategies/firebase/src/lib/firebase-auth.module.ts +2 -1
  20. package/templates/libs/auth-strategies/mongodb/package.json +4 -4
  21. package/templates/libs/auth-strategies/supabase/package.json +5 -5
  22. package/templates/libs/db-strategies/firestore/package.json +5 -4
  23. package/templates/libs/db-strategies/firestore/src/lib/firestore-db.module.ts +2 -1
  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 +2 -2
  27. package/templates/libs/firebase-admin/src/lib/__tests__/firebase-admin.unit.test.ts +11 -11
  28. package/templates/libs/firebase-admin/src/lib/firebase-admin.ts +6 -6
  29. package/templates/libs/jobs-client/package.json +4 -4
  30. package/templates/libs/notes-client/package.json +3 -3
  31. package/templates/libs/payment-client/package.json +3 -3
  32. package/templates/libs/shared/package.json +2 -2
  33. package/templates/libs/storage-strategies/cloudinary/package.json +4 -4
  34. package/templates/libs/storage-strategies/firebase/package.json +5 -4
  35. package/templates/libs/storage-strategies/firebase/src/lib/firebase-storage.module.ts +4 -1
  36. package/templates/libs/storage-strategies/mongodb/package.json +3 -3
  37. package/templates/libs/storage-strategies/supabase/package.json +5 -5
  38. package/templates/libs/template-shared/package.json +8 -8
  39. package/templates/libs/upload-client/package.json +3 -3
  40. package/templates/libs/vite-plugins/package.json +3 -3
  41. package/templates/package.json +32 -32
  42. 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, rmdir as rmdir2, 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
@@ -504,6 +504,9 @@ async function writeGatewayEnv(targetDir, opts) {
504
504
  for (const prefix of ["AUTH", "UPLOAD", "NOTES", "PAYMENT"]) {
505
505
  next = uncommentTransportEnv(next, prefix, opts.transport);
506
506
  }
507
+ if (opts.authProvider === "none") {
508
+ next = next.split("\n").filter((line) => !line.startsWith("AUTH_") && !line.startsWith("# AUTH_")).join("\n");
509
+ }
507
510
  await writeFile(join2(targetDir, "apps/api/.env"), next);
508
511
  }
509
512
  async function writeRootEnv(targetDir, opts) {
@@ -589,6 +592,7 @@ async function removeStrategiesLib(targetDir) {
589
592
  await rm(join3(targetDir, "libs/shared/src/strategies"), { recursive: true, force: true });
590
593
  await rm(join3(targetDir, "libs/shared/src/testing.ts"), { force: true });
591
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 });
592
596
  const indexPath = join3(targetDir, "libs/shared/src/index.ts");
593
597
  try {
594
598
  const src = await readFile4(indexPath, "utf8");
@@ -607,122 +611,6 @@ async function removeStrategiesLib(targetDir) {
607
611
  } catch {
608
612
  }
609
613
  }
610
- async function removeAuthStack(targetDir) {
611
- const rmPaths = [
612
- "apps/microservices/auth",
613
- "libs/auth-strategies",
614
- "libs/auth-client",
615
- "Dockerfile.ms-auth",
616
- "apps/api/src/app/auth",
617
- "apps/api/src/app/profile",
618
- "apps/api/src/app/abilities",
619
- "libs/shared/src/abilities",
620
- "apps/client/src/components/auth",
621
- "apps/client/src/routes/login.tsx",
622
- "apps/client/src/routes/auth.callback.tsx",
623
- "apps/client/src/routes/auth.oauth.callback.tsx",
624
- "apps/client/src/routes/_dashboard/profile.tsx"
625
- ];
626
- for (const p3 of rmPaths) {
627
- await rm(join3(targetDir, p3), { recursive: true, force: true });
628
- }
629
- const appModulePath = join3(targetDir, "apps/api/src/app/app.module.ts");
630
- try {
631
- const src = await readFile4(appModulePath, "utf8");
632
- 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, "");
633
- await writeFile2(appModulePath, next);
634
- } catch {
635
- }
636
- const dashboardPath = join3(targetDir, "apps/client/src/routes/_dashboard.tsx");
637
- try {
638
- const src = await readFile4(dashboardPath, "utf8");
639
- const next = src.replace(/^import \{ useAuthStore \} from '@icore\/template-shared';\n/m, "").replace(/, redirect/g, "").replace(/\n {2}beforeLoad: \(\) => \{[\s\S]*?\n {2}\},/m, "");
640
- await writeFile2(dashboardPath, next);
641
- } catch {
642
- }
643
- for (const alias of [
644
- "@icore/auth-client",
645
- "@icore/auth-supabase",
646
- "@icore/auth-firebase",
647
- "@icore/auth-mongodb"
648
- ]) {
649
- await stripTsconfigPath(targetDir, alias);
650
- }
651
- await stripDeps(join3(targetDir, "apps/api/package.json"), [
652
- "@icore/auth-client",
653
- "cookie-parser"
654
- ]);
655
- const gatewayMainPath = join3(targetDir, "apps/api/src/main.ts");
656
- try {
657
- const src = await readFile4(gatewayMainPath, "utf8");
658
- const next = src.replace(/^import cookieParser from 'cookie-parser';\n/m, "").replace(/^\s*app\.use\(cookieParser\(\)\);\n/m, "");
659
- await writeFile2(gatewayMainPath, next);
660
- } catch {
661
- }
662
- const gatewayEnv = join3(targetDir, "apps/api/.env");
663
- try {
664
- const env = await readFile4(gatewayEnv, "utf8");
665
- const next = env.split("\n").filter((line) => !line.startsWith("AUTH_") && !line.startsWith("# AUTH_")).join("\n");
666
- await writeFile2(gatewayEnv, next);
667
- } catch {
668
- }
669
- const composePath = join3(targetDir, "docker-compose.yml");
670
- try {
671
- const compose = await readFile4(composePath, "utf8");
672
- 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, "");
673
- await writeFile2(composePath, next);
674
- } catch {
675
- }
676
- for (const rel of ["libs/shared/src/index.ts", "libs/shared/src/client.ts"]) {
677
- const sharedIndexPath = join3(targetDir, rel);
678
- try {
679
- const src = await readFile4(sharedIndexPath, "utf8");
680
- await writeFile2(sharedIndexPath, src.replace(/^export \* from '\.\/abilities';\n?/m, ""));
681
- } catch {
682
- }
683
- }
684
- const routesIndexPath = join3(targetDir, "apps/client/src/routes/index.tsx");
685
- try {
686
- const src = await readFile4(routesIndexPath, "utf8");
687
- await writeFile2(
688
- routesIndexPath,
689
- src.replace(/ctaHref="\/login"/, 'ctaHref="/dashboard"').replace(/ctaLabel="Log in →"/, 'ctaLabel="Dashboard \u2192"')
690
- );
691
- } catch {
692
- }
693
- const mainTsxPath = join3(targetDir, "apps/client/src/main.tsx");
694
- try {
695
- const src = await readFile4(mainTsxPath, "utf8");
696
- 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, "");
697
- await writeFile2(mainTsxPath, next);
698
- } catch {
699
- }
700
- await rm(join3(targetDir, "libs/template-shared/src/lib/abilities"), {
701
- recursive: true,
702
- force: true
703
- });
704
- const templateSharedIndexPath = join3(targetDir, "libs/template-shared/src/index.ts");
705
- try {
706
- const src = await readFile4(templateSharedIndexPath, "utf8");
707
- await writeFile2(
708
- templateSharedIndexPath,
709
- src.replace(/^export \* from '\.\/lib\/abilities\/ability-provider\.js';\n/m, "")
710
- );
711
- } catch {
712
- }
713
- const headerPath = join3(targetDir, "apps/client/src/components/layout/LayoutHeader.tsx");
714
- try {
715
- const src = await readFile4(headerPath, "utf8");
716
- await writeFile2(
717
- headerPath,
718
- 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(
719
- /import \{ useAuthStore, setStoredLocale, type IcoreLocale \} from '@icore\/template-shared';/,
720
- "import { setStoredLocale, type IcoreLocale } from '@icore/template-shared';"
721
- ).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")
722
- );
723
- } catch {
724
- }
725
- }
726
614
  async function removeUploadStack(targetDir) {
727
615
  const paths = [
728
616
  "apps/microservices/upload",
@@ -764,9 +652,674 @@ async function removeUploadStack(targetDir) {
764
652
  }
765
653
  }
766
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
+ }
1318
+ }
1319
+
767
1320
  // src/manifest/wire-features.ts
768
- import { readFile as readFile6, writeFile as writeFile4, rm as rm3, rmdir, unlink } from "fs/promises";
769
- 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";
770
1323
 
771
1324
  // src/manifest/index.ts
772
1325
  var EMPTY = { libDirs: [], deps: {}, tsPaths: {} };
@@ -926,8 +1479,8 @@ var MANIFEST = {
926
1479
  };
927
1480
 
928
1481
  // src/manifest/wire-provider.ts
929
- import { readFile as readFile5, writeFile as writeFile3, rm as rm2 } from "fs/promises";
930
- 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";
931
1484
  async function writeProvider(targetDir, axis, provider) {
932
1485
  const nestModule = axis.section[provider]?.nestModule;
933
1486
  if (!nestModule) throw new Error(`provider "${provider}" has no nestModule in the manifest`);
@@ -938,27 +1491,27 @@ const ENV_PATH = '${axis.envPath}';
938
1491
 
939
1492
  export const ${axis.exportConst} = ${symbol}.forRoot(ENV_PATH);
940
1493
  `;
941
- await writeFile3(join4(targetDir, axis.providerFile), content);
1494
+ await writeFile4(join5(targetDir, axis.providerFile), content);
942
1495
  }
943
1496
  async function stripJsonKeys(path, drop) {
944
1497
  try {
945
- const pkg = JSON.parse(await readFile5(path, "utf8"));
1498
+ const pkg = JSON.parse(await readFile6(path, "utf8"));
946
1499
  for (const field of ["dependencies", "devDependencies"]) {
947
1500
  const deps = pkg[field];
948
1501
  if (!deps) continue;
949
1502
  for (const k of Object.keys(deps)) if (drop(k)) delete deps[k];
950
1503
  }
951
- await writeFile3(path, JSON.stringify(pkg, null, 2) + "\n");
1504
+ await writeFile4(path, JSON.stringify(pkg, null, 2) + "\n");
952
1505
  } catch {
953
1506
  }
954
1507
  }
955
1508
  async function stripTsconfigKeys(targetDir, aliases) {
956
- const path = join4(targetDir, "tsconfig.base.json");
1509
+ const path = join5(targetDir, "tsconfig.base.json");
957
1510
  try {
958
- const parsed = JSON.parse(await readFile5(path, "utf8"));
1511
+ const parsed = JSON.parse(await readFile6(path, "utf8"));
959
1512
  const paths = parsed.compilerOptions?.paths;
960
1513
  if (paths) for (const a of aliases) delete paths[a];
961
- await writeFile3(path, JSON.stringify(parsed, null, 2) + "\n");
1514
+ await writeFile4(path, JSON.stringify(parsed, null, 2) + "\n");
962
1515
  } catch {
963
1516
  }
964
1517
  }
@@ -967,10 +1520,10 @@ async function cleanupUnusedAxis(targetDir, axis, chosen) {
967
1520
  if (provider === chosen) continue;
968
1521
  const unit = axis.section[provider];
969
1522
  for (const dir of unit.libDirs)
970
- await rm2(join4(targetDir, dir), { recursive: true, force: true });
971
- 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 });
972
1525
  const dropKeys = /* @__PURE__ */ new Set([...Object.keys(unit.tsPaths), ...Object.keys(unit.deps)]);
973
- await stripJsonKeys(join4(targetDir, axis.msPackageJson), (k) => dropKeys.has(k));
1526
+ await stripJsonKeys(join5(targetDir, axis.msPackageJson), (k) => dropKeys.has(k));
974
1527
  await stripTsconfigKeys(targetDir, Object.keys(unit.tsPaths));
975
1528
  }
976
1529
  }
@@ -999,7 +1552,7 @@ async function writeFeaturesWiring(targetDir, opts) {
999
1552
  })
1000
1553
  export class FeaturesModule {}
1001
1554
  `;
1002
- await writeFile4(join5(targetDir, FEATURES_MODULE), featuresModule);
1555
+ await writeFile5(join6(targetDir, FEATURES_MODULE), featuresModule);
1003
1556
  const services = [];
1004
1557
  if (opts.authProvider !== "none") services.push({ name: "auth", prefix: "AUTH" });
1005
1558
  if (opts.upload !== "none") services.push({ name: "upload", prefix: "UPLOAD" });
@@ -1013,14 +1566,14 @@ export const GATEWAY_SERVICES = [
1013
1566
  ${entries}
1014
1567
  ];
1015
1568
  `;
1016
- await writeFile4(join5(targetDir, GATEWAY_SERVICES), gatewayServices);
1569
+ await writeFile5(join6(targetDir, GATEWAY_SERVICES), gatewayServices);
1017
1570
  }
1018
1571
  async function stripJobsDockerCompose(targetDir) {
1019
- const composePath = join5(targetDir, "docker-compose.yml");
1572
+ const composePath = join6(targetDir, "docker-compose.yml");
1020
1573
  try {
1021
- const compose = await readFile6(composePath, "utf8");
1574
+ const compose = await readFile7(composePath, "utf8");
1022
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, "");
1023
- await writeFile4(composePath, next);
1576
+ await writeFile5(composePath, next);
1024
1577
  } catch {
1025
1578
  }
1026
1579
  }
@@ -1030,34 +1583,38 @@ async function cleanupUnusedFeatures(targetDir, opts) {
1030
1583
  if (chosen.has(key)) continue;
1031
1584
  const unit = FEATURES[key];
1032
1585
  for (const dir of unit.libDirs)
1033
- await rm3(join5(targetDir, dir), { recursive: true, force: true });
1586
+ await rm4(join6(targetDir, dir), { recursive: true, force: true });
1034
1587
  const dropKeys = /* @__PURE__ */ new Set([...Object.keys(unit.tsPaths), ...Object.keys(unit.deps)]);
1035
- await stripJsonKeys(join5(targetDir, API_PKG), (k) => dropKeys.has(k));
1588
+ await stripJsonKeys(join6(targetDir, API_PKG), (k) => dropKeys.has(k));
1036
1589
  await stripTsconfigKeys(targetDir, Object.keys(unit.tsPaths));
1037
1590
  if (unit.gatewayService) await stripGatewayTransport(targetDir, unit.gatewayService.prefix);
1038
1591
  if (unit.dockerService === "jobs") await stripJobsDockerCompose(targetDir);
1039
1592
  if (key === "jobs") {
1040
1593
  try {
1041
- await unlink(join5(targetDir, "libs/shared/src/jobs.ts"));
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"));
1042
1599
  } catch {
1043
1600
  }
1044
- const sharedIdx = join5(targetDir, "libs/shared/src/index.ts");
1601
+ const sharedIdx = join6(targetDir, "libs/shared/src/index.ts");
1045
1602
  try {
1046
- const src = await readFile6(sharedIdx, "utf8");
1047
- await writeFile4(sharedIdx, src.replace(/^export \* from '\.\/jobs';\n/m, ""));
1603
+ const src = await readFile7(sharedIdx, "utf8");
1604
+ await writeFile5(sharedIdx, src.replace(/^export \* from '\.\/jobs';\n/m, ""));
1048
1605
  } catch {
1049
1606
  }
1050
1607
  }
1051
1608
  }
1052
1609
  try {
1053
- await rmdir(join5(targetDir, "apps/client/src/queries"));
1610
+ await rmdir(join6(targetDir, "apps/client/src/queries"));
1054
1611
  } catch {
1055
1612
  }
1056
1613
  }
1057
1614
 
1058
1615
  // src/manifest/wire-client.ts
1059
- import { writeFile as writeFile5 } from "fs/promises";
1060
- import { join as join6 } from "path";
1616
+ import { writeFile as writeFile6 } from "fs/promises";
1617
+ import { join as join7 } from "path";
1061
1618
  var NAV_CONFIG_FILE = "apps/client/src/nav.config.ts";
1062
1619
  var BASE_NAV = [
1063
1620
  { to: "/dashboard", labelKey: "nav.dashboard", iconName: "dashboard", exact: true }
@@ -1095,12 +1652,12 @@ export const NAV_CONFIG: NavItem[] = [
1095
1652
  ` + entries.map(renderEntry).join("\n") + `
1096
1653
  ];
1097
1654
  `;
1098
- await writeFile5(join6(targetDir, NAV_CONFIG_FILE), content);
1655
+ await writeFile6(join7(targetDir, NAV_CONFIG_FILE), content);
1099
1656
  }
1100
1657
 
1101
1658
  // src/manifest/blueprint.ts
1102
- import { writeFile as writeFile6 } from "fs/promises";
1103
- import { join as join7 } from "path";
1659
+ import { writeFile as writeFile7 } from "fs/promises";
1660
+ import { join as join8 } from "path";
1104
1661
  async function writeBlueprintJson(targetDir, opts) {
1105
1662
  const blueprint = {
1106
1663
  schemaVersion: 1,
@@ -1115,10 +1672,10 @@ async function writeBlueprintJson(targetDir, opts) {
1115
1672
  transport: opts.transport,
1116
1673
  packageManager: opts.packageManager
1117
1674
  };
1118
- 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");
1119
1676
  }
1120
1677
  async function writeJson(targetDir, rel, data) {
1121
- 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");
1122
1679
  }
1123
1680
  async function writeServiceBlueprints(targetDir, opts) {
1124
1681
  const t = opts.transport;
@@ -1212,8 +1769,8 @@ var writeDbProvider = (targetDir, provider) => writeProvider(targetDir, DB, prov
1212
1769
  var cleanupUnusedDb = (targetDir, chosen) => cleanupUnusedAxis(targetDir, DB, chosen);
1213
1770
 
1214
1771
  // src/lib/scaffold-pkg.ts
1215
- import { readFile as readFile7, writeFile as writeFile7, mkdir, readdir } from "fs/promises";
1216
- 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";
1217
1774
 
1218
1775
  // src/lib/options.ts
1219
1776
  function pmRun(pm, script) {
@@ -1222,8 +1779,8 @@ function pmRun(pm, script) {
1222
1779
 
1223
1780
  // src/lib/scaffold-pkg.ts
1224
1781
  async function writePnpmWorkspace(targetDir) {
1225
- const pkgPath = join8(targetDir, "package.json");
1226
- const pkg = JSON.parse(await readFile7(pkgPath, "utf8"));
1782
+ const pkgPath = join9(targetDir, "package.json");
1783
+ const pkg = JSON.parse(await readFile8(pkgPath, "utf8"));
1227
1784
  const workspaces = pkg.workspaces ?? [];
1228
1785
  const packagesBlock = workspaces.map((p3) => ` - '${p3}'`).join("\n");
1229
1786
  const allowBuilds = [
@@ -1246,7 +1803,7 @@ ${packagesBlock}
1246
1803
  allowBuilds:
1247
1804
  ${allowBuilds}
1248
1805
  `;
1249
- await writeFile7(join8(targetDir, "pnpm-workspace.yaml"), content);
1806
+ await writeFile8(join9(targetDir, "pnpm-workspace.yaml"), content);
1250
1807
  }
1251
1808
  async function rewritePnpmWorkspaceDeps(targetDir) {
1252
1809
  async function walk(dir) {
@@ -1259,9 +1816,9 @@ async function rewritePnpmWorkspaceDeps(targetDir) {
1259
1816
  }
1260
1817
  for (const e of entries) {
1261
1818
  if (e.isDirectory() && e.name !== "node_modules") {
1262
- found.push(...await walk(join8(dir, e.name)));
1819
+ found.push(...await walk(join9(dir, e.name)));
1263
1820
  } else if (e.isFile() && e.name === "package.json") {
1264
- found.push(join8(dir, e.name));
1821
+ found.push(join9(dir, e.name));
1265
1822
  }
1266
1823
  }
1267
1824
  return found;
@@ -1269,17 +1826,17 @@ async function rewritePnpmWorkspaceDeps(targetDir) {
1269
1826
  const pkgFiles = await walk(targetDir);
1270
1827
  for (const f of pkgFiles) {
1271
1828
  try {
1272
- const raw = await readFile7(f, "utf8");
1829
+ const raw = await readFile8(f, "utf8");
1273
1830
  const next = raw.replace(/"(@icore\/[^"]+)":\s*"\*"/g, '"$1": "workspace:*"');
1274
- if (next !== raw) await writeFile7(f, next);
1831
+ if (next !== raw) await writeFile8(f, next);
1275
1832
  } catch {
1276
1833
  }
1277
1834
  }
1278
1835
  }
1279
1836
  async function patchGitignoreForPm(targetDir, pm) {
1280
- const giPath = join8(targetDir, ".gitignore");
1837
+ const giPath = join9(targetDir, ".gitignore");
1281
1838
  try {
1282
- let src = await readFile7(giPath, "utf8");
1839
+ let src = await readFile8(giPath, "utf8");
1283
1840
  src = src.replace(/^# Build artifacts.*\ntools\/create-icore\/templates\/\s*\n/m, "");
1284
1841
  if (pm !== "yarn") {
1285
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, "");
@@ -1294,7 +1851,7 @@ async function patchGitignoreForPm(targetDir, pm) {
1294
1851
  src += "\n# npm\nnpm-debug.log*\n";
1295
1852
  }
1296
1853
  }
1297
- await writeFile7(giPath, src);
1854
+ await writeFile8(giPath, src);
1298
1855
  } catch {
1299
1856
  }
1300
1857
  }
@@ -1310,7 +1867,7 @@ async function writeAiFiles(targetDir, opts) {
1310
1867
  if (opts.jobs !== "none") activeMSes.push(`jobs (standalone)`);
1311
1868
  const usesSupabase = opts.authProvider === "supabase" || opts.dbProvider === "supabase" || opts.upload === "supabase";
1312
1869
  const usesFirebase = opts.authProvider === "firebase" || opts.dbProvider === "firebase" || opts.upload === "firebase";
1313
- await writeFile7(join8(targetDir, "CLAUDE.md"), "@AGENTS.md\n");
1870
+ await writeFile8(join9(targetDir, "CLAUDE.md"), "@AGENTS.md\n");
1314
1871
  const uiLabel = { shadcn: "shadcn/ui + Tailwind", antd: "Ant Design 6", mui: "MUI 6" }[opts.ui];
1315
1872
  const readme = `# ${opts.projectName}
1316
1873
 
@@ -1358,7 +1915,7 @@ ${pm === "yarn" ? "yarn remove-notes" : pm === "pnpm" ? "pnpm remove-notes" : "n
1358
1915
 
1359
1916
  Apache-2.0
1360
1917
  `;
1361
- await writeFile7(join8(targetDir, "README.md"), readme);
1918
+ await writeFile8(join9(targetDir, "README.md"), readme);
1362
1919
  const agents = `# ${opts.projectName} \u2014 Agent Instructions
1363
1920
 
1364
1921
  ## Stack snapshot
@@ -1439,8 +1996,8 @@ ${opts.authProvider !== "none" ? `| \`apps/microservices/auth/.env\` | \`AUTH_PR
1439
1996
  - Test behaviour, not implementation. Fake strategies from \`@icore/shared\` (FakeAuthStrategy etc.) serve as test doubles.
1440
1997
  - Run: \`${nx} test <project>\`
1441
1998
  `;
1442
- await writeFile7(join8(targetDir, "AGENTS.md"), agents);
1443
- await mkdir(join8(targetDir, ".claude"), { recursive: true });
1999
+ await writeFile8(join9(targetDir, "AGENTS.md"), agents);
2000
+ await mkdir2(join9(targetDir, ".claude"), { recursive: true });
1444
2001
  const mcpServers = {
1445
2002
  nx: {
1446
2003
  command: "npx",
@@ -1481,8 +2038,8 @@ ${opts.authProvider !== "none" ? `| \`apps/microservices/auth/.env\` | \`AUTH_PR
1481
2038
  ]
1482
2039
  }
1483
2040
  };
1484
- await writeFile7(
1485
- join8(targetDir, ".claude", "settings.json"),
2041
+ await writeFile8(
2042
+ join9(targetDir, ".claude", "settings.json"),
1486
2043
  JSON.stringify(settings, null, 2) + "\n"
1487
2044
  );
1488
2045
  }
@@ -1502,20 +2059,20 @@ var IGNORE_TOP = /* @__PURE__ */ new Set([
1502
2059
  ".vscode"
1503
2060
  ]);
1504
2061
  async function copyTree(src, dest) {
1505
- await mkdir2(dest, { recursive: true });
2062
+ await mkdir3(dest, { recursive: true });
1506
2063
  const entries = await readdir2(src, { withFileTypes: true });
1507
2064
  for (const entry of entries) {
1508
2065
  if (IGNORE_TOP.has(entry.name)) continue;
1509
- const s = join9(src, entry.name);
1510
- const d = join9(dest, entry.name);
2066
+ const s = join10(src, entry.name);
2067
+ const d = join10(dest, entry.name);
1511
2068
  if (entry.isDirectory()) await copyTree(s, d);
1512
2069
  else if (entry.isFile()) await copyFile(s, d);
1513
2070
  }
1514
2071
  }
1515
2072
  async function selectClientTemplate(targetDir, opts) {
1516
- const templatesRoot = join9(targetDir, "apps/templates");
1517
- const chosen = join9(templatesRoot, `client-${opts.ui}`);
1518
- 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");
1519
2076
  let chosenUi = opts.ui;
1520
2077
  try {
1521
2078
  const s = await stat(chosen);
@@ -1523,9 +2080,9 @@ async function selectClientTemplate(targetDir, opts) {
1523
2080
  await copyTree(chosen, destClient);
1524
2081
  } catch {
1525
2082
  chosenUi = "shadcn";
1526
- await copyTree(join9(templatesRoot, "client-shadcn"), destClient);
2083
+ await copyTree(join10(templatesRoot, "client-shadcn"), destClient);
1527
2084
  }
1528
- await rm4(templatesRoot, { recursive: true, force: true });
2085
+ await rm5(templatesRoot, { recursive: true, force: true });
1529
2086
  await rewriteClientPaths(destClient, chosenUi);
1530
2087
  }
1531
2088
  async function rewriteClientPaths(clientDir, ui) {
@@ -1538,11 +2095,11 @@ async function rewriteClientPaths(clientDir, ui) {
1538
2095
  "eslint.config.mjs"
1539
2096
  ];
1540
2097
  for (const rel of candidates) {
1541
- const path = join9(clientDir, rel);
2098
+ const path = join10(clientDir, rel);
1542
2099
  try {
1543
- const raw = await readFile8(path, "utf8");
2100
+ const raw = await readFile9(path, "utf8");
1544
2101
  const next = raw.replaceAll("../../../", "../../").replaceAll(`apps/templates/client-${ui}`, "apps/client").replaceAll(`client-${ui}`, "client");
1545
- if (next !== raw) await writeFile8(path, next);
2102
+ if (next !== raw) await writeFile9(path, next);
1546
2103
  } catch {
1547
2104
  }
1548
2105
  }
@@ -1558,12 +2115,12 @@ function gitInit(cwd, projectName) {
1558
2115
  }
1559
2116
  function resolveYarnBin(cwd) {
1560
2117
  try {
1561
- const yarnrc = readFileSync(join9(cwd, ".yarnrc.yml"), "utf8");
2118
+ const yarnrc = readFileSync(join10(cwd, ".yarnrc.yml"), "utf8");
1562
2119
  const match = yarnrc.match(/^yarnPath:\s*(.+)$/m);
1563
- if (match?.[1]) return join9(cwd, match[1].trim());
2120
+ if (match?.[1]) return join10(cwd, match[1].trim());
1564
2121
  } catch {
1565
2122
  }
1566
- return join9(cwd, ".yarn", "releases", "yarn-4.5.0.cjs");
2123
+ return join10(cwd, ".yarn", "releases", "yarn-4.5.0.cjs");
1567
2124
  }
1568
2125
  function runInstall(cwd, pm) {
1569
2126
  if (pm === "yarn") {
@@ -1586,6 +2143,10 @@ async function scaffold(rawOpts, templatesDir2) {
1586
2143
  await writeRootEnv(opts.targetDir, opts);
1587
2144
  await selectClientTemplate(opts.targetDir, opts);
1588
2145
  await writeClientEnv(opts.targetDir);
2146
+ if (opts.authProvider === "none") {
2147
+ await removeAuthOnlyPaths(opts.targetDir);
2148
+ await applyAuthNoneVariants(opts.targetDir, opts.ui);
2149
+ }
1589
2150
  if (opts.upload === "none") await removeUploadStack(opts.targetDir);
1590
2151
  await cleanupUnusedFeatures(opts.targetDir, opts);
1591
2152
  await writeFeaturesWiring(opts.targetDir, opts);
@@ -1594,7 +2155,8 @@ async function scaffold(rawOpts, templatesDir2) {
1594
2155
  await cleanupUnusedAuth(opts.targetDir, opts.authProvider);
1595
2156
  await writeAuthProvider(opts.targetDir, opts.authProvider);
1596
2157
  } else {
1597
- await removeAuthStack(opts.targetDir);
2158
+ await removeAuthTsconfigPaths(opts.targetDir);
2159
+ await removeDockerComposeAuthService(opts.targetDir);
1598
2160
  }
1599
2161
  if (opts.upload !== "none") {
1600
2162
  await cleanupUnusedStorage(opts.targetDir, opts.upload);
@@ -1607,25 +2169,28 @@ async function scaffold(rawOpts, templatesDir2) {
1607
2169
  await writeDbProvider(opts.targetDir, opts.dbProvider);
1608
2170
  }
1609
2171
  await pruneRootProviderDeps(opts.targetDir, opts);
1610
- if (opts.authProvider === "none" && opts.upload === "none" && opts.dbProvider === "none") {
2172
+ if (opts.authProvider === "none" && opts.upload === "none" && opts.dbProvider === "none" && opts.payment === "none") {
1611
2173
  await removeStrategiesLib(opts.targetDir);
1612
2174
  }
1613
2175
  try {
1614
- await rmdir2(join9(opts.targetDir, "apps/microservices"));
2176
+ await rmdir2(join10(opts.targetDir, "apps/microservices"));
1615
2177
  } catch {
1616
2178
  }
1617
2179
  await writeBlueprintJson(opts.targetDir, opts);
1618
2180
  await writeServiceBlueprints(opts.targetDir, opts);
1619
2181
  if (opts.packageManager === "yarn") {
1620
- await writeFile8(join9(opts.targetDir, "yarn.lock"), "");
2182
+ await writeFile9(join10(opts.targetDir, "yarn.lock"), "");
1621
2183
  } else {
1622
- await rm4(join9(opts.targetDir, ".yarn"), { recursive: true, force: true });
1623
- 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 });
1624
2186
  }
1625
2187
  if (opts.packageManager === "pnpm") {
1626
2188
  await writePnpmWorkspace(opts.targetDir);
1627
2189
  await rewritePnpmWorkspaceDeps(opts.targetDir);
1628
2190
  }
2191
+ if (opts.packageManager === "npm") {
2192
+ await writeFile9(join10(opts.targetDir, ".npmrc"), "legacy-peer-deps=true\n");
2193
+ }
1629
2194
  await patchGitignoreForPm(opts.targetDir, opts.packageManager);
1630
2195
  await writeAiFiles(opts.targetDir, opts);
1631
2196
  if (opts.install) runInstall(opts.targetDir, opts.packageManager);
@@ -1642,7 +2207,7 @@ Upgrade: https://nodejs.org
1642
2207
  );
1643
2208
  process.exit(1);
1644
2209
  }
1645
- var here = dirname2(fileURLToPath2(import.meta.url));
2210
+ var here = dirname3(fileURLToPath2(import.meta.url));
1646
2211
  var templatesDir = resolve2(here, "..", "templates");
1647
2212
  async function main() {
1648
2213
  if (!existsSync(templatesDir)) {