@idevconn/create-icore 0.8.0 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -89,6 +89,11 @@ async function rewriteRootPackageJson(targetDir, opts) {
89
89
  const deps = pkg["dependencies"] ??= {};
90
90
  Object.assign(deps, MONGODB_DEPS);
91
91
  }
92
+ if (opts.authProvider === "mongodb") {
93
+ const devDeps = pkg["devDependencies"] ??= {};
94
+ devDeps["@types/bcrypt"] = "^6.0.0";
95
+ devDeps["@types/jsonwebtoken"] = "^9.0.10";
96
+ }
92
97
  if (opts.packageManager !== "yarn") {
93
98
  delete pkg.packageManager;
94
99
  } else {
@@ -224,6 +229,62 @@ async function removeFirebaseAdminLib(targetDir) {
224
229
  "@icore/firebase-admin"
225
230
  ]);
226
231
  }
232
+ async function removeAuthStack(targetDir) {
233
+ const rmPaths = [
234
+ "apps/microservices/auth",
235
+ "libs/auth-strategies",
236
+ "libs/auth-client",
237
+ "Dockerfile.ms-auth",
238
+ "apps/api/src/app/auth",
239
+ "apps/api/src/app/profile",
240
+ "apps/api/src/app/abilities",
241
+ "apps/client/src/components/auth",
242
+ "apps/client/src/routes/login.tsx",
243
+ "apps/client/src/routes/auth.callback.tsx",
244
+ "apps/client/src/routes/auth.oauth.callback.tsx",
245
+ "apps/client/src/routes/_dashboard/profile.tsx"
246
+ ];
247
+ for (const p2 of rmPaths) {
248
+ await rm(join2(targetDir, p2), { recursive: true, force: true });
249
+ }
250
+ const appModulePath = join2(targetDir, "apps/api/src/app/app.module.ts");
251
+ try {
252
+ const src = await readFile2(appModulePath, "utf8");
253
+ 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, "");
254
+ await writeFile2(appModulePath, next);
255
+ } catch {
256
+ }
257
+ const dashboardPath = join2(targetDir, "apps/client/src/routes/_dashboard.tsx");
258
+ try {
259
+ const src = await readFile2(dashboardPath, "utf8");
260
+ const next = src.replace(/^import \{ useAuthStore \} from '@icore\/template-shared';\n/m, "").replace(/, redirect/g, "").replace(/\n {2}beforeLoad: \(\) => \{[\s\S]*?\n {2}\},/m, "");
261
+ await writeFile2(dashboardPath, next);
262
+ } catch {
263
+ }
264
+ for (const alias of [
265
+ "@icore/auth-client",
266
+ "@icore/auth-supabase",
267
+ "@icore/auth-firebase",
268
+ "@icore/auth-mongodb"
269
+ ]) {
270
+ await stripTsconfigPath(targetDir, alias);
271
+ }
272
+ await stripDeps(join2(targetDir, "apps/api/package.json"), ["@icore/auth-client"]);
273
+ const gatewayEnv = join2(targetDir, "apps/api/.env");
274
+ try {
275
+ const env = await readFile2(gatewayEnv, "utf8");
276
+ const next = env.split("\n").filter((line) => !line.startsWith("AUTH_") && !line.startsWith("# AUTH_")).join("\n");
277
+ await writeFile2(gatewayEnv, next);
278
+ } catch {
279
+ }
280
+ const composePath = join2(targetDir, "docker-compose.yml");
281
+ try {
282
+ const compose = await readFile2(composePath, "utf8");
283
+ 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, "");
284
+ await writeFile2(composePath, next);
285
+ } catch {
286
+ }
287
+ }
227
288
  async function removeUploadStack(targetDir) {
228
289
  const paths = [
229
290
  "apps/microservices/upload",
@@ -493,7 +554,8 @@ async function writeFeaturesWiring(targetDir, opts) {
493
554
  export class FeaturesModule {}
494
555
  `;
495
556
  await writeFile4(join4(targetDir, FEATURES_MODULE), featuresModule);
496
- const services = [{ name: "auth", prefix: "AUTH" }];
557
+ const services = [];
558
+ if (opts.authProvider !== "none") services.push({ name: "auth", prefix: "AUTH" });
497
559
  if (opts.upload !== "none") services.push({ name: "upload", prefix: "UPLOAD" });
498
560
  for (const k of chosen) {
499
561
  const svc = FEATURES[k].gatewayService;
@@ -598,12 +660,14 @@ async function writeJson(targetDir, rel, data) {
598
660
  }
599
661
  async function writeServiceBlueprints(targetDir, opts) {
600
662
  const t = opts.transport;
601
- await writeJson(targetDir, "apps/microservices/auth", {
602
- schemaVersion: 1,
603
- service: "auth",
604
- authProvider: opts.authProvider,
605
- transport: t
606
- });
663
+ if (opts.authProvider !== "none") {
664
+ await writeJson(targetDir, "apps/microservices/auth", {
665
+ schemaVersion: 1,
666
+ service: "auth",
667
+ authProvider: opts.authProvider,
668
+ transport: t
669
+ });
670
+ }
607
671
  if (opts.upload !== "none") {
608
672
  await writeJson(targetDir, "apps/microservices/upload", {
609
673
  schemaVersion: 1,
@@ -699,7 +763,9 @@ async function writePnpmWorkspace(targetDir) {
699
763
  "@parcel/watcher",
700
764
  "@scarf/scarf",
701
765
  "@swc/core",
766
+ "bcrypt",
702
767
  "less",
768
+ "mongodb-memory-server",
703
769
  "msgpackr-extract",
704
770
  "nx",
705
771
  "protobufjs",
@@ -1043,7 +1109,7 @@ function runInstall(cwd, pm) {
1043
1109
  async function scaffold(opts, templatesDir) {
1044
1110
  await copyTree(templatesDir, opts.targetDir);
1045
1111
  await rewriteRootPackageJson(opts.targetDir, opts);
1046
- await writeAuthEnv(opts.targetDir, opts);
1112
+ if (opts.authProvider !== "none") await writeAuthEnv(opts.targetDir, opts);
1047
1113
  await writeUploadEnv(opts.targetDir, opts);
1048
1114
  await writeNotesEnv(opts.targetDir, opts);
1049
1115
  await writePaymentEnv(opts.targetDir, opts);
@@ -1055,18 +1121,22 @@ async function scaffold(opts, templatesDir) {
1055
1121
  await cleanupUnusedFeatures(opts.targetDir, opts);
1056
1122
  await writeFeaturesWiring(opts.targetDir, opts);
1057
1123
  await writeNavConfig(opts.targetDir, opts);
1058
- await cleanupUnusedAuth(opts.targetDir, opts.authProvider);
1059
- await writeAuthProvider(opts.targetDir, opts.authProvider);
1124
+ if (opts.authProvider !== "none") {
1125
+ await cleanupUnusedAuth(opts.targetDir, opts.authProvider);
1126
+ await writeAuthProvider(opts.targetDir, opts.authProvider);
1127
+ } else {
1128
+ await removeAuthStack(opts.targetDir);
1129
+ }
1060
1130
  if (opts.upload !== "none") {
1061
1131
  await cleanupUnusedStorage(opts.targetDir, opts.upload);
1062
1132
  await writeStorageProvider(opts.targetDir, opts.upload);
1063
1133
  }
1064
- if (opts.example !== "none") {
1065
- await cleanupUnusedDb(opts.targetDir, opts.dbProvider);
1066
- await writeDbProvider(opts.targetDir, opts.dbProvider);
1067
- }
1068
1134
  const firebaseUsed = opts.authProvider === "firebase" || opts.dbProvider === "firebase" || opts.upload === "firebase";
1069
1135
  if (!firebaseUsed) await removeFirebaseAdminLib(opts.targetDir);
1136
+ await cleanupUnusedDb(opts.targetDir, opts.dbProvider);
1137
+ if (opts.dbProvider !== "none" && opts.example !== "none") {
1138
+ await writeDbProvider(opts.targetDir, opts.dbProvider);
1139
+ }
1070
1140
  await pruneRootProviderDeps(opts.targetDir, opts);
1071
1141
  await writeBlueprintJson(opts.targetDir, opts);
1072
1142
  await writeServiceBlueprints(opts.targetDir, opts);
@@ -1089,9 +1159,98 @@ async function scaffold(opts, templatesDir) {
1089
1159
  // src/lib/prompts.ts
1090
1160
  import * as p from "@clack/prompts";
1091
1161
  import { resolve } from "path";
1092
- import { readFile as readFile7 } from "fs/promises";
1162
+ import { readFile as readFile8 } from "fs/promises";
1093
1163
  import { dirname, join as join9 } from "path";
1094
1164
  import { fileURLToPath } from "url";
1165
+
1166
+ // src/lib/config.ts
1167
+ import { readFile as readFile7 } from "fs/promises";
1168
+ var ConfigFileError = class extends Error {
1169
+ constructor(message) {
1170
+ super(message);
1171
+ this.name = "ConfigFileError";
1172
+ }
1173
+ };
1174
+ var AUTH_PROVIDERS = ["supabase", "firebase", "mongodb", "none"];
1175
+ var DB_PROVIDERS = ["supabase", "firebase", "mongodb", "none"];
1176
+ var UPLOAD_PROVIDERS = [
1177
+ "supabase",
1178
+ "firebase",
1179
+ "cloudinary",
1180
+ "mongodb",
1181
+ "none"
1182
+ ];
1183
+ var PAYMENT_PROVIDERS = ["paypal", "none"];
1184
+ var JOBS_PROVIDERS = ["bullmq", "none"];
1185
+ var EXAMPLE_MODES = ["notes", "none"];
1186
+ var UI_LIBRARIES = ["shadcn", "antd", "mui"];
1187
+ var MS_TRANSPORTS = ["tcp", "redis", "nats", "mqtt", "rmq", "kafka"];
1188
+ var PACKAGE_MANAGERS = ["yarn", "npm", "pnpm"];
1189
+ function assertEnum(field, value, valid) {
1190
+ if (typeof value !== "string" || !valid.includes(value)) {
1191
+ throw new ConfigFileError(
1192
+ `config field "${field}" got "${String(value)}", expected one of: ${valid.join(", ")}`
1193
+ );
1194
+ }
1195
+ return value;
1196
+ }
1197
+ function assertBoolean(field, value) {
1198
+ if (typeof value !== "boolean") {
1199
+ throw new ConfigFileError(`config field "${field}" must be a boolean, got ${typeof value}`);
1200
+ }
1201
+ return value;
1202
+ }
1203
+ function validateConfig(raw) {
1204
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
1205
+ throw new ConfigFileError("config file must be a JSON object");
1206
+ }
1207
+ const obj = raw;
1208
+ const result = {};
1209
+ if ("projectName" in obj) {
1210
+ const v = obj["projectName"];
1211
+ if (typeof v !== "string" || !/^[a-z0-9-]+$/i.test(v)) {
1212
+ throw new ConfigFileError(
1213
+ `config field "projectName" must match /^[a-z0-9-]+$/i, got "${String(v)}"`
1214
+ );
1215
+ }
1216
+ result.projectName = v;
1217
+ }
1218
+ if ("authProvider" in obj)
1219
+ result.authProvider = assertEnum("authProvider", obj["authProvider"], AUTH_PROVIDERS);
1220
+ if ("dbProvider" in obj)
1221
+ result.dbProvider = assertEnum("dbProvider", obj["dbProvider"], DB_PROVIDERS);
1222
+ if ("upload" in obj) result.upload = assertEnum("upload", obj["upload"], UPLOAD_PROVIDERS);
1223
+ if ("payment" in obj) result.payment = assertEnum("payment", obj["payment"], PAYMENT_PROVIDERS);
1224
+ if ("jobs" in obj) result.jobs = assertEnum("jobs", obj["jobs"], JOBS_PROVIDERS);
1225
+ if ("example" in obj) result.example = assertEnum("example", obj["example"], EXAMPLE_MODES);
1226
+ if ("ui" in obj) result.ui = assertEnum("ui", obj["ui"], UI_LIBRARIES);
1227
+ if ("transport" in obj)
1228
+ result.transport = assertEnum("transport", obj["transport"], MS_TRANSPORTS);
1229
+ if ("packageManager" in obj)
1230
+ result.packageManager = assertEnum("packageManager", obj["packageManager"], PACKAGE_MANAGERS);
1231
+ if ("initGit" in obj) result.initGit = assertBoolean("initGit", obj["initGit"]);
1232
+ if ("install" in obj) result.install = assertBoolean("install", obj["install"]);
1233
+ return result;
1234
+ }
1235
+ async function loadConfig(filePath) {
1236
+ let raw;
1237
+ try {
1238
+ raw = await readFile7(filePath, "utf8");
1239
+ } catch {
1240
+ throw new ConfigFileError(`config file not found: ${filePath}`);
1241
+ }
1242
+ let parsed;
1243
+ try {
1244
+ parsed = JSON.parse(raw);
1245
+ } catch (e) {
1246
+ throw new ConfigFileError(
1247
+ `config file is not valid JSON: ${e instanceof Error ? e.message : String(e)}`
1248
+ );
1249
+ }
1250
+ return validateConfig(parsed);
1251
+ }
1252
+
1253
+ // src/lib/prompts.ts
1095
1254
  function detectPackageManager() {
1096
1255
  const ua = process.env["npm_config_user_agent"] ?? "";
1097
1256
  if (ua.startsWith("yarn/")) return "yarn";
@@ -1102,7 +1261,7 @@ function detectPackageManager() {
1102
1261
  async function readSelfVersion() {
1103
1262
  try {
1104
1263
  const here = dirname(fileURLToPath(import.meta.url));
1105
- const pkgRaw = await readFile7(join9(here, "..", "package.json"), "utf8");
1264
+ const pkgRaw = await readFile8(join9(here, "..", "package.json"), "utf8");
1106
1265
  const pkg = JSON.parse(pkgRaw);
1107
1266
  return pkg.version ?? null;
1108
1267
  } catch {
@@ -1176,12 +1335,21 @@ function parseFlags(argv) {
1176
1335
  case "no-install":
1177
1336
  out.install = false;
1178
1337
  break;
1338
+ case "config":
1339
+ out._configPath = v;
1340
+ break;
1179
1341
  }
1180
1342
  }
1181
1343
  return out;
1182
1344
  }
1183
1345
  async function collectOptions({ argv, cwd }) {
1184
1346
  const flags = parseFlags(argv);
1347
+ const configPath = flags._configPath;
1348
+ delete flags._configPath;
1349
+ if (configPath) {
1350
+ const configValues = await loadConfig(configPath);
1351
+ Object.assign(flags, { ...configValues, ...flags });
1352
+ }
1185
1353
  const [selfVersion, latestVersion] = await Promise.all([readSelfVersion(), fetchLatestVersion()]);
1186
1354
  const versionTag = selfVersion ? ` v${selfVersion}` : "";
1187
1355
  p.intro(`iCore${versionTag} \u2014 bootstrap a new project`);
@@ -1204,11 +1372,12 @@ Re-run with @latest to refresh:
1204
1372
  options: [
1205
1373
  { value: "supabase", label: "Supabase" },
1206
1374
  { value: "firebase", label: "Firebase" },
1207
- { value: "mongodb", label: "MongoDB (Custom Auth)" }
1375
+ { value: "mongodb", label: "MongoDB (Custom Auth)" },
1376
+ { value: "none", label: "None \u2014 no login, open API (simple SPA)" }
1208
1377
  ]
1209
1378
  });
1210
1379
  if (p.isCancel(authProvider)) throw new Error("cancelled");
1211
- const dbProvider = flags.dbProvider ?? await p.select({
1380
+ const dbProvider = authProvider === "none" ? "none" : flags.dbProvider ?? await p.select({
1212
1381
  message: "Database backend",
1213
1382
  options: [
1214
1383
  { value: "supabase", label: "Supabase Postgres" },
@@ -1247,7 +1416,7 @@ Re-run with @latest to refresh:
1247
1416
  initialValue: "none"
1248
1417
  });
1249
1418
  if (p.isCancel(jobs)) throw new Error("cancelled");
1250
- const example = flags.example ?? await p.select({
1419
+ const example = authProvider === "none" ? "none" : flags.example ?? await p.select({
1251
1420
  message: "Include notes sample feature? (CRUD demo \u2014 remove before production)",
1252
1421
  options: [
1253
1422
  { value: "notes", label: "Yes \u2014 include notes sample" },
@@ -1272,7 +1441,8 @@ Re-run with @latest to refresh:
1272
1441
  initialValue: "shadcn"
1273
1442
  });
1274
1443
  if (p.isCancel(ui)) throw new Error("cancelled");
1275
- const transport = flags.transport ?? await p.select({
1444
+ const noMicroservices = authProvider === "none" && upload === "none" && payment === "none";
1445
+ const transport = flags.transport ?? (noMicroservices ? "tcp" : await p.select({
1276
1446
  message: "Microservice transport",
1277
1447
  options: [
1278
1448
  { value: "tcp", label: "TCP (default, no broker required)" },
@@ -1283,7 +1453,7 @@ Re-run with @latest to refresh:
1283
1453
  { value: "kafka", label: "Kafka" }
1284
1454
  ],
1285
1455
  initialValue: "tcp"
1286
- });
1456
+ }));
1287
1457
  if (p.isCancel(transport)) throw new Error("cancelled");
1288
1458
  const packageManager = flags.packageManager ?? detectPackageManager();
1289
1459
  if (packageManager === "yarn") {
@@ -1315,7 +1485,7 @@ Re-run with @latest to refresh:
1315
1485
  }
1316
1486
 
1317
1487
  // src/manifest/audit.ts
1318
- import { readFile as readFile8, readdir as readdir3 } from "fs/promises";
1488
+ import { readFile as readFile9, readdir as readdir3 } from "fs/promises";
1319
1489
  import { join as join10 } from "path";
1320
1490
  var IGNORE_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", ".nx"]);
1321
1491
  async function walk(dir, out = []) {
@@ -1331,7 +1501,7 @@ async function walk(dir, out = []) {
1331
1501
  }
1332
1502
  async function tsconfigAliases(dir) {
1333
1503
  try {
1334
- const raw = await readFile8(join10(dir, "tsconfig.base.json"), "utf8");
1504
+ const raw = await readFile9(join10(dir, "tsconfig.base.json"), "utf8");
1335
1505
  const aliases = /* @__PURE__ */ new Set();
1336
1506
  for (const m of raw.matchAll(/"(@icore\/[a-z0-9.-]+)"\s*:/g)) {
1337
1507
  if (m[1]) aliases.add(m[1]);
@@ -1349,7 +1519,7 @@ var PROVIDER_SDKS = {
1349
1519
  };
1350
1520
  async function readBlueprint(dir) {
1351
1521
  try {
1352
- return JSON.parse(await readFile8(join10(dir, "blueprint.json"), "utf8"));
1522
+ return JSON.parse(await readFile9(join10(dir, "blueprint.json"), "utf8"));
1353
1523
  } catch {
1354
1524
  return null;
1355
1525
  }
@@ -1388,7 +1558,7 @@ async function allPackageJsons(dir) {
1388
1558
  }
1389
1559
  async function depKeys(pkgPath) {
1390
1560
  try {
1391
- const pkg = JSON.parse(await readFile8(pkgPath, "utf8"));
1561
+ const pkg = JSON.parse(await readFile9(pkgPath, "utf8"));
1392
1562
  return /* @__PURE__ */ new Set([
1393
1563
  ...Object.keys(pkg.dependencies ?? {}),
1394
1564
  ...Object.keys(pkg.devDependencies ?? {})
@@ -1402,7 +1572,7 @@ async function auditProject(dir, opts = {}) {
1402
1572
  const violations = [];
1403
1573
  const aliases = await tsconfigAliases(dir);
1404
1574
  for (const file of await walk(dir)) {
1405
- const src = await readFile8(file, "utf8");
1575
+ const src = await readFile9(file, "utf8");
1406
1576
  for (const m of src.matchAll(ICORE_IMPORT)) {
1407
1577
  const alias = m[1];
1408
1578
  if (alias && !aliases.has(alias)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@idevconn/create-icore",
3
- "version": "0.8.0",
3
+ "version": "0.9.1",
4
4
  "description": "Bootstrap a new project from the iCore scaffold (Nx + NestJS + React + Vite + shadcn/Tailwind, swappable auth + storage providers).",
5
5
  "license": "Apache-2.0",
6
6
  "author": "iDEVconn",
@@ -48,6 +48,18 @@ services:
48
48
  networks: [icore]
49
49
  restart: unless-stopped
50
50
 
51
+ payment:
52
+ build:
53
+ context: .
54
+ dockerfile: Dockerfile.ms-payment
55
+ env_file: .env.docker
56
+ environment:
57
+ PAYMENT_TRANSPORT: tcp
58
+ PAYMENT_HOST: 0.0.0.0
59
+ PAYMENT_PORT: 4003
60
+ networks: [icore]
61
+ restart: unless-stopped
62
+
51
63
  gateway:
52
64
  build:
53
65
  context: .
@@ -60,6 +72,9 @@ services:
60
72
  UPLOAD_TRANSPORT: redis
61
73
  UPLOAD_REDIS_URL: redis://redis:6379
62
74
  JOBS_REDIS_URL: redis://redis:6379
75
+ PAYMENT_TRANSPORT: tcp
76
+ PAYMENT_HOST: payment
77
+ PAYMENT_PORT: 4003
63
78
  ports:
64
79
  - '3001:3001'
65
80
  depends_on:
@@ -71,6 +86,8 @@ services:
71
86
  condition: service_started
72
87
  jobs:
73
88
  condition: service_started
89
+ payment:
90
+ condition: service_started
74
91
  networks: [icore]
75
92
  restart: unless-stopped
76
93
 
@@ -5,6 +5,10 @@
5
5
  "type": "commonjs",
6
6
  "main": "./src/index.js",
7
7
  "types": "./src/index.d.ts",
8
+ "devDependencies": {
9
+ "@types/bcrypt": "^6.0.0",
10
+ "@types/jsonwebtoken": "^9.0.10"
11
+ },
8
12
  "dependencies": {
9
13
  "@icore/shared": "*",
10
14
  "@nestjs/common": "^11.1.24",