create-warlock 5.10.0 → 5.12.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 +16 -0
- package/README.md +38 -5
- package/esm/commands/create-new-app/index.mjs +102 -9
- package/esm/commands/create-new-app/index.mjs.map +1 -1
- package/esm/commands/create-new-app/types.d.mts +8 -3
- package/esm/commands/create-warlock-app/index.mjs +12 -1
- package/esm/commands/create-warlock-app/index.mjs.map +1 -1
- package/esm/features/agent-kit-targets.mjs +134 -0
- package/esm/features/agent-kit-targets.mjs.map +1 -0
- package/esm/flags/definitions.mjs +89 -0
- package/esm/flags/definitions.mjs.map +1 -0
- package/esm/helpers/app.mjs +21 -0
- package/esm/helpers/app.mjs.map +1 -1
- package/esm/index.mjs +15 -23
- package/esm/index.mjs.map +1 -1
- package/esm/prompts/ask-stack.mjs +35 -0
- package/esm/prompts/ask-stack.mjs.map +1 -0
- package/esm/summary.mjs +25 -0
- package/esm/summary.mjs.map +1 -0
- package/llms-full.txt +65 -38
- package/llms.txt +1 -1
- package/package.json +2 -2
- package/skills/create-a-warlock-project/SKILL.md +65 -38
- package/templates/warlock/AGENTS.md +1 -1
- package/templates/warlock/docs/new-module.md +2 -2
- package/templates/warlock/skills/api-design/SKILL.md +2 -2
- package/templates/warlock/skills/security-baseline/SKILL.md +1 -1
- package/templates/warlock/src/app/auth/controllers/logout-all.controller.ts +1 -1
- package/templates/warlock/src/app/auth/controllers/logout.controller.ts +3 -3
- package/templates/warlock/src/app/auth/controllers/me.controller.ts +1 -1
- package/templates/warlock/src/app/auth/requests/guarded.request.ts +2 -2
- package/templates/warlock/src/app/posts/controllers/create-new-post.controller.ts +1 -1
- package/templates/warlock/src/app/shared/utils/router.ts +12 -10
- package/templates/warlock/src/app/users/models/user/user.model.ts +8 -3
- package/templates/warlock/src/app/users/services/login-social.ts +4 -3
- package/templates/warlock/src/typings.d.ts +14 -8
- package/templates/warlock/src/web/home/components/contact-section.tsx +31 -14
- package/templates/warlock/src/web/home/hooks/use-contact-form.ts +18 -7
- package/templates/warlock/src/web/home/index.page.tsx +1 -1
- package/templates/warlock/src/web/root.tsx +5 -2
- package/templates/warlock/src/web/home/hooks/use-locale-switcher.ts +0 -35
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-kit-targets.mjs","names":[],"sources":["../../../../../../create-warlock/src/features/agent-kit-targets.ts"],"sourcesContent":["import { mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { homedir, tmpdir } from \"node:os\";\nimport path from \"node:path\";\n\nconst requireFromHere = createRequire(import.meta.url);\n\n/**\n * `agent-kit` target selection for the scaffolder's `--agents` flag.\n *\n * The valid target list is NOT hardcoded here as a literal source of truth —\n * it is derived, in order of preference, from:\n *\n * 1. The `@mongez/agent-kit` package installed alongside `create-warlock`\n * itself, when its own metadata exposes a target list.\n * 2. A runtime fetch of https://mongez.js.org/agent-kit/llms.txt, cached\n * locally so repeated runs do not refetch.\n *\n * The one exception is `claude`, the default target: it MUST work fully\n * offline, so it is a tiny built-in fallback rather than something that ever\n * depends on a successful network call.\n */\n\n/** The only target guaranteed valid with no network access and no cache. */\nexport const BUILTIN_AGENT_KIT_TARGETS: readonly string[] = [\"claude\"];\n\n/** The default `--agents` value when the flag is not passed. */\nexport const DEFAULT_AGENT_KIT_TARGET = \"claude\";\n\nconst AGENT_KIT_LLMS_URL = \"https://mongez.js.org/agent-kit/llms.txt\";\n\n/** Local cache of the last successfully fetched/derived target list. */\nfunction cacheFilePath(): string {\n const base = process.env.XDG_CACHE_HOME ?? path.join(homedir(), \".cache\");\n return path.join(base, \"create-warlock\", \"agent-kit-targets.json\");\n}\n\nfunction readCache(): string[] | undefined {\n try {\n const raw = readFileSync(cacheFilePath(), \"utf8\");\n const parsed = JSON.parse(raw) as { targets?: unknown };\n if (Array.isArray(parsed.targets)) {\n return parsed.targets.filter(\n (value): value is string => typeof value === \"string\",\n );\n }\n return undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction writeCache(targets: string[]): void {\n try {\n const filePath = cacheFilePath();\n mkdirSync(path.dirname(filePath), { recursive: true });\n writeFileSync(\n filePath,\n JSON.stringify({ targets, fetchedAt: new Date().toISOString() }),\n \"utf8\",\n );\n } catch {\n // The cache is a best-effort convenience — a read-only filesystem must\n // never turn a successful fetch into a failed run.\n }\n}\n\n/**\n * Best-effort read of the installed `@mongez/agent-kit` package's own\n * declared target list, when it publishes one. Returns `undefined` (never\n * throws) when the package is not installed or exposes no such metadata —\n * the caller falls back to the network source.\n */\nfunction readInstalledPackageTargets(): string[] | undefined {\n for (const base of [process.cwd(), tmpdir()]) {\n try {\n const packageJsonPath = requireFromHere.resolve(\n \"@mongez/agent-kit/package.json\",\n { paths: [base] },\n );\n const pkg = JSON.parse(readFileSync(packageJsonPath, \"utf8\")) as {\n agentKit?: { targets?: unknown };\n };\n if (Array.isArray(pkg.agentKit?.targets)) {\n return pkg.agentKit.targets.filter(\n (value): value is string => typeof value === \"string\",\n );\n }\n } catch {\n // Not installed, or no metadata — try the next base / fall through.\n }\n }\n return undefined;\n}\n\n/**\n * Pull the agent-integrations doc's raw `--target <slug>` occurrences out of\n * `agent-kit`'s own llms.txt index (following the link it documents for\n * itself), so the concrete slug list is parsed from the source rather than\n * retyped here.\n */\nexport async function fetchRemoteAgentKitTargets(\n fetchImpl: typeof fetch = fetch,\n): Promise<string[]> {\n const indexResponse = await fetchImpl(AGENT_KIT_LLMS_URL);\n if (!indexResponse.ok) {\n throw new Error(\n `agent-kit llms.txt request failed with HTTP ${indexResponse.status}`,\n );\n }\n const index = await indexResponse.text();\n\n const rawLinkMatch = index.match(\n /\\[Agent integrations\\]\\((https:\\/\\/[^)]+agent-integrations[^)]*)\\)/,\n );\n\n const docUrl = rawLinkMatch?.[1]\n ?.replace(\"github.com\", \"raw.githubusercontent.com\")\n .replace(\"/blob/\", \"/\");\n\n const targets = new Set<string>();\n\n if (docUrl) {\n const docResponse = await fetchImpl(docUrl);\n if (docResponse.ok) {\n const doc = await docResponse.text();\n for (const match of doc.matchAll(/--target[= ]([a-z][a-z0-9-]*)/gi)) {\n const target = match[1];\n\n if (target) targets.add(target.toLowerCase());\n }\n }\n }\n\n if (targets.size === 0) {\n throw new Error(\n \"Could not parse any agent-kit target names out of the fetched docs\",\n );\n }\n\n return [...targets];\n}\n\n/**\n * Resolve the full list of valid `agent-kit` targets, trying the installed\n * package first, then the cache-backed network fetch.\n *\n * Throws when neither source succeeds — the caller only reaches this for an\n * explicitly requested NON-default target, where a silent fallback to a\n * stale or guessed list would be worse than a clear failure.\n */\nexport async function getValidAgentKitTargets(\n fetchImpl: typeof fetch = fetch,\n): Promise<string[]> {\n const installed = readInstalledPackageTargets();\n if (installed && installed.length > 0) {\n return dedupe([...BUILTIN_AGENT_KIT_TARGETS, ...installed]);\n }\n\n try {\n const fetched = await fetchRemoteAgentKitTargets(fetchImpl);\n writeCache(fetched);\n return dedupe([...BUILTIN_AGENT_KIT_TARGETS, ...fetched]);\n } catch (fetchError) {\n const cached = readCache();\n if (cached && cached.length > 0) {\n return dedupe([...BUILTIN_AGENT_KIT_TARGETS, ...cached]);\n }\n throw new Error(\n `Could not determine the valid agent-kit targets (fetch failed and no cache exists): ${\n (fetchError as Error).message\n }`,\n );\n }\n}\n\nfunction dedupe(values: string[]): string[] {\n return [...new Set(values)];\n}\n\n/**\n * Resolve and validate the `--agents` selection.\n *\n * An unset or empty selection resolves to `[DEFAULT_AGENT_KIT_TARGET]`\n * without touching the disk or the network. A selection made entirely of\n * built-in targets (today, just `claude`) is likewise resolved offline. Only\n * an explicit NON-default target triggers {@link getValidAgentKitTargets},\n * and an invalid one throws, naming every valid target it found.\n */\nexport async function resolveAgentTargets(\n requested: string[] | undefined,\n fetchImpl: typeof fetch = fetch,\n): Promise<string[]> {\n const targets =\n requested && requested.length > 0\n ? requested\n : [DEFAULT_AGENT_KIT_TARGET];\n\n const needsValidation = targets.some(\n target => !BUILTIN_AGENT_KIT_TARGETS.includes(target),\n );\n\n if (!needsValidation) {\n return targets;\n }\n\n const valid = await getValidAgentKitTargets(fetchImpl);\n const invalid = targets.filter(target => !valid.includes(target));\n\n if (invalid.length > 0) {\n throw new Error(\n `Unknown agent-kit target(s): ${invalid.join(\", \")} — valid targets: ${valid\n .slice()\n .sort()\n .join(\", \")}`,\n );\n }\n\n return targets;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
//#region ../create-warlock/src/flags/definitions.ts
|
|
2
|
+
const FLAG_DEFINITIONS = [
|
|
3
|
+
{
|
|
4
|
+
usage: "--name=<name>",
|
|
5
|
+
description: "Project name (or pass it as the first positional arg)",
|
|
6
|
+
defaultValue: "required — prompted if a TTY is available, otherwise fails naming this flag"
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
usage: "--stack=<api|web>",
|
|
10
|
+
description: "API-only or full-stack web — the one structural choice that changes the app's shape",
|
|
11
|
+
defaultValue: "api"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
usage: "--db=<driver> / --no-db",
|
|
15
|
+
description: "Database driver (e.g. postgres, mongodb), or skip a database entirely",
|
|
16
|
+
defaultValue: "mongodb"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
usage: "--features=<list>",
|
|
20
|
+
description: "Comma-separated feature keys (e.g. test,herald)",
|
|
21
|
+
defaultValue: "none (or [\"web\"] when --stack=web and --features is not given)"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
usage: "--ai=<list>",
|
|
25
|
+
description: "Comma-separated AI provider keys (e.g. openai,anthropic)",
|
|
26
|
+
defaultValue: "none"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
usage: "--pm=<manager>",
|
|
30
|
+
description: "Package manager to use (npm, yarn, pnpm, bun)",
|
|
31
|
+
defaultValue: "inferred from the invoking agent (npm_config_user_agent) or the system"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
usage: "--agents=<list>",
|
|
35
|
+
description: "Comma-separated agent-kit targets to derive docs/skills for",
|
|
36
|
+
defaultValue: "claude"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
usage: "--git / --no-git",
|
|
40
|
+
description: "Force-enable or force-disable git initialization",
|
|
41
|
+
defaultValue: "false"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
usage: "--jwt / --no-jwt",
|
|
45
|
+
description: "Force-enable or force-disable JWT secret generation",
|
|
46
|
+
defaultValue: "false"
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
usage: "-y, --yes",
|
|
50
|
+
description: "Skip every prompt and accept defaults for anything unset",
|
|
51
|
+
defaultValue: "false"
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
usage: "--interactive, --customize",
|
|
55
|
+
description: "Restore the full long-form interactive prompt flow",
|
|
56
|
+
defaultValue: "false"
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
usage: "-h, --help",
|
|
60
|
+
description: "Show this help message and exit",
|
|
61
|
+
defaultValue: "n/a"
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
usage: "-v, --version",
|
|
65
|
+
description: "Show the installed create-warlock version and exit",
|
|
66
|
+
defaultValue: "n/a"
|
|
67
|
+
}
|
|
68
|
+
];
|
|
69
|
+
/** Render the full `--help` text, one line per flag with its default. */
|
|
70
|
+
function buildHelpText() {
|
|
71
|
+
return `
|
|
72
|
+
create-warlock — scaffold a new Warlock.js project
|
|
73
|
+
|
|
74
|
+
Usage
|
|
75
|
+
$ create-warlock [project-name] [options]
|
|
76
|
+
|
|
77
|
+
Options
|
|
78
|
+
${FLAG_DEFINITIONS.map(({ usage, description, defaultValue }) => {
|
|
79
|
+
return ` ${usage.padEnd(28)}${description} (default: ${defaultValue})`;
|
|
80
|
+
}).join("\n")}
|
|
81
|
+
|
|
82
|
+
Example
|
|
83
|
+
$ create-warlock my-app --db=postgres --features=test,herald --yes
|
|
84
|
+
`;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
//#endregion
|
|
88
|
+
export { buildHelpText };
|
|
89
|
+
//# sourceMappingURL=definitions.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"definitions.mjs","names":[],"sources":["../../../../../../create-warlock/src/flags/definitions.ts"],"sourcesContent":["/**\n * Single source of truth for every CLI flag's help text and default, so\n * `--help` can never drift out of sync with what the parser actually\n * understands (requirement: `--help` lists every flag together with its\n * default value).\n */\nexport type FlagDefinition = {\n /** How the flag reads on the command line, e.g. `--db=<driver>`. */\n usage: string;\n description: string;\n /** Human-readable default, or what happens when it is left unanswered. */\n defaultValue: string;\n};\n\nexport const FLAG_DEFINITIONS: FlagDefinition[] = [\n {\n usage: \"--name=<name>\",\n description: \"Project name (or pass it as the first positional arg)\",\n defaultValue: \"required — prompted if a TTY is available, otherwise fails naming this flag\",\n },\n {\n usage: \"--stack=<api|web>\",\n description:\n \"API-only or full-stack web — the one structural choice that changes the app's shape\",\n defaultValue: \"api\",\n },\n {\n usage: \"--db=<driver> / --no-db\",\n description: \"Database driver (e.g. postgres, mongodb), or skip a database entirely\",\n defaultValue: \"mongodb\",\n },\n {\n usage: \"--features=<list>\",\n description: \"Comma-separated feature keys (e.g. test,herald)\",\n defaultValue: \"none (or [\\\"web\\\"] when --stack=web and --features is not given)\",\n },\n {\n usage: \"--ai=<list>\",\n description: \"Comma-separated AI provider keys (e.g. openai,anthropic)\",\n defaultValue: \"none\",\n },\n {\n usage: \"--pm=<manager>\",\n description: \"Package manager to use (npm, yarn, pnpm, bun)\",\n defaultValue: \"inferred from the invoking agent (npm_config_user_agent) or the system\",\n },\n {\n usage: \"--agents=<list>\",\n description: \"Comma-separated agent-kit targets to derive docs/skills for\",\n defaultValue: \"claude\",\n },\n {\n usage: \"--git / --no-git\",\n description: \"Force-enable or force-disable git initialization\",\n defaultValue: \"false\",\n },\n {\n usage: \"--jwt / --no-jwt\",\n description: \"Force-enable or force-disable JWT secret generation\",\n defaultValue: \"false\",\n },\n {\n usage: \"-y, --yes\",\n description: \"Skip every prompt and accept defaults for anything unset\",\n defaultValue: \"false\",\n },\n {\n usage: \"--interactive, --customize\",\n description: \"Restore the full long-form interactive prompt flow\",\n defaultValue: \"false\",\n },\n {\n usage: \"-h, --help\",\n description: \"Show this help message and exit\",\n defaultValue: \"n/a\",\n },\n {\n usage: \"-v, --version\",\n description: \"Show the installed create-warlock version and exit\",\n defaultValue: \"n/a\",\n },\n];\n\n/** Render the full `--help` text, one line per flag with its default. */\nexport function buildHelpText(): string {\n const rows = FLAG_DEFINITIONS.map(({ usage, description, defaultValue }) => {\n const label = usage.padEnd(28);\n return ` ${label}${description} (default: ${defaultValue})`;\n }).join(\"\\n\");\n\n return `\n create-warlock — scaffold a new Warlock.js project\n\n Usage\n $ create-warlock [project-name] [options]\n\n Options\n${rows}\n\n Example\n $ create-warlock my-app --db=postgres --features=test,herald --yes\n`;\n}\n"],"mappings":";AAcA,MAAa,mBAAqC;CAChD;EACE,OAAO;EACP,aAAa;EACb,cAAc;CAChB;CACA;EACE,OAAO;EACP,aACE;EACF,cAAc;CAChB;CACA;EACE,OAAO;EACP,aAAa;EACb,cAAc;CAChB;CACA;EACE,OAAO;EACP,aAAa;EACb,cAAc;CAChB;CACA;EACE,OAAO;EACP,aAAa;EACb,cAAc;CAChB;CACA;EACE,OAAO;EACP,aAAa;EACb,cAAc;CAChB;CACA;EACE,OAAO;EACP,aAAa;EACb,cAAc;CAChB;CACA;EACE,OAAO;EACP,aAAa;EACb,cAAc;CAChB;CACA;EACE,OAAO;EACP,aAAa;EACb,cAAc;CAChB;CACA;EACE,OAAO;EACP,aAAa;EACb,cAAc;CAChB;CACA;EACE,OAAO;EACP,aAAa;EACb,cAAc;CAChB;CACA;EACE,OAAO;EACP,aAAa;EACb,cAAc;CAChB;CACA;EACE,OAAO;EACP,aAAa;EACb,cAAc;CAChB;AACF;;AAGA,SAAgB,gBAAwB;CAMtC,OAAO;;;;;;;EALM,iBAAiB,KAAK,EAAE,OAAO,aAAa,mBAAmB;EAE1E,OAAO,OADO,MAAM,OAAO,EACT,IAAI,YAAY,aAAa,aAAa;CAC9D,CAAC,CAAC,CAAC,KAAK,IASL,EAAE;;;;;AAKP"}
|
package/esm/helpers/app.mjs
CHANGED
|
@@ -213,6 +213,27 @@ var App = class {
|
|
|
213
213
|
return this;
|
|
214
214
|
}
|
|
215
215
|
/**
|
|
216
|
+
* Record non-default `agent-kit` targets into the generated `package.json`'s
|
|
217
|
+
* `agentKit.targets` block.
|
|
218
|
+
*
|
|
219
|
+
* The template's own `postinstall` (`agent-kit init && agent-kit sync`)
|
|
220
|
+
* already targets `claude` with no flags, so the single-target default case
|
|
221
|
+
* needs no template edit at all — this only writes when the selection is
|
|
222
|
+
* anything else, keeping the scaffold diff-free for the common path.
|
|
223
|
+
*/
|
|
224
|
+
configureAgentKitTargets(agents) {
|
|
225
|
+
if (agents.length === 1 && agents[0] === "claude") return this;
|
|
226
|
+
const packageJsonPath = path.resolve(this.path, "package.json");
|
|
227
|
+
if (!fileExists(packageJsonPath)) return this;
|
|
228
|
+
const packageJson = getJsonFile(packageJsonPath);
|
|
229
|
+
packageJson.agentKit = {
|
|
230
|
+
...packageJson.agentKit,
|
|
231
|
+
targets: agents
|
|
232
|
+
};
|
|
233
|
+
putJsonFile(packageJsonPath, packageJson);
|
|
234
|
+
return this;
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
216
237
|
* Install the selected optional features by delegating to the project's own
|
|
217
238
|
* `warlock add`. `--no-install` records every dependency in package.json and
|
|
218
239
|
* ejects configs / scripts / setup hooks WITHOUT installing — the caller runs
|
package/esm/helpers/app.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app.mjs","names":[],"sources":["../../../../../../create-warlock/src/helpers/app.ts"],"sourcesContent":["import {\r\n copyDirectory,\r\n copyFile,\r\n fileExists,\r\n getFile,\r\n getJsonFile,\r\n putFile,\r\n putJsonFile,\r\n renameFile,\r\n} from \"@warlock.js/fs\";\r\nimport { existsSync, rmSync, unlinkSync } from \"node:fs\";\r\nimport path from \"path\";\r\nimport { Application, AppOptions } from \"../commands/create-new-app/types\";\r\nimport { getDatabaseDriver } from \"../features/database-drivers\";\r\nimport { executeCommand, runCommand } from \"./exec\";\r\nimport { insertImportInSortedPosition } from \"./insert-import\";\r\nimport { getPackageManager } from \"./package-manager\";\r\nimport { packageRoot, Template, template } from \"./paths\";\r\nimport { fallbackRange } from \"./warlock-versions\";\r\n\r\n/**\r\n * Environment for every install the scaffolder spawns.\r\n *\r\n * A shell that exports `NODE_ENV=production` makes the package managers skip\r\n * every devDependency — and still exit 0. The scaffolded project needs its dev\r\n * toolchain (typescript, vitest, eslint) to be usable at all, so the install\r\n * child is pinned to `development` regardless of the ambient environment. A\r\n * silently incomplete install that reports success is the exact failure mode\r\n * this file exists to prevent.\r\n */\r\nexport function installEnvironment(): NodeJS.ProcessEnv {\r\n return { NODE_ENV: \"development\" };\r\n}\r\n\r\nexport class App {\r\n /**\r\n * Resolved files\r\n */\r\n protected files: Record<string, FileManager> = {};\r\n\r\n /**\r\n * Resolved JSON files\r\n */\r\n protected jsonFiles: Record<string, JsonFileManager> = {};\r\n\r\n public isInstalled = false;\r\n\r\n public constructor(protected app: Application) {}\r\n\r\n public get options(): AppOptions {\r\n return this.app.options;\r\n }\r\n\r\n public use(templateName: Template) {\r\n copyDirectory(template(templateName), this.path);\r\n\r\n if (fileExists(this.path + \"/.env.example\")) {\r\n copyFile(this.path + \"/.env.example\", this.path + \"/.env\");\r\n }\r\n\r\n renameFile(this.path + \"/_.gitignore\", this.path + \"/.gitignore\");\r\n\r\n return this;\r\n }\r\n\r\n public init() {\r\n return this;\r\n }\r\n\r\n public terminate() {\r\n // No longer using outro, using showSuccessScreen instead\r\n }\r\n\r\n public install() {\r\n return runCommand(getPackageManager(), [\"install\"], this.path, {\r\n env: installEnvironment(),\r\n });\r\n }\r\n\r\n public async exec(command: string) {\r\n const [commandName, ...optionsList] = command.split(\" \");\r\n return await executeCommand(commandName, optionsList, this.path);\r\n }\r\n\r\n public async git() {\r\n const { initializeGitRepository } =\r\n await import(\"./project-builder-helpers\");\r\n return await initializeGitRepository(this.path);\r\n }\r\n\r\n /**\r\n * Write the project's `package.json`: the project name, the chosen package\r\n * manager, and the version of every `@warlock.js/*` dependency.\r\n *\r\n * `versions` comes from {@link resolveWarlockVersions} — versions the\r\n * registry has confirmed exist. It is optional because the fluent chain is\r\n * synchronous; without it every sibling gets the caret range floored to the\r\n * scaffolder's major, which is always satisfiable by a published release.\r\n *\r\n * What it must NEVER do again is stamp the scaffolder's own version blind:\r\n * the release tooling bumps that version on every build, published or not,\r\n * so an unverified pin resolves to nothing and the install dies with ETARGET.\r\n */\r\n public updatePackageJson(versions: Record<string, string> = {}) {\r\n const packageManager = getPackageManager();\r\n\r\n const pkg = this.package.replace(\"name\", this.name.replaceAll(\"/\", \"-\"));\r\n\r\n const content: any = pkg.content;\r\n\r\n // Substitute the chosen package manager ONLY into the fields the template\r\n // writes it into literally — the `serve` script — by path, never with a\r\n // blanket substring replace over the serialized JSON. A raw\r\n // `replaceAll(\"yarn\", pm)` rewrites every occurrence of the substring\r\n // \"yarn\" anywhere in the document, so a project named `my-yarn-app`, or any\r\n // dependency/path containing \"yarn\", is silently corrupted (`--pm=npm`\r\n // turned `my-yarn-app` into `my-npm-app`). Field-scoped rewriting is also\r\n // order-independent: the name lives in `content.name` and this token lives\r\n // in `scripts.serve`, a disjoint field that cannot collide with the name\r\n // substitution above regardless of which runs first.\r\n if (typeof content.scripts?.serve === \"string\") {\r\n content.scripts.serve = content.scripts.serve.replaceAll(\r\n \"yarn\",\r\n packageManager,\r\n );\r\n }\r\n\r\n const warlockVersion: string = (\r\n getJsonFile(packageRoot(\"package.json\")) as { version: string }\r\n ).version;\r\n const defaultRange = fallbackRange(warlockVersion);\r\n\r\n for (const field of [\"dependencies\", \"devDependencies\"] as const) {\r\n const deps = content[field] as Record<string, string> | undefined;\r\n if (!deps) continue;\r\n\r\n for (const dependency of Object.keys(deps)) {\r\n if (dependency.startsWith(\"@warlock.js/\")) {\r\n deps[dependency] = versions[dependency] ?? defaultRange;\r\n }\r\n }\r\n }\r\n\r\n pkg.save();\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Configure the chosen database driver: wire `DB_DRIVER` / `DB_PORT` into\r\n * `.env`, AND pin the driver's npm package (`mongodb` / `pg`) into the\r\n * project's `package.json` dependencies.\r\n *\r\n * The dependency is written HERE — before the base `yarn install` — so the\r\n * driver is pulled deterministically by the very first install. We do NOT\r\n * rely on the post-copy `warlock add <driver> --no-install` + separate\r\n * batched install, which can be skipped or fail and leave the driver\r\n * missing (the \"mongodb package is not installed\" runtime error).\r\n */\r\n public configureDatabaseEnv(driverValue: string) {\r\n const driver = getDatabaseDriver(driverValue);\r\n\r\n if (!driver) return this;\r\n\r\n // Pin the driver package into dependencies (idempotent — never downgrade).\r\n const packageJsonPath = path.resolve(this.path, \"package.json\");\r\n const packageJson = getJsonFile(packageJsonPath) as {\r\n dependencies?: Record<string, string>;\r\n };\r\n packageJson.dependencies = packageJson.dependencies ?? {};\r\n if (!packageJson.dependencies[driver.package]) {\r\n packageJson.dependencies[driver.package] = driver.packageVersion;\r\n putJsonFile(packageJsonPath, packageJson);\r\n }\r\n\r\n let envContent = getFile(this.path + \"/.env\") as string;\r\n\r\n envContent = envContent.replace(\r\n /DB_PORT=\\d+/,\r\n `DB_PORT=${driver.defaultPort}`,\r\n );\r\n\r\n if (envContent.includes(\"DB_DRIVER=\")) {\r\n envContent = envContent.replace(\r\n /DB_DRIVER=\\w*/,\r\n `DB_DRIVER=${driver.value}`,\r\n );\r\n } else {\r\n envContent = envContent.replace(\r\n /DB_PORT=\\d+/,\r\n `DB_PORT=${driver.defaultPort}\\nDB_DRIVER=${driver.value}`,\r\n );\r\n }\r\n\r\n putFile(this.path + \"/.env\", envContent);\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Write the correct `src/config/database.ts` for the chosen driver and remove\r\n * every per-driver variant so the scaffold ships exactly one config file.\r\n *\r\n * The template ships a Mongo-shaped `database.ts` (the default) plus a\r\n * `database.postgres.ts` variant. Mongo keeps the default in place; Postgres\r\n * overwrites `database.ts` with the Postgres-shaped variant (typed\r\n * `ConnectionOptions`, `driver: \"postgres\"`, port 5432, no Mongo-only keys —\r\n * no `authSource`, `driverOptions`, `replicaSet`, `randomIncrement` /\r\n * `initialId`). Either way the `.postgres` variant file is deleted afterwards\r\n * so no leftover config ships in the generated project.\r\n *\r\n * For Postgres it also corrects the working `.env`: the Mongo template ships\r\n * `DB_AUTH=admin`, an `authSource` value Postgres ignores and its config no\r\n * longer reads, so the line is dropped to keep `.env` and the config in sync.\r\n * The port is rewritten by {@link configureDatabaseEnv} (driver defaultPort),\r\n * which runs alongside this in the same branch.\r\n */\r\n public configureDatabaseConfig(driverValue: string) {\r\n const configDir = path.resolve(this.path, \"src/config\");\r\n const databasePath = path.resolve(configDir, \"database.ts\");\r\n const postgresVariantPath = path.resolve(configDir, \"database.postgres.ts\");\r\n\r\n if (driverValue === \"postgres\" && fileExists(postgresVariantPath)) {\r\n putFile(databasePath, getFile(postgresVariantPath) as string);\r\n\r\n const envPath = this.path + \"/.env\";\r\n\r\n if (fileExists(envPath)) {\r\n const envContent = getFile(envPath) as string;\r\n // Drop the Mongo-only DB_AUTH line entirely (its whole line, so no blank\r\n // gap is left behind). Postgres has no authSource concept.\r\n putFile(envPath, envContent.replace(/^DB_AUTH=.*\\r?\\n?/m, \"\"));\r\n }\r\n }\r\n\r\n // Whatever the driver, no per-driver variant may survive into the scaffold.\r\n if (fileExists(postgresVariantPath)) {\r\n unlinkSync(postgresVariantPath);\r\n }\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Remove the database layer for a \"no database\" scaffold.\r\n *\r\n * Deletes `src/config/database.ts` (and a `.tsx` / per-driver variant if\r\n * present) from the freshly-copied template. The framework's database\r\n * connector is config-gated on that file — with it gone,\r\n * `config.get(\"database\")` is undefined and the connector no-ops, so the app\r\n * boots with no database wired and no driver package pulled. The `DB_*` lines\r\n * in `.env` are left in place (harmless: no config reads them) as a ready\r\n * template for adding a database back later.\r\n */\r\n public removeDatabaseConfig() {\r\n const configDir = path.resolve(this.path, \"src/config\");\r\n\r\n for (const fileName of [\r\n \"database.ts\",\r\n \"database.tsx\",\r\n \"database.postgres.ts\",\r\n ]) {\r\n const filePath = path.resolve(configDir, fileName);\r\n\r\n if (fileExists(filePath)) {\r\n unlinkSync(filePath);\r\n }\r\n }\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Configure the web starter only when the web feature owns `/`.\r\n *\r\n * A dependency-free HTTP response is retained without web; otherwise the\r\n * SSR page loader owns the route.\r\n * Exactly one survives the scaffold: web projects keep the SSR page and API\r\n * example; every other project keeps the dependency-free JSON controller.\r\n */\r\n public configureWebStarter(useWeb: boolean) {\r\n const remove = (relativePath: string) => {\r\n const target = path.resolve(this.path, relativePath);\r\n if (existsSync(target)) rmSync(target, { recursive: true, force: true });\r\n };\r\n\r\n if (!useWeb) {\r\n for (const entry of [\r\n \"src/web\",\r\n \"src/app/contact\",\r\n \"src/app/locale\",\r\n \"src/shared/contact.schema.ts\",\r\n \"src/shared/locale.schema.ts\",\r\n \"src/shared/locales.ts\",\r\n \"postcss.config.mjs\",\r\n ]) {\r\n remove(entry);\r\n }\r\n\r\n const tsconfigPath = path.resolve(this.path, \"tsconfig.json\");\r\n const tsconfig = getFile(tsconfigPath) as string;\r\n putFile(\r\n tsconfigPath,\r\n tsconfig\r\n .replace(' \"web/*\": [\"./src/web/*\"],\\n', \"\")\r\n .replace(' \"@shared/*\": [\"./src/shared/*\"]\\n', \"\"),\r\n );\r\n\r\n return this;\r\n }\r\n\r\n // The SSR page owns `/`; its loader still uses src/app/home/services.\r\n remove(\"src/app/home/controllers/home-page.controller.ts\");\r\n remove(\"src/app/home/routes.ts\");\r\n\r\n const packageJsonPath = path.resolve(this.path, \"package.json\");\r\n const packageJson = getJsonFile(packageJsonPath) as {\r\n dependencies?: Record<string, string>;\r\n devDependencies?: Record<string, string>;\r\n };\r\n packageJson.dependencies = {\r\n ...packageJson.dependencies,\r\n \"@mongez/http\": \"^3.5.0\",\r\n \"@mongez/react-form\": \"^4.0.0\",\r\n };\r\n packageJson.devDependencies = {\r\n ...packageJson.devDependencies,\r\n \"@tailwindcss/postcss\": \"^4.1.16\",\r\n tailwindcss: \"^4.1.16\",\r\n };\r\n putJsonFile(packageJsonPath, packageJson);\r\n\r\n const configPath = path.resolve(this.path, \"warlock.config.ts\");\r\n const config = getFile(configPath) as string;\r\n if (!config.includes('\"@warlock.js/web/connector\"')) {\r\n // Inserted in sorted position, not prepended. The generated app formats\r\n // with `prettier-plugin-organize-imports`, so an import pushed to the top\r\n // of a file that already imports `@warlock.js/auth` and `@warlock.js/core`\r\n // is out of order the moment it is written — and the developer meets it as\r\n // a `prettier/prettier` error on their first `warlock dev`, in a file they\r\n // did not write.\r\n const withConnector = insertImportInSortedPosition(\r\n config,\r\n 'import { webConnector } from \"@warlock.js/web/connector\";',\r\n \"@warlock.js/web/connector\",\r\n );\r\n\r\n putFile(\r\n configPath,\r\n withConnector.replace(\r\n \"export default defineConfig({\",\r\n \"export default defineConfig({\\n connectors: [webConnector()],\",\r\n ),\r\n );\r\n }\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Install the selected optional features by delegating to the project's own\r\n * `warlock add`. `--no-install` records every dependency in package.json and\r\n * ejects configs / scripts / setup hooks WITHOUT installing — the caller runs\r\n * one batched install afterwards. Versions come from core's feature map, so\r\n * the scaffolder never duplicates them.\r\n *\r\n * `--no-install` is passed LAST on purpose: the CLI parser treats the\r\n * positional after a bare flag as that flag's value, so it must follow the\r\n * feature list, not precede it.\r\n */\r\n public async installFeatures(features: string[]) {\r\n if (features.length === 0) return true;\r\n\r\n return this.exec(`npx warlock add ${features.join(\" \")} --no-install`);\r\n }\r\n\r\n /**\r\n * Force a single copy of `vite` when the project ends up with BOTH `vite`\r\n * (written by the `web` feature) and `vitest` (always in the template).\r\n *\r\n * `vitest` depends on vite through a wide range of its own\r\n * (`^6 || ^7 || ^8`), which resolves to a DIFFERENT major than the range the\r\n * feature map pins. Yarn 1 then has to nest the second copy under\r\n * `vitest/node_modules` and dies on its own linker invariant:\r\n *\r\n * error Invariant Violation: could not find a copy of vite to link in\r\n * <project>/node_modules/vitest/node_modules\r\n *\r\n * It aborts BEFORE `node_modules/.bin` is written, so `warlock` is never\r\n * linked and the very first `yarn dev` fails with \"'warlock' is not\r\n * recognized\". npm and pnpm nest happily and never hit this — for them the\r\n * `overrides` twin below is one copy instead of two, not a crash fix.\r\n *\r\n * The pin is READ BACK from whatever range the project's own package.json\r\n * declares, never re-stated as a literal here: the feature map in\r\n * `@warlock.js/core` owns the version, and a second copy of it would drift\r\n * the moment that one moves.\r\n *\r\n * Returns whether a pin was written, so the caller (and its spec) can assert\r\n * it instead of assuming it.\r\n */\r\n public pinViteResolution() {\r\n const packageJsonPath = path.resolve(this.path, \"package.json\");\r\n\r\n if (!fileExists(packageJsonPath)) return false;\r\n\r\n const packageJson = getJsonFile(packageJsonPath) as {\r\n dependencies?: Record<string, string>;\r\n devDependencies?: Record<string, string>;\r\n resolutions?: Record<string, string>;\r\n overrides?: Record<string, string>;\r\n };\r\n\r\n const declaredRange = (name: string) =>\r\n packageJson.dependencies?.[name] ?? packageJson.devDependencies?.[name];\r\n\r\n const viteRange = declaredRange(\"vite\");\r\n\r\n // No vite, or no vitest to conflict with it — nothing to pin. Writing a\r\n // resolution for a package the project does not depend on would only\r\n // freeze a transitive tree nobody asked us to freeze.\r\n if (!viteRange || !declaredRange(\"vitest\")) return false;\r\n\r\n // yarn 1 / yarn berry read `resolutions`; npm 8+ and pnpm read `overrides`.\r\n packageJson.resolutions = { ...packageJson.resolutions, vite: viteRange };\r\n packageJson.overrides = { ...packageJson.overrides, vite: viteRange };\r\n\r\n putJsonFile(packageJsonPath, packageJson);\r\n\r\n return true;\r\n }\r\n\r\n /**\r\n * Get package json file\r\n */\r\n public get package() {\r\n return this.json(\"package.json\");\r\n }\r\n\r\n public updateDotEnv() {\r\n this.file(\".env\").replaceAll(\"appName\", this.name).save();\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Get env file to update\r\n */\r\n public get env() {\r\n return this.file(\".env\");\r\n }\r\n\r\n public get name() {\r\n return this.app.appName;\r\n }\r\n\r\n public get path() {\r\n return this.app.appPath;\r\n }\r\n\r\n public file(relativePath: string) {\r\n const fullPath = path.resolve(this.path, relativePath);\r\n\r\n if (!this.files[fullPath]) {\r\n this.files[fullPath] = file(fullPath);\r\n }\r\n\r\n return this.files[fullPath];\r\n }\r\n\r\n public json(relativePath: string): JsonFileManager {\r\n const fullPath = path.resolve(this.path, relativePath);\r\n\r\n if (!this.jsonFiles[fullPath]) {\r\n this.jsonFiles[fullPath] = jsonFile(fullPath);\r\n }\r\n\r\n return this.jsonFiles[fullPath];\r\n }\r\n}\r\n\r\nexport function app(app: Application) {\r\n return new App(app);\r\n}\r\n\r\nexport class FileManager {\r\n public content!: string;\r\n public constructor(protected filePath: string) {\r\n this.parseContent();\r\n }\r\n\r\n protected parseContent() {\r\n this.content = getFile(this.filePath) as string;\r\n }\r\n\r\n public replace(search: string, replace: string) {\r\n this.content = this.content.replace(search, replace);\r\n\r\n return this;\r\n }\r\n\r\n public replaceAll(search: string, replace: string) {\r\n this.content = this.content.replaceAll(search, replace);\r\n\r\n return this;\r\n }\r\n\r\n public save() {\r\n putFile(this.filePath, this.content);\r\n }\r\n}\r\n\r\nexport class JsonFileManager extends FileManager {\r\n protected parseContent() {\r\n this.content = getJsonFile(this.filePath);\r\n }\r\n\r\n public save() {\r\n putJsonFile(this.filePath, this.content);\r\n }\r\n\r\n public has(key: string) {\r\n return this.content[key] !== undefined;\r\n }\r\n\r\n public replace(key: string, value: any) {\r\n this.content[key] = value;\r\n\r\n return this;\r\n }\r\n\r\n public replaceAll(key: string, value: any) {\r\n const contentAsString = JSON.stringify(this.content);\r\n\r\n this.content = JSON.parse(contentAsString.replaceAll(key, value));\r\n\r\n return this;\r\n }\r\n}\r\n\r\nexport function file(path: string) {\r\n return new FileManager(path);\r\n}\r\n\r\nexport function jsonFile(path: string) {\r\n return new JsonFileManager(path);\r\n}\r\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"app.mjs","names":[],"sources":["../../../../../../create-warlock/src/helpers/app.ts"],"sourcesContent":["import {\r\n copyDirectory,\r\n copyFile,\r\n fileExists,\r\n getFile,\r\n getJsonFile,\r\n putFile,\r\n putJsonFile,\r\n renameFile,\r\n} from \"@warlock.js/fs\";\r\nimport { existsSync, rmSync, unlinkSync } from \"node:fs\";\r\nimport path from \"path\";\r\nimport { Application, AppOptions } from \"../commands/create-new-app/types\";\r\nimport { getDatabaseDriver } from \"../features/database-drivers\";\r\nimport { executeCommand, runCommand } from \"./exec\";\r\nimport { insertImportInSortedPosition } from \"./insert-import\";\r\nimport { getPackageManager } from \"./package-manager\";\r\nimport { packageRoot, Template, template } from \"./paths\";\r\nimport { fallbackRange } from \"./warlock-versions\";\r\n\r\n/**\r\n * Environment for every install the scaffolder spawns.\r\n *\r\n * A shell that exports `NODE_ENV=production` makes the package managers skip\r\n * every devDependency — and still exit 0. The scaffolded project needs its dev\r\n * toolchain (typescript, vitest, eslint) to be usable at all, so the install\r\n * child is pinned to `development` regardless of the ambient environment. A\r\n * silently incomplete install that reports success is the exact failure mode\r\n * this file exists to prevent.\r\n */\r\nexport function installEnvironment(): NodeJS.ProcessEnv {\r\n return { NODE_ENV: \"development\" };\r\n}\r\n\r\nexport class App {\r\n /**\r\n * Resolved files\r\n */\r\n protected files: Record<string, FileManager> = {};\r\n\r\n /**\r\n * Resolved JSON files\r\n */\r\n protected jsonFiles: Record<string, JsonFileManager> = {};\r\n\r\n public isInstalled = false;\r\n\r\n public constructor(protected app: Application) {}\r\n\r\n public get options(): AppOptions {\r\n return this.app.options;\r\n }\r\n\r\n public use(templateName: Template) {\r\n copyDirectory(template(templateName), this.path);\r\n\r\n if (fileExists(this.path + \"/.env.example\")) {\r\n copyFile(this.path + \"/.env.example\", this.path + \"/.env\");\r\n }\r\n\r\n renameFile(this.path + \"/_.gitignore\", this.path + \"/.gitignore\");\r\n\r\n return this;\r\n }\r\n\r\n public init() {\r\n return this;\r\n }\r\n\r\n public terminate() {\r\n // No longer using outro, using showSuccessScreen instead\r\n }\r\n\r\n public install() {\r\n return runCommand(getPackageManager(), [\"install\"], this.path, {\r\n env: installEnvironment(),\r\n });\r\n }\r\n\r\n public async exec(command: string) {\r\n const [commandName, ...optionsList] = command.split(\" \");\r\n return await executeCommand(commandName, optionsList, this.path);\r\n }\r\n\r\n public async git() {\r\n const { initializeGitRepository } =\r\n await import(\"./project-builder-helpers\");\r\n return await initializeGitRepository(this.path);\r\n }\r\n\r\n /**\r\n * Write the project's `package.json`: the project name, the chosen package\r\n * manager, and the version of every `@warlock.js/*` dependency.\r\n *\r\n * `versions` comes from {@link resolveWarlockVersions} — versions the\r\n * registry has confirmed exist. It is optional because the fluent chain is\r\n * synchronous; without it every sibling gets the caret range floored to the\r\n * scaffolder's major, which is always satisfiable by a published release.\r\n *\r\n * What it must NEVER do again is stamp the scaffolder's own version blind:\r\n * the release tooling bumps that version on every build, published or not,\r\n * so an unverified pin resolves to nothing and the install dies with ETARGET.\r\n */\r\n public updatePackageJson(versions: Record<string, string> = {}) {\r\n const packageManager = getPackageManager();\r\n\r\n const pkg = this.package.replace(\"name\", this.name.replaceAll(\"/\", \"-\"));\r\n\r\n const content: any = pkg.content;\r\n\r\n // Substitute the chosen package manager ONLY into the fields the template\r\n // writes it into literally — the `serve` script — by path, never with a\r\n // blanket substring replace over the serialized JSON. A raw\r\n // `replaceAll(\"yarn\", pm)` rewrites every occurrence of the substring\r\n // \"yarn\" anywhere in the document, so a project named `my-yarn-app`, or any\r\n // dependency/path containing \"yarn\", is silently corrupted (`--pm=npm`\r\n // turned `my-yarn-app` into `my-npm-app`). Field-scoped rewriting is also\r\n // order-independent: the name lives in `content.name` and this token lives\r\n // in `scripts.serve`, a disjoint field that cannot collide with the name\r\n // substitution above regardless of which runs first.\r\n if (typeof content.scripts?.serve === \"string\") {\r\n content.scripts.serve = content.scripts.serve.replaceAll(\r\n \"yarn\",\r\n packageManager,\r\n );\r\n }\r\n\r\n const warlockVersion: string = (\r\n getJsonFile(packageRoot(\"package.json\")) as { version: string }\r\n ).version;\r\n const defaultRange = fallbackRange(warlockVersion);\r\n\r\n for (const field of [\"dependencies\", \"devDependencies\"] as const) {\r\n const deps = content[field] as Record<string, string> | undefined;\r\n if (!deps) continue;\r\n\r\n for (const dependency of Object.keys(deps)) {\r\n if (dependency.startsWith(\"@warlock.js/\")) {\r\n deps[dependency] = versions[dependency] ?? defaultRange;\r\n }\r\n }\r\n }\r\n\r\n pkg.save();\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Configure the chosen database driver: wire `DB_DRIVER` / `DB_PORT` into\r\n * `.env`, AND pin the driver's npm package (`mongodb` / `pg`) into the\r\n * project's `package.json` dependencies.\r\n *\r\n * The dependency is written HERE — before the base `yarn install` — so the\r\n * driver is pulled deterministically by the very first install. We do NOT\r\n * rely on the post-copy `warlock add <driver> --no-install` + separate\r\n * batched install, which can be skipped or fail and leave the driver\r\n * missing (the \"mongodb package is not installed\" runtime error).\r\n */\r\n public configureDatabaseEnv(driverValue: string) {\r\n const driver = getDatabaseDriver(driverValue);\r\n\r\n if (!driver) return this;\r\n\r\n // Pin the driver package into dependencies (idempotent — never downgrade).\r\n const packageJsonPath = path.resolve(this.path, \"package.json\");\r\n const packageJson = getJsonFile(packageJsonPath) as {\r\n dependencies?: Record<string, string>;\r\n };\r\n packageJson.dependencies = packageJson.dependencies ?? {};\r\n if (!packageJson.dependencies[driver.package]) {\r\n packageJson.dependencies[driver.package] = driver.packageVersion;\r\n putJsonFile(packageJsonPath, packageJson);\r\n }\r\n\r\n let envContent = getFile(this.path + \"/.env\") as string;\r\n\r\n envContent = envContent.replace(\r\n /DB_PORT=\\d+/,\r\n `DB_PORT=${driver.defaultPort}`,\r\n );\r\n\r\n if (envContent.includes(\"DB_DRIVER=\")) {\r\n envContent = envContent.replace(\r\n /DB_DRIVER=\\w*/,\r\n `DB_DRIVER=${driver.value}`,\r\n );\r\n } else {\r\n envContent = envContent.replace(\r\n /DB_PORT=\\d+/,\r\n `DB_PORT=${driver.defaultPort}\\nDB_DRIVER=${driver.value}`,\r\n );\r\n }\r\n\r\n putFile(this.path + \"/.env\", envContent);\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Write the correct `src/config/database.ts` for the chosen driver and remove\r\n * every per-driver variant so the scaffold ships exactly one config file.\r\n *\r\n * The template ships a Mongo-shaped `database.ts` (the default) plus a\r\n * `database.postgres.ts` variant. Mongo keeps the default in place; Postgres\r\n * overwrites `database.ts` with the Postgres-shaped variant (typed\r\n * `ConnectionOptions`, `driver: \"postgres\"`, port 5432, no Mongo-only keys —\r\n * no `authSource`, `driverOptions`, `replicaSet`, `randomIncrement` /\r\n * `initialId`). Either way the `.postgres` variant file is deleted afterwards\r\n * so no leftover config ships in the generated project.\r\n *\r\n * For Postgres it also corrects the working `.env`: the Mongo template ships\r\n * `DB_AUTH=admin`, an `authSource` value Postgres ignores and its config no\r\n * longer reads, so the line is dropped to keep `.env` and the config in sync.\r\n * The port is rewritten by {@link configureDatabaseEnv} (driver defaultPort),\r\n * which runs alongside this in the same branch.\r\n */\r\n public configureDatabaseConfig(driverValue: string) {\r\n const configDir = path.resolve(this.path, \"src/config\");\r\n const databasePath = path.resolve(configDir, \"database.ts\");\r\n const postgresVariantPath = path.resolve(configDir, \"database.postgres.ts\");\r\n\r\n if (driverValue === \"postgres\" && fileExists(postgresVariantPath)) {\r\n putFile(databasePath, getFile(postgresVariantPath) as string);\r\n\r\n const envPath = this.path + \"/.env\";\r\n\r\n if (fileExists(envPath)) {\r\n const envContent = getFile(envPath) as string;\r\n // Drop the Mongo-only DB_AUTH line entirely (its whole line, so no blank\r\n // gap is left behind). Postgres has no authSource concept.\r\n putFile(envPath, envContent.replace(/^DB_AUTH=.*\\r?\\n?/m, \"\"));\r\n }\r\n }\r\n\r\n // Whatever the driver, no per-driver variant may survive into the scaffold.\r\n if (fileExists(postgresVariantPath)) {\r\n unlinkSync(postgresVariantPath);\r\n }\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Remove the database layer for a \"no database\" scaffold.\r\n *\r\n * Deletes `src/config/database.ts` (and a `.tsx` / per-driver variant if\r\n * present) from the freshly-copied template. The framework's database\r\n * connector is config-gated on that file — with it gone,\r\n * `config.get(\"database\")` is undefined and the connector no-ops, so the app\r\n * boots with no database wired and no driver package pulled. The `DB_*` lines\r\n * in `.env` are left in place (harmless: no config reads them) as a ready\r\n * template for adding a database back later.\r\n */\r\n public removeDatabaseConfig() {\r\n const configDir = path.resolve(this.path, \"src/config\");\r\n\r\n for (const fileName of [\r\n \"database.ts\",\r\n \"database.tsx\",\r\n \"database.postgres.ts\",\r\n ]) {\r\n const filePath = path.resolve(configDir, fileName);\r\n\r\n if (fileExists(filePath)) {\r\n unlinkSync(filePath);\r\n }\r\n }\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Configure the web starter only when the web feature owns `/`.\r\n *\r\n * A dependency-free HTTP response is retained without web; otherwise the\r\n * SSR page loader owns the route.\r\n * Exactly one survives the scaffold: web projects keep the SSR page and API\r\n * example; every other project keeps the dependency-free JSON controller.\r\n */\r\n public configureWebStarter(useWeb: boolean) {\r\n const remove = (relativePath: string) => {\r\n const target = path.resolve(this.path, relativePath);\r\n if (existsSync(target)) rmSync(target, { recursive: true, force: true });\r\n };\r\n\r\n if (!useWeb) {\r\n for (const entry of [\r\n \"src/web\",\r\n \"src/app/contact\",\r\n \"src/app/locale\",\r\n \"src/shared/contact.schema.ts\",\r\n \"src/shared/locale.schema.ts\",\r\n \"src/shared/locales.ts\",\r\n \"postcss.config.mjs\",\r\n ]) {\r\n remove(entry);\r\n }\r\n\r\n const tsconfigPath = path.resolve(this.path, \"tsconfig.json\");\r\n const tsconfig = getFile(tsconfigPath) as string;\r\n putFile(\r\n tsconfigPath,\r\n tsconfig\r\n .replace(' \"web/*\": [\"./src/web/*\"],\\n', \"\")\r\n .replace(' \"@shared/*\": [\"./src/shared/*\"]\\n', \"\"),\r\n );\r\n\r\n return this;\r\n }\r\n\r\n // The SSR page owns `/`; its loader still uses src/app/home/services.\r\n remove(\"src/app/home/controllers/home-page.controller.ts\");\r\n remove(\"src/app/home/routes.ts\");\r\n\r\n const packageJsonPath = path.resolve(this.path, \"package.json\");\r\n const packageJson = getJsonFile(packageJsonPath) as {\r\n dependencies?: Record<string, string>;\r\n devDependencies?: Record<string, string>;\r\n };\r\n packageJson.dependencies = {\r\n ...packageJson.dependencies,\r\n \"@mongez/http\": \"^3.5.0\",\r\n \"@mongez/react-form\": \"^4.0.0\",\r\n };\r\n packageJson.devDependencies = {\r\n ...packageJson.devDependencies,\r\n \"@tailwindcss/postcss\": \"^4.1.16\",\r\n tailwindcss: \"^4.1.16\",\r\n };\r\n putJsonFile(packageJsonPath, packageJson);\r\n\r\n const configPath = path.resolve(this.path, \"warlock.config.ts\");\r\n const config = getFile(configPath) as string;\r\n if (!config.includes('\"@warlock.js/web/connector\"')) {\r\n // Inserted in sorted position, not prepended. The generated app formats\r\n // with `prettier-plugin-organize-imports`, so an import pushed to the top\r\n // of a file that already imports `@warlock.js/auth` and `@warlock.js/core`\r\n // is out of order the moment it is written — and the developer meets it as\r\n // a `prettier/prettier` error on their first `warlock dev`, in a file they\r\n // did not write.\r\n const withConnector = insertImportInSortedPosition(\r\n config,\r\n 'import { webConnector } from \"@warlock.js/web/connector\";',\r\n \"@warlock.js/web/connector\",\r\n );\r\n\r\n putFile(\r\n configPath,\r\n withConnector.replace(\r\n \"export default defineConfig({\",\r\n \"export default defineConfig({\\n connectors: [webConnector()],\",\r\n ),\r\n );\r\n }\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Record non-default `agent-kit` targets into the generated `package.json`'s\r\n * `agentKit.targets` block.\r\n *\r\n * The template's own `postinstall` (`agent-kit init && agent-kit sync`)\r\n * already targets `claude` with no flags, so the single-target default case\r\n * needs no template edit at all — this only writes when the selection is\r\n * anything else, keeping the scaffold diff-free for the common path.\r\n */\r\n public configureAgentKitTargets(agents: string[]) {\r\n const isDefault = agents.length === 1 && agents[0] === \"claude\";\r\n if (isDefault) return this;\r\n\r\n const packageJsonPath = path.resolve(this.path, \"package.json\");\r\n if (!fileExists(packageJsonPath)) return this;\r\n\r\n const packageJson = getJsonFile(packageJsonPath) as {\r\n agentKit?: { targets?: string[] };\r\n };\r\n packageJson.agentKit = { ...packageJson.agentKit, targets: agents };\r\n putJsonFile(packageJsonPath, packageJson);\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Install the selected optional features by delegating to the project's own\r\n * `warlock add`. `--no-install` records every dependency in package.json and\r\n * ejects configs / scripts / setup hooks WITHOUT installing — the caller runs\r\n * one batched install afterwards. Versions come from core's feature map, so\r\n * the scaffolder never duplicates them.\r\n *\r\n * `--no-install` is passed LAST on purpose: the CLI parser treats the\r\n * positional after a bare flag as that flag's value, so it must follow the\r\n * feature list, not precede it.\r\n */\r\n public async installFeatures(features: string[]) {\r\n if (features.length === 0) return true;\r\n\r\n return this.exec(`npx warlock add ${features.join(\" \")} --no-install`);\r\n }\r\n\r\n /**\r\n * Force a single copy of `vite` when the project ends up with BOTH `vite`\r\n * (written by the `web` feature) and `vitest` (always in the template).\r\n *\r\n * `vitest` depends on vite through a wide range of its own\r\n * (`^6 || ^7 || ^8`), which resolves to a DIFFERENT major than the range the\r\n * feature map pins. Yarn 1 then has to nest the second copy under\r\n * `vitest/node_modules` and dies on its own linker invariant:\r\n *\r\n * error Invariant Violation: could not find a copy of vite to link in\r\n * <project>/node_modules/vitest/node_modules\r\n *\r\n * It aborts BEFORE `node_modules/.bin` is written, so `warlock` is never\r\n * linked and the very first `yarn dev` fails with \"'warlock' is not\r\n * recognized\". npm and pnpm nest happily and never hit this — for them the\r\n * `overrides` twin below is one copy instead of two, not a crash fix.\r\n *\r\n * The pin is READ BACK from whatever range the project's own package.json\r\n * declares, never re-stated as a literal here: the feature map in\r\n * `@warlock.js/core` owns the version, and a second copy of it would drift\r\n * the moment that one moves.\r\n *\r\n * Returns whether a pin was written, so the caller (and its spec) can assert\r\n * it instead of assuming it.\r\n */\r\n public pinViteResolution() {\r\n const packageJsonPath = path.resolve(this.path, \"package.json\");\r\n\r\n if (!fileExists(packageJsonPath)) return false;\r\n\r\n const packageJson = getJsonFile(packageJsonPath) as {\r\n dependencies?: Record<string, string>;\r\n devDependencies?: Record<string, string>;\r\n resolutions?: Record<string, string>;\r\n overrides?: Record<string, string>;\r\n };\r\n\r\n const declaredRange = (name: string) =>\r\n packageJson.dependencies?.[name] ?? packageJson.devDependencies?.[name];\r\n\r\n const viteRange = declaredRange(\"vite\");\r\n\r\n // No vite, or no vitest to conflict with it — nothing to pin. Writing a\r\n // resolution for a package the project does not depend on would only\r\n // freeze a transitive tree nobody asked us to freeze.\r\n if (!viteRange || !declaredRange(\"vitest\")) return false;\r\n\r\n // yarn 1 / yarn berry read `resolutions`; npm 8+ and pnpm read `overrides`.\r\n packageJson.resolutions = { ...packageJson.resolutions, vite: viteRange };\r\n packageJson.overrides = { ...packageJson.overrides, vite: viteRange };\r\n\r\n putJsonFile(packageJsonPath, packageJson);\r\n\r\n return true;\r\n }\r\n\r\n /**\r\n * Get package json file\r\n */\r\n public get package() {\r\n return this.json(\"package.json\");\r\n }\r\n\r\n public updateDotEnv() {\r\n this.file(\".env\").replaceAll(\"appName\", this.name).save();\r\n\r\n return this;\r\n }\r\n\r\n /**\r\n * Get env file to update\r\n */\r\n public get env() {\r\n return this.file(\".env\");\r\n }\r\n\r\n public get name() {\r\n return this.app.appName;\r\n }\r\n\r\n public get path() {\r\n return this.app.appPath;\r\n }\r\n\r\n public file(relativePath: string) {\r\n const fullPath = path.resolve(this.path, relativePath);\r\n\r\n if (!this.files[fullPath]) {\r\n this.files[fullPath] = file(fullPath);\r\n }\r\n\r\n return this.files[fullPath];\r\n }\r\n\r\n public json(relativePath: string): JsonFileManager {\r\n const fullPath = path.resolve(this.path, relativePath);\r\n\r\n if (!this.jsonFiles[fullPath]) {\r\n this.jsonFiles[fullPath] = jsonFile(fullPath);\r\n }\r\n\r\n return this.jsonFiles[fullPath];\r\n }\r\n}\r\n\r\nexport function app(app: Application) {\r\n return new App(app);\r\n}\r\n\r\nexport class FileManager {\r\n public content!: string;\r\n public constructor(protected filePath: string) {\r\n this.parseContent();\r\n }\r\n\r\n protected parseContent() {\r\n this.content = getFile(this.filePath) as string;\r\n }\r\n\r\n public replace(search: string, replace: string) {\r\n this.content = this.content.replace(search, replace);\r\n\r\n return this;\r\n }\r\n\r\n public replaceAll(search: string, replace: string) {\r\n this.content = this.content.replaceAll(search, replace);\r\n\r\n return this;\r\n }\r\n\r\n public save() {\r\n putFile(this.filePath, this.content);\r\n }\r\n}\r\n\r\nexport class JsonFileManager extends FileManager {\r\n protected parseContent() {\r\n this.content = getJsonFile(this.filePath);\r\n }\r\n\r\n public save() {\r\n putJsonFile(this.filePath, this.content);\r\n }\r\n\r\n public has(key: string) {\r\n return this.content[key] !== undefined;\r\n }\r\n\r\n public replace(key: string, value: any) {\r\n this.content[key] = value;\r\n\r\n return this;\r\n }\r\n\r\n public replaceAll(key: string, value: any) {\r\n const contentAsString = JSON.stringify(this.content);\r\n\r\n this.content = JSON.parse(contentAsString.replaceAll(key, value));\r\n\r\n return this;\r\n }\r\n}\r\n\r\nexport function file(path: string) {\r\n return new FileManager(path);\r\n}\r\n\r\nexport function jsonFile(path: string) {\r\n return new JsonFileManager(path);\r\n}\r\n"],"mappings":"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"}
|
package/esm/index.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { NO_DATABASE } from "./features/database-drivers.mjs";
|
|
2
2
|
import { packageRoot } from "./helpers/paths.mjs";
|
|
3
3
|
import createNewApp from "./commands/create-new-app/index.mjs";
|
|
4
|
+
import { buildHelpText } from "./flags/definitions.mjs";
|
|
4
5
|
import { getJsonFile } from "@warlock.js/fs";
|
|
5
6
|
|
|
6
7
|
//#region ../create-warlock/src/index.ts
|
|
@@ -9,30 +10,11 @@ const valueFlags = [
|
|
|
9
10
|
"db",
|
|
10
11
|
"pm",
|
|
11
12
|
"features",
|
|
12
|
-
"ai"
|
|
13
|
+
"ai",
|
|
14
|
+
"agents",
|
|
15
|
+
"stack"
|
|
13
16
|
];
|
|
14
|
-
const HELP_TEXT =
|
|
15
|
-
create-warlock — scaffold a new Warlock.js project
|
|
16
|
-
|
|
17
|
-
Usage
|
|
18
|
-
$ create-warlock [project-name] [options]
|
|
19
|
-
|
|
20
|
-
Options
|
|
21
|
-
--name Project name (or pass it as the first positional arg)
|
|
22
|
-
--db=<driver> Database driver (e.g. postgres, mongodb)
|
|
23
|
-
--no-db Skip database selection entirely
|
|
24
|
-
--features=<list> Comma-separated feature keys (e.g. test,herald)
|
|
25
|
-
--ai=<list> Comma-separated AI provider keys (e.g. openai,anthropic)
|
|
26
|
-
--pm=<manager> Package manager to use (npm, yarn, pnpm)
|
|
27
|
-
--git / --no-git Force-enable or force-disable git initialization
|
|
28
|
-
--jwt / --no-jwt Force-enable or force-disable JWT secret generation
|
|
29
|
-
-y, --yes Skip prompts and accept defaults for anything unset
|
|
30
|
-
-h, --help Show this help message and exit
|
|
31
|
-
-v, --version Show the installed create-warlock version and exit
|
|
32
|
-
|
|
33
|
-
Example
|
|
34
|
-
$ create-warlock my-app --db=postgres --features=test,herald --yes
|
|
35
|
-
`;
|
|
17
|
+
const HELP_TEXT = buildHelpText();
|
|
36
18
|
/**
|
|
37
19
|
* Parse the scaffolder's own CLI flags for non-interactive mode.
|
|
38
20
|
*
|
|
@@ -101,6 +83,16 @@ function parseFlags(argv) {
|
|
|
101
83
|
case "ai":
|
|
102
84
|
flags.ai = splitList(value);
|
|
103
85
|
break;
|
|
86
|
+
case "agents":
|
|
87
|
+
flags.agents = splitList(value);
|
|
88
|
+
break;
|
|
89
|
+
case "stack":
|
|
90
|
+
flags.stack = value;
|
|
91
|
+
break;
|
|
92
|
+
case "interactive":
|
|
93
|
+
case "customize":
|
|
94
|
+
flags.interactive = true;
|
|
95
|
+
break;
|
|
104
96
|
}
|
|
105
97
|
}
|
|
106
98
|
if (!flags.name && positionals.length > 0) flags.name = positionals[0];
|
package/esm/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../../../../create-warlock/src/index.ts"],"sourcesContent":["import { getJsonFile } from \"@warlock.js/fs\";\nimport createNewApp from \"./commands/create-new-app\";\nimport { CliFlags } from \"./commands/create-new-app/types\";\nimport { NO_DATABASE } from \"./features/database-drivers\";\nimport { packageRoot } from \"./helpers/paths\";\n\nconst valueFlags = [\"name\", \"db\", \"pm\", \"features\", \"ai\"
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../../../../create-warlock/src/index.ts"],"sourcesContent":["import { getJsonFile } from \"@warlock.js/fs\";\nimport createNewApp from \"./commands/create-new-app\";\nimport { CliFlags, Stack } from \"./commands/create-new-app/types\";\nimport { NO_DATABASE } from \"./features/database-drivers\";\nimport { buildHelpText } from \"./flags/definitions\";\nimport { packageRoot } from \"./helpers/paths\";\n\nconst valueFlags = [\"name\", \"db\", \"pm\", \"features\", \"ai\", \"agents\", \"stack\"];\n\nconst HELP_TEXT = buildHelpText();\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 \"help\":\n case \"h\":\n flags.help = true;\n break;\n case \"version\":\n case \"v\":\n flags.version = true;\n break;\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 case \"agents\":\n flags.agents = splitList(value);\n break;\n case \"stack\":\n flags.stack = value as Stack;\n break;\n case \"interactive\":\n case \"customize\":\n flags.interactive = true;\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\n/**\n * Read this package's own `version` field. A plain JSON read — no network\n * call, no write, nothing else touched — so `--version` stays a pure,\n * side-effect-free exit.\n */\nexport function packageVersion(): string {\n return (getJsonFile(packageRoot(\"package.json\")) as { version: string })\n .version;\n}\n\nexport default function createApp() {\n const flags = parseFlags(process.argv.slice(2));\n\n // `--help` and `--version` must exit before anything that touches the\n // filesystem, the network, or a prompt — they win over every other flag,\n // including a positional project name.\n if (flags.help) {\n console.log(HELP_TEXT);\n process.exit(0);\n return;\n }\n\n if (flags.version) {\n console.log(packageVersion());\n process.exit(0);\n return;\n }\n\n // An unexpected throw must surface as a readable error AND a non-zero exit\n // code — never as a stack trace the user scrolls past on the way to a green\n // banner (there is no banner after this point).\n Promise.resolve(createNewApp(flags)).catch((error: unknown) => {\n console.error();\n console.error(\n ` create-warlock failed: ${(error as Error)?.message ?? String(error)}`,\n );\n console.error();\n\n process.exit(1);\n });\n}\n"],"mappings":";;;;;;;AAOA,MAAM,aAAa;CAAC;CAAQ;CAAM;CAAM;CAAY;CAAM;CAAU;AAAO;AAE3E,MAAM,YAAY,cAAc;;;;;;;AAQhC,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,OAAO;IACb;GACF,KAAK;GACL,KAAK;IACH,MAAM,UAAU;IAChB;GACF,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;GACF,KAAK;IACH,MAAM,SAAS,UAAU,KAAK;IAC9B;GACF,KAAK;IACH,MAAM,QAAQ;IACd;GACF,KAAK;GACL,KAAK;IACH,MAAM,cAAc;IACpB;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;;;;;;AAOA,SAAgB,iBAAyB;CACvC,OAAQ,YAAY,YAAY,cAAc,CAAC,CAAC,CAC7C;AACL;AAEA,SAAwB,YAAY;CAClC,MAAM,QAAQ,WAAW,QAAQ,KAAK,MAAM,CAAC,CAAC;CAK9C,IAAI,MAAM,MAAM;EACd,QAAQ,IAAI,SAAS;EACrB,QAAQ,KAAK,CAAC;EACd;CACF;CAEA,IAAI,MAAM,SAAS;EACjB,QAAQ,IAAI,eAAe,CAAC;EAC5B,QAAQ,KAAK,CAAC;EACd;CACF;CAKA,QAAQ,QAAQ,aAAa,KAAK,CAAC,CAAC,CAAC,OAAO,UAAmB;EAC7D,QAAQ,MAAM;EACd,QAAQ,MACN,4BAA6B,OAAiB,WAAW,OAAO,KAAK,GACvE;EACA,QAAQ,MAAM;EAEd,QAAQ,KAAK,CAAC;CAChB,CAAC;AACH"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { cancel, isCancel, select } from "@clack/prompts";
|
|
2
|
+
|
|
3
|
+
//#region ../create-warlock/src/prompts/ask-stack.ts
|
|
4
|
+
/**
|
|
5
|
+
* The ONE structural question the default (non-`--interactive`) interactive
|
|
6
|
+
* path is allowed to ask: does the generated app need server-rendered web
|
|
7
|
+
* pages, or is it an API only? This is the sole fork that changes the shape
|
|
8
|
+
* of the generated project — every other choice (database, features,
|
|
9
|
+
* package manager, agent-kit targets, …) is reversible within minutes and
|
|
10
|
+
* ships as a flag with a default instead.
|
|
11
|
+
*/
|
|
12
|
+
async function askStack() {
|
|
13
|
+
const answer = await select({
|
|
14
|
+
message: "What are we building?",
|
|
15
|
+
options: [{
|
|
16
|
+
value: "api",
|
|
17
|
+
label: "API only",
|
|
18
|
+
hint: "A REST/GraphQL API, no server-rendered pages"
|
|
19
|
+
}, {
|
|
20
|
+
value: "web",
|
|
21
|
+
label: "Full-stack web",
|
|
22
|
+
hint: "API + server-rendered React pages (SSR)"
|
|
23
|
+
}],
|
|
24
|
+
initialValue: "api"
|
|
25
|
+
});
|
|
26
|
+
if (isCancel(answer)) {
|
|
27
|
+
cancel("Setup cancelled");
|
|
28
|
+
process.exit(0);
|
|
29
|
+
}
|
|
30
|
+
return answer;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
//#endregion
|
|
34
|
+
export { askStack };
|
|
35
|
+
//# sourceMappingURL=ask-stack.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ask-stack.mjs","names":[],"sources":["../../../../../../create-warlock/src/prompts/ask-stack.ts"],"sourcesContent":["import { cancel, isCancel, select } from \"@clack/prompts\";\nimport type { Stack } from \"../commands/create-new-app/types\";\n\n/**\n * The ONE structural question the default (non-`--interactive`) interactive\n * path is allowed to ask: does the generated app need server-rendered web\n * pages, or is it an API only? This is the sole fork that changes the shape\n * of the generated project — every other choice (database, features,\n * package manager, agent-kit targets, …) is reversible within minutes and\n * ships as a flag with a default instead.\n */\nexport async function askStack(): Promise<Stack> {\n const answer = await select({\n message: \"What are we building?\",\n options: [\n {\n value: \"api\",\n label: \"API only\",\n hint: \"A REST/GraphQL API, no server-rendered pages\",\n },\n {\n value: \"web\",\n label: \"Full-stack web\",\n hint: \"API + server-rendered React pages (SSR)\",\n },\n ],\n initialValue: \"api\",\n });\n\n if (isCancel(answer)) {\n cancel(\"Setup cancelled\");\n process.exit(0);\n }\n\n return answer as Stack;\n}\n"],"mappings":";;;;;;;;;;;AAWA,eAAsB,WAA2B;CAC/C,MAAM,SAAS,MAAM,OAAO;EAC1B,SAAS;EACT,SAAS,CACP;GACE,OAAO;GACP,OAAO;GACP,MAAM;EACR,GACA;GACE,OAAO;GACP,OAAO;GACP,MAAM;EACR,CACF;EACA,cAAc;CAChB,CAAC;CAED,IAAI,SAAS,MAAM,GAAG;EACpB,OAAO,iBAAiB;EACxB,QAAQ,KAAK,CAAC;CAChB;CAEA,OAAO;AACT"}
|
package/esm/summary.mjs
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { getDatabaseLabel } from "./features/database-drivers.mjs";
|
|
2
|
+
import { colors } from "@mongez/copper";
|
|
3
|
+
|
|
4
|
+
//#region ../create-warlock/src/summary.ts
|
|
5
|
+
/**
|
|
6
|
+
* Print what the scaffolder decided on the user's behalf, and how to change
|
|
7
|
+
* each decision — required because almost every choice is now a default
|
|
8
|
+
* rather than a prompt, so this is the only place a user sees them.
|
|
9
|
+
*/
|
|
10
|
+
function printDecisionsSummary({ packageManager, options }) {
|
|
11
|
+
const databaseLabel = getDatabaseLabel(options.databaseDriver);
|
|
12
|
+
const headline = [
|
|
13
|
+
`Using ${packageManager}`,
|
|
14
|
+
`database: ${databaseLabel}`,
|
|
15
|
+
`agent-kit targets: ${options.agents.join(", ")}`
|
|
16
|
+
].join(" · ");
|
|
17
|
+
console.log();
|
|
18
|
+
console.log(colors.cyan(headline));
|
|
19
|
+
console.log(colors.dim(" change these — package manager: --pm, database: --db/--no-db, agents: --agents, features: --features, ai: --ai, git: --git/--no-git, jwt: --jwt/--no-jwt — or re-run with --interactive for the full wizard. Package-manager-level choices (husky, lint config, etc.) can also be edited directly in the generated project."));
|
|
20
|
+
console.log();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
//#endregion
|
|
24
|
+
export { printDecisionsSummary };
|
|
25
|
+
//# sourceMappingURL=summary.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"summary.mjs","names":[],"sources":["../../../../../create-warlock/src/summary.ts"],"sourcesContent":["import { colors } from \"@mongez/copper\";\nimport { getDatabaseLabel } from \"./features/database-drivers\";\nimport { AppOptions } from \"./commands/create-new-app/types\";\n\n/** What decisions.spec.ts / the terminal actually needs to render the summary. */\nexport type Decisions = {\n packageManager: string;\n options: AppOptions;\n};\n\n/**\n * Print what the scaffolder decided on the user's behalf, and how to change\n * each decision — required because almost every choice is now a default\n * rather than a prompt, so this is the only place a user sees them.\n */\nexport function printDecisionsSummary({ packageManager, options }: Decisions): void {\n const databaseLabel = getDatabaseLabel(options.databaseDriver);\n\n const headline = [\n `Using ${packageManager}`,\n `database: ${databaseLabel}`,\n `agent-kit targets: ${options.agents.join(\", \")}`,\n ].join(\" · \");\n\n console.log();\n console.log(colors.cyan(headline));\n console.log(\n colors.dim(\n \" change these — package manager: --pm, database: --db/--no-db, agents: --agents, \" +\n \"features: --features, ai: --ai, git: --git/--no-git, jwt: --jwt/--no-jwt — \" +\n \"or re-run with --interactive for the full wizard. Package-manager-level choices \" +\n \"(husky, lint config, etc.) can also be edited directly in the generated project.\",\n ),\n );\n console.log();\n}\n"],"mappings":";;;;;;;;;AAeA,SAAgB,sBAAsB,EAAE,gBAAgB,WAA4B;CAClF,MAAM,gBAAgB,iBAAiB,QAAQ,cAAc;CAE7D,MAAM,WAAW;EACf,SAAS;EACT,aAAa;EACb,sBAAsB,QAAQ,OAAO,KAAK,IAAI;CAChD,CAAC,CAAC,KAAK,KAAK;CAEZ,QAAQ,IAAI;CACZ,QAAQ,IAAI,OAAO,KAAK,QAAQ,CAAC;CACjC,QAAQ,IACN,OAAO,IACL,+TAIF,CACF;CACA,QAAQ,IAAI;AACd"}
|
package/llms-full.txt
CHANGED
|
@@ -8,52 +8,48 @@
|
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
name: create-a-warlock-project
|
|
11
|
-
description: 'Scaffold a brand-new Warlock.js project with `create-warlock` —
|
|
11
|
+
description: 'Scaffold a brand-new Warlock.js project with `create-warlock` — fully headless by default (at most one prompt — API-only vs full-stack web) plus `--interactive`/`--customize` for the full long-form wizard. Every flag (`--name`, `--stack`, `--db`, `--pm`, `--features`, `--ai`, `--agents`, `--git/--no-git`, `--jwt/--no-jwt`, `--no-db`, `--yes`, `--interactive`), every valid database-driver / feature / AI-provider / agent-kit-target key, opting out of a database (`--db=none` / `--no-db`, which removes `src/config/database.ts`), the post-scaffold decisions summary, and the directory tree the template emits. Triggers: `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", "agent-kit targets". 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` (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.
|
|
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`), derives `agent-kit` targets into the generated project, 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
|
|
|
20
20
|
```bash
|
|
21
|
-
#
|
|
22
|
-
|
|
23
|
-
# or: npx create-warlock
|
|
21
|
+
# Default: at most ONE prompt (API-only vs full-stack web) — everything else is a flag/default
|
|
22
|
+
npx create-warlock my-app
|
|
24
23
|
|
|
25
|
-
#
|
|
24
|
+
# Fully headless — one command scaffolds the whole app, no prompts at all
|
|
26
25
|
npx create-warlock my-app --yes --db=postgres --features=test,redis --ai=ai-openai
|
|
27
26
|
|
|
27
|
+
# The one structural fork
|
|
28
|
+
npx create-warlock my-web-app --yes --stack=web
|
|
29
|
+
|
|
28
30
|
# No database at all
|
|
29
31
|
npx create-warlock my-api --yes --no-db --features=test
|
|
32
|
+
|
|
33
|
+
# The full long-form wizard, one prompt at a time
|
|
34
|
+
npx create-warlock --interactive
|
|
30
35
|
```
|
|
31
36
|
|
|
32
37
|
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.
|
|
33
38
|
|
|
34
|
-
##
|
|
35
|
-
|
|
36
|
-
The wizard asks, in order:
|
|
37
|
-
|
|
38
|
-
1. **Project name** — required.
|
|
39
|
-
2. **Package manager** — chosen from the managers detected on the system (npm is always offered; yarn / pnpm appear if installed).
|
|
40
|
-
3. **Database driver** — a single select: a driver, or **None** to scaffold without a database (see keys below).
|
|
41
|
-
4. **Features** — a multiselect of optional packages (`react` is pre-checked).
|
|
42
|
-
5. **AI providers** — a multiselect; picking any one pulls `@warlock.js/ai` automatically.
|
|
43
|
-
6. **Initialize Git?** — yes/no.
|
|
44
|
-
7. **Generate JWT secret keys?** — yes/no. If no, the cache is warmed instead.
|
|
39
|
+
## Three modes
|
|
45
40
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
41
|
+
1. **Default (a TTY, no `--yes`, no `--interactive`)** — asks the project name (if not already given) plus AT MOST ONE structural question: API-only or full-stack web (`--stack`). This is the only choice that changes the generated app's shape; every other choice below is reversible within minutes of scaffolding, so it is a flag with a sensible default, never a prompt.
|
|
42
|
+
2. **`--yes` (or no TTY at all — CI, a script, an agent harness)** — fully headless. Every answer comes from a flag or its default; nothing is ever prompted, and stdin is never read.
|
|
43
|
+
3. **`--interactive` (alias `--customize`)** — the full long-form wizard: project name, package manager, database driver, features, AI providers, git, JWT — one prompt after another, exactly like earlier `create-warlock` releases.
|
|
49
44
|
|
|
50
45
|
**Without a terminal, `--yes` is not required.** When stdin is not a TTY — CI, a
|
|
51
|
-
script, a container, an agent harness — the scaffolder does not try to prompt
|
|
52
|
-
|
|
53
|
-
|
|
46
|
+
script, a container, an agent harness — the scaffolder does not try to prompt,
|
|
47
|
+
regardless of which of the three modes was requested. If the flags already
|
|
48
|
+
answer everything it needs, it simply scaffolds. Prompting is the fallback,
|
|
49
|
+
not the precondition.
|
|
54
50
|
|
|
55
51
|
Only a question that genuinely cannot be answered stops the run, and in practice
|
|
56
|
-
that is just the project name, the one
|
|
52
|
+
that is just the project name, the one required answer with no default:
|
|
57
53
|
|
|
58
54
|
```
|
|
59
55
|
A project name is required and no terminal is available to ask for one. Pass it as
|
|
@@ -61,21 +57,51 @@ the first argument or --name=<name>. Non-interactive flags: --yes, --pm=<npm|yar
|
|
|
61
57
|
--db=<driver>|--no-db, --features=<list>, --ai=<list>, --git|--no-git, --jwt|--no-jwt.
|
|
62
58
|
```
|
|
63
59
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
| `--no-db` | no | — | Opt out of a database — shorthand for `--db=none`. |
|
|
69
|
-
| `--pm` | yes | auto-detected | Package manager — must be one of `npm` / `yarn` / `pnpm` / `bun`; anything else is rejected before scaffolding starts. |
|
|
70
|
-
| `--features` | yes (CSV) | none | Comma-separated optional feature keys. |
|
|
71
|
-
| `--ai` | yes (CSV) | none | Comma-separated AI provider keys (auto-pulls `@warlock.js/ai`). |
|
|
72
|
-
| `--git` / `--no-git` | no | off | Initialize a Git repo (`git init` + `main` branch + initial commit).|
|
|
73
|
-
| `--jwt` / `--no-jwt` | no | off | Generate JWT secrets via the project's `jwt` script. |
|
|
74
|
-
| `--yes` / `-y` | no | off | Non-interactive mode. |
|
|
60
|
+
## After scaffolding: the decisions summary
|
|
61
|
+
|
|
62
|
+
Because almost every choice defaults silently, `create-warlock` prints what it
|
|
63
|
+
decided right after scaffolding, e.g.:
|
|
75
64
|
|
|
76
|
-
|
|
65
|
+
```
|
|
66
|
+
Using npm · database: none · agent-kit targets: claude
|
|
67
|
+
change these — package manager: --pm, database: --db/--no-db, agents: --agents,
|
|
68
|
+
features: --features, ai: --ai, git: --git/--no-git, jwt: --jwt/--no-jwt — or
|
|
69
|
+
re-run with --interactive for the full wizard.
|
|
70
|
+
```
|
|
77
71
|
|
|
78
|
-
|
|
72
|
+
## Flags
|
|
73
|
+
|
|
74
|
+
| Flag | Takes value | Default | Purpose |
|
|
75
|
+
| --------------------------- | ----------- | -------------- | ------------------------------------------------------------------- |
|
|
76
|
+
| `<positional>` / `--name` | yes | — (required) | Project name + target directory. |
|
|
77
|
+
| `--stack` | yes | `api` | `api` or `web` — the one structural question; `web` seeds the `web` feature by default. |
|
|
78
|
+
| `--db` | yes | `mongodb` | Database driver key (`mongodb`, `postgres`, or `none`). |
|
|
79
|
+
| `--no-db` | no | — | Opt out of a database — shorthand for `--db=none`. |
|
|
80
|
+
| `--pm` | yes | inferred (`npm_config_user_agent` / system) | Package manager — must be one of `npm` / `yarn` / `pnpm` / `bun`; anything else is rejected before scaffolding starts. |
|
|
81
|
+
| `--features` | yes (CSV) | none, or `["web"]` when `--stack=web` and `--features` is not given | Comma-separated optional feature keys. An explicit `--features` always wins over the `--stack` default. |
|
|
82
|
+
| `--ai` | yes (CSV) | none | Comma-separated AI provider keys (auto-pulls `@warlock.js/ai`). |
|
|
83
|
+
| `--agents` | yes (CSV) | `claude` | Comma-separated `agent-kit` targets — see below. |
|
|
84
|
+
| `--git` / `--no-git` | no | off | Initialize a Git repo (`git init` + `main` branch + initial commit).|
|
|
85
|
+
| `--jwt` / `--no-jwt` | no | off | Generate JWT secrets via the project's `jwt` script. |
|
|
86
|
+
| `--yes` / `-y` | no | off | Fully headless mode. |
|
|
87
|
+
| `--interactive`, `--customize` | no | off | Restore the full long-form wizard. |
|
|
88
|
+
|
|
89
|
+
Value flags accept either `--db=postgres` or `--db postgres`. Unknown `--features` / `--ai` / `--agents` keys fail fast before any install.
|
|
90
|
+
|
|
91
|
+
`--help`/`-h` and `--version`/`-v` short-circuit before anything else — no prompt, filesystem write, or network call, and `--help` wins even over a positional project name. `--version` prints the installed `create-warlock` version. `--help` lists every flag above together with its default.
|
|
92
|
+
|
|
93
|
+
## `--agents` and agent-kit targets
|
|
94
|
+
|
|
95
|
+
`--agents` picks which `agent-kit` targets the generated project derives
|
|
96
|
+
docs/skills for (written into the scaffolded `package.json`'s `agentKit.targets`
|
|
97
|
+
when it differs from the default). The default, `claude`, is a tiny built-in
|
|
98
|
+
fallback that always works — even fully offline. Any other target
|
|
99
|
+
(`cursor`, `codex`, `kiro`, `copilot`, `antigravity`, `opencode`, `amp`,
|
|
100
|
+
`goose`, …) is validated against the list published at
|
|
101
|
+
https://mongez.js.org/agent-kit/llms.txt, fetched once and cached locally. If
|
|
102
|
+
that fetch fails for an explicitly requested non-default target, the run fails
|
|
103
|
+
visibly and names every target it *could* determine — it never silently
|
|
104
|
+
guesses.
|
|
79
105
|
|
|
80
106
|
## Valid keys
|
|
81
107
|
|
|
@@ -118,11 +144,12 @@ The template ships a working auth module (login / logout / logout-all / refresh
|
|
|
118
144
|
|
|
119
145
|
## Setup steps the scaffolder runs
|
|
120
146
|
|
|
121
|
-
1. Copy template → substitute name → wire `DB_DRIVER` / `DB_PORT` into `.env` (or, for `--db=none`, remove `src/config/database.ts`).
|
|
147
|
+
1. Copy template → substitute name → wire `DB_DRIVER` / `DB_PORT` into `.env` (or, for `--db=none`, remove `src/config/database.ts`) → record non-default `--agents` targets into `package.json`'s `agentKit.targets`.
|
|
122
148
|
2. Install base dependencies (so the `warlock` binary exists).
|
|
123
149
|
3. `warlock add <driver> <features...> <ai...> --no-install`, then one batched install. The `<driver>` is omitted for `--db=none`.
|
|
124
150
|
4. `git init` (if `--git`).
|
|
125
151
|
5. `jwt` script (if `--jwt`) — otherwise `warlock --warm-cache`.
|
|
152
|
+
6. Print the decisions summary.
|
|
126
153
|
|
|
127
154
|
Failures surface the error and halt; there is no rollback. After it finishes, `cd my-app` and run the dev server.
|
|
128
155
|
|