create-warlock 4.6.0 → 4.7.0
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 +11 -0
- package/esm/commands/create-new-app/index.mjs +39 -24
- package/esm/commands/create-new-app/index.mjs.map +1 -1
- package/esm/commands/create-warlock-app/index.mjs +8 -3
- package/esm/commands/create-warlock-app/index.mjs.map +1 -1
- package/esm/features/database-drivers.mjs +35 -4
- package/esm/features/database-drivers.mjs.map +1 -1
- package/esm/helpers/app.mjs +18 -0
- package/esm/helpers/app.mjs.map +1 -1
- package/esm/index.mjs +4 -0
- package/esm/index.mjs.map +1 -1
- package/llms-full.txt +17 -11
- package/llms.txt +1 -1
- package/package.json +2 -2
- package/skills/create-a-warlock-project/SKILL.md +17 -11
- package/templates/warlock/src/app/auth/models/otp/migrations/22-12-2025_10-30-20.otp-migration.ts +1 -5
- package/templates/warlock/src/app/posts/models/post/migrations/09-01-2026_02-07-51-post.migration.ts +1 -5
- package/templates/warlock/src/app/posts/models/post/post.model.ts +1 -2
- package/templates/warlock/src/app/posts/resources/post.resource.ts +0 -3
- package/templates/warlock/src/app/users/models/user/migrations/11-12-2025_23-58-03-user.migration.ts +1 -5
- package/templates/warlock/src/app/users/models/user/user.model.ts +1 -10
- package/templates/warlock/src/app/users/resources/user.resource.ts +0 -1
- package/templates/warlock/src/app/shared/utils/global-columns-schema.ts +0 -8
- package/templates/warlock/src/app/users/events/inject-created-by-user.into-model.event.ts +0 -32
- package/templates/warlock/src/app/users/events/sync.ts +0 -5
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,17 @@ 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
|
+
## 4.7.0
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Non-interactive scaffolding — `create-warlock <name> --yes` (with `--db`, `--pm`, `--features`, `--ai`, `--git`, `--jwt`) scaffolds the entire app in a single command, no prompts
|
|
12
|
+
- `--db=none` / `--no-db` and a **None** option in the database prompt — scaffold with no database: the driver, its package, and `src/config/database.ts` are all skipped
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- Starter models drop the baked-in `globalColumnsSchema` audit columns (`createdBy` / `updatedBy` / `deletedBy` / `isActive`) — global columns are left to the developer
|
|
17
|
+
|
|
7
18
|
## 4.2.11
|
|
8
19
|
|
|
9
20
|
### Changed
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getDatabaseDriver, getDatabaseDriverOptions } from "../../features/database-drivers.mjs";
|
|
1
|
+
import { getDatabaseDriver, getDatabaseDriverOptions, isNoDatabase } from "../../features/database-drivers.mjs";
|
|
2
2
|
import { getAiPackageOptions, getAiProviderOptions, getAllFeatureKeys, getDefaultFeatureKeys, getFeatureOptions } from "../../features/features-map.mjs";
|
|
3
3
|
import { detectPackageManagers, getPackageManager, getPreferredPackageManager, getSystemPackageManagers, setPackageManager } from "../../helpers/package-manager.mjs";
|
|
4
4
|
import { packageRoot } from "../../helpers/paths.mjs";
|
|
@@ -103,9 +103,38 @@ async function createNewApp(cli = {}) {
|
|
|
103
103
|
}));
|
|
104
104
|
}
|
|
105
105
|
/**
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
106
|
+
* Resolve the full set of `AppOptions` from parsed CLI flags — applying the
|
|
107
|
+
* non-interactive defaults, validating the database driver and feature/provider
|
|
108
|
+
* keys, and handling the `none` (no database) selection.
|
|
109
|
+
*
|
|
110
|
+
* Pure and side-effect free (no prompts, no `process.exit`) so the flag → app
|
|
111
|
+
* mapping is unit-testable. Throws on an unknown driver or feature key so the
|
|
112
|
+
* caller can fail fast before any file is written.
|
|
113
|
+
*/
|
|
114
|
+
function resolveNonInteractiveOptions(cli) {
|
|
115
|
+
const databaseDriver = cli.db ?? "mongodb";
|
|
116
|
+
const noDatabase = isNoDatabase(databaseDriver);
|
|
117
|
+
const driver = noDatabase ? void 0 : getDatabaseDriver(databaseDriver);
|
|
118
|
+
if (!noDatabase && !driver) throw new Error(`Unknown database driver "${databaseDriver}"`);
|
|
119
|
+
const features = cli.features ?? [];
|
|
120
|
+
const aiProviders = cli.ai ?? [];
|
|
121
|
+
const allowedKeys = getAllFeatureKeys();
|
|
122
|
+
const invalidKeys = [...features, ...aiProviders].filter((key) => !allowedKeys.includes(key));
|
|
123
|
+
if (invalidKeys.length > 0) throw new Error(`Unknown feature(s): ${invalidKeys.join(",")}`);
|
|
124
|
+
return {
|
|
125
|
+
databaseDriver,
|
|
126
|
+
databasePort: driver?.defaultPort ?? 27017,
|
|
127
|
+
features,
|
|
128
|
+
aiProviders,
|
|
129
|
+
useGit: cli.git ?? false,
|
|
130
|
+
useJWT: cli.jwt ?? false
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Non-interactive scaffold path for `--yes`. Builds the app from flags with
|
|
135
|
+
* sensible defaults and skips every prompt — a single command scaffolds the
|
|
136
|
+
* entire app. Validation lives in `resolveNonInteractiveOptions`; a typo fails
|
|
137
|
+
* fast here instead of mid-install.
|
|
109
138
|
*/
|
|
110
139
|
async function createNonInteractive(cli) {
|
|
111
140
|
const appName = (cli.name ?? "").trim();
|
|
@@ -117,18 +146,11 @@ async function createNonInteractive(cli) {
|
|
|
117
146
|
if (!appPath) return;
|
|
118
147
|
await detectPackageManagers();
|
|
119
148
|
setPackageManager(cli.pm ?? getPreferredPackageManager());
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
}
|
|
126
|
-
const features = cli.features ?? [];
|
|
127
|
-
const aiProviders = cli.ai ?? [];
|
|
128
|
-
const allowedKeys = getAllFeatureKeys();
|
|
129
|
-
const invalidKeys = [...features, ...aiProviders].filter((key) => !allowedKeys.includes(key));
|
|
130
|
-
if (invalidKeys.length > 0) {
|
|
131
|
-
cancel(`Unknown feature(s): ${invalidKeys.join(",")}`);
|
|
149
|
+
let options;
|
|
150
|
+
try {
|
|
151
|
+
options = resolveNonInteractiveOptions(cli);
|
|
152
|
+
} catch (error) {
|
|
153
|
+
cancel(error.message);
|
|
132
154
|
process.exit(1);
|
|
133
155
|
}
|
|
134
156
|
await createWarlockApp(new App({
|
|
@@ -136,14 +158,7 @@ async function createNonInteractive(cli) {
|
|
|
136
158
|
appType: "warlock",
|
|
137
159
|
appPath,
|
|
138
160
|
pkgManager: getPackageManager(),
|
|
139
|
-
options
|
|
140
|
-
databaseDriver,
|
|
141
|
-
databasePort: driver.defaultPort,
|
|
142
|
-
features,
|
|
143
|
-
aiProviders,
|
|
144
|
-
useGit: cli.git ?? false,
|
|
145
|
-
useJWT: cli.jwt ?? false
|
|
146
|
-
}
|
|
161
|
+
options
|
|
147
162
|
}));
|
|
148
163
|
}
|
|
149
164
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../../../../../../@warlock.js/create-warlock/src/commands/create-new-app/index.ts"],"sourcesContent":["import {\r\n cancel,\r\n confirm,\r\n isCancel,\r\n multiselect,\r\n select,\r\n text,\r\n} from \"@clack/prompts\";\r\nimport { colors } from \"@mongez/copper\";\r\nimport { getJsonFile } from \"@warlock.js/fs\";\r\nimport {\r\n getDatabaseDriver,\r\n getDatabaseDriverOptions,\r\n} from \"../../features/database-drivers\";\r\nimport {\r\n getAiPackageOptions,\r\n getAiProviderOptions,\r\n getAllFeatureKeys,\r\n getDefaultFeatureKeys,\r\n getFeatureOptions,\r\n} from \"../../features/features-map\";\r\nimport { App } from \"../../helpers/app\";\r\nimport {\r\n detectPackageManagers,\r\n getPackageManager,\r\n getPreferredPackageManager,\r\n getSystemPackageManagers,\r\n setPackageManager,\r\n} from \"../../helpers/package-manager\";\r\nimport { packageRoot } from \"../../helpers/paths\";\r\nimport { showIntroBanner } from \"../../ui/banner\";\r\nimport { createWarlockApp } from \"../create-warlock-app\";\r\nimport getAppPath from \"./get-app-path\";\r\nimport { App as AppType, CliFlags } from \"./types\";\r\n\r\nexport default async function createNewApp(cli: CliFlags = {}) {\r\n // Start detecting package managers in the background to avoid delay later\r\n const pmDetectionPromise = detectPackageManagers();\r\n\r\n // Get version from package.json\r\n const packageJson: any = getJsonFile(packageRoot(\"package.json\"));\r\n const createWarlockVersion = packageJson.version;\r\n\r\n // Show the intro banner\r\n showIntroBanner(createWarlockVersion);\r\n\r\n console.log(colors.cyan(\"Let's create something magical! \\n\"));\r\n\r\n // Validate Node.js version (minimum v20)\r\n const [major] = process.versions.node.split(\".\").map(Number);\r\n if (major < 20) {\r\n cancel(\"Node.js version must be at least 20.0.0\");\r\n process.exit(0);\r\n }\r\n\r\n // Non-interactive path: build everything from flags and skip the prompts.\r\n if (cli.yes) {\r\n await createNonInteractive(cli);\r\n return;\r\n }\r\n\r\n // Step 1: Project name\r\n const appName = await text({\r\n message: \"What shall we call your project?\",\r\n placeholder: \"my-warlock-app\",\r\n });\r\n\r\n if (isCancel(appName) || !appName.trim()) {\r\n cancel(\"A project name is required to continue\");\r\n process.exit(0);\r\n }\r\n\r\n const appPath = getAppPath(appName);\r\n if (!appPath) return;\r\n\r\n // Step 2: Package Manager selection\r\n await pmDetectionPromise; // Ensure detection is complete\r\n\r\n const packageManager = await select({\r\n message: \"Which package manager do you want to use?\",\r\n options: getSystemPackageManagers().map(pm => ({\r\n value: pm,\r\n label: pm,\r\n })),\r\n initialValue: getPreferredPackageManager(),\r\n });\r\n\r\n if (isCancel(packageManager)) {\r\n cancel(\"Package manager selection cancelled\");\r\n process.exit(0);\r\n }\r\n\r\n setPackageManager(packageManager as string);\r\n\r\n // Step 3: Database driver selection\r\n const databaseDriver = await select({\r\n message: \"Choose your database driver\",\r\n options: getDatabaseDriverOptions(),\r\n });\r\n\r\n if (isCancel(databaseDriver)) {\r\n cancel(\"Database selection cancelled\");\r\n process.exit(0);\r\n }\r\n\r\n const selectedDriver = getDatabaseDriver(databaseDriver as string);\r\n\r\n // Step 4: Features selection\r\n const selectedFeatures = await multiselect({\r\n message: \"Select optional features to include\",\r\n options: getFeatureOptions(),\r\n initialValues: getDefaultFeatureKeys(),\r\n required: false,\r\n });\r\n\r\n if (isCancel(selectedFeatures)) {\r\n cancel(\"Feature selection cancelled\");\r\n process.exit(0);\r\n }\r\n\r\n // Step 4b: AI providers + capability packages — selecting any pulls\r\n // @warlock.js/ai automatically. The result still flows through the\r\n // `selectedAiProviders` → `aiProviders` pipeline, which now also carries the\r\n // capability packages (ai-tools / ai-panoptic / ai-workspace).\r\n const selectedAiProviders = await multiselect({\r\n message:\r\n \"Add AI packages? Providers + capabilities — the core AI package is included automatically\",\r\n options: [...getAiProviderOptions(), ...getAiPackageOptions()],\r\n required: false,\r\n });\r\n\r\n if (isCancel(selectedAiProviders)) {\r\n cancel(\"AI provider selection cancelled\");\r\n process.exit(0);\r\n }\r\n\r\n // Step 5: Git initialization\r\n const useGit =\r\n (await confirm({\r\n message: \"Initialize a Git repository?\",\r\n })) === true;\r\n\r\n if (isCancel(useGit)) {\r\n cancel(\"Setup cancelled\");\r\n process.exit(0);\r\n }\r\n\r\n // Step 6: JWT secret generation\r\n const useJWT =\r\n (await confirm({\r\n message: \"Generate JWT secret keys?\",\r\n })) === true;\r\n\r\n if (isCancel(useJWT)) {\r\n cancel(\"Setup cancelled\");\r\n process.exit(0);\r\n }\r\n\r\n // Build app details\r\n const appDetails: Required<AppType> = {\r\n appName: appName,\r\n appType: \"warlock\",\r\n appPath: appPath,\r\n pkgManager: getPackageManager(),\r\n options: {\r\n databaseDriver: databaseDriver as string,\r\n databasePort: selectedDriver?.defaultPort || 27017,\r\n features: selectedFeatures as string[],\r\n aiProviders: selectedAiProviders as string[],\r\n useGit,\r\n useJWT,\r\n },\r\n };\r\n\r\n // Create the app\r\n await createWarlockApp(new App(appDetails));\r\n}\r\n\r\n/**\r\n * Non-interactive scaffold path for`--yes`. Builds the app from flags with\r\n * sensible defaults and skips every prompt. Validates the database driver and\r\n * feature/provider keys up front so a typo fails fast instead of mid-install.\r\n */\r\nasync function createNonInteractive(cli: CliFlags) {\r\n const appName = (cli.name ?? \"\").trim();\r\n\r\n if (!appName) {\r\n cancel(\"--yes requires a project name (first argument or --name=<name>)\");\r\n process.exit(1);\r\n }\r\n\r\n const appPath = getAppPath(appName);\r\n\r\n if (!appPath) return;\r\n\r\n await detectPackageManagers();\r\n\r\n const packageManager = cli.pm ?? getPreferredPackageManager();\r\n setPackageManager(packageManager);\r\n\r\n const databaseDriver = cli.db ?? \"mongodb\";\r\n const driver = getDatabaseDriver(databaseDriver);\r\n\r\n if (!driver) {\r\n cancel(`Unknown database driver \"${databaseDriver}\"`);\r\n process.exit(1);\r\n }\r\n\r\n const features = cli.features ?? [];\r\n const aiProviders = cli.ai ?? [];\r\n\r\n const allowedKeys = getAllFeatureKeys();\r\n const invalidKeys = [...features, ...aiProviders].filter(\r\n key => !allowedKeys.includes(key),\r\n );\r\n\r\n if (invalidKeys.length > 0) {\r\n cancel(`Unknown feature(s): ${invalidKeys.join(\",\")}`);\r\n process.exit(1);\r\n }\r\n\r\n const appDetails: Required<AppType> = {\r\n appName,\r\n appType: \"warlock\",\r\n appPath,\r\n pkgManager: getPackageManager(),\r\n options: {\r\n databaseDriver,\r\n databasePort: driver.defaultPort,\r\n features,\r\n aiProviders,\r\n useGit: cli.git ?? false,\r\n useJWT: cli.jwt ?? false,\r\n },\r\n };\r\n\r\n await createWarlockApp(new App(appDetails));\r\n}\r\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../../../../../../@warlock.js/create-warlock/src/commands/create-new-app/index.ts"],"sourcesContent":["import {\r\n cancel,\r\n confirm,\r\n isCancel,\r\n multiselect,\r\n select,\r\n text,\r\n} from \"@clack/prompts\";\r\nimport { colors } from \"@mongez/copper\";\r\nimport { getJsonFile } from \"@warlock.js/fs\";\r\nimport {\r\n getDatabaseDriver,\r\n getDatabaseDriverOptions,\r\n isNoDatabase,\r\n} from \"../../features/database-drivers\";\r\nimport {\r\n getAiPackageOptions,\r\n getAiProviderOptions,\r\n getAllFeatureKeys,\r\n getDefaultFeatureKeys,\r\n getFeatureOptions,\r\n} from \"../../features/features-map\";\r\nimport { App } from \"../../helpers/app\";\r\nimport {\r\n detectPackageManagers,\r\n getPackageManager,\r\n getPreferredPackageManager,\r\n getSystemPackageManagers,\r\n setPackageManager,\r\n} from \"../../helpers/package-manager\";\r\nimport { packageRoot } from \"../../helpers/paths\";\r\nimport { showIntroBanner } from \"../../ui/banner\";\r\nimport { createWarlockApp } from \"../create-warlock-app\";\r\nimport getAppPath from \"./get-app-path\";\r\nimport { AppOptions, App as AppType, CliFlags } from \"./types\";\r\n\r\nexport default async function createNewApp(cli: CliFlags = {}) {\r\n // Start detecting package managers in the background to avoid delay later\r\n const pmDetectionPromise = detectPackageManagers();\r\n\r\n // Get version from package.json\r\n const packageJson: any = getJsonFile(packageRoot(\"package.json\"));\r\n const createWarlockVersion = packageJson.version;\r\n\r\n // Show the intro banner\r\n showIntroBanner(createWarlockVersion);\r\n\r\n console.log(colors.cyan(\"Let's create something magical! \\n\"));\r\n\r\n // Validate Node.js version (minimum v20)\r\n const [major] = process.versions.node.split(\".\").map(Number);\r\n if (major < 20) {\r\n cancel(\"Node.js version must be at least 20.0.0\");\r\n process.exit(0);\r\n }\r\n\r\n // Non-interactive path: build everything from flags and skip the prompts.\r\n if (cli.yes) {\r\n await createNonInteractive(cli);\r\n return;\r\n }\r\n\r\n // Step 1: Project name\r\n const appName = await text({\r\n message: \"What shall we call your project?\",\r\n placeholder: \"my-warlock-app\",\r\n });\r\n\r\n if (isCancel(appName) || !appName.trim()) {\r\n cancel(\"A project name is required to continue\");\r\n process.exit(0);\r\n }\r\n\r\n const appPath = getAppPath(appName);\r\n if (!appPath) return;\r\n\r\n // Step 2: Package Manager selection\r\n await pmDetectionPromise; // Ensure detection is complete\r\n\r\n const packageManager = await select({\r\n message: \"Which package manager do you want to use?\",\r\n options: getSystemPackageManagers().map(pm => ({\r\n value: pm,\r\n label: pm,\r\n })),\r\n initialValue: getPreferredPackageManager(),\r\n });\r\n\r\n if (isCancel(packageManager)) {\r\n cancel(\"Package manager selection cancelled\");\r\n process.exit(0);\r\n }\r\n\r\n setPackageManager(packageManager as string);\r\n\r\n // Step 3: Database driver selection\r\n const databaseDriver = await select({\r\n message: \"Choose your database driver\",\r\n options: getDatabaseDriverOptions(),\r\n });\r\n\r\n if (isCancel(databaseDriver)) {\r\n cancel(\"Database selection cancelled\");\r\n process.exit(0);\r\n }\r\n\r\n const selectedDriver = getDatabaseDriver(databaseDriver as string);\r\n\r\n // Step 4: Features selection\r\n const selectedFeatures = await multiselect({\r\n message: \"Select optional features to include\",\r\n options: getFeatureOptions(),\r\n initialValues: getDefaultFeatureKeys(),\r\n required: false,\r\n });\r\n\r\n if (isCancel(selectedFeatures)) {\r\n cancel(\"Feature selection cancelled\");\r\n process.exit(0);\r\n }\r\n\r\n // Step 4b: AI providers + capability packages — selecting any pulls\r\n // @warlock.js/ai automatically. The result still flows through the\r\n // `selectedAiProviders` → `aiProviders` pipeline, which now also carries the\r\n // capability packages (ai-tools / ai-panoptic / ai-workspace).\r\n const selectedAiProviders = await multiselect({\r\n message:\r\n \"Add AI packages? Providers + capabilities — the core AI package is included automatically\",\r\n options: [...getAiProviderOptions(), ...getAiPackageOptions()],\r\n required: false,\r\n });\r\n\r\n if (isCancel(selectedAiProviders)) {\r\n cancel(\"AI provider selection cancelled\");\r\n process.exit(0);\r\n }\r\n\r\n // Step 5: Git initialization\r\n const useGit =\r\n (await confirm({\r\n message: \"Initialize a Git repository?\",\r\n })) === true;\r\n\r\n if (isCancel(useGit)) {\r\n cancel(\"Setup cancelled\");\r\n process.exit(0);\r\n }\r\n\r\n // Step 6: JWT secret generation\r\n const useJWT =\r\n (await confirm({\r\n message: \"Generate JWT secret keys?\",\r\n })) === true;\r\n\r\n if (isCancel(useJWT)) {\r\n cancel(\"Setup cancelled\");\r\n process.exit(0);\r\n }\r\n\r\n // Build app details\r\n const appDetails: Required<AppType> = {\r\n appName: appName,\r\n appType: \"warlock\",\r\n appPath: appPath,\r\n pkgManager: getPackageManager(),\r\n options: {\r\n databaseDriver: databaseDriver as string,\r\n databasePort: selectedDriver?.defaultPort || 27017,\r\n features: selectedFeatures as string[],\r\n aiProviders: selectedAiProviders as string[],\r\n useGit,\r\n useJWT,\r\n },\r\n };\r\n\r\n // Create the app\r\n await createWarlockApp(new App(appDetails));\r\n}\r\n\r\n/**\r\n * Resolve the full set of `AppOptions` from parsed CLI flags — applying the\r\n * non-interactive defaults, validating the database driver and feature/provider\r\n * keys, and handling the `none` (no database) selection.\r\n *\r\n * Pure and side-effect free (no prompts, no `process.exit`) so the flag → app\r\n * mapping is unit-testable. Throws on an unknown driver or feature key so the\r\n * caller can fail fast before any file is written.\r\n */\r\nexport function resolveNonInteractiveOptions(cli: CliFlags): AppOptions {\r\n const databaseDriver = cli.db ?? \"mongodb\";\r\n const noDatabase = isNoDatabase(databaseDriver);\r\n const driver = noDatabase ? undefined : getDatabaseDriver(databaseDriver);\r\n\r\n if (!noDatabase && !driver) {\r\n throw new Error(`Unknown database driver \"${databaseDriver}\"`);\r\n }\r\n\r\n const features = cli.features ?? [];\r\n const aiProviders = cli.ai ?? [];\r\n\r\n const allowedKeys = getAllFeatureKeys();\r\n const invalidKeys = [...features, ...aiProviders].filter(\r\n key => !allowedKeys.includes(key),\r\n );\r\n\r\n if (invalidKeys.length > 0) {\r\n throw new Error(`Unknown feature(s): ${invalidKeys.join(\",\")}`);\r\n }\r\n\r\n return {\r\n databaseDriver,\r\n databasePort: driver?.defaultPort ?? 27017,\r\n features,\r\n aiProviders,\r\n useGit: cli.git ?? false,\r\n useJWT: cli.jwt ?? false,\r\n };\r\n}\r\n\r\n/**\r\n * Non-interactive scaffold path for `--yes`. Builds the app from flags with\r\n * sensible defaults and skips every prompt — a single command scaffolds the\r\n * entire app. Validation lives in `resolveNonInteractiveOptions`; a typo fails\r\n * fast here instead of mid-install.\r\n */\r\nasync function createNonInteractive(cli: CliFlags) {\r\n const appName = (cli.name ?? \"\").trim();\r\n\r\n if (!appName) {\r\n cancel(\"--yes requires a project name (first argument or --name=<name>)\");\r\n process.exit(1);\r\n }\r\n\r\n const appPath = getAppPath(appName);\r\n\r\n if (!appPath) return;\r\n\r\n await detectPackageManagers();\r\n\r\n const packageManager = cli.pm ?? getPreferredPackageManager();\r\n setPackageManager(packageManager);\r\n\r\n let options: AppOptions;\r\n\r\n try {\r\n options = resolveNonInteractiveOptions(cli);\r\n } catch (error) {\r\n cancel((error as Error).message);\r\n process.exit(1);\r\n }\r\n\r\n const appDetails: Required<AppType> = {\r\n appName,\r\n appType: \"warlock\",\r\n appPath,\r\n pkgManager: getPackageManager(),\r\n options,\r\n };\r\n\r\n await createWarlockApp(new App(appDetails));\r\n}\r\n"],"mappings":"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"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getDatabaseLabel, isNoDatabase } from "../../features/database-drivers.mjs";
|
|
1
2
|
import { getPackageManager, runPackageManagerCommand } from "../../helpers/package-manager.mjs";
|
|
2
3
|
import { showSuccessScreen } from "../../ui/banner.mjs";
|
|
3
4
|
import { spinnerMessages } from "../../ui/spinners.mjs";
|
|
@@ -6,16 +7,20 @@ import { spinner } from "@clack/prompts";
|
|
|
6
7
|
//#region ../@warlock.js/create-warlock/src/commands/create-warlock-app/index.ts
|
|
7
8
|
async function createWarlockApp(application) {
|
|
8
9
|
const { useGit, useJWT, features, aiProviders, databaseDriver } = application.options;
|
|
10
|
+
const noDatabase = isNoDatabase(databaseDriver);
|
|
9
11
|
const templateSpinner = spinner();
|
|
10
12
|
templateSpinner.start(spinnerMessages.copyingTemplate);
|
|
11
|
-
application.init().use("warlock").updatePackageJson().updateDotEnv()
|
|
13
|
+
application.init().use("warlock").updatePackageJson().updateDotEnv();
|
|
14
|
+
if (noDatabase) application.removeDatabaseConfig();
|
|
15
|
+
else application.configureDatabaseEnv(databaseDriver);
|
|
16
|
+
application.configureHomePage(features.includes("react"));
|
|
12
17
|
templateSpinner.stop(spinnerMessages.templateCopied);
|
|
13
18
|
const installSpinner = spinner();
|
|
14
19
|
installSpinner.start(spinnerMessages.installingDeps);
|
|
15
20
|
await application.install().install;
|
|
16
21
|
installSpinner.stop(spinnerMessages.depsInstalled);
|
|
17
22
|
const selectedFeatures = [
|
|
18
|
-
databaseDriver,
|
|
23
|
+
...noDatabase ? [] : [databaseDriver],
|
|
19
24
|
...features,
|
|
20
25
|
...aiProviders
|
|
21
26
|
];
|
|
@@ -46,7 +51,7 @@ async function createWarlockApp(application) {
|
|
|
46
51
|
}
|
|
47
52
|
showSuccessScreen({
|
|
48
53
|
projectName: application.name,
|
|
49
|
-
database: databaseDriver
|
|
54
|
+
database: getDatabaseLabel(databaseDriver),
|
|
50
55
|
features: [...features, ...aiProviders],
|
|
51
56
|
packageManager: getPackageManager()
|
|
52
57
|
});
|
|
@@ -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
|
|
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 {\r\n getDatabaseLabel,\r\n isNoDatabase,\r\n} from \"../../features/database-drivers\";\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 const noDatabase = isNoDatabase(databaseDriver);\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\r\n // Wire the chosen database driver into .env — or, when the user opted out,\r\n // strip the database config entirely so the app boots with no database.\r\n if (noDatabase) {\r\n application.removeDatabaseConfig();\r\n } else {\r\n application.configureDatabaseEnv(databaseDriver);\r\n }\r\n\r\n application.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. When no\r\n // database was chosen, the driver is omitted (there is no `none` feature).\r\n const selectedFeatures = [\r\n ...(noDatabase ? [] : [databaseDriver]),\r\n ...features,\r\n ...aiProviders,\r\n ];\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: getDatabaseLabel(databaseDriver),\r\n features: [...features, ...aiProviders],\r\n packageManager: getPackageManager(),\r\n });\r\n}\r\n"],"mappings":";;;;;;;AAaA,eAAsB,iBAAiB,aAAkB;CAEvD,MAAM,EAAE,QAAQ,QAAQ,UAAU,aAAa,mBAD/B,YAAY;CAE5B,MAAM,aAAa,aAAa,cAAc;CAG9C,MAAM,kBAAkB,QAAQ;CAChC,gBAAgB,MAAM,gBAAgB,eAAe;CAErD,YACG,KAAK,CAAC,CACN,IAAI,SAAS,CAAC,CACd,kBAAkB,CAAC,CACnB,aAAa;CAIhB,IAAI,YACF,YAAY,qBAAqB;MAEjC,YAAY,qBAAqB,cAAc;CAGjD,YAAY,kBAAkB,SAAS,SAAS,OAAO,CAAC;CAExD,gBAAgB,KAAK,gBAAgB,cAAc;CAGnD,MAAM,iBAAiB,QAAQ;CAC/B,eAAe,MAAM,gBAAgB,cAAc;CAEnD,MAAM,YAAY,QAAQ,CAAC,CAAC;CAE5B,eAAe,KAAK,gBAAgB,aAAa;CAMjD,MAAM,mBAAmB;EACvB,GAAI,aAAa,CAAC,IAAI,CAAC,cAAc;EACrC,GAAG;EACH,GAAG;CACL;CAEA,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,CAAC,CAAC;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,iBAAiB,cAAc;EACzC,UAAU,CAAC,GAAG,UAAU,GAAG,WAAW;EACtC,gBAAgB,kBAAkB;CACpC,CAAC;AACH"}
|
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
//#region ../@warlock.js/create-warlock/src/features/database-drivers.ts
|
|
2
|
+
/**
|
|
3
|
+
* Sentinel `--db` value / select option that means "no database at all".
|
|
4
|
+
*
|
|
5
|
+
* Picking it wires no driver, pulls no driver package, and deletes the
|
|
6
|
+
* template's `src/config/database.ts` — the framework's database connector is
|
|
7
|
+
* config-gated on that file, so with it gone the app boots with no database.
|
|
8
|
+
*/
|
|
9
|
+
const NO_DATABASE = "none";
|
|
2
10
|
const databaseDrivers = [
|
|
3
11
|
{
|
|
4
12
|
value: "mongodb",
|
|
@@ -25,15 +33,23 @@ const databaseDrivers = [
|
|
|
25
33
|
}
|
|
26
34
|
];
|
|
27
35
|
/**
|
|
28
|
-
* Get database driver options for the select prompt
|
|
36
|
+
* Get database driver options for the select prompt.
|
|
37
|
+
*
|
|
38
|
+
* The real drivers come first, followed by a "None" opt-out so the user can
|
|
39
|
+
* scaffold an app with no database.
|
|
29
40
|
*/
|
|
30
41
|
function getDatabaseDriverOptions() {
|
|
31
|
-
return databaseDrivers.map((driver) => ({
|
|
42
|
+
return [...databaseDrivers.map((driver) => ({
|
|
32
43
|
value: driver.value,
|
|
33
44
|
label: driver.label,
|
|
34
45
|
hint: driver.hint,
|
|
35
46
|
disabled: driver.disabled
|
|
36
|
-
}))
|
|
47
|
+
})), {
|
|
48
|
+
value: NO_DATABASE,
|
|
49
|
+
label: "None",
|
|
50
|
+
hint: "No database — skips the driver and removes src/config/database.ts",
|
|
51
|
+
disabled: void 0
|
|
52
|
+
}];
|
|
37
53
|
}
|
|
38
54
|
/**
|
|
39
55
|
* Get database driver config by value
|
|
@@ -41,7 +57,22 @@ function getDatabaseDriverOptions() {
|
|
|
41
57
|
function getDatabaseDriver(value) {
|
|
42
58
|
return databaseDrivers.find((driver) => driver.value === value);
|
|
43
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* Whether a `--db` value / selection means "no database".
|
|
62
|
+
*/
|
|
63
|
+
function isNoDatabase(value) {
|
|
64
|
+
return value === NO_DATABASE;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Human-readable database label for the success screen: `"None"` when no
|
|
68
|
+
* database was chosen, otherwise the driver's own label (falling back to the
|
|
69
|
+
* raw value for an unrecognized driver).
|
|
70
|
+
*/
|
|
71
|
+
function getDatabaseLabel(value) {
|
|
72
|
+
if (isNoDatabase(value)) return "None";
|
|
73
|
+
return getDatabaseDriver(value)?.label ?? value;
|
|
74
|
+
}
|
|
44
75
|
|
|
45
76
|
//#endregion
|
|
46
|
-
export { getDatabaseDriver, getDatabaseDriverOptions };
|
|
77
|
+
export { NO_DATABASE, getDatabaseDriver, getDatabaseDriverOptions, getDatabaseLabel, isNoDatabase };
|
|
47
78
|
//# sourceMappingURL=database-drivers.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"database-drivers.mjs","names":[],"sources":["../../../../../../@warlock.js/create-warlock/src/features/database-drivers.ts"],"sourcesContent":["/**\n * Database driver options and configuration\n */\nexport type DatabaseDriver = {\n value: string;\n label: string;\n package: string;\n packageVersion: string;\n defaultPort: number;\n disabled?: boolean;\n hint?: string;\n};\n\nexport const databaseDrivers: DatabaseDriver[] = [\n {\n value: \"mongodb\",\n label: \"MongoDB\",\n package: \"mongodb\",\n packageVersion: \"^7.0.0\",\n defaultPort: 27017,\n },\n {\n value: \"postgres\",\n label: \"PostgreSQL\",\n package: \"pg\",\n packageVersion: \"^8.11.0\",\n defaultPort: 5432,\n },\n {\n value: \"mysql\",\n label: \"MySQL (Coming Soon)\",\n package: \"mysql2\",\n packageVersion: \"^3.5.0\",\n defaultPort: 3306,\n disabled: true,\n hint: \"MySQL support coming in a future release\",\n },\n];\n\n/**\n * Get database driver options for the select prompt\n */\nexport function getDatabaseDriverOptions() {\n return databaseDrivers.map(driver => ({\n
|
|
1
|
+
{"version":3,"file":"database-drivers.mjs","names":[],"sources":["../../../../../../@warlock.js/create-warlock/src/features/database-drivers.ts"],"sourcesContent":["/**\n * Database driver options and configuration\n */\nexport type DatabaseDriver = {\n value: string;\n label: string;\n package: string;\n packageVersion: string;\n defaultPort: number;\n disabled?: boolean;\n hint?: string;\n};\n\n/**\n * Sentinel `--db` value / select option that means \"no database at all\".\n *\n * Picking it wires no driver, pulls no driver package, and deletes the\n * template's `src/config/database.ts` — the framework's database connector is\n * config-gated on that file, so with it gone the app boots with no database.\n */\nexport const NO_DATABASE = \"none\";\n\nexport const databaseDrivers: DatabaseDriver[] = [\n {\n value: \"mongodb\",\n label: \"MongoDB\",\n package: \"mongodb\",\n packageVersion: \"^7.0.0\",\n defaultPort: 27017,\n },\n {\n value: \"postgres\",\n label: \"PostgreSQL\",\n package: \"pg\",\n packageVersion: \"^8.11.0\",\n defaultPort: 5432,\n },\n {\n value: \"mysql\",\n label: \"MySQL (Coming Soon)\",\n package: \"mysql2\",\n packageVersion: \"^3.5.0\",\n defaultPort: 3306,\n disabled: true,\n hint: \"MySQL support coming in a future release\",\n },\n];\n\n/**\n * Get database driver options for the select prompt.\n *\n * The real drivers come first, followed by a \"None\" opt-out so the user can\n * scaffold an app with no database.\n */\nexport function getDatabaseDriverOptions() {\n return [\n ...databaseDrivers.map(driver => ({\n value: driver.value,\n label: driver.label,\n hint: driver.hint,\n disabled: driver.disabled,\n })),\n {\n value: NO_DATABASE,\n label: \"None\",\n hint: \"No database — skips the driver and removes src/config/database.ts\",\n disabled: undefined,\n },\n ];\n}\n\n/**\n * Get database driver config by value\n */\nexport function getDatabaseDriver(value: string): DatabaseDriver | undefined {\n return databaseDrivers.find(driver => driver.value === value);\n}\n\n/**\n * Whether a `--db` value / selection means \"no database\".\n */\nexport function isNoDatabase(value: string | undefined): boolean {\n return value === NO_DATABASE;\n}\n\n/**\n * Human-readable database label for the success screen: `\"None\"` when no\n * database was chosen, otherwise the driver's own label (falling back to the\n * raw value for an unrecognized driver).\n */\nexport function getDatabaseLabel(value: string): string {\n if (isNoDatabase(value)) return \"None\";\n\n return getDatabaseDriver(value)?.label ?? value;\n}\n\n/**\n * Get database driver dependency\n */\nexport function getDatabaseDependency(\n driverValue: string,\n): Record<string, string> {\n const driver = getDatabaseDriver(driverValue);\n if (!driver) return {};\n\n return {\n [driver.package]: driver.packageVersion,\n };\n}\n"],"mappings":";;;;;;;;AAoBA,MAAa,cAAc;AAE3B,MAAa,kBAAoC;CAC/C;EACE,OAAO;EACP,OAAO;EACP,SAAS;EACT,gBAAgB;EAChB,aAAa;CACf;CACA;EACE,OAAO;EACP,OAAO;EACP,SAAS;EACT,gBAAgB;EAChB,aAAa;CACf;CACA;EACE,OAAO;EACP,OAAO;EACP,SAAS;EACT,gBAAgB;EAChB,aAAa;EACb,UAAU;EACV,MAAM;CACR;AACF;;;;;;;AAQA,SAAgB,2BAA2B;CACzC,OAAO,CACL,GAAG,gBAAgB,KAAI,YAAW;EAChC,OAAO,OAAO;EACd,OAAO,OAAO;EACd,MAAM,OAAO;EACb,UAAU,OAAO;CACnB,EAAE,GACF;EACE,OAAO;EACP,OAAO;EACP,MAAM;EACN,UAAU;CACZ,CACF;AACF;;;;AAKA,SAAgB,kBAAkB,OAA2C;CAC3E,OAAO,gBAAgB,MAAK,WAAU,OAAO,UAAU,KAAK;AAC9D;;;;AAKA,SAAgB,aAAa,OAAoC;CAC/D,OAAO,UAAU;AACnB;;;;;;AAOA,SAAgB,iBAAiB,OAAuB;CACtD,IAAI,aAAa,KAAK,GAAG,OAAO;CAEhC,OAAO,kBAAkB,KAAK,CAAC,EAAE,SAAS;AAC5C"}
|
package/esm/helpers/app.mjs
CHANGED
|
@@ -79,6 +79,24 @@ var App = class {
|
|
|
79
79
|
return this;
|
|
80
80
|
}
|
|
81
81
|
/**
|
|
82
|
+
* Remove the database layer for a "no database" scaffold.
|
|
83
|
+
*
|
|
84
|
+
* Deletes `src/config/database.ts` (and a `.tsx` variant if present) from the
|
|
85
|
+
* freshly-copied template. The framework's database connector is config-gated
|
|
86
|
+
* on that file — with it gone, `config.get("database")` is undefined and the
|
|
87
|
+
* connector no-ops, so the app boots with no database wired and no driver
|
|
88
|
+
* package pulled. The `DB_*` lines in `.env` are left in place (harmless: no
|
|
89
|
+
* config reads them) as a ready template for adding a database back later.
|
|
90
|
+
*/
|
|
91
|
+
removeDatabaseConfig() {
|
|
92
|
+
const configDir = path.resolve(this.path, "src/config");
|
|
93
|
+
for (const fileName of ["database.ts", "database.tsx"]) {
|
|
94
|
+
const filePath = path.resolve(configDir, fileName);
|
|
95
|
+
if (fileExists(filePath)) unlinkSync(filePath);
|
|
96
|
+
}
|
|
97
|
+
return this;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
82
100
|
* Pick the home page implementation based on whether React was selected.
|
|
83
101
|
*
|
|
84
102
|
* The template ships BOTH a plain JSON controller (`home-page.controller.ts`)
|
package/esm/helpers/app.mjs.map
CHANGED
|
@@ -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 { 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 * 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(/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,CAAC,CAC/C,WAAW,QAAQ,kBAAkB,CAAC;EAOzC,MAAM,iBAAyB,YAAY,YAAY,cAAc,CAAC,CAAC,CAAC;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;;;;;;;;;;;;CAaA,AAAO,qBAAqB,aAAqB;EAC/C,MAAM,SAAS,kBAAkB,WAAW;EAE5C,IAAI,CAAC,QAAQ,OAAO;EAGpB,MAAM,kBAAkB,KAAK,QAAQ,KAAK,MAAM,cAAc;EAC9D,MAAM,cAAc,YAAY,eAAe;EAG/C,YAAY,eAAe,YAAY,gBAAgB,CAAC;EACxD,IAAI,CAAC,YAAY,aAAa,OAAO,UAAU;GAC7C,YAAY,aAAa,OAAO,WAAW,OAAO;GAClD,YAAY,iBAAiB,WAAW;EAC1C;EAEA,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,CAAC,CAAC,WAAW,WAAW,KAAK,IAAI,CAAC,CAAC,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"}
|
|
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 = (\r\n getJsonFile(packageRoot(\"package.json\")) as { version: string }\r\n ).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 * 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(/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 * Remove the database layer for a \"no database\" scaffold.\r\n *\r\n * Deletes `src/config/database.ts` (and a `.tsx` variant if present) from the\r\n * freshly-copied template. The framework's database connector is config-gated\r\n * on that file — with it gone, `config.get(\"database\")` is undefined and the\r\n * connector no-ops, so the app boots with no database wired and no driver\r\n * package pulled. The `DB_*` lines in `.env` are left in place (harmless: no\r\n * config reads them) as a ready 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 [\"database.ts\", \"database.tsx\"]) {\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 * 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":"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"}
|
package/esm/index.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { NO_DATABASE } from "./features/database-drivers.mjs";
|
|
1
2
|
import createNewApp from "./commands/create-new-app/index.mjs";
|
|
2
3
|
|
|
3
4
|
//#region ../@warlock.js/create-warlock/src/index.ts
|
|
@@ -56,6 +57,9 @@ function parseFlags(argv) {
|
|
|
56
57
|
case "db":
|
|
57
58
|
flags.db = value;
|
|
58
59
|
break;
|
|
60
|
+
case "no-db":
|
|
61
|
+
flags.db = NO_DATABASE;
|
|
62
|
+
break;
|
|
59
63
|
case "pm":
|
|
60
64
|
flags.pm = value;
|
|
61
65
|
break;
|
package/esm/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../../../../@warlock.js/create-warlock/src/index.ts"],"sourcesContent":["import createNewApp from \"./commands/create-new-app\";\nimport { CliFlags } from \"./commands/create-new-app/types\";\n\nconst valueFlags = [\"name\", \"db\", \"pm\", \"features\", \"ai\"];\n\n/**\n * Parse the scaffolder's own CLI flags for non-interactive mode.\n *\n * @example\n * create-warlock my-app --db=postgres --features=test,herald --ai=openai,anthropic --yes\n */\nexport function parseFlags(argv: string[]): CliFlags {\n const flags: CliFlags = {};\n const positionals: string[] = [];\n\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i];\n\n if (!arg.startsWith(\"-\")) {\n positionals.push(arg);\n continue;\n }\n\n const equalIndex = arg.indexOf(\"=\");\n const key = (equalIndex === -1 ? arg : arg.slice(0, equalIndex)).replace(/^-+/, \"\");\n let value: string | undefined = equalIndex === -1 ? undefined : arg.slice(equalIndex + 1);\n\n // Value-taking flags may use either `--key=value` or `--key value`.\n if (valueFlags.includes(key) && value === undefined) {\n const next = argv[i + 1];\n\n if (next && !next.startsWith(\"-\")) {\n value = next;\n i++;\n }\n }\n\n switch (key) {\n case \"yes\":\n case \"y\":\n flags.yes = true;\n break;\n case \"git\":\n flags.git = true;\n break;\n case \"no-git\":\n flags.git = false;\n break;\n case \"jwt\":\n flags.jwt = true;\n break;\n case \"no-jwt\":\n flags.jwt = false;\n break;\n case \"name\":\n flags.name = value;\n break;\n case \"db\":\n flags.db = value;\n break;\n case \"pm\":\n flags.pm = value;\n break;\n case \"features\":\n flags.features = splitList(value);\n break;\n case \"ai\":\n flags.ai = splitList(value);\n break;\n }\n }\n\n if (!flags.name && positionals.length > 0) {\n flags.name = positionals[0];\n }\n\n return flags;\n}\n\nfunction splitList(value: string | undefined): string[] {\n if (!value) return [];\n\n return value\n .split(\",\")\n .map(item => item.trim())\n .filter(Boolean);\n}\n\nexport default function createApp() {\n const flags = parseFlags(process.argv.slice(2));\n\n createNewApp(flags);\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../../../../@warlock.js/create-warlock/src/index.ts"],"sourcesContent":["import createNewApp from \"./commands/create-new-app\";\nimport { CliFlags } from \"./commands/create-new-app/types\";\nimport { NO_DATABASE } from \"./features/database-drivers\";\n\nconst valueFlags = [\"name\", \"db\", \"pm\", \"features\", \"ai\"];\n\n/**\n * Parse the scaffolder's own CLI flags for non-interactive mode.\n *\n * @example\n * create-warlock my-app --db=postgres --features=test,herald --ai=openai,anthropic --yes\n */\nexport function parseFlags(argv: string[]): CliFlags {\n const flags: CliFlags = {};\n const positionals: string[] = [];\n\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i];\n\n if (!arg.startsWith(\"-\")) {\n positionals.push(arg);\n continue;\n }\n\n const equalIndex = arg.indexOf(\"=\");\n const key = (equalIndex === -1 ? arg : arg.slice(0, equalIndex)).replace(/^-+/, \"\");\n let value: string | undefined = equalIndex === -1 ? undefined : arg.slice(equalIndex + 1);\n\n // Value-taking flags may use either `--key=value` or `--key value`.\n if (valueFlags.includes(key) && value === undefined) {\n const next = argv[i + 1];\n\n if (next && !next.startsWith(\"-\")) {\n value = next;\n i++;\n }\n }\n\n switch (key) {\n case \"yes\":\n case \"y\":\n flags.yes = true;\n break;\n case \"git\":\n flags.git = true;\n break;\n case \"no-git\":\n flags.git = false;\n break;\n case \"jwt\":\n flags.jwt = true;\n break;\n case \"no-jwt\":\n flags.jwt = false;\n break;\n case \"name\":\n flags.name = value;\n break;\n case \"db\":\n flags.db = value;\n break;\n case \"no-db\":\n // Opt out of a database entirely — equivalent to `--db=none`.\n flags.db = NO_DATABASE;\n break;\n case \"pm\":\n flags.pm = value;\n break;\n case \"features\":\n flags.features = splitList(value);\n break;\n case \"ai\":\n flags.ai = splitList(value);\n break;\n }\n }\n\n if (!flags.name && positionals.length > 0) {\n flags.name = positionals[0];\n }\n\n return flags;\n}\n\nfunction splitList(value: string | undefined): string[] {\n if (!value) return [];\n\n return value\n .split(\",\")\n .map(item => item.trim())\n .filter(Boolean);\n}\n\nexport default function createApp() {\n const flags = parseFlags(process.argv.slice(2));\n\n createNewApp(flags);\n}\n"],"mappings":";;;;AAIA,MAAM,aAAa;CAAC;CAAQ;CAAM;CAAM;CAAY;AAAI;;;;;;;AAQxD,SAAgB,WAAW,MAA0B;CACnD,MAAM,QAAkB,CAAC;CACzB,MAAM,cAAwB,CAAC;CAE/B,KAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;EACpC,MAAM,MAAM,KAAK;EAEjB,IAAI,CAAC,IAAI,WAAW,GAAG,GAAG;GACxB,YAAY,KAAK,GAAG;GACpB;EACF;EAEA,MAAM,aAAa,IAAI,QAAQ,GAAG;EAClC,MAAM,OAAO,eAAe,KAAK,MAAM,IAAI,MAAM,GAAG,UAAU,EAAC,CAAE,QAAQ,OAAO,EAAE;EAClF,IAAI,QAA4B,eAAe,KAAK,SAAY,IAAI,MAAM,aAAa,CAAC;EAGxF,IAAI,WAAW,SAAS,GAAG,KAAK,UAAU,QAAW;GACnD,MAAM,OAAO,KAAK,IAAI;GAEtB,IAAI,QAAQ,CAAC,KAAK,WAAW,GAAG,GAAG;IACjC,QAAQ;IACR;GACF;EACF;EAEA,QAAQ,KAAR;GACE,KAAK;GACL,KAAK;IACH,MAAM,MAAM;IACZ;GACF,KAAK;IACH,MAAM,MAAM;IACZ;GACF,KAAK;IACH,MAAM,MAAM;IACZ;GACF,KAAK;IACH,MAAM,MAAM;IACZ;GACF,KAAK;IACH,MAAM,MAAM;IACZ;GACF,KAAK;IACH,MAAM,OAAO;IACb;GACF,KAAK;IACH,MAAM,KAAK;IACX;GACF,KAAK;IAEH,MAAM,KAAK;IACX;GACF,KAAK;IACH,MAAM,KAAK;IACX;GACF,KAAK;IACH,MAAM,WAAW,UAAU,KAAK;IAChC;GACF,KAAK;IACH,MAAM,KAAK,UAAU,KAAK;IAC1B;EACJ;CACF;CAEA,IAAI,CAAC,MAAM,QAAQ,YAAY,SAAS,GACtC,MAAM,OAAO,YAAY;CAG3B,OAAO;AACT;AAEA,SAAS,UAAU,OAAqC;CACtD,IAAI,CAAC,OAAO,OAAO,CAAC;CAEpB,OAAO,MACJ,MAAM,GAAG,CAAC,CACV,KAAI,SAAQ,KAAK,KAAK,CAAC,CAAC,CACxB,OAAO,OAAO;AACnB;AAEA,SAAwB,YAAY;CAGlC,aAFc,WAAW,QAAQ,KAAK,MAAM,CAAC,CAE5B,CAAC;AACpB"}
|
package/llms-full.txt
CHANGED
|
@@ -8,12 +8,12 @@
|
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
name: create-a-warlock-project
|
|
11
|
-
description: 'Scaffold a brand-new Warlock.js project with `create-warlock` — the interactive wizard and its non-interactive flag set. Every flag (`--name`, `--db`, `--pm`, `--features`, `--ai`, `--git/--no-git`, `--jwt/--no-jwt`, `--yes`), every valid database-driver / feature / AI-provider key, and the directory tree the template emits. Triggers: `create-warlock`, `yarn create warlock`, `npm create warlock`, `npx create-warlock`; "scaffold a warlock app", "start a new warlock project", "create-warlock flags", "non-interactive warlock scaffold", "which features can I pass to create-warlock". Skip: running an already-created project (`@warlock.js/core/run-app/SKILL.md`); adding a feature to an existing project (`warlock add`, `@warlock.js/core/add-connector/SKILL.md`); generating a module inside a project (`@warlock.js/core/create-module/SKILL.md`).'
|
|
11
|
+
description: 'Scaffold a brand-new Warlock.js project with `create-warlock` — the interactive wizard and its non-interactive flag set. A single `--yes` command scaffolds the entire app from flags. Every flag (`--name`, `--db`, `--pm`, `--features`, `--ai`, `--git/--no-git`, `--jwt/--no-jwt`, `--no-db`, `--yes`), every valid database-driver / feature / AI-provider key, opting out of a database (`--db=none` / `--no-db`, which removes `src/config/database.ts`), and the directory tree the template emits. Triggers: `create-warlock`, `yarn create warlock`, `npm create warlock`, `npx create-warlock`; "scaffold a warlock app", "start a new warlock project", "create-warlock flags", "non-interactive warlock scaffold", "scaffold without a database", "which features can I pass to create-warlock". Skip: running an already-created project (`@warlock.js/core/run-app/SKILL.md`); adding a feature to an existing project (`warlock add`, `@warlock.js/core/add-connector/SKILL.md`); generating a module inside a project (`@warlock.js/core/create-module/SKILL.md`).'
|
|
12
12
|
---
|
|
13
13
|
|
|
14
14
|
# create-warlock — scaffold a new project
|
|
15
15
|
|
|
16
|
-
`create-warlock` builds a fresh Warlock.js project: it copies the `warlock` template, substitutes the project name, wires the chosen database driver into `.env`, then delegates every optional package install to the project's own `warlock add` so dependency versions are never duplicated by the scaffolder.
|
|
16
|
+
`create-warlock` builds a fresh Warlock.js project: it copies the `warlock` template, substitutes the project name, wires the chosen database driver into `.env` (or, when you opt out with `--db=none`, removes `src/config/database.ts`), then delegates every optional package install to the project's own `warlock add` so dependency versions are never duplicated by the scaffolder.
|
|
17
17
|
|
|
18
18
|
## The shape
|
|
19
19
|
|
|
@@ -22,8 +22,11 @@ description: 'Scaffold a brand-new Warlock.js project with `create-warlock` —
|
|
|
22
22
|
yarn create warlock
|
|
23
23
|
# or: npm create warlock@latest / pnpm create warlock / npx create-warlock
|
|
24
24
|
|
|
25
|
-
# Non-interactive (CI, agents, reproducible setups)
|
|
26
|
-
npx create-warlock my-app --yes --db=postgres --features=test,redis --ai=openai
|
|
25
|
+
# Non-interactive (CI, agents, reproducible setups) — one command scaffolds the whole app
|
|
26
|
+
npx create-warlock my-app --yes --db=postgres --features=test,redis --ai=ai-openai
|
|
27
|
+
|
|
28
|
+
# No database at all
|
|
29
|
+
npx create-warlock my-api --yes --no-db --features=test
|
|
27
30
|
```
|
|
28
31
|
|
|
29
32
|
The first positional argument is the project name; it also becomes the directory created under the current working directory. If that directory already exists, the command aborts — it never overwrites.
|
|
@@ -34,7 +37,7 @@ The wizard asks, in order:
|
|
|
34
37
|
|
|
35
38
|
1. **Project name** — required.
|
|
36
39
|
2. **Package manager** — chosen from the managers detected on the system (npm is always offered; yarn / pnpm appear if installed).
|
|
37
|
-
3. **Database driver** — a single select (see keys below).
|
|
40
|
+
3. **Database driver** — a single select: a driver, or **None** to scaffold without a database (see keys below).
|
|
38
41
|
4. **Features** — a multiselect of optional packages (`react` is pre-checked).
|
|
39
42
|
5. **AI providers** — a multiselect; picking any one pulls `@warlock.js/ai` automatically.
|
|
40
43
|
6. **Initialize Git?** — yes/no.
|
|
@@ -47,7 +50,8 @@ Pass `--yes` (or `-y`) to skip every prompt and build from flags with defaults.
|
|
|
47
50
|
| Flag | Takes value | Default | Purpose |
|
|
48
51
|
| ------------------------ | ----------- | -------------- | ------------------------------------------------------------------- |
|
|
49
52
|
| `<positional>` / `--name`| yes | — (required) | Project name + target directory. |
|
|
50
|
-
| `--db` | yes | `mongodb` | Database driver key.
|
|
53
|
+
| `--db` | yes | `mongodb` | Database driver key (`mongodb`, `postgres`, or `none`). |
|
|
54
|
+
| `--no-db` | no | — | Opt out of a database — shorthand for `--db=none`. |
|
|
51
55
|
| `--pm` | yes | auto-detected | Package manager (`npm` / `yarn` / `pnpm`). |
|
|
52
56
|
| `--features` | yes (CSV) | none | Comma-separated optional feature keys. |
|
|
53
57
|
| `--ai` | yes (CSV) | none | Comma-separated AI provider keys (auto-pulls `@warlock.js/ai`). |
|
|
@@ -66,10 +70,11 @@ Value flags accept either `--db=postgres` or `--db postgres`. Unknown `--feature
|
|
|
66
70
|
| `mongodb` | 27017 | available |
|
|
67
71
|
| `postgres` | 5432 | available |
|
|
68
72
|
| `mysql` | 3306 | coming soon (disabled in the wizard) |
|
|
73
|
+
| `none` | — | opt out — no driver, no driver package, `src/config/database.ts` removed |
|
|
69
74
|
|
|
70
75
|
**Features** (`--features`): `react`, `react-email`, `mail`, `ses`, `image`, `s3`, `redis`, `scheduler`, `herald`, `socket`, `swagger`, `postman`, `test`.
|
|
71
76
|
|
|
72
|
-
**AI providers** (`--ai`): `openai`, `google`, `anthropic`, `bedrock`, `ollama`.
|
|
77
|
+
**AI providers / packages** (`--ai`): `ai-openai`, `ai-google`, `ai-anthropic`, `ai-bedrock`, `ai-ollama`, plus the capability packages `ai-tools`, `ai-panoptic`, `ai-workspace`. Any pick auto-pulls the core `@warlock.js/ai` package.
|
|
73
78
|
|
|
74
79
|
> The scaffolder holds **no dependency versions** for these — the keys map to `warlock add <key>`, whose feature map in `@warlock.js/core` is the single source of truth. A feature that resolves there will install; one that does not is rejected.
|
|
75
80
|
|
|
@@ -90,16 +95,16 @@ my-app/
|
|
|
90
95
|
├─ users/ # model, migration, resource, repository, create + list controllers, schema, events, seeds, commands
|
|
91
96
|
├─ posts/ # sample module — model, migration, resource, schema, create + update controllers
|
|
92
97
|
├─ uploads/ # file-serving controller (fetch-uploaded-file)
|
|
93
|
-
└─ shared/ # router/locale utils, home page, scheduler
|
|
98
|
+
└─ shared/ # router/locale utils, home page, scheduler
|
|
94
99
|
```
|
|
95
100
|
|
|
96
101
|
The template ships a working auth module (login / logout / logout-all / refresh / me / forgot + reset password via OTP), a `users` module, and a sample `posts` module — all using the current framework surface: schemas via `v` from `@warlock.js/seal`, controllers typed with `GuardedRequestHandler` carrying a `.validation = { schema }`, models on `@warlock.js/cascade`, and declarative migrations via `Migration.create`.
|
|
97
102
|
|
|
98
103
|
## Setup steps the scaffolder runs
|
|
99
104
|
|
|
100
|
-
1. Copy template → substitute name → wire `DB_DRIVER` / `DB_PORT` into `.env
|
|
105
|
+
1. Copy template → substitute name → wire `DB_DRIVER` / `DB_PORT` into `.env` (or, for `--db=none`, remove `src/config/database.ts`).
|
|
101
106
|
2. Install base dependencies (so the `warlock` binary exists).
|
|
102
|
-
3. `warlock add <driver> <features...> <ai...> --no-install`, then one batched install.
|
|
107
|
+
3. `warlock add <driver> <features...> <ai...> --no-install`, then one batched install. The `<driver>` is omitted for `--db=none`.
|
|
103
108
|
4. `git init` (if `--git`).
|
|
104
109
|
5. `jwt` script (if `--jwt`) — otherwise `warlock --warm-cache`.
|
|
105
110
|
|
|
@@ -108,7 +113,8 @@ Failures surface the error and halt; there is no rollback. After it finishes, `c
|
|
|
108
113
|
## Gotchas
|
|
109
114
|
|
|
110
115
|
- **The target directory must not already exist** — the command exits rather than merge into it.
|
|
111
|
-
- **`--
|
|
116
|
+
- **`--db=none` / `--no-db` scaffolds without a database** — no driver, no driver package, and `src/config/database.ts` is deleted. The framework's database connector is gated on that file, so the app boots with no database. The `DB_*` lines stay in `.env` (harmless) as a template for adding one back later.
|
|
117
|
+
- **`--ai` never takes `ai` itself.** Pick a provider (`ai-openai`, …); the core `@warlock.js/ai` package is pulled in transitively.
|
|
112
118
|
- **`mysql` is disabled** in the wizard; passing `--db=mysql` resolves the driver record but the driver is marked coming-soon.
|
|
113
119
|
- **Versions are not the scaffolder's job.** If a feature installs the wrong version, the fix is in core's `warlock add` feature map, not here.
|
|
114
120
|
|
package/llms.txt
CHANGED
|
@@ -6,4 +6,4 @@
|
|
|
6
6
|
|
|
7
7
|
## Skills
|
|
8
8
|
|
|
9
|
-
- [create-a-warlock-project](create-warlock/create-a-warlock-project/SKILL.md): Scaffold a brand-new Warlock.js project with `create-warlock` — the interactive wizard and its non-interactive flag set. Every flag (`--name`, `--db`, `--pm`, `--features`, `--ai`, `--git/--no-git`, `--jwt/--no-jwt`, `--yes`), every valid database-driver / feature / AI-provider key, and the directory tree the template emits. Triggers: `create-warlock`, `yarn create warlock`, `npm create warlock`, `npx create-warlock`; "scaffold a warlock app", "start a new warlock project", "create-warlock flags", "non-interactive warlock scaffold", "which features can I pass to create-warlock". Skip: running an already-created project (`@warlock.js/core/run-app/SKILL.md`); adding a feature to an existing project (`warlock add`, `@warlock.js/core/add-connector/SKILL.md`); generating a module inside a project (`@warlock.js/core/create-module/SKILL.md`).
|
|
9
|
+
- [create-a-warlock-project](create-warlock/create-a-warlock-project/SKILL.md): Scaffold a brand-new Warlock.js project with `create-warlock` — the interactive wizard and its non-interactive flag set. A single `--yes` command scaffolds the entire app from flags. Every flag (`--name`, `--db`, `--pm`, `--features`, `--ai`, `--git/--no-git`, `--jwt/--no-jwt`, `--no-db`, `--yes`), every valid database-driver / feature / AI-provider key, opting out of a database (`--db=none` / `--no-db`, which removes `src/config/database.ts`), and the directory tree the template emits. Triggers: `create-warlock`, `yarn create warlock`, `npm create warlock`, `npx create-warlock`; "scaffold a warlock app", "start a new warlock project", "create-warlock flags", "non-interactive warlock scaffold", "scaffold without a database", "which features can I pass to create-warlock". Skip: running an already-created project (`@warlock.js/core/run-app/SKILL.md`); adding a feature to an existing project (`warlock add`, `@warlock.js/core/add-connector/SKILL.md`); generating a module inside a project (`@warlock.js/core/create-module/SKILL.md`).
|
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"dependencies": {
|
|
10
10
|
"@clack/prompts": "^0.7.0",
|
|
11
11
|
"@mongez/copper": "^2.1.2",
|
|
12
|
-
"@warlock.js/fs": "4.
|
|
12
|
+
"@warlock.js/fs": "4.7.0",
|
|
13
13
|
"@mongez/reinforcements": "^3.3.0",
|
|
14
14
|
"cross-spawn": "^7.0.3",
|
|
15
15
|
"rimraf": "^6.0.1",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"bin": {
|
|
19
19
|
"create-warlock": "bin/create-app.js"
|
|
20
20
|
},
|
|
21
|
-
"version": "4.
|
|
21
|
+
"version": "4.7.0",
|
|
22
22
|
"type": "module",
|
|
23
23
|
"main": "./esm/index.mjs",
|
|
24
24
|
"module": "./esm/index.mjs",
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: create-a-warlock-project
|
|
3
|
-
description: 'Scaffold a brand-new Warlock.js project with `create-warlock` — the interactive wizard and its non-interactive flag set. Every flag (`--name`, `--db`, `--pm`, `--features`, `--ai`, `--git/--no-git`, `--jwt/--no-jwt`, `--yes`), every valid database-driver / feature / AI-provider key, and the directory tree the template emits. Triggers: `create-warlock`, `yarn create warlock`, `npm create warlock`, `npx create-warlock`; "scaffold a warlock app", "start a new warlock project", "create-warlock flags", "non-interactive warlock scaffold", "which features can I pass to create-warlock". Skip: running an already-created project (`@warlock.js/core/run-app/SKILL.md`); adding a feature to an existing project (`warlock add`, `@warlock.js/core/add-connector/SKILL.md`); generating a module inside a project (`@warlock.js/core/create-module/SKILL.md`).'
|
|
3
|
+
description: 'Scaffold a brand-new Warlock.js project with `create-warlock` — the interactive wizard and its non-interactive flag set. A single `--yes` command scaffolds the entire app from flags. Every flag (`--name`, `--db`, `--pm`, `--features`, `--ai`, `--git/--no-git`, `--jwt/--no-jwt`, `--no-db`, `--yes`), every valid database-driver / feature / AI-provider key, opting out of a database (`--db=none` / `--no-db`, which removes `src/config/database.ts`), and the directory tree the template emits. Triggers: `create-warlock`, `yarn create warlock`, `npm create warlock`, `npx create-warlock`; "scaffold a warlock app", "start a new warlock project", "create-warlock flags", "non-interactive warlock scaffold", "scaffold without a database", "which features can I pass to create-warlock". Skip: running an already-created project (`@warlock.js/core/run-app/SKILL.md`); adding a feature to an existing project (`warlock add`, `@warlock.js/core/add-connector/SKILL.md`); generating a module inside a project (`@warlock.js/core/create-module/SKILL.md`).'
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# create-warlock — scaffold a new project
|
|
7
7
|
|
|
8
|
-
`create-warlock` builds a fresh Warlock.js project: it copies the `warlock` template, substitutes the project name, wires the chosen database driver into `.env`, then delegates every optional package install to the project's own `warlock add` so dependency versions are never duplicated by the scaffolder.
|
|
8
|
+
`create-warlock` builds a fresh Warlock.js project: it copies the `warlock` template, substitutes the project name, wires the chosen database driver into `.env` (or, when you opt out with `--db=none`, removes `src/config/database.ts`), then delegates every optional package install to the project's own `warlock add` so dependency versions are never duplicated by the scaffolder.
|
|
9
9
|
|
|
10
10
|
## The shape
|
|
11
11
|
|
|
@@ -14,8 +14,11 @@ description: 'Scaffold a brand-new Warlock.js project with `create-warlock` —
|
|
|
14
14
|
yarn create warlock
|
|
15
15
|
# or: npm create warlock@latest / pnpm create warlock / npx create-warlock
|
|
16
16
|
|
|
17
|
-
# Non-interactive (CI, agents, reproducible setups)
|
|
18
|
-
npx create-warlock my-app --yes --db=postgres --features=test,redis --ai=openai
|
|
17
|
+
# Non-interactive (CI, agents, reproducible setups) — one command scaffolds the whole app
|
|
18
|
+
npx create-warlock my-app --yes --db=postgres --features=test,redis --ai=ai-openai
|
|
19
|
+
|
|
20
|
+
# No database at all
|
|
21
|
+
npx create-warlock my-api --yes --no-db --features=test
|
|
19
22
|
```
|
|
20
23
|
|
|
21
24
|
The first positional argument is the project name; it also becomes the directory created under the current working directory. If that directory already exists, the command aborts — it never overwrites.
|
|
@@ -26,7 +29,7 @@ The wizard asks, in order:
|
|
|
26
29
|
|
|
27
30
|
1. **Project name** — required.
|
|
28
31
|
2. **Package manager** — chosen from the managers detected on the system (npm is always offered; yarn / pnpm appear if installed).
|
|
29
|
-
3. **Database driver** — a single select (see keys below).
|
|
32
|
+
3. **Database driver** — a single select: a driver, or **None** to scaffold without a database (see keys below).
|
|
30
33
|
4. **Features** — a multiselect of optional packages (`react` is pre-checked).
|
|
31
34
|
5. **AI providers** — a multiselect; picking any one pulls `@warlock.js/ai` automatically.
|
|
32
35
|
6. **Initialize Git?** — yes/no.
|
|
@@ -39,7 +42,8 @@ Pass `--yes` (or `-y`) to skip every prompt and build from flags with defaults.
|
|
|
39
42
|
| Flag | Takes value | Default | Purpose |
|
|
40
43
|
| ------------------------ | ----------- | -------------- | ------------------------------------------------------------------- |
|
|
41
44
|
| `<positional>` / `--name`| yes | — (required) | Project name + target directory. |
|
|
42
|
-
| `--db` | yes | `mongodb` | Database driver key.
|
|
45
|
+
| `--db` | yes | `mongodb` | Database driver key (`mongodb`, `postgres`, or `none`). |
|
|
46
|
+
| `--no-db` | no | — | Opt out of a database — shorthand for `--db=none`. |
|
|
43
47
|
| `--pm` | yes | auto-detected | Package manager (`npm` / `yarn` / `pnpm`). |
|
|
44
48
|
| `--features` | yes (CSV) | none | Comma-separated optional feature keys. |
|
|
45
49
|
| `--ai` | yes (CSV) | none | Comma-separated AI provider keys (auto-pulls `@warlock.js/ai`). |
|
|
@@ -58,10 +62,11 @@ Value flags accept either `--db=postgres` or `--db postgres`. Unknown `--feature
|
|
|
58
62
|
| `mongodb` | 27017 | available |
|
|
59
63
|
| `postgres` | 5432 | available |
|
|
60
64
|
| `mysql` | 3306 | coming soon (disabled in the wizard) |
|
|
65
|
+
| `none` | — | opt out — no driver, no driver package, `src/config/database.ts` removed |
|
|
61
66
|
|
|
62
67
|
**Features** (`--features`): `react`, `react-email`, `mail`, `ses`, `image`, `s3`, `redis`, `scheduler`, `herald`, `socket`, `swagger`, `postman`, `test`.
|
|
63
68
|
|
|
64
|
-
**AI providers** (`--ai`): `openai`, `google`, `anthropic`, `bedrock`, `ollama`.
|
|
69
|
+
**AI providers / packages** (`--ai`): `ai-openai`, `ai-google`, `ai-anthropic`, `ai-bedrock`, `ai-ollama`, plus the capability packages `ai-tools`, `ai-panoptic`, `ai-workspace`. Any pick auto-pulls the core `@warlock.js/ai` package.
|
|
65
70
|
|
|
66
71
|
> The scaffolder holds **no dependency versions** for these — the keys map to `warlock add <key>`, whose feature map in `@warlock.js/core` is the single source of truth. A feature that resolves there will install; one that does not is rejected.
|
|
67
72
|
|
|
@@ -82,16 +87,16 @@ my-app/
|
|
|
82
87
|
├─ users/ # model, migration, resource, repository, create + list controllers, schema, events, seeds, commands
|
|
83
88
|
├─ posts/ # sample module — model, migration, resource, schema, create + update controllers
|
|
84
89
|
├─ uploads/ # file-serving controller (fetch-uploaded-file)
|
|
85
|
-
└─ shared/ # router/locale utils, home page, scheduler
|
|
90
|
+
└─ shared/ # router/locale utils, home page, scheduler
|
|
86
91
|
```
|
|
87
92
|
|
|
88
93
|
The template ships a working auth module (login / logout / logout-all / refresh / me / forgot + reset password via OTP), a `users` module, and a sample `posts` module — all using the current framework surface: schemas via `v` from `@warlock.js/seal`, controllers typed with `GuardedRequestHandler` carrying a `.validation = { schema }`, models on `@warlock.js/cascade`, and declarative migrations via `Migration.create`.
|
|
89
94
|
|
|
90
95
|
## Setup steps the scaffolder runs
|
|
91
96
|
|
|
92
|
-
1. Copy template → substitute name → wire `DB_DRIVER` / `DB_PORT` into `.env
|
|
97
|
+
1. Copy template → substitute name → wire `DB_DRIVER` / `DB_PORT` into `.env` (or, for `--db=none`, remove `src/config/database.ts`).
|
|
93
98
|
2. Install base dependencies (so the `warlock` binary exists).
|
|
94
|
-
3. `warlock add <driver> <features...> <ai...> --no-install`, then one batched install.
|
|
99
|
+
3. `warlock add <driver> <features...> <ai...> --no-install`, then one batched install. The `<driver>` is omitted for `--db=none`.
|
|
95
100
|
4. `git init` (if `--git`).
|
|
96
101
|
5. `jwt` script (if `--jwt`) — otherwise `warlock --warm-cache`.
|
|
97
102
|
|
|
@@ -100,7 +105,8 @@ Failures surface the error and halt; there is no rollback. After it finishes, `c
|
|
|
100
105
|
## Gotchas
|
|
101
106
|
|
|
102
107
|
- **The target directory must not already exist** — the command exits rather than merge into it.
|
|
103
|
-
- **`--
|
|
108
|
+
- **`--db=none` / `--no-db` scaffolds without a database** — no driver, no driver package, and `src/config/database.ts` is deleted. The framework's database connector is gated on that file, so the app boots with no database. The `DB_*` lines stay in `.env` (harmless) as a template for adding one back later.
|
|
109
|
+
- **`--ai` never takes `ai` itself.** Pick a provider (`ai-openai`, …); the core `@warlock.js/ai` package is pulled in transitively.
|
|
104
110
|
- **`mysql` is disabled** in the wizard; passing `--db=mysql` resolves the driver record but the driver is marked coming-soon.
|
|
105
111
|
- **Versions are not the scaffolder's job.** If a feature installs the wrong version, the fix is in core's `warlock add` feature map, not here.
|
|
106
112
|
|
package/templates/warlock/src/app/auth/models/otp/migrations/22-12-2025_10-30-20.otp-migration.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { integer, json, Migration, string, timestamp } from "@warlock.js/cascade";
|
|
2
2
|
import { OTP } from "../otp.model";
|
|
3
3
|
|
|
4
4
|
export default Migration.create(
|
|
@@ -15,10 +15,6 @@ export default Migration.create(
|
|
|
15
15
|
attempts: integer(),
|
|
16
16
|
maxAttempts: integer(),
|
|
17
17
|
metadata: json().nullable(),
|
|
18
|
-
isActive: bool(),
|
|
19
|
-
createdBy: json().nullable(),
|
|
20
|
-
updatedBy: json().nullable(),
|
|
21
|
-
deletedBy: json().nullable(),
|
|
22
18
|
},
|
|
23
19
|
{
|
|
24
20
|
index: [
|
package/templates/warlock/src/app/posts/models/post/migrations/09-01-2026_02-07-51-post.migration.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { integer, Migration, string, text, timestamp } from "@warlock.js/cascade";
|
|
2
2
|
import { Post } from "../post.model";
|
|
3
3
|
|
|
4
4
|
export default Migration.create(Post, {
|
|
@@ -6,10 +6,6 @@ export default Migration.create(Post, {
|
|
|
6
6
|
description: text(),
|
|
7
7
|
slug: string(255).unique(),
|
|
8
8
|
image: string(500).nullable(),
|
|
9
|
-
isActive: bool(),
|
|
10
9
|
authorId: integer().notNullable(),
|
|
11
|
-
createdBy: json().nullable(),
|
|
12
|
-
updatedBy: json().nullable(),
|
|
13
|
-
deletedBy: json().nullable(),
|
|
14
10
|
deletedAt: timestamp().nullable(),
|
|
15
11
|
});
|
|
@@ -2,9 +2,8 @@ import { Model, RegisterModel } from "@warlock.js/cascade";
|
|
|
2
2
|
import { useComputedSlug } from "@warlock.js/core";
|
|
3
3
|
import { type Infer, v } from "@warlock.js/seal";
|
|
4
4
|
import { PostResource } from "app/posts/resources/post.resource";
|
|
5
|
-
import { globalColumnsSchema } from "app/shared/utils/global-columns-schema";
|
|
6
5
|
|
|
7
|
-
export const postSchema =
|
|
6
|
+
export const postSchema = v.object({
|
|
8
7
|
title: v.string().required(),
|
|
9
8
|
description: v.string().required(),
|
|
10
9
|
slug: v.computed(useComputedSlug()),
|
package/templates/warlock/src/app/users/models/user/migrations/11-12-2025_23-58-03-user.migration.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { json, Migration, string, timestamp } from "@warlock.js/cascade";
|
|
2
2
|
import { User } from "../user.model";
|
|
3
3
|
|
|
4
4
|
export default Migration.create(User, {
|
|
@@ -7,9 +7,5 @@ export default Migration.create(User, {
|
|
|
7
7
|
password: string(255),
|
|
8
8
|
image: string(500).nullable(),
|
|
9
9
|
imageMetadata: json().nullable(),
|
|
10
|
-
isActive: bool(),
|
|
11
|
-
createdBy: json().nullable(),
|
|
12
|
-
updatedBy: json().nullable(),
|
|
13
|
-
deletedBy: json().nullable(),
|
|
14
10
|
deletedAt: timestamp().nullable(),
|
|
15
11
|
});
|
|
@@ -2,10 +2,9 @@ import { Auth } from "@warlock.js/auth";
|
|
|
2
2
|
import { RegisterModel } from "@warlock.js/cascade";
|
|
3
3
|
import { useHashedPassword } from "@warlock.js/core";
|
|
4
4
|
import { type Infer, v } from "@warlock.js/seal";
|
|
5
|
-
import { globalColumnsSchema } from "app/shared/utils/global-columns-schema";
|
|
6
5
|
import { UserResource } from "app/users/resources/user.resource";
|
|
7
6
|
|
|
8
|
-
export const userSchema =
|
|
7
|
+
export const userSchema = v.object({
|
|
9
8
|
name: v.string().required(),
|
|
10
9
|
email: v.email().requiredIfEmpty("id"),
|
|
11
10
|
image: v.string(),
|
|
@@ -45,10 +44,6 @@ export class User extends Auth<UserSchema> {
|
|
|
45
44
|
|
|
46
45
|
static {
|
|
47
46
|
// Local scopes
|
|
48
|
-
this.addScope("active", (query) => {
|
|
49
|
-
query.where("isActive", true);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
47
|
this.addScope("admins", (query) => {
|
|
53
48
|
query.where("role", "admin");
|
|
54
49
|
});
|
|
@@ -56,9 +51,5 @@ export class User extends Auth<UserSchema> {
|
|
|
56
51
|
this.addScope("verified", (query) => {
|
|
57
52
|
query.where("emailVerified", true);
|
|
58
53
|
});
|
|
59
|
-
|
|
60
|
-
// this.addGlobalScope("active", (query) => {
|
|
61
|
-
// query.where("isActive", true);
|
|
62
|
-
// });
|
|
63
54
|
}
|
|
64
55
|
}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { Model } from "@warlock.js/cascade";
|
|
2
|
-
import { useCurrentUser } from "@warlock.js/core";
|
|
3
|
-
|
|
4
|
-
const globalEvents = Model.globalEvents();
|
|
5
|
-
|
|
6
|
-
const saveSubscription = globalEvents.onSaving(async (model, { isInsert }) => {
|
|
7
|
-
const user = useCurrentUser();
|
|
8
|
-
|
|
9
|
-
if (!user) return;
|
|
10
|
-
|
|
11
|
-
if (isInsert) {
|
|
12
|
-
if (model.schemaHas("createdBy")) {
|
|
13
|
-
model.set("createdBy", user);
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
if (model.schemaHas("updatedBy")) {
|
|
18
|
-
model.set("updatedBy", user);
|
|
19
|
-
}
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
const deleteSubscription = globalEvents.onDeleting(async (model) => {
|
|
23
|
-
const user = useCurrentUser();
|
|
24
|
-
|
|
25
|
-
if (!user) return;
|
|
26
|
-
|
|
27
|
-
if (model.schemaHas("deletedBy")) {
|
|
28
|
-
model.set("deletedBy", user);
|
|
29
|
-
}
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
export const cleanup = [saveSubscription, deleteSubscription];
|