create-warlock 4.1.11 → 4.1.12
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.
|
@@ -66,18 +66,6 @@ const features = [
|
|
|
66
66
|
hint: "Realtime websocket server",
|
|
67
67
|
group: "Realtime"
|
|
68
68
|
},
|
|
69
|
-
{
|
|
70
|
-
key: "swagger",
|
|
71
|
-
label: "Swagger",
|
|
72
|
-
hint: "OpenAPI documentation for your routes",
|
|
73
|
-
group: "API Docs"
|
|
74
|
-
},
|
|
75
|
-
{
|
|
76
|
-
key: "postman",
|
|
77
|
-
label: "Postman",
|
|
78
|
-
hint: "Generate a Postman collection for your API",
|
|
79
|
-
group: "API Docs"
|
|
80
|
-
},
|
|
81
69
|
{
|
|
82
70
|
key: "test",
|
|
83
71
|
label: "Testing (Vitest)",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"features-map.mjs","names":[],"sources":["../../../../../../@warlock.js/create-warlock/src/features/features-map.ts"],"sourcesContent":["/**\n * Presentation manifest for the scaffolder's feature prompts.\n *\n * This file is **display metadata only** — keys, labels, hints, grouping, and\n * default selections. It deliberately holds NO dependency names or versions:\n * the single source of truth for what each feature installs is the `add`\n * command's feature map in `@warlock.js/core`\n * (`src/generations/add-command.action.ts`). The scaffolder collects the\n * selections here and delegates the actual install to `warlock add`, so the\n * two never drift on versions again.\n *\n * Every `key` below MUST exist in core's `allowedFeatures`; a CI guard should\n * assert that subset relationship so a typo fails the build instead of shipping.\n */\n\nexport type FeatureGroup =\n | \"Rendering & Mail\"\n | \"Media\"\n | \"Storage & Cache\"\n | \"Jobs & Messaging\"\n | \"Realtime\"\n | \"API Docs\"\n | \"Tooling\";\n\nexport type FeatureOption = {\n /** Must match a key in core's `add` feature map. */\n key: string;\n label: string;\n hint: string;\n group: FeatureGroup;\n defaultSelected?: boolean;\n};\n\n/**\n * Optional features offered in the general multiselect step. The database\n * driver (its own select) and AI providers (their own step) are intentionally\n * NOT here — they have dedicated prompts.\n */\nexport const features: FeatureOption[] = [\n // Rendering & Mail\n {\n key: \"react\",\n label: \"React (rendering & mails)\",\n hint: \"React + ReactDOM for non-interactive rendering and HTML/email generation\",\n group: \"Rendering & Mail\",\n defaultSelected: true,\n },\n {\n key: \"react-email\",\n label: \"React Email\",\n hint: \"Build email templates with React + Tailwind (pulls react + mail)\",\n group: \"Rendering & Mail\",\n },\n {\n key: \"mail\",\n label: \"Mail (Nodemailer)\",\n hint: \"Send emails via SMTP\",\n group: \"Rendering & Mail\",\n },\n {\n key: \"ses\",\n label: \"Amazon SES\",\n hint: \"Send emails via the AWS SES API\",\n group: \"Rendering & Mail\",\n },\n\n // Media\n {\n key: \"image\",\n label: \"Image processing (Sharp)\",\n hint: \"Resize, convert, and optimize images\",\n group: \"Media\",\n },\n\n // Storage & Cache\n {\n key: \"s3\",\n label: \"S3 storage\",\n hint: \"AWS S3 for cloud file storage\",\n group: \"Storage & Cache\",\n },\n {\n key: \"redis\",\n label: \"Redis cache\",\n hint: \"Redis driver for the cache layer\",\n group: \"Storage & Cache\",\n },\n\n // Jobs & Messaging\n {\n key: \"scheduler\",\n label: \"Scheduler\",\n hint: \"Background tasks and cron jobs\",\n group: \"Jobs & Messaging\",\n },\n {\n key: \"herald\",\n label: \"Herald (RabbitMQ)\",\n hint: \"Message broker for event-driven architecture\",\n group: \"Jobs & Messaging\",\n },\n\n // Realtime\n {\n key: \"socket\",\n label: \"Socket.IO\",\n hint: \"Realtime websocket server\",\n group: \"Realtime\",\n },\n\n // API Docs
|
|
1
|
+
{"version":3,"file":"features-map.mjs","names":[],"sources":["../../../../../../@warlock.js/create-warlock/src/features/features-map.ts"],"sourcesContent":["/**\n * Presentation manifest for the scaffolder's feature prompts.\n *\n * This file is **display metadata only** — keys, labels, hints, grouping, and\n * default selections. It deliberately holds NO dependency names or versions:\n * the single source of truth for what each feature installs is the `add`\n * command's feature map in `@warlock.js/core`\n * (`src/generations/add-command.action.ts`). The scaffolder collects the\n * selections here and delegates the actual install to `warlock add`, so the\n * two never drift on versions again.\n *\n * Every `key` below MUST exist in core's `allowedFeatures`; a CI guard should\n * assert that subset relationship so a typo fails the build instead of shipping.\n */\n\nexport type FeatureGroup =\n | \"Rendering & Mail\"\n | \"Media\"\n | \"Storage & Cache\"\n | \"Jobs & Messaging\"\n | \"Realtime\"\n | \"API Docs\"\n | \"Tooling\";\n\nexport type FeatureOption = {\n /** Must match a key in core's `add` feature map. */\n key: string;\n label: string;\n hint: string;\n group: FeatureGroup;\n defaultSelected?: boolean;\n};\n\n/**\n * Optional features offered in the general multiselect step. The database\n * driver (its own select) and AI providers (their own step) are intentionally\n * NOT here — they have dedicated prompts.\n */\nexport const features: FeatureOption[] = [\n // Rendering & Mail\n {\n key: \"react\",\n label: \"React (rendering & mails)\",\n hint: \"React + ReactDOM for non-interactive rendering and HTML/email generation\",\n group: \"Rendering & Mail\",\n defaultSelected: true,\n },\n {\n key: \"react-email\",\n label: \"React Email\",\n hint: \"Build email templates with React + Tailwind (pulls react + mail)\",\n group: \"Rendering & Mail\",\n },\n {\n key: \"mail\",\n label: \"Mail (Nodemailer)\",\n hint: \"Send emails via SMTP\",\n group: \"Rendering & Mail\",\n },\n {\n key: \"ses\",\n label: \"Amazon SES\",\n hint: \"Send emails via the AWS SES API\",\n group: \"Rendering & Mail\",\n },\n\n // Media\n {\n key: \"image\",\n label: \"Image processing (Sharp)\",\n hint: \"Resize, convert, and optimize images\",\n group: \"Media\",\n },\n\n // Storage & Cache\n {\n key: \"s3\",\n label: \"S3 storage\",\n hint: \"AWS S3 for cloud file storage\",\n group: \"Storage & Cache\",\n },\n {\n key: \"redis\",\n label: \"Redis cache\",\n hint: \"Redis driver for the cache layer\",\n group: \"Storage & Cache\",\n },\n\n // Jobs & Messaging\n {\n key: \"scheduler\",\n label: \"Scheduler\",\n hint: \"Background tasks and cron jobs\",\n group: \"Jobs & Messaging\",\n },\n {\n key: \"herald\",\n label: \"Herald (RabbitMQ)\",\n hint: \"Message broker for event-driven architecture\",\n group: \"Jobs & Messaging\",\n },\n\n // Realtime\n {\n key: \"socket\",\n label: \"Socket.IO\",\n hint: \"Realtime websocket server\",\n group: \"Realtime\",\n },\n\n // API Docs (swagger / postman / openapi) — not shipped yet; they will arrive\n // together in the unified @warlock.js/api-docs package. Re-add the selection\n // once that package exists. (Removed so the wizard can't offer unbuilt features.)\n\n // Tooling\n {\n key: \"test\",\n label: \"Testing (Vitest)\",\n hint: \"Vitest + coverage + per-worker DB/cache test setup\",\n group: \"Tooling\",\n },\n];\n\nexport type AiProviderOption = {\n /** Must match a provider key in core's `add` feature map. */\n key: string;\n label: string;\n hint: string;\n};\n\n/**\n * AI providers offered in the dedicated AI step. Selecting any of these pulls\n * the core `@warlock.js/ai` package automatically via the provider's `requires`\n * in core's feature map — the scaffolder never lists `ai` as a standalone pick.\n */\nexport const aiProviders: AiProviderOption[] = [\n { key: \"openai\", label: \"OpenAI\", hint: \"GPT models via the OpenAI API\" },\n { key: \"google\", label: \"Google (Gemini)\", hint: \"Gemini models via Google AI\" },\n { key: \"anthropic\", label: \"Anthropic (Claude)\", hint: \"Claude models via the Anthropic API\" },\n { key: \"bedrock\", label: \"AWS Bedrock\", hint: \"Foundation models via Amazon Bedrock\" },\n { key: \"ollama\", label: \"Ollama\", hint: \"Local models via Ollama\" },\n];\n\n/**\n * Feature options for the multiselect prompt, ordered by group with the group\n * surfaced in the hint (keeps a flat list scannable without group widgets).\n */\nexport function getFeatureOptions() {\n return features.map(feature => ({\n value: feature.key,\n label: feature.label,\n hint: `${feature.group} — ${feature.hint}`,\n }));\n}\n\n/**\n * Keys pre-checked in the feature multiselect.\n */\nexport function getDefaultFeatureKeys(): string[] {\n return features.filter(feature => feature.defaultSelected).map(feature => feature.key);\n}\n\n/**\n * AI provider options for the dedicated AI multiselect step.\n */\nexport function getAiProviderOptions() {\n return aiProviders.map(provider => ({\n value: provider.key,\n label: provider.label,\n hint: provider.hint,\n }));\n}\n\n/**\n * Every selectable key the scaffolder knows about (features + AI providers).\n * Used to validate `--features` / `--ai` flags in non-interactive mode.\n */\nexport function getAllFeatureKeys(): string[] {\n return [...features.map(feature => feature.key), ...aiProviders.map(provider => provider.key)];\n}\n"],"mappings":";;;;;;AAsCA,MAAa,WAA4B;CAEvC;EACE,KAAK;EACL,OAAO;EACP,MAAM;EACN,OAAO;EACP,iBAAiB;CACnB;CACA;EACE,KAAK;EACL,OAAO;EACP,MAAM;EACN,OAAO;CACT;CACA;EACE,KAAK;EACL,OAAO;EACP,MAAM;EACN,OAAO;CACT;CACA;EACE,KAAK;EACL,OAAO;EACP,MAAM;EACN,OAAO;CACT;CAGA;EACE,KAAK;EACL,OAAO;EACP,MAAM;EACN,OAAO;CACT;CAGA;EACE,KAAK;EACL,OAAO;EACP,MAAM;EACN,OAAO;CACT;CACA;EACE,KAAK;EACL,OAAO;EACP,MAAM;EACN,OAAO;CACT;CAGA;EACE,KAAK;EACL,OAAO;EACP,MAAM;EACN,OAAO;CACT;CACA;EACE,KAAK;EACL,OAAO;EACP,MAAM;EACN,OAAO;CACT;CAGA;EACE,KAAK;EACL,OAAO;EACP,MAAM;EACN,OAAO;CACT;CAOA;EACE,KAAK;EACL,OAAO;EACP,MAAM;EACN,OAAO;CACT;AACF;;;;;;AAcA,MAAa,cAAkC;CAC7C;EAAE,KAAK;EAAU,OAAO;EAAU,MAAM;CAAgC;CACxE;EAAE,KAAK;EAAU,OAAO;EAAmB,MAAM;CAA8B;CAC/E;EAAE,KAAK;EAAa,OAAO;EAAsB,MAAM;CAAsC;CAC7F;EAAE,KAAK;EAAW,OAAO;EAAe,MAAM;CAAuC;CACrF;EAAE,KAAK;EAAU,OAAO;EAAU,MAAM;CAA0B;AACpE;;;;;AAMA,SAAgB,oBAAoB;CAClC,OAAO,SAAS,KAAI,aAAY;EAC9B,OAAO,QAAQ;EACf,OAAO,QAAQ;EACf,MAAM,GAAG,QAAQ,MAAM,KAAK,QAAQ;CACtC,EAAE;AACJ;;;;AAKA,SAAgB,wBAAkC;CAChD,OAAO,SAAS,QAAO,YAAW,QAAQ,eAAe,EAAE,KAAI,YAAW,QAAQ,GAAG;AACvF;;;;AAKA,SAAgB,uBAAuB;CACrC,OAAO,YAAY,KAAI,cAAa;EAClC,OAAO,SAAS;EAChB,OAAO,SAAS;EAChB,MAAM,SAAS;CACjB,EAAE;AACJ;;;;;AAMA,SAAgB,oBAA8B;CAC5C,OAAO,CAAC,GAAG,SAAS,KAAI,YAAW,QAAQ,GAAG,GAAG,GAAG,YAAY,KAAI,aAAY,SAAS,GAAG,CAAC;AAC/F"}
|
package/esm/helpers/app.mjs
CHANGED
|
@@ -51,14 +51,26 @@ var App = class {
|
|
|
51
51
|
return this;
|
|
52
52
|
}
|
|
53
53
|
/**
|
|
54
|
-
*
|
|
54
|
+
* Configure the chosen database driver: wire `DB_DRIVER` / `DB_PORT` into
|
|
55
|
+
* `.env`, AND pin the driver's npm package (`mongodb` / `pg`) into the
|
|
56
|
+
* project's `package.json` dependencies.
|
|
55
57
|
*
|
|
56
|
-
* The
|
|
57
|
-
*
|
|
58
|
+
* The dependency is written HERE — before the base `yarn install` — so the
|
|
59
|
+
* driver is pulled deterministically by the very first install. We do NOT
|
|
60
|
+
* rely on the post-copy `warlock add <driver> --no-install` + separate
|
|
61
|
+
* batched install, which can be skipped or fail and leave the driver
|
|
62
|
+
* missing (the "mongodb package is not installed" runtime error).
|
|
58
63
|
*/
|
|
59
64
|
configureDatabaseEnv(driverValue) {
|
|
60
65
|
const driver = getDatabaseDriver(driverValue);
|
|
61
66
|
if (!driver) return this;
|
|
67
|
+
const packageJsonPath = path.resolve(this.path, "package.json");
|
|
68
|
+
const packageJson = getJsonFile(packageJsonPath);
|
|
69
|
+
packageJson.dependencies = packageJson.dependencies ?? {};
|
|
70
|
+
if (!packageJson.dependencies[driver.package]) {
|
|
71
|
+
packageJson.dependencies[driver.package] = driver.packageVersion;
|
|
72
|
+
putJsonFile(packageJsonPath, packageJson);
|
|
73
|
+
}
|
|
62
74
|
let envContent = getFile(this.path + "/.env");
|
|
63
75
|
envContent = envContent.replace(/DB_PORT=\d+/, `DB_PORT=${driver.defaultPort}`);
|
|
64
76
|
if (envContent.includes("DB_DRIVER=")) envContent = envContent.replace(/DB_DRIVER=\w*/, `DB_DRIVER=${driver.value}`);
|
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 * Wire DB_DRIVER and DB_PORT into .env.\r\n *\r\n * The driver's npm package is installed via `warlock add` (single source for\r\n * versions), so this only touches environment configuration — not deps.\r\n */\r\n public configureDatabaseEnv(driverValue: string) {\r\n const driver = getDatabaseDriver(driverValue);\r\n\r\n if (!driver) return this;\r\n\r\n let envContent = getFile(this.path + \"/.env\") as string;\r\n\r\n envContent = envContent.replace(/DB_PORT=\\d+/, `DB_PORT=${driver.defaultPort}`);\r\n\r\n if (envContent.includes(\"DB_DRIVER=\")) {\r\n envContent = envContent.replace(/DB_DRIVER=\\w*/, `DB_DRIVER=${driver.value}`);\r\n } else {\r\n envContent = envContent.replace(\r\n /DB_PORT=\\d+/,\r\n `DB_PORT=${driver.defaultPort}\\nDB_DRIVER=${driver.value}`,\r\n );\r\n }\r\n\r\n putFile(this.path + \"/.env\", envContent);\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Pick the home page implementation based on whether React was selected.\r\n *\r\n * The template ships BOTH a plain JSON controller (`home-page.controller.ts`)\r\n * and a React-rendered page (`home-page.controller.tsx` + `HomePageComponent.tsx`).\r\n * Exactly one survives the scaffold: React projects keep the `.tsx` page (its\r\n * `react`/`react-dom` deps come from the `react` feature), every other project\r\n * keeps the dependency-free JSON controller — so a fresh project never imports\r\n * `react` unless it asked for it.\r\n */\r\n public configureHomePage(useReact: boolean) {\r\n const controllers = this.path + \"/src/app/shared/controllers\";\r\n const components = this.path + \"/src/app/shared/components\";\r\n\r\n const remove = (file: string) => {\r\n if (fileExists(file)) unlinkSync(file);\r\n };\r\n\r\n if (useReact) {\r\n remove(controllers + \"/home-page.controller.ts\");\r\n } else {\r\n remove(controllers + \"/home-page.controller.tsx\");\r\n remove(components + \"/HomePageComponent.tsx\");\r\n }\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Install the selected optional features by delegating to the project's own\r\n * `warlock add`. `--no-install` records every dependency in package.json and\r\n * ejects configs / scripts / setup hooks WITHOUT installing — the caller runs\r\n * one batched install afterwards. Versions come from core's feature map, so\r\n * the scaffolder never duplicates them.\r\n *\r\n * `--no-install` is passed LAST on purpose: the CLI parser treats the\r\n * positional after a bare flag as that flag's value, so it must follow the\r\n * feature list, not precede it.\r\n */\r\n public async installFeatures(features: string[]) {\r\n if (features.length === 0) return true;\r\n\r\n return this.exec(`npx warlock add ${features.join(\" \")} --no-install`);\r\n }\r\n\r\n /**\r\n * Get package json file\r\n */\r\n public get package() {\r\n return this.json(\"package.json\");\r\n }\r\n\r\n public updateDotEnv() {\r\n this.file(\".env\").replaceAll(\"appName\", this.name).save();\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Get env file to update\r\n */\r\n public get env() {\r\n return this.file(\".env\");\r\n }\r\n\r\n public get name() {\r\n return this.app.appName;\r\n }\r\n\r\n public get path() {\r\n return this.app.appPath;\r\n }\r\n\r\n public file(relativePath: string) {\r\n const fullPath = path.resolve(this.path, relativePath);\r\n\r\n if (!this.files[fullPath]) {\r\n this.files[fullPath] = file(fullPath);\r\n }\r\n\r\n return this.files[fullPath];\r\n }\r\n\r\n public json(relativePath: string): JsonFileManager {\r\n const fullPath = path.resolve(this.path, relativePath);\r\n\r\n if (!this.jsonFiles[fullPath]) {\r\n this.jsonFiles[fullPath] = jsonFile(fullPath);\r\n }\r\n\r\n return this.jsonFiles[fullPath];\r\n }\r\n}\r\n\r\nexport function app(app: Application) {\r\n return new App(app);\r\n}\r\n\r\nexport class FileManager {\r\n public content!: string;\r\n public constructor(protected filePath: string) {\r\n this.parseContent();\r\n }\r\n\r\n protected parseContent() {\r\n this.content = getFile(this.filePath) as string;\r\n }\r\n\r\n public replace(search: string, replace: string) {\r\n this.content = this.content.replace(search, replace);\r\n\r\n return this;\r\n }\r\n\r\n public replaceAll(search: string, replace: string) {\r\n this.content = this.content.replaceAll(search, replace);\r\n\r\n return this;\r\n }\r\n\r\n public save() {\r\n putFile(this.filePath, this.content);\r\n }\r\n}\r\n\r\nexport class JsonFileManager extends FileManager {\r\n protected parseContent() {\r\n this.content = getJsonFile(this.filePath);\r\n }\r\n\r\n public save() {\r\n putJsonFile(this.filePath, this.content);\r\n }\r\n\r\n public has(key: string) {\r\n return this.content[key] !== undefined;\r\n }\r\n\r\n public replace(key: string, value: any) {\r\n this.content[key] = value;\r\n\r\n return this;\r\n }\r\n\r\n public replaceAll(key: string, value: any) {\r\n const contentAsString = JSON.stringify(this.content);\r\n\r\n this.content = JSON.parse(contentAsString.replaceAll(key, value));\r\n\r\n return this;\r\n }\r\n}\r\n\r\nexport function file(path: string) {\r\n return new FileManager(path);\r\n}\r\n\r\nexport function jsonFile(path: string) {\r\n return new JsonFileManager(path);\r\n}\r\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"app.mjs","names":[],"sources":["../../../../../../@warlock.js/create-warlock/src/helpers/app.ts"],"sourcesContent":["import {\r\n copyDirectory,\r\n copyFile,\r\n fileExists,\r\n getFile,\r\n getJsonFile,\r\n putFile,\r\n putJsonFile,\r\n renameFile,\r\n} from \"@warlock.js/fs\";\r\nimport { unlinkSync } from \"node:fs\";\r\nimport path from \"path\";\r\nimport { AppOptions, Application } from \"../commands/create-new-app/types\";\r\nimport { getDatabaseDriver } from \"../features/database-drivers\";\r\nimport { executeCommand, runCommand } from \"./exec\";\r\nimport { getPackageManager } from \"./package-manager\";\r\nimport { packageRoot, Template, template } from \"./paths\";\r\n\r\nexport class App {\r\n /**\r\n * Resolved files\r\n */\r\n protected files: Record<string, FileManager> = {};\r\n\r\n /**\r\n * Resolved JSON files\r\n */\r\n protected jsonFiles: Record<string, JsonFileManager> = {};\r\n\r\n public isInstalled = false;\r\n\r\n public constructor(protected app: Application) {}\r\n\r\n public get options(): AppOptions {\r\n return this.app.options;\r\n }\r\n\r\n public use(templateName: Template) {\r\n copyDirectory(template(templateName), this.path);\r\n\r\n if (fileExists(this.path + \"/.env.example\")) {\r\n copyFile(this.path + \"/.env.example\", this.path + \"/.env\");\r\n }\r\n\r\n renameFile(this.path + \"/_.gitignore\", this.path + \"/.gitignore\");\r\n\r\n return this;\r\n }\r\n\r\n public init() {\r\n return this;\r\n }\r\n\r\n public terminate() {\r\n // No longer using outro, using showSuccessScreen instead\r\n }\r\n\r\n public install() {\r\n return runCommand(getPackageManager(), [\"install\"], this.path);\r\n }\r\n\r\n public async exec(command: string) {\r\n const [commandName, ...optionsList] = command.split(\" \");\r\n return await executeCommand(commandName, optionsList, this.path);\r\n }\r\n\r\n public async git() {\r\n const { initializeGitRepository } = await import(\r\n \"./project-builder-helpers\"\r\n );\r\n return await initializeGitRepository(this.path);\r\n }\r\n\r\n public updatePackageJson() {\r\n const pkg = this.package\r\n .replace(\"name\", this.name.replaceAll(\"/\", \"-\"))\r\n .replaceAll(\"yarn\", getPackageManager());\r\n\r\n // Pin every @warlock.js/* dependency to THIS create-warlock release version\r\n // so a scaffolded project always matches the framework version it was created\r\n // with. create-warlock and the framework ship in lockstep, so the scaffolder's\r\n // own version is the single source of truth (the template's hardcoded versions\r\n // are irrelevant — they get overwritten here).\r\n const warlockVersion: string = getJsonFile(packageRoot(\"package.json\")).version;\r\n const content: any = pkg.content;\r\n\r\n for (const field of [\"dependencies\", \"devDependencies\"] as const) {\r\n const deps = content[field] as Record<string, string> | undefined;\r\n if (!deps) continue;\r\n\r\n for (const dependency of Object.keys(deps)) {\r\n if (dependency.startsWith(\"@warlock.js/\")) {\r\n deps[dependency] = warlockVersion;\r\n }\r\n }\r\n }\r\n\r\n pkg.save();\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * 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":"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"}
|