create-authenik8-app 2.0.6 → 2.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/README.md +19 -13
  2. package/dist/bin/index.d.ts +5 -0
  3. package/dist/bin/index.d.ts.map +1 -1
  4. package/dist/bin/index.js +500 -302
  5. package/dist/bin/index.js.map +1 -1
  6. package/dist/index.d.ts +3 -0
  7. package/dist/index.d.ts.map +1 -0
  8. package/dist/index.js +262 -0
  9. package/dist/index.js.map +1 -0
  10. package/dist/lib/constants.d.ts +4 -0
  11. package/dist/lib/constants.d.ts.map +1 -0
  12. package/dist/lib/constants.js +25 -0
  13. package/dist/lib/constants.js.map +1 -0
  14. package/dist/lib/process.d.ts +7 -0
  15. package/dist/lib/process.d.ts.map +1 -0
  16. package/dist/lib/process.js +56 -0
  17. package/dist/lib/process.js.map +1 -0
  18. package/dist/lib/state.d.ts +8 -0
  19. package/dist/lib/state.d.ts.map +1 -0
  20. package/dist/lib/state.js +31 -0
  21. package/dist/lib/state.js.map +1 -0
  22. package/dist/lib/types.d.ts +16 -0
  23. package/dist/lib/types.d.ts.map +1 -0
  24. package/dist/lib/types.js +2 -0
  25. package/dist/lib/types.js.map +1 -0
  26. package/dist/lib/ui.d.ts +7 -0
  27. package/dist/lib/ui.d.ts.map +1 -0
  28. package/dist/lib/ui.js +124 -0
  29. package/dist/lib/ui.js.map +1 -0
  30. package/dist/steps/configurePrisma.d.ts +3 -0
  31. package/dist/steps/configurePrisma.d.ts.map +1 -0
  32. package/dist/steps/configurePrisma.js +41 -0
  33. package/dist/steps/configurePrisma.js.map +1 -0
  34. package/dist/steps/createProject.d.ts +4 -0
  35. package/dist/steps/createProject.d.ts.map +1 -0
  36. package/dist/steps/createProject.js +16 -0
  37. package/dist/steps/createProject.js.map +1 -0
  38. package/dist/steps/finalSetup.d.ts +4 -0
  39. package/dist/steps/finalSetup.d.ts.map +1 -0
  40. package/dist/steps/finalSetup.js +52 -0
  41. package/dist/steps/finalSetup.js.map +1 -0
  42. package/dist/steps/installAuth.d.ts +2 -0
  43. package/dist/steps/installAuth.d.ts.map +1 -0
  44. package/dist/steps/installAuth.js +34 -0
  45. package/dist/steps/installAuth.js.map +1 -0
  46. package/dist/steps/installDeps.d.ts +3 -0
  47. package/dist/steps/installDeps.d.ts.map +1 -0
  48. package/dist/steps/installDeps.js +31 -0
  49. package/dist/steps/installDeps.js.map +1 -0
  50. package/dist/steps/prompts.d.ts +3 -0
  51. package/dist/steps/prompts.d.ts.map +1 -0
  52. package/dist/steps/prompts.js +47 -0
  53. package/dist/steps/prompts.js.map +1 -0
  54. package/dist/utils/hash.d.ts +3 -0
  55. package/dist/utils/hash.d.ts.map +1 -0
  56. package/dist/utils/hash.js +41 -0
  57. package/dist/utils/hash.js.map +1 -0
  58. package/dist/utils/output.d.ts +3 -0
  59. package/dist/utils/output.d.ts.map +1 -0
  60. package/dist/utils/output.js +62 -0
  61. package/dist/utils/output.js.map +1 -0
  62. package/package.json +7 -4
  63. package/templates/express-auth+/src/auth/protected.routes.ts +2 -2
  64. package/templates/express-auth/ecosystem.config.ts +0 -16
  65. package/templates/express-auth+/ecosystem.config.ts +0 -16
  66. package/templates/express-base/ecosystem.config.ts +0 -16
package/dist/bin/index.js CHANGED
@@ -5,39 +5,86 @@ import chalk from "chalk";
5
5
  import inquirer from "inquirer";
6
6
  import { ExitPromptError } from "@inquirer/core";
7
7
  import ora from "ora";
8
- import { execSync } from "child_process";
8
+ import { execSync, ChildProcess } from "child_process";
9
9
  import { fileURLToPath } from "url";
10
- import { spawnSync } from "child_process";
10
+ import { spawnSync, spawn } from "child_process";
11
11
  import os from "os";
