create-warlock 5.2.3 → 5.3.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/esm/commands/create-new-app/index.mjs.map +1 -1
- package/esm/commands/create-warlock-app/index.mjs +1 -1
- package/esm/commands/create-warlock-app/index.mjs.map +1 -1
- package/esm/features/database-drivers.mjs.map +1 -1
- package/esm/features/features-map.mjs.map +1 -1
- package/esm/helpers/app.mjs +44 -17
- package/esm/helpers/app.mjs.map +1 -1
- package/esm/helpers/exec.mjs.map +1 -1
- package/esm/helpers/package-manager.mjs.map +1 -1
- package/esm/helpers/warlock-versions.mjs.map +1 -1
- package/esm/index.mjs.map +1 -1
- package/package.json +2 -2
- package/templates/warlock/eslint.config.js +98 -98
- package/templates/warlock/postcss.config.mjs +6 -0
- package/templates/warlock/src/app/contact/controllers/contact.controller.ts +21 -0
- package/templates/warlock/src/app/contact/routes.ts +4 -0
- package/templates/warlock/src/app/{shared → home}/controllers/home-page.controller.ts +1 -7
- package/templates/warlock/src/app/home/services/home.service.ts +86 -0
- package/templates/warlock/src/app/locale/controllers/locale.controller.ts +16 -0
- package/templates/warlock/src/app/locale/routes.ts +4 -0
- package/templates/warlock/src/shared/contact.schema.ts +7 -0
- package/templates/warlock/src/shared/locale.schema.ts +11 -0
- package/templates/warlock/src/shared/locales.ts +7 -0
- package/templates/warlock/src/web/404.css +113 -0
- package/templates/warlock/src/web/404.page.tsx +23 -0
- package/templates/warlock/src/web/app.css +3 -0
- package/templates/warlock/src/web/home/components/contact-form-controls.tsx +42 -0
- package/templates/warlock/src/web/home/components/contact-section.tsx +108 -0
- package/templates/warlock/src/web/home/components/content-sections.tsx +61 -0
- package/templates/warlock/src/web/home/components/hero-section.tsx +68 -0
- package/templates/warlock/src/web/home/components/home-footer.tsx +35 -0
- package/templates/warlock/src/web/home/components/home-header.tsx +26 -0
- package/templates/warlock/src/web/home/components/logo.tsx +19 -0
- package/templates/warlock/src/web/home/components/runtime-preview.tsx +75 -0
- package/templates/warlock/src/web/home/hooks/use-contact-form.ts +57 -0
- package/templates/warlock/src/web/home/hooks/use-locale-switcher.ts +35 -0
- package/templates/warlock/src/web/home/index.page.tsx +44 -0
- package/templates/warlock/src/web/home/register.ts +30 -0
- package/templates/warlock/src/web/home/styles/home.css +1185 -0
- package/templates/warlock/src/web/root.tsx +49 -0
- package/templates/warlock/src/web/shared/utils/set-form-errors.ts +60 -0
- package/templates/warlock/tsconfig.json +3 -1
- package/templates/warlock/public/home.css +0 -523
- package/templates/warlock/src/app/shared/components/HomePageComponent.tsx +0 -229
- package/templates/warlock/src/app/shared/controllers/home-page.controller.tsx +0 -17
- /package/templates/warlock/src/app/{shared → home}/routes.ts +0 -0
package/esm/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../../../../create-warlock/src/index.ts"],"sourcesContent":["import { getJsonFile } from \"@warlock.js/fs\";\nimport createNewApp from \"./commands/create-new-app\";\nimport { CliFlags } from \"./commands/create-new-app/types\";\nimport { NO_DATABASE } from \"./features/database-drivers\";\nimport { packageRoot } from \"./helpers/paths\";\n\nconst valueFlags = [\"name\", \"db\", \"pm\", \"features\", \"ai\"];\n\nconst HELP_TEXT = `\n create-warlock — scaffold a new Warlock.js project\n\n Usage\n $ create-warlock [project-name] [options]\n\n Options\n --name Project name (or pass it as the first positional arg)\n --db=<driver> Database driver (e.g. postgres, mongodb)\n --no-db Skip database selection entirely\n --features=<list> Comma-separated feature keys (e.g. test,herald)\n --ai=<list> Comma-separated AI provider keys (e.g. openai,anthropic)\n --pm=<manager> Package manager to use (npm, yarn, pnpm)\n --git / --no-git Force-enable or force-disable git initialization\n --jwt / --no-jwt Force-enable or force-disable JWT secret generation\n -y, --yes Skip prompts and accept defaults for anything unset\n -h, --help Show this help message and exit\n -v, --version Show the installed create-warlock version and exit\n\n Example\n $ create-warlock my-app --db=postgres --features=test,herald --yes\n`;\n\n/**\n * Parse the scaffolder's own CLI flags for non-interactive mode.\n *\n * @example\n * create-warlock my-app --db=postgres --features=test,herald --ai=openai,anthropic --yes\n */\nexport function parseFlags(argv: string[]): CliFlags {\n const flags: CliFlags = {};\n const positionals: string[] = [];\n\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i];\n\n if (!arg.startsWith(\"-\")) {\n positionals.push(arg);\n continue;\n }\n\n const equalIndex = arg.indexOf(\"=\");\n const key = (equalIndex === -1 ? arg : arg.slice(0, equalIndex)).replace(/^-+/, \"\");\n let value: string | undefined = equalIndex === -1 ? undefined : arg.slice(equalIndex + 1);\n\n // Value-taking flags may use either `--key=value` or `--key value`.\n if (valueFlags.includes(key) && value === undefined) {\n const next = argv[i + 1];\n\n if (next && !next.startsWith(\"-\")) {\n value = next;\n i++;\n }\n }\n\n switch (key) {\n case \"help\":\n case \"h\":\n flags.help = true;\n break;\n case \"version\":\n case \"v\":\n flags.version = true;\n break;\n case \"yes\":\n case \"y\":\n flags.yes = true;\n break;\n case \"git\":\n flags.git = true;\n break;\n case \"no-git\":\n flags.git = false;\n break;\n case \"jwt\":\n flags.jwt = true;\n break;\n case \"no-jwt\":\n flags.jwt = false;\n break;\n case \"name\":\n flags.name = value;\n break;\n case \"db\":\n flags.db = value;\n break;\n case \"no-db\":\n // Opt out of a database entirely — equivalent to `--db=none`.\n flags.db = NO_DATABASE;\n break;\n case \"pm\":\n flags.pm = value;\n break;\n case \"features\":\n flags.features = splitList(value);\n break;\n case \"ai\":\n flags.ai = splitList(value);\n break;\n }\n }\n\n if (!flags.name && positionals.length > 0) {\n flags.name = positionals[0];\n }\n\n return flags;\n}\n\nfunction splitList(value: string | undefined): string[] {\n if (!value) return [];\n\n return value\n .split(\",\")\n .map(item => item.trim())\n .filter(Boolean);\n}\n\n/**\n * Read this package's own `version` field. A plain JSON read — no network\n * call, no write, nothing else touched — so `--version` stays a pure,\n * side-effect-free exit.\n */\nexport function packageVersion(): string {\n return (getJsonFile(packageRoot(\"package.json\")) as { version: string })\n .version;\n}\n\nexport default function createApp() {\n const flags = parseFlags(process.argv.slice(2));\n\n // `--help` and `--version` must exit before anything that touches the\n // filesystem, the network, or a prompt — they win over every other flag,\n // including a positional project name.\n if (flags.help) {\n console.log(HELP_TEXT);\n process.exit(0);\n return;\n }\n\n if (flags.version) {\n console.log(packageVersion());\n process.exit(0);\n return;\n }\n\n // An unexpected throw must surface as a readable error AND a non-zero exit\n // code — never as a stack trace the user scrolls past on the way to a green\n // banner (there is no banner after this point).\n Promise.resolve(createNewApp(flags)).catch((error: unknown) => {\n console.error();\n console.error(\n ` create-warlock failed: ${(error as Error)?.message ?? String(error)}`,\n );\n console.error();\n\n process.exit(1);\n });\n}\n"],"mappings":";;;;;;AAMA,MAAM,aAAa;CAAC;CAAQ;CAAM;CAAM;CAAY;AAAI;AAExD,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BlB,SAAgB,WAAW,MAA0B;CACnD,MAAM,QAAkB,CAAC;CACzB,MAAM,cAAwB,CAAC;CAE/B,KAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;EACpC,MAAM,MAAM,KAAK;EAEjB,IAAI,CAAC,IAAI,WAAW,GAAG,GAAG;GACxB,YAAY,KAAK,GAAG;GACpB;EACF;EAEA,MAAM,aAAa,IAAI,QAAQ,GAAG;EAClC,MAAM,OAAO,eAAe,KAAK,MAAM,IAAI,MAAM,GAAG,UAAU,
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../../../../create-warlock/src/index.ts"],"sourcesContent":["import { getJsonFile } from \"@warlock.js/fs\";\nimport createNewApp from \"./commands/create-new-app\";\nimport { CliFlags } from \"./commands/create-new-app/types\";\nimport { NO_DATABASE } from \"./features/database-drivers\";\nimport { packageRoot } from \"./helpers/paths\";\n\nconst valueFlags = [\"name\", \"db\", \"pm\", \"features\", \"ai\"];\n\nconst HELP_TEXT = `\n create-warlock — scaffold a new Warlock.js project\n\n Usage\n $ create-warlock [project-name] [options]\n\n Options\n --name Project name (or pass it as the first positional arg)\n --db=<driver> Database driver (e.g. postgres, mongodb)\n --no-db Skip database selection entirely\n --features=<list> Comma-separated feature keys (e.g. test,herald)\n --ai=<list> Comma-separated AI provider keys (e.g. openai,anthropic)\n --pm=<manager> Package manager to use (npm, yarn, pnpm)\n --git / --no-git Force-enable or force-disable git initialization\n --jwt / --no-jwt Force-enable or force-disable JWT secret generation\n -y, --yes Skip prompts and accept defaults for anything unset\n -h, --help Show this help message and exit\n -v, --version Show the installed create-warlock version and exit\n\n Example\n $ create-warlock my-app --db=postgres --features=test,herald --yes\n`;\n\n/**\n * Parse the scaffolder's own CLI flags for non-interactive mode.\n *\n * @example\n * create-warlock my-app --db=postgres --features=test,herald --ai=openai,anthropic --yes\n */\nexport function parseFlags(argv: string[]): CliFlags {\n const flags: CliFlags = {};\n const positionals: string[] = [];\n\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i];\n\n if (!arg.startsWith(\"-\")) {\n positionals.push(arg);\n continue;\n }\n\n const equalIndex = arg.indexOf(\"=\");\n const key = (equalIndex === -1 ? arg : arg.slice(0, equalIndex)).replace(/^-+/, \"\");\n let value: string | undefined = equalIndex === -1 ? undefined : arg.slice(equalIndex + 1);\n\n // Value-taking flags may use either `--key=value` or `--key value`.\n if (valueFlags.includes(key) && value === undefined) {\n const next = argv[i + 1];\n\n if (next && !next.startsWith(\"-\")) {\n value = next;\n i++;\n }\n }\n\n switch (key) {\n case \"help\":\n case \"h\":\n flags.help = true;\n break;\n case \"version\":\n case \"v\":\n flags.version = true;\n break;\n case \"yes\":\n case \"y\":\n flags.yes = true;\n break;\n case \"git\":\n flags.git = true;\n break;\n case \"no-git\":\n flags.git = false;\n break;\n case \"jwt\":\n flags.jwt = true;\n break;\n case \"no-jwt\":\n flags.jwt = false;\n break;\n case \"name\":\n flags.name = value;\n break;\n case \"db\":\n flags.db = value;\n break;\n case \"no-db\":\n // Opt out of a database entirely — equivalent to `--db=none`.\n flags.db = NO_DATABASE;\n break;\n case \"pm\":\n flags.pm = value;\n break;\n case \"features\":\n flags.features = splitList(value);\n break;\n case \"ai\":\n flags.ai = splitList(value);\n break;\n }\n }\n\n if (!flags.name && positionals.length > 0) {\n flags.name = positionals[0];\n }\n\n return flags;\n}\n\nfunction splitList(value: string | undefined): string[] {\n if (!value) return [];\n\n return value\n .split(\",\")\n .map(item => item.trim())\n .filter(Boolean);\n}\n\n/**\n * Read this package's own `version` field. A plain JSON read — no network\n * call, no write, nothing else touched — so `--version` stays a pure,\n * side-effect-free exit.\n */\nexport function packageVersion(): string {\n return (getJsonFile(packageRoot(\"package.json\")) as { version: string })\n .version;\n}\n\nexport default function createApp() {\n const flags = parseFlags(process.argv.slice(2));\n\n // `--help` and `--version` must exit before anything that touches the\n // filesystem, the network, or a prompt — they win over every other flag,\n // including a positional project name.\n if (flags.help) {\n console.log(HELP_TEXT);\n process.exit(0);\n return;\n }\n\n if (flags.version) {\n console.log(packageVersion());\n process.exit(0);\n return;\n }\n\n // An unexpected throw must surface as a readable error AND a non-zero exit\n // code — never as a stack trace the user scrolls past on the way to a green\n // banner (there is no banner after this point).\n Promise.resolve(createNewApp(flags)).catch((error: unknown) => {\n console.error();\n console.error(\n ` create-warlock failed: ${(error as Error)?.message ?? String(error)}`,\n );\n console.error();\n\n process.exit(1);\n });\n}\n"],"mappings":";;;;;;AAMA,MAAM,aAAa;CAAC;CAAQ;CAAM;CAAM;CAAY;AAAI;AAExD,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BlB,SAAgB,WAAW,MAA0B;CACnD,MAAM,QAAkB,CAAC;CACzB,MAAM,cAAwB,CAAC;CAE/B,KAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;EACpC,MAAM,MAAM,KAAK;EAEjB,IAAI,CAAC,IAAI,WAAW,GAAG,GAAG;GACxB,YAAY,KAAK,GAAG;GACpB;EACF;EAEA,MAAM,aAAa,IAAI,QAAQ,GAAG;EAClC,MAAM,OAAO,eAAe,KAAK,MAAM,IAAI,MAAM,GAAG,UAAU,EAAC,CAAE,QAAQ,OAAO,EAAE;EAClF,IAAI,QAA4B,eAAe,KAAK,SAAY,IAAI,MAAM,aAAa,CAAC;EAGxF,IAAI,WAAW,SAAS,GAAG,KAAK,UAAU,QAAW;GACnD,MAAM,OAAO,KAAK,IAAI;GAEtB,IAAI,QAAQ,CAAC,KAAK,WAAW,GAAG,GAAG;IACjC,QAAQ;IACR;GACF;EACF;EAEA,QAAQ,KAAR;GACE,KAAK;GACL,KAAK;IACH,MAAM,OAAO;IACb;GACF,KAAK;GACL,KAAK;IACH,MAAM,UAAU;IAChB;GACF,KAAK;GACL,KAAK;IACH,MAAM,MAAM;IACZ;GACF,KAAK;IACH,MAAM,MAAM;IACZ;GACF,KAAK;IACH,MAAM,MAAM;IACZ;GACF,KAAK;IACH,MAAM,MAAM;IACZ;GACF,KAAK;IACH,MAAM,MAAM;IACZ;GACF,KAAK;IACH,MAAM,OAAO;IACb;GACF,KAAK;IACH,MAAM,KAAK;IACX;GACF,KAAK;IAEH,MAAM,KAAK;IACX;GACF,KAAK;IACH,MAAM,KAAK;IACX;GACF,KAAK;IACH,MAAM,WAAW,UAAU,KAAK;IAChC;GACF,KAAK;IACH,MAAM,KAAK,UAAU,KAAK;IAC1B;EACJ;CACF;CAEA,IAAI,CAAC,MAAM,QAAQ,YAAY,SAAS,GACtC,MAAM,OAAO,YAAY;CAG3B,OAAO;AACT;AAEA,SAAS,UAAU,OAAqC;CACtD,IAAI,CAAC,OAAO,OAAO,CAAC;CAEpB,OAAO,MACJ,MAAM,GAAG,CAAC,CACV,KAAI,SAAQ,KAAK,KAAK,CAAC,CAAC,CACxB,OAAO,OAAO;AACnB;;;;;;AAOA,SAAgB,iBAAyB;CACvC,OAAQ,YAAY,YAAY,cAAc,CAAC,CAAC,CAC7C;AACL;AAEA,SAAwB,YAAY;CAClC,MAAM,QAAQ,WAAW,QAAQ,KAAK,MAAM,CAAC,CAAC;CAK9C,IAAI,MAAM,MAAM;EACd,QAAQ,IAAI,SAAS;EACrB,QAAQ,KAAK,CAAC;EACd;CACF;CAEA,IAAI,MAAM,SAAS;EACjB,QAAQ,IAAI,eAAe,CAAC;EAC5B,QAAQ,KAAK,CAAC;EACd;CACF;CAKA,QAAQ,QAAQ,aAAa,KAAK,CAAC,CAAC,CAAC,OAAO,UAAmB;EAC7D,QAAQ,MAAM;EACd,QAAQ,MACN,4BAA6B,OAAiB,WAAW,OAAO,KAAK,GACvE;EACA,QAAQ,MAAM;EAEd,QAAQ,KAAK,CAAC;CAChB,CAAC;AACH"}
|
package/package.json
CHANGED
|
@@ -12,13 +12,13 @@
|
|
|
12
12
|
"dependencies": {
|
|
13
13
|
"@clack/prompts": "^0.7.0",
|
|
14
14
|
"@mongez/copper": "^2.1.2",
|
|
15
|
-
"@warlock.js/fs": "5.
|
|
15
|
+
"@warlock.js/fs": "5.3.0",
|
|
16
16
|
"@mongez/reinforcements": "^4.0.1",
|
|
17
17
|
"cross-spawn": "^7.0.3",
|
|
18
18
|
"rimraf": "^6.0.1",
|
|
19
19
|
"which-pm-runs": "^1.1.0"
|
|
20
20
|
},
|
|
21
|
-
"version": "5.
|
|
21
|
+
"version": "5.3.0",
|
|
22
22
|
"type": "module",
|
|
23
23
|
"main": "./esm/index.mjs",
|
|
24
24
|
"module": "./esm/index.mjs",
|
|
@@ -1,98 +1,98 @@
|
|
|
1
|
-
import tsPlugin from "@typescript-eslint/eslint-plugin";
|
|
2
|
-
import tsParser from "@typescript-eslint/parser";
|
|
3
|
-
import prettierConfig from "eslint-config-prettier";
|
|
4
|
-
import prettierPlugin from "eslint-plugin-prettier";
|
|
5
|
-
import unusedImports from "eslint-plugin-unused-imports";
|
|
6
|
-
|
|
7
|
-
export default [
|
|
8
|
-
// Global configuration
|
|
9
|
-
{
|
|
10
|
-
languageOptions: {
|
|
11
|
-
ecmaVersion: "latest",
|
|
12
|
-
sourceType: "module",
|
|
13
|
-
globals: {
|
|
14
|
-
// Node.js globals
|
|
15
|
-
console: "readonly",
|
|
16
|
-
process: "readonly",
|
|
17
|
-
Buffer: "readonly",
|
|
18
|
-
__dirname: "readonly",
|
|
19
|
-
__filename: "readonly",
|
|
20
|
-
global: "readonly",
|
|
21
|
-
module: "readonly",
|
|
22
|
-
require: "readonly",
|
|
23
|
-
exports: "readonly",
|
|
24
|
-
// ES2021 globals
|
|
25
|
-
Promise: "readonly",
|
|
26
|
-
// Jest globals
|
|
27
|
-
describe: "readonly",
|
|
28
|
-
it: "readonly",
|
|
29
|
-
test: "readonly",
|
|
30
|
-
expect: "readonly",
|
|
31
|
-
beforeEach: "readonly",
|
|
32
|
-
afterEach: "readonly",
|
|
33
|
-
beforeAll: "readonly",
|
|
34
|
-
afterAll: "readonly",
|
|
35
|
-
jest: "readonly",
|
|
36
|
-
},
|
|
37
|
-
},
|
|
38
|
-
},
|
|
39
|
-
|
|
40
|
-
// TypeScript files configuration
|
|
41
|
-
{
|
|
42
|
-
files: ["**/*.ts", "**/*.tsx"
|
|
43
|
-
languageOptions: {
|
|
44
|
-
parser: tsParser,
|
|
45
|
-
parserOptions: {
|
|
46
|
-
ecmaVersion: "latest",
|
|
47
|
-
sourceType: "module",
|
|
48
|
-
},
|
|
49
|
-
},
|
|
50
|
-
plugins: {
|
|
51
|
-
"@typescript-eslint": tsPlugin,
|
|
52
|
-
prettier: prettierPlugin,
|
|
53
|
-
"unused-imports": unusedImports,
|
|
54
|
-
},
|
|
55
|
-
rules: {
|
|
56
|
-
// Prettier integration
|
|
57
|
-
...prettierConfig.rules,
|
|
58
|
-
"prettier/prettier": "error",
|
|
59
|
-
|
|
60
|
-
// TypeScript rules
|
|
61
|
-
"@typescript-eslint/no-explicit-any": "off",
|
|
62
|
-
"@typescript-eslint/no-unused-vars": "off",
|
|
63
|
-
"no-unused-vars": "off",
|
|
64
|
-
"unused-imports/no-unused-vars": [
|
|
65
|
-
"warn",
|
|
66
|
-
{
|
|
67
|
-
vars: "all",
|
|
68
|
-
varsIgnorePattern: "^_",
|
|
69
|
-
args: "after-used",
|
|
70
|
-
argsIgnorePattern: "^_",
|
|
71
|
-
},
|
|
72
|
-
],
|
|
73
|
-
"unused-imports/no-unused-imports": "error",
|
|
74
|
-
"@typescript-eslint/explicit-member-accessibility": [
|
|
75
|
-
"error",
|
|
76
|
-
{ accessibility: "explicit" },
|
|
77
|
-
],
|
|
78
|
-
"@typescript-eslint/consistent-type-imports": [
|
|
79
|
-
"error",
|
|
80
|
-
{
|
|
81
|
-
prefer: "type-imports",
|
|
82
|
-
disallowTypeAnnotations: false,
|
|
83
|
-
},
|
|
84
|
-
],
|
|
85
|
-
},
|
|
86
|
-
},
|
|
87
|
-
|
|
88
|
-
// Ignore patterns
|
|
89
|
-
{
|
|
90
|
-
ignores: [
|
|
91
|
-
"dist/",
|
|
92
|
-
"node_modules/",
|
|
93
|
-
"**/*.js",
|
|
94
|
-
"!**/*.config.js",
|
|
95
|
-
"!eslint.config.js",
|
|
96
|
-
],
|
|
97
|
-
},
|
|
98
|
-
];
|
|
1
|
+
import tsPlugin from "@typescript-eslint/eslint-plugin";
|
|
2
|
+
import tsParser from "@typescript-eslint/parser";
|
|
3
|
+
import prettierConfig from "eslint-config-prettier";
|
|
4
|
+
import prettierPlugin from "eslint-plugin-prettier";
|
|
5
|
+
import unusedImports from "eslint-plugin-unused-imports";
|
|
6
|
+
|
|
7
|
+
export default [
|
|
8
|
+
// Global configuration
|
|
9
|
+
{
|
|
10
|
+
languageOptions: {
|
|
11
|
+
ecmaVersion: "latest",
|
|
12
|
+
sourceType: "module",
|
|
13
|
+
globals: {
|
|
14
|
+
// Node.js globals
|
|
15
|
+
console: "readonly",
|
|
16
|
+
process: "readonly",
|
|
17
|
+
Buffer: "readonly",
|
|
18
|
+
__dirname: "readonly",
|
|
19
|
+
__filename: "readonly",
|
|
20
|
+
global: "readonly",
|
|
21
|
+
module: "readonly",
|
|
22
|
+
require: "readonly",
|
|
23
|
+
exports: "readonly",
|
|
24
|
+
// ES2021 globals
|
|
25
|
+
Promise: "readonly",
|
|
26
|
+
// Jest globals
|
|
27
|
+
describe: "readonly",
|
|
28
|
+
it: "readonly",
|
|
29
|
+
test: "readonly",
|
|
30
|
+
expect: "readonly",
|
|
31
|
+
beforeEach: "readonly",
|
|
32
|
+
afterEach: "readonly",
|
|
33
|
+
beforeAll: "readonly",
|
|
34
|
+
afterAll: "readonly",
|
|
35
|
+
jest: "readonly",
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
// TypeScript files configuration
|
|
41
|
+
{
|
|
42
|
+
files: ["**/*.ts", "**/*.tsx"],
|
|
43
|
+
languageOptions: {
|
|
44
|
+
parser: tsParser,
|
|
45
|
+
parserOptions: {
|
|
46
|
+
ecmaVersion: "latest",
|
|
47
|
+
sourceType: "module",
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
plugins: {
|
|
51
|
+
"@typescript-eslint": tsPlugin,
|
|
52
|
+
prettier: prettierPlugin,
|
|
53
|
+
"unused-imports": unusedImports,
|
|
54
|
+
},
|
|
55
|
+
rules: {
|
|
56
|
+
// Prettier integration
|
|
57
|
+
...prettierConfig.rules,
|
|
58
|
+
"prettier/prettier": "error",
|
|
59
|
+
|
|
60
|
+
// TypeScript rules
|
|
61
|
+
"@typescript-eslint/no-explicit-any": "off",
|
|
62
|
+
"@typescript-eslint/no-unused-vars": "off",
|
|
63
|
+
"no-unused-vars": "off",
|
|
64
|
+
"unused-imports/no-unused-vars": [
|
|
65
|
+
"warn",
|
|
66
|
+
{
|
|
67
|
+
vars: "all",
|
|
68
|
+
varsIgnorePattern: "^_",
|
|
69
|
+
args: "after-used",
|
|
70
|
+
argsIgnorePattern: "^_",
|
|
71
|
+
},
|
|
72
|
+
],
|
|
73
|
+
"unused-imports/no-unused-imports": "error",
|
|
74
|
+
"@typescript-eslint/explicit-member-accessibility": [
|
|
75
|
+
"error",
|
|
76
|
+
{ accessibility: "explicit" },
|
|
77
|
+
],
|
|
78
|
+
"@typescript-eslint/consistent-type-imports": [
|
|
79
|
+
"error",
|
|
80
|
+
{
|
|
81
|
+
prefer: "type-imports",
|
|
82
|
+
disallowTypeAnnotations: false,
|
|
83
|
+
},
|
|
84
|
+
],
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
// Ignore patterns
|
|
89
|
+
{
|
|
90
|
+
ignores: [
|
|
91
|
+
"dist/",
|
|
92
|
+
"node_modules/",
|
|
93
|
+
"**/*.js",
|
|
94
|
+
"!**/*.config.js",
|
|
95
|
+
"!eslint.config.js",
|
|
96
|
+
],
|
|
97
|
+
},
|
|
98
|
+
];
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { contactSchema } from "@shared/contact.schema";
|
|
2
|
+
import { type Request, type RequestHandler } from "@warlock.js/core";
|
|
3
|
+
import { type Infer } from "@warlock.js/seal";
|
|
4
|
+
|
|
5
|
+
export type ContactSchema = Infer.Output<typeof contactSchema>;
|
|
6
|
+
|
|
7
|
+
/** POST /api/contact — validates the starter contact form. */
|
|
8
|
+
export const contactController: RequestHandler<Request<ContactSchema>> = async ({
|
|
9
|
+
request,
|
|
10
|
+
response,
|
|
11
|
+
}) => {
|
|
12
|
+
const contact = request.validated();
|
|
13
|
+
|
|
14
|
+
// Replace this with delivery/persistence for your app. Keeping the accepted
|
|
15
|
+
// payload visible makes the endpoint useful while remaining side-effect free.
|
|
16
|
+
return response.success({
|
|
17
|
+
message: "Thanks, " + contact.name + ". Your message has been received.",
|
|
18
|
+
});
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
contactController.validation = { schema: contactSchema };
|
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
import { Application, type RequestHandler } from "@warlock.js/core";
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
* Default welcome route — a dependency-free JSON response.
|
|
5
|
-
*
|
|
6
|
-
* Projects scaffolded with the `react` feature get the richer HTML welcome
|
|
7
|
-
* page (`home-page.controller.tsx` + `HomePageComponent.tsx`) instead; this
|
|
8
|
-
* plain controller is removed at scaffold time when React is selected.
|
|
9
|
-
*/
|
|
3
|
+
/** The dependency-free home response for applications without the web feature. */
|
|
10
4
|
export const homePageController: RequestHandler = async ({ response }) => {
|
|
11
5
|
return response.success({
|
|
12
6
|
message: "Welcome to Warlock 🧙 — your app is up and running!",
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
const capabilities = [
|
|
2
|
+
{
|
|
3
|
+
index: "01",
|
|
4
|
+
eyebrow: "Runtime",
|
|
5
|
+
title: "One server. Every layer.",
|
|
6
|
+
body: "HTTP, SSR React, queues, schedules, and agents share one typed runtime instead of a stack of disconnected tools.",
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
index: "02",
|
|
10
|
+
eyebrow: "Types",
|
|
11
|
+
title: "Confidence at the edges.",
|
|
12
|
+
body: "Validation, request data, page loaders, and tool calls stay typed from input to response.",
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
index: "03",
|
|
16
|
+
eyebrow: "Architecture",
|
|
17
|
+
title: "Packages that compose.",
|
|
18
|
+
body: "Start with Core, then reach for Cascade, Auth, Cache, Scheduler, Seal, or AI only when the product needs them.",
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
index: "04",
|
|
22
|
+
eyebrow: "Developer loop",
|
|
23
|
+
title: "Fast where it matters.",
|
|
24
|
+
body: "File-based pages, hot updates, generators, and project-local skills keep momentum inside the codebase.",
|
|
25
|
+
},
|
|
26
|
+
] as const;
|
|
27
|
+
|
|
28
|
+
const packages = [
|
|
29
|
+
{
|
|
30
|
+
name: "core",
|
|
31
|
+
area: "Foundation",
|
|
32
|
+
description:
|
|
33
|
+
"The application runtime for configuration, commands, connectors, and the development loop.",
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: "web",
|
|
37
|
+
area: "Full-stack React",
|
|
38
|
+
description:
|
|
39
|
+
"Server-rendered pages, loaders, metadata, layouts, and client-side navigation in one package.",
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: "cascade",
|
|
43
|
+
area: "Data",
|
|
44
|
+
description:
|
|
45
|
+
"Models, queries, relationships, and migrations for applications that need durable data.",
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
name: "seal",
|
|
49
|
+
area: "Validation",
|
|
50
|
+
description: "Composable schemas that validate and shape data at every application boundary.",
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: "auth",
|
|
54
|
+
area: "Identity",
|
|
55
|
+
description:
|
|
56
|
+
"Authentication primitives for protecting routes and carrying user identity through requests.",
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
name: "cache",
|
|
60
|
+
area: "Performance",
|
|
61
|
+
description:
|
|
62
|
+
"A consistent caching layer with replaceable drivers and predictable key management.",
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
name: "scheduler",
|
|
66
|
+
area: "Automation",
|
|
67
|
+
description:
|
|
68
|
+
"Declare recurring application work and run it alongside the same Warlock runtime.",
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
name: "ai",
|
|
72
|
+
area: "Intelligence",
|
|
73
|
+
description:
|
|
74
|
+
"Build agents, tool-driven workflows, and model-backed product capabilities with typed contracts.",
|
|
75
|
+
},
|
|
76
|
+
] as const;
|
|
77
|
+
|
|
78
|
+
export async function getHomeService() {
|
|
79
|
+
return {
|
|
80
|
+
packages,
|
|
81
|
+
capabilities,
|
|
82
|
+
statusMessage: "Ready to build.",
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export type HomePageData = Awaited<ReturnType<typeof getHomeService>>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { localeSchema, type LocaleSchema } from "@shared/locale.schema";
|
|
2
|
+
import { type Request, type RequestHandler } from "@warlock.js/core";
|
|
3
|
+
|
|
4
|
+
/** POST /api/locale — persists the visitor's locale preference in a cookie. */
|
|
5
|
+
export const localeController: RequestHandler<Request<LocaleSchema>> = async ({
|
|
6
|
+
request,
|
|
7
|
+
response,
|
|
8
|
+
}) => {
|
|
9
|
+
const { locale } = request.validated();
|
|
10
|
+
|
|
11
|
+
response.cookie("locale", locale, { raw: true, path: "/" });
|
|
12
|
+
|
|
13
|
+
return response.success({ locale });
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
localeController.validation = { schema: localeSchema };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { v, type Infer } from "@warlock.js/seal";
|
|
2
|
+
import { localeCodes } from "./locales";
|
|
3
|
+
|
|
4
|
+
export const localeSchema = v.object({
|
|
5
|
+
locale: v
|
|
6
|
+
.string()
|
|
7
|
+
.in([...localeCodes])
|
|
8
|
+
.required(),
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
export type LocaleSchema = Infer.Output<typeof localeSchema>;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
.not-found,
|
|
2
|
+
.not-found *,
|
|
3
|
+
.not-found *::before,
|
|
4
|
+
.not-found *::after {
|
|
5
|
+
box-sizing: border-box;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.not-found {
|
|
9
|
+
display: grid;
|
|
10
|
+
min-block-size: 100vh;
|
|
11
|
+
min-block-size: 100dvb;
|
|
12
|
+
align-items: center;
|
|
13
|
+
padding-block: clamp(2rem, 8vh, 6rem);
|
|
14
|
+
padding-inline: clamp(1.5rem, 7vw, 8rem);
|
|
15
|
+
background: #fafafa;
|
|
16
|
+
color: #18181b;
|
|
17
|
+
font-family:
|
|
18
|
+
system-ui,
|
|
19
|
+
-apple-system,
|
|
20
|
+
BlinkMacSystemFont,
|
|
21
|
+
"Segoe UI",
|
|
22
|
+
sans-serif;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.not-found__content {
|
|
26
|
+
inline-size: min(100%, 62rem);
|
|
27
|
+
margin-inline-start: clamp(0rem, 7vw, 8rem);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.not-found__title,
|
|
31
|
+
.not-found__description {
|
|
32
|
+
margin: 0;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.not-found h1 {
|
|
36
|
+
margin: 0;
|
|
37
|
+
color: #facc15;
|
|
38
|
+
font-size: clamp(8rem, 25vw, 22rem);
|
|
39
|
+
font-weight: 800;
|
|
40
|
+
letter-spacing: -0.09em;
|
|
41
|
+
line-height: 0.72;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.not-found__title {
|
|
45
|
+
margin-block-start: clamp(2rem, 6vw, 5rem);
|
|
46
|
+
font-size: clamp(1.5rem, 3vw, 2.5rem);
|
|
47
|
+
font-weight: 680;
|
|
48
|
+
letter-spacing: -0.035em;
|
|
49
|
+
line-height: 1.1;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.not-found__description {
|
|
53
|
+
max-inline-size: 30rem;
|
|
54
|
+
margin-block-start: 0.75rem;
|
|
55
|
+
color: #52525b;
|
|
56
|
+
font-size: clamp(1rem, 1.4vw, 1.125rem);
|
|
57
|
+
line-height: 1.55;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.not-found__action {
|
|
61
|
+
display: inline-block;
|
|
62
|
+
margin-block-start: clamp(1.5rem, 3vw, 2.5rem);
|
|
63
|
+
padding-block: 0.65rem;
|
|
64
|
+
padding-inline: 1rem;
|
|
65
|
+
background: #facc15;
|
|
66
|
+
color: #18181b;
|
|
67
|
+
font-weight: 700;
|
|
68
|
+
text-decoration: none;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.not-found__action:hover {
|
|
72
|
+
text-decoration: underline;
|
|
73
|
+
text-underline-offset: 0.18em;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.not-found__action:focus-visible {
|
|
77
|
+
outline: 3px solid #18181b;
|
|
78
|
+
outline-offset: 4px;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
:dir(rtl) .not-found__content {
|
|
82
|
+
margin-inline-start: 0;
|
|
83
|
+
margin-inline-end: clamp(0rem, 7vw, 8rem);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
@media (max-width: 40rem) {
|
|
87
|
+
.not-found__content {
|
|
88
|
+
margin-inline-start: 0;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
:dir(rtl) .not-found__content {
|
|
92
|
+
margin-inline-end: 0;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
@media (prefers-color-scheme: dark) {
|
|
97
|
+
.not-found {
|
|
98
|
+
background: #18181b;
|
|
99
|
+
color: #fafafa;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.not-found__description {
|
|
103
|
+
color: #d4d4d8;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.not-found__action {
|
|
107
|
+
color: #18181b;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.not-found__action:focus-visible {
|
|
111
|
+
outline-color: #fafafa;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import "./404.css";
|
|
2
|
+
|
|
3
|
+
export const metadata = {
|
|
4
|
+
title: "Page not found",
|
|
5
|
+
robots: "noindex",
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export default function NotFoundPage() {
|
|
9
|
+
return (
|
|
10
|
+
<main className="not-found">
|
|
11
|
+
<div className="not-found__content">
|
|
12
|
+
<h1>404</h1>
|
|
13
|
+
<p className="not-found__title">Page not found.</p>
|
|
14
|
+
<p className="not-found__description">
|
|
15
|
+
The link may be outdated, or the page may have moved.
|
|
16
|
+
</p>
|
|
17
|
+
<a className="not-found__action" href="/">
|
|
18
|
+
Return home
|
|
19
|
+
</a>
|
|
20
|
+
</div>
|
|
21
|
+
</main>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { type FormControlProps, useFormControl, useSubmitButton } from "@mongez/react-form";
|
|
2
|
+
import { type ReactNode } from "react";
|
|
3
|
+
|
|
4
|
+
type ContactFieldProps = FormControlProps & {
|
|
5
|
+
label: ReactNode;
|
|
6
|
+
multiline?: boolean;
|
|
7
|
+
rows?: number;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export function ContactField({ label, multiline, rows, ...props }: ContactFieldProps) {
|
|
11
|
+
const { error, getErrorProps, getInputProps, id } = useFormControl(props);
|
|
12
|
+
const inputProps = getInputProps(rows ? { rows } : undefined);
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<label htmlFor={id}>
|
|
16
|
+
<span>{label}</span>
|
|
17
|
+
{multiline ? <textarea {...inputProps} /> : <input {...inputProps} />}
|
|
18
|
+
{error ? (
|
|
19
|
+
<small className="warlock-field-error" {...getErrorProps()}>
|
|
20
|
+
{error}
|
|
21
|
+
</small>
|
|
22
|
+
) : null}
|
|
23
|
+
</label>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function ContactSubmitButton({
|
|
28
|
+
idleLabel,
|
|
29
|
+
submittingLabel,
|
|
30
|
+
}: {
|
|
31
|
+
idleLabel: ReactNode;
|
|
32
|
+
submittingLabel: ReactNode;
|
|
33
|
+
}) {
|
|
34
|
+
const { disabled, isSubmitting } = useSubmitButton();
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<button className="warlock-contact-submit" type="submit" disabled={disabled}>
|
|
38
|
+
{isSubmitting ? submittingLabel : idleLabel}
|
|
39
|
+
<span aria-hidden="true">→</span>
|
|
40
|
+
</button>
|
|
41
|
+
);
|
|
42
|
+
}
|