create-warlock 5.30.0 → 5.31.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 +29 -2
- package/README.md +66 -15
- package/esm/commands/create-new-app/index.mjs +19 -16
- package/esm/commands/create-new-app/index.mjs.map +1 -1
- package/esm/commands/create-new-app/types.d.mts +2 -1
- package/esm/commands/create-warlock-app/index.mjs +1 -0
- package/esm/commands/create-warlock-app/index.mjs.map +1 -1
- package/esm/features/agent-kit-targets.mjs.map +1 -1
- package/esm/features/database-drivers.mjs.map +1 -1
- package/esm/features/features-map.mjs +12 -12
- package/esm/features/features-map.mjs.map +1 -1
- package/esm/flags/assert-package-manager.mjs.map +1 -1
- package/esm/flags/definitions.mjs +5 -0
- package/esm/flags/definitions.mjs.map +1 -1
- package/esm/flags/seed-from-flags.mjs +1 -0
- package/esm/flags/seed-from-flags.mjs.map +1 -1
- package/esm/helpers/app.mjs +44 -0
- package/esm/helpers/app.mjs.map +1 -1
- package/esm/helpers/exec.mjs.map +1 -1
- package/esm/helpers/insert-import.mjs.map +1 -1
- package/esm/helpers/package-manager.mjs.map +1 -1
- package/esm/helpers/pnpm-build-approvals.mjs.map +1 -1
- package/esm/helpers/project-builder-helpers.mjs.map +1 -1
- package/esm/helpers/warlock-versions.mjs.map +1 -1
- package/esm/index.mjs +5 -1
- package/esm/index.mjs.map +1 -1
- package/esm/prompts/ask-agent-targets.mjs.map +1 -1
- package/esm/prompts/ask-ai-features.mjs.map +1 -1
- package/esm/prompts/ask-features.mjs +53 -0
- package/esm/prompts/ask-features.mjs.map +1 -0
- package/esm/prompts/ask-lint-toolchain.mjs +21 -0
- package/esm/prompts/ask-lint-toolchain.mjs.map +1 -0
- package/esm/prompts/fit-to-terminal.mjs.map +1 -1
- package/esm/summary.mjs +11 -1
- package/esm/summary.mjs.map +1 -1
- package/esm/ui/banner.mjs +114 -46
- package/esm/ui/banner.mjs.map +1 -1
- package/esm/ui/report.mjs.map +1 -1
- package/esm/ui/spinners.mjs.map +1 -1
- package/esm/ui/wrap-text.mjs +55 -0
- package/esm/ui/wrap-text.mjs.map +1 -0
- package/llms-full.txt +71 -25
- package/package.json +3 -3
- package/skills/create-a-warlock-project/SKILL.md +71 -25
- package/templates/toolchains/oxc/.oxfmtrc.json +12 -0
- package/templates/toolchains/oxc/.oxlintrc.json +24 -0
- package/templates/toolchains/oxc/.vscode/extensions.json +3 -0
- package/templates/toolchains/oxc/.vscode/settings.json +35 -0
- package/templates/warlock/AGENTS.md +22 -2
- package/templates/warlock/README.md +5 -3
- package/templates/warlock/package.json +6 -4
- package/templates/warlock/skills/api-design/SKILL.md +2 -2
- package/templates/warlock/skills/code-standards/SKILL.md +18 -4
- package/templates/warlock/skills/security-baseline/SKILL.md +12 -1
- package/templates/warlock/skills/testing-strategy/SKILL.md +17 -0
- package/templates/warlock/src/app/auth/routes.ts +25 -5
- package/templates/warlock/src/app/auth/utils/types.ts +6 -1
- package/templates/warlock/src/app/contact/routes.ts +1 -1
- package/templates/warlock/src/app/locale/routes.ts +1 -1
- package/templates/warlock/src/app/users/controllers/list-users.controller.ts +10 -2
- package/templates/warlock/src/app/users/schema/list-users.schema.ts +15 -0
- package/templates/warlock/src/config/tests.ts +8 -0
- package/templates/warlock/src/web/404.css +2 -6
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"features-map.mjs","names":[],"sources":["../../../../../../../packages/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`. That guard now\n * EXISTS: `builder/scripts/check-feature-parity.ts`, run by every publishing\n * script in `builder/package.json`, so a release aborts before `pkgist` starts.\n * It could not live in this repo: this package deliberately does not depend on\n * `@warlock.js/core`, and our own CI checks out only this repo, so core’s keys\n * are not present here to compare against. `builder` sits above both and\n * imports each by relative path, adding no dependency edge in either direction.\n *\n * A subset assertion ALONE is not enough and has already let a\n * bug ship: `--features=web,tailwind` was rejected with \"Unknown feature(s):\n * tailwind\" because `tailwind` and `shadcn` existed in core and were missing\n * from THIS file — a direction a scaffolder ⊆ core check cannot see. The guard\n * has to run both ways:\n *\n * 1. scaffolder ⊆ core — catches a typo or a key core renamed.\n * 2. core ⊆ scaffolder + an explicit omission list — catches a feature core\n * gained that never became scaffoldable.\n *\n * The two lists are not equal and should not be forced to be. As of this\n * writing core holds 28 keys and this file offers 24; the other four are\n * deliberate and are the entire allow-list direction (2) needs:\n * `mongodb`, `postgres`, `mysql` (the database driver has its own select) and\n * `ai` (pulled automatically by every `ai-*` key's `requires`). Anything beyond\n * those four is a gap, not a decision, and should fail the build.\n */\n\nexport type FeatureGroup =\n | \"Auth & Access\"\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 + capability packages (their own\n * step) are intentionally NOT here — they have dedicated prompts.\n */\nexport const features: FeatureOption[] = [\n // Auth & Access\n {\n key: \"access\",\n label: \"Access (authorization)\",\n hint: \"RBAC permission checks, ABAC policies, and roles — pairs with auth\",\n group: \"Auth & Access\",\n },\n {\n key: \"auth-google\",\n label: \"Google sign-in\",\n hint: \"Google OAuth sign-in for @warlock.js/auth\",\n group: \"Auth & Access\",\n },\n {\n key: \"auth-passkeys\",\n label: \"Passkey sign-in\",\n hint: \"WebAuthn passkey login for @warlock.js/auth\",\n group: \"Auth & Access\",\n },\n\n // Rendering & Mail\n {\n key: \"web\",\n label: \"Web (SSR React pages)\",\n hint: \"Server-rendered React pages on the same server as your API — scaffolds src/web and registers the connector\",\n group: \"Rendering & Mail\",\n },\n {\n key: \"tailwind\",\n label: \"Tailwind CSS\",\n hint: \"Tailwind v4 for the SSR page layer — creates src/web/app.css (the CSS-first config) and wires it through Vite (pulls web)\",\n group: \"Rendering & Mail\",\n },\n {\n key: \"shadcn\",\n label: \"shadcn/ui prerequisites\",\n hint: \"components.json, cn(), design tokens, and the web/* tsconfig path so `npx shadcn add <component>` works first time (pulls tailwind)\",\n group: \"Rendering & Mail\",\n },\n {\n key: \"sitemap\",\n label: \"Sitemap\",\n hint: \"Runtime sitemap.xml generation from the page registry — requires app.publicUrl or PUBLIC_APP_URL (pulls web)\",\n group: \"Rendering & Mail\",\n },\n {\n key: \"devtools\",\n label: \"Devtools (/__warlock)\",\n hint: \"Development-only dashboard: request timeline, queries + EXPLAIN, N+1 warnings, dev mailbox, logs, cache, routes — installed as a dev dependency, never runs in production\",\n group: \"Rendering & Mail\",\n },\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: \"queue\",\n label: \"Queue (BullMQ)\",\n hint: \"Durable Redis-backed background jobs with retries and workers\",\n group: \"Jobs & Messaging\",\n },\n {\n key: \"bull-board\",\n label: \"Bull Board (queue dashboard)\",\n hint: \"Web dashboard for inspecting queues and jobs — enabled outside production only, and refuses to mount unguarded (pulls queue)\",\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 key: \"notifications\",\n label: \"Notifications\",\n hint: \"Multi-channel notifications — mail + in-app database, preferences, idempotency\",\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/package 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 *\n * Keys are `ai-`prefixed to mirror core's renamed provider feature keys\n * (`ai-openai` → `@warlock.js/ai-openai`, etc.).\n */\nexport const aiProviders: AiProviderOption[] = [\n { key: \"ai-openai\", label: \"OpenAI\", hint: \"GPT models via the OpenAI API\" },\n {\n key: \"ai-google\",\n label: \"Google (Gemini)\",\n hint: \"Gemini models via Google AI\",\n },\n {\n key: \"ai-anthropic\",\n label: \"Anthropic (Claude)\",\n hint: \"Claude models via the Anthropic API\",\n },\n {\n key: \"ai-bedrock\",\n label: \"AWS Bedrock\",\n hint: \"Foundation models via Amazon Bedrock\",\n },\n { key: \"ai-ollama\", label: \"Ollama\", hint: \"Local models via Ollama\" },\n];\n\n/**\n * AI capability packages offered alongside the providers in the dedicated AI\n * step. These are the satellite packages that sit on top of the core\n * `@warlock.js/ai` toolkit (each pulls the core `ai` package via its `requires`\n * in core's feature map, same as the providers).\n */\nexport const aiPackages: AiProviderOption[] = [\n {\n key: \"ai-tools\",\n label: \"AI Tools\",\n hint: \"Ready-made agent tools (web search, fetch, HTTP, calculator, date-time) + MCP client/server\",\n },\n {\n key: \"ai-panoptic\",\n label: \"AI Observability (Panoptic)\",\n hint: \"Tracing + exporters + a zero-setup local dashboard\",\n },\n {\n key: \"ai-workspace\",\n label: \"AI Workspace\",\n hint: \"Policy-jailed filesystem + shell for coding agents\",\n },\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\n .filter(feature => feature.defaultSelected)\n .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 * AI capability package options for the dedicated AI multiselect step. Offered\n * together with the providers (see `getAiProviderOptions`).\n */\nexport function getAiPackageOptions() {\n return aiPackages.map(pkg => ({\n value: pkg.key,\n label: pkg.label,\n hint: pkg.hint,\n }));\n}\n\n/**\n * Every selectable key the scaffolder knows about (features + AI providers +\n * AI capability packages). Used to validate `--features` / `--ai` flags in\n * non-interactive mode.\n */\nexport function getAllFeatureKeys(): string[] {\n return [\n ...features.map(feature => feature.key),\n ...aiProviders.map(provider => provider.key),\n ...aiPackages.map(pkg => pkg.key),\n ];\n}\n"],"mappings":";;;;;;AA6DA,MAAa,WAA4B;CAEvC;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;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;CACA;EACE,KAAK;EACL,OAAO;EACP,MAAM;EACN,OAAO;CACT;CACA;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;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;CAOA;EACE,KAAK;EACL,OAAO;EACP,MAAM;EACN,OAAO;CACT;AACF;;;;;;;;;AAiBA,MAAa,cAAkC;CAC7C;EAAE,KAAK;EAAa,OAAO;EAAU,MAAM;CAAgC;CAC3E;EACE,KAAK;EACL,OAAO;EACP,MAAM;CACR;CACA;EACE,KAAK;EACL,OAAO;EACP,MAAM;CACR;CACA;EACE,KAAK;EACL,OAAO;EACP,MAAM;CACR;CACA;EAAE,KAAK;EAAa,OAAO;EAAU,MAAM;CAA0B;AACvE;;;;;;;AAQA,MAAa,aAAiC;CAC5C;EACE,KAAK;EACL,OAAO;EACP,MAAM;CACR;CACA;EACE,KAAK;EACL,OAAO;EACP,MAAM;CACR;CACA;EACE,KAAK;EACL,OAAO;EACP,MAAM;CACR;AACF;;;;;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,SACJ,QAAO,YAAW,QAAQ,eAAe,CAAC,CAC1C,KAAI,YAAW,QAAQ,GAAG;AAC/B;;;;AAKA,SAAgB,uBAAuB;CACrC,OAAO,YAAY,KAAI,cAAa;EAClC,OAAO,SAAS;EAChB,OAAO,SAAS;EAChB,MAAM,SAAS;CACjB,EAAE;AACJ;;;;;AAMA,SAAgB,sBAAsB;CACpC,OAAO,WAAW,KAAI,SAAQ;EAC5B,OAAO,IAAI;EACX,OAAO,IAAI;EACX,MAAM,IAAI;CACZ,EAAE;AACJ;;;;;;AAOA,SAAgB,oBAA8B;CAC5C,OAAO;EACL,GAAG,SAAS,KAAI,YAAW,QAAQ,GAAG;EACtC,GAAG,YAAY,KAAI,aAAY,SAAS,GAAG;EAC3C,GAAG,WAAW,KAAI,QAAO,IAAI,GAAG;CAClC;AACF"}
|
|
1
|
+
{"version":3,"file":"features-map.mjs","names":[],"sources":["../../../../../../../packages/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`. That guard now\n * EXISTS: `builder/scripts/check-feature-parity.ts`, run by every publishing\n * script in `builder/package.json`, so a release aborts before `pkgist` starts.\n * It could not live in this repo: this package deliberately does not depend on\n * `@warlock.js/core`, and our own CI checks out only this repo, so core’s keys\n * are not present here to compare against. `builder` sits above both and\n * imports each by relative path, adding no dependency edge in either direction.\n *\n * A subset assertion ALONE is not enough and has already let a\n * bug ship: `--features=web,tailwind` was rejected with \"Unknown feature(s):\n * tailwind\" because `tailwind` and `shadcn` existed in core and were missing\n * from THIS file — a direction a scaffolder ⊆ core check cannot see. The guard\n * has to run both ways:\n *\n * 1. scaffolder ⊆ core — catches a typo or a key core renamed.\n * 2. core ⊆ scaffolder + an explicit omission list — catches a feature core\n * gained that never became scaffoldable.\n *\n * The two lists are not equal and should not be forced to be. As of this\n * writing core holds 28 keys and this file offers 24; the other four are\n * deliberate and are the entire allow-list direction (2) needs:\n * `mongodb`, `postgres`, `mysql` (the database driver has its own select) and\n * `ai` (pulled automatically by every `ai-*` key's `requires`). Anything beyond\n * those four is a gap, not a decision, and should fail the build.\n */\n\nexport type FeatureGroup =\n | \"Auth & Access\"\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 + capability packages (their own\n * step) are intentionally NOT here — they have dedicated prompts.\n */\nexport const features: FeatureOption[] = [\n // Auth & Access\n {\n key: \"access\",\n label: \"Access (authorization)\",\n hint: \"RBAC permission checks, ABAC policies, and roles — pairs with auth\",\n group: \"Auth & Access\",\n },\n {\n key: \"auth-google\",\n label: \"Google sign-in\",\n hint: \"Google OAuth sign-in for @warlock.js/auth\",\n group: \"Auth & Access\",\n },\n {\n key: \"auth-passkeys\",\n label: \"Passkey sign-in\",\n hint: \"WebAuthn passkey login for @warlock.js/auth\",\n group: \"Auth & Access\",\n },\n\n // Rendering & Mail\n {\n key: \"web\",\n label: \"Web (SSR React pages)\",\n hint: \"Server-rendered React pages on the same server as your API — scaffolds src/web and registers the connector\",\n group: \"Rendering & Mail\",\n },\n {\n key: \"tailwind\",\n label: \"Tailwind CSS\",\n hint: \"Tailwind v4 for the SSR page layer — creates src/web/app.css (the CSS-first config) and wires it through Vite (pulls web)\",\n group: \"Rendering & Mail\",\n },\n {\n key: \"shadcn\",\n label: \"shadcn/ui prerequisites\",\n hint: \"components.json, cn(), design tokens, and the web/* tsconfig path so `npx shadcn add <component>` works first time (pulls tailwind)\",\n group: \"Rendering & Mail\",\n },\n {\n key: \"sitemap\",\n label: \"Sitemap\",\n hint: \"Runtime sitemap.xml generation from the page registry — requires app.publicUrl or PUBLIC_APP_URL (pulls web)\",\n group: \"Rendering & Mail\",\n },\n {\n key: \"devtools\",\n label: \"Devtools (/__warlock)\",\n hint: \"Development-only dashboard: request timeline, queries + EXPLAIN, N+1 warnings, dev mailbox, logs, cache, routes — installed as a dev dependency, never runs in production\",\n group: \"Rendering & Mail\",\n },\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: \"queue\",\n label: \"Queue (BullMQ)\",\n hint: \"Durable Redis-backed background jobs with retries and workers\",\n group: \"Jobs & Messaging\",\n },\n {\n key: \"bull-board\",\n label: \"Bull Board (queue dashboard)\",\n hint: \"Web dashboard for inspecting queues and jobs — enabled outside production only, and refuses to mount unguarded (pulls queue)\",\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 key: \"notifications\",\n label: \"Notifications\",\n hint: \"Multi-channel notifications — mail + in-app database, preferences, idempotency\",\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/package 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 *\n * Keys are `ai-`prefixed to mirror core's renamed provider feature keys\n * (`ai-openai` → `@warlock.js/ai-openai`, etc.).\n */\nexport const aiProviders: AiProviderOption[] = [\n { key: \"ai-openai\", label: \"OpenAI\", hint: \"GPT models via the OpenAI API\" },\n {\n key: \"ai-google\",\n label: \"Google (Gemini)\",\n hint: \"Gemini models via Google AI\",\n },\n {\n key: \"ai-anthropic\",\n label: \"Anthropic (Claude)\",\n hint: \"Claude models via the Anthropic API\",\n },\n {\n key: \"ai-bedrock\",\n label: \"AWS Bedrock\",\n hint: \"Foundation models via Amazon Bedrock\",\n },\n { key: \"ai-ollama\", label: \"Ollama\", hint: \"Local models via Ollama\" },\n];\n\n/**\n * AI capability packages offered alongside the providers in the dedicated AI\n * step. These are the satellite packages that sit on top of the core\n * `@warlock.js/ai` toolkit (each pulls the core `ai` package via its `requires`\n * in core's feature map, same as the providers).\n */\nexport const aiPackages: AiProviderOption[] = [\n {\n key: \"ai-tools\",\n label: \"AI Tools\",\n hint: \"Ready-made agent tools (web search, fetch, HTTP, calculator, date-time) + MCP client/server\",\n },\n {\n key: \"ai-panoptic\",\n label: \"AI Observability (Panoptic)\",\n hint: \"Tracing + exporters + a zero-setup local dashboard\",\n },\n {\n key: \"ai-workspace\",\n label: \"AI Workspace\",\n hint: \"Policy-jailed filesystem + shell for coding agents\",\n },\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 * AI capability package options for the dedicated AI multiselect step. Offered\n * together with the providers (see `getAiProviderOptions`).\n */\nexport function getAiPackageOptions() {\n return aiPackages.map((pkg) => ({\n value: pkg.key,\n label: pkg.label,\n hint: pkg.hint,\n }));\n}\n\n/**\n * Every selectable key the scaffolder knows about (features + AI providers +\n * AI capability packages). Used to validate `--features` / `--ai` flags in\n * non-interactive mode.\n */\nexport function getAllFeatureKeys(): string[] {\n return [\n ...features.map((feature) => feature.key),\n ...aiProviders.map((provider) => provider.key),\n ...aiPackages.map((pkg) => pkg.key),\n ];\n}\n\n/** Feature sections in manifest order, with short lists for the grouped picker. */\nexport function getFeatureGroups() {\n const labels = [...new Set(features.map((feature) => feature.group))];\n return labels.map((label) => ({\n label,\n options: features\n .filter((feature) => feature.group === label)\n .map((feature) => ({\n value: feature.key,\n label: feature.label,\n hint: feature.hint,\n })),\n }));\n}\n"],"mappings":";;;;;;AA6DA,MAAa,WAA4B;CAEvC;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;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;CACA;EACE,KAAK;EACL,OAAO;EACP,MAAM;EACN,OAAO;CACT;CACA;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;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;CAOA;EACE,KAAK;EACL,OAAO;EACP,MAAM;EACN,OAAO;CACT;AACF;;;;;;;;;AAiBA,MAAa,cAAkC;CAC7C;EAAE,KAAK;EAAa,OAAO;EAAU,MAAM;CAAgC;CAC3E;EACE,KAAK;EACL,OAAO;EACP,MAAM;CACR;CACA;EACE,KAAK;EACL,OAAO;EACP,MAAM;CACR;CACA;EACE,KAAK;EACL,OAAO;EACP,MAAM;CACR;CACA;EAAE,KAAK;EAAa,OAAO;EAAU,MAAM;CAA0B;AACvE;;;;;;;AAQA,MAAa,aAAiC;CAC5C;EACE,KAAK;EACL,OAAO;EACP,MAAM;CACR;CACA;EACE,KAAK;EACL,OAAO;EACP,MAAM;CACR;CACA;EACE,KAAK;EACL,OAAO;EACP,MAAM;CACR;AACF;;;;AAiBA,SAAgB,wBAAkC;CAChD,OAAO,SAAS,QAAQ,YAAY,QAAQ,eAAe,CAAC,CAAC,KAAK,YAAY,QAAQ,GAAG;AAC3F;;;;AAKA,SAAgB,uBAAuB;CACrC,OAAO,YAAY,KAAK,cAAc;EACpC,OAAO,SAAS;EAChB,OAAO,SAAS;EAChB,MAAM,SAAS;CACjB,EAAE;AACJ;;;;;AAMA,SAAgB,sBAAsB;CACpC,OAAO,WAAW,KAAK,SAAS;EAC9B,OAAO,IAAI;EACX,OAAO,IAAI;EACX,MAAM,IAAI;CACZ,EAAE;AACJ;;;;;;AAOA,SAAgB,oBAA8B;CAC5C,OAAO;EACL,GAAG,SAAS,KAAK,YAAY,QAAQ,GAAG;EACxC,GAAG,YAAY,KAAK,aAAa,SAAS,GAAG;EAC7C,GAAG,WAAW,KAAK,QAAQ,IAAI,GAAG;CACpC;AACF;;AAGA,SAAgB,mBAAmB;CAEjC,OAAO,CADS,GAAG,IAAI,IAAI,SAAS,KAAK,YAAY,QAAQ,KAAK,CAAC,CACvD,CAAC,CAAC,KAAK,WAAW;EAC5B;EACA,SAAS,SACN,QAAQ,YAAY,QAAQ,UAAU,KAAK,CAAC,CAC5C,KAAK,aAAa;GACjB,OAAO,QAAQ;GACf,OAAO,QAAQ;GACf,MAAM,QAAQ;EAChB,EAAE;CACN,EAAE;AACJ"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assert-package-manager.mjs","names":[],"sources":["../../../../../../../packages/create-warlock/src/flags/assert-package-manager.ts"],"sourcesContent":["import { cancel } from \"@clack/prompts\";\nimport {\n ALLOWED_PACKAGE_MANAGERS,\n getSystemPackageManagers,\n isValidPackageManager,\n} from \"../helpers/package-manager\";\n\n/**\n * Is `--pm` spelled like a package manager we support? Availability validation\n * calls this first, so an unknown executable is rejected before installation.\n */\nexport function assertPackageManagerAllowed(
|
|
1
|
+
{"version":3,"file":"assert-package-manager.mjs","names":[],"sources":["../../../../../../../packages/create-warlock/src/flags/assert-package-manager.ts"],"sourcesContent":["import { cancel } from \"@clack/prompts\";\nimport {\n ALLOWED_PACKAGE_MANAGERS,\n getSystemPackageManagers,\n isValidPackageManager,\n} from \"../helpers/package-manager\";\n\n/**\n * Is `--pm` spelled like a package manager we support? Availability validation\n * calls this first, so an unknown executable is rejected before installation.\n */\nexport function assertPackageManagerAllowed(requested: string | undefined): void {\n if (!requested) return;\n\n if (!isValidPackageManager(requested)) {\n cancel(\n `Unknown package manager \"${requested}\" — expected one of: ${ALLOWED_PACKAGE_MANAGERS.join(\", \")}`,\n );\n process.exit(1);\n }\n}\n\n/**\n * Is `--pm` spelled right AND actually on this machine? Every CLI path uses\n * this stronger check before it selects a manager or begins scaffolding. The\n * scaffold always installs dependencies; accepting a known-unavailable\n * executable would leave a known partial project behind.\n *\n * `bun` is the case that makes this concrete. It is in\n * {@link ALLOWED_PACKAGE_MANAGERS}, so the spelling check passes.\n *\n * MUST be called after package-manager detection has completed, since\n * detection is what {@link getSystemPackageManagers} reports.\n */\nexport function assertPackageManagerAvailable(requested: string | undefined): void {\n if (requested === undefined) return;\n\n if (!requested.trim()) {\n cancel(\"The --pm flag requires a package manager value (npm, yarn, pnpm, or bun).\");\n process.exit(1);\n }\n\n assertPackageManagerAllowed(requested);\n\n const available = getSystemPackageManagers();\n\n if (!available.includes(requested)) {\n cancel(\n `Package manager \"${requested}\" was not found on this machine — detected: ${available.join(\", \")}. Install it, or pass one of those to --pm.`,\n );\n process.exit(1);\n }\n}\n"],"mappings":";;;;;;;;AAWA,SAAgB,4BAA4B,WAAqC;CAC/E,IAAI,CAAC,WAAW;CAEhB,IAAI,CAAC,sBAAsB,SAAS,GAAG;EACrC,OACE,4BAA4B,UAAU,uBAAuB,yBAAyB,KAAK,IAAI,GACjG;EACA,QAAQ,KAAK,CAAC;CAChB;AACF;;;;;;;;;;;;;AAcA,SAAgB,8BAA8B,WAAqC;CACjF,IAAI,cAAc,QAAW;CAE7B,IAAI,CAAC,UAAU,KAAK,GAAG;EACrB,OAAO,2EAA2E;EAClF,QAAQ,KAAK,CAAC;CAChB;CAEA,4BAA4B,SAAS;CAErC,MAAM,YAAY,yBAAyB;CAE3C,IAAI,CAAC,UAAU,SAAS,SAAS,GAAG;EAClC,OACE,oBAAoB,UAAU,8CAA8C,UAAU,KAAK,IAAI,EAAE,4CACnG;EACA,QAAQ,KAAK,CAAC;CAChB;AACF"}
|
|
@@ -33,6 +33,11 @@ const FLAG_DEFINITIONS = [
|
|
|
33
33
|
description: "Package manager to use (npm, yarn, pnpm, bun)",
|
|
34
34
|
defaultValue: "inferred from the invoking agent (npm_config_user_agent) or the system"
|
|
35
35
|
},
|
|
36
|
+
{
|
|
37
|
+
usage: "--lint=<oxc|eslint>",
|
|
38
|
+
description: "Linter and formatter: Oxlint/Oxfmt or ESLint/Prettier",
|
|
39
|
+
defaultValue: "eslint"
|
|
40
|
+
},
|
|
36
41
|
{
|
|
37
42
|
usage: "--agents=<list>",
|
|
38
43
|
description: "Comma-separated agent-kit targets to derive docs/skills for",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"definitions.mjs","names":[],"sources":["../../../../../../../packages/create-warlock/src/flags/definitions.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"definitions.mjs","names":[],"sources":["../../../../../../../packages/create-warlock/src/flags/definitions.ts"],"sourcesContent":["import { getAiPackageOptions, getAiProviderOptions } from \"../features/features-map\";\n\n/**\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\nconst AI_FEATURE_KEYS = [...getAiProviderOptions(), ...getAiPackageOptions()].map(\n ({ value }) => value,\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. Customizing everything else is a flow, not a stack: use --customize, where --stack=web arrives as a pre-ticked web feature\",\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 or capability keys (${AI_FEATURE_KEYS.join(\", \")})`,\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: \"--lint=<oxc|eslint>\",\n description: \"Linter and formatter: Oxlint/Oxfmt or ESLint/Prettier\",\n defaultValue: \"eslint\",\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: \"--customize, --interactive\",\n description:\n 'Full long-form wizard — every choice asked one by one. Also selectable as \"Customize\" in the \"What are we building?\" menu. Any other flag you pass pre-selects its prompt; --yes is refused',\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":";;;AAgBA,MAAM,kBAAkB,CAAC,GAAG,qBAAqB,GAAG,GAAG,oBAAoB,CAAC,CAAC,CAAC,KAC3E,EAAE,YAAY,KACjB;AAEA,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,mDAAmD,gBAAgB,KAAK,IAAI,EAAE;EAC3F,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,aACE;EACF,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"}
|
|
@@ -5,6 +5,7 @@ import { getPreferredPackageManager } from "../helpers/package-manager.mjs";
|
|
|
5
5
|
function seedFromFlags(cli) {
|
|
6
6
|
return {
|
|
7
7
|
packageManager: cli.pm ?? getPreferredPackageManager(),
|
|
8
|
+
lint: cli.lint === "oxc" ? "oxc" : "eslint",
|
|
8
9
|
databaseDriver: cli.db,
|
|
9
10
|
features: cli.features ?? defaultFeatures(cli),
|
|
10
11
|
aiProviders: cli.ai ?? [],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"seed-from-flags.mjs","names":[],"sources":["../../../../../../../packages/create-warlock/src/flags/seed-from-flags.ts"],"sourcesContent":["import type { CliFlags } from \"../commands/create-new-app/types\";\nimport { getDefaultFeatureKeys } from \"../features/features-map\";\nimport { getPreferredPackageManager } from \"../helpers/package-manager\";\n\n/**\n * The wizard's prompts, pre-selected from whatever the user already spelled\n * out on the command line.\n *\n * Before this existed, `--customize --db=postgres` silently threw the driver\n * away: the wizard asked its own question with its own default and the flag\n * never reached anything. A flag that is accepted and then ignored is the\n * worst of both worlds — the run succeeds, and the answer is wrong.\n *\n * So a flag passed alongside `--customize` is not a contradiction to refuse,\n * it is an answer given early: the prompt opens on that value and the user\n * can still change it.\n */\nexport type WizardSeeds = {\n packageManager: string;\n databaseDriver: string | undefined;\n features: string[];\n aiProviders: string[];\n useGit: boolean;\n useJWT: boolean;\n};\n\nexport function seedFromFlags(cli: CliFlags): WizardSeeds {\n return {\n packageManager: cli.pm ?? getPreferredPackageManager(),\n databaseDriver: cli.db,\n features: cli.features ?? defaultFeatures(cli),\n aiProviders: cli.ai ?? [],\n // clack's confirm opens on `true` when nothing is passed, which is the\n // behaviour these two prompts already had.\n useGit: cli.git ?? true,\n useJWT: cli.jwt ?? true,\n };\n}\n\n/**\n * `--stack=web` is the one structural answer, and the wizard has no stack\n * question of its own — it asks for features instead. So the stack arrives as\n * a pre-ticked `web` feature rather than being dropped on the floor.\n */\nfunction defaultFeatures(cli: CliFlags): string[] {\n const defaults = getDefaultFeatureKeys();\n\n if (cli.stack !== \"web\") return defaults;\n\n return [...new Set([...defaults, \"web\"])];\n}\n"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"seed-from-flags.mjs","names":[],"sources":["../../../../../../../packages/create-warlock/src/flags/seed-from-flags.ts"],"sourcesContent":["import type { CliFlags, LintToolchain } from \"../commands/create-new-app/types\";\nimport { getDefaultFeatureKeys } from \"../features/features-map\";\nimport { getPreferredPackageManager } from \"../helpers/package-manager\";\n\n/**\n * The wizard's prompts, pre-selected from whatever the user already spelled\n * out on the command line.\n *\n * Before this existed, `--customize --db=postgres` silently threw the driver\n * away: the wizard asked its own question with its own default and the flag\n * never reached anything. A flag that is accepted and then ignored is the\n * worst of both worlds — the run succeeds, and the answer is wrong.\n *\n * So a flag passed alongside `--customize` is not a contradiction to refuse,\n * it is an answer given early: the prompt opens on that value and the user\n * can still change it.\n */\nexport type WizardSeeds = {\n packageManager: string;\n databaseDriver: string | undefined;\n features: string[];\n aiProviders: string[];\n useGit: boolean;\n useJWT: boolean;\n lint: LintToolchain;\n};\n\nexport function seedFromFlags(cli: CliFlags): WizardSeeds {\n return {\n packageManager: cli.pm ?? getPreferredPackageManager(),\n lint: cli.lint === \"oxc\" ? \"oxc\" : \"eslint\",\n databaseDriver: cli.db,\n features: cli.features ?? defaultFeatures(cli),\n aiProviders: cli.ai ?? [],\n // clack's confirm opens on `true` when nothing is passed, which is the\n // behaviour these two prompts already had.\n useGit: cli.git ?? true,\n useJWT: cli.jwt ?? true,\n };\n}\n\n/**\n * `--stack=web` is the one structural answer, and the wizard has no stack\n * question of its own — it asks for features instead. So the stack arrives as\n * a pre-ticked `web` feature rather than being dropped on the floor.\n */\nfunction defaultFeatures(cli: CliFlags): string[] {\n const defaults = getDefaultFeatureKeys();\n\n if (cli.stack !== \"web\") return defaults;\n\n return [...new Set([...defaults, \"web\"])];\n}\n"],"mappings":";;;;AA2BA,SAAgB,cAAc,KAA4B;CACxD,OAAO;EACL,gBAAgB,IAAI,MAAM,2BAA2B;EACrD,MAAM,IAAI,SAAS,QAAQ,QAAQ;EACnC,gBAAgB,IAAI;EACpB,UAAU,IAAI,YAAY,gBAAgB,GAAG;EAC7C,aAAa,IAAI,MAAM,CAAC;EAGxB,QAAQ,IAAI,OAAO;EACnB,QAAQ,IAAI,OAAO;CACrB;AACF;;;;;;AAOA,SAAS,gBAAgB,KAAyB;CAChD,MAAM,WAAW,sBAAsB;CAEvC,IAAI,IAAI,UAAU,OAAO,OAAO;CAEhC,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,UAAU,KAAK,CAAC,CAAC;AAC1C"}
|
package/esm/helpers/app.mjs
CHANGED
|
@@ -124,6 +124,50 @@ var App = class {
|
|
|
124
124
|
pkg.save();
|
|
125
125
|
return this;
|
|
126
126
|
}
|
|
127
|
+
/** Apply the selected development tools before dependency installation. */
|
|
128
|
+
configureLintToolchain(toolchain = this.options.lint ?? "eslint") {
|
|
129
|
+
if (toolchain === "eslint") return this;
|
|
130
|
+
delete this.jsonFiles[path.resolve(this.path, "package.json")];
|
|
131
|
+
const pkg = this.package;
|
|
132
|
+
const content = pkg.content;
|
|
133
|
+
const deps = content.devDependencies;
|
|
134
|
+
for (const dependency of [
|
|
135
|
+
"@types/prettier",
|
|
136
|
+
"@typescript-eslint/eslint-plugin",
|
|
137
|
+
"@typescript-eslint/parser",
|
|
138
|
+
"eslint",
|
|
139
|
+
"eslint-config-prettier",
|
|
140
|
+
"eslint-plugin-prettier",
|
|
141
|
+
"eslint-plugin-unused-imports",
|
|
142
|
+
"prettier",
|
|
143
|
+
"prettier-plugin-organize-imports"
|
|
144
|
+
]) delete deps[dependency];
|
|
145
|
+
Object.assign(deps, {
|
|
146
|
+
oxlint: "1.88.0",
|
|
147
|
+
oxfmt: "0.73.0"
|
|
148
|
+
});
|
|
149
|
+
content.devDependencies = Object.fromEntries(Object.entries(deps).sort(([left], [right]) => left.localeCompare(right)));
|
|
150
|
+
Object.assign(content.scripts, {
|
|
151
|
+
lint: "oxlint --fix ./src --max-warnings=0",
|
|
152
|
+
"lint:check": "oxlint ./src --max-warnings=0",
|
|
153
|
+
format: "oxfmt --write ./src",
|
|
154
|
+
"format:check": "oxfmt --check ./src"
|
|
155
|
+
});
|
|
156
|
+
pkg.save();
|
|
157
|
+
for (const fileName of [
|
|
158
|
+
"eslint.config.js",
|
|
159
|
+
".prettierrc.json",
|
|
160
|
+
".prettierignore"
|
|
161
|
+
]) rmSync(path.join(this.path, fileName), { force: true });
|
|
162
|
+
for (const fileName of [".oxlintrc.json", ".oxfmtrc.json"]) copyFile(packageRoot("templates", "toolchains", "oxc", fileName), path.join(this.path, fileName));
|
|
163
|
+
for (const fileName of ["settings.json", "extensions.json"]) copyFile(packageRoot("templates", "toolchains", "oxc", ".vscode", fileName), path.join(this.path, ".vscode", fileName));
|
|
164
|
+
const config = this.file("warlock.config.ts");
|
|
165
|
+
if (!config.content.includes("healthCheckers:")) config.replace("export default defineConfig({", `export default defineConfig({
|
|
166
|
+
devServer: {
|
|
167
|
+
healthCheckers: { eslint: false, oxlint: true },
|
|
168
|
+
},`).save();
|
|
169
|
+
return this;
|
|
170
|
+
}
|
|
127
171
|
/**
|
|
128
172
|
* Configure the chosen database driver: wire `DB_DRIVER` / `DB_PORT` into
|
|
129
173
|
* `.env`, AND pin the driver's npm package (`mongodb` / `pg`) into the
|
package/esm/helpers/app.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app.mjs","names":[],"sources":["../../../../../../../packages/create-warlock/src/helpers/app.ts"],"sourcesContent":["import {\n copyDirectory,\n copyFile,\n fileExists,\n getFile,\n getJsonFile,\n putFile,\n putJsonFile,\n renameFile,\n} from \"@warlock.js/fs\";\nimport { existsSync, rmSync, unlinkSync } from \"node:fs\";\nimport path from \"path\";\nimport { Application, AppOptions } from \"../commands/create-new-app/types\";\nimport { getDatabaseDriver } from \"../features/database-drivers\";\nimport { executeCommand, runCommand } from \"./exec\";\nimport { insertImportInSortedPosition } from \"./insert-import\";\nimport { getPackageManager, getPnpmVersion } from \"./package-manager\";\nimport { packageRoot, Template, template } from \"./paths\";\nimport {\n BASE_PNPM_BUILD_APPROVALS,\n mergeOnlyBuiltDependencies,\n mergePackageJsonBuildApprovals,\n mergePnpmBuildApprovals,\n pnpmBuildApprovalLayout,\n requiredPnpmBuildApprovals,\n} from \"./pnpm-build-approvals\";\nimport { fallbackRange } from \"./warlock-versions\";\n\n/**\n * Environment for every install the scaffolder spawns.\n *\n * A shell that exports `NODE_ENV=production` makes the package managers skip\n * every devDependency — and still exit 0. The scaffolded project needs its dev\n * toolchain (typescript, vitest, eslint) to be usable at all, so the install\n * child is pinned to `development` regardless of the ambient environment. A\n * silently incomplete install that reports success is the exact failure mode\n * this file exists to prevent.\n */\nexport function installEnvironment(): NodeJS.ProcessEnv {\n return { NODE_ENV: \"development\" };\n}\n\n/** True when any ancestor of `targetPath` (starting at its parent) has a pnpm-workspace.yaml. */\nexport function isInsidePnpmWorkspace(targetPath: string): boolean {\n let dir = path.dirname(path.resolve(targetPath));\n\n while (true) {\n if (existsSync(path.join(dir, \"pnpm-workspace.yaml\"))) return true;\n const parent = path.dirname(dir);\n if (parent === dir) return false;\n dir = parent;\n }\n}\n\nexport class App {\n /**\n * Resolved files\n */\n protected files: Record<string, FileManager> = {};\n\n /**\n * Resolved JSON files\n */\n protected jsonFiles: Record<string, JsonFileManager> = {};\n\n public isInstalled = false;\n\n public constructor(protected app: Application) {}\n\n public get options(): AppOptions {\n return this.app.options;\n }\n\n public use(templateName: Template) {\n copyDirectory(template(templateName), this.path);\n\n if (fileExists(this.path + \"/.env.example\")) {\n copyFile(this.path + \"/.env.example\", this.path + \"/.env\");\n }\n\n renameFile(this.path + \"/_.gitignore\", this.path + \"/.gitignore\");\n\n // Inside an existing pnpm workspace the parent owns the policy; a per-app\n // workspace file would nest a second workspace root.\n if (isInsidePnpmWorkspace(this.path)) {\n rmSync(path.join(this.path, \"pnpm-workspace.yaml\"), { force: true });\n } else if (getPackageManager() === \"pnpm\") {\n this.fitPnpmSettingsToVersion(BASE_PNPM_BUILD_APPROVALS);\n }\n\n return this;\n }\n\n /**\n * Write install-script approvals where the user's pnpm reads them. The\n * template ships the pnpm 10.26+ layout (settings-only pnpm-workspace.yaml\n * with `allowBuilds`); see pnpmBuildApprovalLayout for what older versions\n * need and why.\n */\n protected fitPnpmSettingsToVersion(packages: readonly string[]) {\n const workspacePath = path.join(this.path, \"pnpm-workspace.yaml\");\n const layout = pnpmBuildApprovalLayout(getPnpmVersion());\n\n if (layout === \"packageJson\") {\n // pnpm 7-10.4 refuse a workspace file without `packages:`, and adding\n // one breaks `pnpm add` in the app. The template's other setting,\n // minimumReleaseAgeExclude, does not exist before pnpm 10.16.\n rmSync(workspacePath, { force: true });\n const packageJsonPath = path.join(this.path, \"package.json\");\n const packageJson = getJsonFile(packageJsonPath) as Record<\n string,\n unknown\n >;\n putJsonFile(\n packageJsonPath,\n mergePackageJsonBuildApprovals(packageJson, packages),\n );\n // Drop any cached copy so later package.json edits start from this one.\n delete this.jsonFiles[path.resolve(packageJsonPath)];\n return;\n }\n\n if (!fileExists(workspacePath)) return;\n\n const current = getFile(workspacePath) as string;\n let next = mergePnpmBuildApprovals(current, packages);\n\n if (layout === \"onlyBuiltDependencies\") {\n next = mergeOnlyBuiltDependencies(next, packages);\n }\n\n if (next !== current) putFile(workspacePath, next);\n }\n\n public init() {\n return this;\n }\n\n public terminate() {\n // No longer using outro, using showSuccessScreen instead\n }\n\n public install() {\n return runCommand(getPackageManager(), [\"install\"], this.path, {\n env: installEnvironment(),\n });\n }\n\n /** Record native build approvals before pnpm installs selected feature dependencies. */\n public approveSelectedFeatureBuilds(features: readonly string[]) {\n if (getPackageManager() !== \"pnpm\") return this;\n const packages = requiredPnpmBuildApprovals(features);\n if (packages.length === 0) return this;\n // Inside a parent workspace the parent's policy decides; leave it alone.\n if (isInsidePnpmWorkspace(this.path)) return this;\n this.fitPnpmSettingsToVersion(packages);\n return this;\n }\n\n public async exec(command: string) {\n const [commandName, ...optionsList] = command.split(\" \");\n return await executeCommand(commandName, optionsList, this.path);\n }\n\n public async git() {\n const { initializeGitRepository } =\n await import(\"./project-builder-helpers\");\n return await initializeGitRepository(this.path);\n }\n\n /**\n * Write the project's `package.json`: the project name and the version of\n * every `@warlock.js/*` dependency. No template script names a package\n * manager, so nothing here depends on the one the user chose.\n *\n * `versions` comes from {@link resolveWarlockVersions} — versions the\n * registry has confirmed exist. It is optional because the fluent chain is\n * synchronous; without it every sibling gets the caret range floored to the\n * scaffolder's major, which is always satisfiable by a published release.\n *\n * What it must NEVER do again is stamp the scaffolder's own version blind:\n * the release tooling bumps that version on every build, published or not,\n * so an unverified pin resolves to nothing and the install dies with ETARGET.\n */\n public updatePackageJson(versions: Record<string, string> = {}) {\n const pkg = this.package.replace(\"name\", this.name.replaceAll(\"/\", \"-\"));\n\n const content: any = pkg.content;\n\n const warlockVersion: string = (\n getJsonFile(packageRoot(\"package.json\")) as { version: string }\n ).version;\n const defaultRange = fallbackRange(warlockVersion);\n\n for (const field of [\"dependencies\", \"devDependencies\"] as const) {\n const deps = content[field] as Record<string, string> | undefined;\n if (!deps) continue;\n\n for (const dependency of Object.keys(deps)) {\n if (dependency.startsWith(\"@warlock.js/\")) {\n deps[dependency] = versions[dependency] ?? defaultRange;\n }\n }\n }\n\n pkg.save();\n\n return this;\n }\n\n /**\n * Configure the chosen database driver: wire `DB_DRIVER` / `DB_PORT` into\n * `.env`, AND pin the driver's npm package (`mongodb` / `pg`) into the\n * project's `package.json` dependencies.\n *\n * The dependency is written HERE — before the base `yarn install` — so the\n * driver is pulled deterministically by the very first install. We do NOT\n * rely on the post-copy `warlock add <driver> --no-install` + separate\n * batched install, which can be skipped or fail and leave the driver\n * missing (the \"mongodb package is not installed\" runtime error).\n */\n public configureDatabaseEnv(driverValue: string) {\n const driver = getDatabaseDriver(driverValue);\n\n if (!driver) return this;\n\n // Pin the driver package into dependencies (idempotent — never downgrade).\n const packageJsonPath = path.resolve(this.path, \"package.json\");\n const packageJson = getJsonFile(packageJsonPath) as {\n dependencies?: Record<string, string>;\n };\n packageJson.dependencies = packageJson.dependencies ?? {};\n if (!packageJson.dependencies[driver.package]) {\n packageJson.dependencies[driver.package] = driver.packageVersion;\n putJsonFile(packageJsonPath, packageJson);\n }\n\n // Both files carry the driver defaults: `.env` is what runs, `.env.example`\n // is what the next developer copies.\n for (const fileName of [\".env\", \".env.example\"]) {\n const envPath = this.path + \"/\" + fileName;\n if (!fileExists(envPath)) continue;\n\n let envContent = getFile(envPath) as string;\n\n envContent = envContent.replace(\n /DB_PORT=\\d+/,\n `DB_PORT=${driver.defaultPort}`,\n );\n\n if (envContent.includes(\"DB_DRIVER=\")) {\n envContent = envContent.replace(\n /DB_DRIVER=\\w*/,\n `DB_DRIVER=${driver.value}`,\n );\n } else {\n envContent = envContent.replace(\n /DB_PORT=\\d+/,\n `DB_PORT=${driver.defaultPort}\\nDB_DRIVER=${driver.value}`,\n );\n }\n\n // DB_AUTH is Mongo's authSource; no other driver reads it.\n if (driver.value !== \"mongodb\") {\n envContent = envContent.replace(/^DB_AUTH=.*\\r?\\n?/m, \"\");\n }\n\n putFile(envPath, envContent);\n }\n\n return this;\n }\n\n /**\n * Write the correct `src/config/database.ts` for the chosen driver and remove\n * every per-driver variant so the scaffold ships exactly one config file.\n *\n * The template ships a Mongo-shaped `database.ts` (the default) plus a\n * `database.postgres.ts` variant. Mongo keeps the default in place; Postgres\n * overwrites `database.ts` with the Postgres-shaped variant (typed\n * `ConnectionOptions`, `driver: \"postgres\"`, port 5432, no Mongo-only keys —\n * no `authSource`, `driverOptions`, `replicaSet`, `randomIncrement` /\n * `initialId`). Either way the `.postgres` variant file is deleted afterwards\n * so no leftover config ships in the generated project.\n *\n * For Postgres it also corrects the working `.env`: the Mongo template ships\n * `DB_AUTH=admin`, an `authSource` value Postgres ignores and its config no\n * longer reads, so the line is dropped to keep `.env` and the config in sync.\n * The port is rewritten by {@link configureDatabaseEnv} (driver defaultPort),\n * which runs alongside this in the same branch.\n */\n public configureDatabaseConfig(driverValue: string) {\n const configDir = path.resolve(this.path, \"src/config\");\n const databasePath = path.resolve(configDir, \"database.ts\");\n const postgresVariantPath = path.resolve(configDir, \"database.postgres.ts\");\n\n if (driverValue === \"postgres\" && fileExists(postgresVariantPath)) {\n putFile(databasePath, getFile(postgresVariantPath) as string);\n\n const envPath = this.path + \"/.env\";\n\n if (fileExists(envPath)) {\n const envContent = getFile(envPath) as string;\n // Drop the Mongo-only DB_AUTH line entirely (its whole line, so no blank\n // gap is left behind). Postgres has no authSource concept.\n putFile(envPath, envContent.replace(/^DB_AUTH=.*\\r?\\n?/m, \"\"));\n }\n }\n\n // Whatever the driver, no per-driver variant may survive into the scaffold.\n if (fileExists(postgresVariantPath)) {\n unlinkSync(postgresVariantPath);\n }\n\n return this;\n }\n\n /**\n * Remove the database layer for a \"no database\" scaffold.\n *\n * Deletes `src/config/database.ts` (and a `.tsx` / per-driver variant if\n * present) from the freshly-copied template. The framework's database\n * connector is config-gated on that file — with it gone,\n * `config.get(\"database\")` is undefined and the connector no-ops, so the app\n * boots with no database wired and no driver package pulled. The `DB_*` lines\n * in `.env` are left in place (harmless: no config reads them) as a ready\n * template for adding a database back later.\n */\n public removeDatabaseConfig() {\n const configDir = path.resolve(this.path, \"src/config\");\n\n for (const fileName of [\n \"database.ts\",\n \"database.tsx\",\n \"database.postgres.ts\",\n ]) {\n const filePath = path.resolve(configDir, fileName);\n\n if (fileExists(filePath)) {\n unlinkSync(filePath);\n }\n }\n\n return this;\n }\n\n /**\n * Configure the web starter only when the web feature owns `/`.\n *\n * A dependency-free HTTP response is retained without web; otherwise the\n * SSR page loader owns the route.\n * Exactly one survives the scaffold: web projects keep the SSR page and API\n * example; every other project keeps the dependency-free JSON controller.\n */\n public configureWebStarter(useWeb: boolean) {\n const remove = (relativePath: string) => {\n const target = path.resolve(this.path, relativePath);\n if (existsSync(target)) rmSync(target, { recursive: true, force: true });\n };\n\n if (!useWeb) {\n for (const entry of [\n \"src/web\",\n \"src/app/contact\",\n \"src/app/locale\",\n \"src/shared/contact.schema.ts\",\n \"src/shared/locale.schema.ts\",\n \"src/shared/locales.ts\",\n \"postcss.config.mjs\",\n \"skills/web-code-standards\",\n ]) {\n remove(entry);\n }\n\n const agentsPath = path.resolve(this.path, \"AGENTS.md\");\n const agents = getFile(agentsPath) as string;\n putFile(\n agentsPath,\n agents.replace(/^.*\\*\\*web-code-standards\\*\\*.*\\r?\\n/m, \"\"),\n );\n\n const tsconfigPath = path.resolve(this.path, \"tsconfig.json\");\n const tsconfig = getFile(tsconfigPath) as string;\n putFile(\n tsconfigPath,\n tsconfig\n .replace(' \"web/*\": [\"./src/web/*\"],\\n', \"\")\n .replace(' \"@shared/*\": [\"./src/shared/*\"]\\n', \"\"),\n );\n\n return this;\n }\n\n // The SSR page owns `/`; its loader still uses src/app/home/services.\n remove(\"src/app/home/controllers/home-page.controller.ts\");\n remove(\"src/app/home/routes.ts\");\n\n const packageJsonPath = path.resolve(this.path, \"package.json\");\n const packageJson = getJsonFile(packageJsonPath) as {\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n };\n packageJson.dependencies = {\n ...packageJson.dependencies,\n \"@mongez/http\": \"^3.5.0\",\n \"@mongez/react-form\": \"^4.0.0\",\n };\n packageJson.devDependencies = {\n ...packageJson.devDependencies,\n \"@tailwindcss/postcss\": \"^4.1.16\",\n tailwindcss: \"^4.1.16\",\n };\n putJsonFile(packageJsonPath, packageJson);\n\n const configPath = path.resolve(this.path, \"warlock.config.ts\");\n const config = getFile(configPath) as string;\n if (!config.includes('\"@warlock.js/web/connector\"')) {\n // Inserted in sorted position, not prepended. The generated app formats\n // with `prettier-plugin-organize-imports`, so an import pushed to the top\n // of a file that already imports `@warlock.js/auth` and `@warlock.js/core`\n // is out of order the moment it is written — and the developer meets it as\n // a `prettier/prettier` error on their first `warlock dev`, in a file they\n // did not write.\n const withConnector = insertImportInSortedPosition(\n config,\n 'import { webConnector } from \"@warlock.js/web/connector\";',\n \"@warlock.js/web/connector\",\n );\n\n putFile(\n configPath,\n withConnector.replace(\n \"export default defineConfig({\",\n \"export default defineConfig({\\n connectors: [webConnector()],\",\n ),\n );\n }\n\n return this;\n }\n\n /**\n * Record non-default `agent-kit` targets into the generated `package.json`'s\n * `agentKit.targets` block.\n *\n * The template's own `postinstall` (`agent-kit init && agent-kit sync`)\n * already targets `claude` with no flags, so the single-target default case\n * needs no template edit at all — this only writes when the selection is\n * anything else, keeping the scaffold diff-free for the common path.\n */\n public configureAgentKitTargets(agents: string[]) {\n const isDefault = agents.length === 1 && agents[0] === \"claude\";\n if (isDefault) return this;\n\n const packageJsonPath = path.resolve(this.path, \"package.json\");\n if (!fileExists(packageJsonPath)) return this;\n\n const packageJson = getJsonFile(packageJsonPath) as {\n agentKit?: { targets?: string[] };\n };\n packageJson.agentKit = { ...packageJson.agentKit, targets: agents };\n putJsonFile(packageJsonPath, packageJson);\n\n return this;\n }\n\n /**\n * Install the selected optional features by delegating to the project's own\n * `warlock add`. `--no-install` records every dependency in package.json and\n * ejects configs / scripts / setup hooks WITHOUT installing — the caller runs\n * one batched install afterwards. Versions come from core's feature map, so\n * the scaffolder never duplicates them.\n *\n * `--no-install` is passed LAST on purpose: the CLI parser treats the\n * positional after a bare flag as that flag's value, so it must follow the\n * feature list, not precede it.\n */\n public async installFeatures(features: string[]) {\n if (features.length === 0) return true;\n\n return this.exec(`npx warlock add ${features.join(\" \")} --no-install`);\n }\n\n /**\n * Force a single copy of `vite` when the project ends up with BOTH `vite`\n * (written by the `web` feature) and `vitest` (always in the template).\n *\n * `vitest` depends on vite through a wide range of its own\n * (`^6 || ^7 || ^8`), which resolves to a DIFFERENT major than the range the\n * feature map pins. Yarn 1 then has to nest the second copy under\n * `vitest/node_modules` and dies on its own linker invariant:\n *\n * error Invariant Violation: could not find a copy of vite to link in\n * <project>/node_modules/vitest/node_modules\n *\n * It aborts BEFORE `node_modules/.bin` is written, so `warlock` is never\n * linked and the very first `yarn dev` fails with \"'warlock' is not\n * recognized\". npm and pnpm nest happily and never hit this — for them the\n * `overrides` twin below is one copy instead of two, not a crash fix.\n *\n * The pin is READ BACK from whatever range the project's own package.json\n * declares, never re-stated as a literal here: the feature map in\n * `@warlock.js/core` owns the version, and a second copy of it would drift\n * the moment that one moves.\n *\n * Returns whether a pin was written, so the caller (and its spec) can assert\n * it instead of assuming it.\n */\n public pinViteResolution() {\n const packageJsonPath = path.resolve(this.path, \"package.json\");\n\n if (!fileExists(packageJsonPath)) return false;\n\n const packageJson = getJsonFile(packageJsonPath) as {\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n resolutions?: Record<string, string>;\n overrides?: Record<string, string>;\n };\n\n const declaredRange = (name: string) =>\n packageJson.dependencies?.[name] ?? packageJson.devDependencies?.[name];\n\n const viteRange = declaredRange(\"vite\");\n\n // No vite, or no vitest to conflict with it — nothing to pin. Writing a\n // resolution for a package the project does not depend on would only\n // freeze a transitive tree nobody asked us to freeze.\n if (!viteRange || !declaredRange(\"vitest\")) return false;\n\n // yarn 1 / yarn berry read `resolutions`; npm 8+ and pnpm read `overrides`.\n packageJson.resolutions = { ...packageJson.resolutions, vite: viteRange };\n packageJson.overrides = { ...packageJson.overrides, vite: viteRange };\n\n putJsonFile(packageJsonPath, packageJson);\n\n return true;\n }\n\n /**\n * Get package json file\n */\n public get package() {\n return this.json(\"package.json\");\n }\n\n public updateDotEnv() {\n this.file(\".env\").replaceAll(\"appName\", this.name).save();\n\n return this;\n }\n\n /**\n * Get env file to update\n */\n public get env() {\n return this.file(\".env\");\n }\n\n public get name() {\n return this.app.appName;\n }\n\n public get path() {\n return this.app.appPath;\n }\n\n public file(relativePath: string) {\n const fullPath = path.resolve(this.path, relativePath);\n\n if (!this.files[fullPath]) {\n this.files[fullPath] = file(fullPath);\n }\n\n return this.files[fullPath];\n }\n\n public json(relativePath: string): JsonFileManager {\n const fullPath = path.resolve(this.path, relativePath);\n\n if (!this.jsonFiles[fullPath]) {\n this.jsonFiles[fullPath] = jsonFile(fullPath);\n }\n\n return this.jsonFiles[fullPath];\n }\n}\n\nexport function app(app: Application) {\n return new App(app);\n}\n\nexport class FileManager {\n public content!: string;\n public constructor(protected filePath: string) {\n this.parseContent();\n }\n\n protected parseContent() {\n this.content = getFile(this.filePath) as string;\n }\n\n public replace(search: string, replace: string) {\n this.content = this.content.replace(search, replace);\n\n return this;\n }\n\n public replaceAll(search: string, replace: string) {\n this.content = this.content.replaceAll(search, replace);\n\n return this;\n }\n\n public save() {\n putFile(this.filePath, this.content);\n }\n}\n\nexport class JsonFileManager extends FileManager {\n protected parseContent() {\n this.content = getJsonFile(this.filePath);\n }\n\n public save() {\n putJsonFile(this.filePath, this.content);\n }\n\n public has(key: string) {\n return this.content[key] !== undefined;\n }\n\n public replace(key: string, value: any) {\n this.content[key] = value;\n\n return this;\n }\n\n public replaceAll(key: string, value: any) {\n const contentAsString = JSON.stringify(this.content);\n\n this.content = JSON.parse(contentAsString.replaceAll(key, value));\n\n return this;\n }\n}\n\nexport function file(path: string) {\n return new FileManager(path);\n}\n\nexport function jsonFile(path: string) {\n return new JsonFileManager(path);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"app.mjs","names":[],"sources":["../../../../../../../packages/create-warlock/src/helpers/app.ts"],"sourcesContent":["import {\n copyDirectory,\n copyFile,\n fileExists,\n getFile,\n getJsonFile,\n putFile,\n putJsonFile,\n renameFile,\n} from \"@warlock.js/fs\";\nimport { existsSync, rmSync, unlinkSync } from \"node:fs\";\nimport path from \"path\";\nimport { Application, AppOptions, LintToolchain } from \"../commands/create-new-app/types\";\nimport { getDatabaseDriver } from \"../features/database-drivers\";\nimport { executeCommand, runCommand } from \"./exec\";\nimport { insertImportInSortedPosition } from \"./insert-import\";\nimport { getPackageManager, getPnpmVersion } from \"./package-manager\";\nimport { packageRoot, Template, template } from \"./paths\";\nimport {\n BASE_PNPM_BUILD_APPROVALS,\n mergeOnlyBuiltDependencies,\n mergePackageJsonBuildApprovals,\n mergePnpmBuildApprovals,\n pnpmBuildApprovalLayout,\n requiredPnpmBuildApprovals,\n} from \"./pnpm-build-approvals\";\nimport { fallbackRange } from \"./warlock-versions\";\n\n/**\n * Environment for every install the scaffolder spawns.\n *\n * A shell that exports `NODE_ENV=production` makes the package managers skip\n * every devDependency — and still exit 0. The scaffolded project needs its dev\n * toolchain (typescript, vitest, eslint) to be usable at all, so the install\n * child is pinned to `development` regardless of the ambient environment. A\n * silently incomplete install that reports success is the exact failure mode\n * this file exists to prevent.\n */\nexport function installEnvironment(): NodeJS.ProcessEnv {\n return { NODE_ENV: \"development\" };\n}\n\n/** True when any ancestor of `targetPath` (starting at its parent) has a pnpm-workspace.yaml. */\nexport function isInsidePnpmWorkspace(targetPath: string): boolean {\n let dir = path.dirname(path.resolve(targetPath));\n\n while (true) {\n if (existsSync(path.join(dir, \"pnpm-workspace.yaml\"))) return true;\n const parent = path.dirname(dir);\n if (parent === dir) return false;\n dir = parent;\n }\n}\n\nexport class App {\n /**\n * Resolved files\n */\n protected files: Record<string, FileManager> = {};\n\n /**\n * Resolved JSON files\n */\n protected jsonFiles: Record<string, JsonFileManager> = {};\n\n public isInstalled = false;\n\n public constructor(protected app: Application) {}\n\n public get options(): AppOptions {\n return this.app.options;\n }\n\n public use(templateName: Template) {\n copyDirectory(template(templateName), this.path);\n\n if (fileExists(this.path + \"/.env.example\")) {\n copyFile(this.path + \"/.env.example\", this.path + \"/.env\");\n }\n\n renameFile(this.path + \"/_.gitignore\", this.path + \"/.gitignore\");\n\n // Inside an existing pnpm workspace the parent owns the policy; a per-app\n // workspace file would nest a second workspace root.\n if (isInsidePnpmWorkspace(this.path)) {\n rmSync(path.join(this.path, \"pnpm-workspace.yaml\"), { force: true });\n } else if (getPackageManager() === \"pnpm\") {\n this.fitPnpmSettingsToVersion(BASE_PNPM_BUILD_APPROVALS);\n }\n\n return this;\n }\n\n /**\n * Write install-script approvals where the user's pnpm reads them. The\n * template ships the pnpm 10.26+ layout (settings-only pnpm-workspace.yaml\n * with `allowBuilds`); see pnpmBuildApprovalLayout for what older versions\n * need and why.\n */\n protected fitPnpmSettingsToVersion(packages: readonly string[]) {\n const workspacePath = path.join(this.path, \"pnpm-workspace.yaml\");\n const layout = pnpmBuildApprovalLayout(getPnpmVersion());\n\n if (layout === \"packageJson\") {\n // pnpm 7-10.4 refuse a workspace file without `packages:`, and adding\n // one breaks `pnpm add` in the app. The template's other setting,\n // minimumReleaseAgeExclude, does not exist before pnpm 10.16.\n rmSync(workspacePath, { force: true });\n const packageJsonPath = path.join(this.path, \"package.json\");\n const packageJson = getJsonFile(packageJsonPath) as Record<string, unknown>;\n putJsonFile(packageJsonPath, mergePackageJsonBuildApprovals(packageJson, packages));\n // Drop any cached copy so later package.json edits start from this one.\n delete this.jsonFiles[path.resolve(packageJsonPath)];\n return;\n }\n\n if (!fileExists(workspacePath)) return;\n\n const current = getFile(workspacePath) as string;\n let next = mergePnpmBuildApprovals(current, packages);\n\n if (layout === \"onlyBuiltDependencies\") {\n next = mergeOnlyBuiltDependencies(next, packages);\n }\n\n if (next !== current) putFile(workspacePath, next);\n }\n\n public init() {\n return this;\n }\n\n public terminate() {\n // No longer using outro, using showSuccessScreen instead\n }\n\n public install() {\n return runCommand(getPackageManager(), [\"install\"], this.path, {\n env: installEnvironment(),\n });\n }\n\n /** Record native build approvals before pnpm installs selected feature dependencies. */\n public approveSelectedFeatureBuilds(features: readonly string[]) {\n if (getPackageManager() !== \"pnpm\") return this;\n const packages = requiredPnpmBuildApprovals(features);\n if (packages.length === 0) return this;\n // Inside a parent workspace the parent's policy decides; leave it alone.\n if (isInsidePnpmWorkspace(this.path)) return this;\n this.fitPnpmSettingsToVersion(packages);\n return this;\n }\n\n public async exec(command: string) {\n const [commandName, ...optionsList] = command.split(\" \");\n return await executeCommand(commandName, optionsList, this.path);\n }\n\n public async git() {\n const { initializeGitRepository } = await import(\"./project-builder-helpers\");\n return await initializeGitRepository(this.path);\n }\n\n /**\n * Write the project's `package.json`: the project name and the version of\n * every `@warlock.js/*` dependency. No template script names a package\n * manager, so nothing here depends on the one the user chose.\n *\n * `versions` comes from {@link resolveWarlockVersions} — versions the\n * registry has confirmed exist. It is optional because the fluent chain is\n * synchronous; without it every sibling gets the caret range floored to the\n * scaffolder's major, which is always satisfiable by a published release.\n *\n * What it must NEVER do again is stamp the scaffolder's own version blind:\n * the release tooling bumps that version on every build, published or not,\n * so an unverified pin resolves to nothing and the install dies with ETARGET.\n */\n public updatePackageJson(versions: Record<string, string> = {}) {\n const pkg = this.package.replace(\"name\", this.name.replaceAll(\"/\", \"-\"));\n\n const content: any = pkg.content;\n\n const warlockVersion: string = (getJsonFile(packageRoot(\"package.json\")) as { version: string })\n .version;\n const defaultRange = fallbackRange(warlockVersion);\n\n for (const field of [\"dependencies\", \"devDependencies\"] as const) {\n const deps = content[field] as Record<string, string> | undefined;\n if (!deps) continue;\n\n for (const dependency of Object.keys(deps)) {\n if (dependency.startsWith(\"@warlock.js/\")) {\n deps[dependency] = versions[dependency] ?? defaultRange;\n }\n }\n }\n\n pkg.save();\n\n return this;\n }\n\n /** Apply the selected development tools before dependency installation. */\n public configureLintToolchain(toolchain: LintToolchain = this.options.lint ?? \"eslint\") {\n if (toolchain === \"eslint\") return this;\n\n // Database and web setup may have edited package.json after it was cached.\n delete this.jsonFiles[path.resolve(this.path, \"package.json\")];\n const pkg = this.package;\n const content = pkg.content as unknown as {\n devDependencies: Record<string, string>;\n scripts: Record<string, string>;\n };\n const deps = content.devDependencies;\n for (const dependency of [\n \"@types/prettier\",\n \"@typescript-eslint/eslint-plugin\",\n \"@typescript-eslint/parser\",\n \"eslint\",\n \"eslint-config-prettier\",\n \"eslint-plugin-prettier\",\n \"eslint-plugin-unused-imports\",\n \"prettier\",\n \"prettier-plugin-organize-imports\",\n ])\n delete deps[dependency];\n Object.assign(deps, { oxlint: \"1.88.0\", oxfmt: \"0.73.0\" });\n content.devDependencies = Object.fromEntries(\n Object.entries(deps).sort(([left], [right]) => left.localeCompare(right)),\n );\n Object.assign(content.scripts, {\n lint: \"oxlint --fix ./src --max-warnings=0\",\n \"lint:check\": \"oxlint ./src --max-warnings=0\",\n format: \"oxfmt --write ./src\",\n \"format:check\": \"oxfmt --check ./src\",\n });\n pkg.save();\n\n for (const fileName of [\"eslint.config.js\", \".prettierrc.json\", \".prettierignore\"]) {\n rmSync(path.join(this.path, fileName), { force: true });\n }\n for (const fileName of [\".oxlintrc.json\", \".oxfmtrc.json\"]) {\n copyFile(\n packageRoot(\"templates\", \"toolchains\", \"oxc\", fileName),\n path.join(this.path, fileName),\n );\n }\n\n for (const fileName of [\"settings.json\", \"extensions.json\"]) {\n copyFile(\n packageRoot(\"templates\", \"toolchains\", \"oxc\", \".vscode\", fileName),\n path.join(this.path, \".vscode\", fileName),\n );\n }\n\n const config = this.file(\"warlock.config.ts\");\n if (!config.content.includes(\"healthCheckers:\")) {\n config\n .replace(\n \"export default defineConfig({\",\n `export default defineConfig({\n devServer: {\n healthCheckers: { eslint: false, oxlint: true },\n },`,\n )\n .save();\n }\n return this;\n }\n\n /**\n * Configure the chosen database driver: wire `DB_DRIVER` / `DB_PORT` into\n * `.env`, AND pin the driver's npm package (`mongodb` / `pg`) into the\n * project's `package.json` dependencies.\n *\n * The dependency is written HERE — before the base `yarn install` — so the\n * driver is pulled deterministically by the very first install. We do NOT\n * rely on the post-copy `warlock add <driver> --no-install` + separate\n * batched install, which can be skipped or fail and leave the driver\n * missing (the \"mongodb package is not installed\" runtime error).\n */\n public configureDatabaseEnv(driverValue: string) {\n const driver = getDatabaseDriver(driverValue);\n\n if (!driver) return this;\n\n // Pin the driver package into dependencies (idempotent — never downgrade).\n const packageJsonPath = path.resolve(this.path, \"package.json\");\n const packageJson = getJsonFile(packageJsonPath) as {\n dependencies?: Record<string, string>;\n };\n packageJson.dependencies = packageJson.dependencies ?? {};\n if (!packageJson.dependencies[driver.package]) {\n packageJson.dependencies[driver.package] = driver.packageVersion;\n putJsonFile(packageJsonPath, packageJson);\n }\n\n // Both files carry the driver defaults: `.env` is what runs, `.env.example`\n // is what the next developer copies.\n for (const fileName of [\".env\", \".env.example\"]) {\n const envPath = this.path + \"/\" + fileName;\n if (!fileExists(envPath)) continue;\n\n let envContent = getFile(envPath) as string;\n\n envContent = envContent.replace(/DB_PORT=\\d+/, `DB_PORT=${driver.defaultPort}`);\n\n if (envContent.includes(\"DB_DRIVER=\")) {\n envContent = envContent.replace(/DB_DRIVER=\\w*/, `DB_DRIVER=${driver.value}`);\n } else {\n envContent = envContent.replace(\n /DB_PORT=\\d+/,\n `DB_PORT=${driver.defaultPort}\\nDB_DRIVER=${driver.value}`,\n );\n }\n\n // DB_AUTH is Mongo's authSource; no other driver reads it.\n if (driver.value !== \"mongodb\") {\n envContent = envContent.replace(/^DB_AUTH=.*\\r?\\n?/m, \"\");\n }\n\n putFile(envPath, envContent);\n }\n\n return this;\n }\n\n /**\n * Write the correct `src/config/database.ts` for the chosen driver and remove\n * every per-driver variant so the scaffold ships exactly one config file.\n *\n * The template ships a Mongo-shaped `database.ts` (the default) plus a\n * `database.postgres.ts` variant. Mongo keeps the default in place; Postgres\n * overwrites `database.ts` with the Postgres-shaped variant (typed\n * `ConnectionOptions`, `driver: \"postgres\"`, port 5432, no Mongo-only keys —\n * no `authSource`, `driverOptions`, `replicaSet`, `randomIncrement` /\n * `initialId`). Either way the `.postgres` variant file is deleted afterwards\n * so no leftover config ships in the generated project.\n *\n * For Postgres it also corrects the working `.env`: the Mongo template ships\n * `DB_AUTH=admin`, an `authSource` value Postgres ignores and its config no\n * longer reads, so the line is dropped to keep `.env` and the config in sync.\n * The port is rewritten by {@link configureDatabaseEnv} (driver defaultPort),\n * which runs alongside this in the same branch.\n */\n public configureDatabaseConfig(driverValue: string) {\n const configDir = path.resolve(this.path, \"src/config\");\n const databasePath = path.resolve(configDir, \"database.ts\");\n const postgresVariantPath = path.resolve(configDir, \"database.postgres.ts\");\n\n if (driverValue === \"postgres\" && fileExists(postgresVariantPath)) {\n putFile(databasePath, getFile(postgresVariantPath) as string);\n\n const envPath = this.path + \"/.env\";\n\n if (fileExists(envPath)) {\n const envContent = getFile(envPath) as string;\n // Drop the Mongo-only DB_AUTH line entirely (its whole line, so no blank\n // gap is left behind). Postgres has no authSource concept.\n putFile(envPath, envContent.replace(/^DB_AUTH=.*\\r?\\n?/m, \"\"));\n }\n }\n\n // Whatever the driver, no per-driver variant may survive into the scaffold.\n if (fileExists(postgresVariantPath)) {\n unlinkSync(postgresVariantPath);\n }\n\n return this;\n }\n\n /**\n * Remove the database layer for a \"no database\" scaffold.\n *\n * Deletes `src/config/database.ts` (and a `.tsx` / per-driver variant if\n * present) from the freshly-copied template. The framework's database\n * connector is config-gated on that file — with it gone,\n * `config.get(\"database\")` is undefined and the connector no-ops, so the app\n * boots with no database wired and no driver package pulled. The `DB_*` lines\n * in `.env` are left in place (harmless: no config reads them) as a ready\n * template for adding a database back later.\n */\n public removeDatabaseConfig() {\n const configDir = path.resolve(this.path, \"src/config\");\n\n for (const fileName of [\"database.ts\", \"database.tsx\", \"database.postgres.ts\"]) {\n const filePath = path.resolve(configDir, fileName);\n\n if (fileExists(filePath)) {\n unlinkSync(filePath);\n }\n }\n\n return this;\n }\n\n /**\n * Configure the web starter only when the web feature owns `/`.\n *\n * A dependency-free HTTP response is retained without web; otherwise the\n * SSR page loader owns the route.\n * Exactly one survives the scaffold: web projects keep the SSR page and API\n * example; every other project keeps the dependency-free JSON controller.\n */\n public configureWebStarter(useWeb: boolean) {\n const remove = (relativePath: string) => {\n const target = path.resolve(this.path, relativePath);\n if (existsSync(target)) rmSync(target, { recursive: true, force: true });\n };\n\n if (!useWeb) {\n for (const entry of [\n \"src/web\",\n \"src/app/contact\",\n \"src/app/locale\",\n \"src/shared/contact.schema.ts\",\n \"src/shared/locale.schema.ts\",\n \"src/shared/locales.ts\",\n \"postcss.config.mjs\",\n \"skills/web-code-standards\",\n ]) {\n remove(entry);\n }\n\n const agentsPath = path.resolve(this.path, \"AGENTS.md\");\n const agents = getFile(agentsPath) as string;\n putFile(agentsPath, agents.replace(/^.*\\*\\*web-code-standards\\*\\*.*\\r?\\n/m, \"\"));\n\n const tsconfigPath = path.resolve(this.path, \"tsconfig.json\");\n const tsconfig = getFile(tsconfigPath) as string;\n putFile(\n tsconfigPath,\n tsconfig\n .replace(' \"web/*\": [\"./src/web/*\"],\\n', \"\")\n .replace(' \"@shared/*\": [\"./src/shared/*\"]\\n', \"\"),\n );\n\n return this;\n }\n\n // The SSR page owns `/`; its loader still uses src/app/home/services.\n remove(\"src/app/home/controllers/home-page.controller.ts\");\n remove(\"src/app/home/routes.ts\");\n\n const packageJsonPath = path.resolve(this.path, \"package.json\");\n const packageJson = getJsonFile(packageJsonPath) as {\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n };\n packageJson.dependencies = {\n ...packageJson.dependencies,\n \"@mongez/http\": \"^3.5.0\",\n \"@mongez/react-form\": \"^4.0.0\",\n };\n packageJson.devDependencies = {\n ...packageJson.devDependencies,\n \"@tailwindcss/postcss\": \"^4.1.16\",\n tailwindcss: \"^4.1.16\",\n };\n putJsonFile(packageJsonPath, packageJson);\n\n const configPath = path.resolve(this.path, \"warlock.config.ts\");\n const config = getFile(configPath) as string;\n if (!config.includes('\"@warlock.js/web/connector\"')) {\n // Inserted in sorted position, not prepended. The generated app formats\n // with `prettier-plugin-organize-imports`, so an import pushed to the top\n // of a file that already imports `@warlock.js/auth` and `@warlock.js/core`\n // is out of order the moment it is written — and the developer meets it as\n // a `prettier/prettier` error on their first `warlock dev`, in a file they\n // did not write.\n const withConnector = insertImportInSortedPosition(\n config,\n 'import { webConnector } from \"@warlock.js/web/connector\";',\n \"@warlock.js/web/connector\",\n );\n\n putFile(\n configPath,\n withConnector.replace(\n \"export default defineConfig({\",\n \"export default defineConfig({\\n connectors: [webConnector()],\",\n ),\n );\n }\n\n return this;\n }\n\n /**\n * Record non-default `agent-kit` targets into the generated `package.json`'s\n * `agentKit.targets` block.\n *\n * The template's own `postinstall` (`agent-kit init && agent-kit sync`)\n * already targets `claude` with no flags, so the single-target default case\n * needs no template edit at all — this only writes when the selection is\n * anything else, keeping the scaffold diff-free for the common path.\n */\n public configureAgentKitTargets(agents: string[]) {\n const isDefault = agents.length === 1 && agents[0] === \"claude\";\n if (isDefault) return this;\n\n const packageJsonPath = path.resolve(this.path, \"package.json\");\n if (!fileExists(packageJsonPath)) return this;\n\n const packageJson = getJsonFile(packageJsonPath) as {\n agentKit?: { targets?: string[] };\n };\n packageJson.agentKit = { ...packageJson.agentKit, targets: agents };\n putJsonFile(packageJsonPath, packageJson);\n\n return this;\n }\n\n /**\n * Install the selected optional features by delegating to the project's own\n * `warlock add`. `--no-install` records every dependency in package.json and\n * ejects configs / scripts / setup hooks WITHOUT installing — the caller runs\n * one batched install afterwards. Versions come from core's feature map, so\n * the scaffolder never duplicates them.\n *\n * `--no-install` is passed LAST on purpose: the CLI parser treats the\n * positional after a bare flag as that flag's value, so it must follow the\n * feature list, not precede it.\n */\n public async installFeatures(features: string[]) {\n if (features.length === 0) return true;\n\n return this.exec(`npx warlock add ${features.join(\" \")} --no-install`);\n }\n\n /**\n * Force a single copy of `vite` when the project ends up with BOTH `vite`\n * (written by the `web` feature) and `vitest` (always in the template).\n *\n * `vitest` depends on vite through a wide range of its own\n * (`^6 || ^7 || ^8`), which resolves to a DIFFERENT major than the range the\n * feature map pins. Yarn 1 then has to nest the second copy under\n * `vitest/node_modules` and dies on its own linker invariant:\n *\n * error Invariant Violation: could not find a copy of vite to link in\n * <project>/node_modules/vitest/node_modules\n *\n * It aborts BEFORE `node_modules/.bin` is written, so `warlock` is never\n * linked and the very first `yarn dev` fails with \"'warlock' is not\n * recognized\". npm and pnpm nest happily and never hit this — for them the\n * `overrides` twin below is one copy instead of two, not a crash fix.\n *\n * The pin is READ BACK from whatever range the project's own package.json\n * declares, never re-stated as a literal here: the feature map in\n * `@warlock.js/core` owns the version, and a second copy of it would drift\n * the moment that one moves.\n *\n * Returns whether a pin was written, so the caller (and its spec) can assert\n * it instead of assuming it.\n */\n public pinViteResolution() {\n const packageJsonPath = path.resolve(this.path, \"package.json\");\n\n if (!fileExists(packageJsonPath)) return false;\n\n const packageJson = getJsonFile(packageJsonPath) as {\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n resolutions?: Record<string, string>;\n overrides?: Record<string, string>;\n };\n\n const declaredRange = (name: string) =>\n packageJson.dependencies?.[name] ?? packageJson.devDependencies?.[name];\n\n const viteRange = declaredRange(\"vite\");\n\n // No vite, or no vitest to conflict with it — nothing to pin. Writing a\n // resolution for a package the project does not depend on would only\n // freeze a transitive tree nobody asked us to freeze.\n if (!viteRange || !declaredRange(\"vitest\")) return false;\n\n // yarn 1 / yarn berry read `resolutions`; npm 8+ and pnpm read `overrides`.\n packageJson.resolutions = { ...packageJson.resolutions, vite: viteRange };\n packageJson.overrides = { ...packageJson.overrides, vite: viteRange };\n\n putJsonFile(packageJsonPath, packageJson);\n\n return true;\n }\n\n /**\n * Get package json file\n */\n public get package() {\n return this.json(\"package.json\");\n }\n\n public updateDotEnv() {\n this.file(\".env\").replaceAll(\"appName\", this.name).save();\n\n return this;\n }\n\n /**\n * Get env file to update\n */\n public get env() {\n return this.file(\".env\");\n }\n\n public get name() {\n return this.app.appName;\n }\n\n public get path() {\n return this.app.appPath;\n }\n\n public file(relativePath: string) {\n const fullPath = path.resolve(this.path, relativePath);\n\n if (!this.files[fullPath]) {\n this.files[fullPath] = file(fullPath);\n }\n\n return this.files[fullPath];\n }\n\n public json(relativePath: string): JsonFileManager {\n const fullPath = path.resolve(this.path, relativePath);\n\n if (!this.jsonFiles[fullPath]) {\n this.jsonFiles[fullPath] = jsonFile(fullPath);\n }\n\n return this.jsonFiles[fullPath];\n }\n}\n\nexport function app(app: Application) {\n return new App(app);\n}\n\nexport class FileManager {\n public content!: string;\n public constructor(protected filePath: string) {\n this.parseContent();\n }\n\n protected parseContent() {\n this.content = getFile(this.filePath) as string;\n }\n\n public replace(search: string, replace: string) {\n this.content = this.content.replace(search, replace);\n\n return this;\n }\n\n public replaceAll(search: string, replace: string) {\n this.content = this.content.replaceAll(search, replace);\n\n return this;\n }\n\n public save() {\n putFile(this.filePath, this.content);\n }\n}\n\nexport class JsonFileManager extends FileManager {\n protected parseContent() {\n this.content = getJsonFile(this.filePath);\n }\n\n public save() {\n putJsonFile(this.filePath, this.content);\n }\n\n public has(key: string) {\n return this.content[key] !== undefined;\n }\n\n public replace(key: string, value: any) {\n this.content[key] = value;\n\n return this;\n }\n\n public replaceAll(key: string, value: any) {\n const contentAsString = JSON.stringify(this.content);\n\n this.content = JSON.parse(contentAsString.replaceAll(key, value));\n\n return this;\n }\n}\n\nexport function file(path: string) {\n return new FileManager(path);\n}\n\nexport function jsonFile(path: string) {\n return new JsonFileManager(path);\n}\n"],"mappings":"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"}
|
package/esm/helpers/exec.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"exec.mjs","names":["spawn"],"sources":["../../../../../../../packages/create-warlock/src/helpers/exec.ts"],"sourcesContent":["import { log } from \"@clack/prompts\";\nimport { colors } from \"@mongez/copper\";\nimport { ChildProcess } from \"child_process\";\nimport { default as childProcess, default as spawn } from \"cross-spawn\";\n\n/**\n * The full outcome of a spawned command — everything a human needs to act on a\n * failure: what ran, where, how it ended, and what it printed.\n *\n * Every runner in this file produces one. A boolean is never enough: \"it\n * failed\" with no command, no exit code and no stderr is exactly how a broken\n * scaffold gets announced as a success.\n */\nexport type CommandResult = {\n /** Whether the command exited cleanly (code 0, no spawn error). */\n ok: boolean;\n /** The command as typed, e.g. `npm install`. */\n command: string;\n /** Directory the command ran in, when known. */\n cwd?: string;\n /** Exit code, or `null` when the process was signalled / never spawned. */\n code: number | null;\n /** Terminating signal, when the process was killed. */\n signal: NodeJS.Signals | null;\n stdout: string;\n stderr: string;\n /** Set when the process could not be spawned at all (ENOENT, EACCES, ...). */\n error?: Error;\n};\n\n/** Options accepted by the capturing runners. */\nexport type RunOptions = {\n /** Extra environment for the child; merged over `process.env`. */\n env?: NodeJS.ProcessEnv;\n};\n\n/**\n * The last command executed through {@link executeCommand}.\n *\n * `executeCommand` resolves a bare boolean (a contract several callers depend\n * on), so the *reason* a command failed would otherwise be lost. It is parked\n * here and claimed with {@link takeLastCommandOutput}, which clears it — a\n * caller can never accidentally attribute a stale failure to the wrong step.\n *\n * The scaffolder runs one command at a time, so a single slot is enough.\n */\nlet lastCommandOutput: CommandResult | undefined;\n\n/**\n * Claim (and clear) the output of the most recent {@link executeCommand} call.\n */\nexport function takeLastCommandOutput(): CommandResult | undefined {\n const output = lastCommandOutput;\n\n lastCommandOutput = undefined;\n\n return output;\n}\n\n/**\n * Keep only the last `lines` non-empty lines of a stream — enough context to\n * act on, without dumping a 500-line npm log over the wizard.\n */\nexport function tail(text: string, lines = 12): string {\n return text\n .split(/\\r?\\n/)\n .filter(line => line.trim().length > 0)\n .slice(-lines)\n .join(\"\\n\");\n}\n\nexport default async function exec(command: string, options: any = {}) {\n const [commandName, ...optionsList] = command.split(\" \");\n\n const commandOutput = childProcess.sync(commandName, optionsList, options);\n\n // it means command didn't end as expected, then stop the rest of the program\n if (commandOutput.error !== null) {\n process.exit(1);\n }\n\n return commandOutput;\n}\n\n/**\n * Attach stdout/stderr collectors to a child and return the accumulated text.\n *\n * The streams are optional on purpose: a child spawned with `stdio: \"ignore\"`\n * (or a test double) exposes none.\n */\nfunction collectOutput(child: ChildProcess) {\n const chunks = { stdout: \"\", stderr: \"\" };\n\n child.stdout?.on(\"data\", data => {\n chunks.stdout += String(data);\n });\n\n child.stderr?.on(\"data\", data => {\n chunks.stderr += String(data);\n });\n\n return chunks;\n}\n\nfunction logSpawnError(error: unknown) {\n if (!error) return;\n\n const message = (error as Error)?.message;\n\n log.error(colors.red(String(message ?? error)) + `\\n\\n`);\n}\n\n/**\n * This function directly executes a command\n *\n * Resolves a boolean for backwards compatibility; the full outcome (exit code\n * and captured output) is available to the caller via\n * {@link takeLastCommandOutput}.\n */\nexport async function executeCommand(cmd: string, args: string[], cwd: string) {\n const result = await runCapturedCommand(cmd, args, cwd);\n\n lastCommandOutput = result;\n\n return result.ok;\n}\n\n/**\n * Run a command to completion and resolve its full {@link CommandResult}.\n *\n * Output is piped and captured rather than discarded — the whole point is that\n * a failure can be explained instead of merely announced.\n */\nexport function runCapturedCommand(\n cmd: string,\n args: string[],\n cwd: string,\n options: RunOptions = {},\n): Promise<CommandResult> {\n return new Promise<CommandResult>(resolve => {\n const command = [cmd, ...args].join(\" \");\n\n const settle = (result: Partial<CommandResult>): void =>\n resolve({\n ok: false,\n command,\n cwd,\n code: null,\n signal: null,\n stdout: \"\",\n stderr: \"\",\n ...result,\n });\n\n let child: ChildProcess;\n\n try {\n child = spawn(cmd, args, {\n cwd,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n env: options.env ? { ...process.env, ...options.env } : process.env,\n });\n } catch (error) {\n logSpawnError(error);\n\n return settle({ error: error as Error, stderr: String(error) });\n }\n\n const output = collectOutput(child);\n\n child.on(\"error\", error => {\n logSpawnError(error);\n\n settle({\n error: error as Error,\n stdout: output.stdout,\n stderr: output.stderr || String((error as Error)?.message ?? error),\n });\n });\n\n child.on(\"close\", (code, signal) => {\n settle({\n ok: code === 0,\n code,\n signal,\n stdout: output.stdout,\n stderr: output.stderr,\n });\n });\n });\n}\n\n/**\n * Run a long command with an abort handle.\n *\n * `install` stays a `Promise<boolean>` for existing callers; `result` carries\n * the exit code and the captured output so a failing install can be reported\n * with the command, its status and its stderr instead of a shrug.\n */\nexport function runCommand(
|
|
1
|
+
{"version":3,"file":"exec.mjs","names":["spawn"],"sources":["../../../../../../../packages/create-warlock/src/helpers/exec.ts"],"sourcesContent":["import { log } from \"@clack/prompts\";\nimport { colors } from \"@mongez/copper\";\nimport { ChildProcess } from \"child_process\";\nimport { default as childProcess, default as spawn } from \"cross-spawn\";\n\n/**\n * The full outcome of a spawned command — everything a human needs to act on a\n * failure: what ran, where, how it ended, and what it printed.\n *\n * Every runner in this file produces one. A boolean is never enough: \"it\n * failed\" with no command, no exit code and no stderr is exactly how a broken\n * scaffold gets announced as a success.\n */\nexport type CommandResult = {\n /** Whether the command exited cleanly (code 0, no spawn error). */\n ok: boolean;\n /** The command as typed, e.g. `npm install`. */\n command: string;\n /** Directory the command ran in, when known. */\n cwd?: string;\n /** Exit code, or `null` when the process was signalled / never spawned. */\n code: number | null;\n /** Terminating signal, when the process was killed. */\n signal: NodeJS.Signals | null;\n stdout: string;\n stderr: string;\n /** Set when the process could not be spawned at all (ENOENT, EACCES, ...). */\n error?: Error;\n};\n\n/** Options accepted by the capturing runners. */\nexport type RunOptions = {\n /** Extra environment for the child; merged over `process.env`. */\n env?: NodeJS.ProcessEnv;\n};\n\n/**\n * The last command executed through {@link executeCommand}.\n *\n * `executeCommand` resolves a bare boolean (a contract several callers depend\n * on), so the *reason* a command failed would otherwise be lost. It is parked\n * here and claimed with {@link takeLastCommandOutput}, which clears it — a\n * caller can never accidentally attribute a stale failure to the wrong step.\n *\n * The scaffolder runs one command at a time, so a single slot is enough.\n */\nlet lastCommandOutput: CommandResult | undefined;\n\n/**\n * Claim (and clear) the output of the most recent {@link executeCommand} call.\n */\nexport function takeLastCommandOutput(): CommandResult | undefined {\n const output = lastCommandOutput;\n\n lastCommandOutput = undefined;\n\n return output;\n}\n\n/**\n * Keep only the last `lines` non-empty lines of a stream — enough context to\n * act on, without dumping a 500-line npm log over the wizard.\n */\nexport function tail(text: string, lines = 12): string {\n return text\n .split(/\\r?\\n/)\n .filter((line) => line.trim().length > 0)\n .slice(-lines)\n .join(\"\\n\");\n}\n\nexport default async function exec(command: string, options: any = {}) {\n const [commandName, ...optionsList] = command.split(\" \");\n\n const commandOutput = childProcess.sync(commandName, optionsList, options);\n\n // it means command didn't end as expected, then stop the rest of the program\n if (commandOutput.error !== null) {\n process.exit(1);\n }\n\n return commandOutput;\n}\n\n/**\n * Attach stdout/stderr collectors to a child and return the accumulated text.\n *\n * The streams are optional on purpose: a child spawned with `stdio: \"ignore\"`\n * (or a test double) exposes none.\n */\nfunction collectOutput(child: ChildProcess) {\n const chunks = { stdout: \"\", stderr: \"\" };\n\n child.stdout?.on(\"data\", (data) => {\n chunks.stdout += String(data);\n });\n\n child.stderr?.on(\"data\", (data) => {\n chunks.stderr += String(data);\n });\n\n return chunks;\n}\n\nfunction logSpawnError(error: unknown) {\n if (!error) return;\n\n const message = (error as Error)?.message;\n\n log.error(colors.red(String(message ?? error)) + `\\n\\n`);\n}\n\n/**\n * This function directly executes a command\n *\n * Resolves a boolean for backwards compatibility; the full outcome (exit code\n * and captured output) is available to the caller via\n * {@link takeLastCommandOutput}.\n */\nexport async function executeCommand(cmd: string, args: string[], cwd: string) {\n const result = await runCapturedCommand(cmd, args, cwd);\n\n lastCommandOutput = result;\n\n return result.ok;\n}\n\n/**\n * Run a command to completion and resolve its full {@link CommandResult}.\n *\n * Output is piped and captured rather than discarded — the whole point is that\n * a failure can be explained instead of merely announced.\n */\nexport function runCapturedCommand(\n cmd: string,\n args: string[],\n cwd: string,\n options: RunOptions = {},\n): Promise<CommandResult> {\n return new Promise<CommandResult>((resolve) => {\n const command = [cmd, ...args].join(\" \");\n\n const settle = (result: Partial<CommandResult>): void =>\n resolve({\n ok: false,\n command,\n cwd,\n code: null,\n signal: null,\n stdout: \"\",\n stderr: \"\",\n ...result,\n });\n\n let child: ChildProcess;\n\n try {\n child = spawn(cmd, args, {\n cwd,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n env: options.env ? { ...process.env, ...options.env } : process.env,\n });\n } catch (error) {\n logSpawnError(error);\n\n return settle({ error: error as Error, stderr: String(error) });\n }\n\n const output = collectOutput(child);\n\n child.on(\"error\", (error) => {\n logSpawnError(error);\n\n settle({\n error: error as Error,\n stdout: output.stdout,\n stderr: output.stderr || String((error as Error)?.message ?? error),\n });\n });\n\n child.on(\"close\", (code, signal) => {\n settle({\n ok: code === 0,\n code,\n signal,\n stdout: output.stdout,\n stderr: output.stderr,\n });\n });\n });\n}\n\n/**\n * Run a long command with an abort handle.\n *\n * `install` stays a `Promise<boolean>` for existing callers; `result` carries\n * the exit code and the captured output so a failing install can be reported\n * with the command, its status and its stderr instead of a shrug.\n */\nexport function runCommand(cmd: string, args: string[], cwd: string, options: RunOptions = {}) {\n let child: ChildProcess;\n\n const result = new Promise<CommandResult>((resolve) => {\n const command = [cmd, ...args].join(\" \");\n\n const settle = (partial: Partial<CommandResult>): void =>\n resolve({\n ok: false,\n command,\n cwd,\n code: null,\n signal: null,\n stdout: \"\",\n stderr: \"\",\n ...partial,\n });\n\n try {\n child = spawn(cmd, args, {\n cwd,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n env: options.env ? { ...process.env, ...options.env } : process.env,\n });\n\n const output = collectOutput(child);\n\n child.on(\"error\", (error) => {\n logSpawnError(error);\n\n settle({\n error: error as Error,\n stdout: output.stdout,\n stderr: output.stderr || String((error as Error)?.message ?? error),\n });\n });\n\n child.on(\"close\", (code, signal) => {\n settle({\n ok: code === 0,\n code,\n signal,\n stdout: output.stdout,\n stderr: output.stderr,\n });\n });\n } catch (error) {\n settle({ error: error as Error, stderr: String(error) });\n }\n });\n\n const abort = async () => {\n if (child) {\n child.kill(\"SIGINT\");\n }\n };\n\n return { abort, install: result.then((outcome) => outcome.ok), result };\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"insert-import.mjs","names":[],"sources":["../../../../../../../packages/create-warlock/src/helpers/insert-import.ts"],"sourcesContent":["/**\n * Splice an `import` statement into a generated source file **in sorted\n * position**, rather than pushing it to the top.\n *\n * The scaffolded app formats with `prettier-plugin-organize-imports`, which\n * orders imports alphabetically by module specifier. So an import prepended to\n * a file that already has some is, by construction, in the wrong place — and\n * the developer meets it as a `prettier/prettier` error on their very first\n * `warlock dev`, in a file they did not write, about code they did not add.\n *\n * That is the defect class this scaffolder keeps paying for: a failure that\n * waits for the user's first ordinary action and then arrives attributed to\n * them.\n */\n\n/**\n * Matches a top-level import statement and captures its module specifier.\n *\n * Deliberately narrow: this reads files THIS scaffolder generates from its own\n * templates, not arbitrary user source. It handles the two forms those\n * templates actually contain — a single-line import and a braced multi-line one\n * — and is not a general TypeScript parser. If a template ever grows an import\n * shape this misses, the insertion falls back to appending after the last match,\n * which is still ordered relative to everything it did recognise.\n */\nconst IMPORT_STATEMENT = /^import\\s[\\s\\S]*?from\\s+[\"']([^\"']+)[\"'];?$/gm;\n\n/**\n * Insert `statement` into `source` so the import block stays alphabetically\n * ordered by module specifier.\n *\n * @param source the file's current contents.\n * @param statement the whole import line to add, without a trailing newline.\n * @param specifier the module specifier `statement` imports from — passed\n * rather than re-parsed, because the caller already knows it\n * and a second parser is a second thing to disagree.\n * @returns the file contents with the import in place. A file with no imports\n * at all gets it at the top, which is the only ordering available.\n */\nexport function insertImportInSortedPosition(\n source: string,\n statement: string,\n specifier: string,\n): string {\n const matches = [...source.matchAll(IMPORT_STATEMENT)];\n\n if (matches.length === 0) {\n return `${statement}\\n${source}`;\n }\n\n const successor = matches.find(
|
|
1
|
+
{"version":3,"file":"insert-import.mjs","names":[],"sources":["../../../../../../../packages/create-warlock/src/helpers/insert-import.ts"],"sourcesContent":["/**\n * Splice an `import` statement into a generated source file **in sorted\n * position**, rather than pushing it to the top.\n *\n * The scaffolded app formats with `prettier-plugin-organize-imports`, which\n * orders imports alphabetically by module specifier. So an import prepended to\n * a file that already has some is, by construction, in the wrong place — and\n * the developer meets it as a `prettier/prettier` error on their very first\n * `warlock dev`, in a file they did not write, about code they did not add.\n *\n * That is the defect class this scaffolder keeps paying for: a failure that\n * waits for the user's first ordinary action and then arrives attributed to\n * them.\n */\n\n/**\n * Matches a top-level import statement and captures its module specifier.\n *\n * Deliberately narrow: this reads files THIS scaffolder generates from its own\n * templates, not arbitrary user source. It handles the two forms those\n * templates actually contain — a single-line import and a braced multi-line one\n * — and is not a general TypeScript parser. If a template ever grows an import\n * shape this misses, the insertion falls back to appending after the last match,\n * which is still ordered relative to everything it did recognise.\n */\nconst IMPORT_STATEMENT = /^import\\s[\\s\\S]*?from\\s+[\"']([^\"']+)[\"'];?$/gm;\n\n/**\n * Insert `statement` into `source` so the import block stays alphabetically\n * ordered by module specifier.\n *\n * @param source the file's current contents.\n * @param statement the whole import line to add, without a trailing newline.\n * @param specifier the module specifier `statement` imports from — passed\n * rather than re-parsed, because the caller already knows it\n * and a second parser is a second thing to disagree.\n * @returns the file contents with the import in place. A file with no imports\n * at all gets it at the top, which is the only ordering available.\n */\nexport function insertImportInSortedPosition(\n source: string,\n statement: string,\n specifier: string,\n): string {\n const matches = [...source.matchAll(IMPORT_STATEMENT)];\n\n if (matches.length === 0) {\n return `${statement}\\n${source}`;\n }\n\n const successor = matches.find((match) => (match[1] as string).localeCompare(specifier) > 0);\n\n if (successor) {\n return `${source.slice(0, successor.index)}${statement}\\n${source.slice(successor.index)}`;\n }\n\n const last = matches[matches.length - 1] as RegExpMatchArray;\n const endOfLast = (last.index as number) + last[0].length;\n\n return `${source.slice(0, endOfLast)}\\n${statement}${source.slice(endOfLast)}`;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAyBA,MAAM,mBAAmB;;;;;;;;;;;;;AAczB,SAAgB,6BACd,QACA,WACA,WACQ;CACR,MAAM,UAAU,CAAC,GAAG,OAAO,SAAS,gBAAgB,CAAC;CAErD,IAAI,QAAQ,WAAW,GACrB,OAAO,GAAG,UAAU,IAAI;CAG1B,MAAM,YAAY,QAAQ,MAAM,UAAW,MAAM,EAAE,CAAY,cAAc,SAAS,IAAI,CAAC;CAE3F,IAAI,WACF,OAAO,GAAG,OAAO,MAAM,GAAG,UAAU,KAAK,IAAI,UAAU,IAAI,OAAO,MAAM,UAAU,KAAK;CAGzF,MAAM,OAAO,QAAQ,QAAQ,SAAS;CACtC,MAAM,YAAa,KAAK,QAAmB,KAAK,EAAE,CAAC;CAEnD,OAAO,GAAG,OAAO,MAAM,GAAG,SAAS,EAAE,IAAI,YAAY,OAAO,MAAM,SAAS;AAC7E"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"package-manager.mjs","names":[],"sources":["../../../../../../../packages/create-warlock/src/helpers/package-manager.ts"],"sourcesContent":["import { exec, execSync } from \"child_process\";\nimport { promisify } from \"util\";\nimport detectPackageManager from \"which-pm-runs\";\n\nconst execAsync = promisify(exec);\n\nlet detectedPackageManager: string | undefined;\nlet cachedSystemManagers: string[] | undefined;\nlet cachedPreferredManager: string | undefined;\n\n/**\n * The only package managers `--pm` may select. This value reaches `spawn()`\n * as the executable to run and is spliced into the generated\n * `package.json`'s scripts — an allow-list here is a hard security\n * boundary, not just input hygiene, so it stays a fixed literal list\n * rather than anything derived from user input or the running environment.\n */\nexport const ALLOWED_PACKAGE_MANAGERS = [\"npm\", \"yarn\", \"pnpm\", \"bun\"] as const;\n\nexport type AllowedPackageManager = (typeof ALLOWED_PACKAGE_MANAGERS)[number];\n\n/** Whether `value` is one of the allow-listed package managers. */\nexport function isValidPackageManager(
|
|
1
|
+
{"version":3,"file":"package-manager.mjs","names":[],"sources":["../../../../../../../packages/create-warlock/src/helpers/package-manager.ts"],"sourcesContent":["import { exec, execSync } from \"child_process\";\nimport { promisify } from \"util\";\nimport detectPackageManager from \"which-pm-runs\";\n\nconst execAsync = promisify(exec);\n\nlet detectedPackageManager: string | undefined;\nlet cachedSystemManagers: string[] | undefined;\nlet cachedPreferredManager: string | undefined;\n\n/**\n * The only package managers `--pm` may select. This value reaches `spawn()`\n * as the executable to run and is spliced into the generated\n * `package.json`'s scripts — an allow-list here is a hard security\n * boundary, not just input hygiene, so it stays a fixed literal list\n * rather than anything derived from user input or the running environment.\n */\nexport const ALLOWED_PACKAGE_MANAGERS = [\"npm\", \"yarn\", \"pnpm\", \"bun\"] as const;\n\nexport type AllowedPackageManager = (typeof ALLOWED_PACKAGE_MANAGERS)[number];\n\n/** Whether `value` is one of the allow-listed package managers. */\nexport function isValidPackageManager(value: string): value is AllowedPackageManager {\n return (ALLOWED_PACKAGE_MANAGERS as readonly string[]).includes(value);\n}\n\nexport function getPackageManager() {\n if (detectedPackageManager) {\n return detectedPackageManager;\n }\n\n return getPreferredPackageManager();\n}\n\n/**\n * Check if a package manager is installed\n */\nfunction isInstalled(manager: string): boolean {\n try {\n execSync(`${manager} --version`, { stdio: \"ignore\" });\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Check if a package manager is installed (async)\n */\nasync function checkManager(manager: string): Promise<boolean> {\n try {\n await execAsync(`${manager} --version`);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Every allow-listed manager that has to be PROBED. npm is excluded because\n * it is assumed present.\n *\n * Both detectors read this one list. They used to hold their own literal\n * copies, and the copies drifted: `bun` reached the allow-list and `--help`\n * without reaching either probe, so `--pm=bun` passed the spelling check and\n * then could never appear among the options that would honour it.\n */\nconst PROBED_PACKAGE_MANAGERS = ALLOWED_PACKAGE_MANAGERS.filter((manager) => manager !== \"npm\");\n\nconst PREFERRED_PACKAGE_MANAGERS = [\"pnpm\", \"yarn\", \"npm\"] as const;\n\n/**\n * A running non-npm manager is already usable. Every fallback must be present\n * in the detector's installed set, so both sync and async paths agree.\n */\nfunction preferredPackageManager(isAvailable: (manager: AllowedPackageManager) => boolean): string {\n const runningPm = detectPackageManager()?.name;\n if (runningPm && runningPm !== \"npm\" && isValidPackageManager(runningPm)) {\n return runningPm;\n }\n\n return PREFERRED_PACKAGE_MANAGERS.find(isAvailable) ?? \"npm\";\n}\n\n/**\n * Detect available package managers asynchronously and cache results\n */\nexport async function detectPackageManagers() {\n const found = await Promise.all(PROBED_PACKAGE_MANAGERS.map(checkManager));\n const available = PROBED_PACKAGE_MANAGERS.filter((_, index) => found[index]);\n const managers = [\"npm\", ...available];\n\n cachedSystemManagers = managers;\n cachedPreferredManager = preferredPackageManager((manager) => managers.includes(manager));\n}\n\n/**\n * Get available package managers on the system\n */\nexport function getSystemPackageManagers(): string[] {\n if (cachedSystemManagers) return cachedSystemManagers;\n\n // Same list the async detector probes, so the two can no longer disagree\n // about which managers exist. npm is assumed to be always available.\n return [\"npm\", ...PROBED_PACKAGE_MANAGERS.filter(isInstalled)];\n}\n\n/**\n * Get the preferred package manager based on priority\n */\nexport function getPreferredPackageManager(): string {\n if (cachedPreferredManager) return cachedPreferredManager;\n return preferredPackageManager((manager) => manager === \"npm\" || isInstalled(manager));\n}\n\nexport function setPackageManager(packageManager: string) {\n detectedPackageManager = packageManager;\n}\n\nlet cachedPnpmVersion: string | null | undefined;\n\n/**\n * The version `pnpm --version` reports, or undefined when pnpm cannot be run.\n * The scaffold's pnpm settings depend on it (see pnpmBuildApprovalLayout).\n */\nexport function getPnpmVersion(): string | undefined {\n if (cachedPnpmVersion === undefined) {\n try {\n cachedPnpmVersion = execSync(\"pnpm --version\", {\n encoding: \"utf8\",\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n }).trim();\n } catch {\n cachedPnpmVersion = null;\n }\n }\n\n return cachedPnpmVersion ?? undefined;\n}\n\nexport function installCommand() {\n return `${getPackageManager()} install`;\n}\n\nexport function startCommand() {\n if (getPackageManager() === \"npm\") return \"npm run dev\";\n\n return `${getPackageManager()} dev`;\n}\n\nexport function runPackageManagerCommand(command: string) {\n const packageManager = getPackageManager();\n\n if (packageManager === \"npm\") return `npm run ${command}`;\n\n return `${packageManager} ${command}`;\n}\n"],"mappings":";;;;;AAIA,MAAM,YAAY,UAAU,IAAI;AAEhC,IAAI;AACJ,IAAI;AACJ,IAAI;;;;;;;;AASJ,MAAa,2BAA2B;CAAC;CAAO;CAAQ;CAAQ;AAAK;;AAKrE,SAAgB,sBAAsB,OAA+C;CACnF,OAAQ,yBAA+C,SAAS,KAAK;AACvE;AAEA,SAAgB,oBAAoB;CAClC,IAAI,wBACF,OAAO;CAGT,OAAO,2BAA2B;AACpC;;;;AAKA,SAAS,YAAY,SAA0B;CAC7C,IAAI;EACF,SAAS,GAAG,QAAQ,aAAa,EAAE,OAAO,SAAS,CAAC;EACpD,OAAO;CACT,QAAQ;EACN,OAAO;CACT;AACF;;;;AAKA,eAAe,aAAa,SAAmC;CAC7D,IAAI;EACF,MAAM,UAAU,GAAG,QAAQ,WAAW;EACtC,OAAO;CACT,QAAQ;EACN,OAAO;CACT;AACF;;;;;;;;;;AAWA,MAAM,0BAA0B,yBAAyB,QAAQ,YAAY,YAAY,KAAK;AAE9F,MAAM,6BAA6B;CAAC;CAAQ;CAAQ;AAAK;;;;;AAMzD,SAAS,wBAAwB,aAAkE;CACjG,MAAM,YAAY,qBAAqB,CAAC,EAAE;CAC1C,IAAI,aAAa,cAAc,SAAS,sBAAsB,SAAS,GACrE,OAAO;CAGT,OAAO,2BAA2B,KAAK,WAAW,KAAK;AACzD;;;;AAKA,eAAsB,wBAAwB;CAC5C,MAAM,QAAQ,MAAM,QAAQ,IAAI,wBAAwB,IAAI,YAAY,CAAC;CAEzE,MAAM,WAAW,CAAC,OAAO,GADP,wBAAwB,QAAQ,GAAG,UAAU,MAAM,MACjC,CAAC;CAErC,uBAAuB;CACvB,yBAAyB,yBAAyB,YAAY,SAAS,SAAS,OAAO,CAAC;AAC1F;;;;AAKA,SAAgB,2BAAqC;CACnD,IAAI,sBAAsB,OAAO;CAIjC,OAAO,CAAC,OAAO,GAAG,wBAAwB,OAAO,WAAW,CAAC;AAC/D;;;;AAKA,SAAgB,6BAAqC;CACnD,IAAI,wBAAwB,OAAO;CACnC,OAAO,yBAAyB,YAAY,YAAY,SAAS,YAAY,OAAO,CAAC;AACvF;AAEA,SAAgB,kBAAkB,gBAAwB;CACxD,yBAAyB;AAC3B;AAEA,IAAI;;;;;AAMJ,SAAgB,iBAAqC;CACnD,IAAI,sBAAsB,QACxB,IAAI;EACF,oBAAoB,SAAS,kBAAkB;GAC7C,UAAU;GACV,OAAO;IAAC;IAAU;IAAQ;GAAQ;EACpC,CAAC,CAAC,CAAC,KAAK;CACV,QAAQ;EACN,oBAAoB;CACtB;CAGF,OAAO,qBAAqB;AAC9B;AAYA,SAAgB,yBAAyB,SAAiB;CACxD,MAAM,iBAAiB,kBAAkB;CAEzC,IAAI,mBAAmB,OAAO,OAAO,WAAW;CAEhD,OAAO,GAAG,eAAe,GAAG;AAC9B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pnpm-build-approvals.mjs","names":[],"sources":["../../../../../../../packages/create-warlock/src/helpers/pnpm-build-approvals.ts"],"sourcesContent":["/**\n * Install scripts the template itself needs, mirrored from the `allowBuilds`\n * map in templates/warlock/pnpm-workspace.yaml (a spec keeps the two equal).\n */\nexport const BASE_PNPM_BUILD_APPROVALS: readonly string[] = [\"esbuild\"];\n\n/**\n * Where a pnpm version reads build approvals from, measured by real installs\n * of an esbuild dependency (create-warlock 5.30.0, FW / macOS report):\n *\n * - `packageJson`: pnpm 7–9 and 10.0–10.4. They reject a pnpm-workspace.yaml\n * without `packages:` (\"packages field missing or empty\"), and adding\n * `packages: [\".\"]` makes every later `pnpm add` fail with\n * ERR_PNPM_ADDING_TO_ROOT. So the app gets no workspace file and lists the\n * approvals under `pnpm.onlyBuiltDependencies` in package.json, which these\n * versions honour.\n * - `onlyBuiltDependencies`: pnpm 10.5–10.25 accept the settings-only file but\n * ignore `allowBuilds`; they read `onlyBuiltDependencies` from it.\n * - `allowBuilds`: pnpm 10.26 and later, 11 and 12. pnpm 11+ fails the install\n * (ERR_PNPM_IGNORED_BUILDS) when only `onlyBuiltDependencies` is present.\n *\n * An unknown version gets the current layout, `allowBuilds`.\n */\nexport type PnpmBuildApprovalLayout
|
|
1
|
+
{"version":3,"file":"pnpm-build-approvals.mjs","names":[],"sources":["../../../../../../../packages/create-warlock/src/helpers/pnpm-build-approvals.ts"],"sourcesContent":["/**\n * Install scripts the template itself needs, mirrored from the `allowBuilds`\n * map in templates/warlock/pnpm-workspace.yaml (a spec keeps the two equal).\n */\nexport const BASE_PNPM_BUILD_APPROVALS: readonly string[] = [\"esbuild\"];\n\n/**\n * Where a pnpm version reads build approvals from, measured by real installs\n * of an esbuild dependency (create-warlock 5.30.0, FW / macOS report):\n *\n * - `packageJson`: pnpm 7–9 and 10.0–10.4. They reject a pnpm-workspace.yaml\n * without `packages:` (\"packages field missing or empty\"), and adding\n * `packages: [\".\"]` makes every later `pnpm add` fail with\n * ERR_PNPM_ADDING_TO_ROOT. So the app gets no workspace file and lists the\n * approvals under `pnpm.onlyBuiltDependencies` in package.json, which these\n * versions honour.\n * - `onlyBuiltDependencies`: pnpm 10.5–10.25 accept the settings-only file but\n * ignore `allowBuilds`; they read `onlyBuiltDependencies` from it.\n * - `allowBuilds`: pnpm 10.26 and later, 11 and 12. pnpm 11+ fails the install\n * (ERR_PNPM_IGNORED_BUILDS) when only `onlyBuiltDependencies` is present.\n *\n * An unknown version gets the current layout, `allowBuilds`.\n */\nexport type PnpmBuildApprovalLayout = \"packageJson\" | \"onlyBuiltDependencies\" | \"allowBuilds\";\n\nexport function pnpmBuildApprovalLayout(version: string | undefined): PnpmBuildApprovalLayout {\n const match = /^(\\d+)\\.(\\d+)\\./.exec(version?.trim() ?? \"\");\n\n if (!match) return \"allowBuilds\";\n\n const major = Number(match[1]);\n const minor = Number(match[2]);\n\n if (major < 10 || (major === 10 && minor < 5)) return \"packageJson\";\n if (major === 10 && minor < 26) return \"onlyBuiltDependencies\";\n\n return \"allowBuilds\";\n}\n\n/**\n * Add packages to a block-style `onlyBuiltDependencies:` list, creating it when\n * absent. A flow list or an alias is left alone rather than rewritten.\n */\nexport function mergeOnlyBuiltDependencies(yaml: string, packages: readonly string[]): string {\n if (packages.length === 0) return yaml;\n\n const block = /^onlyBuiltDependencies:\\s*(?:#.*)?(?:\\r?\\n|$)/m.exec(yaml);\n\n if (!block) {\n if (\n /^(?:onlyBuiltDependencies|\"onlyBuiltDependencies\"|'onlyBuiltDependencies')\\s*:/m.test(yaml)\n ) {\n return yaml;\n }\n\n const items = packages.map((name) => ` - ${name}`).join(\"\\n\");\n\n return `${yaml.replace(/\\s*$/, \"\")}\\n\\n# pnpm 10.5-10.25 read install-script approvals from this list.\\nonlyBuiltDependencies:\\n${items}\\n`;\n }\n\n const start = block.index + block[0].length;\n const tail = yaml.slice(start);\n const nextTopLevel = /^(?!\\s|#|$)[^\\r\\n]+/m.exec(tail);\n const end = nextTopLevel ? start + nextTopLevel.index : yaml.length;\n const list = yaml.slice(start, end);\n const listed = new Set(\n [...list.matchAll(/^\\s*-\\s*[\"']?([^\"'\\s#]+)[\"']?/gm)].map((entry) => entry[1]),\n );\n const missing = packages.filter((name) => !listed.has(name));\n\n if (missing.length === 0) return yaml;\n\n const indentation = /^([ \\t]*)-/m.exec(list)?.[1] ?? \" \";\n const additions = missing.map((name) => `${indentation}- ${name}\\n`).join(\"\");\n\n return `${yaml.slice(0, start)}${additions}${list}${yaml.slice(end)}`;\n}\n\n/**\n * Add packages to `pnpm.onlyBuiltDependencies` in a parsed package.json,\n * keeping existing entries and their order.\n */\nexport function mergePackageJsonBuildApprovals(\n packageJson: Record<string, unknown>,\n packages: readonly string[],\n): Record<string, unknown> {\n const pnpm = (packageJson.pnpm ?? {}) as Record<string, unknown>;\n const current = Array.isArray(pnpm.onlyBuiltDependencies)\n ? (pnpm.onlyBuiltDependencies as string[])\n : [];\n const merged = [...current, ...packages.filter((name) => !current.includes(name))];\n\n return { ...packageJson, pnpm: { ...pnpm, onlyBuiltDependencies: merged } };\n}\n\n/** Native dependency build scripts required by features the scaffold explicitly selected. */\nconst FEATURE_BUILD_APPROVALS: Readonly<Record<string, readonly string[]>> = {\n image: [\"sharp\"],\n};\n\nexport function requiredPnpmBuildApprovals(features: readonly string[]): readonly string[] {\n return [...new Set(features.flatMap((feature) => FEATURE_BUILD_APPROVALS[feature] ?? []))];\n}\n\n/**\n * Add only known feature-required builds to the workspace `allowBuilds` policy\n * (pnpm 10.26+, 11, 12).\n * Existing `false` values are user decisions and are never overridden.\n */\nexport function mergePnpmBuildApprovals(yaml: string, packages: readonly string[]): string {\n let result = yaml;\n for (const packageName of packages) {\n result = mergeOneApproval(result, packageName);\n }\n return result;\n}\n\nconst quotedKey = (name: string) => `(?:${name}|\"${name}\"|'${name}')`;\n\n/** Edit one ordinary YAML mapping while preserving every untouched byte. */\nfunction mergeOneApproval(yaml: string, packageName: string): string {\n const escaped = packageName.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n const block = /^allowBuilds:\\s*(?:#.*)?(?:\\r?\\n|$)/m.exec(yaml);\n\n if (!block) {\n // Flow mappings are deliberately not rewritten. A hand parser must never\n // turn a decided flow entry into a duplicate block key.\n if (/^(?:allowBuilds|\"allowBuilds\"|'allowBuilds')\\s*:/m.test(yaml)) return yaml;\n return `${yaml.replace(/\\s*$/, \"\")}\\nallowBuilds:\\n ${packageName}: true\\n`;\n }\n\n const start = block.index + block[0].length;\n const tail = yaml.slice(start);\n const nextTopLevel = /^(?!\\s|#|$)[^\\r\\n]+/m.exec(tail);\n const end = nextTopLevel ? start + nextTopLevel.index : yaml.length;\n const mapping = yaml.slice(start, end);\n const key = quotedKey(escaped);\n const entry = new RegExp(`^(\\\\s*)${key}\\\\s*:\\\\s*([^#\\\\r\\\\n]*)(.*)$`, \"m\").exec(mapping);\n\n if (entry) {\n const value = entry[2]!.trim();\n if (value === \"true\" || value === \"false\") return yaml;\n if (\n !/^(?:set this to true or false|\"set this to true or false\"|'set this to true or false')$/.test(\n value,\n )\n )\n return yaml;\n // pnpm's undecided placeholder belongs to this generated mapping: decide it\n // in place rather than emitting a duplicate YAML key.\n const comment = entry[3] ?? \"\";\n const replacement = `${entry[1]}${packageName}: true${comment.startsWith(\"#\") ? \" \" : \"\"}${comment}`;\n return `${yaml.slice(0, start)}${mapping.slice(0, entry.index)}${replacement}${mapping.slice(entry.index + entry[0].length)}${yaml.slice(end)}`;\n }\n\n const indentation = /^([ \\t]+)\\S/m.exec(mapping)?.[1] ?? \" \";\n return `${yaml.slice(0, start)}${indentation}${packageName}: true\\n${mapping}${yaml.slice(end)}`;\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"project-builder-helpers.mjs","names":[],"sources":["../../../../../../../packages/create-warlock/src/helpers/project-builder-helpers.ts"],"sourcesContent":["import { outro } from \"@clack/prompts\";\nimport { colors } from \"@mongez/copper\";\nimport {\n copyDirectory,\n getFile,\n getJsonFile,\n putFile,\n putJsonFile,\n renameFile,\n} from \"@warlock.js/fs\";\nimport path from \"path\";\nimport { executeCommand } from \"./exec\";\nimport { startCommand } from \"./package-manager\";\nimport { Template, template } from \"./paths\";\n\n/**\n * Bootstrap the project's git repository.\n *\n * Returns `false` as soon as a step fails, and leaves that step's captured\n * output where the caller can claim it with `takeLastCommandOutput()`. It used\n * to `return true` unconditionally, which meant a machine without git printed\n * \"Grimoire initialized!\" over a directory that was not a repository.\n */\nexport async function initializeGitRepository(appPath: string) {\n const steps: string[][] = [\n // initialize git repository\n [\"init\"],\n // switching to `main` branch\n [\"checkout\", \"-b\", \"main\"],\n // add files\n [\"add\", \".\"],\n // commit files\n [\"commit\", \"-m\", \"Initial commit\"],\n ];\n\n for (const args of steps) {\n const succeeded = await executeCommand(`git`, args, appPath);\n\n if (!succeeded) return false;\n }\n\n return true;\n}\n\nexport async function updateEnvFile(appPath: string, appName: string) {\n // update package.json file\n const packageJson: any = getJsonFile(path.resolve(appPath, \"package.json\"));\n\n packageJson.name = appName;\n\n putJsonFile(path.resolve(appPath, \"package.json\"), packageJson);\n\n // update env file\n const dotEnv = getFile(path.resolve(appPath, \".env\"))\n .replace(\"AppName\", appName)\n .replace(\n \"AppCodeName\",\n appName\n .split(/-|_/g)\n .map(word => word[0])\n .join(\"\"),\n );\n\n putFile(path.resolve(appPath, \".env\"), dotEnv);\n\n // update .env.production file\n let dotEnvProduction = getFile(path.resolve(appPath, \".env.shared\"));\n\n dotEnvProduction = dotEnvProduction.replace(\"AppName\", appName).replace(\n \"AppCodeName\",\n appName\n .split(/-|_/g)\n .map(word => word[0])\n .join(\"\"),\n );\n\n putFile(path.resolve(appPath, \".env.shared\"), dotEnvProduction);\n}\n\nexport async function copyTemplateFiles(
|
|
1
|
+
{"version":3,"file":"project-builder-helpers.mjs","names":[],"sources":["../../../../../../../packages/create-warlock/src/helpers/project-builder-helpers.ts"],"sourcesContent":["import { outro } from \"@clack/prompts\";\nimport { colors } from \"@mongez/copper\";\nimport {\n copyDirectory,\n getFile,\n getJsonFile,\n putFile,\n putJsonFile,\n renameFile,\n} from \"@warlock.js/fs\";\nimport path from \"path\";\nimport { executeCommand } from \"./exec\";\nimport { startCommand } from \"./package-manager\";\nimport { Template, template } from \"./paths\";\n\n/**\n * Bootstrap the project's git repository.\n *\n * Returns `false` as soon as a step fails, and leaves that step's captured\n * output where the caller can claim it with `takeLastCommandOutput()`. It used\n * to `return true` unconditionally, which meant a machine without git printed\n * \"Grimoire initialized!\" over a directory that was not a repository.\n */\nexport async function initializeGitRepository(appPath: string) {\n const steps: string[][] = [\n // initialize git repository\n [\"init\"],\n // switching to `main` branch\n [\"checkout\", \"-b\", \"main\"],\n // add files\n [\"add\", \".\"],\n // commit files\n [\"commit\", \"-m\", \"Initial commit\"],\n ];\n\n for (const args of steps) {\n const succeeded = await executeCommand(`git`, args, appPath);\n\n if (!succeeded) return false;\n }\n\n return true;\n}\n\nexport async function updateEnvFile(appPath: string, appName: string) {\n // update package.json file\n const packageJson: any = getJsonFile(path.resolve(appPath, \"package.json\"));\n\n packageJson.name = appName;\n\n putJsonFile(path.resolve(appPath, \"package.json\"), packageJson);\n\n // update env file\n const dotEnv = getFile(path.resolve(appPath, \".env\"))\n .replace(\"AppName\", appName)\n .replace(\n \"AppCodeName\",\n appName\n .split(/-|_/g)\n .map((word) => word[0])\n .join(\"\"),\n );\n\n putFile(path.resolve(appPath, \".env\"), dotEnv);\n\n // update .env.production file\n let dotEnvProduction = getFile(path.resolve(appPath, \".env.shared\"));\n\n dotEnvProduction = dotEnvProduction.replace(\"AppName\", appName).replace(\n \"AppCodeName\",\n appName\n .split(/-|_/g)\n .map((word) => word[0])\n .join(\"\"),\n );\n\n putFile(path.resolve(appPath, \".env.shared\"), dotEnvProduction);\n}\n\nexport async function copyTemplateFiles(templateName: Template, appPath: string, _appName: string) {\n // copy project files\n copyDirectory(template(templateName), appPath);\n\n // replace _.gitignore to\n renameFile(path.resolve(appPath, \"_.gitignore\"), path.resolve(appPath, \".gitignore\"));\n}\n\nexport async function allDone(appName: string) {\n outro(\n \"Awesome! Your project is ready to rock! \" + \"Run the following command to start development:\",\n );\n\n console.log(colors.cyan(`cd ${appName} && ${startCommand()}`));\n\n console.log();\n\n console.log(\n `Pro tip: Install the ${colors.yellow(\n \"Generator Z\",\n )} extension in VSCode for helpful code snippets and productivity boosters!`,\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAuBA,eAAsB,wBAAwB,SAAiB;CAY7D,KAAK,MAAM,QAAQ;EATjB,CAAC,MAAM;EAEP;GAAC;GAAY;GAAM;EAAM;EAEzB,CAAC,OAAO,GAAG;EAEX;GAAC;GAAU;GAAM;EAAgB;CAGZ,GAGrB,IAAI,CAAC,MAFmB,eAAe,OAAO,MAAM,OAAO,GAE3C,OAAO;CAGzB,OAAO;AACT"}
|