@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/README.md +44 -9
- package/dist/cli.js +218 -48
- package/dist/index.cjs +198 -28
- package/dist/index.d.cts +4 -3
- package/dist/index.d.ts +4 -3
- package/dist/index.js +196 -26
- package/package.json +1 -1
- package/templates/docker-compose.yml +17 -0
- package/templates/libs/auth-strategies/mongodb/package.json +4 -0
package/README.md
CHANGED
|
@@ -19,15 +19,16 @@ npm i -g @idevconn/create-icore && create-icore my-saas
|
|
|
19
19
|
|
|
20
20
|
## Flags
|
|
21
21
|
|
|
22
|
-
| Flag | Values | Default | Notes
|
|
23
|
-
| -------------- | -------------------------------------------------- | --------------- |
|
|
24
|
-
| `--auth` | `supabase` \| `firebase`
|
|
25
|
-
| `--db` | `supabase` \| `firebase`
|
|
26
|
-
| `--upload` | `supabase` \| `firebase` \| `cloudinary` \| `none` | prompted | File upload provider. Use `none` to remove the upload microservice entirely.
|
|
27
|
-
| `--ui` | `shadcn` \| `antd` \| `mui` | `shadcn` | UI library. All three are fully implemented: `shadcn` (Tailwind 4 + shadcn/ui), `antd` (Ant Design 6), `mui` (MUI 6 / Material Design).
|
|
28
|
-
| `--transport` | `tcp` \| `redis` \| `nats` | `tcp` | Microservice transport
|
|
29
|
-
| `--no-git` | — | git enabled | Skip `git init`
|
|
30
|
-
| `--no-install` | — | install enabled | Skip `yarn install`
|
|
22
|
+
| Flag | Values | Default | Notes |
|
|
23
|
+
| -------------- | -------------------------------------------------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
24
|
+
| `--auth` | `supabase` \| `firebase` \| `mongodb` \| `none` | prompted | Auth provider. Use `none` for a minimal SPA with no login system — skips db, example, and transport questions. |
|
|
25
|
+
| `--db` | `supabase` \| `firebase` \| `mongodb` \| `none` | prompted | Database backend. Fully independent of `--auth` — mix-and-match combos like `--auth=firebase --db=supabase` are first-class. Use `none` when `--auth=none` (set automatically). |
|
|
26
|
+
| `--upload` | `supabase` \| `firebase` \| `cloudinary` \| `none` | prompted | File upload provider. Use `none` to remove the upload microservice entirely. |
|
|
27
|
+
| `--ui` | `shadcn` \| `antd` \| `mui` | `shadcn` | UI library. All three are fully implemented: `shadcn` (Tailwind 4 + shadcn/ui), `antd` (Ant Design 6), `mui` (MUI 6 / Material Design). |
|
|
28
|
+
| `--transport` | `tcp` \| `redis` \| `nats` | `tcp` | Microservice transport |
|
|
29
|
+
| `--no-git` | — | git enabled | Skip `git init` |
|
|
30
|
+
| `--no-install` | — | install enabled | Skip `yarn install` |
|
|
31
|
+
| `--config` | path to `.json` file | — | Pre-fill any wizard answer from a JSON file. Missing fields still prompt interactively. CLI flags override config values. See **Non-interactive / CI mode** below. |
|
|
31
32
|
|
|
32
33
|
> **Deprecated:** `--storage` is a deprecated alias for `--upload`. A warning is printed to stderr and the value is forwarded to `--upload`. Remove `--storage` from your scripts.
|
|
33
34
|
|
|
@@ -52,10 +53,44 @@ npm init @idevconn/icore my-app -- --auth=firebase --db=firebase --upload=cloudi
|
|
|
52
53
|
# Skip the upload microservice entirely (--upload=none)
|
|
53
54
|
npm init @idevconn/icore api-only -- --auth=supabase --db=supabase --upload=none --no-install
|
|
54
55
|
|
|
56
|
+
# No auth — simple SPA (no login system, no auth microservice)
|
|
57
|
+
npm init @idevconn/icore spa-app -- --auth=none --upload=none --no-install
|
|
58
|
+
|
|
55
59
|
# NATS transport
|
|
56
60
|
npm init @idevconn/icore my-app -- --auth=supabase --db=supabase --upload=supabase --transport=nats
|
|
57
61
|
```
|
|
58
62
|
|
|
63
|
+
## Non-interactive / CI mode
|
|
64
|
+
|
|
65
|
+
Pass `--config <path>` to skip individual prompts using a JSON file. Any field omitted from the file is still asked interactively. Individual CLI flags always override config file values.
|
|
66
|
+
|
|
67
|
+
```json
|
|
68
|
+
{
|
|
69
|
+
"projectName": "demo-saas",
|
|
70
|
+
"authProvider": "supabase",
|
|
71
|
+
"dbProvider": "supabase",
|
|
72
|
+
"upload": "cloudinary",
|
|
73
|
+
"payment": "none",
|
|
74
|
+
"jobs": "bullmq",
|
|
75
|
+
"example": "notes",
|
|
76
|
+
"ui": "shadcn",
|
|
77
|
+
"transport": "nats",
|
|
78
|
+
"packageManager": "yarn",
|
|
79
|
+
"initGit": true,
|
|
80
|
+
"install": false
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
# Fully non-interactive — all fields in config, no prompts
|
|
86
|
+
npx @idevconn/create-icore --config ./my-config.json
|
|
87
|
+
|
|
88
|
+
# CLI flag overrides config value (firebase wins over supabase in the file)
|
|
89
|
+
npx @idevconn/create-icore --auth firebase --config ./my-config.json
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Field names mirror the TypeScript `CreateIcoreOptions` type. Unknown fields are silently ignored. `targetDir` is always derived from `projectName` + the working directory and is ignored if present.
|
|
93
|
+
|
|
59
94
|
## Building
|
|
60
95
|
|
|
61
96
|
Run `nx build create-icore` to build the library.
|
package/dist/cli.js
CHANGED
|
@@ -10,9 +10,98 @@ import * as p2 from "@clack/prompts";
|
|
|
10
10
|
// src/lib/prompts.ts
|
|
11
11
|
import * as p from "@clack/prompts";
|
|
12
12
|
import { resolve } from "path";
|
|
13
|
-
import { readFile } from "fs/promises";
|
|
13
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
14
14
|
import { dirname, join } from "path";
|
|
15
15
|
import { fileURLToPath } from "url";
|
|
16
|
+
|
|
17
|
+
// src/lib/config.ts
|
|
18
|
+
import { readFile } from "fs/promises";
|
|
19
|
+
var ConfigFileError = class extends Error {
|
|
20
|
+
constructor(message) {
|
|
21
|
+
super(message);
|
|
22
|
+
this.name = "ConfigFileError";
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
var AUTH_PROVIDERS = ["supabase", "firebase", "mongodb", "none"];
|
|
26
|
+
var DB_PROVIDERS = ["supabase", "firebase", "mongodb", "none"];
|
|
27
|
+
var UPLOAD_PROVIDERS = [
|
|
28
|
+
"supabase",
|
|
29
|
+
"firebase",
|
|
30
|
+
"cloudinary",
|
|
31
|
+
"mongodb",
|
|
32
|
+
"none"
|
|
33
|
+
];
|
|
34
|
+
var PAYMENT_PROVIDERS = ["paypal", "none"];
|
|
35
|
+
var JOBS_PROVIDERS = ["bullmq", "none"];
|
|
36
|
+
var EXAMPLE_MODES = ["notes", "none"];
|
|
37
|
+
var UI_LIBRARIES = ["shadcn", "antd", "mui"];
|
|
38
|
+
var MS_TRANSPORTS = ["tcp", "redis", "nats", "mqtt", "rmq", "kafka"];
|
|
39
|
+
var PACKAGE_MANAGERS = ["yarn", "npm", "pnpm"];
|
|
40
|
+
function assertEnum(field, value, valid) {
|
|
41
|
+
if (typeof value !== "string" || !valid.includes(value)) {
|
|
42
|
+
throw new ConfigFileError(
|
|
43
|
+
`config field "${field}" got "${String(value)}", expected one of: ${valid.join(", ")}`
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
return value;
|
|
47
|
+
}
|
|
48
|
+
function assertBoolean(field, value) {
|
|
49
|
+
if (typeof value !== "boolean") {
|
|
50
|
+
throw new ConfigFileError(`config field "${field}" must be a boolean, got ${typeof value}`);
|
|
51
|
+
}
|
|
52
|
+
return value;
|
|
53
|
+
}
|
|
54
|
+
function validateConfig(raw) {
|
|
55
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
56
|
+
throw new ConfigFileError("config file must be a JSON object");
|
|
57
|
+
}
|
|
58
|
+
const obj = raw;
|
|
59
|
+
const result = {};
|
|
60
|
+
if ("projectName" in obj) {
|
|
61
|
+
const v = obj["projectName"];
|
|
62
|
+
if (typeof v !== "string" || !/^[a-z0-9-]+$/i.test(v)) {
|
|
63
|
+
throw new ConfigFileError(
|
|
64
|
+
`config field "projectName" must match /^[a-z0-9-]+$/i, got "${String(v)}"`
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
result.projectName = v;
|
|
68
|
+
}
|
|
69
|
+
if ("authProvider" in obj)
|
|
70
|
+
result.authProvider = assertEnum("authProvider", obj["authProvider"], AUTH_PROVIDERS);
|
|
71
|
+
if ("dbProvider" in obj)
|
|
72
|
+
result.dbProvider = assertEnum("dbProvider", obj["dbProvider"], DB_PROVIDERS);
|
|
73
|
+
if ("upload" in obj) result.upload = assertEnum("upload", obj["upload"], UPLOAD_PROVIDERS);
|
|
74
|
+
if ("payment" in obj) result.payment = assertEnum("payment", obj["payment"], PAYMENT_PROVIDERS);
|
|
75
|
+
if ("jobs" in obj) result.jobs = assertEnum("jobs", obj["jobs"], JOBS_PROVIDERS);
|
|
76
|
+
if ("example" in obj) result.example = assertEnum("example", obj["example"], EXAMPLE_MODES);
|
|
77
|
+
if ("ui" in obj) result.ui = assertEnum("ui", obj["ui"], UI_LIBRARIES);
|
|
78
|
+
if ("transport" in obj)
|
|
79
|
+
result.transport = assertEnum("transport", obj["transport"], MS_TRANSPORTS);
|
|
80
|
+
if ("packageManager" in obj)
|
|
81
|
+
result.packageManager = assertEnum("packageManager", obj["packageManager"], PACKAGE_MANAGERS);
|
|
82
|
+
if ("initGit" in obj) result.initGit = assertBoolean("initGit", obj["initGit"]);
|
|
83
|
+
if ("install" in obj) result.install = assertBoolean("install", obj["install"]);
|
|
84
|
+
return result;
|
|
85
|
+
}
|
|
86
|
+
async function loadConfig(filePath) {
|
|
87
|
+
let raw;
|
|
88
|
+
try {
|
|
89
|
+
raw = await readFile(filePath, "utf8");
|
|
90
|
+
} catch {
|
|
91
|
+
throw new ConfigFileError(`config file not found: ${filePath}`);
|
|
92
|
+
}
|
|
93
|
+
let parsed;
|
|
94
|
+
try {
|
|
95
|
+
parsed = JSON.parse(raw);
|
|
96
|
+
} catch (e) {
|
|
97
|
+
throw new ConfigFileError(
|
|
98
|
+
`config file is not valid JSON: ${e instanceof Error ? e.message : String(e)}`
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
return validateConfig(parsed);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// src/lib/prompts.ts
|
|
16
105
|
function detectPackageManager() {
|
|
17
106
|
const ua = process.env["npm_config_user_agent"] ?? "";
|
|
18
107
|
if (ua.startsWith("yarn/")) return "yarn";
|
|
@@ -23,7 +112,7 @@ function detectPackageManager() {
|
|
|
23
112
|
async function readSelfVersion() {
|
|
24
113
|
try {
|
|
25
114
|
const here2 = dirname(fileURLToPath(import.meta.url));
|
|
26
|
-
const pkgRaw = await
|
|
115
|
+
const pkgRaw = await readFile2(join(here2, "..", "package.json"), "utf8");
|
|
27
116
|
const pkg = JSON.parse(pkgRaw);
|
|
28
117
|
return pkg.version ?? null;
|
|
29
118
|
} catch {
|
|
@@ -97,12 +186,21 @@ function parseFlags(argv) {
|
|
|
97
186
|
case "no-install":
|
|
98
187
|
out.install = false;
|
|
99
188
|
break;
|
|
189
|
+
case "config":
|
|
190
|
+
out._configPath = v;
|
|
191
|
+
break;
|
|
100
192
|
}
|
|
101
193
|
}
|
|
102
194
|
return out;
|
|
103
195
|
}
|
|
104
196
|
async function collectOptions({ argv, cwd }) {
|
|
105
197
|
const flags = parseFlags(argv);
|
|
198
|
+
const configPath = flags._configPath;
|
|
199
|
+
delete flags._configPath;
|
|
200
|
+
if (configPath) {
|
|
201
|
+
const configValues = await loadConfig(configPath);
|
|
202
|
+
Object.assign(flags, { ...configValues, ...flags });
|
|
203
|
+
}
|
|
106
204
|
const [selfVersion, latestVersion] = await Promise.all([readSelfVersion(), fetchLatestVersion()]);
|
|
107
205
|
const versionTag = selfVersion ? ` v${selfVersion}` : "";
|
|
108
206
|
p.intro(`iCore${versionTag} \u2014 bootstrap a new project`);
|
|
@@ -125,11 +223,12 @@ Re-run with @latest to refresh:
|
|
|
125
223
|
options: [
|
|
126
224
|
{ value: "supabase", label: "Supabase" },
|
|
127
225
|
{ value: "firebase", label: "Firebase" },
|
|
128
|
-
{ value: "mongodb", label: "MongoDB (Custom Auth)" }
|
|
226
|
+
{ value: "mongodb", label: "MongoDB (Custom Auth)" },
|
|
227
|
+
{ value: "none", label: "None \u2014 no login, open API (simple SPA)" }
|
|
129
228
|
]
|
|
130
229
|
});
|
|
131
230
|
if (p.isCancel(authProvider)) throw new Error("cancelled");
|
|
132
|
-
const dbProvider = flags.dbProvider ?? await p.select({
|
|
231
|
+
const dbProvider = authProvider === "none" ? "none" : flags.dbProvider ?? await p.select({
|
|
133
232
|
message: "Database backend",
|
|
134
233
|
options: [
|
|
135
234
|
{ value: "supabase", label: "Supabase Postgres" },
|
|
@@ -168,7 +267,7 @@ Re-run with @latest to refresh:
|
|
|
168
267
|
initialValue: "none"
|
|
169
268
|
});
|
|
170
269
|
if (p.isCancel(jobs)) throw new Error("cancelled");
|
|
171
|
-
const example = flags.example ?? await p.select({
|
|
270
|
+
const example = authProvider === "none" ? "none" : flags.example ?? await p.select({
|
|
172
271
|
message: "Include notes sample feature? (CRUD demo \u2014 remove before production)",
|
|
173
272
|
options: [
|
|
174
273
|
{ value: "notes", label: "Yes \u2014 include notes sample" },
|
|
@@ -193,7 +292,8 @@ Re-run with @latest to refresh:
|
|
|
193
292
|
initialValue: "shadcn"
|
|
194
293
|
});
|
|
195
294
|
if (p.isCancel(ui)) throw new Error("cancelled");
|
|
196
|
-
const
|
|
295
|
+
const noMicroservices = authProvider === "none" && upload === "none" && payment === "none";
|
|
296
|
+
const transport = flags.transport ?? (noMicroservices ? "tcp" : await p.select({
|
|
197
297
|
message: "Microservice transport",
|
|
198
298
|
options: [
|
|
199
299
|
{ value: "tcp", label: "TCP (default, no broker required)" },
|
|
@@ -204,7 +304,7 @@ Re-run with @latest to refresh:
|
|
|
204
304
|
{ value: "kafka", label: "Kafka" }
|
|
205
305
|
],
|
|
206
306
|
initialValue: "tcp"
|
|
207
|
-
});
|
|
307
|
+
}));
|
|
208
308
|
if (p.isCancel(transport)) throw new Error("cancelled");
|
|
209
309
|
const packageManager = flags.packageManager ?? detectPackageManager();
|
|
210
310
|
if (packageManager === "yarn") {
|
|
@@ -236,13 +336,13 @@ Re-run with @latest to refresh:
|
|
|
236
336
|
}
|
|
237
337
|
|
|
238
338
|
// src/lib/scaffold.ts
|
|
239
|
-
import { copyFile, mkdir as mkdir2, readdir as readdir2, readFile as
|
|
339
|
+
import { copyFile, mkdir as mkdir2, readdir as readdir2, readFile as readFile8, stat, writeFile as writeFile8, rm as rm4 } from "fs/promises";
|
|
240
340
|
import { readFileSync } from "fs";
|
|
241
341
|
import { join as join9 } from "path";
|
|
242
342
|
import { spawnSync } from "child_process";
|
|
243
343
|
|
|
244
344
|
// src/lib/scaffold-env.ts
|
|
245
|
-
import { readFile as
|
|
345
|
+
import { readFile as readFile3, writeFile } from "fs/promises";
|
|
246
346
|
import { join as join2 } from "path";
|
|
247
347
|
var TRANSPORT_ENV_TOKEN = {
|
|
248
348
|
redis: "REDIS",
|
|
@@ -271,7 +371,7 @@ function uncommentTransportEnv(text2, prefix, transport) {
|
|
|
271
371
|
async function stripGatewayTransport(targetDir, prefix) {
|
|
272
372
|
const gatewayEnv = join2(targetDir, "apps/api/.env");
|
|
273
373
|
try {
|
|
274
|
-
const env = await
|
|
374
|
+
const env = await readFile3(gatewayEnv, "utf8");
|
|
275
375
|
const next = env.split("\n").filter(
|
|
276
376
|
(line) => !line.startsWith(`${prefix}_`) && !line.startsWith(`# ${prefix}_`) && !line.includes(`${prefix} MS transport`)
|
|
277
377
|
).join("\n");
|
|
@@ -294,7 +394,7 @@ async function pruneRootProviderDeps(targetDir, opts) {
|
|
|
294
394
|
if (drop.size === 0) return;
|
|
295
395
|
const pkgPath = join2(targetDir, "package.json");
|
|
296
396
|
try {
|
|
297
|
-
const pkg = JSON.parse(await
|
|
397
|
+
const pkg = JSON.parse(await readFile3(pkgPath, "utf8"));
|
|
298
398
|
for (const field of ["dependencies", "devDependencies"]) {
|
|
299
399
|
const deps = pkg[field];
|
|
300
400
|
if (!deps) continue;
|
|
@@ -306,7 +406,7 @@ async function pruneRootProviderDeps(targetDir, opts) {
|
|
|
306
406
|
}
|
|
307
407
|
async function rewriteRootPackageJson(targetDir, opts) {
|
|
308
408
|
const pkgPath = join2(targetDir, "package.json");
|
|
309
|
-
const raw = await
|
|
409
|
+
const raw = await readFile3(pkgPath, "utf8");
|
|
310
410
|
const pkg = JSON.parse(raw);
|
|
311
411
|
pkg["name"] = opts.projectName;
|
|
312
412
|
pkg["version"] = "0.0.1";
|
|
@@ -321,11 +421,16 @@ async function rewriteRootPackageJson(targetDir, opts) {
|
|
|
321
421
|
const deps = pkg["dependencies"] ??= {};
|
|
322
422
|
Object.assign(deps, MONGODB_DEPS);
|
|
323
423
|
}
|
|
424
|
+
if (opts.authProvider === "mongodb") {
|
|
425
|
+
const devDeps = pkg["devDependencies"] ??= {};
|
|
426
|
+
devDeps["@types/bcrypt"] = "^6.0.0";
|
|
427
|
+
devDeps["@types/jsonwebtoken"] = "^9.0.10";
|
|
428
|
+
}
|
|
324
429
|
if (opts.packageManager !== "yarn") {
|
|
325
430
|
delete pkg.packageManager;
|
|
326
431
|
} else {
|
|
327
432
|
try {
|
|
328
|
-
const yarnrc = await
|
|
433
|
+
const yarnrc = await readFile3(join2(targetDir, ".yarnrc.yml"), "utf8");
|
|
329
434
|
const match = yarnrc.match(/^yarnPath:\s*.+yarn-(\d+\.\d+\.\d+)\.cjs/m);
|
|
330
435
|
if (match?.[1]) {
|
|
331
436
|
pkg["packageManager"] = `yarn@${match[1]}`;
|
|
@@ -338,7 +443,7 @@ async function rewriteRootPackageJson(targetDir, opts) {
|
|
|
338
443
|
}
|
|
339
444
|
async function writeAuthEnv(targetDir, opts) {
|
|
340
445
|
const envExample = join2(targetDir, "apps/microservices/auth/.env.example");
|
|
341
|
-
const env = await
|
|
446
|
+
const env = await readFile3(envExample, "utf8");
|
|
342
447
|
let next = env.replace(/^AUTH_PROVIDER=.*$/m, `AUTH_PROVIDER=${opts.authProvider}`).replace(/^AUTH_TRANSPORT=.*$/m, `AUTH_TRANSPORT=${opts.transport}`);
|
|
343
448
|
next = uncommentTransportEnv(next, "AUTH", opts.transport);
|
|
344
449
|
if (opts.authProvider === "mongodb") {
|
|
@@ -349,7 +454,7 @@ async function writeAuthEnv(targetDir, opts) {
|
|
|
349
454
|
async function writeUploadEnv(targetDir, opts) {
|
|
350
455
|
if (opts.upload === "none") return;
|
|
351
456
|
const envExample = join2(targetDir, "apps/microservices/upload/.env.example");
|
|
352
|
-
const env = await
|
|
457
|
+
const env = await readFile3(envExample, "utf8");
|
|
353
458
|
let next = env.replace(/^STORAGE_PROVIDER=.*$/m, `STORAGE_PROVIDER=${opts.upload}`).replace(/^UPLOAD_TRANSPORT=.*$/m, `UPLOAD_TRANSPORT=${opts.transport}`);
|
|
354
459
|
next = uncommentTransportEnv(next, "UPLOAD", opts.transport);
|
|
355
460
|
if (opts.upload === "mongodb") {
|
|
@@ -361,7 +466,7 @@ async function writeNotesEnv(targetDir, opts) {
|
|
|
361
466
|
if (opts.example === "none") return;
|
|
362
467
|
const envExample = join2(targetDir, "apps/microservices/notes/.env.example");
|
|
363
468
|
try {
|
|
364
|
-
const env = await
|
|
469
|
+
const env = await readFile3(envExample, "utf8");
|
|
365
470
|
let next = env.replace(/^NOTES_TRANSPORT=.*$/m, `NOTES_TRANSPORT=${opts.transport}`);
|
|
366
471
|
next = uncommentTransportEnv(next, "NOTES", opts.transport);
|
|
367
472
|
await writeFile(join2(targetDir, "apps/microservices/notes/.env"), next);
|
|
@@ -370,7 +475,7 @@ async function writeNotesEnv(targetDir, opts) {
|
|
|
370
475
|
}
|
|
371
476
|
async function writeGatewayEnv(targetDir, opts) {
|
|
372
477
|
const envExample = join2(targetDir, "apps/api/.env.example");
|
|
373
|
-
const env = await
|
|
478
|
+
const env = await readFile3(envExample, "utf8");
|
|
374
479
|
let next = env.replace(/^AUTH_TRANSPORT=.*$/m, `AUTH_TRANSPORT=${opts.transport}`).replace(/^UPLOAD_TRANSPORT=.*$/m, `UPLOAD_TRANSPORT=${opts.transport}`).replace(/^NOTES_TRANSPORT=.*$/m, `NOTES_TRANSPORT=${opts.transport}`).replace(/^PAYMENT_TRANSPORT=.*$/m, `PAYMENT_TRANSPORT=${opts.transport}`);
|
|
375
480
|
for (const prefix of ["AUTH", "UPLOAD", "NOTES", "PAYMENT"]) {
|
|
376
481
|
next = uncommentTransportEnv(next, prefix, opts.transport);
|
|
@@ -393,7 +498,7 @@ async function writeRootEnv(targetDir, opts) {
|
|
|
393
498
|
async function writeClientEnv(targetDir) {
|
|
394
499
|
const envExample = join2(targetDir, "apps/client/.env.example");
|
|
395
500
|
try {
|
|
396
|
-
const env = await
|
|
501
|
+
const env = await readFile3(envExample, "utf8");
|
|
397
502
|
await writeFile(join2(targetDir, "apps/client/.env"), env);
|
|
398
503
|
} catch {
|
|
399
504
|
}
|
|
@@ -402,7 +507,7 @@ async function writePaymentEnv(targetDir, opts) {
|
|
|
402
507
|
if (opts.payment === "none") return;
|
|
403
508
|
const envExample = join2(targetDir, "apps/microservices/payment/.env.example");
|
|
404
509
|
try {
|
|
405
|
-
const env = await
|
|
510
|
+
const env = await readFile3(envExample, "utf8");
|
|
406
511
|
let next = env.replace(/^PAYMENT_PROVIDER=.*$/m, `PAYMENT_PROVIDER=${opts.payment}`).replace(/^PAYMENT_TRANSPORT=.*$/m, `PAYMENT_TRANSPORT=${opts.transport}`);
|
|
407
512
|
next = uncommentTransportEnv(next, "PAYMENT", opts.transport);
|
|
408
513
|
await writeFile(join2(targetDir, "apps/microservices/payment/.env"), next);
|
|
@@ -411,11 +516,11 @@ async function writePaymentEnv(targetDir, opts) {
|
|
|
411
516
|
}
|
|
412
517
|
|
|
413
518
|
// src/lib/scaffold-strip.ts
|
|
414
|
-
import { readFile as
|
|
519
|
+
import { readFile as readFile4, writeFile as writeFile2, rm } from "fs/promises";
|
|
415
520
|
import { join as join3 } from "path";
|
|
416
521
|
async function stripDeps(pkgPath, names) {
|
|
417
522
|
try {
|
|
418
|
-
const raw = await
|
|
523
|
+
const raw = await readFile4(pkgPath, "utf8");
|
|
419
524
|
const pkg = JSON.parse(raw);
|
|
420
525
|
for (const n of names) {
|
|
421
526
|
if (pkg.dependencies) delete pkg.dependencies[n];
|
|
@@ -428,7 +533,7 @@ async function stripDeps(pkgPath, names) {
|
|
|
428
533
|
async function stripTsconfigPath(targetDir, alias) {
|
|
429
534
|
const tsconfigPath = join3(targetDir, "tsconfig.base.json");
|
|
430
535
|
try {
|
|
431
|
-
const src = await
|
|
536
|
+
const src = await readFile4(tsconfigPath, "utf8");
|
|
432
537
|
const escaped = alias.replace(/[@/]/g, (c) => c === "@" ? "@" : "\\/");
|
|
433
538
|
const pretty = src.replace(new RegExp(`^\\s*"${escaped}": \\[[^\\]]*\\],?\\n`, "m"), "");
|
|
434
539
|
if (pretty !== src) {
|
|
@@ -456,6 +561,62 @@ async function removeFirebaseAdminLib(targetDir) {
|
|
|
456
561
|
"@icore/firebase-admin"
|
|
457
562
|
]);
|
|
458
563
|
}
|
|
564
|
+
async function removeAuthStack(targetDir) {
|
|
565
|
+
const rmPaths = [
|
|
566
|
+
"apps/microservices/auth",
|
|
567
|
+
"libs/auth-strategies",
|
|
568
|
+
"libs/auth-client",
|
|
569
|
+
"Dockerfile.ms-auth",
|
|
570
|
+
"apps/api/src/app/auth",
|
|
571
|
+
"apps/api/src/app/profile",
|
|
572
|
+
"apps/api/src/app/abilities",
|
|
573
|
+
"apps/client/src/components/auth",
|
|
574
|
+
"apps/client/src/routes/login.tsx",
|
|
575
|
+
"apps/client/src/routes/auth.callback.tsx",
|
|
576
|
+
"apps/client/src/routes/auth.oauth.callback.tsx",
|
|
577
|
+
"apps/client/src/routes/_dashboard/profile.tsx"
|
|
578
|
+
];
|
|
579
|
+
for (const p3 of rmPaths) {
|
|
580
|
+
await rm(join3(targetDir, p3), { recursive: true, force: true });
|
|
581
|
+
}
|
|
582
|
+
const appModulePath = join3(targetDir, "apps/api/src/app/app.module.ts");
|
|
583
|
+
try {
|
|
584
|
+
const src = await readFile4(appModulePath, "utf8");
|
|
585
|
+
const next = src.replace(/^import \{ AuthModule \} from '\.\/auth\/auth\.module';\n/m, "").replace(/^import \{ ProfileModule \} from '\.\/profile\/profile\.module';\n/m, "").replace(/^import \{ AbilitiesModule \} from '\.\/abilities\/abilities\.module';\n/m, "").replace(/\bAuthModule,\s*/g, "").replace(/,\s*AuthModule\b/g, "").replace(/\bProfileModule,\s*/g, "").replace(/,\s*ProfileModule\b/g, "").replace(/\bAbilitiesModule,\s*/g, "").replace(/,\s*AbilitiesModule\b/g, "");
|
|
586
|
+
await writeFile2(appModulePath, next);
|
|
587
|
+
} catch {
|
|
588
|
+
}
|
|
589
|
+
const dashboardPath = join3(targetDir, "apps/client/src/routes/_dashboard.tsx");
|
|
590
|
+
try {
|
|
591
|
+
const src = await readFile4(dashboardPath, "utf8");
|
|
592
|
+
const next = src.replace(/^import \{ useAuthStore \} from '@icore\/template-shared';\n/m, "").replace(/, redirect/g, "").replace(/\n {2}beforeLoad: \(\) => \{[\s\S]*?\n {2}\},/m, "");
|
|
593
|
+
await writeFile2(dashboardPath, next);
|
|
594
|
+
} catch {
|
|
595
|
+
}
|
|
596
|
+
for (const alias of [
|
|
597
|
+
"@icore/auth-client",
|
|
598
|
+
"@icore/auth-supabase",
|
|
599
|
+
"@icore/auth-firebase",
|
|
600
|
+
"@icore/auth-mongodb"
|
|
601
|
+
]) {
|
|
602
|
+
await stripTsconfigPath(targetDir, alias);
|
|
603
|
+
}
|
|
604
|
+
await stripDeps(join3(targetDir, "apps/api/package.json"), ["@icore/auth-client"]);
|
|
605
|
+
const gatewayEnv = join3(targetDir, "apps/api/.env");
|
|
606
|
+
try {
|
|
607
|
+
const env = await readFile4(gatewayEnv, "utf8");
|
|
608
|
+
const next = env.split("\n").filter((line) => !line.startsWith("AUTH_") && !line.startsWith("# AUTH_")).join("\n");
|
|
609
|
+
await writeFile2(gatewayEnv, next);
|
|
610
|
+
} catch {
|
|
611
|
+
}
|
|
612
|
+
const composePath = join3(targetDir, "docker-compose.yml");
|
|
613
|
+
try {
|
|
614
|
+
const compose = await readFile4(composePath, "utf8");
|
|
615
|
+
const next = compose.replace(/\n {2}auth:[\s\S]+?(?=\n {2}\w|\nnetworks:)/m, "\n").replace(/\n {6}auth:\n {8}condition: service_started/g, "").replace(/\n {6}AUTH_TRANSPORT:[^\n]*/g, "").replace(/\n {6}AUTH_REDIS_URL:[^\n]*/g, "");
|
|
616
|
+
await writeFile2(composePath, next);
|
|
617
|
+
} catch {
|
|
618
|
+
}
|
|
619
|
+
}
|
|
459
620
|
async function removeUploadStack(targetDir) {
|
|
460
621
|
const paths = [
|
|
461
622
|
"apps/microservices/upload",
|
|
@@ -469,14 +630,14 @@ async function removeUploadStack(targetDir) {
|
|
|
469
630
|
}
|
|
470
631
|
const appModulePath = join3(targetDir, "apps/api/src/app/app.module.ts");
|
|
471
632
|
try {
|
|
472
|
-
const appModule = await
|
|
633
|
+
const appModule = await readFile4(appModulePath, "utf8");
|
|
473
634
|
const next = appModule.replace(/^import \{ StorageModule \} from '\.\/storage\/storage\.module';\n/m, "").replace(/,\s*StorageModule/g, "");
|
|
474
635
|
await writeFile2(appModulePath, next);
|
|
475
636
|
} catch {
|
|
476
637
|
}
|
|
477
638
|
const gatewayEnv = join3(targetDir, "apps/api/.env");
|
|
478
639
|
try {
|
|
479
|
-
const env = await
|
|
640
|
+
const env = await readFile4(gatewayEnv, "utf8");
|
|
480
641
|
const next = env.split("\n").filter(
|
|
481
642
|
(line) => !line.startsWith("UPLOAD_") && !line.startsWith("# UPLOAD_") && !line.startsWith("MAX_FILE_SIZE_KB")
|
|
482
643
|
).join("\n");
|
|
@@ -490,7 +651,7 @@ async function removeUploadStack(targetDir) {
|
|
|
490
651
|
}
|
|
491
652
|
|
|
492
653
|
// src/manifest/wire-features.ts
|
|
493
|
-
import { readFile as
|
|
654
|
+
import { readFile as readFile6, writeFile as writeFile4, rm as rm3 } from "fs/promises";
|
|
494
655
|
import { join as join5 } from "path";
|
|
495
656
|
|
|
496
657
|
// src/manifest/index.ts
|
|
@@ -651,7 +812,7 @@ var MANIFEST = {
|
|
|
651
812
|
};
|
|
652
813
|
|
|
653
814
|
// src/manifest/wire-provider.ts
|
|
654
|
-
import { readFile as
|
|
815
|
+
import { readFile as readFile5, writeFile as writeFile3, rm as rm2 } from "fs/promises";
|
|
655
816
|
import { join as join4 } from "path";
|
|
656
817
|
async function writeProvider(targetDir, axis, provider) {
|
|
657
818
|
const nestModule = axis.section[provider]?.nestModule;
|
|
@@ -667,7 +828,7 @@ export const ${axis.exportConst} = ${symbol}.forRoot(ENV_PATH);
|
|
|
667
828
|
}
|
|
668
829
|
async function stripJsonKeys(path, drop) {
|
|
669
830
|
try {
|
|
670
|
-
const pkg = JSON.parse(await
|
|
831
|
+
const pkg = JSON.parse(await readFile5(path, "utf8"));
|
|
671
832
|
for (const field of ["dependencies", "devDependencies"]) {
|
|
672
833
|
const deps = pkg[field];
|
|
673
834
|
if (!deps) continue;
|
|
@@ -680,7 +841,7 @@ async function stripJsonKeys(path, drop) {
|
|
|
680
841
|
async function stripTsconfigKeys(targetDir, aliases) {
|
|
681
842
|
const path = join4(targetDir, "tsconfig.base.json");
|
|
682
843
|
try {
|
|
683
|
-
const parsed = JSON.parse(await
|
|
844
|
+
const parsed = JSON.parse(await readFile5(path, "utf8"));
|
|
684
845
|
const paths = parsed.compilerOptions?.paths;
|
|
685
846
|
if (paths) for (const a of aliases) delete paths[a];
|
|
686
847
|
await writeFile3(path, JSON.stringify(parsed, null, 2) + "\n");
|
|
@@ -725,7 +886,8 @@ async function writeFeaturesWiring(targetDir, opts) {
|
|
|
725
886
|
export class FeaturesModule {}
|
|
726
887
|
`;
|
|
727
888
|
await writeFile4(join5(targetDir, FEATURES_MODULE), featuresModule);
|
|
728
|
-
const services = [
|
|
889
|
+
const services = [];
|
|
890
|
+
if (opts.authProvider !== "none") services.push({ name: "auth", prefix: "AUTH" });
|
|
729
891
|
if (opts.upload !== "none") services.push({ name: "upload", prefix: "UPLOAD" });
|
|
730
892
|
for (const k of chosen) {
|
|
731
893
|
const svc = FEATURES[k].gatewayService;
|
|
@@ -742,7 +904,7 @@ ${entries}
|
|
|
742
904
|
async function stripJobsDockerCompose(targetDir) {
|
|
743
905
|
const composePath = join5(targetDir, "docker-compose.yml");
|
|
744
906
|
try {
|
|
745
|
-
const compose = await
|
|
907
|
+
const compose = await readFile6(composePath, "utf8");
|
|
746
908
|
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, "");
|
|
747
909
|
await writeFile4(composePath, next);
|
|
748
910
|
} catch {
|
|
@@ -830,12 +992,14 @@ async function writeJson(targetDir, rel, data) {
|
|
|
830
992
|
}
|
|
831
993
|
async function writeServiceBlueprints(targetDir, opts) {
|
|
832
994
|
const t = opts.transport;
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
995
|
+
if (opts.authProvider !== "none") {
|
|
996
|
+
await writeJson(targetDir, "apps/microservices/auth", {
|
|
997
|
+
schemaVersion: 1,
|
|
998
|
+
service: "auth",
|
|
999
|
+
authProvider: opts.authProvider,
|
|
1000
|
+
transport: t
|
|
1001
|
+
});
|
|
1002
|
+
}
|
|
839
1003
|
if (opts.upload !== "none") {
|
|
840
1004
|
await writeJson(targetDir, "apps/microservices/upload", {
|
|
841
1005
|
schemaVersion: 1,
|
|
@@ -918,7 +1082,7 @@ var writeDbProvider = (targetDir, provider) => writeProvider(targetDir, DB, prov
|
|
|
918
1082
|
var cleanupUnusedDb = (targetDir, chosen) => cleanupUnusedAxis(targetDir, DB, chosen);
|
|
919
1083
|
|
|
920
1084
|
// src/lib/scaffold-pkg.ts
|
|
921
|
-
import { readFile as
|
|
1085
|
+
import { readFile as readFile7, writeFile as writeFile7, mkdir, readdir } from "fs/promises";
|
|
922
1086
|
import { join as join8 } from "path";
|
|
923
1087
|
|
|
924
1088
|
// src/lib/options.ts
|
|
@@ -929,7 +1093,7 @@ function pmRun(pm, script) {
|
|
|
929
1093
|
// src/lib/scaffold-pkg.ts
|
|
930
1094
|
async function writePnpmWorkspace(targetDir) {
|
|
931
1095
|
const pkgPath = join8(targetDir, "package.json");
|
|
932
|
-
const pkg = JSON.parse(await
|
|
1096
|
+
const pkg = JSON.parse(await readFile7(pkgPath, "utf8"));
|
|
933
1097
|
const workspaces = pkg.workspaces ?? [];
|
|
934
1098
|
const packagesBlock = workspaces.map((p3) => ` - '${p3}'`).join("\n");
|
|
935
1099
|
const allowBuilds = [
|
|
@@ -938,7 +1102,9 @@ async function writePnpmWorkspace(targetDir) {
|
|
|
938
1102
|
"@parcel/watcher",
|
|
939
1103
|
"@scarf/scarf",
|
|
940
1104
|
"@swc/core",
|
|
1105
|
+
"bcrypt",
|
|
941
1106
|
"less",
|
|
1107
|
+
"mongodb-memory-server",
|
|
942
1108
|
"msgpackr-extract",
|
|
943
1109
|
"nx",
|
|
944
1110
|
"protobufjs",
|
|
@@ -973,7 +1139,7 @@ async function rewritePnpmWorkspaceDeps(targetDir) {
|
|
|
973
1139
|
const pkgFiles = await walk(targetDir);
|
|
974
1140
|
for (const f of pkgFiles) {
|
|
975
1141
|
try {
|
|
976
|
-
const raw = await
|
|
1142
|
+
const raw = await readFile7(f, "utf8");
|
|
977
1143
|
const next = raw.replace(/"(@icore\/[^"]+)":\s*"\*"/g, '"$1": "workspace:*"');
|
|
978
1144
|
if (next !== raw) await writeFile7(f, next);
|
|
979
1145
|
} catch {
|
|
@@ -983,7 +1149,7 @@ async function rewritePnpmWorkspaceDeps(targetDir) {
|
|
|
983
1149
|
async function patchGitignoreForPm(targetDir, pm) {
|
|
984
1150
|
const giPath = join8(targetDir, ".gitignore");
|
|
985
1151
|
try {
|
|
986
|
-
let src = await
|
|
1152
|
+
let src = await readFile7(giPath, "utf8");
|
|
987
1153
|
src = src.replace(/^# Build artifacts.*\ntools\/create-icore\/templates\/\s*\n/m, "");
|
|
988
1154
|
if (pm !== "yarn") {
|
|
989
1155
|
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, "");
|
|
@@ -1245,7 +1411,7 @@ async function rewriteClientPaths(clientDir, ui) {
|
|
|
1245
1411
|
for (const rel of candidates) {
|
|
1246
1412
|
const path = join9(clientDir, rel);
|
|
1247
1413
|
try {
|
|
1248
|
-
const raw = await
|
|
1414
|
+
const raw = await readFile8(path, "utf8");
|
|
1249
1415
|
const next = raw.replaceAll("../../../", "../../").replaceAll(`apps/templates/client-${ui}`, "apps/client").replaceAll(`client-${ui}`, "client");
|
|
1250
1416
|
if (next !== raw) await writeFile8(path, next);
|
|
1251
1417
|
} catch {
|
|
@@ -1282,7 +1448,7 @@ function runInstall(cwd, pm) {
|
|
|
1282
1448
|
async function scaffold(opts, templatesDir2) {
|
|
1283
1449
|
await copyTree(templatesDir2, opts.targetDir);
|
|
1284
1450
|
await rewriteRootPackageJson(opts.targetDir, opts);
|
|
1285
|
-
await writeAuthEnv(opts.targetDir, opts);
|
|
1451
|
+
if (opts.authProvider !== "none") await writeAuthEnv(opts.targetDir, opts);
|
|
1286
1452
|
await writeUploadEnv(opts.targetDir, opts);
|
|
1287
1453
|
await writeNotesEnv(opts.targetDir, opts);
|
|
1288
1454
|
await writePaymentEnv(opts.targetDir, opts);
|
|
@@ -1294,18 +1460,22 @@ async function scaffold(opts, templatesDir2) {
|
|
|
1294
1460
|
await cleanupUnusedFeatures(opts.targetDir, opts);
|
|
1295
1461
|
await writeFeaturesWiring(opts.targetDir, opts);
|
|
1296
1462
|
await writeNavConfig(opts.targetDir, opts);
|
|
1297
|
-
|
|
1298
|
-
|
|
1463
|
+
if (opts.authProvider !== "none") {
|
|
1464
|
+
await cleanupUnusedAuth(opts.targetDir, opts.authProvider);
|
|
1465
|
+
await writeAuthProvider(opts.targetDir, opts.authProvider);
|
|
1466
|
+
} else {
|
|
1467
|
+
await removeAuthStack(opts.targetDir);
|
|
1468
|
+
}
|
|
1299
1469
|
if (opts.upload !== "none") {
|
|
1300
1470
|
await cleanupUnusedStorage(opts.targetDir, opts.upload);
|
|
1301
1471
|
await writeStorageProvider(opts.targetDir, opts.upload);
|
|
1302
1472
|
}
|
|
1303
|
-
if (opts.example !== "none") {
|
|
1304
|
-
await cleanupUnusedDb(opts.targetDir, opts.dbProvider);
|
|
1305
|
-
await writeDbProvider(opts.targetDir, opts.dbProvider);
|
|
1306
|
-
}
|
|
1307
1473
|
const firebaseUsed = opts.authProvider === "firebase" || opts.dbProvider === "firebase" || opts.upload === "firebase";
|
|
1308
1474
|
if (!firebaseUsed) await removeFirebaseAdminLib(opts.targetDir);
|
|
1475
|
+
await cleanupUnusedDb(opts.targetDir, opts.dbProvider);
|
|
1476
|
+
if (opts.dbProvider !== "none" && opts.example !== "none") {
|
|
1477
|
+
await writeDbProvider(opts.targetDir, opts.dbProvider);
|
|
1478
|
+
}
|
|
1309
1479
|
await pruneRootProviderDeps(opts.targetDir, opts);
|
|
1310
1480
|
await writeBlueprintJson(opts.targetDir, opts);
|
|
1311
1481
|
await writeServiceBlueprints(opts.targetDir, opts);
|