create-warlock 5.22.1 → 5.23.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,25 @@ All notable changes to `create-warlock` are documented in this file.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). `@warlock.js/*` packages are released in lockstep — every package shares the same version number, so a version below may list only the changes that affected this package.
6
6
 
7
+ ## 5.23.1 - 2026-09-26
8
+
9
+ ### Changed
10
+
11
+ - Lockstep patch release; package APIs are unchanged.
12
+
13
+ ## 5.23.0 - 2026-09-25
14
+
15
+ ### Fixed
16
+
17
+ - Scaffolding inside an existing pnpm workspace no longer writes a nested `pnpm-workspace.yaml`; a standalone app still gets one.
18
+ - Postgres apps get their own database defaults in `.env` and `.env.example` (MongoDB keeps `DB_AUTH` and port 27017).
19
+ - The `gen.*` scripts use the registered `warlock generate.<x>` commands.
20
+ - `minimumReleaseAgeExclude` covers `@warlock.js/*` by pattern, so a freshly published family installs without listing versions or unused packages.
21
+
22
+ ### Changed
23
+
24
+ - Template schemas and models drop redundant `.required()` and non-load-bearing `satisfies`, matching the framework skills.
25
+
7
26
  ## 5.22.1 - 2026-09-25
8
27
 
9
28
  ### Changed
@@ -23,6 +23,16 @@ import path from "path";
23
23
  function installEnvironment() {
24
24
  return { NODE_ENV: "development" };
25
25
  }
