create-authenik8-app 2.0.7 → 2.0.9

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 (65) hide show
  1. package/README.md +19 -11
  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 +308 -180
  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 +265 -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 +17 -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 +7 -0
  39. package/dist/steps/finalSetup.d.ts.map +1 -0
  40. package/dist/steps/finalSetup.js +113 -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 +76 -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 +3 -2
  63. package/templates/express-auth/ecosystem.config.ts +0 -16
  64. package/templates/express-auth+/ecosystem.config.ts +0 -16
  65. package/templates/express-base/ecosystem.config.ts +0 -16
package/dist/bin/index.js CHANGED
@@ -5,18 +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
- let answers = {};
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();
44
+ }
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;
54
+ }
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
+ }
67
+ }
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);
74
+ }
75
+ const projectName = projectNameArg;
76
+ //let answers:any = {};
77
+ let state = {
78
+ step: "start",
79
+ projectName,
80
+ };
13
81
  const platform = os.platform();
14
82
  // 'linux' | 'darwin' | 'win32'
15
83
  const isTermux = process.env.PREFIX?.includes("com.termux") ||
16
84
  process.env.TERMUX === "true";
17
85
  function getBestHashLib() {
18
86
  if (isTermux)
19
- return "bcryptjs"; // argon2 breaks here
87
+ return "bcryptjs";
20
88
  if (platform === "win32")
21
89
  return "bcryptjs";
22
90
  // avoids build tools issues for most users
@@ -56,14 +124,9 @@ return bcrypt.compare(password, hash);
56
124
  }
57
125
  const __filename = fileURLToPath(import.meta.url);
58
126
  const __dirname = path.dirname(__filename);
59
- const projectName = process.argv[2];
60
- if (!projectName) {
61
- console.log(chalk.red("❌ Please provide a project name"));
62
- process.exit(1);
63
- }
64
- const isProduction = process.argv.includes("--production-ready");
65
- const isResume = process.argv.includes("--resume");
66
- 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);
67
130
  let projectCreated = false;
