@idevconn/create-icore 0.4.0 → 0.5.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 +238 -2
- package/dist/index.cjs +238 -2
- package/dist/index.js +238 -2
- package/package.json +1 -1
- package/templates/.yarn/releases/yarn-4.15.0.cjs +940 -0
- package/templates/.yarnrc.yml +6 -1
- package/templates/apps/api/src/app/app.module.ts +5 -1
- package/templates/apps/api/src/main.ts +12 -6
- package/templates/apps/microservices/auth/project.json +2 -1
- package/templates/apps/microservices/auth/src/app/app.module.ts +47 -23
- package/templates/apps/microservices/jobs/project.json +2 -1
- package/templates/apps/microservices/notes/project.json +2 -1
- package/templates/apps/microservices/notes/src/app/app.module.ts +44 -25
- package/templates/apps/microservices/payment/project.json +2 -1
- package/templates/apps/microservices/payment/src/app/app.module.ts +35 -12
- package/templates/apps/microservices/upload/project.json +2 -1
- package/templates/apps/microservices/upload/src/app/app.module.ts +48 -28
- package/templates/apps/templates/client-antd/.env.example +7 -0
- package/templates/apps/templates/client-antd/vite.config.mts +4 -4
- package/templates/apps/templates/client-mui/.env.example +7 -0
- package/templates/apps/templates/client-mui/vite.config.mts +4 -4
- package/templates/apps/templates/client-shadcn/.env.example +6 -1
- package/templates/apps/templates/client-shadcn/vite.config.mts +4 -4
- package/templates/libs/auth-client/src/index.ts +1 -0
- package/templates/libs/auth-client/src/lib/auth-client.module.ts +1 -1
- package/templates/libs/auth-client/src/lib/auth-client.service.ts +1 -1
- package/templates/libs/auth-client/src/lib/auth-client.tokens.ts +4 -0
- package/templates/libs/jobs-client/src/index.ts +1 -0
- package/templates/libs/jobs-client/src/lib/jobs-client.module.ts +1 -1
- package/templates/libs/jobs-client/src/lib/jobs-client.service.ts +1 -1
- package/templates/libs/jobs-client/src/lib/jobs-client.tokens.ts +4 -0
- package/templates/libs/notes-client/src/index.ts +1 -0
- package/templates/libs/notes-client/src/lib/notes-client.module.ts +1 -1
- package/templates/libs/notes-client/src/lib/notes-client.service.ts +1 -1
- package/templates/libs/notes-client/src/lib/notes-client.tokens.ts +4 -0
- package/templates/libs/payment-client/src/index.ts +1 -0
- package/templates/libs/payment-client/src/lib/payment-client.module.ts +1 -1
- package/templates/libs/payment-client/src/lib/payment-client.service.ts +1 -1
- package/templates/libs/payment-client/src/lib/payment-client.tokens.ts +4 -0
- package/templates/libs/shared/src/env.ts +88 -0
- package/templates/libs/shared/src/index.ts +1 -0
- package/templates/libs/shared/src/transport.ts +37 -0
- package/templates/libs/upload-client/src/index.ts +1 -0
- package/templates/libs/upload-client/src/lib/upload-client.module.ts +1 -1
- package/templates/libs/upload-client/src/lib/upload-client.service.ts +1 -1
- package/templates/libs/upload-client/src/lib/upload-client.tokens.ts +4 -0
- package/templates/libs/vite-plugins/src/index.d.mts +6 -0
- package/templates/libs/vite-plugins/src/index.mjs +50 -0
- package/templates/package.json +1 -0
- package/templates/tools/create-icore/_template-shell/package.json +1 -0
package/dist/cli.js
CHANGED
|
@@ -201,6 +201,12 @@ Re-run with @latest to refresh:
|
|
|
201
201
|
});
|
|
202
202
|
if (p.isCancel(transport)) throw new Error("cancelled");
|
|
203
203
|
const packageManager = flags.packageManager ?? detectPackageManager();
|
|
204
|
+
if (packageManager === "yarn") {
|
|
205
|
+
p.note(
|
|
206
|
+
"yarn 4.15+ enforces a 24h publish-age gate (npmMinimalAgeGate=1d), so a\n`yarn create @idevconn/icore@latest` run within 24h of a release resolves an\nolder version. If the banner above shows an unexpectedly old version, either:\n \u2022 wait \u2014 the version auto-unlocks 24h after publish, or\n \u2022 bypass once: yarn config set npmMinimalAgeGate 0 (then re-run), or\n \u2022 use npm/pnpm: npm init @idevconn/icore@latest <name> -- [flags]",
|
|
207
|
+
"\u26A0 yarn 24h age-gate"
|
|
208
|
+
);
|
|
209
|
+
}
|
|
204
210
|
const initGit = flags.initGit ?? !await p.confirm({ message: "Initialise git repo?", initialValue: true }) === false;
|
|
205
211
|
const install = flags.install ?? !await p.confirm({
|
|
206
212
|
message: `Run ${packageManager} install?`,
|
|
@@ -225,6 +231,7 @@ Re-run with @latest to refresh:
|
|
|
225
231
|
|
|
226
232
|
// src/lib/scaffold.ts
|
|
227
233
|
import { copyFile, mkdir, readdir, readFile as readFile2, stat, writeFile, rm } from "fs/promises";
|
|
234
|
+
import { readFileSync } from "fs";
|
|
228
235
|
import { join as join2 } from "path";
|
|
229
236
|
import { spawnSync } from "child_process";
|
|
230
237
|
var IGNORE_TOP = /* @__PURE__ */ new Set([
|
|
@@ -262,6 +269,22 @@ async function rewriteRootPackageJson(targetDir, opts) {
|
|
|
262
269
|
if (opts.packageManager !== "yarn") {
|
|
263
270
|
delete pkg.packageManager;
|
|
264
271
|
}
|
|
272
|
+
if (opts.packageManager === "pnpm") {
|
|
273
|
+
pkg["pnpm"] = {
|
|
274
|
+
onlyBuiltDependencies: [
|
|
275
|
+
"@firebase/util",
|
|
276
|
+
"@nestjs/core",
|
|
277
|
+
"@parcel/watcher",
|
|
278
|
+
"@scarf/scarf",
|
|
279
|
+
"@swc/core",
|
|
280
|
+
"less",
|
|
281
|
+
"msgpackr-extract",
|
|
282
|
+
"nx",
|
|
283
|
+
"protobufjs",
|
|
284
|
+
"unrs-resolver"
|
|
285
|
+
]
|
|
286
|
+
};
|
|
287
|
+
}
|
|
265
288
|
await writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
266
289
|
}
|
|
267
290
|
async function writeAuthEnv(targetDir, opts) {
|
|
@@ -314,6 +337,14 @@ async function writeRootEnv(targetDir, opts) {
|
|
|
314
337
|
];
|
|
315
338
|
await writeFile(join2(targetDir, ".env"), lines.join("\n"));
|
|
316
339
|
}
|
|
340
|
+
async function writeClientEnv(targetDir) {
|
|
341
|
+
const envExample = join2(targetDir, "apps/client/.env.example");
|
|
342
|
+
try {
|
|
343
|
+
const env = await readFile2(envExample, "utf8");
|
|
344
|
+
await writeFile(join2(targetDir, "apps/client/.env"), env);
|
|
345
|
+
} catch {
|
|
346
|
+
}
|
|
347
|
+
}
|
|
317
348
|
async function writePaymentEnv(targetDir, opts) {
|
|
318
349
|
if (opts.payment === "none") return;
|
|
319
350
|
const envExample = join2(targetDir, "apps/microservices/payment/.env.example");
|
|
@@ -662,9 +693,23 @@ function gitInit(cwd, projectName) {
|
|
|
662
693
|
{ cwd, stdio: "inherit" }
|
|
663
694
|
);
|
|
664
695
|
}
|
|
696
|
+
function resolveYarnBin(cwd) {
|
|
697
|
+
try {
|
|
698
|
+
const yarnrc = readFileSync(join2(cwd, ".yarnrc.yml"), "utf8");
|
|
699
|
+
const match = yarnrc.match(/^yarnPath:\s*(.+)$/m);
|
|
700
|
+
if (match?.[1]) return join2(cwd, match[1].trim());
|
|
701
|
+
} catch {
|
|
702
|
+
}
|
|
703
|
+
return join2(cwd, ".yarn", "releases", "yarn-4.5.0.cjs");
|
|
704
|
+
}
|
|
665
705
|
function runInstall(cwd, pm) {
|
|
666
|
-
|
|
667
|
-
|
|
706
|
+
if (pm === "yarn") {
|
|
707
|
+
spawnSync("node", [resolveYarnBin(cwd), "install"], { cwd, stdio: "inherit" });
|
|
708
|
+
} else if (pm === "npm") {
|
|
709
|
+
spawnSync("npm", ["install"], { cwd, stdio: "inherit" });
|
|
710
|
+
} else {
|
|
711
|
+
spawnSync("pnpm", ["install"], { cwd, stdio: "inherit" });
|
|
712
|
+
}
|
|
668
713
|
}
|
|
669
714
|
async function scaffold(opts, templatesDir2) {
|
|
670
715
|
await copyTree(templatesDir2, opts.targetDir);
|
|
@@ -676,6 +721,7 @@ async function scaffold(opts, templatesDir2) {
|
|
|
676
721
|
await writeGatewayEnv(opts.targetDir, opts);
|
|
677
722
|
await writeRootEnv(opts.targetDir, opts);
|
|
678
723
|
await selectClientTemplate(opts.targetDir, opts);
|
|
724
|
+
await writeClientEnv(opts.targetDir);
|
|
679
725
|
if (opts.upload === "none") await removeUploadStack(opts.targetDir);
|
|
680
726
|
if (opts.payment === "none") await removePaymentStack(opts.targetDir);
|
|
681
727
|
if (opts.jobs === "none") await removeJobsStack(opts.targetDir);
|
|
@@ -686,9 +732,199 @@ async function scaffold(opts, templatesDir2) {
|
|
|
686
732
|
if (opts.packageManager === "yarn") {
|
|
687
733
|
await writeFile(join2(opts.targetDir, "yarn.lock"), "");
|
|
688
734
|
}
|
|
735
|
+
await writeAiFiles(opts.targetDir, opts);
|
|
689
736
|
if (opts.install) runInstall(opts.targetDir, opts.packageManager);
|
|
690
737
|
if (opts.initGit) gitInit(opts.targetDir, opts.projectName);
|
|
691
738
|
}
|
|
739
|
+
async function writeAiFiles(targetDir, opts) {
|
|
740
|
+
const pm = opts.packageManager;
|
|
741
|
+
const nx = pm === "npm" ? "npx nx" : `${pm} nx`;
|
|
742
|
+
const devCmd = `${pm} dev`;
|
|
743
|
+
const activeMSes = ["auth (port 4001)"];
|
|
744
|
+
if (opts.upload !== "none") activeMSes.push(`upload (port 4002)`);
|
|
745
|
+
if (opts.payment !== "none") activeMSes.push(`payment (port 4003)`);
|
|
746
|
+
if (opts.example !== "none") activeMSes.push(`notes (port 4004)`);
|
|
747
|
+
if (opts.jobs !== "none") activeMSes.push(`jobs (standalone)`);
|
|
748
|
+
const usesSupabase = opts.authProvider === "supabase" || opts.dbProvider === "supabase" || opts.upload === "supabase";
|
|
749
|
+
const usesFirebase = opts.authProvider === "firebase" || opts.dbProvider === "firebase" || opts.upload === "firebase";
|
|
750
|
+
await writeFile(join2(targetDir, "CLAUDE.md"), "@AGENTS.md\n");
|
|
751
|
+
const uiLabel = { shadcn: "shadcn/ui + Tailwind", antd: "Ant Design 6", mui: "MUI 6" }[opts.ui];
|
|
752
|
+
const readme = `# ${opts.projectName}
|
|
753
|
+
|
|
754
|
+
> Scaffolded with [iCore](https://github.com/iDEVconn/create-icore) \u2014 Nx + NestJS + React full-stack template.
|
|
755
|
+
|
|
756
|
+
## Stack
|
|
757
|
+
|
|
758
|
+
| Layer | Technology |
|
|
759
|
+
|-------|-----------|
|
|
760
|
+
| Monorepo | Nx + ${pm} |
|
|
761
|
+
| Gateway | NestJS 11 + Swagger |
|
|
762
|
+
| Auth | ${opts.authProvider} |
|
|
763
|
+
| Database | ${opts.dbProvider} |
|
|
764
|
+
| Upload | ${opts.upload === "none" ? "\u2014" : opts.upload} |
|
|
765
|
+
| UI | ${uiLabel} + TanStack Router + Query |
|
|
766
|
+
| i18n | i18next (en / ru / he) |
|
|
767
|
+
|
|
768
|
+
## Quick start
|
|
769
|
+
|
|
770
|
+
\`\`\`bash
|
|
771
|
+
# 1. Fill in provider credentials
|
|
772
|
+
# apps/microservices/auth/.env
|
|
773
|
+
# apps/microservices/upload/.env (if upload is enabled)
|
|
774
|
+
# apps/client/.env (VITE_API_URL \u2014 already defaults to /api)
|
|
775
|
+
|
|
776
|
+
# 2. Start everything
|
|
777
|
+
${devCmd}
|
|
778
|
+
# \u2192 http://localhost:4200 client
|
|
779
|
+
# \u2192 http://localhost:3001/api/docs Swagger
|
|
780
|
+
\`\`\`
|
|
781
|
+
|
|
782
|
+
## Commands
|
|
783
|
+
|
|
784
|
+
\`\`\`bash
|
|
785
|
+
${nx} run <project>:serve # start a single service
|
|
786
|
+
${nx} test <project> # unit tests
|
|
787
|
+
${nx} lint <project> # lint
|
|
788
|
+
${nx} build <project> # production build
|
|
789
|
+
${pm === "yarn" ? "yarn remove-notes" : pm === "pnpm" ? "pnpm remove-notes" : "npm run remove-notes"} # strip the notes sample feature
|
|
790
|
+
\`\`\`
|
|
791
|
+
|
|
792
|
+
## Scaffolded by
|
|
793
|
+
|
|
794
|
+
[iCore](https://github.com/iDEVconn/create-icore) \u2014 [@idevconn/create-icore](https://www.npmjs.com/package/@idevconn/create-icore)
|
|
795
|
+
|
|
796
|
+
## License
|
|
797
|
+
|
|
798
|
+
Apache-2.0
|
|
799
|
+
`;
|
|
800
|
+
await writeFile(join2(targetDir, "README.md"), readme);
|
|
801
|
+
const agents = `# ${opts.projectName} \u2014 Agent Instructions
|
|
802
|
+
|
|
803
|
+
## Stack snapshot
|
|
804
|
+
|
|
805
|
+
| Dimension | Choice |
|
|
806
|
+
|------------|--------|
|
|
807
|
+
| Auth | ${opts.authProvider} |
|
|
808
|
+
| Database | ${opts.dbProvider} |
|
|
809
|
+
| Upload | ${opts.upload} |
|
|
810
|
+
| Payment | ${opts.payment} |
|
|
811
|
+
| Jobs | ${opts.jobs} |
|
|
812
|
+
| UI | ${opts.ui} |
|
|
813
|
+
| Transport | ${opts.transport} |
|
|
814
|
+
| PM | ${pm} |
|
|
815
|
+
|
|
816
|
+
## \u{1F680} Mandatory Workflow
|
|
817
|
+
|
|
818
|
+
- **Branch strategy**: \`dev\` is default. Cut \`feature/<name>\` or \`bug/<name>\` from dev. PRs only target dev. Never push directly to main.
|
|
819
|
+
- **No code without approval**: Propose changes first, wait for go-ahead.
|
|
820
|
+
- **\u0417\u0410\u041A\u041E\u041D \u2014 no crash on missing .env**: MS factories must catch config errors, print a boxed banner with ALL missing vars, and return a Fake strategy in dev. In prod (\`NODE_ENV=production\`) throw the same banner. The \`formatEnvBanner\` + \`missingEnv\` helpers from \`@icore/shared\` handle this.
|
|
821
|
+
- **Post-coding routine**: \`npx prettier --write <files>\` \u2192 \`${nx} lint <project>\` \u2192 \`${nx} build <project>\` \u2014 all green before committing.
|
|
822
|
+
- **Nx generators only**: never hand-write \`project.json\` / tsconfig stacks. Use \`${nx} g @nx/<plugin>:<schematic>\`.
|
|
823
|
+
|
|
824
|
+
## Architecture
|
|
825
|
+
|
|
826
|
+
\`\`\`
|
|
827
|
+
apps/
|
|
828
|
+
\u251C\u2500\u2500 api/ NestJS gateway \u2014 all client traffic enters here (:3001)
|
|
829
|
+
\u251C\u2500\u2500 microservices/
|
|
830
|
+
${activeMSes.map((s) => `\u2502 \u251C\u2500\u2500 ${s.split(" ")[0]}/`).join("\n")}
|
|
831
|
+
\u2514\u2500\u2500 client/ Vite + React 19 + ${opts.ui} (:4200)
|
|
832
|
+
libs/
|
|
833
|
+
\u251C\u2500\u2500 shared/ contracts, CASL, transport helpers, env banner utils
|
|
834
|
+
\u251C\u2500\u2500 auth-strategies/${opts.authProvider}/
|
|
835
|
+
${opts.upload !== "none" ? `\u251C\u2500\u2500 storage-strategies/${opts.upload}/
|
|
836
|
+
` : ""}\u251C\u2500\u2500 db-strategies/${opts.dbProvider === "firebase" ? "firestore" : opts.dbProvider}/
|
|
837
|
+
\u251C\u2500\u2500 auth-client/ gateway \u2192 auth MS (TCP/Redis/NATS)
|
|
838
|
+
${opts.upload !== "none" ? `\u251C\u2500\u2500 upload-client/ gateway \u2192 upload MS
|
|
839
|
+
` : ""}\u2514\u2500\u2500 template-shared/ browser-safe React foundation (stores, i18n, CASL)
|
|
840
|
+
\`\`\`
|
|
841
|
+
|
|
842
|
+
## Key patterns
|
|
843
|
+
|
|
844
|
+
**Strategy swap** \u2014 provider is chosen at runtime via env. Never import a concrete strategy in app code; always inject via the factory token (\`AuthStrategy\`, \`StorageStrategy\`, \`DBStrategy\`).
|
|
845
|
+
|
|
846
|
+
**Transport** \u2014 \`buildTransport(prefix)\` reads \`${opts.transport.toUpperCase()}*\` vars. Same helper on gateway client-modules and each MS \`main.ts\`. Supports tcp / redis / nats \u2014 change by flipping \`*_TRANSPORT\` in \`.env\`.
|
|
847
|
+
|
|
848
|
+
**Env layering**:
|
|
849
|
+
1. Root \`.env\` \u2014 \`DB_PROVIDER\`
|
|
850
|
+
2. \`apps/api/.env\` \u2014 gateway transport endpoints
|
|
851
|
+
3. \`apps/microservices/<name>/.env\` \u2014 each MS provider + transport
|
|
852
|
+
4. \`apps/client/.env\` \u2014 \`VITE_API_URL\`
|
|
853
|
+
|
|
854
|
+
## Commands
|
|
855
|
+
|
|
856
|
+
\`\`\`bash
|
|
857
|
+
${devCmd} # start all services
|
|
858
|
+
${nx} run api:serve # gateway only
|
|
859
|
+
${nx} run auth:serve # auth MS only
|
|
860
|
+
${nx} test <project> # run tests
|
|
861
|
+
${nx} lint <project> # lint
|
|
862
|
+
${nx} build <project> # build
|
|
863
|
+
${nx} g @nx/nest:resource # generate NestJS resource
|
|
864
|
+
\`\`\`
|
|
865
|
+
|
|
866
|
+
## .env files to configure
|
|
867
|
+
|
|
868
|
+
| File | Key vars |
|
|
869
|
+
|------|----------|
|
|
870
|
+
| \`apps/microservices/auth/.env\` | \`AUTH_PROVIDER=${opts.authProvider}\`, ${opts.authProvider === "supabase" ? "`SUPABASE_URL`, `SUPABASE_SERVICE_ROLE_KEY`" : "`FB_ADMIN_*`, `FIREBASE_WEB_API_KEY`"} |
|
|
871
|
+
${opts.upload !== "none" ? `| \`apps/microservices/upload/.env\` | \`STORAGE_PROVIDER=${opts.upload}\`, provider creds |
|
|
872
|
+
` : ""}| \`apps/microservices/notes/.env\` | \`DB_PROVIDER=${opts.dbProvider}\`, DB creds |
|
|
873
|
+
| \`apps/client/.env\` | \`VITE_API_URL=/api\` (proxied to :3001 in dev) |
|
|
874
|
+
|
|
875
|
+
## Testing
|
|
876
|
+
|
|
877
|
+
- Unit tests: Vitest, files named \`*.unit.test.ts(x)\` in \`__tests__/\` next to source.
|
|
878
|
+
- Test behaviour, not implementation. Fake strategies from \`@icore/shared\` (FakeAuthStrategy etc.) serve as test doubles.
|
|
879
|
+
- Run: \`${nx} test <project>\`
|
|
880
|
+
`;
|
|
881
|
+
await writeFile(join2(targetDir, "AGENTS.md"), agents);
|
|
882
|
+
await mkdir(join2(targetDir, ".claude"), { recursive: true });
|
|
883
|
+
const mcpServers = {
|
|
884
|
+
nx: {
|
|
885
|
+
command: "npx",
|
|
886
|
+
args: ["-y", "@nx/mcp@latest", "--directory", "."],
|
|
887
|
+
type: "stdio"
|
|
888
|
+
}
|
|
889
|
+
};
|
|
890
|
+
if (usesSupabase) {
|
|
891
|
+
mcpServers["supabase"] = {
|
|
892
|
+
command: "npx",
|
|
893
|
+
args: [
|
|
894
|
+
"-y",
|
|
895
|
+
"@supabase/mcp-server-supabase@latest",
|
|
896
|
+
"--access-token",
|
|
897
|
+
"<SUPABASE_PERSONAL_ACCESS_TOKEN>"
|
|
898
|
+
],
|
|
899
|
+
type: "stdio"
|
|
900
|
+
};
|
|
901
|
+
}
|
|
902
|
+
if (usesFirebase) {
|
|
903
|
+
mcpServers["firebase"] = {
|
|
904
|
+
command: "npx",
|
|
905
|
+
args: ["-y", "firebase-tools@latest", "experimental:mcp"],
|
|
906
|
+
type: "stdio"
|
|
907
|
+
};
|
|
908
|
+
}
|
|
909
|
+
const nxCmds = [`Bash(${nx} *)`, `Bash(${devCmd})`];
|
|
910
|
+
if (pm !== "npm") nxCmds.push(`Bash(npx nx *)`);
|
|
911
|
+
const settings = {
|
|
912
|
+
mcpServers,
|
|
913
|
+
permissions: {
|
|
914
|
+
allow: [
|
|
915
|
+
...nxCmds,
|
|
916
|
+
"Bash(npx prettier *)",
|
|
917
|
+
"Bash(git status)",
|
|
918
|
+
"Bash(git diff *)",
|
|
919
|
+
"Bash(git log *)"
|
|
920
|
+
]
|
|
921
|
+
}
|
|
922
|
+
};
|
|
923
|
+
await writeFile(
|
|
924
|
+
join2(targetDir, ".claude", "settings.json"),
|
|
925
|
+
JSON.stringify(settings, null, 2) + "\n"
|
|
926
|
+
);
|
|
927
|
+
}
|
|
692
928
|
|
|
693
929
|
// src/cli.ts
|
|
694
930
|
var [nodeMajor] = process.versions.node.split(".").map(Number);
|
package/dist/index.cjs
CHANGED
|
@@ -41,6 +41,7 @@ var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
|
|
|
41
41
|
|
|
42
42
|
// src/lib/scaffold.ts
|
|
43
43
|
var import_promises = require("fs/promises");
|
|
44
|
+
var import_node_fs = require("fs");
|
|
44
45
|
var import_node_path = require("path");
|
|
45
46
|
var import_node_child_process = require("child_process");
|
|
46
47
|
var IGNORE_TOP = /* @__PURE__ */ new Set([
|
|
@@ -78,6 +79,22 @@ async function rewriteRootPackageJson(targetDir, opts) {
|
|
|
78
79
|
if (opts.packageManager !== "yarn") {
|
|
79
80
|
delete pkg.packageManager;
|
|
80
81
|
}
|
|
82
|
+
if (opts.packageManager === "pnpm") {
|
|
83
|
+
pkg["pnpm"] = {
|
|
84
|
+
onlyBuiltDependencies: [
|
|
85
|
+
"@firebase/util",
|
|
86
|
+
"@nestjs/core",
|
|
87
|
+
"@parcel/watcher",
|
|
88
|
+
"@scarf/scarf",
|
|
89
|
+
"@swc/core",
|
|
90
|
+
"less",
|
|
91
|
+
"msgpackr-extract",
|
|
92
|
+
"nx",
|
|
93
|
+
"protobufjs",
|
|
94
|
+
"unrs-resolver"
|
|
95
|
+
]
|
|
96
|
+
};
|
|
97
|
+
}
|
|
81
98
|
await (0, import_promises.writeFile)(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
82
99
|
}
|
|
83
100
|
async function writeAuthEnv(targetDir, opts) {
|
|
@@ -130,6 +147,14 @@ async function writeRootEnv(targetDir, opts) {
|
|
|
130
147
|
];
|
|
131
148
|
await (0, import_promises.writeFile)((0, import_node_path.join)(targetDir, ".env"), lines.join("\n"));
|
|
132
149
|
}
|
|
150
|
+
async function writeClientEnv(targetDir) {
|
|
151
|
+
const envExample = (0, import_node_path.join)(targetDir, "apps/client/.env.example");
|
|
152
|
+
try {
|
|
153
|
+
const env = await (0, import_promises.readFile)(envExample, "utf8");
|
|
154
|
+
await (0, import_promises.writeFile)((0, import_node_path.join)(targetDir, "apps/client/.env"), env);
|
|
155
|
+
} catch {
|
|
156
|
+
}
|
|
157
|
+
}
|
|
133
158
|
async function writePaymentEnv(targetDir, opts) {
|
|
134
159
|
if (opts.payment === "none") return;
|
|
135
160
|
const envExample = (0, import_node_path.join)(targetDir, "apps/microservices/payment/.env.example");
|
|
@@ -478,9 +503,23 @@ function gitInit(cwd, projectName) {
|
|
|
478
503
|
{ cwd, stdio: "inherit" }
|
|
479
504
|
);
|
|
480
505
|
}
|
|
506
|
+
function resolveYarnBin(cwd) {
|
|
507
|
+
try {
|
|
508
|
+
const yarnrc = (0, import_node_fs.readFileSync)((0, import_node_path.join)(cwd, ".yarnrc.yml"), "utf8");
|
|
509
|
+
const match = yarnrc.match(/^yarnPath:\s*(.+)$/m);
|
|
510
|
+
if (match?.[1]) return (0, import_node_path.join)(cwd, match[1].trim());
|
|
511
|
+
} catch {
|
|
512
|
+
}
|
|
513
|
+
return (0, import_node_path.join)(cwd, ".yarn", "releases", "yarn-4.5.0.cjs");
|
|
514
|
+
}
|
|
481
515
|
function runInstall(cwd, pm) {
|
|
482
|
-
|
|
483
|
-
|
|
516
|
+
if (pm === "yarn") {
|
|
517
|
+
(0, import_node_child_process.spawnSync)("node", [resolveYarnBin(cwd), "install"], { cwd, stdio: "inherit" });
|
|
518
|
+
} else if (pm === "npm") {
|
|
519
|
+
(0, import_node_child_process.spawnSync)("npm", ["install"], { cwd, stdio: "inherit" });
|
|
520
|
+
} else {
|
|
521
|
+
(0, import_node_child_process.spawnSync)("pnpm", ["install"], { cwd, stdio: "inherit" });
|
|
522
|
+
}
|
|
484
523
|
}
|
|
485
524
|
async function scaffold(opts, templatesDir) {
|
|
486
525
|
await copyTree(templatesDir, opts.targetDir);
|
|
@@ -492,6 +531,7 @@ async function scaffold(opts, templatesDir) {
|
|
|
492
531
|
await writeGatewayEnv(opts.targetDir, opts);
|
|
493
532
|
await writeRootEnv(opts.targetDir, opts);
|
|
494
533
|
await selectClientTemplate(opts.targetDir, opts);
|
|
534
|
+
await writeClientEnv(opts.targetDir);
|
|
495
535
|
if (opts.upload === "none") await removeUploadStack(opts.targetDir);
|
|
496
536
|
if (opts.payment === "none") await removePaymentStack(opts.targetDir);
|
|
497
537
|
if (opts.jobs === "none") await removeJobsStack(opts.targetDir);
|
|
@@ -502,9 +542,199 @@ async function scaffold(opts, templatesDir) {
|
|
|
502
542
|
if (opts.packageManager === "yarn") {
|
|
503
543
|
await (0, import_promises.writeFile)((0, import_node_path.join)(opts.targetDir, "yarn.lock"), "");
|
|
504
544
|
}
|
|
545
|
+
await writeAiFiles(opts.targetDir, opts);
|
|
505
546
|
if (opts.install) runInstall(opts.targetDir, opts.packageManager);
|
|
506
547
|
if (opts.initGit) gitInit(opts.targetDir, opts.projectName);
|
|
507
548
|
}
|
|
549
|
+
async function writeAiFiles(targetDir, opts) {
|
|
550
|
+
const pm = opts.packageManager;
|
|
551
|
+
const nx = pm === "npm" ? "npx nx" : `${pm} nx`;
|
|
552
|
+
const devCmd = `${pm} dev`;
|
|
553
|
+
const activeMSes = ["auth (port 4001)"];
|
|
554
|
+
if (opts.upload !== "none") activeMSes.push(`upload (port 4002)`);
|
|
555
|
+
if (opts.payment !== "none") activeMSes.push(`payment (port 4003)`);
|
|
556
|
+
if (opts.example !== "none") activeMSes.push(`notes (port 4004)`);
|
|
557
|
+
if (opts.jobs !== "none") activeMSes.push(`jobs (standalone)`);
|
|
558
|
+
const usesSupabase = opts.authProvider === "supabase" || opts.dbProvider === "supabase" || opts.upload === "supabase";
|
|
559
|
+
const usesFirebase = opts.authProvider === "firebase" || opts.dbProvider === "firebase" || opts.upload === "firebase";
|
|
560
|
+
await (0, import_promises.writeFile)((0, import_node_path.join)(targetDir, "CLAUDE.md"), "@AGENTS.md\n");
|
|
561
|
+
const uiLabel = { shadcn: "shadcn/ui + Tailwind", antd: "Ant Design 6", mui: "MUI 6" }[opts.ui];
|
|
562
|
+
const readme = `# ${opts.projectName}
|
|
563
|
+
|
|
564
|
+
> Scaffolded with [iCore](https://github.com/iDEVconn/create-icore) \u2014 Nx + NestJS + React full-stack template.
|
|
565
|
+
|
|
566
|
+
## Stack
|
|
567
|
+
|
|
568
|
+
| Layer | Technology |
|
|
569
|
+
|-------|-----------|
|
|
570
|
+
| Monorepo | Nx + ${pm} |
|
|
571
|
+
| Gateway | NestJS 11 + Swagger |
|
|
572
|
+
| Auth | ${opts.authProvider} |
|
|
573
|
+
| Database | ${opts.dbProvider} |
|
|
574
|
+
| Upload | ${opts.upload === "none" ? "\u2014" : opts.upload} |
|
|
575
|
+
| UI | ${uiLabel} + TanStack Router + Query |
|
|
576
|
+
| i18n | i18next (en / ru / he) |
|
|
577
|
+
|
|
578
|
+
## Quick start
|
|
579
|
+
|
|
580
|
+
\`\`\`bash
|
|
581
|
+
# 1. Fill in provider credentials
|
|
582
|
+
# apps/microservices/auth/.env
|
|
583
|
+
# apps/microservices/upload/.env (if upload is enabled)
|
|
584
|
+
# apps/client/.env (VITE_API_URL \u2014 already defaults to /api)
|
|
585
|
+
|
|
586
|
+
# 2. Start everything
|
|
587
|
+
${devCmd}
|
|
588
|
+
# \u2192 http://localhost:4200 client
|
|
589
|
+
# \u2192 http://localhost:3001/api/docs Swagger
|
|
590
|
+
\`\`\`
|
|
591
|
+
|
|
592
|
+
## Commands
|
|
593
|
+
|
|
594
|
+
\`\`\`bash
|
|
595
|
+
${nx} run <project>:serve # start a single service
|
|
596
|
+
${nx} test <project> # unit tests
|
|
597
|
+
${nx} lint <project> # lint
|
|
598
|
+
${nx} build <project> # production build
|
|
599
|
+
${pm === "yarn" ? "yarn remove-notes" : pm === "pnpm" ? "pnpm remove-notes" : "npm run remove-notes"} # strip the notes sample feature
|
|
600
|
+
\`\`\`
|
|
601
|
+
|
|
602
|
+
## Scaffolded by
|
|
603
|
+
|
|
604
|
+
[iCore](https://github.com/iDEVconn/create-icore) \u2014 [@idevconn/create-icore](https://www.npmjs.com/package/@idevconn/create-icore)
|
|
605
|
+
|
|
606
|
+
## License
|
|
607
|
+
|
|
608
|
+
Apache-2.0
|
|
609
|
+
`;
|
|
610
|
+
await (0, import_promises.writeFile)((0, import_node_path.join)(targetDir, "README.md"), readme);
|
|
611
|
+
const agents = `# ${opts.projectName} \u2014 Agent Instructions
|
|
612
|
+
|
|
613
|
+
## Stack snapshot
|
|
614
|
+
|
|
615
|
+
| Dimension | Choice |
|
|
616
|
+
|------------|--------|
|
|
617
|
+
| Auth | ${opts.authProvider} |
|
|
618
|
+
| Database | ${opts.dbProvider} |
|
|
619
|
+
| Upload | ${opts.upload} |
|
|
620
|
+
| Payment | ${opts.payment} |
|
|
621
|
+
| Jobs | ${opts.jobs} |
|
|
622
|
+
| UI | ${opts.ui} |
|
|
623
|
+
| Transport | ${opts.transport} |
|
|
624
|
+
| PM | ${pm} |
|
|
625
|
+
|
|
626
|
+
## \u{1F680} Mandatory Workflow
|
|
627
|
+
|
|
628
|
+
- **Branch strategy**: \`dev\` is default. Cut \`feature/<name>\` or \`bug/<name>\` from dev. PRs only target dev. Never push directly to main.
|
|
629
|
+
- **No code without approval**: Propose changes first, wait for go-ahead.
|
|
630
|
+
- **\u0417\u0410\u041A\u041E\u041D \u2014 no crash on missing .env**: MS factories must catch config errors, print a boxed banner with ALL missing vars, and return a Fake strategy in dev. In prod (\`NODE_ENV=production\`) throw the same banner. The \`formatEnvBanner\` + \`missingEnv\` helpers from \`@icore/shared\` handle this.
|
|
631
|
+
- **Post-coding routine**: \`npx prettier --write <files>\` \u2192 \`${nx} lint <project>\` \u2192 \`${nx} build <project>\` \u2014 all green before committing.
|
|
632
|
+
- **Nx generators only**: never hand-write \`project.json\` / tsconfig stacks. Use \`${nx} g @nx/<plugin>:<schematic>\`.
|
|
633
|
+
|
|
634
|
+
## Architecture
|
|
635
|
+
|
|
636
|
+
\`\`\`
|
|
637
|
+
apps/
|
|
638
|
+
\u251C\u2500\u2500 api/ NestJS gateway \u2014 all client traffic enters here (:3001)
|
|
639
|
+
\u251C\u2500\u2500 microservices/
|
|
640
|
+
${activeMSes.map((s) => `\u2502 \u251C\u2500\u2500 ${s.split(" ")[0]}/`).join("\n")}
|
|
641
|
+
\u2514\u2500\u2500 client/ Vite + React 19 + ${opts.ui} (:4200)
|
|
642
|
+
libs/
|
|
643
|
+
\u251C\u2500\u2500 shared/ contracts, CASL, transport helpers, env banner utils
|
|
644
|
+
\u251C\u2500\u2500 auth-strategies/${opts.authProvider}/
|
|
645
|
+
${opts.upload !== "none" ? `\u251C\u2500\u2500 storage-strategies/${opts.upload}/
|
|
646
|
+
` : ""}\u251C\u2500\u2500 db-strategies/${opts.dbProvider === "firebase" ? "firestore" : opts.dbProvider}/
|
|
647
|
+
\u251C\u2500\u2500 auth-client/ gateway \u2192 auth MS (TCP/Redis/NATS)
|
|
648
|
+
${opts.upload !== "none" ? `\u251C\u2500\u2500 upload-client/ gateway \u2192 upload MS
|
|
649
|
+
` : ""}\u2514\u2500\u2500 template-shared/ browser-safe React foundation (stores, i18n, CASL)
|
|
650
|
+
\`\`\`
|
|
651
|
+
|
|
652
|
+
## Key patterns
|
|
653
|
+
|
|
654
|
+
**Strategy swap** \u2014 provider is chosen at runtime via env. Never import a concrete strategy in app code; always inject via the factory token (\`AuthStrategy\`, \`StorageStrategy\`, \`DBStrategy\`).
|
|
655
|
+
|
|
656
|
+
**Transport** \u2014 \`buildTransport(prefix)\` reads \`${opts.transport.toUpperCase()}*\` vars. Same helper on gateway client-modules and each MS \`main.ts\`. Supports tcp / redis / nats \u2014 change by flipping \`*_TRANSPORT\` in \`.env\`.
|
|
657
|
+
|
|
658
|
+
**Env layering**:
|
|
659
|
+
1. Root \`.env\` \u2014 \`DB_PROVIDER\`
|
|
660
|
+
2. \`apps/api/.env\` \u2014 gateway transport endpoints
|
|
661
|
+
3. \`apps/microservices/<name>/.env\` \u2014 each MS provider + transport
|
|
662
|
+
4. \`apps/client/.env\` \u2014 \`VITE_API_URL\`
|
|
663
|
+
|
|
664
|
+
## Commands
|
|
665
|
+
|
|
666
|
+
\`\`\`bash
|
|
667
|
+
${devCmd} # start all services
|
|
668
|
+
${nx} run api:serve # gateway only
|
|
669
|
+
${nx} run auth:serve # auth MS only
|
|
670
|
+
${nx} test <project> # run tests
|
|
671
|
+
${nx} lint <project> # lint
|
|
672
|
+
${nx} build <project> # build
|
|
673
|
+
${nx} g @nx/nest:resource # generate NestJS resource
|
|
674
|
+
\`\`\`
|
|
675
|
+
|
|
676
|
+
## .env files to configure
|
|
677
|
+
|
|
678
|
+
| File | Key vars |
|
|
679
|
+
|------|----------|
|
|
680
|
+
| \`apps/microservices/auth/.env\` | \`AUTH_PROVIDER=${opts.authProvider}\`, ${opts.authProvider === "supabase" ? "`SUPABASE_URL`, `SUPABASE_SERVICE_ROLE_KEY`" : "`FB_ADMIN_*`, `FIREBASE_WEB_API_KEY`"} |
|
|
681
|
+
${opts.upload !== "none" ? `| \`apps/microservices/upload/.env\` | \`STORAGE_PROVIDER=${opts.upload}\`, provider creds |
|
|
682
|
+
` : ""}| \`apps/microservices/notes/.env\` | \`DB_PROVIDER=${opts.dbProvider}\`, DB creds |
|
|
683
|
+
| \`apps/client/.env\` | \`VITE_API_URL=/api\` (proxied to :3001 in dev) |
|
|
684
|
+
|
|
685
|
+
## Testing
|
|
686
|
+
|
|
687
|
+
- Unit tests: Vitest, files named \`*.unit.test.ts(x)\` in \`__tests__/\` next to source.
|
|
688
|
+
- Test behaviour, not implementation. Fake strategies from \`@icore/shared\` (FakeAuthStrategy etc.) serve as test doubles.
|
|
689
|
+
- Run: \`${nx} test <project>\`
|
|
690
|
+
`;
|
|
691
|
+
await (0, import_promises.writeFile)((0, import_node_path.join)(targetDir, "AGENTS.md"), agents);
|
|
692
|
+
await (0, import_promises.mkdir)((0, import_node_path.join)(targetDir, ".claude"), { recursive: true });
|
|
693
|
+
const mcpServers = {
|
|
694
|
+
nx: {
|
|
695
|
+
command: "npx",
|
|
696
|
+
args: ["-y", "@nx/mcp@latest", "--directory", "."],
|
|
697
|
+
type: "stdio"
|
|
698
|
+
}
|
|
699
|
+
};
|
|
700
|
+
if (usesSupabase) {
|
|
701
|
+
mcpServers["supabase"] = {
|
|
702
|
+
command: "npx",
|
|
703
|
+
args: [
|
|
704
|
+
"-y",
|
|
705
|
+
"@supabase/mcp-server-supabase@latest",
|
|
706
|
+
"--access-token",
|
|
707
|
+
"<SUPABASE_PERSONAL_ACCESS_TOKEN>"
|
|
708
|
+
],
|
|
709
|
+
type: "stdio"
|
|
710
|
+
};
|
|
711
|
+
}
|
|
712
|
+
if (usesFirebase) {
|
|
713
|
+
mcpServers["firebase"] = {
|
|
714
|
+
command: "npx",
|
|
715
|
+
args: ["-y", "firebase-tools@latest", "experimental:mcp"],
|
|
716
|
+
type: "stdio"
|
|
717
|
+
};
|
|
718
|
+
}
|
|
719
|
+
const nxCmds = [`Bash(${nx} *)`, `Bash(${devCmd})`];
|
|
720
|
+
if (pm !== "npm") nxCmds.push(`Bash(npx nx *)`);
|
|
721
|
+
const settings = {
|
|
722
|
+
mcpServers,
|
|
723
|
+
permissions: {
|
|
724
|
+
allow: [
|
|
725
|
+
...nxCmds,
|
|
726
|
+
"Bash(npx prettier *)",
|
|
727
|
+
"Bash(git status)",
|
|
728
|
+
"Bash(git diff *)",
|
|
729
|
+
"Bash(git log *)"
|
|
730
|
+
]
|
|
731
|
+
}
|
|
732
|
+
};
|
|
733
|
+
await (0, import_promises.writeFile)(
|
|
734
|
+
(0, import_node_path.join)(targetDir, ".claude", "settings.json"),
|
|
735
|
+
JSON.stringify(settings, null, 2) + "\n"
|
|
736
|
+
);
|
|
737
|
+
}
|
|
508
738
|
|
|
509
739
|
// src/lib/prompts.ts
|
|
510
740
|
var p = __toESM(require("@clack/prompts"), 1);
|
|
@@ -700,6 +930,12 @@ Re-run with @latest to refresh:
|
|
|
700
930
|
});
|
|
701
931
|
if (p.isCancel(transport)) throw new Error("cancelled");
|
|
702
932
|
const packageManager = flags.packageManager ?? detectPackageManager();
|
|
933
|
+
if (packageManager === "yarn") {
|
|
934
|
+
p.note(
|
|
935
|
+
"yarn 4.15+ enforces a 24h publish-age gate (npmMinimalAgeGate=1d), so a\n`yarn create @idevconn/icore@latest` run within 24h of a release resolves an\nolder version. If the banner above shows an unexpectedly old version, either:\n \u2022 wait \u2014 the version auto-unlocks 24h after publish, or\n \u2022 bypass once: yarn config set npmMinimalAgeGate 0 (then re-run), or\n \u2022 use npm/pnpm: npm init @idevconn/icore@latest <name> -- [flags]",
|
|
936
|
+
"\u26A0 yarn 24h age-gate"
|
|
937
|
+
);
|
|
938
|
+
}
|
|
703
939
|
const initGit = flags.initGit ?? !await p.confirm({ message: "Initialise git repo?", initialValue: true }) === false;
|
|
704
940
|
const install = flags.install ?? !await p.confirm({
|
|
705
941
|
message: `Run ${packageManager} install?`,
|