create-warlock 4.1.9 → 4.1.10

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 (29) hide show
  1. package/esm/commands/create-warlock-app/index.mjs +1 -1
  2. package/esm/commands/create-warlock-app/index.mjs.map +1 -1
  3. package/esm/helpers/app.mjs +24 -0
  4. package/esm/helpers/app.mjs.map +1 -1
  5. package/package.json +1 -1
  6. package/templates/warlock/src/app/auth/controllers/forgot-password.controller.ts +1 -6
  7. package/templates/warlock/src/app/auth/controllers/login.controller.ts +1 -4
  8. package/templates/warlock/src/app/auth/controllers/logout-all.controller.ts +2 -10
  9. package/templates/warlock/src/app/auth/controllers/logout.controller.ts +2 -10
  10. package/templates/warlock/src/app/auth/controllers/me.controller.ts +2 -9
  11. package/templates/warlock/src/app/auth/controllers/refresh-token.controller.ts +1 -5
  12. package/templates/warlock/src/app/auth/controllers/reset-password.controller.ts +1 -1
  13. package/templates/warlock/src/app/auth/main.ts +1 -3
  14. package/templates/warlock/src/app/auth/models/otp/migrations/22-12-2025_10-30-20.otp-migration.ts +1 -8
  15. package/templates/warlock/src/app/auth/requests/guarded.request.ts +3 -4
  16. package/templates/warlock/src/app/auth/services/otp.service.ts +5 -15
  17. package/templates/warlock/src/app/auth/services/reset-password.service.ts +1 -3
  18. package/templates/warlock/src/app/posts/models/post/migrations/09-01-2026_02-07-51-post.migration.ts +1 -9
  19. package/templates/warlock/src/app/posts/routes.ts +1 -4
  20. package/templates/warlock/src/app/shared/components/HomePageComponent.tsx +37 -59
  21. package/templates/warlock/src/app/shared/controllers/home-page.controller.ts +18 -0
  22. package/templates/warlock/src/app/shared/controllers/home-page.controller.tsx +2 -9
  23. package/templates/warlock/src/app/uploads/controllers/fetch-uploaded-file.controller.ts +2 -5
  24. package/templates/warlock/src/app/users/events/inject-created-by-user.into-model.event.ts +1 -1
  25. package/templates/warlock/src/app/users/models/user/user.model.ts +4 -8
  26. package/templates/warlock/src/app/users/routes.ts +1 -4
  27. package/templates/warlock/src/app/users/services/login-social.ts +1 -4
  28. package/templates/warlock/src/config/cache.ts +1 -2
  29. package/templates/warlock/src/config/database.ts +1 -4