68
131
  const stepOrder = [
69
132
  "start",
@@ -77,18 +140,28 @@ const stepOrder = [
77
140
  "git-initialized",
78
141
  "done",
79
142
  ];
80
- const stateFile = path.join(targetDir, ".authenik8-state.json");
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`);
81
157
  function hasReachedStep(currentStep, targetStep) {
82
158
  return stepOrder.indexOf(currentStep) >= stepOrder.indexOf(targetStep);
83
159
  }
84
- function saveState(step, extra = {}) {
85
- fs.ensureDirSync(targetDir);
86
- fs.writeJsonSync(stateFile, {
87
- step,
88
- projectName,
89
- ...answers,
90
- ...extra,
91
- }, { spaces: 2 });
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 });
92
165
  }
93
166
  function loadState() {
94
167
  if (!fs.existsSync(stateFile))
@@ -100,6 +173,27 @@ function clearState() {
100
173
  fs.removeSync(stateFile);
101
174
  }
102
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
+ }
194
+ }
195
+ console.log("");
196
+ }
103
197
  function isInterruptedError(err) {
104
198
  return (typeof err === "object" &&
105
199
  err !== null &&
@@ -107,12 +201,10 @@ function isInterruptedError(err) {
107
201
  (err.signal === "SIGINT" ||
108
202
  err.signal === "SIGTERM"));
109
203
  }
110
- function exitForInterrupt(err) {
204
+ async function exitForInterrupt(err) {
111
205
  if (isInterruptedError(err)) {
112
206
  throw err;
113
207
  }
114
- console.error(err);
115
- process.exit(1);
116
208
  }
117
209
  async function cleanupIncompleteProject() {
118
210
  if (projectCreated && fs.existsSync(targetDir)) {
@@ -121,87 +213,92 @@ async function cleanupIncompleteProject() {
121
213
  }
122
214
  }
123
215
  const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
124
- const cleanLogo = `
125
-
126
- █████╗ █████╗
127
- ██╔══██╗ ██╔══██╗
128
- ███████║ ╚█████╔╝
129
- ██╔══██║ ██╔══██╗
130
- ██║ ██║ ╚█████╔╝
131
- ╚═╝ ╚═╝ ╚════╝
132
-
133
- A8
134
-
135
- Authenik8 CLI
136
- Build Faster
137
- 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
138
230
  `;
139
231
  const glitchFrames = [
140
232
  `
141
- █████╗ █████╗
142
- ██╔══██╗ ██▒▒▒▒██
143
- ███████║ ╚█████╔╝
144
- ██╔══██║ ██▒▒▒▒██
145
- ██║ ██║ ╚█████╔╝
146
- ╚═╝ ╚═╝ ╚════╝
147
-
233
+ █████╗ █████╗
234
+ ██╔══██╗ ██▒▒▒▒██
235
+ ███████║ ╚█████╔╝
236
+ ██╔══██║ ██▒▒▒▒██
237
+ ██║ ██║ ╚█████╔╝
238
+ ╚═╝ ╚═╝ ╚════╝
239
+
148
240
  A8
149
241
  Authenik8 CLI
150
-
151
- More
242
+
243
+ More
152
244
  `,
153
- `
154
- ██▓▓██╗ ██▓▓██╗
155
- ██▒▒██╔╝ ██▒▒██╔╝
156
- ██▒▒▒▒██ ╚█████╔╝
157
- ██▓▓██╔╝ ██▒▒██╗
158
- ██▒▒██║ ╚█████╔╝
159
- ╚═════╝ ╚════╝
160
-
245
+ `
246
+ ██▓▓██╗ ██▓▓██╗
247
+ ██▒▒██╔╝ ██▒▒██╔╝
248
+ ██▒▒▒▒██ ╚█████╔╝
249
+ ██▓▓██╔╝ ██▒▒██╗
250
+ ██▒▒██║ ╚█████╔╝
251
+ ╚═════╝ ╚════╝
252
+
161
253
  A8
162
254
  Authenik8 CLI
163
- Faster
255
+ Faster
164
256
  `,
165
- `
166
- ██▒▒██╗ ██▒▒██╗
167
- ██▓▓██╔╝ ██▓▓██╔╝
168
- ██▒▒▒▒██ ╚█████╔╝
169
- ██▓▓██╔╝ ██▓▓██╗
170
- ██▒▒██║ ╚█████╔╝
171
- ╚═════╝ ╚════╝
172
-
257
+ `
258
+ ██▒▒██╗ ██▒▒██╗
259
+ ██▓▓██╔╝ ██▓▓██╔╝
260
+ ██▒▒▒▒██ ╚█████╔╝
261
+ ██▓▓██╔╝ ██▓▓██╗
262
+ ██▒▒██║ ╚█████╔╝
263
+ ╚═════╝ ╚════╝
264
+
173
265
  A8
174
266
  Authenik8 CLI
175
- Build
267
+ Build
176
268
  `,
177
- `
178
- ██▓▓██╗ ██▓▓██╗
179
- ██▒▒██╔╝ ██▒▒██╔╝
180
- ██▒▒▒▒██ ╚█████╔╝
181
- ██▓▓██╔╝ ██▒▒██╗
182
- ██▒▒██║ ╚█████╔╝
183
- ╚═════╝ ╚════╝
184
-
269
+ `
270
+ ██▓▓██╗ ██▓▓██╗
271
+ ██▒▒██╔╝ ██▒▒██╔╝
272
+ ██▒▒▒▒██ ╚█████╔╝
273
+ ██▓▓██╔╝ ██▒▒██╗
274
+ ██▒▒██║ ╚█████╔╝
275
+ ╚═════╝ ╚════╝
276
+
185
277
  A8
186
- Authenik8 CLI
187
-
278
+ Authenik8 CLI
279
+
188
280
  `
189
281
  ];