26
+ /** True when any ancestor of `targetPath` (starting at its parent) has a pnpm-workspace.yaml. */
27
+ function isInsidePnpmWorkspace(targetPath) {
28
+ let dir = path.dirname(path.resolve(targetPath));
29
+ while (true) {
30
+ if (existsSync(path.join(dir, "pnpm-workspace.yaml"))) return true;
31
+ const parent = path.dirname(dir);
32
+ if (parent === dir) return false;
33
+ dir = parent;
34
+ }
35
+ }
26
36
  var App = class {
27
37
  constructor(app) {
28
38
  this.app = app;
@@ -37,6 +47,7 @@ var App = class {
37
47
  copyDirectory(template(templateName), this.path);
38
48
  if (fileExists(this.path + "/.env.example")) copyFile(this.path + "/.env.example", this.path + "/.env");
39
49
  renameFile(this.path + "/_.gitignore", this.path + "/.gitignore");
50
+ if (isInsidePnpmWorkspace(this.path)) rmSync(path.join(this.path, "pnpm-workspace.yaml"), { force: true });
40
51
  return this;
41
52
  }
42
53
  init() {
@@ -113,11 +124,16 @@ var App = class {
113
124
  packageJson.dependencies[driver.package] = driver.packageVersion;
114
125
  putJsonFile(packageJsonPath, packageJson);
115
126
  }
116
- let envContent = getFile(this.path + "/.env");
117
- envContent = envContent.replace(/DB_PORT=\d+/, `DB_PORT=${driver.defaultPort}`);
118
- if (envContent.includes("DB_DRIVER=")) envContent = envContent.replace(/DB_DRIVER=\w*/, `DB_DRIVER=${driver.value}`);
119
- else envContent = envContent.replace(/DB_PORT=\d+/, `DB_PORT=${driver.defaultPort}\nDB_DRIVER=${driver.value}`);
120
- putFile(this.path + "/.env", envContent);
127
+ for (const fileName of [".env", ".env.example"]) {
128
+ const envPath = this.path + "/" + fileName;
129
+ if (!fileExists(envPath)) continue;
130
+ let envContent = getFile(envPath);
131
+ envContent = envContent.replace(/DB_PORT=\d+/, `DB_PORT=${driver.defaultPort}`);
132
+ if (envContent.includes("DB_DRIVER=")) envContent = envContent.replace(/DB_DRIVER=\w*/, `DB_DRIVER=${driver.value}`);
133
+ else envContent = envContent.replace(/DB_PORT=\d+/, `DB_PORT=${driver.defaultPort}\nDB_DRIVER=${driver.value}`);
134
+ if (driver.value !== "mongodb") envContent = envContent.replace(/^DB_AUTH=.*\r?\n?/m, "");
135
+ putFile(envPath, envContent);
136
+ }
121
137
  return this;
122
138
  }
123
139
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"app.mjs","names":[],"sources":["../../../../../../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 { existsSync, rmSync, unlinkSync } from \"node:fs\";\r\nimport path from \"path\";\r\nimport { Application, AppOptions } from \"../commands/create-new-app/types\";\r\nimport { getDatabaseDriver } from \"../features/database-drivers\";\r\nimport { executeCommand, runCommand } from \"./exec\";\r\nimport { insertImportInSortedPosition } from \"./insert-import\";\r\nimport { getPackageManager } from \"./package-manager\";\r\nimport { packageRoot, Template, template } from \"./paths\";\r\nimport {\r\n mergePnpmBuildApprovals,\r\n requiredPnpmBuildApprovals,\r\n} from \"./pnpm-build-approvals\";\r\nimport { fallbackRange } from \"./warlock-versions\";\r\n\r\n/**\r\n * Environment for every install the scaffolder spawns.\r\n *\r\n * A shell that exports `NODE_ENV=production` makes the package managers skip\r\n * every devDependency — and still exit 0. The scaffolded project needs its dev\r\n * toolchain (typescript, vitest, eslint) to be usable at all, so the install\r\n * child is pinned to `development` regardless of the ambient environment. A\r\n * silently incomplete install that reports success is the exact failure mode\r\n * this file exists to prevent.\r\n */\r\nexport function installEnvironment(): NodeJS.ProcessEnv {\r\n return { NODE_ENV: \"development\" };\r\n}\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 env: installEnvironment(),\r\n });\r\n }\r\n\r\n /** Record native build approvals before pnpm installs selected feature dependencies. */\r\n public approveSelectedFeatureBuilds(features: readonly string[]) {\r\n if (getPackageManager() !== \"pnpm\") return this;\r\n const packages = requiredPnpmBuildApprovals(features);\r\n if (packages.length === 0) return this;\r\n const workspacePath = path.join(this.path, \"pnpm-workspace.yaml\");\r\n if (!fileExists(workspacePath)) return this;\r\n const current = getFile(workspacePath) as string;\r\n putFile(workspacePath, mergePnpmBuildApprovals(current, packages));\r\n return this;\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 } =\r\n await import(\"./project-builder-helpers\");\r\n return await initializeGitRepository(this.path);\r\n }\r\n\r\n /**\r\n * Write the project's `package.json`: the project name, the chosen package\r\n * manager, and the version of every `@warlock.js/*` dependency.\r\n *\r\n * `versions` comes from {@link resolveWarlockVersions} — versions the\r\n * registry has confirmed exist. It is optional because the fluent chain is\r\n * synchronous; without it every sibling gets the caret range floored to the\r\n * scaffolder's major, which is always satisfiable by a published release.\r\n *\r\n * What it must NEVER do again is stamp the scaffolder's own version blind:\r\n * the release tooling bumps that version on every build, published or not,\r\n * so an unverified pin resolves to nothing and the install dies with ETARGET.\r\n */\r\n public updatePackageJson(versions: Record<string, string> = {}) {\r\n const packageManager = getPackageManager();\r\n\r\n const pkg = this.package.replace(\"name\", this.name.replaceAll(\"/\", \"-\"));\r\n\r\n const content: any = pkg.content;\r\n\r\n // Substitute the chosen package manager ONLY into the fields the template\r\n // writes it into literally — the `serve` script — by path, never with a\r\n // blanket substring replace over the serialized JSON. A raw\r\n // `replaceAll(\"yarn\", pm)` rewrites every occurrence of the substring\r\n // \"yarn\" anywhere in the document, so a project named `my-yarn-app`, or any\r\n // dependency/path containing \"yarn\", is silently corrupted (`--pm=npm`\r\n // turned `my-yarn-app` into `my-npm-app`). Field-scoped rewriting is also\r\n // order-independent: the name lives in `content.name` and this token lives\r\n // in `scripts.serve`, a disjoint field that cannot collide with the name\r\n // substitution above regardless of which runs first.\r\n if (typeof content.scripts?.serve === \"string\") {\r\n content.scripts.serve = content.scripts.serve.replaceAll(\r\n \"yarn\",\r\n packageManager,\r\n );\r\n }\r\n\r\n const warlockVersion: string = (\r\n getJsonFile(packageRoot(\"package.json\")) as { version: string }\r\n ).version;\r\n const defaultRange = fallbackRange(warlockVersion);\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] = versions[dependency] ?? defaultRange;\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 * Configure the chosen database driver: wire `DB_DRIVER` / `DB_PORT` into\r\n * `.env`, AND pin the driver's npm package (`mongodb` / `pg`) into the\r\n * project's `package.json` dependencies.\r\n *\r\n * The dependency is written HERE — before the base `yarn install` — so the\r\n * driver is pulled deterministically by the very first install. We do NOT\r\n * rely on the post-copy `warlock add <driver> --no-install` + separate\r\n * batched install, which can be skipped or fail and leave the driver\r\n * missing (the \"mongodb package is not installed\" runtime error).\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 // Pin the driver package into dependencies (idempotent — never downgrade).\r\n const packageJsonPath = path.resolve(this.path, \"package.json\");\r\n const packageJson = getJsonFile(packageJsonPath) as {\r\n dependencies?: Record<string, string>;\r\n };\r\n packageJson.dependencies = packageJson.dependencies ?? {};\r\n if (!packageJson.dependencies[driver.package]) {\r\n packageJson.dependencies[driver.package] = driver.packageVersion;\r\n putJsonFile(packageJsonPath, packageJson);\r\n }\r\n\r\n let envContent = getFile(this.path + \"/.env\") as string;\r\n\r\n envContent = envContent.replace(\r\n /DB_PORT=\\d+/,\r\n `DB_PORT=${driver.defaultPort}`,\r\n );\r\n\r\n if (envContent.includes(\"DB_DRIVER=\")) {\r\n envContent = envContent.replace(\r\n /DB_DRIVER=\\w*/,\r\n `DB_DRIVER=${driver.value}`,\r\n );\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 * Write the correct `src/config/database.ts` for the chosen driver and remove\r\n * every per-driver variant so the scaffold ships exactly one config file.\r\n *\r\n * The template ships a Mongo-shaped `database.ts` (the default) plus a\r\n * `database.postgres.ts` variant. Mongo keeps the default in place; Postgres\r\n * overwrites `database.ts` with the Postgres-shaped variant (typed\r\n * `ConnectionOptions`, `driver: \"postgres\"`, port 5432, no Mongo-only keys —\r\n * no `authSource`, `driverOptions`, `replicaSet`, `randomIncrement` /\r\n * `initialId`). Either way the `.postgres` variant file is deleted afterwards\r\n * so no leftover config ships in the generated project.\r\n *\r\n * For Postgres it also corrects the working `.env`: the Mongo template ships\r\n * `DB_AUTH=admin`, an `authSource` value Postgres ignores and its config no\r\n * longer reads, so the line is dropped to keep `.env` and the config in sync.\r\n * The port is rewritten by {@link configureDatabaseEnv} (driver defaultPort),\r\n * which runs alongside this in the same branch.\r\n */\r\n public configureDatabaseConfig(driverValue: string) {\r\n const configDir = path.resolve(this.path, \"src/config\");\r\n const databasePath = path.resolve(configDir, \"database.ts\");\r\n const postgresVariantPath = path.resolve(configDir, \"database.postgres.ts\");\r\n\r\n if (driverValue === \"postgres\" && fileExists(postgresVariantPath)) {\r\n putFile(databasePath, getFile(postgresVariantPath) as string);\r\n\r\n const envPath = this.path + \"/.env\";\r\n\r\n if (fileExists(envPath)) {\r\n const envContent = getFile(envPath) as string;\r\n // Drop the Mongo-only DB_AUTH line entirely (its whole line, so no blank\r\n // gap is left behind). Postgres has no authSource concept.\r\n putFile(envPath, envContent.replace(/^DB_AUTH=.*\\r?\\n?/m, \"\"));\r\n }\r\n }\r\n\r\n // Whatever the driver, no per-driver variant may survive into the scaffold.\r\n if (fileExists(postgresVariantPath)) {\r\n unlinkSync(postgresVariantPath);\r\n }\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Remove the database layer for a \"no database\" scaffold.\r\n *\r\n * Deletes `src/config/database.ts` (and a `.tsx` / per-driver variant if\r\n * present) from the freshly-copied template. The framework's database\r\n * connector is config-gated on that file — with it gone,\r\n * `config.get(\"database\")` is undefined and the connector no-ops, so the app\r\n * boots with no database wired and no driver package pulled. The `DB_*` lines\r\n * in `.env` are left in place (harmless: no config reads them) as a ready\r\n * template for adding a database back later.\r\n */\r\n public removeDatabaseConfig() {\r\n const configDir = path.resolve(this.path, \"src/config\");\r\n\r\n for (const fileName of [\r\n \"database.ts\",\r\n \"database.tsx\",\r\n \"database.postgres.ts\",\r\n ]) {\r\n const filePath = path.resolve(configDir, fileName);\r\n\r\n if (fileExists(filePath)) {\r\n unlinkSync(filePath);\r\n }\r\n }\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Configure the web starter only when the web feature owns `/`.\r\n *\r\n * A dependency-free HTTP response is retained without web; otherwise the\r\n * SSR page loader owns the route.\r\n * Exactly one survives the scaffold: web projects keep the SSR page and API\r\n * example; every other project keeps the dependency-free JSON controller.\r\n */\r\n public configureWebStarter(useWeb: boolean) {\r\n const remove = (relativePath: string) => {\r\n const target = path.resolve(this.path, relativePath);\r\n if (existsSync(target)) rmSync(target, { recursive: true, force: true });\r\n };\r\n\r\n if (!useWeb) {\r\n for (const entry of [\r\n \"src/web\",\r\n \"src/app/contact\",\r\n \"src/app/locale\",\r\n \"src/shared/contact.schema.ts\",\r\n \"src/shared/locale.schema.ts\",\r\n \"src/shared/locales.ts\",\r\n \"postcss.config.mjs\",\r\n \"skills/web-code-standards\",\r\n ]) {\r\n remove(entry);\r\n }\r\n\r\n const agentsPath = path.resolve(this.path, \"AGENTS.md\");\r\n const agents = getFile(agentsPath) as string;\r\n putFile(\r\n agentsPath,\r\n agents.replace(/^.*\\*\\*web-code-standards\\*\\*.*\\r?\\n/m, \"\"),\r\n );\r\n\r\n const tsconfigPath = path.resolve(this.path, \"tsconfig.json\");\r\n const tsconfig = getFile(tsconfigPath) as string;\r\n putFile(\r\n tsconfigPath,\r\n tsconfig\r\n .replace(' \"web/*\": [\"./src/web/*\"],\\n', \"\")\r\n .replace(' \"@shared/*\": [\"./src/shared/*\"]\\n', \"\"),\r\n );\r\n\r\n return this;\r\n }\r\n\r\n // The SSR page owns `/`; its loader still uses src/app/home/services.\r\n remove(\"src/app/home/controllers/home-page.controller.ts\");\r\n remove(\"src/app/home/routes.ts\");\r\n\r\n const packageJsonPath = path.resolve(this.path, \"package.json\");\r\n const packageJson = getJsonFile(packageJsonPath) as {\r\n dependencies?: Record<string, string>;\r\n devDependencies?: Record<string, string>;\r\n };\r\n packageJson.dependencies = {\r\n ...packageJson.dependencies,\r\n \"@mongez/http\": \"^3.5.0\",\r\n \"@mongez/react-form\": \"^4.0.0\",\r\n };\r\n packageJson.devDependencies = {\r\n ...packageJson.devDependencies,\r\n \"@tailwindcss/postcss\": \"^4.1.16\",\r\n tailwindcss: \"^4.1.16\",\r\n };\r\n putJsonFile(packageJsonPath, packageJson);\r\n\r\n const configPath = path.resolve(this.path, \"warlock.config.ts\");\r\n const config = getFile(configPath) as string;\r\n if (!config.includes('\"@warlock.js/web/connector\"')) {\r\n // Inserted in sorted position, not prepended. The generated app formats\r\n // with `prettier-plugin-organize-imports`, so an import pushed to the top\r\n // of a file that already imports `@warlock.js/auth` and `@warlock.js/core`\r\n // is out of order the moment it is written — and the developer meets it as\r\n // a `prettier/prettier` error on their first `warlock dev`, in a file they\r\n // did not write.\r\n const withConnector = insertImportInSortedPosition(\r\n config,\r\n 'import { webConnector } from \"@warlock.js/web/connector\";',\r\n \"@warlock.js/web/connector\",\r\n );\r\n\r\n putFile(\r\n configPath,\r\n withConnector.replace(\r\n \"export default defineConfig({\",\r\n \"export default defineConfig({\\n connectors: [webConnector()],\",\r\n ),\r\n );\r\n }\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Record non-default `agent-kit` targets into the generated `package.json`'s\r\n * `agentKit.targets` block.\r\n *\r\n * The template's own `postinstall` (`agent-kit init && agent-kit sync`)\r\n * already targets `claude` with no flags, so the single-target default case\r\n * needs no template edit at all — this only writes when the selection is\r\n * anything else, keeping the scaffold diff-free for the common path.\r\n */\r\n public configureAgentKitTargets(agents: string[]) {\r\n const isDefault = agents.length === 1 && agents[0] === \"claude\";\r\n if (isDefault) return this;\r\n\r\n const packageJsonPath = path.resolve(this.path, \"package.json\");\r\n if (!fileExists(packageJsonPath)) return this;\r\n\r\n const packageJson = getJsonFile(packageJsonPath) as {\r\n agentKit?: { targets?: string[] };\r\n };\r\n packageJson.agentKit = { ...packageJson.agentKit, targets: agents };\r\n putJsonFile(packageJsonPath, packageJson);\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 * Force a single copy of `vite` when the project ends up with BOTH `vite`\r\n * (written by the `web` feature) and `vitest` (always in the template).\r\n *\r\n * `vitest` depends on vite through a wide range of its own\r\n * (`^6 || ^7 || ^8`), which resolves to a DIFFERENT major than the range the\r\n * feature map pins. Yarn 1 then has to nest the second copy under\r\n * `vitest/node_modules` and dies on its own linker invariant:\r\n *\r\n * error Invariant Violation: could not find a copy of vite to link in\r\n * <project>/node_modules/vitest/node_modules\r\n *\r\n * It aborts BEFORE `node_modules/.bin` is written, so `warlock` is never\r\n * linked and the very first `yarn dev` fails with \"'warlock' is not\r\n * recognized\". npm and pnpm nest happily and never hit this — for them the\r\n * `overrides` twin below is one copy instead of two, not a crash fix.\r\n *\r\n * The pin is READ BACK from whatever range the project's own package.json\r\n * declares, never re-stated as a literal here: the feature map in\r\n * `@warlock.js/core` owns the version, and a second copy of it would drift\r\n * the moment that one moves.\r\n *\r\n * Returns whether a pin was written, so the caller (and its spec) can assert\r\n * it instead of assuming it.\r\n */\r\n public pinViteResolution() {\r\n const packageJsonPath = path.resolve(this.path, \"package.json\");\r\n\r\n if (!fileExists(packageJsonPath)) return false;\r\n\r\n const packageJson = getJsonFile(packageJsonPath) as {\r\n dependencies?: Record<string, string>;\r\n devDependencies?: Record<string, string>;\r\n resolutions?: Record<string, string>;\r\n overrides?: Record<string, string>;\r\n };\r\n\r\n const declaredRange = (name: string) =>\r\n packageJson.dependencies?.[name] ?? packageJson.devDependencies?.[name];\r\n\r\n const viteRange = declaredRange(\"vite\");\r\n\r\n // No vite, or no vitest to conflict with it — nothing to pin. Writing a\r\n // resolution for a package the project does not depend on would only\r\n // freeze a transitive tree nobody asked us to freeze.\r\n if (!viteRange || !declaredRange(\"vitest\")) return false;\r\n\r\n // yarn 1 / yarn berry read `resolutions`; npm 8+ and pnpm read `overrides`.\r\n packageJson.resolutions = { ...packageJson.resolutions, vite: viteRange };\r\n packageJson.overrides = { ...packageJson.overrides, vite: viteRange };\r\n\r\n putJsonFile(packageJsonPath, packageJson);\r\n\r\n return true;\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":["../../../../../../create-warlock/src/helpers/app.ts"],"sourcesContent":["import {\n copyDirectory,\n copyFile,\n fileExists,\n getFile,\n getJsonFile,\n putFile,\n putJsonFile,\n renameFile,\n} from \"@warlock.js/fs\";\nimport { existsSync, rmSync, unlinkSync } from \"node:fs\";\nimport path from \"path\";\nimport { Application, AppOptions } from \"../commands/create-new-app/types\";\nimport { getDatabaseDriver } from \"../features/database-drivers\";\nimport { executeCommand, runCommand } from \"./exec\";\nimport { insertImportInSortedPosition } from \"./insert-import\";\nimport { getPackageManager } from \"./package-manager\";\nimport { packageRoot, Template, template } from \"./paths\";\nimport {\n mergePnpmBuildApprovals,\n requiredPnpmBuildApprovals,\n} from \"./pnpm-build-approvals\";\nimport { fallbackRange } from \"./warlock-versions\";\n\n/**\n * Environment for every install the scaffolder spawns.\n *\n * A shell that exports `NODE_ENV=production` makes the package managers skip\n * every devDependency — and still exit 0. The scaffolded project needs its dev\n * toolchain (typescript, vitest, eslint) to be usable at all, so the install\n * child is pinned to `development` regardless of the ambient environment. A\n * silently incomplete install that reports success is the exact failure mode\n * this file exists to prevent.\n */\nexport function installEnvironment(): NodeJS.ProcessEnv {\n return { NODE_ENV: \"development\" };\n}\n\n/** True when any ancestor of `targetPath` (starting at its parent) has a pnpm-workspace.yaml. */\nexport function isInsidePnpmWorkspace(targetPath: string): boolean {\n let dir = path.dirname(path.resolve(targetPath));\n\n while (true) {\n if (existsSync(path.join(dir, \"pnpm-workspace.yaml\"))) return true;\n const parent = path.dirname(dir);\n if (parent === dir) return false;\n dir = parent;\n }\n}\n\nexport class App {\n /**\n * Resolved files\n */\n protected files: Record<string, FileManager> = {};\n\n /**\n * Resolved JSON files\n */\n protected jsonFiles: Record<string, JsonFileManager> = {};\n\n public isInstalled = false;\n\n public constructor(protected app: Application) {}\n\n public get options(): AppOptions {\n return this.app.options;\n }\n\n public use(templateName: Template) {\n copyDirectory(template(templateName), this.path);\n\n if (fileExists(this.path + \"/.env.example\")) {\n copyFile(this.path + \"/.env.example\", this.path + \"/.env\");\n }\n\n renameFile(this.path + \"/_.gitignore\", this.path + \"/.gitignore\");\n\n // Inside an existing pnpm workspace the parent owns the policy; a per-app\n // workspace file would nest a second workspace root.\n if (isInsidePnpmWorkspace(this.path)) {\n rmSync(path.join(this.path, \"pnpm-workspace.yaml\"), { force: true });\n }\n\n return this;\n }\n\n public init() {\n return this;\n }\n\n public terminate() {\n // No longer using outro, using showSuccessScreen instead\n }\n\n public install() {\n return runCommand(getPackageManager(), [\"install\"], this.path, {\n env: installEnvironment(),\n });\n }\n\n /** Record native build approvals before pnpm installs selected feature dependencies. */\n public approveSelectedFeatureBuilds(features: readonly string[]) {\n if (getPackageManager() !== \"pnpm\") return this;\n const packages = requiredPnpmBuildApprovals(features);\n if (packages.length === 0) return this;\n const workspacePath = path.join(this.path, \"pnpm-workspace.yaml\");\n if (!fileExists(workspacePath)) return this;\n const current = getFile(workspacePath) as string;\n putFile(workspacePath, mergePnpmBuildApprovals(current, packages));\n return this;\n }\n\n public async exec(command: string) {\n const [commandName, ...optionsList] = command.split(\" \");\n return await executeCommand(commandName, optionsList, this.path);\n }\n\n public async git() {\n const { initializeGitRepository } =\n await import(\"./project-builder-helpers\");\n return await initializeGitRepository(this.path);\n }\n\n /**\n * Write the project's `package.json`: the project name, the chosen package\n * manager, and the version of every `@warlock.js/*` dependency.\n *\n * `versions` comes from {@link resolveWarlockVersions} — versions the\n * registry has confirmed exist. It is optional because the fluent chain is\n * synchronous; without it every sibling gets the caret range floored to the\n * scaffolder's major, which is always satisfiable by a published release.\n *\n * What it must NEVER do again is stamp the scaffolder's own version blind:\n * the release tooling bumps that version on every build, published or not,\n * so an unverified pin resolves to nothing and the install dies with ETARGET.\n */\n public updatePackageJson(versions: Record<string, string> = {}) {\n const packageManager = getPackageManager();\n\n const pkg = this.package.replace(\"name\", this.name.replaceAll(\"/\", \"-\"));\n\n const content: any = pkg.content;\n\n // Substitute the chosen package manager ONLY into the fields the template\n // writes it into literally — the `serve` script — by path, never with a\n // blanket substring replace over the serialized JSON. A raw\n // `replaceAll(\"yarn\", pm)` rewrites every occurrence of the substring\n // \"yarn\" anywhere in the document, so a project named `my-yarn-app`, or any\n // dependency/path containing \"yarn\", is silently corrupted (`--pm=npm`\n // turned `my-yarn-app` into `my-npm-app`). Field-scoped rewriting is also\n // order-independent: the name lives in `content.name` and this token lives\n // in `scripts.serve`, a disjoint field that cannot collide with the name\n // substitution above regardless of which runs first.\n if (typeof content.scripts?.serve === \"string\") {\n content.scripts.serve = content.scripts.serve.replaceAll(\n \"yarn\",\n packageManager,\n );\n }\n\n const warlockVersion: string = (\n getJsonFile(packageRoot(\"package.json\")) as { version: string }\n ).version;\n const defaultRange = fallbackRange(warlockVersion);\n\n for (const field of [\"dependencies\", \"devDependencies\"] as const) {\n const deps = content[field] as Record<string, string> | undefined;\n if (!deps) continue;\n\n for (const dependency of Object.keys(deps)) {\n if (dependency.startsWith(\"@warlock.js/\")) {\n deps[dependency] = versions[dependency] ?? defaultRange;\n }\n }\n }\n\n pkg.save();\n\n return this;\n }\n\n /**\n * Configure the chosen database driver: wire `DB_DRIVER` / `DB_PORT` into\n * `.env`, AND pin the driver's npm package (`mongodb` / `pg`) into the\n * project's `package.json` dependencies.\n *\n * The dependency is written HERE — before the base `yarn install` — so the\n * driver is pulled deterministically by the very first install. We do NOT\n * rely on the post-copy `warlock add <driver> --no-install` + separate\n * batched install, which can be skipped or fail and leave the driver\n * missing (the \"mongodb package is not installed\" runtime error).\n */\n public configureDatabaseEnv(driverValue: string) {\n const driver = getDatabaseDriver(driverValue);\n\n if (!driver) return this;\n\n // Pin the driver package into dependencies (idempotent — never downgrade).\n const packageJsonPath = path.resolve(this.path, \"package.json\");\n const packageJson = getJsonFile(packageJsonPath) as {\n dependencies?: Record<string, string>;\n };\n packageJson.dependencies = packageJson.dependencies ?? {};\n if (!packageJson.dependencies[driver.package]) {\n packageJson.dependencies[driver.package] = driver.packageVersion;\n putJsonFile(packageJsonPath, packageJson);\n }\n\n // Both files carry the driver defaults: `.env` is what runs, `.env.example`\n // is what the next developer copies.\n for (const fileName of [\".env\", \".env.example\"]) {\n const envPath = this.path + \"/\" + fileName;\n if (!fileExists(envPath)) continue;\n\n let envContent = getFile(envPath) as string;\n\n envContent = envContent.replace(\n /DB_PORT=\\d+/,\n `DB_PORT=${driver.defaultPort}`,\n );\n\n if (envContent.includes(\"DB_DRIVER=\")) {\n envContent = envContent.replace(\n /DB_DRIVER=\\w*/,\n `DB_DRIVER=${driver.value}`,\n );\n } else {\n envContent = envContent.replace(\n /DB_PORT=\\d+/,\n `DB_PORT=${driver.defaultPort}\\nDB_DRIVER=${driver.value}`,\n );\n }\n\n // DB_AUTH is Mongo's authSource; no other driver reads it.\n if (driver.value !== \"mongodb\") {\n envContent = envContent.replace(/^DB_AUTH=.*\\r?\\n?/m, \"\");\n }\n\n putFile(envPath, envContent);\n }\n\n return this;\n }\n\n /**\n * Write the correct `src/config/database.ts` for the chosen driver and remove\n * every per-driver variant so the scaffold ships exactly one config file.\n *\n * The template ships a Mongo-shaped `database.ts` (the default) plus a\n * `database.postgres.ts` variant. Mongo keeps the default in place; Postgres\n * overwrites `database.ts` with the Postgres-shaped variant (typed\n * `ConnectionOptions`, `driver: \"postgres\"`, port 5432, no Mongo-only keys —\n * no `authSource`, `driverOptions`, `replicaSet`, `randomIncrement` /\n * `initialId`). Either way the `.postgres` variant file is deleted afterwards\n * so no leftover config ships in the generated project.\n *\n * For Postgres it also corrects the working `.env`: the Mongo template ships\n * `DB_AUTH=admin`, an `authSource` value Postgres ignores and its config no\n * longer reads, so the line is dropped to keep `.env` and the config in sync.\n * The port is rewritten by {@link configureDatabaseEnv} (driver defaultPort),\n * which runs alongside this in the same branch.\n */\n public configureDatabaseConfig(driverValue: string) {\n const configDir = path.resolve(this.path, \"src/config\");\n const databasePath = path.resolve(configDir, \"database.ts\");\n const postgresVariantPath = path.resolve(configDir, \"database.postgres.ts\");\n\n if (driverValue === \"postgres\" && fileExists(postgresVariantPath)) {\n putFile(databasePath, getFile(postgresVariantPath) as string);\n\n const envPath = this.path + \"/.env\";\n\n if (fileExists(envPath)) {\n const envContent = getFile(envPath) as string;\n // Drop the Mongo-only DB_AUTH line entirely (its whole line, so no blank\n // gap is left behind). Postgres has no authSource concept.\n putFile(envPath, envContent.replace(/^DB_AUTH=.*\\r?\\n?/m, \"\"));\n }\n }\n\n // Whatever the driver, no per-driver variant may survive into the scaffold.\n if (fileExists(postgresVariantPath)) {\n unlinkSync(postgresVariantPath);\n }\n\n return this;\n }\n\n /**\n * Remove the database layer for a \"no database\" scaffold.\n *\n * Deletes `src/config/database.ts` (and a `.tsx` / per-driver variant if\n * present) from the freshly-copied template. The framework's database\n * connector is config-gated on that file — with it gone,\n * `config.get(\"database\")` is undefined and the connector no-ops, so the app\n * boots with no database wired and no driver package pulled. The `DB_*` lines\n * in `.env` are left in place (harmless: no config reads them) as a ready\n * template for adding a database back later.\n */\n public removeDatabaseConfig() {\n const configDir = path.resolve(this.path, \"src/config\");\n\n for (const fileName of [\n \"database.ts\",\n \"database.tsx\",\n \"database.postgres.ts\",\n ]) {\n const filePath = path.resolve(configDir, fileName);\n\n if (fileExists(filePath)) {\n unlinkSync(filePath);\n }\n }\n\n return this;\n }\n\n /**\n * Configure the web starter only when the web feature owns `/`.\n *\n * A dependency-free HTTP response is retained without web; otherwise the\n * SSR page loader owns the route.\n * Exactly one survives the scaffold: web projects keep the SSR page and API\n * example; every other project keeps the dependency-free JSON controller.\n */\n public configureWebStarter(useWeb: boolean) {\n const remove = (relativePath: string) => {\n const target = path.resolve(this.path, relativePath);\n if (existsSync(target)) rmSync(target, { recursive: true, force: true });\n };\n\n if (!useWeb) {\n for (const entry of [\n \"src/web\",\n \"src/app/contact\",\n \"src/app/locale\",\n \"src/shared/contact.schema.ts\",\n \"src/shared/locale.schema.ts\",\n \"src/shared/locales.ts\",\n \"postcss.config.mjs\",\n \"skills/web-code-standards\",\n ]) {\n remove(entry);\n }\n\n const agentsPath = path.resolve(this.path, \"AGENTS.md\");\n const agents = getFile(agentsPath) as string;\n putFile(\n agentsPath,\n agents.replace(/^.*\\*\\*web-code-standards\\*\\*.*\\r?\\n/m, \"\"),\n );\n\n const tsconfigPath = path.resolve(this.path, \"tsconfig.json\");\n const tsconfig = getFile(tsconfigPath) as string;\n putFile(\n tsconfigPath,\n tsconfig\n .replace(' \"web/*\": [\"./src/web/*\"],\\n', \"\")\n .replace(' \"@shared/*\": [\"./src/shared/*\"]\\n', \"\"),\n );\n\n return this;\n }\n\n // The SSR page owns `/`; its loader still uses src/app/home/services.\n remove(\"src/app/home/controllers/home-page.controller.ts\");\n remove(\"src/app/home/routes.ts\");\n\n const packageJsonPath = path.resolve(this.path, \"package.json\");\n const packageJson = getJsonFile(packageJsonPath) as {\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n };\n packageJson.dependencies = {\n ...packageJson.dependencies,\n \"@mongez/http\": \"^3.5.0\",\n \"@mongez/react-form\": \"^4.0.0\",\n };\n packageJson.devDependencies = {\n ...packageJson.devDependencies,\n \"@tailwindcss/postcss\": \"^4.1.16\",\n tailwindcss: \"^4.1.16\",\n };\n putJsonFile(packageJsonPath, packageJson);\n\n const configPath = path.resolve(this.path, \"warlock.config.ts\");\n const config = getFile(configPath) as string;\n if (!config.includes('\"@warlock.js/web/connector\"')) {\n // Inserted in sorted position, not prepended. The generated app formats\n // with `prettier-plugin-organize-imports`, so an import pushed to the top\n // of a file that already imports `@warlock.js/auth` and `@warlock.js/core`\n // is out of order the moment it is written — and the developer meets it as\n // a `prettier/prettier` error on their first `warlock dev`, in a file they\n // did not write.\n const withConnector = insertImportInSortedPosition(\n config,\n 'import { webConnector } from \"@warlock.js/web/connector\";',\n \"@warlock.js/web/connector\",\n );\n\n putFile(\n configPath,\n withConnector.replace(\n \"export default defineConfig({\",\n \"export default defineConfig({\\n connectors: [webConnector()],\",\n ),\n );\n }\n\n return this;\n }\n\n /**\n * Record non-default `agent-kit` targets into the generated `package.json`'s\n * `agentKit.targets` block.\n *\n * The template's own `postinstall` (`agent-kit init && agent-kit sync`)\n * already targets `claude` with no flags, so the single-target default case\n * needs no template edit at all — this only writes when the selection is\n * anything else, keeping the scaffold diff-free for the common path.\n */\n public configureAgentKitTargets(agents: string[]) {\n const isDefault = agents.length === 1 && agents[0] === \"claude\";\n if (isDefault) return this;\n\n const packageJsonPath = path.resolve(this.path, \"package.json\");\n if (!fileExists(packageJsonPath)) return this;\n\n const packageJson = getJsonFile(packageJsonPath) as {\n agentKit?: { targets?: string[] };\n };\n packageJson.agentKit = { ...packageJson.agentKit, targets: agents };\n putJsonFile(packageJsonPath, packageJson);\n\n return this;\n }\n\n /**\n * Install the selected optional features by delegating to the project's own\n * `warlock add`. `--no-install` records every dependency in package.json and\n * ejects configs / scripts / setup hooks WITHOUT installing — the caller runs\n * one batched install afterwards. Versions come from core's feature map, so\n * the scaffolder never duplicates them.\n *\n * `--no-install` is passed LAST on purpose: the CLI parser treats the\n * positional after a bare flag as that flag's value, so it must follow the\n * feature list, not precede it.\n */\n public async installFeatures(features: string[]) {\n if (features.length === 0) return true;\n\n return this.exec(`npx warlock add ${features.join(\" \")} --no-install`);\n }\n\n /**\n * Force a single copy of `vite` when the project ends up with BOTH `vite`\n * (written by the `web` feature) and `vitest` (always in the template).\n *\n * `vitest` depends on vite through a wide range of its own\n * (`^6 || ^7 || ^8`), which resolves to a DIFFERENT major than the range the\n * feature map pins. Yarn 1 then has to nest the second copy under\n * `vitest/node_modules` and dies on its own linker invariant:\n *\n * error Invariant Violation: could not find a copy of vite to link in\n * <project>/node_modules/vitest/node_modules\n *\n * It aborts BEFORE `node_modules/.bin` is written, so `warlock` is never\n * linked and the very first `yarn dev` fails with \"'warlock' is not\n * recognized\". npm and pnpm nest happily and never hit this — for them the\n * `overrides` twin below is one copy instead of two, not a crash fix.\n *\n * The pin is READ BACK from whatever range the project's own package.json\n * declares, never re-stated as a literal here: the feature map in\n * `@warlock.js/core` owns the version, and a second copy of it would drift\n * the moment that one moves.\n *\n * Returns whether a pin was written, so the caller (and its spec) can assert\n * it instead of assuming it.\n */\n public pinViteResolution() {\n const packageJsonPath = path.resolve(this.path, \"package.json\");\n\n if (!fileExists(packageJsonPath)) return false;\n\n const packageJson = getJsonFile(packageJsonPath) as {\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n resolutions?: Record<string, string>;\n overrides?: Record<string, string>;\n };\n\n const declaredRange = (name: string) =>\n packageJson.dependencies?.[name] ?? packageJson.devDependencies?.[name];\n\n const viteRange = declaredRange(\"vite\");\n\n // No vite, or no vitest to conflict with it — nothing to pin. Writing a\n // resolution for a package the project does not depend on would only\n // freeze a transitive tree nobody asked us to freeze.\n if (!viteRange || !declaredRange(\"vitest\")) return false;\n\n // yarn 1 / yarn berry read `resolutions`; npm 8+ and pnpm read `overrides`.\n packageJson.resolutions = { ...packageJson.resolutions, vite: viteRange };\n packageJson.overrides = { ...packageJson.overrides, vite: viteRange };\n\n putJsonFile(packageJsonPath, packageJson);\n\n return true;\n }\n\n /**\n * Get package json file\n */\n public get package() {\n return this.json(\"package.json\");\n }\n\n public updateDotEnv() {\n this.file(\".env\").replaceAll(\"appName\", this.name).save();\n\n return this;\n }\n\n /**\n * Get env file to update\n */\n public get env() {\n return this.file(\".env\");\n }\n\n public get name() {\n return this.app.appName;\n }\n\n public get path() {\n return this.app.appPath;\n }\n\n public file(relativePath: string) {\n const fullPath = path.resolve(this.path, relativePath);\n\n if (!this.files[fullPath]) {\n this.files[fullPath] = file(fullPath);\n }\n\n return this.files[fullPath];\n }\n\n public json(relativePath: string): JsonFileManager {\n const fullPath = path.resolve(this.path, relativePath);\n\n if (!this.jsonFiles[fullPath]) {\n this.jsonFiles[fullPath] = jsonFile(fullPath);\n }\n\n return this.jsonFiles[fullPath];\n }\n}\n\nexport function app(app: Application) {\n return new App(app);\n}\n\nexport class FileManager {\n public content!: string;\n public constructor(protected filePath: string) {\n this.parseContent();\n }\n\n protected parseContent() {\n this.content = getFile(this.filePath) as string;\n }\n\n public replace(search: string, replace: string) {\n this.content = this.content.replace(search, replace);\n\n return this;\n }\n\n public replaceAll(search: string, replace: string) {\n this.content = this.content.replaceAll(search, replace);\n\n return this;\n }\n\n public save() {\n putFile(this.filePath, this.content);\n }\n}\n\nexport class JsonFileManager extends FileManager {\n protected parseContent() {\n this.content = getJsonFile(this.filePath);\n }\n\n public save() {\n putJsonFile(this.filePath, this.content);\n }\n\n public has(key: string) {\n return this.content[key] !== undefined;\n }\n\n public replace(key: string, value: any) {\n this.content[key] = value;\n\n return this;\n }\n\n public replaceAll(key: string, value: any) {\n const contentAsString = JSON.stringify(this.content);\n\n this.content = JSON.parse(contentAsString.replaceAll(key, value));\n\n return this;\n }\n}\n\nexport function file(path: string) {\n return new FileManager(path);\n}\n\nexport function jsonFile(path: string) {\n return new JsonFileManager(path);\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -12,7 +12,7 @@
12
12
  "dependencies": {
13
13
  "@clack/prompts": "^0.7.0",
14
14
  "@mongez/copper": "^2.1.3",
15
- "@warlock.js/fs": "5.22.1",
15
+ "@warlock.js/fs": "5.23.1",
16
16
  "@mongez/reinforcements": "^4.0.2",
17
17
  "cross-spawn": "^7.0.3",
18
18
  "rimraf": "^6.0.1",
@@ -21,7 +21,7 @@
21
21
  "engines": {
22
22
  "node": ">=22.13"
23
23
  },
24
- "version": "5.22.1",
24
+ "version": "5.23.1",
25
25
  "type": "module",
26
26
  "main": "./esm/index.mjs",
27
27
  "module": "./esm/index.mjs",
@@ -13,9 +13,9 @@
13
13
  "migrate.list": "warlock migrate --list",
14
14
  "jwt": "warlock jwt.generate",
15
15
  "gen": "warlock generate.module",
16
- "gen.s": "warlock.generate.service",
17
- "gen.m": "warlock.generate.model",
18
- "gen.c": "warlock.generate.controller",
16
+ "gen.s": "warlock generate.service",
17
+ "gen.m": "warlock generate.model",
18
+ "gen.c": "warlock generate.controller",
19
19
  "gen.mig": "warlock generate.migration",
20
20
  "gen.md": "warlock generate.model",
21
21
  "gen.r": "warlock generate.repository",
@@ -1,2 +1,7 @@
1
1
  allowBuilds:
2
2
  esbuild: true
3
+
4
+ # Our own release family is published minutes before installs run; exclude the
5
+ # scope by pattern, never by pinned version or a package the app does not use.
6
+ minimumReleaseAgeExclude:
7
+ - '@warlock.js/*'
@@ -20,6 +20,6 @@ forgotPasswordController.description = "Request password reset";
20
20
 
21
21
  forgotPasswordController.validation = {
22
22
  schema: v.object({
23
- email: v.email().required(),
23
+ email: v.email(),
24
24
  }),
25
25
  };
@@ -21,6 +21,6 @@ refreshTokenController.description = "Refresh Access Token";
21
21
 
22
22
  refreshTokenController.validation = {
23
23
  schema: v.object({
24
- refreshToken: v.string().required(),
24
+ refreshToken: v.string(),
25
25
  }),
26
26
  };
@@ -2,13 +2,13 @@ import { Model, RegisterModel } from "@warlock.js/cascade";
2
2
  import { v, type Infer } from "@warlock.js/seal";
3
3
 
4
4
  const otpSchema = v.object({
5
- code: v.string().required(),
6
- type: v.string().required(),
7
- target: v.string().required(),
8
- channel: v.string().required(),
9
- userId: v.number().required(),
10
- userType: v.string().required(),
11
- expiresAt: v.date().required(),
5
+ code: v.string(),
6
+ type: v.string(),
7
+ target: v.string(),
8
+ channel: v.string(),
9
+ userId: v.number(),
10
+ userType: v.string(),
11
+ expiresAt: v.date(),
12
12
  usedAt: v.date().optional(),
13
13
  attempts: v.number().default(0),
14
14
  maxAttempts: v.number().default(5),
@@ -1,8 +1,8 @@
1
1
  import { v, type Infer } from "@warlock.js/seal";
2
2
 
3
3
  export const loginSchema = v.object({
4
- email: v.email().required(),
5
- password: v.string().required(),
4
+ email: v.email(),
5
+ password: v.string(),
6
6
  });
7
7
 
8
8
  export type LoginSchema = Infer<typeof loginSchema>;
@@ -1,9 +1,9 @@
1
1
  import { v, type Infer } from "@warlock.js/seal";
2
2
 
3
3
  export const resetPasswordSchema = v.object({
4
- email: v.string().email().required(),
5
- code: v.string().required(),
6
- newPassword: v.string().min(8).required(),
4
+ email: v.string().email(),
5
+ code: v.string(),
6
+ newPassword: v.string().min(8),
7
7
  });
8
8
 
9
9
  export type ResetPasswordSchema = Infer<typeof resetPasswordSchema>;
@@ -4,10 +4,10 @@ import { type Infer, v } from "@warlock.js/seal";
4
4
  import { PostResource } from "app/posts/resources/post.resource";
5
5
 
6
6
  export const postSchema = v.object({
7
- title: v.string().required(),
8
- description: v.string().required(),
7
+ title: v.string(),
8
+ description: v.string(),
9
9
  slug: v.computed(useComputedSlug()),
10
- image: v.string().required(),
10
+ image: v.string(),
11
11
  });
12
12
 
13
13
  type PostSchema = Infer<typeof postSchema>;
@@ -1,7 +1,7 @@
1
1
  import { type Infer, v } from "@warlock.js/seal";
2
2
 
3
3
  export const createPostSchema = v.object({
4
- title: v.string().required(),
4
+ title: v.string(),
5
5
  description: v.string(),
6
6
  image: v.file().image().maxSize({ size: 2, unit: "MB" }).saveTo("posts"),
7
7
  });
@@ -1,7 +1,7 @@
1
1
  import { type Infer, v } from "@warlock.js/seal";
2
2
 
3
3
  export const updatePostSchema = v.object({
4
- title: v.string().required(),
4
+ title: v.string(),
5
5
  description: v.string(),
6
6
  image: v.file().image().maxSize({ size: 2, unit: "MB" }).saveTo("posts"),
7
7
  });
@@ -5,7 +5,7 @@ import { type Infer, v } from "@warlock.js/seal";
5
5
  import { UserResource } from "app/users/resources/user.resource";
6
6
 
7
7
  export const userSchema = v.object({
8
- name: v.string().required(),
8
+ name: v.string(),
9
9
  email: v.email().requiredIfEmpty("id"),
10
10
  // Required at the controller layer (`create-user.schema.ts`) on
11
11
  // registration, but not here: a freshly-registered user's record is
@@ -2,10 +2,10 @@ import { type Infer, v } from "@warlock.js/seal";
2
2
  import { User } from "../models/user";
3
3
 
4
4
  export const createUserSchema = v.object({
5
- name: v.string().required(),
6
- email: v.email().required().unique(User),
5
+ name: v.string(),
6
+ email: v.email().unique(User),
7
7
  password: v.string().min(6),
8
- image: v.file().image().required().maxSize({ unit: "MB", size: 1.5 }),
8
+ image: v.file().image().maxSize({ unit: "MB", size: 1.5 }),
9
9
  });
10
10
 
11
11
  export type CreateUserSchema = Infer<typeof createUserSchema>;
@@ -2,10 +2,7 @@ import { v, type Infer } from "@warlock.js/seal";
2
2
  import { localeCodes } from "./locales";
3
3
 
4
4
  export const localeSchema = v.object({
5
- locale: v
6
- .string()
7
- .in([...localeCodes])
8
- .required(),
5
+ locale: v.string().in([...localeCodes]),
9
6
  });
10
7
 
11
8
  export type LocaleSchema = Infer.Output<typeof localeSchema>;
@@ -1,8 +1,8 @@
1
1
  import type { PageConfig } from "@warlock.js/web";
2
2
 
3
- export const config = {
3
+ export const config: PageConfig = {
4
4
  metadata: {
5
5
  title: "Page not found",
6
6
  robots: "noindex",
7
7
  },
8
- } satisfies PageConfig;
8
+ };
@@ -1,4 +1,4 @@
1
1
  import type { RootConfig } from "@warlock.js/web";
2
2
 
3
3
  /** Enable React's development checks for the hydrated page/layout tree. */
4
- export const config = { strictMode: true } satisfies RootConfig;
4
+ export const config: RootConfig = { strictMode: true };