@@ -8,7 +8,7 @@ async function createWarlockApp(application) {
8
8
  const { useGit, useJWT, features, aiProviders, databaseDriver } = application.options;
9
9
  const templateSpinner = spinner();
10
10
  templateSpinner.start(spinnerMessages.copyingTemplate);
11
- application.init().use("warlock").updatePackageJson().updateDotEnv().configureDatabaseEnv(databaseDriver);
11
+ application.init().use("warlock").updatePackageJson().updateDotEnv().configureDatabaseEnv(databaseDriver).configureHomePage(features.includes("react"));
12
12
  templateSpinner.stop(spinnerMessages.templateCopied);
13
13
  const installSpinner = spinner();
14
14
  installSpinner.start(spinnerMessages.installingDeps);
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../../../../../../@warlock.js/create-warlock/src/commands/create-warlock-app/index.ts"],"sourcesContent":["import { spinner } from \"@clack/prompts\";\r\nimport { App } from \"../../helpers/app\";\r\nimport {\r\n getPackageManager,\r\n runPackageManagerCommand,\r\n} from \"../../helpers/package-manager\";\r\nimport { showSuccessScreen } from \"../../ui/banner\";\r\nimport { spinnerMessages } from \"../../ui/spinners\";\r\n\r\nexport async function createWarlockApp(application: App) {\r\n const options = application.options;\r\n const { useGit, useJWT, features, aiProviders, databaseDriver } = options;\r\n\r\n // Step 1: Initialize and copy template\r\n const templateSpinner = spinner();\r\n templateSpinner.start(spinnerMessages.copyingTemplate);\r\n\r\n application\r\n .init()\r\n .use(\"warlock\")\r\n .updatePackageJson()\r\n .updateDotEnv()\r\n .configureDatabaseEnv(databaseDriver);\r\n\r\n templateSpinner.stop(spinnerMessages.templateCopied);\r\n\r\n // Step 2: Install base dependencies (so the `warlock` binary is available)\r\n const installSpinner = spinner();\r\n installSpinner.start(spinnerMessages.installingDeps);\r\n\r\n await application.install().install;\r\n\r\n installSpinner.stop(spinnerMessages.depsInstalled);\r\n\r\n // Step 3: Add features via `warlock add --no-install`, then one batched install.\r\n // The DB driver, optional features, and AI providers all go through the single\r\n // source of truth (core's feature map) so versions never drift.\r\n const selectedFeatures = [databaseDriver, ...features, ...aiProviders];\r\n\r\n if (selectedFeatures.length > 0) {\r\n const featuresSpinner = spinner();\r\n featuresSpinner.start(spinnerMessages.addingFeatures);\r\n\r\n const added = await application.installFeatures(selectedFeatures);\r\n\r\n if (added) {\r\n await application.install().install;\r\n featuresSpinner.stop(spinnerMessages.featuresAdded);\r\n } else {\r\n featuresSpinner.stop(spinnerMessages.featuresFailed);\r\n }\r\n }\r\n\r\n // Step 4: Initialize Git repository if requested\r\n if (useGit) {\r\n const gitSpinner = spinner();\r\n gitSpinner.start(spinnerMessages.initializingGit);\r\n\r\n await application.git();\r\n\r\n gitSpinner.stop(spinnerMessages.gitInitialized);\r\n }\r\n\r\n // Step 5: Generate JWT or warm cache\r\n if (useJWT) {\r\n const jwtSpinner = spinner();\r\n jwtSpinner.start(spinnerMessages.generatingJwt);\r\n\r\n await application.exec(runPackageManagerCommand(\"jwt\"));\r\n\r\n jwtSpinner.stop(spinnerMessages.jwtGenerated);\r\n } else {\r\n const warmSpinner = spinner();\r\n warmSpinner.start(spinnerMessages.warmingCache);\r\n\r\n await application.exec(\"npx warlock --warm-cache\");\r\n\r\n warmSpinner.stop(spinnerMessages.cacheWarmed);\r\n }\r\n\r\n // Step 6: Show success screen\r\n showSuccessScreen({\r\n projectName: application.name,\r\n database: databaseDriver === \"mongodb\" ? \"MongoDB\" : \"PostgreSQL\",\r\n features: [...features, ...aiProviders],\r\n packageManager: getPackageManager(),\r\n });\r\n}\r\n"],"mappings":";;;;;;AASA,eAAsB,iBAAiB,aAAkB;CAEvD,MAAM,EAAE,QAAQ,QAAQ,UAAU,aAAa,mBAD/B,YAAY;CAI5B,MAAM,kBAAkB,QAAQ;CAChC,gBAAgB,MAAM,gBAAgB,eAAe;CAErD,YACG,KAAK,EACL,IAAI,SAAS,EACb,kBAAkB,EAClB,aAAa,EACb,qBAAqB,cAAc;CAEtC,gBAAgB,KAAK,gBAAgB,cAAc;CAGnD,MAAM,iBAAiB,QAAQ;CAC/B,eAAe,MAAM,gBAAgB,cAAc;CAEnD,MAAM,YAAY,QAAQ,EAAE;CAE5B,eAAe,KAAK,gBAAgB,aAAa;CAKjD,MAAM,mBAAmB;EAAC;EAAgB,GAAG;EAAU,GAAG;CAAW;CAErE,IAAI,iBAAiB,SAAS,GAAG;EAC/B,MAAM,kBAAkB,QAAQ;EAChC,gBAAgB,MAAM,gBAAgB,cAAc;EAIpD,IAAI,MAFgB,YAAY,gBAAgB,gBAAgB,GAErD;GACT,MAAM,YAAY,QAAQ,EAAE;GAC5B,gBAAgB,KAAK,gBAAgB,aAAa;EACpD,OACE,gBAAgB,KAAK,gBAAgB,cAAc;CAEvD;CAGA,IAAI,QAAQ;EACV,MAAM,aAAa,QAAQ;EAC3B,WAAW,MAAM,gBAAgB,eAAe;EAEhD,MAAM,YAAY,IAAI;EAEtB,WAAW,KAAK,gBAAgB,cAAc;CAChD;CAGA,IAAI,QAAQ;EACV,MAAM,aAAa,QAAQ;EAC3B,WAAW,MAAM,gBAAgB,aAAa;EAE9C,MAAM,YAAY,KAAK,yBAAyB,KAAK,CAAC;EAEtD,WAAW,KAAK,gBAAgB,YAAY;CAC9C,OAAO;EACL,MAAM,cAAc,QAAQ;EAC5B,YAAY,MAAM,gBAAgB,YAAY;EAE9C,MAAM,YAAY,KAAK,0BAA0B;EAEjD,YAAY,KAAK,gBAAgB,WAAW;CAC9C;CAGA,kBAAkB;EAChB,aAAa,YAAY;EACzB,UAAU,mBAAmB,YAAY,YAAY;EACrD,UAAU,CAAC,GAAG,UAAU,GAAG,WAAW;EACtC,gBAAgB,kBAAkB;CACpC,CAAC;AACH"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../../../../../../@warlock.js/create-warlock/src/commands/create-warlock-app/index.ts"],"sourcesContent":["import { spinner } from \"@clack/prompts\";\r\nimport { App } from \"../../helpers/app\";\r\nimport {\r\n getPackageManager,\r\n runPackageManagerCommand,\r\n} from \"../../helpers/package-manager\";\r\nimport { showSuccessScreen } from \"../../ui/banner\";\r\nimport { spinnerMessages } from \"../../ui/spinners\";\r\n\r\nexport async function createWarlockApp(application: App) {\r\n const options = application.options;\r\n const { useGit, useJWT, features, aiProviders, databaseDriver } = options;\r\n\r\n // Step 1: Initialize and copy template\r\n const templateSpinner = spinner();\r\n templateSpinner.start(spinnerMessages.copyingTemplate);\r\n\r\n application\r\n .init()\r\n .use(\"warlock\")\r\n .updatePackageJson()\r\n .updateDotEnv()\r\n .configureDatabaseEnv(databaseDriver)\r\n .configureHomePage(features.includes(\"react\"));\r\n\r\n templateSpinner.stop(spinnerMessages.templateCopied);\r\n\r\n // Step 2: Install base dependencies (so the `warlock` binary is available)\r\n const installSpinner = spinner();\r\n installSpinner.start(spinnerMessages.installingDeps);\r\n\r\n await application.install().install;\r\n\r\n installSpinner.stop(spinnerMessages.depsInstalled);\r\n\r\n // Step 3: Add features via `warlock add --no-install`, then one batched install.\r\n // The DB driver, optional features, and AI providers all go through the single\r\n // source of truth (core's feature map) so versions never drift.\r\n const selectedFeatures = [databaseDriver, ...features, ...aiProviders];\r\n\r\n if (selectedFeatures.length > 0) {\r\n const featuresSpinner = spinner();\r\n featuresSpinner.start(spinnerMessages.addingFeatures);\r\n\r\n const added = await application.installFeatures(selectedFeatures);\r\n\r\n if (added) {\r\n await application.install().install;\r\n featuresSpinner.stop(spinnerMessages.featuresAdded);\r\n } else {\r\n featuresSpinner.stop(spinnerMessages.featuresFailed);\r\n }\r\n }\r\n\r\n // Step 4: Initialize Git repository if requested\r\n if (useGit) {\r\n const gitSpinner = spinner();\r\n gitSpinner.start(spinnerMessages.initializingGit);\r\n\r\n await application.git();\r\n\r\n gitSpinner.stop(spinnerMessages.gitInitialized);\r\n }\r\n\r\n // Step 5: Generate JWT or warm cache\r\n if (useJWT) {\r\n const jwtSpinner = spinner();\r\n jwtSpinner.start(spinnerMessages.generatingJwt);\r\n\r\n await application.exec(runPackageManagerCommand(\"jwt\"));\r\n\r\n jwtSpinner.stop(spinnerMessages.jwtGenerated);\r\n } else {\r\n const warmSpinner = spinner();\r\n warmSpinner.start(spinnerMessages.warmingCache);\r\n\r\n await application.exec(\"npx warlock --warm-cache\");\r\n\r\n warmSpinner.stop(spinnerMessages.cacheWarmed);\r\n }\r\n\r\n // Step 6: Show success screen\r\n showSuccessScreen({\r\n projectName: application.name,\r\n database: databaseDriver === \"mongodb\" ? \"MongoDB\" : \"PostgreSQL\",\r\n features: [...features, ...aiProviders],\r\n packageManager: getPackageManager(),\r\n });\r\n}\r\n"],"mappings":";;;;;;AASA,eAAsB,iBAAiB,aAAkB;CAEvD,MAAM,EAAE,QAAQ,QAAQ,UAAU,aAAa,mBAD/B,YAAY;CAI5B,MAAM,kBAAkB,QAAQ;CAChC,gBAAgB,MAAM,gBAAgB,eAAe;CAErD,YACG,KAAK,EACL,IAAI,SAAS,EACb,kBAAkB,EAClB,aAAa,EACb,qBAAqB,cAAc,EACnC,kBAAkB,SAAS,SAAS,OAAO,CAAC;CAE/C,gBAAgB,KAAK,gBAAgB,cAAc;CAGnD,MAAM,iBAAiB,QAAQ;CAC/B,eAAe,MAAM,gBAAgB,cAAc;CAEnD,MAAM,YAAY,QAAQ,EAAE;CAE5B,eAAe,KAAK,gBAAgB,aAAa;CAKjD,MAAM,mBAAmB;EAAC;EAAgB,GAAG;EAAU,GAAG;CAAW;CAErE,IAAI,iBAAiB,SAAS,GAAG;EAC/B,MAAM,kBAAkB,QAAQ;EAChC,gBAAgB,MAAM,gBAAgB,cAAc;EAIpD,IAAI,MAFgB,YAAY,gBAAgB,gBAAgB,GAErD;GACT,MAAM,YAAY,QAAQ,EAAE;GAC5B,gBAAgB,KAAK,gBAAgB,aAAa;EACpD,OACE,gBAAgB,KAAK,gBAAgB,cAAc;CAEvD;CAGA,IAAI,QAAQ;EACV,MAAM,aAAa,QAAQ;EAC3B,WAAW,MAAM,gBAAgB,eAAe;EAEhD,MAAM,YAAY,IAAI;EAEtB,WAAW,KAAK,gBAAgB,cAAc;CAChD;CAGA,IAAI,QAAQ;EACV,MAAM,aAAa,QAAQ;EAC3B,WAAW,MAAM,gBAAgB,aAAa;EAE9C,MAAM,YAAY,KAAK,yBAAyB,KAAK,CAAC;EAEtD,WAAW,KAAK,gBAAgB,YAAY;CAC9C,OAAO;EACL,MAAM,cAAc,QAAQ;EAC5B,YAAY,MAAM,gBAAgB,YAAY;EAE9C,MAAM,YAAY,KAAK,0BAA0B;EAEjD,YAAY,KAAK,gBAAgB,WAAW;CAC9C;CAGA,kBAAkB;EAChB,aAAa,YAAY;EACzB,UAAU,mBAAmB,YAAY,YAAY;EACrD,UAAU,CAAC,GAAG,UAAU,GAAG,WAAW;EACtC,gBAAgB,kBAAkB;CACpC,CAAC;AACH"}
@@ -3,6 +3,7 @@ import { executeCommand, runCommand } from "./exec.mjs";
3
3
  import { getPackageManager } from "./package-manager.mjs";
4
4
  import { packageRoot, template } from "./paths.mjs";
5
5
  import { copyDirectory, copyFile, fileExists, getFile, getJsonFile, putFile, putJsonFile, renameFile } from "@warlock.js/fs";
6
+ import { unlinkSync } from "node:fs";
6
7
  import path from "path";
7
8
 
8
9
  //#region ../@warlock.js/create-warlock/src/helpers/app.ts
@@ -66,6 +67,29 @@ var App = class {
66
67
  return this;
67
68
  }