282
+ function renderHeader() {
283
+ console.log(chalk.cyan.bold("Happy building \nAuthenik8 CLI"));
284
+ console.log(chalk.gray("────────────────────"));
285
+ console.log("");
286
+ }
190
287
  async function showBootLogo() {
191
288
  console.clear();
192
- const boot = ora("Initializing Authenik8 engine...").start();
193
- // Phase 1: clean → unstable
289
+ spinner.text = "Initializing Authenik8 engine...";
290
+ // Phase 1: clean → unstable
194
291
  console.clear();
195
292
  console.log(chalk.cyan(cleanLogo));
196
293
  await sleep(200);
197
- // Phase 2: glitch burst (irregular feel)
294
+ // Phase 2: glitch burst (irregular feel)
198
295
  for (let i = 0; i < 5; i++) {
199
296
  console.clear();
200
297
  const frame = glitchFrames[Math.floor(Math.random() * glitchFrames.length)];
201
298
  console.log(chalk.cyan(frame));
202
299
  await sleep(120 + Math.random() * 120);
203
300
  }
204
- // Phase 3: stabilization flicker
301
+ // Phase 3: stabilization flicker
205
302
  for (let i = 0; i < 2; i++) {
206
303
  console.clear();
207
304
  console.log(chalk.cyan(cleanLogo));
@@ -210,16 +307,19 @@ async function showBootLogo() {
210
307
  console.log(chalk.gray(cleanLogo));
211
308
  await sleep(120);
212
309
  }
213
- // Final render
310
+ // Final render
214
311
  console.clear();
215
312
  console.log(chalk.cyan.bold(cleanLogo));
216
313
  await sleep(800);
217
- boot.succeed("Engine ready");
314
+ spinner.succeed("Engine ready");
218
315
  }
219
316
  async function main() {
220
317
  process.on("SIGINT", async () => {
221
- console.log("\n👋 Authenik8 setup cancelled.");
222
- await cleanupIncompleteProject();
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`));
223
323
  process.exit(0);
224
324
  });
225
325
  try {
@@ -274,6 +374,8 @@ Features:
274
374
  • Prisma ORM (optional)
275
375
  • Git initialization (optional)
276
376
  • OAuth + Auth
377
+
378
+
277
379
  `));
278
380
  const savedState = loadState();
279
381
  let currentStep = "start";
@@ -286,23 +388,28 @@ Features:
286
388
  }
287
389
  process.exit(1);
288
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
400
+ `));
401
+ }
289
402
  if (isResume) {
290
403
  if (!savedState) {
291
404
  console.log(chalk.red(`❌ No saved setup state found for "${projectName}".`));
292
405
  process.exit(1);
293
406
  }
294
- answers = {
295
- framework: savedState.framework,
296
- usePrisma: savedState.usePrisma,
297
- database: savedState.database,
298
- useGit: savedState.useGit,
299
- authMode: savedState.authMode,
300
- };
301
- currentStep = savedState.step;
407
+ state = savedState;
408
+ currentStep = state.step;
302
409
  console.log(chalk.yellow(`\n↻ Resuming setup for ${projectName} from "${currentStep}"...\n`));
303
410
  }
304
411
  else {
305
- answers = await inquirer.prompt([
412
+ const promptAnswers = await inquirer.prompt([
306
413
  {
307
414
  type: "list",
308
415
  name: "framework",
@@ -344,7 +451,17 @@ Features:
344
451
  default: "base"
345
452
  }
346
453
  ]);
347
- saveState("prompts");
454
+ //saveState({
455
+ //...promptAnswers,
456
+ //step:"prompts"});
457
+ state = {
458
+ ...state,
459
+ ...promptAnswers,
460
+ step: "prompts"
461
+ };
462
+ saveState({
463
+ ...state
464
+ });
348
465
  currentStep = "prompts";
349
466
  }
350
467
  function assertRequired(value, name) {
@@ -353,36 +470,35 @@ Features:
353
470
  process.exit(1);
354
471
  }
355
472
  }
356
- assertRequired(answers.framework, "framework");
357
- assertRequired(answers.authMode, "authMode");
358
- if (answers.usePrisma) {
359
- if (!answers.database) {
360
- answers.database = "sqlite";
473
+ assertRequired(state.framework, "framework");
474
+ assertRequired(state.authMode, "authMode");
475
+ if (state.usePrisma) {
476
+ if (!state.database) {
477
+ state.database = "sqlite";
361
478
  }
362
- assertRequired(answers.database, "database");
479
+ assertRequired(state.database, "database");
363
480
  }
364
- console.log(chalk.cyan("\n⚙️ Setting things up...\n"));
481
+ console.log(chalk.cyan("\nSetting up your project\n"));
365
482
  const templateRoot = path.resolve(__dirname, "../../templates");
366
483
  let templateName = "express-base";
367
- if (answers.authMode === "auth") {
484
+ if (state.authMode === "auth") {
368
485
  templateName = "express-auth";
369
486
  }
370
- if (answers.authMode === "auth-oauth") {
487
+ if (state.authMode === "auth-oauth") {
371
488
  templateName = "express-auth+";
372
489
  }
373
490
  const templatePath = path.join(templateRoot, templateName);
374
491
  // 📁 Create project (SPINNER)
375
492
  if (!hasReachedStep(currentStep, "project-created")) {
376
- const createSpinner = ora("Creating project structure...").start();
493
+ renderStep("project-created");
377
494
  try {
378
495
  await fs.copy(templatePath, targetDir);
379
496
  projectCreated = true;
380
- createSpinner.succeed("Project files created");
381
- saveState("project-created");
497
+ saveState({ step: "project-created" });
382
498
  currentStep = "project-created";
499
+ renderStep(currentStep);
383
500
  }
384
501
  catch (err) {
385
- createSpinner.fail("Failed to create project");
386
502
  console.error(err);
387
503
  process.exit(1);
388
504
  }
@@ -392,53 +508,44 @@ Features:
392
508
  }
393
509
  let selectedHash = "bcryptjs"; // default safe fallback
394
510
  if (!hasReachedStep(currentStep, "auth-installed")) {
395
- if (answers.authMode !== "base") {
396
- const authSpinner = ora("Installing password auth...").start();
511
+ if (state.authMode !== "base") {
512
+ spinner.start("Installing password auth...");
397
513
  selectedHash = getBestHashLib();
398
- const installResult = spawnSync("npm", ["install", selectedHash], {
399
- cwd: targetDir,
400
- stdio: "ignore"
401
- });
402
- if (!installResult.error && installResult.status === 0) {
403
- authSpinner.succeed(`Password auth ready ${selectedHash}`);
514
+ try {
515
+ const installResult = await run(getCommand("npm"), ["install", selectedHash], {
516
+ cwd: targetDir,
517
+ stdio: "ignore"
518
+ });
404
519
  }
405
- else {
520
+ catch {
406
521
  if (selectedHash !== "bcryptjs") {
407
- authSpinner.warn(`${selectedHash} failed, falling back to bcryptjs`);
408
- const fallbackResult = spawnSync("npm", ["install", "bcryptjs"], {
522
+ spinner.warn(`${selectedHash} failed, falling back to bcryptjs`);
523
+ const fallbackResult = await run(getCommand("npm"), ["install", "bcryptjs"], {
409
524
  cwd: targetDir,
410
525
  stdio: "ignore",
411
526
  });
412
- if (!fallbackResult.error && fallbackResult.status === 0) {
413
- selectedHash = "bcryptjs";
414
- authSpinner.succeed("Password auth ready (bcryptjs fallback)");
415
- }
416
- else {
417
- authSpinner.fail("Failed to install password auth");
418
- process.exit(1);
419
- }
527
+ spinner.stop();
528
+ selectedHash = "bcryptjs";
529
+ renderStep("auth-installed");
420
530
  }
421
531
  else {
422
- authSpinner.fail("Failed to install password auth");
532
+ spinner.fail("Failed to install password auth");
533
+ process.exit(1);
534
+ spinner.fail("Failed to install password auth");
423
535
  process.exit(1);
424
536
  }
425
537
  }
426
538
  }
427
- if (answers.authMode !== "base") {
539
+ renderStep("auth-installed");
540
+ if (state.authMode !== "base") {
541
+ spinner.start("Installing password auth...");
428
542
  const hashLib = selectedHash;
429
543
  await fs.writeFile(path.join(targetDir, "src/utils/hash.ts"), generateHashModule(hashLib));
430
- //const deps = [];
431
- //if (hashLib === "argon2") deps.push("argon2");
432
- //if (hashLib === "bcryptjs") deps.push("bcryptjs");
433
- //if (deps.length) {
434
- //execSync(`npm install ${deps.join(" ")}`, {
435
- //cwd: targetDir,
436
- //stdio: "ignore",
437
- //});
438
- //};
439
544
  }
440
- saveState("auth-installed", answers.authMode !== "base" ? { hashLib: selectedHash } : {});
545
+ saveState({ step: "auth-installed", ...(state.authMode !== "base" && { hashLib: selectedHash })
546
+ });
441
547
  currentStep = "auth-installed";
548
+ renderStep(currentStep);
442
549
  }
443
550
  else {
444
551
  selectedHash = savedState?.hashLib ?? selectedHash;
@@ -451,12 +558,12 @@ Features:
451
558
  ...pkg.dependencies,
452
559
  ioredis: "^5.8.1",
453
560
  };
454
- if (answers.usePrisma) {
455
- const prismaSpinner = ora("Adding Prisma setup...").start();
561
+ if (state.usePrisma) {
562
+ spinner.start("Adding Prisma setup...");
456
563
  try {
457
- const dbType = answers.database ===
458
- "postgresql" ? "postgresql"
459
- : "sqlite";
564
+ const dbType = state.database ===
565
+ "postgresql" ? "postgresql" :
566
+ "sqlite";
460
567
  const prismaTemplatePath = path.join(templateRoot, `prisma/${dbType}`);
461
568
  // Copy prisma schema
462
569
  await fs.copy(path.join(prismaTemplatePath, "schema.prisma"), path.join(targetDir, "prisma/schema.prisma"));
@@ -478,92 +585,106 @@ Features:
478
585
  "prisma:migrate": "prisma migrate dev",
479
586
  };
480
587
  await fs.writeJson(pkgPath, pkg, { spaces: 2 });
481
- prismaSpinner.succeed(`Prisma (${dbType}) configured`);
588
+ spinner.succeed(`Prisma (${dbType}) configured`);
482
589
  }
483
590
  catch (err) {
484
- prismaSpinner.fail("Failed to setup Prisma");
591
+ spinner.fail("Failed to setup Prisma");
485
592
  exitForInterrupt(err);
486
593
  }
487
594
  }
488
595
  await fs.writeJson(pkgPath, pkg, { spaces: 2 });
489
- saveState("prisma-configured", answers.authMode !== "base" ? { hashLib: selectedHash } : {});
596
+ saveState({ step: "prisma-configured", ...(state.authMode !== "base" && { hashLib: selectedHash })
597
+ });
490
598
  currentStep = "prisma-configured";
599
+ renderStep(currentStep);
491
600
  }
492
601
  else {
493
- console.log(chalk.gray(" Skipping Prisma/package setup (already completed)"));
602
+ console.log(chalk.gray(" Skipping Prisma/package setup (already completed)"));
494
603
  }
495
604
  if (!hasReachedStep(currentStep, "deps-installed")) {
496
- const installSpinner = ora("Installing dependencies...(this may take a few minutes)").start();
605
+ renderStep("deps-installed");
606
+ spinner.start("Installing dependencies...(this may take a few minutes)");
497
607
  try {
498
- execSync("npm install", {
608
+ await run(getCommand("npm"), ["install"], {
499
609
  cwd: targetDir,
500
610
  stdio: "ignore",
501
611
  });
502
- installSpinner.succeed("Dependencies installed");
612
+ spinner.stop();
503
613
  }
504
614
  catch (err) {
505
- installSpinner.fail("Failed to install dependencies");
615
+ spinner.fail("Failed to install dependencies");
506
616
  exitForInterrupt(err);
507
617
  }
508
- saveState("deps-installed", answers.authMode !== "base" ? { hashLib: selectedHash } : {});
618
+ saveState({ step: "deps-installed", ...(state.authMode !== "base" && { hashLib: selectedHash })
619
+ });
509
620
  currentStep = "deps-installed";
621
+ renderStep(currentStep);
510
622
  }
511
623
  else {
512
- console.log(chalk.gray(" Skipping dependency install (already completed)"));
624
+ await run(getCommand("npm"), ["install"], {
625
+ cwd: targetDir,
626
+ stdio: "ignore",
627
+ });
513
628
  }
514
629
  if (!hasReachedStep(currentStep, "prisma-generated")) {
515
- if (answers.usePrisma) {
516
- const prismaGenSpinner = ora("Generating Prisma client...").start();
630
+ if (state.usePrisma) {
631
+ spinner.start("Generating Prisma client...");
517
632
  try {
518
- execSync("npx prisma@5.22.0 generate", {
633
+ await run(getCommand("npx"), ["prisma@5.22.0", "generate"], {
519
634
  cwd: targetDir,
520
635
  stdio: "ignore"
521
636
  });
522
- prismaGenSpinner.succeed("Prisma client generated");
637
+ spinner.stop();
523
638
  }
524
639
  catch (err) {
525
- prismaGenSpinner.fail("Failed to generate Prisma client");
640
+ spinner.fail("Failed to generate Prisma client");
526
641
  exitForInterrupt(err);
527
642
  }
528
643
  }
529
- saveState("prisma-generated", answers.authMode !== "base" ? { hashLib: selectedHash } : {});
644
+ saveState({ step: "prisma-generated", ...(state.authMode !== "base" && { hashLib: selectedHash })
645
+ });
530
646
  currentStep = "prisma-generated";
647
+ renderStep(currentStep);
531
648
  }
532
649
  else {
533
650
  console.log(chalk.gray("↷ Skipping Prisma client generation (already completed)"));
534
651
  }
535
652
  if (isProduction && !hasReachedStep(currentStep, "production-configured")) {
536
- const pm2Spinner = ora("Setting up production mode (PM2)...").start();
653
+ spinner.start("Setting up production mode (PM2)...");
537
654
  try {
538
- execSync("npm install pm2", {
655
+ await run(getCommand("npm"), ["install pm2"], {
539
656
  cwd: targetDir,
540
657
  stdio: "ignore",
541
658
  });
542
- pm2Spinner.succeed("PM2 installed (production-ready)");
659
+ spinner.stop();
543
660
  }
544
661
  catch (err) {
545
- pm2Spinner.fail("Failed to install PM2");
662
+ spinner.fail("Failed to install PM2");
546
663
  exitForInterrupt(err);
547
664
  }
548
- saveState("production-configured", answers.authMode !== "base" ? { hashLib: selectedHash } : {});
665
+ saveState({ step: "production-configured", ...(state.authMode !== "base" && { hashLib: selectedHash })
666
+ });
549
667
  currentStep = "production-configured";
668
+ renderStep(currentStep);
550
669
  }
551
670
  if (!hasReachedStep(currentStep, "git-initialized")) {
552
- if (answers.useGit) {
553
- const gitSpinner = ora("Initializing git...").start();
671
+ if (state.useGit) {
672
+ renderStep("git-initialized");
554
673
  try {
555
- execSync("git init", {
674
+ await run(getCommand("git"), ["init"], {
556
675
  cwd: targetDir,
557
676
  stdio: "ignore",
558
677
  });
559
- gitSpinner.succeed("Git initialized");
678
+ spinner.stop();
560
679
  }
561
680
  catch (err) {
562
- gitSpinner.fail("Git init failed");
681
+ spinner.fail("Git init failed");
563
682
  }
564
683
  }
565
- saveState("git-initialized", answers.authMode !== "base" ? { hashLib: selectedHash } : {});
684
+ saveState({ step: "git-initialized", ...(state.authMode !== "base" && { hashLib: selectedHash }),
685
+ });
566
686
  currentStep = "git-initialized";
687
+ renderStep("done");
567
688
  }
568
689
  else {
569
690
  console.log(chalk.gray("↷ Skipping git init (already completed)"));
@@ -598,28 +719,28 @@ redis-server --daemonize yes
598
719
  npm run dev
599
720
 
600
721
  Auth Features:
601
- ${answers.authMode === "base"
722
+ ${state.authMode === "base"
602
723
  ? "✓ JWT only"
603
- : answers.authMode === "auth"
724
+ : state.authMode === "auth"
604
725
  ? "✓ Email + Password"
605
726
  : "✓ Password + OAuth (Google/GitHub)"}
606
727
 
607
728
  🛠 Stack:
608
729
  ✔ Express
609
- ✔ ${answers.usePrisma ? (answers.database === "postgresql" ? "PostgreSQL" : "SQLite") : "No database"}
610
- ✔ ${answers.usePrisma ? "Prisma ORM" : "No ORM"}
730
+ ✔ ${state.usePrisma ? (state.database === "postgresql" ? "PostgreSQL" : "SQLite") : "No database"}
731
+ ✔ ${state.usePrisma ? "Prisma ORM" : "No ORM"}
611
732
 
612
733
  📡 API Routes:
613
- ${answers.authMode === "base"
734
+ ${state.authMode === "base"
614
735
  ? `
615
736
  GET /public
616
737
  GET /guest
617
738
  GET /protected
618
739
  POST /refresh
619
740
  `
620
- : answers.authMode === "auth"
741
+ : state.authMode === "auth"
621
742
  ? `
622
- POST /auth/register
743
+ POST /auth/register
623
744
  POST /auth/login
624
745
  POST /auth/refresh
625
746
  GET /protected
@@ -647,17 +768,24 @@ Run:
647
768
  npm run pm2:start
648
769
  `);
649
770
  }
650
- saveState("done", answers.authMode !== "base" ? { hashLib: selectedHash } : {});
771
+ saveState({ step: "done", ...(state.authMode !== "base" && { hashLib: selectedHash }),
772
+ });
651
773
  clearState();
652
774
  }
653
775
  catch (err) {
654
- console.error("Fatal error", err);
655
- process.exit(1);
776
+ if (err instanceof ExitPromptError) {
777
+ throw err;
778
+ console.error("Fatal error", err);
779
+ process.exit(1);
780
+ }
656
781
  }
657
782
  }
658
783
  main().catch(async (err) => {
659
784
  if (err instanceof ExitPromptError) {
660
- console.log("\n👋 Authenik8 setup cancelled.");
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 "));
661
789
  await cleanupIncompleteProject();
662
790
  process.exit(0);
663
791
  }