@farm.js/create-app 0.1.0-beta.20 → 0.1.0-beta.22
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/README.md +8 -6
- package/bin/create-farm-app.js +1 -1
- package/dist/index.js +34 -8
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +34 -8
- package/dist/index.mjs.map +1 -1
- package/dist/utils.js +8 -6
- package/dist/utils.js.map +1 -1
- package/dist/utils.mjs +8 -6
- package/dist/utils.mjs.map +1 -1
- package/package.json +2 -2
- package/templates/auth/.env.example +6 -0
- package/templates/auth/README.md +95 -0
- package/templates/auth/farm.config.ts +11 -0
- package/templates/auth/gitignore +10 -0
- package/templates/auth/package.json +32 -0
- package/templates/auth/pnpm-workspace.yaml +13 -0
- package/templates/auth/src/app/dashboard/middleware.ts +13 -0
- package/templates/auth/src/app/dashboard/page.tsx +140 -0
- package/templates/auth/src/app/error.tsx +25 -0
- package/templates/auth/src/app/globals.css +1064 -0
- package/templates/auth/src/app/layout.tsx +11 -0
- package/templates/auth/src/app/loading.tsx +15 -0
- package/templates/auth/src/app/not-found.tsx +26 -0
- package/templates/auth/src/app/page.tsx +46 -0
- package/templates/auth/src/app/sign-in/page.tsx +16 -0
- package/templates/auth/src/app/sign-up/page.tsx +16 -0
- package/templates/auth/src/components/auth-form.tsx +120 -0
- package/templates/auth/src/components/auth-shell.tsx +21 -0
- package/templates/auth/src/components/resource-links.tsx +78 -0
- package/templates/auth/src/components/sign-out-button.tsx +45 -0
- package/templates/auth/src/components/site-header.tsx +46 -0
- package/templates/auth/tsconfig.json +20 -0
- package/templates/basic/package.json +4 -2
- package/templates/basic/pnpm-workspace.yaml +8 -0
- package/templates/basic/src/app/globals.css +241 -0
- package/templates/basic/src/app/layout.tsx +2 -4
- package/templates/basic/src/app/page.tsx +20 -61
- package/templates/basic/src/components/resource-links.tsx +78 -0
- package/templates/better-auth/.env.example +3 -0
- package/templates/better-auth/README.md +87 -0
- package/templates/better-auth/farm.config.ts +29 -0
- package/templates/better-auth/gitignore +10 -0
- package/templates/better-auth/package.json +34 -0
- package/templates/better-auth/pnpm-workspace.yaml +13 -0
- package/templates/better-auth/src/app/dashboard/middleware.ts +15 -0
- package/templates/better-auth/src/app/dashboard/page.tsx +188 -0
- package/templates/better-auth/src/app/error.tsx +25 -0
- package/templates/better-auth/src/app/globals.css +1064 -0
- package/templates/better-auth/src/app/layout.tsx +11 -0
- package/templates/better-auth/src/app/loading.tsx +15 -0
- package/templates/better-auth/src/app/not-found.tsx +26 -0
- package/templates/better-auth/src/app/page.tsx +50 -0
- package/templates/better-auth/src/app/sign-in/page.tsx +16 -0
- package/templates/better-auth/src/app/sign-up/page.tsx +16 -0
- package/templates/better-auth/src/components/auth-form.tsx +120 -0
- package/templates/better-auth/src/components/auth-shell.tsx +21 -0
- package/templates/better-auth/src/components/resource-links.tsx +78 -0
- package/templates/better-auth/src/components/sign-out-button.tsx +45 -0
- package/templates/better-auth/src/components/site-header.tsx +46 -0
- package/templates/better-auth/src/lib/auth-client.ts +5 -0
- package/templates/better-auth/src/lib/auth.ts +50 -0
- package/templates/better-auth/src/lib/session.ts +8 -0
- package/templates/better-auth/tsconfig.json +20 -0
- package/templates/basic/src/app/about/page.tsx +0 -32
package/README.md
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
# @farm.js/create-app
|
|
2
2
|
|
|
3
|
-
Create a new
|
|
3
|
+
Create a new FARMJS application
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
FARMJS is currently in beta.
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
pnpm create @farm.js/app my-app
|
|
8
|
+
pnpm create @farm.js/app@beta my-app --template basic --typescript
|
|
9
9
|
cd my-app
|
|
10
10
|
pnpm dev
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
The scaffolder installs React, React DOM,
|
|
14
|
-
automatically.
|
|
13
|
+
The scaffolder installs React, React DOM, FARMJS, TypeScript, and the other starter dependencies
|
|
14
|
+
automatically. The command explicitly selects the minimal Basic starter. Use `pnpm create`, not
|
|
15
|
+
`pnpm add`; pnpm resolves this initializer command to the published `@farm.js/create-app` package.
|
|
16
|
+
Pass `--skip-install` when you only want to generate the project files.
|
|
15
17
|
|
|
16
|
-
See the [
|
|
18
|
+
See the [FARMJS repository](https://github.com/farming-labs/farm.js) for documentation, examples, and support.
|
package/bin/create-farm-app.js
CHANGED
|
@@ -6,7 +6,7 @@ const { version } = require("../package.json");
|
|
|
6
6
|
|
|
7
7
|
program
|
|
8
8
|
.name("create-farm-app")
|
|
9
|
-
.description("Create a new
|
|
9
|
+
.description("Create a new FARMJS application")
|
|
10
10
|
.version(version)
|
|
11
11
|
.argument("[project-name]", "Name of the project")
|
|
12
12
|
.option("-t, --template <template>", "Template to use")
|
package/dist/index.js
CHANGED
|
@@ -9,6 +9,23 @@ let fs_promises = require("fs/promises");
|
|
|
9
9
|
fs_promises = require_rolldown_runtime.__toESM(fs_promises);
|
|
10
10
|
let node_child_process = require("node:child_process");
|
|
11
11
|
//#region src/index.ts
|
|
12
|
+
const templateDetails = {
|
|
13
|
+
basic: {
|
|
14
|
+
title: "FARMJS Basic",
|
|
15
|
+
description: "A minimal FARMJS app with built-in Tailwind support",
|
|
16
|
+
instructions: ["Tailwind is enabled by default. You only need postcss config for custom plugins."]
|
|
17
|
+
},
|
|
18
|
+
auth: {
|
|
19
|
+
title: "FARMJS Auth",
|
|
20
|
+
description: "FARMJS-native auth with local SQLite, secure sessions, and protected routes",
|
|
21
|
+
instructions: ["FARMJS Auth uses local SQLite automatically; no auth environment variables are needed locally.", "For production, set FARM_AUTH_URL, FARM_AUTH_SECRET, and DATABASE_URL, then run the auth:migrate script."]
|
|
22
|
+
},
|
|
23
|
+
"better-auth": {
|
|
24
|
+
title: "FARMJS Better Auth",
|
|
25
|
+
description: "Better Auth with Postgres, secure sessions, and protected routes",
|
|
26
|
+
instructions: ["Before starting, copy .env.example to .env.local and set DATABASE_URL and BETTER_AUTH_SECRET.", "Better Auth migrations run automatically when the auth instance starts."]
|
|
27
|
+
}
|
|
28
|
+
};
|
|
12
29
|
async function createApp(projectName, options = {}) {
|
|
13
30
|
require_utils.showBanner();
|
|
14
31
|
const templates = await getAvailableTemplates();
|
|
@@ -42,11 +59,14 @@ async function createApp(projectName, options = {}) {
|
|
|
42
59
|
type: "select",
|
|
43
60
|
name: "template",
|
|
44
61
|
message: "Which template would you like to use?",
|
|
45
|
-
choices: templates.map((name) =>
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
62
|
+
choices: templates.map((name) => {
|
|
63
|
+
const details = templateDetails[name];
|
|
64
|
+
return {
|
|
65
|
+
title: details?.title ?? prettifyTemplateName(name),
|
|
66
|
+
value: name,
|
|
67
|
+
description: details?.description
|
|
68
|
+
};
|
|
69
|
+
}),
|
|
50
70
|
initial: 0
|
|
51
71
|
});
|
|
52
72
|
if (!response.template) {
|
|
@@ -85,7 +105,7 @@ async function createApp(projectName, options = {}) {
|
|
|
85
105
|
process.exit(1);
|
|
86
106
|
}
|
|
87
107
|
}
|
|
88
|
-
require_utils.logger.info(`Creating
|
|
108
|
+
require_utils.logger.info(`Creating FARMJS app in ${projectPath}`);
|
|
89
109
|
await fs_promises.default.mkdir(projectPath, { recursive: true });
|
|
90
110
|
await copyTemplate(template, projectPath, useTypeScript);
|
|
91
111
|
await updatePackageJson(projectPath, projectName, packageManager);
|
|
@@ -101,7 +121,7 @@ async function createApp(projectName, options = {}) {
|
|
|
101
121
|
if (options.skipInstall) require_utils.logger.info(` ${packageManager.name} install`);
|
|
102
122
|
require_utils.logger.info(` ${getDevCommand(packageManager.name)}`);
|
|
103
123
|
require_utils.logger.info("");
|
|
104
|
-
|
|
124
|
+
for (const instruction of templateDetails[template]?.instructions ?? []) require_utils.logger.info(instruction);
|
|
105
125
|
}
|
|
106
126
|
async function copyTemplate(template, projectPath, useTypeScript) {
|
|
107
127
|
await copyDir(path.default.join(__dirname, "..", "templates", template), projectPath);
|
|
@@ -152,7 +172,13 @@ function prettifyTemplateName(name) {
|
|
|
152
172
|
}
|
|
153
173
|
async function getAvailableTemplates() {
|
|
154
174
|
const templatesRoot = path.default.join(__dirname, "..", "templates");
|
|
155
|
-
|
|
175
|
+
const entries = await fs_promises.default.readdir(templatesRoot, { withFileTypes: true });
|
|
176
|
+
const templateOrder = Object.keys(templateDetails);
|
|
177
|
+
return entries.filter((entry) => entry.isDirectory() && !entry.name.startsWith("_")).map((entry) => entry.name).sort((left, right) => {
|
|
178
|
+
const leftIndex = templateOrder.indexOf(left);
|
|
179
|
+
const rightIndex = templateOrder.indexOf(right);
|
|
180
|
+
return (leftIndex === -1 ? Number.MAX_SAFE_INTEGER : leftIndex) - (rightIndex === -1 ? Number.MAX_SAFE_INTEGER : rightIndex) || left.localeCompare(right);
|
|
181
|
+
});
|
|
156
182
|
}
|
|
157
183
|
async function updatePackageJson(projectPath, projectName, packageManager) {
|
|
158
184
|
const packageJsonPath = path.default.join(projectPath, "package.json");
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["fs","path","spawn"],"sources":["../src/index.ts"],"sourcesContent":["import prompts from \"prompts\";\nimport path from \"path\";\nimport fs from \"fs/promises\";\nimport { spawn } from \"node:child_process\";\nimport { logger, showBanner } from \"./utils\";\n\ninterface CreateAppOptions {\n template?: string;\n typescript?: boolean;\n skipInstall?: boolean;\n}\n\nexport type PackageManagerName = \"npm\" | \"pnpm\" | \"yarn\" | \"bun\";\n\nexport interface PackageManager {\n name: PackageManagerName;\n version?: string;\n}\n\nexport async function createApp(projectName?: string, options: CreateAppOptions = {}) {\n showBanner();\n\n const templates = await getAvailableTemplates();\n if (templates.length === 0) {\n logger.error(\"No templates are available in this package.\");\n process.exit(1);\n }\n\n // Get project name if not provided\n if (!projectName) {\n const response = await prompts({\n type: \"text\",\n name: \"projectName\",\n message: \"What is your project named?\",\n initial: \"my-farm-app\",\n validate: validateProjectName,\n });\n\n if (!response.projectName) {\n logger.error(\"Operation cancelled.\");\n process.exit(1);\n }\n\n projectName = response.projectName;\n } else {\n const validation = validateProjectPathArg(projectName);\n if (validation !== true) {\n logger.error(validation);\n process.exit(1);\n }\n }\n\n // Get template if not provided\n let template = options.template;\n if (!template) {\n const response = await prompts({\n type: \"select\",\n name: \"template\",\n message: \"Which template would you like to use?\",\n choices: templates.map((name) => ({\n title: prettifyTemplateName(name),\n value: name,\n description:\n name === \"basic\" ? \"A simple Farm.js app with built-in Tailwind support\" : undefined,\n })),\n initial: 0,\n });\n\n if (!response.template) {\n logger.error(\"Operation cancelled.\");\n process.exit(1);\n }\n template = response.template;\n } else if (!templates.includes(template)) {\n logger.error(\n `Unknown template \"${template}\". Available: ${templates.map((t) => `\"${t}\"`).join(\", \")}`,\n );\n process.exit(1);\n }\n\n // Check TypeScript preference\n let useTypeScript = options.typescript;\n if (useTypeScript === undefined) {\n const response = await prompts({\n type: \"confirm\",\n name: \"typescript\",\n message: \"Would you like to use TypeScript?\",\n initial: true,\n });\n\n if (response.typescript === undefined) {\n logger.error(\"Operation cancelled.\");\n process.exit(1);\n }\n useTypeScript = response.typescript;\n }\n\n const projectPath = path.resolve(process.cwd(), projectName!);\n const packageManager = detectPackageManager();\n const hasExistingFiles = await directoryHasFiles(projectPath);\n if (hasExistingFiles) {\n const overwriteResponse = await prompts({\n type: \"confirm\",\n name: \"overwrite\",\n message: `Directory \"${projectName}\" is not empty. Continue and overwrite conflicting files?`,\n initial: false,\n });\n\n if (!overwriteResponse.overwrite) {\n logger.error(\"Operation cancelled.\");\n process.exit(1);\n }\n }\n\n logger.info(`Creating Farm.js app in ${projectPath}`);\n\n await fs.mkdir(projectPath, { recursive: true });\n\n await copyTemplate(template!, projectPath, useTypeScript!);\n\n await updatePackageJson(projectPath, projectName!, packageManager);\n\n logger.success(`🚜 Created ${projectName}`);\n\n if (!options.skipInstall) {\n logger.info(`Installing dependencies with ${packageManager.name}...`);\n await installDependencies(projectPath, packageManager);\n logger.success(\"Dependencies installed\");\n }\n\n logger.info(\"\");\n logger.info(\"Next steps\");\n logger.info(` cd ${projectName}`);\n if (options.skipInstall) {\n logger.info(` ${packageManager.name} install`);\n }\n logger.info(` ${getDevCommand(packageManager.name)}`);\n logger.info(\"\");\n logger.info(\"Tailwind is enabled by default. You only need postcss config for custom plugins.\");\n}\n\nasync function copyTemplate(template: string, projectPath: string, useTypeScript: boolean) {\n const templatePath = path.join(__dirname, \"..\", \"templates\", template);\n\n // Copy base template files\n await copyDir(templatePath, projectPath);\n\n // If TypeScript is requested, copy TS-specific files\n if (useTypeScript) {\n const tsTemplatePath = path.join(__dirname, \"..\", \"templates\", \"_typescript\");\n if (await dirExists(tsTemplatePath)) {\n await copyDir(tsTemplatePath, projectPath);\n }\n }\n}\n\nasync function copyDir(src: string, dest: string) {\n await fs.mkdir(dest, { recursive: true });\n\n const entries = await fs.readdir(src, { withFileTypes: true });\n\n for (const entry of entries) {\n const srcPath = path.join(src, entry.name);\n // npm excludes .gitignore files from published tarballs. Keep the template\n // file publishable and restore its dot when generating the application.\n const destinationName = entry.name === \"gitignore\" ? \".gitignore\" : entry.name;\n const destPath = path.join(dest, destinationName);\n\n if (entry.isDirectory()) {\n await copyDir(srcPath, destPath);\n } else {\n await fs.copyFile(srcPath, destPath);\n }\n }\n}\n\nasync function dirExists(path: string): Promise<boolean> {\n try {\n const stat = await fs.stat(path);\n return stat.isDirectory();\n } catch {\n return false;\n }\n}\n\nasync function directoryHasFiles(dirPath: string): Promise<boolean> {\n if (!(await dirExists(dirPath))) {\n return false;\n }\n const files = await fs.readdir(dirPath);\n return files.length > 0;\n}\n\nfunction validateProjectName(value: string): true | string {\n const trimmed = value.trim();\n if (!trimmed) {\n return \"Project name is required\";\n }\n\n // npm package-safe pattern\n if (!/^[a-z0-9._-]+$/i.test(trimmed)) {\n return \"Use letters, numbers, hyphens, underscores, or dots\";\n }\n\n if (trimmed.startsWith(\".\") || trimmed.startsWith(\"_\")) {\n return \"Project name cannot start with '.' or '_'\";\n }\n\n return true;\n}\n\nfunction validateProjectPathArg(value: string): true | string {\n const trimmed = value.trim();\n if (!trimmed) {\n return \"Project name is required\";\n }\n\n const normalized = trimmed.replace(/[\\\\/]+$/, \"\");\n const baseName = path.basename(normalized);\n if (!baseName || baseName === \".\" || baseName === \"..\") {\n return \"Please provide a valid project directory name\";\n }\n\n return validateProjectName(baseName);\n}\n\nfunction prettifyTemplateName(name: string): string {\n return name\n .split(\"-\")\n .map((part) => part.charAt(0).toUpperCase() + part.slice(1))\n .join(\" \");\n}\n\nasync function getAvailableTemplates(): Promise<string[]> {\n const templatesRoot = path.join(__dirname, \"..\", \"templates\");\n const entries = await fs.readdir(templatesRoot, { withFileTypes: true });\n return entries\n .filter((entry) => entry.isDirectory() && !entry.name.startsWith(\"_\"))\n .map((entry) => entry.name)\n .sort();\n}\n\nasync function updatePackageJson(\n projectPath: string,\n projectName: string,\n packageManager: PackageManager,\n) {\n const packageJsonPath = path.join(projectPath, \"package.json\");\n\n try {\n const content = await fs.readFile(packageJsonPath, \"utf-8\");\n const packageJson = JSON.parse(content);\n\n packageJson.name = projectName;\n if (packageManager.version) {\n packageJson.packageManager = `${packageManager.name}@${packageManager.version}`;\n }\n\n await fs.writeFile(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}\\n`);\n } catch {\n logger.warn(\"Could not update package.json\");\n }\n}\n\nexport function detectPackageManager(\n userAgent = process.env.npm_config_user_agent,\n): PackageManager {\n const match = userAgent?.match(/^(npm|pnpm|yarn|bun)\\/([^\\s]+)/);\n if (!match) {\n return { name: \"pnpm\" };\n }\n\n const [, name, version] = match;\n return {\n name: name as PackageManagerName,\n version: version === \"?\" ? undefined : version,\n };\n}\n\nexport function installDependencies(\n projectPath: string,\n packageManager: PackageManager,\n): Promise<void> {\n return new Promise((resolve, reject) => {\n const command =\n process.platform === \"win32\" ? `${packageManager.name}.cmd` : packageManager.name;\n const child = spawn(command, [\"install\"], {\n cwd: projectPath,\n env: process.env,\n stdio: \"inherit\",\n });\n\n child.on(\"error\", reject);\n child.on(\"close\", (code, signal) => {\n if (code === 0) {\n resolve();\n return;\n }\n\n const reason = signal ? `signal ${signal}` : `exit code ${code ?? \"unknown\"}`;\n reject(new Error(`${packageManager.name} install failed with ${reason}.`));\n });\n });\n}\n\nfunction getDevCommand(packageManager: PackageManagerName) {\n if (packageManager === \"npm\" || packageManager === \"bun\") {\n return `${packageManager} run dev`;\n }\n return `${packageManager} dev`;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["fs","path","spawn"],"sources":["../src/index.ts"],"sourcesContent":["import prompts from \"prompts\";\nimport path from \"path\";\nimport fs from \"fs/promises\";\nimport { spawn } from \"node:child_process\";\nimport { logger, showBanner } from \"./utils\";\n\ninterface CreateAppOptions {\n template?: string;\n typescript?: boolean;\n skipInstall?: boolean;\n}\n\nconst templateDetails: Record<\n string,\n {\n title: string;\n description: string;\n instructions: string[];\n }\n> = {\n basic: {\n title: \"FARMJS Basic\",\n description: \"A minimal FARMJS app with built-in Tailwind support\",\n instructions: [\n \"Tailwind is enabled by default. You only need postcss config for custom plugins.\",\n ],\n },\n auth: {\n title: \"FARMJS Auth\",\n description: \"FARMJS-native auth with local SQLite, secure sessions, and protected routes\",\n instructions: [\n \"FARMJS Auth uses local SQLite automatically; no auth environment variables are needed locally.\",\n \"For production, set FARM_AUTH_URL, FARM_AUTH_SECRET, and DATABASE_URL, then run the auth:migrate script.\",\n ],\n },\n \"better-auth\": {\n title: \"FARMJS Better Auth\",\n description: \"Better Auth with Postgres, secure sessions, and protected routes\",\n instructions: [\n \"Before starting, copy .env.example to .env.local and set DATABASE_URL and BETTER_AUTH_SECRET.\",\n \"Better Auth migrations run automatically when the auth instance starts.\",\n ],\n },\n};\n\nexport type PackageManagerName = \"npm\" | \"pnpm\" | \"yarn\" | \"bun\";\n\nexport interface PackageManager {\n name: PackageManagerName;\n version?: string;\n}\n\nexport async function createApp(projectName?: string, options: CreateAppOptions = {}) {\n showBanner();\n\n const templates = await getAvailableTemplates();\n if (templates.length === 0) {\n logger.error(\"No templates are available in this package.\");\n process.exit(1);\n }\n\n // Get project name if not provided\n if (!projectName) {\n const response = await prompts({\n type: \"text\",\n name: \"projectName\",\n message: \"What is your project named?\",\n initial: \"my-farm-app\",\n validate: validateProjectName,\n });\n\n if (!response.projectName) {\n logger.error(\"Operation cancelled.\");\n process.exit(1);\n }\n\n projectName = response.projectName;\n } else {\n const validation = validateProjectPathArg(projectName);\n if (validation !== true) {\n logger.error(validation);\n process.exit(1);\n }\n }\n\n // Get template if not provided\n let template = options.template;\n if (!template) {\n const response = await prompts({\n type: \"select\",\n name: \"template\",\n message: \"Which template would you like to use?\",\n choices: templates.map((name) => {\n const details = templateDetails[name];\n return {\n title: details?.title ?? prettifyTemplateName(name),\n value: name,\n description: details?.description,\n };\n }),\n initial: 0,\n });\n\n if (!response.template) {\n logger.error(\"Operation cancelled.\");\n process.exit(1);\n }\n template = response.template;\n } else if (!templates.includes(template)) {\n logger.error(\n `Unknown template \"${template}\". Available: ${templates.map((t) => `\"${t}\"`).join(\", \")}`,\n );\n process.exit(1);\n }\n\n // Check TypeScript preference\n let useTypeScript = options.typescript;\n if (useTypeScript === undefined) {\n const response = await prompts({\n type: \"confirm\",\n name: \"typescript\",\n message: \"Would you like to use TypeScript?\",\n initial: true,\n });\n\n if (response.typescript === undefined) {\n logger.error(\"Operation cancelled.\");\n process.exit(1);\n }\n useTypeScript = response.typescript;\n }\n\n const projectPath = path.resolve(process.cwd(), projectName!);\n const packageManager = detectPackageManager();\n const hasExistingFiles = await directoryHasFiles(projectPath);\n if (hasExistingFiles) {\n const overwriteResponse = await prompts({\n type: \"confirm\",\n name: \"overwrite\",\n message: `Directory \"${projectName}\" is not empty. Continue and overwrite conflicting files?`,\n initial: false,\n });\n\n if (!overwriteResponse.overwrite) {\n logger.error(\"Operation cancelled.\");\n process.exit(1);\n }\n }\n\n logger.info(`Creating FARMJS app in ${projectPath}`);\n\n await fs.mkdir(projectPath, { recursive: true });\n\n await copyTemplate(template!, projectPath, useTypeScript!);\n\n await updatePackageJson(projectPath, projectName!, packageManager);\n\n logger.success(`🚜 Created ${projectName}`);\n\n if (!options.skipInstall) {\n logger.info(`Installing dependencies with ${packageManager.name}...`);\n await installDependencies(projectPath, packageManager);\n logger.success(\"Dependencies installed\");\n }\n\n logger.info(\"\");\n logger.info(\"Next steps\");\n logger.info(` cd ${projectName}`);\n if (options.skipInstall) {\n logger.info(` ${packageManager.name} install`);\n }\n logger.info(` ${getDevCommand(packageManager.name)}`);\n logger.info(\"\");\n for (const instruction of templateDetails[template!]?.instructions ?? []) {\n logger.info(instruction);\n }\n}\n\nasync function copyTemplate(template: string, projectPath: string, useTypeScript: boolean) {\n const templatePath = path.join(__dirname, \"..\", \"templates\", template);\n\n // Copy base template files\n await copyDir(templatePath, projectPath);\n\n // If TypeScript is requested, copy TS-specific files\n if (useTypeScript) {\n const tsTemplatePath = path.join(__dirname, \"..\", \"templates\", \"_typescript\");\n if (await dirExists(tsTemplatePath)) {\n await copyDir(tsTemplatePath, projectPath);\n }\n }\n}\n\nasync function copyDir(src: string, dest: string) {\n await fs.mkdir(dest, { recursive: true });\n\n const entries = await fs.readdir(src, { withFileTypes: true });\n\n for (const entry of entries) {\n const srcPath = path.join(src, entry.name);\n // npm excludes .gitignore files from published tarballs. Keep the template\n // file publishable and restore its dot when generating the application.\n const destinationName = entry.name === \"gitignore\" ? \".gitignore\" : entry.name;\n const destPath = path.join(dest, destinationName);\n\n if (entry.isDirectory()) {\n await copyDir(srcPath, destPath);\n } else {\n await fs.copyFile(srcPath, destPath);\n }\n }\n}\n\nasync function dirExists(path: string): Promise<boolean> {\n try {\n const stat = await fs.stat(path);\n return stat.isDirectory();\n } catch {\n return false;\n }\n}\n\nasync function directoryHasFiles(dirPath: string): Promise<boolean> {\n if (!(await dirExists(dirPath))) {\n return false;\n }\n const files = await fs.readdir(dirPath);\n return files.length > 0;\n}\n\nfunction validateProjectName(value: string): true | string {\n const trimmed = value.trim();\n if (!trimmed) {\n return \"Project name is required\";\n }\n\n // npm package-safe pattern\n if (!/^[a-z0-9._-]+$/i.test(trimmed)) {\n return \"Use letters, numbers, hyphens, underscores, or dots\";\n }\n\n if (trimmed.startsWith(\".\") || trimmed.startsWith(\"_\")) {\n return \"Project name cannot start with '.' or '_'\";\n }\n\n return true;\n}\n\nfunction validateProjectPathArg(value: string): true | string {\n const trimmed = value.trim();\n if (!trimmed) {\n return \"Project name is required\";\n }\n\n const normalized = trimmed.replace(/[\\\\/]+$/, \"\");\n const baseName = path.basename(normalized);\n if (!baseName || baseName === \".\" || baseName === \"..\") {\n return \"Please provide a valid project directory name\";\n }\n\n return validateProjectName(baseName);\n}\n\nfunction prettifyTemplateName(name: string): string {\n return name\n .split(\"-\")\n .map((part) => part.charAt(0).toUpperCase() + part.slice(1))\n .join(\" \");\n}\n\nasync function getAvailableTemplates(): Promise<string[]> {\n const templatesRoot = path.join(__dirname, \"..\", \"templates\");\n const entries = await fs.readdir(templatesRoot, { withFileTypes: true });\n const templateOrder = Object.keys(templateDetails);\n return entries\n .filter((entry) => entry.isDirectory() && !entry.name.startsWith(\"_\"))\n .map((entry) => entry.name)\n .sort((left, right) => {\n const leftIndex = templateOrder.indexOf(left);\n const rightIndex = templateOrder.indexOf(right);\n const normalizedLeft = leftIndex === -1 ? Number.MAX_SAFE_INTEGER : leftIndex;\n const normalizedRight = rightIndex === -1 ? Number.MAX_SAFE_INTEGER : rightIndex;\n return normalizedLeft - normalizedRight || left.localeCompare(right);\n });\n}\n\nasync function updatePackageJson(\n projectPath: string,\n projectName: string,\n packageManager: PackageManager,\n) {\n const packageJsonPath = path.join(projectPath, \"package.json\");\n\n try {\n const content = await fs.readFile(packageJsonPath, \"utf-8\");\n const packageJson = JSON.parse(content);\n\n packageJson.name = projectName;\n if (packageManager.version) {\n packageJson.packageManager = `${packageManager.name}@${packageManager.version}`;\n }\n\n await fs.writeFile(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}\\n`);\n } catch {\n logger.warn(\"Could not update package.json\");\n }\n}\n\nexport function detectPackageManager(\n userAgent = process.env.npm_config_user_agent,\n): PackageManager {\n const match = userAgent?.match(/^(npm|pnpm|yarn|bun)\\/([^\\s]+)/);\n if (!match) {\n return { name: \"pnpm\" };\n }\n\n const [, name, version] = match;\n return {\n name: name as PackageManagerName,\n version: version === \"?\" ? undefined : version,\n };\n}\n\nexport function installDependencies(\n projectPath: string,\n packageManager: PackageManager,\n): Promise<void> {\n return new Promise((resolve, reject) => {\n const command =\n process.platform === \"win32\" ? `${packageManager.name}.cmd` : packageManager.name;\n const child = spawn(command, [\"install\"], {\n cwd: projectPath,\n env: process.env,\n stdio: \"inherit\",\n });\n\n child.on(\"error\", reject);\n child.on(\"close\", (code, signal) => {\n if (code === 0) {\n resolve();\n return;\n }\n\n const reason = signal ? `signal ${signal}` : `exit code ${code ?? \"unknown\"}`;\n reject(new Error(`${packageManager.name} install failed with ${reason}.`));\n });\n });\n}\n\nfunction getDevCommand(packageManager: PackageManagerName) {\n if (packageManager === \"npm\" || packageManager === \"bun\") {\n return `${packageManager} run dev`;\n }\n return `${packageManager} dev`;\n}\n"],"mappings":"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"}
|
package/dist/index.mjs
CHANGED
|
@@ -4,6 +4,23 @@ import path from "path";
|
|
|
4
4
|
import fs from "fs/promises";
|
|
5
5
|
import { spawn } from "node:child_process";
|
|
6
6
|
//#region src/index.ts
|
|
7
|
+
const templateDetails = {
|
|
8
|
+
basic: {
|
|
9
|
+
title: "FARMJS Basic",
|
|
10
|
+
description: "A minimal FARMJS app with built-in Tailwind support",
|
|
11
|
+
instructions: ["Tailwind is enabled by default. You only need postcss config for custom plugins."]
|
|
12
|
+
},
|
|
13
|
+
auth: {
|
|
14
|
+
title: "FARMJS Auth",
|
|
15
|
+
description: "FARMJS-native auth with local SQLite, secure sessions, and protected routes",
|
|
16
|
+
instructions: ["FARMJS Auth uses local SQLite automatically; no auth environment variables are needed locally.", "For production, set FARM_AUTH_URL, FARM_AUTH_SECRET, and DATABASE_URL, then run the auth:migrate script."]
|
|
17
|
+
},
|
|
18
|
+
"better-auth": {
|
|
19
|
+
title: "FARMJS Better Auth",
|
|
20
|
+
description: "Better Auth with Postgres, secure sessions, and protected routes",
|
|
21
|
+
instructions: ["Before starting, copy .env.example to .env.local and set DATABASE_URL and BETTER_AUTH_SECRET.", "Better Auth migrations run automatically when the auth instance starts."]
|
|
22
|
+
}
|
|
23
|
+
};
|
|
7
24
|
async function createApp(projectName, options = {}) {
|
|
8
25
|
showBanner();
|
|
9
26
|
const templates = await getAvailableTemplates();
|
|
@@ -37,11 +54,14 @@ async function createApp(projectName, options = {}) {
|
|
|
37
54
|
type: "select",
|
|
38
55
|
name: "template",
|
|
39
56
|
message: "Which template would you like to use?",
|
|
40
|
-
choices: templates.map((name) =>
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
57
|
+
choices: templates.map((name) => {
|
|
58
|
+
const details = templateDetails[name];
|
|
59
|
+
return {
|
|
60
|
+
title: details?.title ?? prettifyTemplateName(name),
|
|
61
|
+
value: name,
|
|
62
|
+
description: details?.description
|
|
63
|
+
};
|
|
64
|
+
}),
|
|
45
65
|
initial: 0
|
|
46
66
|
});
|
|
47
67
|
if (!response.template) {
|
|
@@ -80,7 +100,7 @@ async function createApp(projectName, options = {}) {
|
|
|
80
100
|
process.exit(1);
|
|
81
101
|
}
|
|
82
102
|
}
|
|
83
|
-
logger.info(`Creating
|
|
103
|
+
logger.info(`Creating FARMJS app in ${projectPath}`);
|
|
84
104
|
await fs.mkdir(projectPath, { recursive: true });
|
|
85
105
|
await copyTemplate(template, projectPath, useTypeScript);
|
|
86
106
|
await updatePackageJson(projectPath, projectName, packageManager);
|
|
@@ -96,7 +116,7 @@ async function createApp(projectName, options = {}) {
|
|
|
96
116
|
if (options.skipInstall) logger.info(` ${packageManager.name} install`);
|
|
97
117
|
logger.info(` ${getDevCommand(packageManager.name)}`);
|
|
98
118
|
logger.info("");
|
|
99
|
-
|
|
119
|
+
for (const instruction of templateDetails[template]?.instructions ?? []) logger.info(instruction);
|
|
100
120
|
}
|
|
101
121
|
async function copyTemplate(template, projectPath, useTypeScript) {
|
|
102
122
|
await copyDir(path.join(__dirname, "..", "templates", template), projectPath);
|
|
@@ -147,7 +167,13 @@ function prettifyTemplateName(name) {
|
|
|
147
167
|
}
|
|
148
168
|
async function getAvailableTemplates() {
|
|
149
169
|
const templatesRoot = path.join(__dirname, "..", "templates");
|
|
150
|
-
|
|
170
|
+
const entries = await fs.readdir(templatesRoot, { withFileTypes: true });
|
|
171
|
+
const templateOrder = Object.keys(templateDetails);
|
|
172
|
+
return entries.filter((entry) => entry.isDirectory() && !entry.name.startsWith("_")).map((entry) => entry.name).sort((left, right) => {
|
|
173
|
+
const leftIndex = templateOrder.indexOf(left);
|
|
174
|
+
const rightIndex = templateOrder.indexOf(right);
|
|
175
|
+
return (leftIndex === -1 ? Number.MAX_SAFE_INTEGER : leftIndex) - (rightIndex === -1 ? Number.MAX_SAFE_INTEGER : rightIndex) || left.localeCompare(right);
|
|
176
|
+
});
|
|
151
177
|
}
|
|
152
178
|
async function updatePackageJson(projectPath, projectName, packageManager) {
|
|
153
179
|
const packageJsonPath = path.join(projectPath, "package.json");
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["import prompts from \"prompts\";\nimport path from \"path\";\nimport fs from \"fs/promises\";\nimport { spawn } from \"node:child_process\";\nimport { logger, showBanner } from \"./utils\";\n\ninterface CreateAppOptions {\n template?: string;\n typescript?: boolean;\n skipInstall?: boolean;\n}\n\nexport type PackageManagerName = \"npm\" | \"pnpm\" | \"yarn\" | \"bun\";\n\nexport interface PackageManager {\n name: PackageManagerName;\n version?: string;\n}\n\nexport async function createApp(projectName?: string, options: CreateAppOptions = {}) {\n showBanner();\n\n const templates = await getAvailableTemplates();\n if (templates.length === 0) {\n logger.error(\"No templates are available in this package.\");\n process.exit(1);\n }\n\n // Get project name if not provided\n if (!projectName) {\n const response = await prompts({\n type: \"text\",\n name: \"projectName\",\n message: \"What is your project named?\",\n initial: \"my-farm-app\",\n validate: validateProjectName,\n });\n\n if (!response.projectName) {\n logger.error(\"Operation cancelled.\");\n process.exit(1);\n }\n\n projectName = response.projectName;\n } else {\n const validation = validateProjectPathArg(projectName);\n if (validation !== true) {\n logger.error(validation);\n process.exit(1);\n }\n }\n\n // Get template if not provided\n let template = options.template;\n if (!template) {\n const response = await prompts({\n type: \"select\",\n name: \"template\",\n message: \"Which template would you like to use?\",\n choices: templates.map((name) => ({\n title: prettifyTemplateName(name),\n value: name,\n description:\n name === \"basic\" ? \"A simple Farm.js app with built-in Tailwind support\" : undefined,\n })),\n initial: 0,\n });\n\n if (!response.template) {\n logger.error(\"Operation cancelled.\");\n process.exit(1);\n }\n template = response.template;\n } else if (!templates.includes(template)) {\n logger.error(\n `Unknown template \"${template}\". Available: ${templates.map((t) => `\"${t}\"`).join(\", \")}`,\n );\n process.exit(1);\n }\n\n // Check TypeScript preference\n let useTypeScript = options.typescript;\n if (useTypeScript === undefined) {\n const response = await prompts({\n type: \"confirm\",\n name: \"typescript\",\n message: \"Would you like to use TypeScript?\",\n initial: true,\n });\n\n if (response.typescript === undefined) {\n logger.error(\"Operation cancelled.\");\n process.exit(1);\n }\n useTypeScript = response.typescript;\n }\n\n const projectPath = path.resolve(process.cwd(), projectName!);\n const packageManager = detectPackageManager();\n const hasExistingFiles = await directoryHasFiles(projectPath);\n if (hasExistingFiles) {\n const overwriteResponse = await prompts({\n type: \"confirm\",\n name: \"overwrite\",\n message: `Directory \"${projectName}\" is not empty. Continue and overwrite conflicting files?`,\n initial: false,\n });\n\n if (!overwriteResponse.overwrite) {\n logger.error(\"Operation cancelled.\");\n process.exit(1);\n }\n }\n\n logger.info(`Creating Farm.js app in ${projectPath}`);\n\n await fs.mkdir(projectPath, { recursive: true });\n\n await copyTemplate(template!, projectPath, useTypeScript!);\n\n await updatePackageJson(projectPath, projectName!, packageManager);\n\n logger.success(`🚜 Created ${projectName}`);\n\n if (!options.skipInstall) {\n logger.info(`Installing dependencies with ${packageManager.name}...`);\n await installDependencies(projectPath, packageManager);\n logger.success(\"Dependencies installed\");\n }\n\n logger.info(\"\");\n logger.info(\"Next steps\");\n logger.info(` cd ${projectName}`);\n if (options.skipInstall) {\n logger.info(` ${packageManager.name} install`);\n }\n logger.info(` ${getDevCommand(packageManager.name)}`);\n logger.info(\"\");\n logger.info(\"Tailwind is enabled by default. You only need postcss config for custom plugins.\");\n}\n\nasync function copyTemplate(template: string, projectPath: string, useTypeScript: boolean) {\n const templatePath = path.join(__dirname, \"..\", \"templates\", template);\n\n // Copy base template files\n await copyDir(templatePath, projectPath);\n\n // If TypeScript is requested, copy TS-specific files\n if (useTypeScript) {\n const tsTemplatePath = path.join(__dirname, \"..\", \"templates\", \"_typescript\");\n if (await dirExists(tsTemplatePath)) {\n await copyDir(tsTemplatePath, projectPath);\n }\n }\n}\n\nasync function copyDir(src: string, dest: string) {\n await fs.mkdir(dest, { recursive: true });\n\n const entries = await fs.readdir(src, { withFileTypes: true });\n\n for (const entry of entries) {\n const srcPath = path.join(src, entry.name);\n // npm excludes .gitignore files from published tarballs. Keep the template\n // file publishable and restore its dot when generating the application.\n const destinationName = entry.name === \"gitignore\" ? \".gitignore\" : entry.name;\n const destPath = path.join(dest, destinationName);\n\n if (entry.isDirectory()) {\n await copyDir(srcPath, destPath);\n } else {\n await fs.copyFile(srcPath, destPath);\n }\n }\n}\n\nasync function dirExists(path: string): Promise<boolean> {\n try {\n const stat = await fs.stat(path);\n return stat.isDirectory();\n } catch {\n return false;\n }\n}\n\nasync function directoryHasFiles(dirPath: string): Promise<boolean> {\n if (!(await dirExists(dirPath))) {\n return false;\n }\n const files = await fs.readdir(dirPath);\n return files.length > 0;\n}\n\nfunction validateProjectName(value: string): true | string {\n const trimmed = value.trim();\n if (!trimmed) {\n return \"Project name is required\";\n }\n\n // npm package-safe pattern\n if (!/^[a-z0-9._-]+$/i.test(trimmed)) {\n return \"Use letters, numbers, hyphens, underscores, or dots\";\n }\n\n if (trimmed.startsWith(\".\") || trimmed.startsWith(\"_\")) {\n return \"Project name cannot start with '.' or '_'\";\n }\n\n return true;\n}\n\nfunction validateProjectPathArg(value: string): true | string {\n const trimmed = value.trim();\n if (!trimmed) {\n return \"Project name is required\";\n }\n\n const normalized = trimmed.replace(/[\\\\/]+$/, \"\");\n const baseName = path.basename(normalized);\n if (!baseName || baseName === \".\" || baseName === \"..\") {\n return \"Please provide a valid project directory name\";\n }\n\n return validateProjectName(baseName);\n}\n\nfunction prettifyTemplateName(name: string): string {\n return name\n .split(\"-\")\n .map((part) => part.charAt(0).toUpperCase() + part.slice(1))\n .join(\" \");\n}\n\nasync function getAvailableTemplates(): Promise<string[]> {\n const templatesRoot = path.join(__dirname, \"..\", \"templates\");\n const entries = await fs.readdir(templatesRoot, { withFileTypes: true });\n return entries\n .filter((entry) => entry.isDirectory() && !entry.name.startsWith(\"_\"))\n .map((entry) => entry.name)\n .sort();\n}\n\nasync function updatePackageJson(\n projectPath: string,\n projectName: string,\n packageManager: PackageManager,\n) {\n const packageJsonPath = path.join(projectPath, \"package.json\");\n\n try {\n const content = await fs.readFile(packageJsonPath, \"utf-8\");\n const packageJson = JSON.parse(content);\n\n packageJson.name = projectName;\n if (packageManager.version) {\n packageJson.packageManager = `${packageManager.name}@${packageManager.version}`;\n }\n\n await fs.writeFile(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}\\n`);\n } catch {\n logger.warn(\"Could not update package.json\");\n }\n}\n\nexport function detectPackageManager(\n userAgent = process.env.npm_config_user_agent,\n): PackageManager {\n const match = userAgent?.match(/^(npm|pnpm|yarn|bun)\\/([^\\s]+)/);\n if (!match) {\n return { name: \"pnpm\" };\n }\n\n const [, name, version] = match;\n return {\n name: name as PackageManagerName,\n version: version === \"?\" ? undefined : version,\n };\n}\n\nexport function installDependencies(\n projectPath: string,\n packageManager: PackageManager,\n): Promise<void> {\n return new Promise((resolve, reject) => {\n const command =\n process.platform === \"win32\" ? `${packageManager.name}.cmd` : packageManager.name;\n const child = spawn(command, [\"install\"], {\n cwd: projectPath,\n env: process.env,\n stdio: \"inherit\",\n });\n\n child.on(\"error\", reject);\n child.on(\"close\", (code, signal) => {\n if (code === 0) {\n resolve();\n return;\n }\n\n const reason = signal ? `signal ${signal}` : `exit code ${code ?? \"unknown\"}`;\n reject(new Error(`${packageManager.name} install failed with ${reason}.`));\n });\n });\n}\n\nfunction getDevCommand(packageManager: PackageManagerName) {\n if (packageManager === \"npm\" || packageManager === \"bun\") {\n return `${packageManager} run dev`;\n }\n return `${packageManager} dev`;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["import prompts from \"prompts\";\nimport path from \"path\";\nimport fs from \"fs/promises\";\nimport { spawn } from \"node:child_process\";\nimport { logger, showBanner } from \"./utils\";\n\ninterface CreateAppOptions {\n template?: string;\n typescript?: boolean;\n skipInstall?: boolean;\n}\n\nconst templateDetails: Record<\n string,\n {\n title: string;\n description: string;\n instructions: string[];\n }\n> = {\n basic: {\n title: \"FARMJS Basic\",\n description: \"A minimal FARMJS app with built-in Tailwind support\",\n instructions: [\n \"Tailwind is enabled by default. You only need postcss config for custom plugins.\",\n ],\n },\n auth: {\n title: \"FARMJS Auth\",\n description: \"FARMJS-native auth with local SQLite, secure sessions, and protected routes\",\n instructions: [\n \"FARMJS Auth uses local SQLite automatically; no auth environment variables are needed locally.\",\n \"For production, set FARM_AUTH_URL, FARM_AUTH_SECRET, and DATABASE_URL, then run the auth:migrate script.\",\n ],\n },\n \"better-auth\": {\n title: \"FARMJS Better Auth\",\n description: \"Better Auth with Postgres, secure sessions, and protected routes\",\n instructions: [\n \"Before starting, copy .env.example to .env.local and set DATABASE_URL and BETTER_AUTH_SECRET.\",\n \"Better Auth migrations run automatically when the auth instance starts.\",\n ],\n },\n};\n\nexport type PackageManagerName = \"npm\" | \"pnpm\" | \"yarn\" | \"bun\";\n\nexport interface PackageManager {\n name: PackageManagerName;\n version?: string;\n}\n\nexport async function createApp(projectName?: string, options: CreateAppOptions = {}) {\n showBanner();\n\n const templates = await getAvailableTemplates();\n if (templates.length === 0) {\n logger.error(\"No templates are available in this package.\");\n process.exit(1);\n }\n\n // Get project name if not provided\n if (!projectName) {\n const response = await prompts({\n type: \"text\",\n name: \"projectName\",\n message: \"What is your project named?\",\n initial: \"my-farm-app\",\n validate: validateProjectName,\n });\n\n if (!response.projectName) {\n logger.error(\"Operation cancelled.\");\n process.exit(1);\n }\n\n projectName = response.projectName;\n } else {\n const validation = validateProjectPathArg(projectName);\n if (validation !== true) {\n logger.error(validation);\n process.exit(1);\n }\n }\n\n // Get template if not provided\n let template = options.template;\n if (!template) {\n const response = await prompts({\n type: \"select\",\n name: \"template\",\n message: \"Which template would you like to use?\",\n choices: templates.map((name) => {\n const details = templateDetails[name];\n return {\n title: details?.title ?? prettifyTemplateName(name),\n value: name,\n description: details?.description,\n };\n }),\n initial: 0,\n });\n\n if (!response.template) {\n logger.error(\"Operation cancelled.\");\n process.exit(1);\n }\n template = response.template;\n } else if (!templates.includes(template)) {\n logger.error(\n `Unknown template \"${template}\". Available: ${templates.map((t) => `\"${t}\"`).join(\", \")}`,\n );\n process.exit(1);\n }\n\n // Check TypeScript preference\n let useTypeScript = options.typescript;\n if (useTypeScript === undefined) {\n const response = await prompts({\n type: \"confirm\",\n name: \"typescript\",\n message: \"Would you like to use TypeScript?\",\n initial: true,\n });\n\n if (response.typescript === undefined) {\n logger.error(\"Operation cancelled.\");\n process.exit(1);\n }\n useTypeScript = response.typescript;\n }\n\n const projectPath = path.resolve(process.cwd(), projectName!);\n const packageManager = detectPackageManager();\n const hasExistingFiles = await directoryHasFiles(projectPath);\n if (hasExistingFiles) {\n const overwriteResponse = await prompts({\n type: \"confirm\",\n name: \"overwrite\",\n message: `Directory \"${projectName}\" is not empty. Continue and overwrite conflicting files?`,\n initial: false,\n });\n\n if (!overwriteResponse.overwrite) {\n logger.error(\"Operation cancelled.\");\n process.exit(1);\n }\n }\n\n logger.info(`Creating FARMJS app in ${projectPath}`);\n\n await fs.mkdir(projectPath, { recursive: true });\n\n await copyTemplate(template!, projectPath, useTypeScript!);\n\n await updatePackageJson(projectPath, projectName!, packageManager);\n\n logger.success(`🚜 Created ${projectName}`);\n\n if (!options.skipInstall) {\n logger.info(`Installing dependencies with ${packageManager.name}...`);\n await installDependencies(projectPath, packageManager);\n logger.success(\"Dependencies installed\");\n }\n\n logger.info(\"\");\n logger.info(\"Next steps\");\n logger.info(` cd ${projectName}`);\n if (options.skipInstall) {\n logger.info(` ${packageManager.name} install`);\n }\n logger.info(` ${getDevCommand(packageManager.name)}`);\n logger.info(\"\");\n for (const instruction of templateDetails[template!]?.instructions ?? []) {\n logger.info(instruction);\n }\n}\n\nasync function copyTemplate(template: string, projectPath: string, useTypeScript: boolean) {\n const templatePath = path.join(__dirname, \"..\", \"templates\", template);\n\n // Copy base template files\n await copyDir(templatePath, projectPath);\n\n // If TypeScript is requested, copy TS-specific files\n if (useTypeScript) {\n const tsTemplatePath = path.join(__dirname, \"..\", \"templates\", \"_typescript\");\n if (await dirExists(tsTemplatePath)) {\n await copyDir(tsTemplatePath, projectPath);\n }\n }\n}\n\nasync function copyDir(src: string, dest: string) {\n await fs.mkdir(dest, { recursive: true });\n\n const entries = await fs.readdir(src, { withFileTypes: true });\n\n for (const entry of entries) {\n const srcPath = path.join(src, entry.name);\n // npm excludes .gitignore files from published tarballs. Keep the template\n // file publishable and restore its dot when generating the application.\n const destinationName = entry.name === \"gitignore\" ? \".gitignore\" : entry.name;\n const destPath = path.join(dest, destinationName);\n\n if (entry.isDirectory()) {\n await copyDir(srcPath, destPath);\n } else {\n await fs.copyFile(srcPath, destPath);\n }\n }\n}\n\nasync function dirExists(path: string): Promise<boolean> {\n try {\n const stat = await fs.stat(path);\n return stat.isDirectory();\n } catch {\n return false;\n }\n}\n\nasync function directoryHasFiles(dirPath: string): Promise<boolean> {\n if (!(await dirExists(dirPath))) {\n return false;\n }\n const files = await fs.readdir(dirPath);\n return files.length > 0;\n}\n\nfunction validateProjectName(value: string): true | string {\n const trimmed = value.trim();\n if (!trimmed) {\n return \"Project name is required\";\n }\n\n // npm package-safe pattern\n if (!/^[a-z0-9._-]+$/i.test(trimmed)) {\n return \"Use letters, numbers, hyphens, underscores, or dots\";\n }\n\n if (trimmed.startsWith(\".\") || trimmed.startsWith(\"_\")) {\n return \"Project name cannot start with '.' or '_'\";\n }\n\n return true;\n}\n\nfunction validateProjectPathArg(value: string): true | string {\n const trimmed = value.trim();\n if (!trimmed) {\n return \"Project name is required\";\n }\n\n const normalized = trimmed.replace(/[\\\\/]+$/, \"\");\n const baseName = path.basename(normalized);\n if (!baseName || baseName === \".\" || baseName === \"..\") {\n return \"Please provide a valid project directory name\";\n }\n\n return validateProjectName(baseName);\n}\n\nfunction prettifyTemplateName(name: string): string {\n return name\n .split(\"-\")\n .map((part) => part.charAt(0).toUpperCase() + part.slice(1))\n .join(\" \");\n}\n\nasync function getAvailableTemplates(): Promise<string[]> {\n const templatesRoot = path.join(__dirname, \"..\", \"templates\");\n const entries = await fs.readdir(templatesRoot, { withFileTypes: true });\n const templateOrder = Object.keys(templateDetails);\n return entries\n .filter((entry) => entry.isDirectory() && !entry.name.startsWith(\"_\"))\n .map((entry) => entry.name)\n .sort((left, right) => {\n const leftIndex = templateOrder.indexOf(left);\n const rightIndex = templateOrder.indexOf(right);\n const normalizedLeft = leftIndex === -1 ? Number.MAX_SAFE_INTEGER : leftIndex;\n const normalizedRight = rightIndex === -1 ? Number.MAX_SAFE_INTEGER : rightIndex;\n return normalizedLeft - normalizedRight || left.localeCompare(right);\n });\n}\n\nasync function updatePackageJson(\n projectPath: string,\n projectName: string,\n packageManager: PackageManager,\n) {\n const packageJsonPath = path.join(projectPath, \"package.json\");\n\n try {\n const content = await fs.readFile(packageJsonPath, \"utf-8\");\n const packageJson = JSON.parse(content);\n\n packageJson.name = projectName;\n if (packageManager.version) {\n packageJson.packageManager = `${packageManager.name}@${packageManager.version}`;\n }\n\n await fs.writeFile(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}\\n`);\n } catch {\n logger.warn(\"Could not update package.json\");\n }\n}\n\nexport function detectPackageManager(\n userAgent = process.env.npm_config_user_agent,\n): PackageManager {\n const match = userAgent?.match(/^(npm|pnpm|yarn|bun)\\/([^\\s]+)/);\n if (!match) {\n return { name: \"pnpm\" };\n }\n\n const [, name, version] = match;\n return {\n name: name as PackageManagerName,\n version: version === \"?\" ? undefined : version,\n };\n}\n\nexport function installDependencies(\n projectPath: string,\n packageManager: PackageManager,\n): Promise<void> {\n return new Promise((resolve, reject) => {\n const command =\n process.platform === \"win32\" ? `${packageManager.name}.cmd` : packageManager.name;\n const child = spawn(command, [\"install\"], {\n cwd: projectPath,\n env: process.env,\n stdio: \"inherit\",\n });\n\n child.on(\"error\", reject);\n child.on(\"close\", (code, signal) => {\n if (code === 0) {\n resolve();\n return;\n }\n\n const reason = signal ? `signal ${signal}` : `exit code ${code ?? \"unknown\"}`;\n reject(new Error(`${packageManager.name} install failed with ${reason}.`));\n });\n });\n}\n\nfunction getDevCommand(packageManager: PackageManagerName) {\n if (packageManager === \"npm\" || packageManager === \"bun\") {\n return `${packageManager} run dev`;\n }\n return `${packageManager} dev`;\n}\n"],"mappings":"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"}
|
package/dist/utils.js
CHANGED
|
@@ -11,15 +11,17 @@ const logger = {
|
|
|
11
11
|
};
|
|
12
12
|
function showBanner() {
|
|
13
13
|
const art = [
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
"
|
|
14
|
+
"░██████████ ░███ ░█████████ ░███ ░███ ░█████ ░██████",
|
|
15
|
+
"░██ ░██░██ ░██ ░██ ░████ ░████ ░██ ░██ ░██",
|
|
16
|
+
"░██ ░██ ░██ ░██ ░██ ░██░██ ░██░██ ░██ ░██",
|
|
17
|
+
"░█████████ ░█████████ ░█████████ ░██ ░████ ░██ ░██ ░████████",
|
|
18
|
+
"░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██",
|
|
19
|
+
"░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██",
|
|
20
|
+
"░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██████ ░██████"
|
|
19
21
|
];
|
|
20
22
|
console.log("");
|
|
21
23
|
for (const line of art) console.log(picocolors.default.cyan(line));
|
|
22
|
-
console.log(picocolors.default.bold(picocolors.default.green("Create
|
|
24
|
+
console.log(picocolors.default.bold(picocolors.default.green("Create FARMJS App")) + picocolors.default.dim(" modern React meta-framework"));
|
|
23
25
|
console.log("");
|
|
24
26
|
}
|
|
25
27
|
//#endregion
|
package/dist/utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","names":["pc"],"sources":["../src/utils.ts"],"sourcesContent":["import pc from \"picocolors\";\n\nexport const logger = {\n info: (message: string) => console.log(`${pc.blue(\"ℹ\")} ${message}`),\n success: (message: string) => console.log(`${pc.green(\"✓\")} ${message}`),\n warn: (message: string) => console.warn(`${pc.yellow(\"⚠\")} ${message}`),\n error: (message: string) => console.error(`${pc.red(\"✗\")} ${message}`),\n};\n\nexport function showBanner() {\n const art = [\n \"
|
|
1
|
+
{"version":3,"file":"utils.js","names":["pc"],"sources":["../src/utils.ts"],"sourcesContent":["import pc from \"picocolors\";\n\nexport const logger = {\n info: (message: string) => console.log(`${pc.blue(\"ℹ\")} ${message}`),\n success: (message: string) => console.log(`${pc.green(\"✓\")} ${message}`),\n warn: (message: string) => console.warn(`${pc.yellow(\"⚠\")} ${message}`),\n error: (message: string) => console.error(`${pc.red(\"✗\")} ${message}`),\n};\n\nexport function showBanner() {\n const art = [\n \"░██████████ ░███ ░█████████ ░███ ░███ ░█████ ░██████\",\n \"░██ ░██░██ ░██ ░██ ░████ ░████ ░██ ░██ ░██\",\n \"░██ ░██ ░██ ░██ ░██ ░██░██ ░██░██ ░██ ░██\",\n \"░█████████ ░█████████ ░█████████ ░██ ░████ ░██ ░██ ░████████\",\n \"░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██\",\n \"░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██\",\n \"░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██████ ░██████\",\n ];\n\n console.log(\"\");\n for (const line of art) {\n console.log(pc.cyan(line));\n }\n console.log(pc.bold(pc.green(\"Create FARMJS App\")) + pc.dim(\" modern React meta-framework\"));\n console.log(\"\");\n}\n"],"mappings":";;;;;AAEA,MAAa,SAAS;CACpB,OAAO,YAAoB,QAAQ,IAAI,GAAGA,WAAAA,QAAG,KAAK,GAAG,EAAE,GAAG,SAAS;CACnE,UAAU,YAAoB,QAAQ,IAAI,GAAGA,WAAAA,QAAG,MAAM,GAAG,EAAE,GAAG,SAAS;CACvE,OAAO,YAAoB,QAAQ,KAAK,GAAGA,WAAAA,QAAG,OAAO,GAAG,EAAE,GAAG,SAAS;CACtE,QAAQ,YAAoB,QAAQ,MAAM,GAAGA,WAAAA,QAAG,IAAI,GAAG,EAAE,GAAG,SAAS;AACvE;AAEA,SAAgB,aAAa;CAC3B,MAAM,MAAM;EACV;EACA;EACA;EACA;EACA;EACA;EACA;CACF;CAEA,QAAQ,IAAI,EAAE;CACd,KAAK,MAAM,QAAQ,KACjB,QAAQ,IAAIA,WAAAA,QAAG,KAAK,IAAI,CAAC;CAE3B,QAAQ,IAAIA,WAAAA,QAAG,KAAKA,WAAAA,QAAG,MAAM,mBAAmB,CAAC,IAAIA,WAAAA,QAAG,IAAI,+BAA+B,CAAC;CAC5F,QAAQ,IAAI,EAAE;AAChB"}
|
package/dist/utils.mjs
CHANGED
|
@@ -8,15 +8,17 @@ const logger = {
|
|
|
8
8
|
};
|
|
9
9
|
function showBanner() {
|
|
10
10
|
const art = [
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"
|
|
11
|
+
"░██████████ ░███ ░█████████ ░███ ░███ ░█████ ░██████",
|
|
12
|
+
"░██ ░██░██ ░██ ░██ ░████ ░████ ░██ ░██ ░██",
|
|
13
|
+
"░██ ░██ ░██ ░██ ░██ ░██░██ ░██░██ ░██ ░██",
|
|
14
|
+
"░█████████ ░█████████ ░█████████ ░██ ░████ ░██ ░██ ░████████",
|
|
15
|
+
"░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██",
|
|
16
|
+
"░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██",
|
|
17
|
+
"░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██████ ░██████"
|
|
16
18
|
];
|
|
17
19
|
console.log("");
|
|
18
20
|
for (const line of art) console.log(pc.cyan(line));
|
|
19
|
-
console.log(pc.bold(pc.green("Create
|
|
21
|
+
console.log(pc.bold(pc.green("Create FARMJS App")) + pc.dim(" modern React meta-framework"));
|
|
20
22
|
console.log("");
|
|
21
23
|
}
|
|
22
24
|
//#endregion
|
package/dist/utils.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.mjs","names":[],"sources":["../src/utils.ts"],"sourcesContent":["import pc from \"picocolors\";\n\nexport const logger = {\n info: (message: string) => console.log(`${pc.blue(\"ℹ\")} ${message}`),\n success: (message: string) => console.log(`${pc.green(\"✓\")} ${message}`),\n warn: (message: string) => console.warn(`${pc.yellow(\"⚠\")} ${message}`),\n error: (message: string) => console.error(`${pc.red(\"✗\")} ${message}`),\n};\n\nexport function showBanner() {\n const art = [\n \"
|
|
1
|
+
{"version":3,"file":"utils.mjs","names":[],"sources":["../src/utils.ts"],"sourcesContent":["import pc from \"picocolors\";\n\nexport const logger = {\n info: (message: string) => console.log(`${pc.blue(\"ℹ\")} ${message}`),\n success: (message: string) => console.log(`${pc.green(\"✓\")} ${message}`),\n warn: (message: string) => console.warn(`${pc.yellow(\"⚠\")} ${message}`),\n error: (message: string) => console.error(`${pc.red(\"✗\")} ${message}`),\n};\n\nexport function showBanner() {\n const art = [\n \"░██████████ ░███ ░█████████ ░███ ░███ ░█████ ░██████\",\n \"░██ ░██░██ ░██ ░██ ░████ ░████ ░██ ░██ ░██\",\n \"░██ ░██ ░██ ░██ ░██ ░██░██ ░██░██ ░██ ░██\",\n \"░█████████ ░█████████ ░█████████ ░██ ░████ ░██ ░██ ░████████\",\n \"░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██\",\n \"░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██\",\n \"░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██ ░██████ ░██████\",\n ];\n\n console.log(\"\");\n for (const line of art) {\n console.log(pc.cyan(line));\n }\n console.log(pc.bold(pc.green(\"Create FARMJS App\")) + pc.dim(\" modern React meta-framework\"));\n console.log(\"\");\n}\n"],"mappings":";;AAEA,MAAa,SAAS;CACpB,OAAO,YAAoB,QAAQ,IAAI,GAAG,GAAG,KAAK,GAAG,EAAE,GAAG,SAAS;CACnE,UAAU,YAAoB,QAAQ,IAAI,GAAG,GAAG,MAAM,GAAG,EAAE,GAAG,SAAS;CACvE,OAAO,YAAoB,QAAQ,KAAK,GAAG,GAAG,OAAO,GAAG,EAAE,GAAG,SAAS;CACtE,QAAQ,YAAoB,QAAQ,MAAM,GAAG,GAAG,IAAI,GAAG,EAAE,GAAG,SAAS;AACvE;AAEA,SAAgB,aAAa;CAC3B,MAAM,MAAM;EACV;EACA;EACA;EACA;EACA;EACA;EACA;CACF;CAEA,QAAQ,IAAI,EAAE;CACd,KAAK,MAAM,QAAQ,KACjB,QAAQ,IAAI,GAAG,KAAK,IAAI,CAAC;CAE3B,QAAQ,IAAI,GAAG,KAAK,GAAG,MAAM,mBAAmB,CAAC,IAAI,GAAG,IAAI,+BAA+B,CAAC;CAC5F,QAAQ,IAAI,EAAE;AAChB"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# FARMJS Auth needs no environment variables in local development.
|
|
2
|
+
# Configure these values in the production deployment environment.
|
|
3
|
+
|
|
4
|
+
# FARM_AUTH_URL=https://your-app.example.com
|
|
5
|
+
# FARM_AUTH_SECRET=replace-with-a-random-secret-at-least-32-characters
|
|
6
|
+
# DATABASE_URL=postgresql://user:password@your-postgres-host/database?sslmode=require
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# FARMJS Auth Starter
|
|
2
|
+
|
|
3
|
+
A standalone email/password authentication starter using FARMJS built-in authentication and
|
|
4
|
+
React.
|
|
5
|
+
|
|
6
|
+
This project was generated from the FARMJS Auth template included with `@farm.js/create-app`.
|
|
7
|
+
|
|
8
|
+
## Included
|
|
9
|
+
|
|
10
|
+
- email/password sign-up and sign-in enabled by `auth: true`
|
|
11
|
+
- database-backed, HTTP-only session cookies
|
|
12
|
+
- a server-middleware-protected `/dashboard`
|
|
13
|
+
- `@farm.js/auth/server` request helpers and the `@farm.js/auth/client` React hook
|
|
14
|
+
- automatic local SQLite storage in `.farm/auth.sqlite`
|
|
15
|
+
- production Postgres support through `DATABASE_URL`
|
|
16
|
+
- a dark-first, one-page welcome screen set in Geist Sans and Geist Mono
|
|
17
|
+
- pending, error, unauthorized, loading, and not-found states
|
|
18
|
+
- exact FARMJS beta dependencies for reproducible installs
|
|
19
|
+
|
|
20
|
+
## Quick start
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pnpm create @farm.js/app@beta my-app --template auth --typescript
|
|
24
|
+
cd my-app
|
|
25
|
+
pnpm dev
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Open [http://localhost:3000](http://localhost:3000), create an account, and continue to the
|
|
29
|
+
protected dashboard. Local development needs no database or auth environment variables; FARMJS Auth
|
|
30
|
+
creates and migrates `.farm/auth.sqlite` when auth is first used.
|
|
31
|
+
|
|
32
|
+
## How it is wired
|
|
33
|
+
|
|
34
|
+
- [`farm.config.ts`](./farm.config.ts) enables email/password authentication with `auth: true`.
|
|
35
|
+
- [`src/app/page.tsx`](./src/app/page.tsx) reads the current session with
|
|
36
|
+
`auth.session()` from `@farm.js/auth/server`.
|
|
37
|
+
- [`src/components/auth-form.tsx`](./src/components/auth-form.tsx) uses the built-in `signIn` and
|
|
38
|
+
`signUp` client functions.
|
|
39
|
+
- [`src/app/dashboard/middleware.ts`](./src/app/dashboard/middleware.ts) redirects anonymous
|
|
40
|
+
requests before the dashboard route runs.
|
|
41
|
+
- [`src/app/dashboard/page.tsx`](./src/app/dashboard/page.tsx) uses the built-in `useAuth` React
|
|
42
|
+
hook to render the current account and session.
|
|
43
|
+
|
|
44
|
+
FARMJS owns the auth action routes, so the starter does not need an app-local Better Auth instance,
|
|
45
|
+
client factory, database pool, or catch-all API route:
|
|
46
|
+
|
|
47
|
+
```text
|
|
48
|
+
/api/auth/*
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Production environment
|
|
52
|
+
|
|
53
|
+
Local development uses safe defaults. Production requires these deployment environment variables:
|
|
54
|
+
|
|
55
|
+
| Variable | Purpose |
|
|
56
|
+
| ------------------ | ----------------------------------------------------------- |
|
|
57
|
+
| `FARM_AUTH_URL` | Public application origin; inferred automatically on Vercel |
|
|
58
|
+
| `FARM_AUTH_SECRET` | Random secret used to sign and encrypt auth data |
|
|
59
|
+
| `DATABASE_URL` | Postgres connection string |
|
|
60
|
+
|
|
61
|
+
Generate a production secret with:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
openssl rand -base64 32
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Loading `farm.config.ts` and running `farm build` do not connect to the database. Apply the
|
|
68
|
+
production auth schema before serving traffic:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
pnpm auth:migrate
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
The FARMJS deployment target is configured in [`farm.config.ts`](./farm.config.ts) for Vercel.
|
|
75
|
+
|
|
76
|
+
## Advanced authentication
|
|
77
|
+
|
|
78
|
+
`auth: true` intentionally owns the common email/password path. If an application needs Better Auth
|
|
79
|
+
plugins, adapters, social providers, or callbacks, replace the top-level auth option with an
|
|
80
|
+
app-owned `integrations.auth` configuration using `@farm.js/better-auth`.
|
|
81
|
+
|
|
82
|
+
## Commands
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
pnpm dev # start the development server
|
|
86
|
+
pnpm type-check # run TypeScript checks
|
|
87
|
+
pnpm build # create the production build without connecting to the database
|
|
88
|
+
pnpm check # type-check and build
|
|
89
|
+
pnpm auth:migrate # apply the production auth schema
|
|
90
|
+
pnpm run deploy -- --prod
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## License
|
|
94
|
+
|
|
95
|
+
MIT
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "farmjs-auth-starter",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "farm dev",
|
|
7
|
+
"build": "farm build",
|
|
8
|
+
"auth:migrate": "farm auth migrate",
|
|
9
|
+
"check": "pnpm type-check && pnpm build",
|
|
10
|
+
"deploy": "farm deploy",
|
|
11
|
+
"start": "farm start",
|
|
12
|
+
"type-check": "tsc --noEmit"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@farm.js/auth": "0.1.0-beta.22",
|
|
16
|
+
"@farm.js/core": "0.1.0-beta.22",
|
|
17
|
+
"@fontsource-variable/geist": "5.3.0",
|
|
18
|
+
"@fontsource-variable/geist-mono": "5.3.0",
|
|
19
|
+
"better-call": "1.3.2",
|
|
20
|
+
"react": "^19.0.0",
|
|
21
|
+
"react-dom": "^19.0.0"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@farm.js/cli": "0.1.0-beta.22",
|
|
25
|
+
"@types/node": "20.19.9",
|
|
26
|
+
"@types/react": "^19.0.0",
|
|
27
|
+
"@types/react-dom": "^19.0.0",
|
|
28
|
+
"tailwindcss": "^4.0.0",
|
|
29
|
+
"typescript": "^5.3.3"
|
|
30
|
+
},
|
|
31
|
+
"packageManager": "pnpm@11.18.0"
|
|
32
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { auth } from "@farm.js/auth/server";
|
|
2
|
+
|
|
3
|
+
export const config = {
|
|
4
|
+
runtime: "nodejs" as const,
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export async function middleware(request: Request) {
|
|
8
|
+
const user = await auth.user({ request });
|
|
9
|
+
|
|
10
|
+
if (!user) {
|
|
11
|
+
return Response.redirect(new URL("/sign-in", request.url), 307);
|
|
12
|
+
}
|
|
13
|
+
}
|