@idevconn/create-icore 0.9.2 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +832 -130
- package/dist/index.cjs +848 -149
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +841 -142
- package/package.json +4 -4
- package/templates/.yarn/releases/{yarn-4.16.0.cjs → yarn-4.17.0.cjs} +326 -326
- package/templates/.yarnrc.yml +1 -1
- package/templates/apps/api/package.json +6 -6
- package/templates/apps/microservices/auth/package.json +4 -4
- package/templates/apps/microservices/jobs/package.json +5 -5
- package/templates/apps/microservices/notes/package.json +5 -5
- package/templates/apps/microservices/payment/package.json +4 -4
- package/templates/apps/microservices/upload/package.json +6 -6
- package/templates/apps/templates/client-antd/package.json +2 -2
- package/templates/apps/templates/client-mui/package.json +4 -4
- package/templates/apps/templates/client-shadcn/package.json +7 -7
- package/templates/apps/templates/client-shadcn/src/components/ui/button.tsx +1 -2
- package/templates/libs/auth-client/package.json +3 -3
- package/templates/libs/auth-strategies/firebase/package.json +4 -4
- package/templates/libs/auth-strategies/mongodb/package.json +4 -4
- package/templates/libs/auth-strategies/supabase/package.json +5 -5
- package/templates/libs/db-strategies/firestore/package.json +4 -4
- package/templates/libs/db-strategies/mongodb/package.json +3 -3
- package/templates/libs/db-strategies/supabase/package.json +5 -5
- package/templates/libs/firebase-admin/package.json +1 -1
- package/templates/libs/jobs-client/package.json +4 -4
- package/templates/libs/notes-client/package.json +3 -3
- package/templates/libs/payment-client/package.json +3 -3
- package/templates/libs/shared/package.json +2 -2
- package/templates/libs/storage-strategies/cloudinary/package.json +4 -4
- package/templates/libs/storage-strategies/firebase/package.json +4 -4
- package/templates/libs/storage-strategies/mongodb/package.json +3 -3
- package/templates/libs/storage-strategies/supabase/package.json +5 -5
- package/templates/libs/template-shared/package.json +8 -8
- package/templates/libs/upload-client/package.json +3 -3
- package/templates/libs/vite-plugins/package.json +3 -3
- package/templates/package.json +32 -32
- package/templates/tools/create-icore/_template-shell/package.json +32 -32
package/dist/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
|
|
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
|
|
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
|
|
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
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
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(
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
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
|
|
605
|
+
const pkgPath = join3(targetDir, "libs/shared/package.json");
|
|
613
606
|
try {
|
|
614
|
-
const
|
|
615
|
-
|
|
616
|
-
|
|
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
|
|
655
|
-
import { join as
|
|
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
|
|
816
|
-
import { join as
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
1509
|
+
const path = join5(targetDir, "tsconfig.base.json");
|
|
843
1510
|
try {
|
|
844
|
-
const parsed = JSON.parse(await
|
|
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
|
|
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
|
|
857
|
-
for (const t of unit.appTests ?? []) await
|
|
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(
|
|
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
|
|
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
|
|
1569
|
+
await writeFile5(join6(targetDir, GATEWAY_SERVICES), gatewayServices);
|
|
903
1570
|
}
|
|
904
1571
|
async function stripJobsDockerCompose(targetDir) {
|
|
905
|
-
const composePath =
|
|
1572
|
+
const composePath = join6(targetDir, "docker-compose.yml");
|
|
906
1573
|
try {
|
|
907
|
-
const compose = await
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
930
|
-
import { join as
|
|
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
|
|
1655
|
+
await writeFile6(join7(targetDir, NAV_CONFIG_FILE), content);
|
|
969
1656
|
}
|
|
970
1657
|
|
|
971
1658
|
// src/manifest/blueprint.ts
|
|
972
|
-
import { writeFile as
|
|
973
|
-
import { join as
|
|
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
|
|
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
|
|
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
|
|
1086
|
-
import { join as
|
|
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 =
|
|
1096
|
-
const pkg = JSON.parse(await
|
|
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
|
|
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(
|
|
1819
|
+
found.push(...await walk(join9(dir, e.name)));
|
|
1133
1820
|
} else if (e.isFile() && e.name === "package.json") {
|
|
1134
|
-
found.push(
|
|
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
|
|
1829
|
+
const raw = await readFile8(f, "utf8");
|
|
1143
1830
|
const next = raw.replace(/"(@icore\/[^"]+)":\s*"\*"/g, '"$1": "workspace:*"');
|
|
1144
|
-
if (next !== raw) await
|
|
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 =
|
|
1837
|
+
const giPath = join9(targetDir, ".gitignore");
|
|
1151
1838
|
try {
|
|
1152
|
-
let src = await
|
|
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
|
|
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 = [
|
|
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
|
|
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
|
|
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
|
-
|
|
1303
|
-
${opts.upload !== "none" ? `| \`apps/microservices/upload/.env\` | \`STORAGE_PROVIDER=${opts.upload}\`, provider creds |
|
|
1304
|
-
` : ""}
|
|
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
|
|
1314
|
-
await
|
|
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
|
|
1356
|
-
|
|
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
|
|
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 =
|
|
1381
|
-
const d =
|
|
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 =
|
|
1388
|
-
const chosen =
|
|
1389
|
-
const destClient =
|
|
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(
|
|
2083
|
+
await copyTree(join10(templatesRoot, "client-shadcn"), destClient);
|
|
1398
2084
|
}
|
|
1399
|
-
await
|
|
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 =
|
|
2098
|
+
const path = join10(clientDir, rel);
|
|
1413
2099
|
try {
|
|
1414
|
-
const raw = await
|
|
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
|
|
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(
|
|
2118
|
+
const yarnrc = readFileSync(join10(cwd, ".yarnrc.yml"), "utf8");
|
|
1433
2119
|
const match = yarnrc.match(/^yarnPath:\s*(.+)$/m);
|
|
1434
|
-
if (match?.[1]) return
|
|
2120
|
+
if (match?.[1]) return join10(cwd, match[1].trim());
|
|
1435
2121
|
} catch {
|
|
1436
2122
|
}
|
|
1437
|
-
return
|
|
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(
|
|
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
|
|
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
|
|
2182
|
+
await writeFile9(join10(opts.targetDir, "yarn.lock"), "");
|
|
1484
2183
|
} else {
|
|
1485
|
-
await
|
|
1486
|
-
await
|
|
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 =
|
|
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
|
-
|
|
1529
|
-
|
|
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
|
-
|
|
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));
|