howone 0.0.3
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/bin/index.d.mts +1 -0
- package/bin/index.mjs +294 -0
- package/package.json +44 -0
- package/templates/nextjs/AGENTS.md +5 -0
- package/templates/nextjs/CLAUDE.md +1 -0
- package/templates/nextjs/README.md +36 -0
- package/templates/nextjs/app/favicon.ico +0 -0
- package/templates/nextjs/app/globals.css +49 -0
- package/templates/nextjs/app/layout.tsx +30 -0
- package/templates/nextjs/app/page.module.css +142 -0
- package/templates/nextjs/app/page.tsx +66 -0
- package/templates/nextjs/bun.lock +813 -0
- package/templates/nextjs/eslint.config.mjs +18 -0
- package/templates/nextjs/gitignore +41 -0
- package/templates/nextjs/next-env.d.ts +6 -0
- package/templates/nextjs/next.config.ts +7 -0
- package/templates/nextjs/package.json +32 -0
- package/templates/nextjs/public/file.svg +1 -0
- package/templates/nextjs/public/globe.svg +1 -0
- package/templates/nextjs/public/next.svg +1 -0
- package/templates/nextjs/public/vercel.svg +1 -0
- package/templates/nextjs/public/window.svg +1 -0
- package/templates/nextjs/tsconfig.json +34 -0
- package/templates/vite/README.md +73 -0
- package/templates/vite/eslint.config.js +22 -0
- package/templates/vite/gitignore +24 -0
- package/templates/vite/index.html +13 -0
- package/templates/vite/package.json +30 -0
- package/templates/vite/public/favicon.svg +1 -0
- package/templates/vite/public/icons.svg +24 -0
- package/templates/vite/src/App.css +184 -0
- package/templates/vite/src/App.tsx +122 -0
- package/templates/vite/src/assets/hero.png +0 -0
- package/templates/vite/src/assets/react.svg +1 -0
- package/templates/vite/src/assets/vite.svg +1 -0
- package/templates/vite/src/index.css +111 -0
- package/templates/vite/src/main.tsx +10 -0
- package/templates/vite/tsconfig.app.json +25 -0
- package/templates/vite/tsconfig.json +7 -0
- package/templates/vite/tsconfig.node.json +24 -0
- package/templates/vite/vite.config.ts +7 -0
package/bin/index.d.mts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
package/bin/index.mjs
ADDED
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import { cancel, confirm, intro, isCancel, outro, select, spinner, text } from "@clack/prompts";
|
|
4
|
+
import { cac } from "cac";
|
|
5
|
+
import { readdir, rename, stat } from "node:fs/promises";
|
|
6
|
+
import { copy, emptyDir, ensureDir, pathExists } from "fs-extra/esm";
|
|
7
|
+
import pc from "picocolors";
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
import { fileURLToPath } from "node:url";
|
|
10
|
+
import validatePackageName from "validate-npm-package-name";
|
|
11
|
+
//#region src/index.ts
|
|
12
|
+
const packageJson = createRequire(import.meta.url)("../package.json");
|
|
13
|
+
const DEFAULT_PROJECT_NAME = "howone-app";
|
|
14
|
+
const TEMPLATES = ["vite", "nextjs"];
|
|
15
|
+
const TEMPLATE_ALIASES = {
|
|
16
|
+
vite: "vite",
|
|
17
|
+
next: "nextjs",
|
|
18
|
+
nextjs: "nextjs"
|
|
19
|
+
};
|
|
20
|
+
var CliError = class extends Error {
|
|
21
|
+
constructor(code, message, details) {
|
|
22
|
+
super(message);
|
|
23
|
+
this.code = code;
|
|
24
|
+
this.details = details;
|
|
25
|
+
this.name = "CliError";
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
const cli = cac("howone");
|
|
29
|
+
cli.version(packageJson.version, "-v, --version");
|
|
30
|
+
cli.help();
|
|
31
|
+
cli.command("create [projectName] [legacyProjectName]", "Create a new project from a HowOne template.").alias("new").alias("init").option("-t, --template <template>", "Template to use: vite, nextjs").option("--vite", "Shortcut for --template vite").option("--nextjs", "Shortcut for --template nextjs").option("--next", "Alias for --nextjs").option("-f, --force", "Overwrite an existing non-empty target directory").option("-y, --yes", "Disable prompts and accept defaults where possible").option("--json", "Print machine-readable JSON output").option("--cwd <dir>", "Directory in which to create the project").example("howone vite my-app").example("howone next my-app").example("howone create my-app --template vite").example("howone create my-app --template nextjs --yes").example("howone create my-app --template vite --json --yes").example("howone create template --vite my-app").action((projectName, legacyProjectName, options) => {
|
|
32
|
+
runCreate(projectName, legacyProjectName, options).catch((error) => handleFatalError(error, Boolean(options.json)));
|
|
33
|
+
});
|
|
34
|
+
try {
|
|
35
|
+
cli.parse(normalizeCliArgv(process.argv), { run: true });
|
|
36
|
+
} catch (error) {
|
|
37
|
+
handleFatalError(error, process.argv.includes("--json"));
|
|
38
|
+
}
|
|
39
|
+
function normalizeCliArgv(argv) {
|
|
40
|
+
if (argv.length < 3) return argv;
|
|
41
|
+
const runtime = argv[0];
|
|
42
|
+
const script = argv[1];
|
|
43
|
+
const userArgs = argv.slice(2);
|
|
44
|
+
const first = userArgs[0];
|
|
45
|
+
if (!first || first === "create" || first === "new" || first === "init" || first === "--help" || first === "-h") return normalizeCreateSubcommandArgv(argv);
|
|
46
|
+
const template = resolveTemplateAlias(first);
|
|
47
|
+
if (template) return [
|
|
48
|
+
runtime,
|
|
49
|
+
script,
|
|
50
|
+
"create",
|
|
51
|
+
...userArgs.slice(1),
|
|
52
|
+
"--template",
|
|
53
|
+
template
|
|
54
|
+
];
|
|
55
|
+
if (!first.startsWith("-") && hasTemplateFlag(userArgs)) return [
|
|
56
|
+
runtime,
|
|
57
|
+
script,
|
|
58
|
+
"create",
|
|
59
|
+
...userArgs
|
|
60
|
+
];
|
|
61
|
+
return argv;
|
|
62
|
+
}
|
|
63
|
+
function normalizeCreateSubcommandArgv(argv) {
|
|
64
|
+
const runtime = argv[0];
|
|
65
|
+
const script = argv[1];
|
|
66
|
+
const command = argv[2];
|
|
67
|
+
const rest = argv.slice(3);
|
|
68
|
+
if (!command || command !== "create" && command !== "new" && command !== "init") return argv;
|
|
69
|
+
const first = rest[0];
|
|
70
|
+
const template = resolveTemplateAlias(first);
|
|
71
|
+
if (!template) return argv;
|
|
72
|
+
return [
|
|
73
|
+
runtime,
|
|
74
|
+
script,
|
|
75
|
+
command,
|
|
76
|
+
...rest.slice(1),
|
|
77
|
+
"--template",
|
|
78
|
+
template
|
|
79
|
+
];
|
|
80
|
+
}
|
|
81
|
+
function resolveTemplateAlias(value) {
|
|
82
|
+
if (!value) return void 0;
|
|
83
|
+
const normalized = TEMPLATE_ALIASES[value];
|
|
84
|
+
return normalized && isTemplate(normalized) ? normalized : void 0;
|
|
85
|
+
}
|
|
86
|
+
function hasTemplateFlag(args) {
|
|
87
|
+
return args.some((arg) => arg === "--vite" || arg === "--next" || arg === "--nextjs" || arg === "--template");
|
|
88
|
+
}
|
|
89
|
+
async function runCreate(projectNameArg, legacyProjectNameArg, options) {
|
|
90
|
+
const plan = await resolveCreatePlan(projectNameArg, legacyProjectNameArg, options, path.resolve(options.cwd ?? process.cwd()), process.stdout.isTTY && !options.yes && !options.json);
|
|
91
|
+
if (!options.json) intro(pc.bold("HowOne"));
|
|
92
|
+
await createTemplate(plan, Boolean(options.json));
|
|
93
|
+
const result = {
|
|
94
|
+
ok: true,
|
|
95
|
+
command: "create",
|
|
96
|
+
template: plan.template,
|
|
97
|
+
projectName: plan.projectName,
|
|
98
|
+
targetDir: plan.targetDir,
|
|
99
|
+
packageManager: "bun",
|
|
100
|
+
nextSteps: [
|
|
101
|
+
`cd ${formatPathForShell(path.relative(plan.cwd, plan.targetDir) || ".")}`,
|
|
102
|
+
"bun install",
|
|
103
|
+
"bun run dev"
|
|
104
|
+
]
|
|
105
|
+
};
|
|
106
|
+
if (options.json) {
|
|
107
|
+
console.log(JSON.stringify(result, null, 2));
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
outro([
|
|
111
|
+
`${pc.green("Created")} ${pc.bold(plan.template)} template in ${pc.cyan(path.relative(plan.cwd, plan.targetDir) || ".")}`,
|
|
112
|
+
"",
|
|
113
|
+
pc.bold("Next steps:"),
|
|
114
|
+
...result.nextSteps.map((step) => ` ${step}`)
|
|
115
|
+
].join("\n"));
|
|
116
|
+
}
|
|
117
|
+
async function resolveCreatePlan(projectNameArg, legacyProjectNameArg, options, cwd, isInteractive) {
|
|
118
|
+
const template = resolveTemplateOption(options);
|
|
119
|
+
const projectName = projectNameArg === "template" ? legacyProjectNameArg : projectNameArg;
|
|
120
|
+
const resolvedTemplate = template ?? (isInteractive ? await promptTemplate() : void 0);
|
|
121
|
+
if (!resolvedTemplate) throw new CliError("E_TEMPLATE_REQUIRED", "Missing template. Use --template vite or --template nextjs.");
|
|
122
|
+
const resolvedProjectName = projectName ?? (isInteractive ? await promptProjectName() : DEFAULT_PROJECT_NAME);
|
|
123
|
+
validateProjectName(resolvedProjectName);
|
|
124
|
+
const targetDir = path.resolve(cwd, resolvedProjectName);
|
|
125
|
+
const force = Boolean(options.force);
|
|
126
|
+
if (!force && await isNonEmptyDirectory(targetDir)) {
|
|
127
|
+
const entries = await readdir(targetDir);
|
|
128
|
+
if (!isInteractive) throw new CliError("E_TARGET_NOT_EMPTY", `Target directory already exists and is not empty: ${targetDir}`, {
|
|
129
|
+
targetDir,
|
|
130
|
+
entries: entries.slice(0, 20),
|
|
131
|
+
retryWithForce: [
|
|
132
|
+
"howone",
|
|
133
|
+
"create",
|
|
134
|
+
resolvedProjectName,
|
|
135
|
+
"--template",
|
|
136
|
+
resolvedTemplate,
|
|
137
|
+
"--cwd",
|
|
138
|
+
cwd,
|
|
139
|
+
"--force",
|
|
140
|
+
"--yes",
|
|
141
|
+
...options.json ? ["--json"] : []
|
|
142
|
+
]
|
|
143
|
+
});
|
|
144
|
+
const shouldOverwrite = await confirm({
|
|
145
|
+
message: `Directory "${path.relative(cwd, targetDir) || "."}" is not empty. Overwrite it?`,
|
|
146
|
+
initialValue: false
|
|
147
|
+
});
|
|
148
|
+
if (isCancel(shouldOverwrite) || !shouldOverwrite) {
|
|
149
|
+
cancel("Cancelled.");
|
|
150
|
+
process.exit(0);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
await assertTargetWritable(targetDir);
|
|
154
|
+
return {
|
|
155
|
+
template: resolvedTemplate,
|
|
156
|
+
projectName: resolvedProjectName,
|
|
157
|
+
targetDir,
|
|
158
|
+
force,
|
|
159
|
+
cwd
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
function resolveTemplateOption(options) {
|
|
163
|
+
const flags = [
|
|
164
|
+
options.template,
|
|
165
|
+
options.vite ? "vite" : void 0,
|
|
166
|
+
options.nextjs || options.next ? "nextjs" : void 0
|
|
167
|
+
].filter(Boolean);
|
|
168
|
+
if (flags.length > 1) throw new CliError("E_TEMPLATE_CONFLICT", "Use only one template option.");
|
|
169
|
+
const template = flags[0];
|
|
170
|
+
if (!template) return void 0;
|
|
171
|
+
if (!isTemplate(template)) throw new CliError("E_TEMPLATE_INVALID", `Unsupported template "${template}". Use vite or nextjs.`);
|
|
172
|
+
return template;
|
|
173
|
+
}
|
|
174
|
+
async function promptTemplate() {
|
|
175
|
+
const value = await select({
|
|
176
|
+
message: "Choose a template",
|
|
177
|
+
options: [{
|
|
178
|
+
value: "vite",
|
|
179
|
+
label: "Vite React TypeScript",
|
|
180
|
+
hint: "Fast SPA starter"
|
|
181
|
+
}, {
|
|
182
|
+
value: "nextjs",
|
|
183
|
+
label: "Next.js App Router TypeScript",
|
|
184
|
+
hint: "Full-stack app starter"
|
|
185
|
+
}],
|
|
186
|
+
initialValue: "vite"
|
|
187
|
+
});
|
|
188
|
+
if (isCancel(value)) {
|
|
189
|
+
cancel("Cancelled.");
|
|
190
|
+
process.exit(0);
|
|
191
|
+
}
|
|
192
|
+
if (!isTemplate(value)) throw new CliError("E_TEMPLATE_INVALID", `Unsupported template "${value}". Use vite or nextjs.`);
|
|
193
|
+
return value;
|
|
194
|
+
}
|
|
195
|
+
async function promptProjectName() {
|
|
196
|
+
const value = await text({
|
|
197
|
+
message: "Project name",
|
|
198
|
+
placeholder: DEFAULT_PROJECT_NAME,
|
|
199
|
+
defaultValue: DEFAULT_PROJECT_NAME,
|
|
200
|
+
validate(value) {
|
|
201
|
+
try {
|
|
202
|
+
validateProjectName(value ?? "");
|
|
203
|
+
return;
|
|
204
|
+
} catch (error) {
|
|
205
|
+
return error instanceof Error ? error.message : String(error);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
if (isCancel(value)) {
|
|
210
|
+
cancel("Cancelled.");
|
|
211
|
+
process.exit(0);
|
|
212
|
+
}
|
|
213
|
+
return value;
|
|
214
|
+
}
|
|
215
|
+
async function createTemplate(plan, json) {
|
|
216
|
+
const templateDir = getTemplateDir(plan.template);
|
|
217
|
+
const task = json ? null : spinner();
|
|
218
|
+
task?.start("Creating project");
|
|
219
|
+
if (plan.force && await pathExists(plan.targetDir)) await emptyDir(plan.targetDir);
|
|
220
|
+
else await ensureDir(plan.targetDir);
|
|
221
|
+
await copy(templateDir, plan.targetDir, {
|
|
222
|
+
overwrite: true,
|
|
223
|
+
errorOnExist: false,
|
|
224
|
+
filter: (source) => {
|
|
225
|
+
return !path.relative(templateDir, source).split(path.sep).includes("node_modules");
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
await restoreGitignore(plan.targetDir);
|
|
229
|
+
await assertTemplateCopied(plan);
|
|
230
|
+
task?.stop("Project created");
|
|
231
|
+
}
|
|
232
|
+
function getTemplateDir(template) {
|
|
233
|
+
const currentFile = fileURLToPath(import.meta.url);
|
|
234
|
+
const packageDir = path.resolve(path.dirname(currentFile), "..");
|
|
235
|
+
return path.join(packageDir, "templates", template);
|
|
236
|
+
}
|
|
237
|
+
async function restoreGitignore(targetDir) {
|
|
238
|
+
const source = path.join(targetDir, "gitignore");
|
|
239
|
+
if (!await pathExists(source)) return;
|
|
240
|
+
await rename(source, path.join(targetDir, ".gitignore"));
|
|
241
|
+
}
|
|
242
|
+
async function assertTemplateCopied(plan) {
|
|
243
|
+
if (await pathExists(path.join(plan.targetDir, "package.json"))) return;
|
|
244
|
+
throw new CliError("E_TEMPLATE_COPY_FAILED", `Template copy failed: ${plan.targetDir} is missing package.json`, {
|
|
245
|
+
template: plan.template,
|
|
246
|
+
targetDir: plan.targetDir
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
async function assertTargetWritable(targetDir) {
|
|
250
|
+
if (!await pathExists(targetDir)) return;
|
|
251
|
+
if (!(await stat(targetDir)).isDirectory()) throw new CliError("E_TARGET_NOT_DIRECTORY", `Target exists and is not a directory: ${targetDir}`);
|
|
252
|
+
}
|
|
253
|
+
async function isNonEmptyDirectory(targetDir) {
|
|
254
|
+
if (!await pathExists(targetDir)) return false;
|
|
255
|
+
if (!(await stat(targetDir)).isDirectory()) return false;
|
|
256
|
+
return (await readdir(targetDir)).length > 0;
|
|
257
|
+
}
|
|
258
|
+
function validateProjectName(projectName) {
|
|
259
|
+
if (!projectName.trim()) throw new CliError("E_PROJECT_NAME_REQUIRED", "Project name is required.");
|
|
260
|
+
const result = validatePackageName(path.basename(projectName));
|
|
261
|
+
if (result.validForNewPackages) return;
|
|
262
|
+
throw new CliError("E_PROJECT_NAME_INVALID", [...result.errors ?? [], ...result.warnings ?? []][0] ?? `Invalid project name: ${projectName}`);
|
|
263
|
+
}
|
|
264
|
+
function isTemplate(value) {
|
|
265
|
+
return TEMPLATES.includes(value);
|
|
266
|
+
}
|
|
267
|
+
function formatPathForShell(value) {
|
|
268
|
+
return value.includes(" ") ? JSON.stringify(value) : value;
|
|
269
|
+
}
|
|
270
|
+
function handleFatalError(error, json) {
|
|
271
|
+
if (error instanceof CliError) {
|
|
272
|
+
printError(error, json);
|
|
273
|
+
process.exit(1);
|
|
274
|
+
}
|
|
275
|
+
printError(new CliError("E_UNEXPECTED", error instanceof Error ? error.message : String(error)), json);
|
|
276
|
+
process.exit(1);
|
|
277
|
+
}
|
|
278
|
+
function printError(error, json) {
|
|
279
|
+
const payload = {
|
|
280
|
+
ok: false,
|
|
281
|
+
error: {
|
|
282
|
+
code: error.code,
|
|
283
|
+
message: error.message,
|
|
284
|
+
...error.details ? { details: error.details } : {}
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
if (json) {
|
|
288
|
+
console.error(JSON.stringify(payload, null, 2));
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
console.error(`${pc.red("Error")} ${error.message}`);
|
|
292
|
+
}
|
|
293
|
+
//#endregion
|
|
294
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "howone",
|
|
3
|
+
"version": "0.0.3",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "HowOne command line tools for creating app templates.",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"main": "./bin/index.mjs",
|
|
9
|
+
"types": "./bin/index.d.mts",
|
|
10
|
+
"bin": {
|
|
11
|
+
"howone": "./bin/index.mjs"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"bin",
|
|
15
|
+
"templates"
|
|
16
|
+
],
|
|
17
|
+
"exports": {
|
|
18
|
+
".": "./bin/index.mjs"
|
|
19
|
+
},
|
|
20
|
+
"publishConfig": {
|
|
21
|
+
"access": "public"
|
|
22
|
+
},
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsdown src/index.ts --format esm --platform node --out-dir bin --clean --dts",
|
|
25
|
+
"dev": "bun src/index.ts",
|
|
26
|
+
"lint": "biome check src templates",
|
|
27
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@clack/prompts": "^1.3.0",
|
|
31
|
+
"cac": "^7.0.0",
|
|
32
|
+
"fs-extra": "^11.3.4",
|
|
33
|
+
"picocolors": "^1.1.1",
|
|
34
|
+
"validate-npm-package-name": "^7.0.2"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@howone/tsconfig": "workspace:*",
|
|
38
|
+
"@types/fs-extra": "^11.0.4",
|
|
39
|
+
"@types/node": "^25.6.0",
|
|
40
|
+
"@types/validate-npm-package-name": "^4.0.2",
|
|
41
|
+
"tsdown": "^0.21.10",
|
|
42
|
+
"typescript": "^5.7.3"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<!-- BEGIN:nextjs-agent-rules -->
|
|
2
|
+
# This is NOT the Next.js you know
|
|
3
|
+
|
|
4
|
+
This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` before writing any code. Heed deprecation notices.
|
|
5
|
+
<!-- END:nextjs-agent-rules -->
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@AGENTS.md
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
|
|
2
|
+
|
|
3
|
+
## Getting Started
|
|
4
|
+
|
|
5
|
+
First, run the development server:
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm run dev
|
|
9
|
+
# or
|
|
10
|
+
yarn dev
|
|
11
|
+
# or
|
|
12
|
+
pnpm dev
|
|
13
|
+
# or
|
|
14
|
+
bun dev
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
|
18
|
+
|
|
19
|
+
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
|
|
20
|
+
|
|
21
|
+
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
|
|
22
|
+
|
|
23
|
+
## Learn More
|
|
24
|
+
|
|
25
|
+
To learn more about Next.js, take a look at the following resources:
|
|
26
|
+
|
|
27
|
+
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
|
28
|
+
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
|
29
|
+
|
|
30
|
+
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
|
|
31
|
+
|
|
32
|
+
## Deploy on Vercel
|
|
33
|
+
|
|
34
|
+
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
|
35
|
+
|
|
36
|
+
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
|
|
Binary file
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
--background: #ffffff;
|
|
3
|
+
--foreground: #171717;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
@media (prefers-color-scheme: dark) {
|
|
7
|
+
:root {
|
|
8
|
+
--background: #0a0a0a;
|
|
9
|
+
--foreground: #ededed;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
html {
|
|
14
|
+
height: 100%;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
html,
|
|
18
|
+
body {
|
|
19
|
+
max-width: 100vw;
|
|
20
|
+
overflow-x: hidden;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
body {
|
|
24
|
+
min-height: 100%;
|
|
25
|
+
display: flex;
|
|
26
|
+
flex-direction: column;
|
|
27
|
+
color: var(--foreground);
|
|
28
|
+
background: var(--background);
|
|
29
|
+
font-family: Arial, Helvetica, sans-serif;
|
|
30
|
+
-webkit-font-smoothing: antialiased;
|
|
31
|
+
-moz-osx-font-smoothing: grayscale;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
* {
|
|
35
|
+
box-sizing: border-box;
|
|
36
|
+
padding: 0;
|
|
37
|
+
margin: 0;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
a {
|
|
41
|
+
color: inherit;
|
|
42
|
+
text-decoration: none;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
@media (prefers-color-scheme: dark) {
|
|
46
|
+
html {
|
|
47
|
+
color-scheme: dark;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Metadata } from "next";
|
|
2
|
+
import { Geist, Geist_Mono } from "next/font/google";
|
|
3
|
+
import "./globals.css";
|
|
4
|
+
|
|
5
|
+
const geistSans = Geist({
|
|
6
|
+
variable: "--font-geist-sans",
|
|
7
|
+
subsets: ["latin"],
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
const geistMono = Geist_Mono({
|
|
11
|
+
variable: "--font-geist-mono",
|
|
12
|
+
subsets: ["latin"],
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
export const metadata: Metadata = {
|
|
16
|
+
title: "Create Next App",
|
|
17
|
+
description: "Generated by create next app",
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export default function RootLayout({
|
|
21
|
+
children,
|
|
22
|
+
}: Readonly<{
|
|
23
|
+
children: React.ReactNode;
|
|
24
|
+
}>) {
|
|
25
|
+
return (
|
|
26
|
+
<html lang="en" className={`${geistSans.variable} ${geistMono.variable}`}>
|
|
27
|
+
<body>{children}</body>
|
|
28
|
+
</html>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
.page {
|
|
2
|
+
--background: #fafafa;
|
|
3
|
+
--foreground: #fff;
|
|
4
|
+
|
|
5
|
+
--text-primary: #000;
|
|
6
|
+
--text-secondary: #666;
|
|
7
|
+
|
|
8
|
+
--button-primary-hover: #383838;
|
|
9
|
+
--button-secondary-hover: #f2f2f2;
|
|
10
|
+
--button-secondary-border: #ebebeb;
|
|
11
|
+
|
|
12
|
+
display: flex;
|
|
13
|
+
flex: 1;
|
|
14
|
+
flex-direction: column;
|
|
15
|
+
align-items: center;
|
|
16
|
+
justify-content: center;
|
|
17
|
+
font-family: var(--font-geist-sans);
|
|
18
|
+
background-color: var(--background);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.main {
|
|
22
|
+
display: flex;
|
|
23
|
+
flex: 1;
|
|
24
|
+
width: 100%;
|
|
25
|
+
max-width: 800px;
|
|
26
|
+
flex-direction: column;
|
|
27
|
+
align-items: flex-start;
|
|
28
|
+
justify-content: space-between;
|
|
29
|
+
background-color: var(--foreground);
|
|
30
|
+
padding: 120px 60px;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.intro {
|
|
34
|
+
display: flex;
|
|
35
|
+
flex-direction: column;
|
|
36
|
+
align-items: flex-start;
|
|
37
|
+
text-align: left;
|
|
38
|
+
gap: 24px;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.intro h1 {
|
|
42
|
+
max-width: 320px;
|
|
43
|
+
font-size: 40px;
|
|
44
|
+
font-weight: 600;
|
|
45
|
+
line-height: 48px;
|
|
46
|
+
letter-spacing: -2.4px;
|
|
47
|
+
text-wrap: balance;
|
|
48
|
+
color: var(--text-primary);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.intro p {
|
|
52
|
+
max-width: 440px;
|
|
53
|
+
font-size: 18px;
|
|
54
|
+
line-height: 32px;
|
|
55
|
+
text-wrap: balance;
|
|
56
|
+
color: var(--text-secondary);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.intro a {
|
|
60
|
+
font-weight: 500;
|
|
61
|
+
color: var(--text-primary);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.ctas {
|
|
65
|
+
display: flex;
|
|
66
|
+
flex-direction: row;
|
|
67
|
+
width: 100%;
|
|
68
|
+
max-width: 440px;
|
|
69
|
+
gap: 16px;
|
|
70
|
+
font-size: 14px;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.ctas a {
|
|
74
|
+
display: flex;
|
|
75
|
+
justify-content: center;
|
|
76
|
+
align-items: center;
|
|
77
|
+
height: 40px;
|
|
78
|
+
padding: 0 16px;
|
|
79
|
+
border-radius: 128px;
|
|
80
|
+
border: 1px solid transparent;
|
|
81
|
+
transition: 0.2s;
|
|
82
|
+
cursor: pointer;
|
|
83
|
+
width: fit-content;
|
|
84
|
+
font-weight: 500;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
a.primary {
|
|
88
|
+
background: var(--text-primary);
|
|
89
|
+
color: var(--background);
|
|
90
|
+
gap: 8px;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
a.secondary {
|
|
94
|
+
border-color: var(--button-secondary-border);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/* Enable hover only on non-touch devices */
|
|
98
|
+
@media (hover: hover) and (pointer: fine) {
|
|
99
|
+
a.primary:hover {
|
|
100
|
+
background: var(--button-primary-hover);
|
|
101
|
+
border-color: transparent;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
a.secondary:hover {
|
|
105
|
+
background: var(--button-secondary-hover);
|
|
106
|
+
border-color: transparent;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
@media (max-width: 600px) {
|
|
111
|
+
.main {
|
|
112
|
+
padding: 48px 24px;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.intro {
|
|
116
|
+
gap: 16px;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.intro h1 {
|
|
120
|
+
font-size: 32px;
|
|
121
|
+
line-height: 40px;
|
|
122
|
+
letter-spacing: -1.92px;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
@media (prefers-color-scheme: dark) {
|
|
127
|
+
.logo {
|
|
128
|
+
filter: invert();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.page {
|
|
132
|
+
--background: #000;
|
|
133
|
+
--foreground: #000;
|
|
134
|
+
|
|
135
|
+
--text-primary: #ededed;
|
|
136
|
+
--text-secondary: #999;
|
|
137
|
+
|
|
138
|
+
--button-primary-hover: #ccc;
|
|
139
|
+
--button-secondary-hover: #1a1a1a;
|
|
140
|
+
--button-secondary-border: #1a1a1a;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import Image from "next/image";
|
|
2
|
+
import styles from "./page.module.css";
|
|
3
|
+
|
|
4
|
+
export default function Home() {
|
|
5
|
+
return (
|
|
6
|
+
<div className={styles.page}>
|
|
7
|
+
<main className={styles.main}>
|
|
8
|
+
<Image
|
|
9
|
+
className={styles.logo}
|
|
10
|
+
src="/next.svg"
|
|
11
|
+
alt="Next.js logo"
|
|
12
|
+
width={100}
|
|
13
|
+
height={20}
|
|
14
|
+
priority
|
|
15
|
+
/>
|
|
16
|
+
<div className={styles.intro}>
|
|
17
|
+
<h1>To get started, edit the page.tsx file.</h1>
|
|
18
|
+
<p>
|
|
19
|
+
Looking for a starting point or more instructions? Head over to{" "}
|
|
20
|
+
<a
|
|
21
|
+
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
|
22
|
+
target="_blank"
|
|
23
|
+
rel="noopener noreferrer"
|
|
24
|
+
>
|
|
25
|
+
Templates
|
|
26
|
+
</a>{" "}
|
|
27
|
+
or the{" "}
|
|
28
|
+
<a
|
|
29
|
+
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
|
|
30
|
+
target="_blank"
|
|
31
|
+
rel="noopener noreferrer"
|
|
32
|
+
>
|
|
33
|
+
Learning
|
|
34
|
+
</a>{" "}
|
|
35
|
+
center.
|
|
36
|
+
</p>
|
|
37
|
+
</div>
|
|
38
|
+
<div className={styles.ctas}>
|
|
39
|
+
<a
|
|
40
|
+
className={styles.primary}
|
|
41
|
+
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
|
|
42
|
+
target="_blank"
|
|
43
|
+
rel="noopener noreferrer"
|
|
44
|
+
>
|
|
45
|
+
<Image
|
|
46
|
+
className={styles.logo}
|
|
47
|
+
src="/vercel.svg"
|
|
48
|
+
alt="Vercel logomark"
|
|
49
|
+
width={16}
|
|
50
|
+
height={16}
|
|
51
|
+
/>
|
|
52
|
+
Deploy Now
|
|
53
|
+
</a>
|
|
54
|
+
<a
|
|
55
|
+
className={styles.secondary}
|
|
56
|
+
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
|
|
57
|
+
target="_blank"
|
|
58
|
+
rel="noopener noreferrer"
|
|
59
|
+
>
|
|
60
|
+
Documentation
|
|
61
|
+
</a>
|
|
62
|
+
</div>
|
|
63
|
+
</main>
|
|
64
|
+
</div>
|
|
65
|
+
);
|
|
66
|
+
}
|