12
- function step(name, fn) {
13
- return { name, fn };
12
+ const spinner = ora().start();
13
+ const activeProcesses = new Set();
14
+ export function run(cmd, args, options) {
15
+ return new Promise((resolve, reject) => {
16
+ const child = spawn(cmd, args, {
17
+ cwd: options.cwd,
18
+ stdio: "ignore",
19
+ });
20
+ activeProcesses.add(child);
21
+ child.on("exit", (code) => {
22
+ activeProcesses.delete(child);
23
+ if (code === 0) {
24
+ resolve();
25
+ }
26
+ else {
27
+ reject(new Error(`${cmd} exited with code ${code}`));
28
+ }
29
+ });
30
+ child.on("error", (err) => {
31
+ activeProcesses.delete(child);
32
+ reject(err);
33
+ });
34
+ });
35
+ }
36
+ function killAllProcesses() {
37
+ for (const proc of activeProcesses) {
38
+ try {
39
+ proc.kill("SIGINT");
40
+ }
41
+ catch { }
42
+ }
43
+ activeProcesses.clear();
14
44
  }
15
- let state = {};
16
- let answers = {};
17
- const stateFile = (project) => path.join(process.cwd(), project, ".authenik8-state.json");
18
- function saveState(project, state) {
19
- fs.ensureDirSync(path.join(process.cwd(), project));
20
- fs.writeJsonSync(stateFile(project), state, { spaces: 2 });
45
+ function getCommand(cmd) {
46
+ const isWin = process.platform === "win32";
47
+ if (cmd === "npm")
48
+ return isWin ? "npm.cmd" : "npm";
49
+ if (cmd === "npx")
50
+ return isWin ? "npx.cmd" : "npx";
51
+ if (cmd === "git")
52
+ return isWin ? "git.exe" : "git";
53
+ return cmd;
21
54
  }
22
- function loadState(project) {
23
- const file = stateFile(project);
24
- if (!fs.existsSync(file))
25
- return null;
26
- return fs.readJsonSync(file);
55
+ //make sure steps are completed
56
+ function stepActuallyCompleted(step) {
57
+ switch (step) {
58
+ case "deps-installed":
59
+ return fs.existsSync(path.join(targetDir, "node_modules"));
60
+ case "prisma-generated":
61
+ return fs.existsSync(path.join(targetDir, "node_modules/.prisma/client"));
62
+ case "git-initialized":
63
+ return fs.existsSync(path.join(targetDir, ".git"));
64
+ default:
65
+ return true;
66
+ }
27
67
  }
28
- function clearState(project) {
29
- const file = stateFile(project);
30
- if (fs.existsSync(file))
31
- fs.removeSync(file);
68
+ const args = process.argv.slice(2);
69
+ const projectNameArg = args.find(arg => !arg.startsWith("--"));
70
+ //const projectNameArg = process.argv[2];
71
+ if (!projectNameArg) {
72
+ console.log(chalk.red("❌ Please provide a project name"));
73
+ process.exit(1);
32
74
  }
33
- const isResume = process.argv.includes("--resume");
75
+ const projectName = projectNameArg;
76
+ //let answers:any = {};
77
+ let state = {
78
+ step: "start",
79
+ projectName,
80
+ };
34
81
  const platform = os.platform();
35
82
  // 'linux' | 'darwin' | 'win32'
36
83
  const isTermux = process.env.PREFIX?.includes("com.termux") ||
37
84
  process.env.TERMUX === "true";
38
85
  function getBestHashLib() {
39
86
  if (isTermux)
40
- return "bcryptjs"; // argon2 breaks here
87
+ return "bcryptjs";
41
88
  if (platform === "win32")
42
89
  return "bcryptjs";
43
90
  // avoids build tools issues for most users
@@ -49,17 +96,6 @@ function getBestHashLib() {
49
96
  // but still fallback later if needed
50
97
  return "bcryptjs";
51
98
  }
52
- function createSaver(projectName, ctx) {
53
- return (step, extra) => {
54
- saveState(projectName, {
55
- step,
56
- projectName,
57
- ...ctx.state,
58
- ...ctx.answers,
59
- ...extra,
60
- });
61
- };
62
- }
63
99
  function generateHashModule(hashLib) {
64
100
  if (hashLib === "argon2") {
65
101
  return `
@@ -88,122 +124,181 @@ return bcrypt.compare(password, hash);
88
124
  }
89
125
  const __filename = fileURLToPath(import.meta.url);
90
126
  const __dirname = path.dirname(__filename);
91
- const projectName = process.argv[2];
92
- if (!projectName) {
93
- console.log(chalk.red("❌ Please provide a project name"));
94
- process.exit(1);
95
- }
96
- const isProduction = process.argv.includes("--production-ready");
97
- const targetDir = path.join(process.cwd(), projectName);
127
+ const isProduction = args.includes("--production-ready");
128
+ const isResume = args.includes("--resume");
129
+ const targetDir = path.resolve(process.cwd(), projectName);
98
130
  let projectCreated = false;
99
- const exists = fs.existsSync(targetDir);
100
- if (isResume) {
131
+ const stepOrder = [
132
+ "start",
133
+ "prompts",
134
+ "project-created",
135
+ "auth-installed",
136
+ "prisma-configured",
137
+ "deps-installed",
138
+ "prisma-generated",
139
+ "production-configured",
140
+ "git-initialized",
141
+ "done",
142
+ ];
143
+ const stepLabels = {
144
+ start: "Starting",
145
+ prompts: "Collecting inputs",
146
+ "project-created": "Project scaffold",
147
+ "auth-installed": "Auth setup",
148
+ "prisma-configured": "Prisma setup",
149
+ "deps-installed": "Dependencies install",
150
+ "prisma-generated": "Prisma generate",
151
+ "production-configured": "Production setup",
152
+ "git-initialized": "Git init",
153
+ done: "Completed",
154
+ };
155
+ const globalStateDir = path.join(process.cwd(), ".authenik8");
156
+ const stateFile = path.join(globalStateDir, `${projectName}.json`);
157
+ function hasReachedStep(currentStep, targetStep) {
158
+ return stepOrder.indexOf(currentStep) >= stepOrder.indexOf(targetStep);
101
159
  }
102
- if (exists && !isResume) {
103
- const { action } = await inquirer.prompt([
104
- {
105
- type: "select",
106
- name: "action",
107
- message: `Folder "${projectName}" already exists. What do you want to do?`,
108
- choices: [
109
- { name: "Resume setup", value: "resume" },
110
- { name: "Overwrite", value: "overwrite" },
111
- { name: "Cancel", value: "cancel" },
112
- ],
113
- },
114
- ]);
115
- if (action === "resume") {
116
- // trigger resume logic
160
+ //function saveState(step: StepName, extra: Partial<CliState> = {}) {
161
+ function saveState(update) {
162
+ state = { ...state, ...update };
163
+ fs.ensureDirSync(path.dirname(stateFile));
164
+ fs.writeJsonSync(stateFile, state, { spaces: 2 });
165
+ }
166
+ function loadState() {
167
+ if (!fs.existsSync(stateFile))
168
+ return null;
169
+ return fs.readJsonSync(stateFile);
170
+ }
171
+ function clearState() {
172
+ if (fs.existsSync(stateFile)) {
173
+ fs.removeSync(stateFile);
117
174
  }
118
- if (action === "overwrite") {
119
- await fs.remove(targetDir);
175
+ }
176
+ function renderStep(current) {
177
+ console.clear();
178
+ renderHeader();
179
+ for (const step of stepOrder) {
180
+ const label = stepLabels[step];
181
+ if (step === "production-configured" && !isProduction) {
182
+ continue;
183
+ }
184
+ if (step === current) {
185
+ console.log(chalk.yellow(`⏳ ${label}...`));
186
+ break;
187
+ }
188
+ if (hasReachedStep(current, step)) {
189
+ console.log(chalk.green(`✔ ${label}`));
190
+ }
191
+ else {
192
+ console.log(chalk.gray(`○ ${label}`));
193
+ }
120
194
  }
121
- if (action === "cancel") {
122
- process.exit(0);
195
+ console.log("");
196
+ }
197
+ function isInterruptedError(err) {
198
+ return (typeof err === "object" &&
199
+ err !== null &&
200
+ "signal" in err &&
201
+ (err.signal === "SIGINT" ||
202
+ err.signal === "SIGTERM"));
203
+ }
204
+ async function exitForInterrupt(err) {
205
+ if (isInterruptedError(err)) {
206
+ throw err;
207
+ }
208
+ }
209
+ async function cleanupIncompleteProject() {
210
+ if (projectCreated && fs.existsSync(targetDir)) {
211
+ await fs.remove(targetDir);
212
+ console.log("🧹 Cleaned up incomplete project.");
123
213
  }
124
214
  }
125
215
  const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
126
- const cleanLogo = `
127
-
128
- █████╗ █████╗
129
- ██╔══██╗ ██╔══██╗
130
- ███████║ ╚█████╔╝
131
- ██╔══██║ ██╔══██╗
132
- ██║ ██║ ╚█████╔╝
133
- ╚═╝ ╚═╝ ╚════╝
134
-
135
- A8
136
-
137
- Authenik8 CLI
138
- Build Faster
139
- More , Secure
216
+ const cleanLogo = `
217
+
218
+ █████╗ █████╗
219
+ ██╔══██╗ ██╔══██╗
220
+ ███████║ ╚█████╔╝
221
+ ██╔══██║ ██╔══██╗
222
+ ██║ ██║ ╚█████╔╝
223
+ ╚═╝ ╚═╝ ╚════╝
224
+
225
+ A8
226
+
227
+ Authenik8 CLI
228
+ Build Faster
229
+ More , Secure
140
230
  `;
141
231
  const glitchFrames = [
142
232
  `
143
- █████╗ █████╗
144
- ██╔══██╗ ██▒▒▒▒██
145
- ███████║ ╚█████╔╝
146
- ██╔══██║ ██▒▒▒▒██
147
- ██║ ██║ ╚█████╔╝
148
- ╚═╝ ╚═╝ ╚════╝
149
-
233
+ █████╗ █████╗
234
+ ██╔══██╗ ██▒▒▒▒██
235
+ ███████║ ╚█████╔╝
236
+ ██╔══██║ ██▒▒▒▒██
237
+ ██║ ██║ ╚█████╔╝
238
+ ╚═╝ ╚═╝ ╚════╝
239
+
150
240
  A8
151
241
  Authenik8 CLI
152
-
153
- More
242
+
243
+ More
154
244
  `,
155
- `
156
- ██▓▓██╗ ██▓▓██╗
157
- ██▒▒██╔╝ ██▒▒██╔╝
158
- ██▒▒▒▒██ ╚█████╔╝
159
- ██▓▓██╔╝ ██▒▒██╗
160
- ██▒▒██║ ╚█████╔╝
161
- ╚═════╝ ╚════╝
162
-
245
+ `
246
+ ██▓▓██╗ ██▓▓██╗
247
+ ██▒▒██╔╝ ██▒▒██╔╝
248
+ ██▒▒▒▒██ ╚█████╔╝
249
+ ██▓▓██╔╝ ██▒▒██╗
250
+ ██▒▒██║ ╚█████╔╝
251
+ ╚═════╝ ╚════╝
252
+
163
253
  A8
164
254
  Authenik8 CLI
165
- Faster
255
+ Faster
166
256
  `,
167
- `
168
- ██▒▒██╗ ██▒▒██╗
169
- ██▓▓██╔╝ ██▓▓██╔╝
170
- ██▒▒▒▒██ ╚█████╔╝
171
- ██▓▓██╔╝ ██▓▓██╗
172
- ██▒▒██║ ╚█████╔╝
173
- ╚═════╝ ╚════╝
174
-
257
+ `
258
+ ██▒▒██╗ ██▒▒██╗
259
+ ██▓▓██╔╝ ██▓▓██╔╝
260
+ ██▒▒▒▒██ ╚█████╔╝
261
+ ██▓▓██╔╝ ██▓▓██╗
262
+ ██▒▒██║ ╚█████╔╝
263
+ ╚═════╝ ╚════╝
264
+
175
265
  A8
176
266
  Authenik8 CLI
177
- Build
267
+ Build
178
268
  `,
179
- `
180
- ██▓▓██╗ ██▓▓██╗
181
- ██▒▒██╔╝ ██▒▒██╔╝
182
- ██▒▒▒▒██ ╚█████╔╝
183
- ██▓▓██╔╝ ██▒▒██╗
184
- ██▒▒██║ ╚█████╔╝
185
- ╚═════╝ ╚════╝
186
-
269
+ `
270
+ ██▓▓██╗ ██▓▓██╗
271
+ ██▒▒██╔╝ ██▒▒██╔╝
272
+ ██▒▒▒▒██ ╚█████╔╝
273
+ ██▓▓██╔╝ ██▒▒██╗
274
+ ██▒▒██║ ╚█████╔╝
275
+ ╚═════╝ ╚════╝
276
+
187
277
  A8
188
- Authenik8 CLI
189
-
278
+ Authenik8 CLI
279
+
190
280
  `
191
281
  ];
282
+ function renderHeader() {
283
+ console.log(chalk.cyan.bold("Happy building \nAuthenik8 CLI"));
284
+ console.log(chalk.gray("────────────────────"));
285
+ console.log("");
286
+ }
192
287
  async function showBootLogo() {
193
288
  console.clear();
194
- const boot = ora("Initializing Authenik8 engine...").start();
195
- // Phase 1: clean → unstable
289
+ spinner.text = "Initializing Authenik8 engine...";
290
+ // Phase 1: clean → unstable
196
291
  console.clear();
197
292
  console.log(chalk.cyan(cleanLogo));
198
293
  await sleep(200);
199
- // Phase 2: glitch burst (irregular feel)
294
+ // Phase 2: glitch burst (irregular feel)
200
295
  for (let i = 0; i < 5; i++) {
201
296
  console.clear();
202
297
  const frame = glitchFrames[Math.floor(Math.random() * glitchFrames.length)];
203
298
  console.log(chalk.cyan(frame));
204
299
  await sleep(120 + Math.random() * 120);
205
300
  }
206
- // Phase 3: stabilization flicker
301
+ // Phase 3: stabilization flicker
207
302
  for (let i = 0; i < 2; i++) {
208
303
  console.clear();
209
304
  console.log(chalk.cyan(cleanLogo));
@@ -212,14 +307,21 @@ async function showBootLogo() {
212
307
  console.log(chalk.gray(cleanLogo));
213
308
  await sleep(120);
214
309
  }
215
- // Final render
310
+ // Final render
216
311
  console.clear();
217
312
  console.log(chalk.cyan.bold(cleanLogo));
218
313
  await sleep(800);
219
- boot.succeed("Engine ready");
314
+ spinner.succeed("Engine ready");
220
315
  }
221
- //const isResume = process.argv.includes("--resume");
222
316
  async function main() {
317
+ process.on("SIGINT", async () => {
318
+ console.log("\n");
319
+ spinner.stop();
320
+ killAllProcesses();
321
+ console.log(chalk.yellow("⏸ Setup interrupted."));
322
+ console.log(chalk.gray(`↻ Resume with: create-authenik8-app ${projectName} --resume`));
323
+ process.exit(0);
324
+ });
223
325
  try {
224
326
  await showBootLogo();
225
327
  if (process.argv.includes("--help")) {
@@ -272,227 +374,320 @@ Features:
272
374
  • Prisma ORM (optional)
273
375
  • Git initialization (optional)
274
376
  • OAuth + Auth
377
+
378
+
379
+ `));
380
+ const savedState = loadState();
381
+ let currentStep = "start";
382
+ if (!isResume && fs.existsSync(targetDir)) {
383
+ if (savedState) {
384
+ console.log(chalk.red(`❌ "${projectName}" already contains an incomplete Authenik8 setup. Run again with --resume.`));
385
+ }
386
+ else {
387
+ console.log(chalk.red(`❌ Directory "${projectName}" already exists.`));
388
+ }
389
+ process.exit(1);
390
+ }
391
+ if (isResume || fs.existsSync(stateFile)) {
392
+ console.log(chalk.gray(`
393
+ Resumable steps:
394
+ ✔ project scaffold
395
+ ✔ auth install
396
+ ✔ prisma setup
397
+ ✔ deps install
398
+ ✔ prisma generate
399
+ ✔ git init
275
400
  `));
276
- answers = await inquirer.prompt([
277
- {
278
- type: "list",
279
- name: "framework",
280
- message: "Choose framework:",
281
- choices: ["Express", "Fastify (coming soon)"],
282
- default: state.framework ?? "Express",
283
- },
284
- {
285
- type: "confirm",
286
- name: "usePrisma",
287
- message: "Use Prisma?",
288
- default: state.usePrisma ?? true,
289
- },
290
- {
291
- type: "list",
292
- name: "database",
293
- message: "Choose database:",
294
- choices: [
295
- { name: "PostgreSQL", value: "postgresql" },
296
- { name: "SQLite ", value: "sqlite" }
297
- ],
298
- when: (answers) => answers.usePrisma,
299
- default: state.database ?? "sqlite",
300
- },
301
- {
302
- type: "confirm",
303
- name: "useGit",
304
- message: "Initialize git?",
305
- default: state.useGit ?? true,
306
- }, {
307
- type: "list",
308
- name: "authMode",
309
- message: "Choose authentication setup:",
310
- choices: [
311
- { name: "JWT only", value: "base" },
312
- { name: "Email + Password Auth", value: "auth" },
313
- { name: "Full Auth (Password + OAuth)", value: "auth-oauth" },
314
- ],
315
- default: state.authMode ?? "base"
401
+ }
402
+ if (isResume) {
403
+ if (!savedState) {
404
+ console.log(chalk.red(`❌ No saved setup state found for "${projectName}".`));
405
+ process.exit(1);
316
406
  }
317
- ]);
407
+ state = savedState;
408
+ currentStep = state.step;
409
+ console.log(chalk.yellow(`\n↻ Resuming setup for ${projectName} from "${currentStep}"...\n`));
410
+ }
411
+ else {
412
+ const promptAnswers = await inquirer.prompt([
413
+ {
414
+ type: "list",
415
+ name: "framework",
416
+ message: "Choose framework:",
417
+ choices: ["Express", "Fastify (coming soon)"],
418
+ default: "Express",
419
+ },
420
+ {
421
+ type: "confirm",
422
+ name: "usePrisma",
423
+ message: "Use Prisma?",
424
+ default: true,
425
+ },
426
+ {
427
+ type: "list",
428
+ name: "database",
429
+ message: "Choose database:",
430
+ choices: [
431
+ { name: "PostgreSQL", value: "postgresql" },
432
+ { name: "SQLite ", value: "sqlite" }
433
+ ],
434
+ when: (answers) => answers.usePrisma,
435
+ default: "sqlite",
436
+ },
437
+ {
438
+ type: "confirm",
439
+ name: "useGit",
440
+ message: "Initialize git?",
441
+ default: true,
442
+ }, {
443
+ type: "list",
444
+ name: "authMode",
445
+ message: "Choose authentication setup:",
446
+ choices: [
447
+ { name: "JWT only", value: "base" },
448
+ { name: "Email + Password Auth", value: "auth" },
449
+ { name: "Full Auth (Password + OAuth)", value: "auth-oauth" },
450
+ ],
451
+ default: "base"
452
+ }
453
+ ]);
454
+ //saveState({
455
+ //...promptAnswers,
456
+ //step:"prompts"});
457
+ state = {
458
+ ...state,
459
+ ...promptAnswers,
460
+ step: "prompts"
461
+ };
462
+ saveState({
463
+ ...state
464
+ });
465
+ currentStep = "prompts";
466
+ }
318
467
  function assertRequired(value, name) {
319
468
  if (value === undefined || value === null || value === "") {
320
469
  console.log(`❌ Missing required input: ${name}`);
321
470
  process.exit(1);
322
471
  }
323
472
  }
324
- const finalConfig = {
325
- ...state,
326
- ...answers,
327
- };
328
- assertRequired(finalConfig.framework, "framework");
329
- assertRequired(finalConfig.authMode, "authMode");
330
- if (finalConfig.usePrisma && !finalConfig.database) {
331
- finalConfig.database = "sqlite";
332
- }
333
- if (finalConfig.usePrisma) {
334
- assertRequired(finalConfig.database, "database");
335
- }
336
- if (finalConfig.usePrisma && !answers.database && !state.database) {
337
- console.log(chalk.gray("ℹ️ Defaulting to SQLite (quick start)"));
338
- }
339
- if (!answers.authMode || !answers.framework) {
340
- console.log("❌ Invalid setup state. Restart CLI.");
341
- process.exit(1);
473
+ assertRequired(state.framework, "framework");
474
+ assertRequired(state.authMode, "authMode");
475
+ if (state.usePrisma) {
476
+ if (!state.database) {
477
+ state.database = "sqlite";
478
+ }
479
+ assertRequired(state.database, "database");
342
480
  }
343
- console.log(chalk.cyan("\n⚙️ Setting things up...\n"));
481
+ console.log(chalk.cyan("\nSetting up your project\n"));
344
482
  const templateRoot = path.resolve(__dirname, "../../templates");
345
483
  let templateName = "express-base";
346
- if (answers.authMode === "auth") {
484
+ if (state.authMode === "auth") {
347
485
  templateName = "express-auth";
348
486
  }
349
- if (answers.authMode === "auth-oauth") {
487
+ if (state.authMode === "auth-oauth") {
350
488
  templateName = "express-auth+";
351
489
  }
352
490
  const templatePath = path.join(templateRoot, templateName);
353
491
  // 📁 Create project (SPINNER)
354
- const createSpinner = ora("Creating project structure...").start();
355
- try {
356
- await fs.copy(templatePath, targetDir);
357
- projectCreated = true;
358
- createSpinner.succeed("Project files created");
359
- }
360
- catch (err) {
361
- createSpinner.fail("Failed to create project");
362
- console.error(err);
363
- process.exit(1);
364
- }
365
- let selectedHash = "bcryptjs"; // default safe fallback
366
- if (answers.authMode !== "base") {
367
- const authSpinner = ora("Installing password auth...").start();
368
- selectedHash = getBestHashLib();
492
+ if (!hasReachedStep(currentStep, "project-created")) {
493
+ renderStep("project-created");
369
494
  try {
370
- spawnSync("npm", ["install", selectedHash], {
371
- cwd: targetDir,
372
- stdio: "ignore"
373
- });
374
- authSpinner.succeed(`Password auth ready ${selectedHash}`);
495
+ await fs.copy(templatePath, targetDir);
496
+ projectCreated = true;
497
+ saveState({ step: "project-created" });
498
+ currentStep = "project-created";
499
+ renderStep(currentStep);
375
500
  }
376
501
  catch (err) {
377
- if (selectedHash !== "bcryptjs") {
378
- authSpinner.warn(`${selectedHash} failed, falling back to bcryptjs`);
379
- spawnSync("npm", ["install", "bcryptjs"], {
502
+ console.error(err);
503
+ process.exit(1);
504
+ }
505
+ }
506
+ else {
507
+ console.log(chalk.gray("↷ Skipping project creation (already completed)"));
508
+ }
509
+ let selectedHash = "bcryptjs"; // default safe fallback
510
+ if (!hasReachedStep(currentStep, "auth-installed")) {
511
+ if (state.authMode !== "base") {
512
+ spinner.start("Installing password auth...");
513
+ selectedHash = getBestHashLib();
514
+ try {
515
+ const installResult = await run(getCommand("npm"), ["install", selectedHash], {
380
516
  cwd: targetDir,
381
- stdio: "ignore",
517
+ stdio: "ignore"
382
518
  });
383
- selectedHash = "bcryptjs";
384
- authSpinner.succeed("Password auth ready (bcryptjs fallback)");
385
519
  }
386
- else {
387
- authSpinner.fail("Failed to install password auth");
388
- process.exit(1);
520
+ catch {
521
+ if (selectedHash !== "bcryptjs") {
522
+ spinner.warn(`${selectedHash} failed, falling back to bcryptjs`);
523
+ const fallbackResult = await run(getCommand("npm"), ["install", "bcryptjs"], {
524
+ cwd: targetDir,
525
+ stdio: "ignore",
526
+ });
527
+ spinner.stop();
528
+ selectedHash = "bcryptjs";
529
+ renderStep("auth-installed");
530
+ }
531
+ else {
532
+ spinner.fail("Failed to install password auth");
533
+ process.exit(1);
534
+ spinner.fail("Failed to install password auth");
535
+ process.exit(1);
536
+ }
389
537
  }
390
538
  }
539
+ renderStep("auth-installed");
540
+ if (state.authMode !== "base") {
541
+ spinner.start("Installing password auth...");
542
+ const hashLib = selectedHash;
543
+ await fs.writeFile(path.join(targetDir, "src/utils/hash.ts"), generateHashModule(hashLib));
544
+ }
545
+ saveState({ step: "auth-installed", ...(state.authMode !== "base" && { hashLib: selectedHash })
546
+ });
547
+ currentStep = "auth-installed";
548
+ renderStep(currentStep);
391
549
  }
392
- if (answers.authMode !== "base") {
393
- const hashLib = getBestHashLib(); // or CLI decision
394
- await fs.writeFile(path.join(targetDir, "src/utils/hash.ts"), generateHashModule(hashLib));
395
- const deps = [];
396
- if (hashLib === "argon2")
397
- deps.push("argon2");
398
- if (hashLib === "bcryptjs")
399
- deps.push("bcryptjs");
400
- if (deps.length) {
401
- execSync(`npm install ${deps.join(" ")}`, {
402
- cwd: targetDir,
403
- stdio: "ignore",
404
- });
550
+ else {
551
+ selectedHash = savedState?.hashLib ?? selectedHash;
552
+ console.log(chalk.gray(" Skipping auth setup (already completed)"));
553
+ }
554
+ if (!hasReachedStep(currentStep, "prisma-configured")) {
555
+ const pkgPath = path.join(targetDir, "package.json");
556
+ const pkg = await fs.readJson(pkgPath);
557
+ pkg.dependencies = {
558
+ ...pkg.dependencies,
559
+ ioredis: "^5.8.1",
560
+ };
561
+ if (state.usePrisma) {
562
+ spinner.start("Adding Prisma setup...");
563
+ try {
564
+ const dbType = state.database ===
565
+ "postgresql" ? "postgresql" :
566
+ "sqlite";
567
+ const prismaTemplatePath = path.join(templateRoot, `prisma/${dbType}`);
568
+ // Copy prisma schema
569
+ await fs.copy(path.join(prismaTemplatePath, "schema.prisma"), path.join(targetDir, "prisma/schema.prisma"));
570
+ // Copy env
571
+ await fs.copy(path.join(prismaTemplatePath, ".env"), path.join(targetDir, ".env"));
572
+ // Inject dependencies
573
+ pkg.dependencies = {
574
+ ...pkg.dependencies,
575
+ "@prisma/client": "5.22.0",
576
+ };
577
+ pkg.devDependencies = {
578
+ ...pkg.devDependencies,
579
+ prisma: "5.22.0",
580
+ };
581
+ // Add scripts
582
+ pkg.scripts = {
583
+ ...pkg.scripts,
584
+ "prisma:generate": "prisma generate",
585
+ "prisma:migrate": "prisma migrate dev",
586
+ };
587
+ await fs.writeJson(pkgPath, pkg, { spaces: 2 });
588
+ spinner.succeed(`Prisma (${dbType}) configured`);
589
+ }
590
+ catch (err) {
591
+ spinner.fail("Failed to setup Prisma");
592
+ exitForInterrupt(err);
593
+ }
405
594
  }
406
- ;
595
+ await fs.writeJson(pkgPath, pkg, { spaces: 2 });
596
+ saveState({ step: "prisma-configured", ...(state.authMode !== "base" && { hashLib: selectedHash })
597
+ });
598
+ currentStep = "prisma-configured";
599
+ renderStep(currentStep);
600
+ }
601
+ else {
602
+ console.log(chalk.gray(" Skipping Prisma/package setup (already completed)"));
407
603
  }
408
- if (answers.usePrisma) {
409
- const prismaSpinner = ora("Adding Prisma setup...").start();
604
+ if (!hasReachedStep(currentStep, "deps-installed")) {
605
+ renderStep("deps-installed");
606
+ spinner.start("Installing dependencies...(this may take a few minutes)");
410
607
  try {
411
- const dbType = answers.database ===
412
- "postgresql" ? "postgresql"
413
- : "sqlite";
414
- const prismaTemplatePath = path.join(templateRoot, `prisma/${dbType}`);
415
- // Copy prisma schema
416
- await fs.copy(path.join(prismaTemplatePath, "schema.prisma"), path.join(targetDir, "prisma/schema.prisma"));
417
- // Copy env
418
- await fs.copy(path.join(prismaTemplatePath, ".env"), path.join(targetDir, ".env"));
419
- const pkgPath = path.join(targetDir, "package.json");
420
- const pkg = await fs.readJson(pkgPath);
421
- await fs.writeJson(pkgPath, pkg, { spaces: 2 });
422
- // Inject dependencies
423
- pkg.dependencies = {
424
- ...pkg.dependencies,
425
- "@prisma/client": "5.22.0",
426
- };
427
- pkg.devDependencies = {
428
- ...pkg.devDependencies,
429
- prisma: "5.22.0",
430
- };
431
- // Add scripts
432
- pkg.scripts = {
433
- ...pkg.scripts,
434
- "prisma:generate": "prisma generate",
435
- "prisma:migrate": "prisma migrate dev",
436
- };
437
- prismaSpinner.succeed(`Prisma (${dbType}) configured`);
608
+ await run(getCommand("npm"), ["install"], {
609
+ cwd: targetDir,
610
+ stdio: "ignore",
611
+ });
612
+ spinner.stop();
438
613
  }
439
614
  catch (err) {
440
- prismaSpinner.fail("Failed to setup Prisma");
441
- console.error(err);
615
+ spinner.fail("Failed to install dependencies");
616
+ exitForInterrupt(err);
442
617
  }
443
- if (answers.usePrisma) {
444
- const prismaGenSpinner = ora("Generating Prisma client...").start();
618
+ saveState({ step: "deps-installed", ...(state.authMode !== "base" && { hashLib: selectedHash })
619
+ });
620
+ currentStep = "deps-installed";
621
+ renderStep(currentStep);
622
+ }
623
+ else {
624
+ await run(getCommand("npm"), ["install"], {
625
+ cwd: targetDir,
626
+ stdio: "ignore",
627
+ });
628
+ }
629
+ if (!hasReachedStep(currentStep, "prisma-generated")) {
630
+ if (state.usePrisma) {
631
+ spinner.start("Generating Prisma client...");
445
632
  try {
446
- execSync("npx prisma@5.22.0 generate", {
633
+ await run(getCommand("npx"), ["prisma@5.22.0", "generate"], {
447
634
  cwd: targetDir,
448
635
  stdio: "ignore"
449
636
  });
450
- prismaGenSpinner.succeed("Prisma client generated");
637
+ spinner.stop();
451
638
  }
452
639
  catch (err) {
453
- prismaGenSpinner.fail("Failed to generate Prisma client");
454
- console.error(err);
640
+ spinner.fail("Failed to generate Prisma client");
641
+ exitForInterrupt(err);
455
642
  }
456
643
  }
457
- const installSpinner = ora("Installing dependencies...(this may take a few minutes)").start();
644
+ saveState({ step: "prisma-generated", ...(state.authMode !== "base" && { hashLib: selectedHash })
645
+ });
646
+ currentStep = "prisma-generated";
647
+ renderStep(currentStep);
648
+ }
649
+ else {
650
+ console.log(chalk.gray("↷ Skipping Prisma client generation (already completed)"));
651
+ }
652
+ if (isProduction && !hasReachedStep(currentStep, "production-configured")) {
653
+ spinner.start("Setting up production mode (PM2)...");
458
654
  try {
459
- execSync("npm install", {
655
+ await run(getCommand("npm"), ["install pm2"], {
460
656
  cwd: targetDir,
461
657
  stdio: "ignore",
462
658
  });
463
- installSpinner.succeed("Dependencies installed");
659
+ spinner.stop();
464
660
  }
465
661
  catch (err) {
466
- installSpinner.fail("Failed to install dependencies");
467
- console.error(err);
468
- process.exit(1);
662
+ spinner.fail("Failed to install PM2");
663
+ exitForInterrupt(err);
469
664
  }
470
- if (isProduction) {
471
- const pm2Spinner = ora("Setting up production mode (PM2)...").start();
665
+ saveState({ step: "production-configured", ...(state.authMode !== "base" && { hashLib: selectedHash })
666
+ });
667
+ currentStep = "production-configured";
668
+ renderStep(currentStep);
669
+ }
670
+ if (!hasReachedStep(currentStep, "git-initialized")) {
671
+ if (state.useGit) {
672
+ renderStep("git-initialized");
472
673
  try {
473
- execSync("npm install pm2", {
674
+ await run(getCommand("git"), ["init"], {
474
675
  cwd: targetDir,
475
676
  stdio: "ignore",
476
677
  });
477
- pm2Spinner.succeed("PM2 installed (production-ready)");
678
+ spinner.stop();
478
679
  }
479
680
  catch (err) {
480
- pm2Spinner.fail("Failed to install PM2");
681
+ spinner.fail("Git init failed");
481
682
  }
482
683
  }
684
+ saveState({ step: "git-initialized", ...(state.authMode !== "base" && { hashLib: selectedHash }),
685
+ });
686
+ currentStep = "git-initialized";
687
+ renderStep("done");
483
688
  }
484
- if (answers.useGit) {
485
- const gitSpinner = ora("Initializing git...").start();
486
- try {
487
- execSync("git init", {
488
- cwd: targetDir,
489
- stdio: "ignore",
490
- });
491
- gitSpinner.succeed("Git initialized");
492
- }
493
- catch (err) {
494
- gitSpinner.fail("Git init failed");
495
- }
689
+ else {
690
+ console.log(chalk.gray("↷ Skipping git init (already completed)"));
496
691
  }
497
692
  console.log(chalk.green.bold("\n🎉 Authenik8 app created successfully!\n"));
498
693
  if (isProduction) {
@@ -524,28 +719,28 @@ redis-server --daemonize yes
524
719
  npm run dev
525
720
 
526
721
  Auth Features:
527
- ${answers.authMode === "base"
722
+ ${state.authMode === "base"
528
723
  ? "✓ JWT only"
529
- : answers.authMode === "auth"
724
+ : state.authMode === "auth"
530
725
  ? "✓ Email + Password"
531
726
  : "✓ Password + OAuth (Google/GitHub)"}
532
727
 
533
728
  🛠 Stack:
534
729
  ✔ Express
535
- ✔ ${answers.usePrisma ? (answers.database.includes("postgresql") ? "PostgreSQL" : "SQLite") : "No database"}
536
- ✔ ${answers.usePrisma ? "Prisma ORM" : "No ORM"}
730
+ ✔ ${state.usePrisma ? (state.database === "postgresql" ? "PostgreSQL" : "SQLite") : "No database"}
731
+ ✔ ${state.usePrisma ? "Prisma ORM" : "No ORM"}
537
732
 
538
733
  📡 API Routes:
539
- ${answers.authMode === "base"
734
+ ${state.authMode === "base"
540
735
  ? `
541
736
  GET /public
542
737
  GET /guest
543
738
  GET /protected
544
739
  POST /refresh
545
740
  `
546
- : answers.authMode === "auth"
741
+ : state.authMode === "auth"
547
742
  ? `
548
- POST /auth/register
743
+ POST /auth/register
549
744
  POST /auth/login
550
745
  POST /auth/refresh
551
746
  GET /protected
@@ -573,26 +768,29 @@ Run:
573
768
  npm run pm2:start
574
769
  `);
575
770
  }
771
+ saveState({ step: "done", ...(state.authMode !== "base" && { hashLib: selectedHash }),
772
+ });
773
+ clearState();
576
774
  }
577
775
  catch (err) {
578
- console.error("Fatal error", err);
579
- process.exit(1);
776
+ if (err instanceof ExitPromptError) {
777
+ throw err;
778
+ console.error("Fatal error", err);
779
+ process.exit(1);
780
+ }
580
781
  }
581
782
  }
582
- process.on("SIGINT", () => {
583
- console.log("\n👋 Cancelled.");
584
- process.exit(0);
585
- });
586
783
  main().catch(async (err) => {
587
784
  if (err instanceof ExitPromptError) {
588
- console.log("\n👋 Authenik8 setup cancelled.");
589
- if (projectCreated && fs.existsSync(targetDir)) {
590
- await fs.remove(targetDir);
591
- console.log("🧹 Cleaned up incomplete project.");
592
- }
785
+ console.log("\n👋 Authenik8 setup cancelled.You can --resume later");
786
+ killAllProcesses();
787
+ await new Promise((r) => setTimeout(r, 300));
788
+ console.log(chalk.bgYellow.black(" SETUP CANCELLED "));
789
+ await cleanupIncompleteProject();
593
790
  process.exit(0);
594
791
  }
595
- console.error("❌ Unexpected error:", err);
792
+ console.error(chalk.red("\n❌ Unexpected error:"), err);
793
+ await cleanupIncompleteProject();
596
794
  process.exit(1);
597
795
  });
598
796
  //# sourceMappingURL=index.js.map