68
69
  /**
70
+ * Pick the home page implementation based on whether React was selected.
71
+ *
72
+ * The template ships BOTH a plain JSON controller (`home-page.controller.ts`)
73
+ * and a React-rendered page (`home-page.controller.tsx` + `HomePageComponent.tsx`).
74
+ * Exactly one survives the scaffold: React projects keep the `.tsx` page (its
75
+ * `react`/`react-dom` deps come from the `react` feature), every other project
76
+ * keeps the dependency-free JSON controller — so a fresh project never imports
77
+ * `react` unless it asked for it.
78
+ */
79
+ configureHomePage(useReact) {
80
+ const controllers = this.path + "/src/app/shared/controllers";
81
+ const components = this.path + "/src/app/shared/components";
82
+ const remove = (file) => {
83
+ if (fileExists(file)) unlinkSync(file);
84
+ };
85
+ if (useReact) remove(controllers + "/home-page.controller.ts");
86
+ else {
87
+ remove(controllers + "/home-page.controller.tsx");
88
+ remove(components + "/HomePageComponent.tsx");
89
+ }
90
+ return this;
91
+ }
92
+ /**
69
93
  * Install the selected optional features by delegating to the project's own
70
94
  * `warlock add`. `--no-install` records every dependency in package.json and
71
95
  * ejects configs / scripts / setup hooks WITHOUT installing — the caller runs
@@ -1 +1 @@
1
- {"version":3,"file":"app.mjs","names":[],"sources":["../../../../../../@warlock.js/create-warlock/src/helpers/app.ts"],"sourcesContent":["import {\r\n copyDirectory,\r\n copyFile,\r\n fileExists,\r\n getFile,\r\n getJsonFile,\r\n putFile,\r\n putJsonFile,\r\n renameFile,\r\n} from \"@warlock.js/fs\";\r\nimport path from \"path\";\r\nimport { AppOptions, Application } from \"../commands/create-new-app/types\";\r\nimport { getDatabaseDriver } from \"../features/database-drivers\";\r\nimport { executeCommand, runCommand } from \"./exec\";\r\nimport { getPackageManager } from \"./package-manager\";\r\nimport { packageRoot, Template, template } from \"./paths\";\r\n\r\nexport class App {\r\n /**\r\n * Resolved files\r\n */\r\n protected files: Record<string, FileManager> = {};\r\n\r\n /**\r\n * Resolved JSON files\r\n */\r\n protected jsonFiles: Record<string, JsonFileManager> = {};\r\n\r\n public isInstalled = false;\r\n\r\n public constructor(protected app: Application) {}\r\n\r\n public get options(): AppOptions {\r\n return this.app.options;\r\n }\r\n\r\n public use(templateName: Template) {\r\n copyDirectory(template(templateName), this.path);\r\n\r\n if (fileExists(this.path + \"/.env.example\")) {\r\n copyFile(this.path + \"/.env.example\", this.path + \"/.env\");\r\n }\r\n\r\n renameFile(this.path + \"/_.gitignore\", this.path + \"/.gitignore\");\r\n\r\n return this;\r\n }\r\n\r\n public init() {\r\n return this;\r\n }\r\n\r\n public terminate() {\r\n // No longer using outro, using showSuccessScreen instead\r\n }\r\n\r\n public install() {\r\n return runCommand(getPackageManager(), [\"install\"], this.path);\r\n }\r\n\r\n public async exec(command: string) {\r\n const [commandName, ...optionsList] = command.split(\" \");\r\n return await executeCommand(commandName, optionsList, this.path);\r\n }\r\n\r\n public async git() {\r\n const { initializeGitRepository } = await import(\r\n \"./project-builder-helpers\"\r\n );\r\n return await initializeGitRepository(this.path);\r\n }\r\n\r\n public updatePackageJson() {\r\n const pkg = this.package\r\n .replace(\"name\", this.name.replaceAll(\"/\", \"-\"))\r\n .replaceAll(\"yarn\", getPackageManager());\r\n\r\n // Pin every @warlock.js/* dependency to THIS create-warlock release version\r\n // so a scaffolded project always matches the framework version it was created\r\n // with. create-warlock and the framework ship in lockstep, so the scaffolder's\r\n // own version is the single source of truth (the template's hardcoded versions\r\n // are irrelevant — they get overwritten here).\r\n const warlockVersion: string = getJsonFile(packageRoot(\"package.json\")).version;\r\n const content: any = pkg.content;\r\n\r\n for (const field of [\"dependencies\", \"devDependencies\"] as const) {\r\n const deps = content[field] as Record<string, string> | undefined;\r\n if (!deps) continue;\r\n\r\n for (const dependency of Object.keys(deps)) {\r\n if (dependency.startsWith(\"@warlock.js/\")) {\r\n deps[dependency] = warlockVersion;\r\n }\r\n }\r\n }\r\n\r\n pkg.save();\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Wire DB_DRIVER and DB_PORT into .env.\r\n *\r\n * The driver's npm package is installed via `warlock add` (single source for\r\n * versions), so this only touches environment configuration — not deps.\r\n */\r\n public configureDatabaseEnv(driverValue: string) {\r\n const driver = getDatabaseDriver(driverValue);\r\n\r\n if (!driver) return this;\r\n\r\n let envContent = getFile(this.path + \"/.env\") as string;\r\n\r\n envContent = envContent.replace(/DB_PORT=\\d+/, `DB_PORT=${driver.defaultPort}`);\r\n\r\n if (envContent.includes(\"DB_DRIVER=\")) {\r\n envContent = envContent.replace(/DB_DRIVER=\\w*/, `DB_DRIVER=${driver.value}`);\r\n } else {\r\n envContent = envContent.replace(\r\n /DB_PORT=\\d+/,\r\n `DB_PORT=${driver.defaultPort}\\nDB_DRIVER=${driver.value}`,\r\n );\r\n }\r\n\r\n putFile(this.path + \"/.env\", envContent);\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Install the selected optional features by delegating to the project's own\r\n * `warlock add`. `--no-install` records every dependency in package.json and\r\n * ejects configs / scripts / setup hooks WITHOUT installing — the caller runs\r\n * one batched install afterwards. Versions come from core's feature map, so\r\n * the scaffolder never duplicates them.\r\n *\r\n * `--no-install` is passed LAST on purpose: the CLI parser treats the\r\n * positional after a bare flag as that flag's value, so it must follow the\r\n * feature list, not precede it.\r\n */\r\n public async installFeatures(features: string[]) {\r\n if (features.length === 0) return true;\r\n\r\n return this.exec(`npx warlock add ${features.join(\" \")} --no-install`);\r\n }\r\n\r\n /**\r\n * Get package json file\r\n */\r\n public get package() {\r\n return this.json(\"package.json\");\r\n }\r\n\r\n public updateDotEnv() {\r\n this.file(\".env\").replaceAll(\"appName\", this.name).save();\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Get env file to update\r\n */\r\n public get env() {\r\n return this.file(\".env\");\r\n }\r\n\r\n public get name() {\r\n return this.app.appName;\r\n }\r\n\r\n public get path() {\r\n return this.app.appPath;\r\n }\r\n\r\n public file(relativePath: string) {\r\n const fullPath = path.resolve(this.path, relativePath);\r\n\r\n if (!this.files[fullPath]) {\r\n this.files[fullPath] = file(fullPath);\r\n }\r\n\r\n return this.files[fullPath];\r\n }\r\n\r\n public json(relativePath: string): JsonFileManager {\r\n const fullPath = path.resolve(this.path, relativePath);\r\n\r\n if (!this.jsonFiles[fullPath]) {\r\n this.jsonFiles[fullPath] = jsonFile(fullPath);\r\n }\r\n\r\n return this.jsonFiles[fullPath];\r\n }\r\n}\r\n\r\nexport function app(app: Application) {\r\n return new App(app);\r\n}\r\n\r\nexport class FileManager {\r\n public content!: string;\r\n public constructor(protected filePath: string) {\r\n this.parseContent();\r\n }\r\n\r\n protected parseContent() {\r\n this.content = getFile(this.filePath) as string;\r\n }\r\n\r\n public replace(search: string, replace: string) {\r\n this.content = this.content.replace(search, replace);\r\n\r\n return this;\r\n }\r\n\r\n public replaceAll(search: string, replace: string) {\r\n this.content = this.content.replaceAll(search, replace);\r\n\r\n return this;\r\n }\r\n\r\n public save() {\r\n putFile(this.filePath, this.content);\r\n }\r\n}\r\n\r\nexport class JsonFileManager extends FileManager {\r\n protected parseContent() {\r\n this.content = getJsonFile(this.filePath);\r\n }\r\n\r\n public save() {\r\n putJsonFile(this.filePath, this.content);\r\n }\r\n\r\n public has(key: string) {\r\n return this.content[key] !== undefined;\r\n }\r\n\r\n public replace(key: string, value: any) {\r\n this.content[key] = value;\r\n\r\n return this;\r\n }\r\n\r\n public replaceAll(key: string, value: any) {\r\n const contentAsString = JSON.stringify(this.content);\r\n\r\n this.content = JSON.parse(contentAsString.replaceAll(key, value));\r\n\r\n return this;\r\n }\r\n}\r\n\r\nexport function file(path: string) {\r\n return new FileManager(path);\r\n}\r\n\r\nexport function jsonFile(path: string) {\r\n return new JsonFileManager(path);\r\n}\r\n"],"mappings":"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"}
1
+ {"version":3,"file":"app.mjs","names":[],"sources":["../../../../../../@warlock.js/create-warlock/src/helpers/app.ts"],"sourcesContent":["import {\r\n copyDirectory,\r\n copyFile,\r\n fileExists,\r\n getFile,\r\n getJsonFile,\r\n putFile,\r\n putJsonFile,\r\n renameFile,\r\n} from \"@warlock.js/fs\";\r\nimport { unlinkSync } from \"node:fs\";\r\nimport path from \"path\";\r\nimport { AppOptions, Application } from \"../commands/create-new-app/types\";\r\nimport { getDatabaseDriver } from \"../features/database-drivers\";\r\nimport { executeCommand, runCommand } from \"./exec\";\r\nimport { getPackageManager } from \"./package-manager\";\r\nimport { packageRoot, Template, template } from \"./paths\";\r\n\r\nexport class App {\r\n /**\r\n * Resolved files\r\n */\r\n protected files: Record<string, FileManager> = {};\r\n\r\n /**\r\n * Resolved JSON files\r\n */\r\n protected jsonFiles: Record<string, JsonFileManager> = {};\r\n\r\n public isInstalled = false;\r\n\r\n public constructor(protected app: Application) {}\r\n\r\n public get options(): AppOptions {\r\n return this.app.options;\r\n }\r\n\r\n public use(templateName: Template) {\r\n copyDirectory(template(templateName), this.path);\r\n\r\n if (fileExists(this.path + \"/.env.example\")) {\r\n copyFile(this.path + \"/.env.example\", this.path + \"/.env\");\r\n }\r\n\r\n renameFile(this.path + \"/_.gitignore\", this.path + \"/.gitignore\");\r\n\r\n return this;\r\n }\r\n\r\n public init() {\r\n return this;\r\n }\r\n\r\n public terminate() {\r\n // No longer using outro, using showSuccessScreen instead\r\n }\r\n\r\n public install() {\r\n return runCommand(getPackageManager(), [\"install\"], this.path);\r\n }\r\n\r\n public async exec(command: string) {\r\n const [commandName, ...optionsList] = command.split(\" \");\r\n return await executeCommand(commandName, optionsList, this.path);\r\n }\r\n\r\n public async git() {\r\n const { initializeGitRepository } = await import(\r\n \"./project-builder-helpers\"\r\n );\r\n return await initializeGitRepository(this.path);\r\n }\r\n\r\n public updatePackageJson() {\r\n const pkg = this.package\r\n .replace(\"name\", this.name.replaceAll(\"/\", \"-\"))\r\n .replaceAll(\"yarn\", getPackageManager());\r\n\r\n // Pin every @warlock.js/* dependency to THIS create-warlock release version\r\n // so a scaffolded project always matches the framework version it was created\r\n // with. create-warlock and the framework ship in lockstep, so the scaffolder's\r\n // own version is the single source of truth (the template's hardcoded versions\r\n // are irrelevant — they get overwritten here).\r\n const warlockVersion: string = getJsonFile(packageRoot(\"package.json\")).version;\r\n const content: any = pkg.content;\r\n\r\n for (const field of [\"dependencies\", \"devDependencies\"] as const) {\r\n const deps = content[field] as Record<string, string> | undefined;\r\n if (!deps) continue;\r\n\r\n for (const dependency of Object.keys(deps)) {\r\n if (dependency.startsWith(\"@warlock.js/\")) {\r\n deps[dependency] = warlockVersion;\r\n }\r\n }\r\n }\r\n\r\n pkg.save();\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Wire DB_DRIVER and DB_PORT into .env.\r\n *\r\n * The driver's npm package is installed via `warlock add` (single source for\r\n * versions), so this only touches environment configuration — not deps.\r\n */\r\n public configureDatabaseEnv(driverValue: string) {\r\n const driver = getDatabaseDriver(driverValue);\r\n\r\n if (!driver) return this;\r\n\r\n let envContent = getFile(this.path + \"/.env\") as string;\r\n\r\n envContent = envContent.replace(/DB_PORT=\\d+/, `DB_PORT=${driver.defaultPort}`);\r\n\r\n if (envContent.includes(\"DB_DRIVER=\")) {\r\n envContent = envContent.replace(/DB_DRIVER=\\w*/, `DB_DRIVER=${driver.value}`);\r\n } else {\r\n envContent = envContent.replace(\r\n /DB_PORT=\\d+/,\r\n `DB_PORT=${driver.defaultPort}\\nDB_DRIVER=${driver.value}`,\r\n );\r\n }\r\n\r\n putFile(this.path + \"/.env\", envContent);\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Pick the home page implementation based on whether React was selected.\r\n *\r\n * The template ships BOTH a plain JSON controller (`home-page.controller.ts`)\r\n * and a React-rendered page (`home-page.controller.tsx` + `HomePageComponent.tsx`).\r\n * Exactly one survives the scaffold: React projects keep the `.tsx` page (its\r\n * `react`/`react-dom` deps come from the `react` feature), every other project\r\n * keeps the dependency-free JSON controller — so a fresh project never imports\r\n * `react` unless it asked for it.\r\n */\r\n public configureHomePage(useReact: boolean) {\r\n const controllers = this.path + \"/src/app/shared/controllers\";\r\n const components = this.path + \"/src/app/shared/components\";\r\n\r\n const remove = (file: string) => {\r\n if (fileExists(file)) unlinkSync(file);\r\n };\r\n\r\n if (useReact) {\r\n remove(controllers + \"/home-page.controller.ts\");\r\n } else {\r\n remove(controllers + \"/home-page.controller.tsx\");\r\n remove(components + \"/HomePageComponent.tsx\");\r\n }\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Install the selected optional features by delegating to the project's own\r\n * `warlock add`. `--no-install` records every dependency in package.json and\r\n * ejects configs / scripts / setup hooks WITHOUT installing — the caller runs\r\n * one batched install afterwards. Versions come from core's feature map, so\r\n * the scaffolder never duplicates them.\r\n *\r\n * `--no-install` is passed LAST on purpose: the CLI parser treats the\r\n * positional after a bare flag as that flag's value, so it must follow the\r\n * feature list, not precede it.\r\n */\r\n public async installFeatures(features: string[]) {\r\n if (features.length === 0) return true;\r\n\r\n return this.exec(`npx warlock add ${features.join(\" \")} --no-install`);\r\n }\r\n\r\n /**\r\n * Get package json file\r\n */\r\n public get package() {\r\n return this.json(\"package.json\");\r\n }\r\n\r\n public updateDotEnv() {\r\n this.file(\".env\").replaceAll(\"appName\", this.name).save();\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Get env file to update\r\n */\r\n public get env() {\r\n return this.file(\".env\");\r\n }\r\n\r\n public get name() {\r\n return this.app.appName;\r\n }\r\n\r\n public get path() {\r\n return this.app.appPath;\r\n }\r\n\r\n public file(relativePath: string) {\r\n const fullPath = path.resolve(this.path, relativePath);\r\n\r\n if (!this.files[fullPath]) {\r\n this.files[fullPath] = file(fullPath);\r\n }\r\n\r\n return this.files[fullPath];\r\n }\r\n\r\n public json(relativePath: string): JsonFileManager {\r\n const fullPath = path.resolve(this.path, relativePath);\r\n\r\n if (!this.jsonFiles[fullPath]) {\r\n this.jsonFiles[fullPath] = jsonFile(fullPath);\r\n }\r\n\r\n return this.jsonFiles[fullPath];\r\n }\r\n}\r\n\r\nexport function app(app: Application) {\r\n return new App(app);\r\n}\r\n\r\nexport class FileManager {\r\n public content!: string;\r\n public constructor(protected filePath: string) {\r\n this.parseContent();\r\n }\r\n\r\n protected parseContent() {\r\n this.content = getFile(this.filePath) as string;\r\n }\r\n\r\n public replace(search: string, replace: string) {\r\n this.content = this.content.replace(search, replace);\r\n\r\n return this;\r\n }\r\n\r\n public replaceAll(search: string, replace: string) {\r\n this.content = this.content.replaceAll(search, replace);\r\n\r\n return this;\r\n }\r\n\r\n public save() {\r\n putFile(this.filePath, this.content);\r\n }\r\n}\r\n\r\nexport class JsonFileManager extends FileManager {\r\n protected parseContent() {\r\n this.content = getJsonFile(this.filePath);\r\n }\r\n\r\n public save() {\r\n putJsonFile(this.filePath, this.content);\r\n }\r\n\r\n public has(key: string) {\r\n return this.content[key] !== undefined;\r\n }\r\n\r\n public replace(key: string, value: any) {\r\n this.content[key] = value;\r\n\r\n return this;\r\n }\r\n\r\n public replaceAll(key: string, value: any) {\r\n const contentAsString = JSON.stringify(this.content);\r\n\r\n this.content = JSON.parse(contentAsString.replaceAll(key, value));\r\n\r\n return this;\r\n }\r\n}\r\n\r\nexport function file(path: string) {\r\n return new FileManager(path);\r\n}\r\n\r\nexport function jsonFile(path: string) {\r\n return new JsonFileManager(path);\r\n}\r\n"],"mappings":";;;;;;;;;AAkBA,IAAa,MAAb,MAAiB;CAaf,AAAO,YAAY,AAAU,KAAkB;EAAlB;eATkB,CAAC;mBAKO,CAAC;qBAEnC;CAE2B;CAEhD,IAAW,UAAsB;EAC/B,OAAO,KAAK,IAAI;CAClB;CAEA,AAAO,IAAI,cAAwB;EACjC,cAAc,SAAS,YAAY,GAAG,KAAK,IAAI;EAE/C,IAAI,WAAW,KAAK,OAAO,eAAe,GACxC,SAAS,KAAK,OAAO,iBAAiB,KAAK,OAAO,OAAO;EAG3D,WAAW,KAAK,OAAO,gBAAgB,KAAK,OAAO,aAAa;EAEhE,OAAO;CACT;CAEA,AAAO,OAAO;EACZ,OAAO;CACT;CAEA,AAAO,YAAY,CAEnB;CAEA,AAAO,UAAU;EACf,OAAO,WAAW,kBAAkB,GAAG,CAAC,SAAS,GAAG,KAAK,IAAI;CAC/D;CAEA,MAAa,KAAK,SAAiB;EACjC,MAAM,CAAC,aAAa,GAAG,eAAe,QAAQ,MAAM,GAAG;EACvD,OAAO,MAAM,eAAe,aAAa,aAAa,KAAK,IAAI;CACjE;CAEA,MAAa,MAAM;EACjB,MAAM,EAAE,4BAA4B,MAAM,OACxC;EAEF,OAAO,MAAM,wBAAwB,KAAK,IAAI;CAChD;CAEA,AAAO,oBAAoB;EACzB,MAAM,MAAM,KAAK,QACd,QAAQ,QAAQ,KAAK,KAAK,WAAW,KAAK,GAAG,CAAC,EAC9C,WAAW,QAAQ,kBAAkB,CAAC;EAOzC,MAAM,iBAAyB,YAAY,YAAY,cAAc,CAAC,EAAE;EACxE,MAAM,UAAe,IAAI;EAEzB,KAAK,MAAM,SAAS,CAAC,gBAAgB,iBAAiB,GAAY;GAChE,MAAM,OAAO,QAAQ;GACrB,IAAI,CAAC,MAAM;GAEX,KAAK,MAAM,cAAc,OAAO,KAAK,IAAI,GACvC,IAAI,WAAW,WAAW,cAAc,GACtC,KAAK,cAAc;EAGzB;EAEA,IAAI,KAAK;EAET,OAAO;CACT;;;;;;;CAQA,AAAO,qBAAqB,aAAqB;EAC/C,MAAM,SAAS,kBAAkB,WAAW;EAE5C,IAAI,CAAC,QAAQ,OAAO;EAEpB,IAAI,aAAa,QAAQ,KAAK,OAAO,OAAO;EAE5C,aAAa,WAAW,QAAQ,eAAe,WAAW,OAAO,aAAa;EAE9E,IAAI,WAAW,SAAS,YAAY,GAClC,aAAa,WAAW,QAAQ,iBAAiB,aAAa,OAAO,OAAO;OAE5E,aAAa,WAAW,QACtB,eACA,WAAW,OAAO,YAAY,cAAc,OAAO,OACrD;EAGF,QAAQ,KAAK,OAAO,SAAS,UAAU;EAEvC,OAAO;CACT;;;;;;;;;;;CAYA,AAAO,kBAAkB,UAAmB;EAC1C,MAAM,cAAc,KAAK,OAAO;EAChC,MAAM,aAAa,KAAK,OAAO;EAE/B,MAAM,UAAU,SAAiB;GAC/B,IAAI,WAAW,IAAI,GAAG,WAAW,IAAI;EACvC;EAEA,IAAI,UACF,OAAO,cAAc,0BAA0B;OAC1C;GACL,OAAO,cAAc,2BAA2B;GAChD,OAAO,aAAa,wBAAwB;EAC9C;EAEA,OAAO;CACT;;;;;;;;;;;;CAaA,MAAa,gBAAgB,UAAoB;EAC/C,IAAI,SAAS,WAAW,GAAG,OAAO;EAElC,OAAO,KAAK,KAAK,mBAAmB,SAAS,KAAK,GAAG,EAAE,cAAc;CACvE;;;;CAKA,IAAW,UAAU;EACnB,OAAO,KAAK,KAAK,cAAc;CACjC;CAEA,AAAO,eAAe;EACpB,KAAK,KAAK,MAAM,EAAE,WAAW,WAAW,KAAK,IAAI,EAAE,KAAK;EAExD,OAAO;CACT;;;;CAKA,IAAW,MAAM;EACf,OAAO,KAAK,KAAK,MAAM;CACzB;CAEA,IAAW,OAAO;EAChB,OAAO,KAAK,IAAI;CAClB;CAEA,IAAW,OAAO;EAChB,OAAO,KAAK,IAAI;CAClB;CAEA,AAAO,KAAK,cAAsB;EAChC,MAAM,WAAW,KAAK,QAAQ,KAAK,MAAM,YAAY;EAErD,IAAI,CAAC,KAAK,MAAM,WACd,KAAK,MAAM,YAAY,KAAK,QAAQ;EAGtC,OAAO,KAAK,MAAM;CACpB;CAEA,AAAO,KAAK,cAAuC;EACjD,MAAM,WAAW,KAAK,QAAQ,KAAK,MAAM,YAAY;EAErD,IAAI,CAAC,KAAK,UAAU,WAClB,KAAK,UAAU,YAAY,SAAS,QAAQ;EAG9C,OAAO,KAAK,UAAU;CACxB;AACF;AAMA,IAAa,cAAb,MAAyB;CAEvB,AAAO,YAAY,AAAU,UAAkB;EAAlB;EAC3B,KAAK,aAAa;CACpB;CAEA,AAAU,eAAe;EACvB,KAAK,UAAU,QAAQ,KAAK,QAAQ;CACtC;CAEA,AAAO,QAAQ,QAAgB,SAAiB;EAC9C,KAAK,UAAU,KAAK,QAAQ,QAAQ,QAAQ,OAAO;EAEnD,OAAO;CACT;CAEA,AAAO,WAAW,QAAgB,SAAiB;EACjD,KAAK,UAAU,KAAK,QAAQ,WAAW,QAAQ,OAAO;EAEtD,OAAO;CACT;CAEA,AAAO,OAAO;EACZ,QAAQ,KAAK,UAAU,KAAK,OAAO;CACrC;AACF;AAEA,IAAa,kBAAb,cAAqC,YAAY;CAC/C,AAAU,eAAe;EACvB,KAAK,UAAU,YAAY,KAAK,QAAQ;CAC1C;CAEA,AAAO,OAAO;EACZ,YAAY,KAAK,UAAU,KAAK,OAAO;CACzC;CAEA,AAAO,IAAI,KAAa;EACtB,OAAO,KAAK,QAAQ,SAAS;CAC/B;CAEA,AAAO,QAAQ,KAAa,OAAY;EACtC,KAAK,QAAQ,OAAO;EAEpB,OAAO;CACT;CAEA,AAAO,WAAW,KAAa,OAAY;EACzC,MAAM,kBAAkB,KAAK,UAAU,KAAK,OAAO;EAEnD,KAAK,UAAU,KAAK,MAAM,gBAAgB,WAAW,KAAK,KAAK,CAAC;EAEhE,OAAO;CACT;AACF;AAEA,SAAgB,KAAK,MAAc;CACjC,OAAO,IAAI,YAAY,IAAI;AAC7B;AAEA,SAAgB,SAAS,MAAc;CACrC,OAAO,IAAI,gBAAgB,IAAI;AACjC"}
package/package.json CHANGED
@@ -18,7 +18,7 @@
18
18
  "bin": {
19
19
  "create-warlock": "bin/create-app.js"
20
20
  },
21
- "version": "4.1.9",
21
+ "version": "4.1.10",
22
22
  "type": "module",
23
23
  "main": "./esm/index.mjs",
24
24
  "module": "./esm/index.mjs",
@@ -1,9 +1,4 @@
1
- import {
2
- t,
3
- type Request,
4
- type RequestHandler,
5
- type Response,
6
- } from "@warlock.js/core";
1
+ import { t, type Request, type RequestHandler, type Response } from "@warlock.js/core";
7
2
  import { v } from "@warlock.js/seal";
8
3
  import { forgotPasswordService } from "../services/forgot-password.service";
9
4
 
@@ -6,10 +6,7 @@ import { loginService } from "../services/auth.service";
6
6
  * Login controller
7
7
  * POST /auth/login
8
8
  */
9
- export const loginController: RequestHandler<Request<LoginSchema>> = async (
10
- request,
11
- response,
12
- ) => {
9
+ export const loginController: RequestHandler<Request<LoginSchema>> = async (request, response) => {
13
10
  const result = await loginService(request.validated(), {
14
11
  userAgent: request.userAgent,
15
12
  ip: request.ip,
@@ -1,19 +1,11 @@
1
- import {
2
- t,
3
- type Request,
4
- type RequestHandler,
5
- type Response,
6
- } from "@warlock.js/core";
1
+ import { t, type Request, type RequestHandler, type Response } from "@warlock.js/core";
7
2
  import { logoutAllService } from "../services/auth.service";
8
3
 
9
4
  /**
10
5
  * Logout from all devices controller
11
6
  * POST /auth/logout-all
12
7
  */
13
- export const logoutAllController: RequestHandler = async (
14
- request: Request,
15
- response: Response,
16
- ) => {
8
+ export const logoutAllController: RequestHandler = async (request: Request, response: Response) => {
17
9
  await logoutAllService(request.user);
18
10
 
19
11
  return response.success({
@@ -1,19 +1,11 @@
1
- import {
2
- t,
3
- type Request,
4
- type RequestHandler,
5
- type Response,
6
- } from "@warlock.js/core";
1
+ import { t, type Request, type RequestHandler, type Response } from "@warlock.js/core";
7
2
  import { logoutService } from "../services/auth.service";
8
3
 
9
4
  /**
10
5
  * Logout controller
11
6
  * POST /auth/logout
12
7
  */
13
- export const logoutController: RequestHandler = async (
14
- request: Request,
15
- response: Response,
16
- ) => {
8
+ export const logoutController: RequestHandler = async (request: Request, response: Response) => {
17
9
  await logoutService(request.user);
18
10
 
19
11
  return response.success({
@@ -1,17 +1,10 @@
1
- import {
2
- type Request,
3
- type RequestHandler,
4
- type Response,
5
- } from "@warlock.js/core";
1
+ import { type Request, type RequestHandler, type Response } from "@warlock.js/core";
6
2
 
7
3
  /**
8
4
  * Get current user controller
9
5
  * GET /auth/me
10
6
  */
11
- export const meController: RequestHandler = async (
12
- request: Request,
13
- response: Response,
14
- ) => {
7
+ export const meController: RequestHandler = async (request: Request, response: Response) => {
15
8
  return response.success({
16
9
  user: request.user,
17
10
  });
@@ -1,8 +1,4 @@
1
- import {
2
- type Request,
3
- type RequestHandler,
4
- type Response,
5
- } from "@warlock.js/core";
1
+ import { type Request, type RequestHandler, type Response } from "@warlock.js/core";
6
2
  import { v } from "@warlock.js/seal";
7
3
  import { refreshTokensService } from "../services/auth.service";
8
4
 
@@ -1,5 +1,5 @@
1
1
  import { t, type Request, type RequestHandler } from "@warlock.js/core";
2
- import { type ResetPasswordSchema, resetPasswordSchema } from "../schema/reset-password.schema";
2
+ import { resetPasswordSchema, type ResetPasswordSchema } from "../schema/reset-password.schema";
3
3
  import { resetPasswordService } from "../services/reset-password.service";
4
4
 
5
5
  /**
@@ -6,6 +6,4 @@ import { cleanupExpiredOtpsService } from "./services/otp.service";
6
6
  scheduler.newJob("cleanup-expired-otps", cleanupExpiredOtpsService).everyHour();
7
7
 
8
8
  // Cleanup expired refresh tokens every hour
9
- scheduler
10
- .newJob("cleanup-expired-tokens", () => authService.cleanupExpiredTokens())
11
- .everyHour();
9
+ scheduler.newJob("cleanup-expired-tokens", () => authService.cleanupExpiredTokens()).everyHour();
@@ -1,11 +1,4 @@
1
- import {
2
- bool,
3
- integer,
4
- json,
5
- Migration,
6
- string,
7
- timestamp,
8
- } from "@warlock.js/cascade";
1
+ import { bool, integer, json, Migration, string, timestamp } from "@warlock.js/cascade";
9
2
  import { OTP } from "../otp.model";
10
3
 
11
4
  export default Migration.create(
@@ -1,10 +1,9 @@
1
1
  import type { Request, RequestHandler } from "@warlock.js/core";
2
2
  import type { User } from "app/users/models/user";
3
3
 
4
- export type GuardedRequest<RequestPayload = unknown> =
5
- Request<RequestPayload> & {
6
- user: User;
7
- };
4
+ export type GuardedRequest<RequestPayload = unknown> = Request<RequestPayload> & {
5
+ user: User;
6
+ };
8
7
 
9
8
  export type GuardedRequestHandler<RequestPayload = unknown> = RequestHandler<
10
9
  GuardedRequest<RequestPayload>
@@ -44,10 +44,7 @@ export type CreateOTPOptions = {
44
44
  /**
45
45
  * Generate OTP code
46
46
  */
47
- function generateCode(
48
- length: number = 6,
49
- alphanumeric: boolean = false,
50
- ): string {
47
+ function generateCode(length: number = 6, alphanumeric: boolean = false): string {
51
48
  if (alphanumeric) {
52
49
  // Generate alphanumeric code
53
50
  const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
@@ -66,9 +63,7 @@ function generateCode(
66
63
  /**
67
64
  * Create a new OTP
68
65
  */
69
- export async function createOtpService(
70
- options: CreateOTPOptions,
71
- ): Promise<OTP> {
66
+ export async function createOtpService(options: CreateOTPOptions): Promise<OTP> {
72
67
  const {
73
68
  target,
74
69
  channel,
@@ -140,10 +135,7 @@ export async function verifyOtpService(
140
135
  return otp;
141
136
  }
142
137
 
143
- export async function cleanupOtpService(
144
- target: string,
145
- type: OTPType,
146
- ): Promise<void> {
138
+ export async function cleanupOtpService(target: string, type: OTPType): Promise<void> {
147
139
  await OTP.delete({
148
140
  target,
149
141
  type,
@@ -173,11 +165,9 @@ export async function resendOtpService(
173
165
  * Cleanup expired OTPs
174
166
  */
175
167
  export async function cleanupExpiredOtpsService(): Promise<number> {
176
- const expiredOtps = await OTP.query()
177
- .where("expiresAt", "<", new Date())
178
- .get();
168
+ const expiredOtps = await OTP.query().where("expiresAt", "<", new Date()).get();
179
169
 
180
- await Promise.all(expiredOtps.map(otp => otp.destroy()));
170
+ await Promise.all(expiredOtps.map((otp) => otp.destroy()));
181
171
 
182
172
  return expiredOtps.length;
183
173
  }
@@ -13,9 +13,7 @@ type ResetPasswordOptions = {
13
13
  /**
14
14
  * Reset user password using OTP verification
15
15
  */
16
- export async function resetPasswordService(
17
- options: ResetPasswordOptions,
18
- ): Promise<User> {
16
+ export async function resetPasswordService(options: ResetPasswordOptions): Promise<User> {
19
17
  const { email, code, newPassword } = options;
20
18
 
21
19
  // Verify OTP
@@ -1,12 +1,4 @@
1
- import {
2
- bool,
3
- integer,
4
- json,
5
- Migration,
6
- string,
7
- text,
8
- timestamp,
9
- } from "@warlock.js/cascade";
1
+ import { bool, integer, json, Migration, string, text, timestamp } from "@warlock.js/cascade";
10
2
  import { Post } from "../post.model";
11
3
 
12
4
  export default Migration.create(Post, {
@@ -4,8 +4,5 @@ import { createNewPostController } from "./controllers/create-new-post.controlle
4
4
  import { updatePostController } from "./controllers/update-post.controller";
5
5
 
6
6
  guarded(() => {
7
- router
8
- .route("/posts")
9
- .create(createNewPostController)
10
- .update(updatePostController);
7
+ router.route("/posts").create(createNewPostController).update(updatePostController);
11
8
  });
@@ -13,11 +13,7 @@ export function HomePageComponent() {
13
13
  />
14
14
  <link rel="stylesheet" href={publicUrl("home.css")} />
15
15
  <link rel="preconnect" href="https://fonts.googleapis.com" />
16
- <link
17
- rel="preconnect"
18
- href="https://fonts.gstatic.com"
19
- crossOrigin="anonymous"
20
- />
16
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="anonymous" />
21
17
  <link
22
18
  href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&display=swap"
23
19
  rel="stylesheet"
@@ -33,22 +29,13 @@ export function HomePageComponent() {
33
29
  <span>Warlock.js</span>
34
30
  </a>
35
31
  <nav className="warlock-nav">
36
- <a
37
- href="https://warlock.js.org"
38
- target="_blank"
39
- rel="noopener noreferrer">
32
+ <a href="https://warlock.js.org" target="_blank" rel="noopener noreferrer">
40
33
  Docs
41
34
  </a>
42
- <a
43
- href="https://github.com/warlockjs"
44
- target="_blank"
45
- rel="noopener noreferrer">
35
+ <a href="https://github.com/warlockjs" target="_blank" rel="noopener noreferrer">
46
36
  GitHub
47
37
  </a>
48
- <a
49
- href="https://discord.gg/x3W9SN2jvx"
50
- target="_blank"
51
- rel="noopener noreferrer">
38
+ <a href="https://discord.gg/x3W9SN2jvx" target="_blank" rel="noopener noreferrer">
52
39
  Discord
53
40
  </a>
54
41
  </nav>
@@ -61,20 +48,18 @@ export function HomePageComponent() {
61
48
  with Confidence
62
49
  </h1>
63
50
  <p>
64
- A powerful, elegant TypeScript framework designed for developers
65
- who demand excellence. Ship faster, scale better, and enjoy the
66
- journey.
51
+ A powerful, elegant TypeScript framework designed for developers who demand
52
+ excellence. Ship faster, scale better, and enjoy the journey.
67
53
  </p>
68
54
  <div className="warlock-cta">
69
- <a
70
- href="https://warlock.js.org"
71
- className="warlock-btn warlock-btn-primary">
55
+ <a href="https://warlock.js.org" className="warlock-btn warlock-btn-primary">
72
56
  <span>Get Started</span>
73
57
  <span>→</span>
74
58
  </a>
75
59
  <a
76
60
  href="https://github.com/warlockjs"
77
- className="warlock-btn warlock-btn-secondary">
61
+ className="warlock-btn warlock-btn-secondary"
62
+ >
78
63
  <span>View on GitHub</span>
79
64
  </a>
80
65
  </div>
@@ -92,8 +77,8 @@ export function HomePageComponent() {
92
77
  <span className="warlock-feature-icon">🚀</span>
93
78
  <h3>Lightning Fast</h3>
94
79
  <p>
95
- Blazing fast development server with instant hot reload.
96
- Build and deploy optimized production bundles in seconds.
80
+ Blazing fast development server with instant hot reload. Build and deploy
81
+ optimized production bundles in seconds.
97
82
  </p>
98
83
  </div>
99
84
 
@@ -101,9 +86,8 @@ export function HomePageComponent() {
101
86
  <span className="warlock-feature-icon">🎯</span>
102
87
  <h3>Type-Safe</h3>
103
88
  <p>
104
- Built with TypeScript from the ground up. Enjoy full type
105
- safety across your entire application with intelligent
106
- auto-completion.
89
+ Built with TypeScript from the ground up. Enjoy full type safety across your
90
+ entire application with intelligent auto-completion.
107
91
  </p>
108
92
  </div>
109
93
 
@@ -111,8 +95,8 @@ export function HomePageComponent() {
111
95
  <span className="warlock-feature-icon">🏗️</span>
112
96
  <h3>Battle-Tested Architecture</h3>
113
97
  <p>
114
- Proven patterns and best practices baked in. From routing to
115
- database management, we've got you covered.
98
+ Proven patterns and best practices baked in. From routing to database
99
+ management, we've got you covered.
116
100
  </p>
117
101
  </div>
118
102
 
@@ -120,9 +104,8 @@ export function HomePageComponent() {
120
104
  <span className="warlock-feature-icon">🔌</span>
121
105
  <h3>Powerful CLI</h3>
122
106
  <p>
123
- Scaffold components, run migrations, manage seeds, and more
124
- with an intuitive command-line interface that boosts
125
- productivity.
107
+ Scaffold components, run migrations, manage seeds, and more with an intuitive
108
+ command-line interface that boosts productivity.
126
109
  </p>
127
110
  </div>
128
111
 
@@ -130,8 +113,8 @@ export function HomePageComponent() {
130
113
  <span className="warlock-feature-icon">🎨</span>
131
114
  <h3>Flexible & Extensible</h3>
132
115
  <p>
133
- Plugin architecture allows you to extend core functionality.
134
- Build your own tools or use community packages.
116
+ Plugin architecture allows you to extend core functionality. Build your own
117
+ tools or use community packages.
135
118
  </p>
136
119
  </div>
137
120
 
@@ -139,8 +122,8 @@ export function HomePageComponent() {
139
122
  <span className="warlock-feature-icon">🌍</span>
140
123
  <h3>Multi-Tenant Ready</h3>
141
124
  <p>
142
- Built-in support for multi-tenancy. Scale from single to
143
- multi-tenant applications without architectural changes.
125
+ Built-in support for multi-tenancy. Scale from single to multi-tenant
126
+ applications without architectural changes.
144
127
  </p>
145
128
  </div>
146
129
  </div>
@@ -166,8 +149,8 @@ export function HomePageComponent() {
166
149
  </span>
167
150
  </code>
168
151
  <code className="warlock-code-line">
169
- <span className="warlock-code-command">npx</span> warlock
170
- add <span className="warlock-code-flag">react</span>
152
+ <span className="warlock-code-command">npx</span> warlock add{" "}
153
+ <span className="warlock-code-flag">react</span>
171
154
  </code>
172
155
  <code className="warlock-code-line">&nbsp;</code>
173
156
  <code className="warlock-code-line">
@@ -176,27 +159,22 @@ export function HomePageComponent() {
176
159
  </span>
177
160
  </code>
178
161
  <code className="warlock-code-line">
179
- <span className="warlock-code-command">npx</span> warlock
180
- add{" "}
181
- <span className="warlock-code-flag">
182
- mongodb redis test
183
- </span>
162
+ <span className="warlock-code-command">npx</span> warlock add{" "}
163
+ <span className="warlock-code-flag">mongodb redis test</span>
184
164
  </code>
185
165
  <code className="warlock-code-line">&nbsp;</code>
186
166
  <code className="warlock-code-line">
187
- <span className="warlock-code-comment">
188
- # List all available features
189
- </span>
167
+ <span className="warlock-code-comment"># List all available features</span>
190
168
  </code>
191
169
  <code className="warlock-code-line">
192
- <span className="warlock-code-command">npx</span> warlock
193
- add <span className="warlock-code-flag">--list</span>
170
+ <span className="warlock-code-command">npx</span> warlock add{" "}
171
+ <span className="warlock-code-flag">--list</span>
194
172
  </code>
195
173
  <code className="warlock-code-line">&nbsp;</code>
196
174
  <code className="warlock-code-line">
197
175
  <span className="warlock-code-comment">
198
- # ✨ Features: react, mongodb, postgres, mysql, redis, s3,
199
- mail, image, test, swagger, scheduler & more
176
+ # ✨ Features: react, mongodb, postgres, mysql, redis, s3, mail, image, test,
177
+ swagger, scheduler & more
200
178
  </span>
201
179
  </code>
202
180
  </div>
@@ -210,7 +188,8 @@ export function HomePageComponent() {
210
188
  href="https://discord.gg/x3W9SN2jvx"
211
189
  className="warlock-social-link"
212
190
  target="_blank"
213
- rel="noopener noreferrer">
191
+ rel="noopener noreferrer"
192
+ >
214
193
  <span className="warlock-social-icon">💬</span>
215
194
  <span>Join Discord</span>
216
195
  </a>
@@ -218,7 +197,8 @@ export function HomePageComponent() {
218
197
  href="https://github.com/warlockjs"
219
198
  className="warlock-social-link"
220
199
  target="_blank"
221
- rel="noopener noreferrer">
200
+ rel="noopener noreferrer"
201
+ >
222
202
  <span className="warlock-social-icon">⭐</span>
223
203
  <span>Star on GitHub</span>
224
204
  </a>
@@ -226,17 +206,15 @@ export function HomePageComponent() {
226
206
  href="https://warlock.js.org"
227
207
  className="warlock-social-link"
228
208
  target="_blank"
229
- rel="noopener noreferrer">
209
+ rel="noopener noreferrer"
210
+ >
230
211
  <span className="warlock-social-icon">📚</span>
231
212
  <span>Read Docs</span>
232
213
  </a>
233
214
  </div>
234
215
  <p className="warlock-footer-text">
235
216
  Built with ⚡ by the{" "}
236
- <a
237
- href="https://github.com/warlockjs"
238
- target="_blank"
239
- rel="noopener noreferrer">
217
+ <a href="https://github.com/warlockjs" target="_blank" rel="noopener noreferrer">
240
218
  Warlock.js Team
241
219
  </a>
242
220
  {" · "}
@@ -0,0 +1,18 @@
1
+ import { Application, type RequestHandler } from "@warlock.js/core";
2
+
3
+ /**
4
+ * Default welcome route — a dependency-free JSON response.
5
+ *
6
+ * Projects scaffolded with the `react` feature get the richer HTML welcome
7
+ * page (`home-page.controller.tsx` + `HomePageComponent.tsx`) instead; this
8
+ * plain controller is removed at scaffold time when React is selected.
9
+ */
10
+ export const homePageController: RequestHandler = async (_request, response) => {
11
+ return response.success({
12
+ message: "Welcome to Warlock 🧙 — your app is up and running!",
13
+ version: Application.version,
14
+ docs: "https://warlock.js.org",
15
+ });
16
+ };
17
+
18
+ homePageController.description = "Welcome Home Page";
@@ -1,14 +1,7 @@
1
- import {
2
- type Request,
3
- type RequestHandler,
4
- type Response,
5
- } from "@warlock.js/core";
1
+ import { type Request, type RequestHandler, type Response } from "@warlock.js/core";
6
2
  import { HomePageComponent } from "../components/HomePageComponent";
7
3
 
8
- export const homePageController: RequestHandler = async (
9
- _request: Request,
10
- response: Response,
11
- ) => {
4
+ export const homePageController: RequestHandler = async (_request: Request, response: Response) => {
12
5
  return response.render(<HomePageComponent />);
13
6
  };
14
7
 
@@ -1,12 +1,9 @@
1
- import { fileExistsAsync } from "@warlock.js/fs";
2
1
  import { CACHE_FOR } from "@warlock.js/cache";
3
2
  import { Image, type RequestHandler, storage } from "@warlock.js/core";
3
+ import { fileExistsAsync } from "@warlock.js/fs";
4
4
  import { v } from "@warlock.js/seal";
5
5
 
6
- export const fetchUploadedFileController: RequestHandler = async (
7
- request,
8
- response,
9
- ) => {
6
+ export const fetchUploadedFileController: RequestHandler = async (request, response) => {
10
7
  const absolutePath = storage.root(request.input("*"));
11
8
 
12
9
  const { w: width, h: height } = request.validated();
@@ -19,7 +19,7 @@ const saveSubscription = globalEvents.onSaving(async (model, { isInsert }) => {
19
19
  }
20
20
  });
21
21
 
22
- const deleteSubscription = globalEvents.onDeleting(async model => {
22
+ const deleteSubscription = globalEvents.onDeleting(async (model) => {
23
23
  const user = useCurrentUser();
24
24
 
25
25
  if (!user) return;
@@ -9,11 +9,7 @@ export const userSchema = globalColumnsSchema.extend({
9
9
  name: v.string().required(),
10
10
  email: v.email().requiredIfEmpty("id"),
11
11
  image: v.string(),
12
- password: v
13
- .string()
14
- .min(6)
15
- .requiredIfEmpty("id")
16
- .addTransformer(useHashedPassword()),
12
+ password: v.string().min(6).requiredIfEmpty("id").addTransformer(useHashedPassword()),
17
13
  });
18
14
 
19
15
  export type UserSchema = Infer<typeof userSchema>;
@@ -49,15 +45,15 @@ export class User extends Auth<UserSchema> {
49
45
 
50
46
  static {
51
47
  // Local scopes
52
- this.addScope("active", query => {
48
+ this.addScope("active", (query) => {
53
49
  query.where("isActive", true);
54
50
  });
55
51
 
56
- this.addScope("admins", query => {
52
+ this.addScope("admins", (query) => {
57
53
  query.where("role", "admin");
58
54
  });
59
55
 
60
- this.addScope("verified", query => {
56
+ this.addScope("verified", (query) => {
61
57
  query.where("emailVerified", true);
62
58
  });
63
59
 
@@ -4,8 +4,5 @@ import { createNewUserController } from "./controllers/create-new-user.controlle
4
4
  import { listUsersController } from "./controllers/list-users.controller";
5
5
 
6
6
  guarded(() => {
7
- router
8
- .route("/users")
9
- .list(listUsersController)
10
- .post(createNewUserController);
7
+ router.route("/users").list(listUsersController).post(createNewUserController);
11
8
  });
@@ -1,9 +1,6 @@
1
1
  import type { Request, Response } from "@warlock.js/core";
2
2
 
3
- export default async function loginSocial(
4
- request: Request,
5
- response: Response,
6
- ) {
3
+ export default async function loginSocial(request: Request, response: Response) {
7
4
  const user = request.user;
8
5
 
9
6
  const auth = await user.generateAccessToken();
@@ -20,8 +20,7 @@ const globalPrefix = () => {
20
20
  return cachePrefix;
21
21
  }
22
22
 
23
- const domain =
24
- request.originDomain || request.header("domain") || request.input("domain");
23
+ const domain = request.originDomain || request.header("domain") || request.input("domain");
25
24
 
26
25
  if (!domain) return cachePrefix;
27
26
 
@@ -5,10 +5,7 @@ import type {
5
5
  } from "@warlock.js/cascade";
6
6
  import { env } from "@warlock.js/core";
7
7
 
8
- const databaseConfigurations: ConnectionOptions<
9
- MongoDriverOptions,
10
- MongoClientOptions
11
- > = {
8
+ const databaseConfigurations: ConnectionOptions<MongoDriverOptions, MongoClientOptions> = {
12
9
  driver: env("DB_DRIVER", "mongodb"),
13
10
  name: "default",
14
11
  database: env("DB_NAME"),