create-flamefront 0.0.0 → 0.1.4
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 +15 -0
- package/bin/create-flamefront.js +5 -0
- package/package.json +23 -9
- package/src/cli.d.ts +33 -0
- package/src/cli.js +198 -0
- package/template/gitignore +3 -0
- package/template/index.html +12 -0
- package/template/package.json +27 -0
- package/template/pnpm-workspace.yaml +2 -0
- package/template/src/AppShell.tsrx +8 -0
- package/template/src/HomePage.tsrx +16 -0
- package/template/src/app.ts +6 -0
- package/template/src/entry-server.ts +16 -0
- package/template/src/env.d.ts +27 -0
- package/template/src/main.ts +5 -0
- package/template/src/styles.css +31 -0
- package/template/tsconfig.json +15 -0
- package/template/vite.config.ts +7 -0
- package/readme.md +0 -1
package/README.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# create-flamefront
|
|
2
|
+
|
|
3
|
+
Create a Flamefront app with one server-rendered Octane route:
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
pnpm create flamefront@latest my-app
|
|
7
|
+
cd my-app
|
|
8
|
+
pnpm dev
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Use `--no-install` to create the project files without installing dependencies.
|
|
12
|
+
The target directory must be empty.
|
|
13
|
+
|
|
14
|
+
See the [Flamefront create-app guide](https://github.com/aleclarson/flamefront/blob/main/docs/create-app.md)
|
|
15
|
+
for the generated files and how they work.
|
package/package.json
CHANGED
|
@@ -1,13 +1,27 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-flamefront",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
|
|
3
|
+
"version": "0.1.4",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "Create a Flamefront app.",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/aleclarson/flamefront.git",
|
|
10
|
+
"directory": "packages/create-flamefront"
|
|
8
11
|
},
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
"type": "module",
|
|
13
|
+
"bin": {
|
|
14
|
+
"create-flamefront": "./bin/create-flamefront.js"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"bin",
|
|
18
|
+
"src",
|
|
19
|
+
"template"
|
|
20
|
+
],
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=26.0.0"
|
|
23
|
+
},
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
}
|
|
13
27
|
}
|
package/src/cli.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export type PackageManager = "bun" | "npm" | "pnpm" | "yarn"
|
|
2
|
+
|
|
3
|
+
export interface CreateProjectOptions {
|
|
4
|
+
readonly cwd?: string
|
|
5
|
+
readonly install?: boolean
|
|
6
|
+
readonly installDependencies?: (
|
|
7
|
+
packageManager: PackageManager,
|
|
8
|
+
directory: string,
|
|
9
|
+
) => Promise<void> | void
|
|
10
|
+
readonly packageManager?: PackageManager
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface CreatedProject {
|
|
14
|
+
readonly name: string
|
|
15
|
+
readonly packageManager: PackageManager
|
|
16
|
+
readonly targetDirectory: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function detectPackageManager(
|
|
20
|
+
environment?: Record<string, string | undefined>,
|
|
21
|
+
): PackageManager
|
|
22
|
+
|
|
23
|
+
export function projectName(directory: string): string
|
|
24
|
+
|
|
25
|
+
export function createProject(
|
|
26
|
+
directory: string,
|
|
27
|
+
options?: CreateProjectOptions,
|
|
28
|
+
): Promise<CreatedProject>
|
|
29
|
+
|
|
30
|
+
export function run(
|
|
31
|
+
arguments_: readonly string[],
|
|
32
|
+
options?: Omit<CreateProjectOptions, "install">,
|
|
33
|
+
): Promise<void>
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { spawn } from "node:child_process"
|
|
2
|
+
import {
|
|
3
|
+
cp,
|
|
4
|
+
mkdir,
|
|
5
|
+
readFile,
|
|
6
|
+
readdir,
|
|
7
|
+
rename,
|
|
8
|
+
stat,
|
|
9
|
+
writeFile,
|
|
10
|
+
} from "node:fs/promises"
|
|
11
|
+
import { basename, resolve } from "node:path"
|
|
12
|
+
import { fileURLToPath } from "node:url"
|
|
13
|
+
|
|
14
|
+
const templateDirectory = fileURLToPath(
|
|
15
|
+
new URL("../template/", import.meta.url),
|
|
16
|
+
)
|
|
17
|
+
const packageMetadata = JSON.parse(
|
|
18
|
+
await readFile(new URL("../package.json", import.meta.url), "utf8"),
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
const usage = `Create a Flamefront app.
|
|
22
|
+
|
|
23
|
+
Usage:
|
|
24
|
+
create-flamefront <directory> [--no-install]
|
|
25
|
+
|
|
26
|
+
Options:
|
|
27
|
+
--no-install Create the project without installing dependencies
|
|
28
|
+
-h, --help Show this help
|
|
29
|
+
-v, --version Show the installed create-flamefront version`
|
|
30
|
+
|
|
31
|
+
export function detectPackageManager(environment = process.env) {
|
|
32
|
+
const userAgent = environment.npm_config_user_agent ?? ""
|
|
33
|
+
const name = userAgent.split("/", 1)[0]
|
|
34
|
+
|
|
35
|
+
return ["pnpm", "npm", "yarn", "bun"].includes(name) ? name : "npm"
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function projectName(directory) {
|
|
39
|
+
const name = basename(resolve(directory))
|
|
40
|
+
const valid =
|
|
41
|
+
name.length <= 214 &&
|
|
42
|
+
/^(?:[a-z0-9][a-z0-9._-]*)$/.test(name) &&
|
|
43
|
+
!name.startsWith(".") &&
|
|
44
|
+
!name.startsWith("_")
|
|
45
|
+
|
|
46
|
+
if (!valid) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
`${JSON.stringify(name)} is not a valid package name. Use lowercase letters, numbers, dots, hyphens, or underscores.`,
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return name
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function parseArguments(arguments_) {
|
|
56
|
+
let install = true
|
|
57
|
+
let directory
|
|
58
|
+
|
|
59
|
+
for (const argument of arguments_) {
|
|
60
|
+
if (argument === "--no-install") {
|
|
61
|
+
install = false
|
|
62
|
+
} else if (argument === "-h" || argument === "--help") {
|
|
63
|
+
return { help: true }
|
|
64
|
+
} else if (argument === "-v" || argument === "--version") {
|
|
65
|
+
return { version: true }
|
|
66
|
+
} else if (argument.startsWith("-")) {
|
|
67
|
+
throw new Error(`Unknown option: ${argument}`)
|
|
68
|
+
} else if (directory === undefined) {
|
|
69
|
+
directory = argument
|
|
70
|
+
} else {
|
|
71
|
+
throw new Error("Provide exactly one target directory.")
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (directory === undefined) {
|
|
76
|
+
throw new Error("A target directory is required.")
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return { directory, install }
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function ensureEmptyDirectory(directory) {
|
|
83
|
+
try {
|
|
84
|
+
const details = await stat(directory)
|
|
85
|
+
|
|
86
|
+
if (!details.isDirectory()) {
|
|
87
|
+
throw new Error(`${directory} exists and is not a directory.`)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const entries = await readdir(directory)
|
|
91
|
+
|
|
92
|
+
if (entries.length > 0) {
|
|
93
|
+
throw new Error(`${directory} is not empty.`)
|
|
94
|
+
}
|
|
95
|
+
} catch (error) {
|
|
96
|
+
if (error?.code !== "ENOENT") {
|
|
97
|
+
throw error
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
await mkdir(directory, { recursive: true })
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async function installDependencies(packageManager, directory) {
|
|
105
|
+
await new Promise((fulfill, reject) => {
|
|
106
|
+
const child = spawn(packageManager, ["install"], {
|
|
107
|
+
cwd: directory,
|
|
108
|
+
stdio: "inherit",
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
child.on("error", reject)
|
|
112
|
+
child.on("exit", (code, signal) => {
|
|
113
|
+
if (code === 0) {
|
|
114
|
+
fulfill()
|
|
115
|
+
} else {
|
|
116
|
+
reject(
|
|
117
|
+
new Error(
|
|
118
|
+
signal
|
|
119
|
+
? `${packageManager} install was terminated by ${signal}.`
|
|
120
|
+
: `${packageManager} install exited with code ${code}.`,
|
|
121
|
+
),
|
|
122
|
+
)
|
|
123
|
+
}
|
|
124
|
+
})
|
|
125
|
+
})
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export async function createProject(directory, options = {}) {
|
|
129
|
+
const targetDirectory = resolve(options.cwd ?? process.cwd(), directory)
|
|
130
|
+
const name = projectName(targetDirectory)
|
|
131
|
+
const packageManager = options.packageManager ?? detectPackageManager()
|
|
132
|
+
|
|
133
|
+
await ensureEmptyDirectory(targetDirectory)
|
|
134
|
+
await cp(templateDirectory, targetDirectory, { recursive: true })
|
|
135
|
+
await rename(
|
|
136
|
+
resolve(targetDirectory, "gitignore"),
|
|
137
|
+
resolve(targetDirectory, ".gitignore"),
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
const packageFile = resolve(targetDirectory, "package.json")
|
|
141
|
+
const template = await readFile(packageFile, "utf8")
|
|
142
|
+
|
|
143
|
+
await writeFile(
|
|
144
|
+
packageFile,
|
|
145
|
+
template
|
|
146
|
+
.replaceAll("__PACKAGE_NAME__", name)
|
|
147
|
+
.replaceAll("__FLAMEFRONT_VERSION__", packageMetadata.version),
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
if (options.install !== false) {
|
|
151
|
+
await (options.installDependencies ?? installDependencies)(
|
|
152
|
+
packageManager,
|
|
153
|
+
targetDirectory,
|
|
154
|
+
)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return { name, packageManager, targetDirectory }
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export async function run(arguments_, options = {}) {
|
|
161
|
+
try {
|
|
162
|
+
const parsed = parseArguments(arguments_)
|
|
163
|
+
|
|
164
|
+
if (parsed.help) {
|
|
165
|
+
console.log(usage)
|
|
166
|
+
return
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (parsed.version) {
|
|
170
|
+
console.log(packageMetadata.version)
|
|
171
|
+
return
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const result = await createProject(parsed.directory, {
|
|
175
|
+
cwd: options.cwd,
|
|
176
|
+
install: parsed.install,
|
|
177
|
+
installDependencies: options.installDependencies,
|
|
178
|
+
packageManager: options.packageManager,
|
|
179
|
+
})
|
|
180
|
+
const relativeTarget = parsed.directory === "." ? "." : parsed.directory
|
|
181
|
+
|
|
182
|
+
console.log(`Created ${result.name} in ${result.targetDirectory}.`)
|
|
183
|
+
if (!parsed.install) {
|
|
184
|
+
console.log(
|
|
185
|
+
`\nNext steps:\n cd ${relativeTarget}\n ${result.packageManager} install\n ${result.packageManager} run dev`,
|
|
186
|
+
)
|
|
187
|
+
} else {
|
|
188
|
+
console.log(
|
|
189
|
+
`\nNext steps:\n cd ${relativeTarget}\n ${result.packageManager} run dev`,
|
|
190
|
+
)
|
|
191
|
+
}
|
|
192
|
+
} catch (error) {
|
|
193
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
194
|
+
|
|
195
|
+
console.error(`create-flamefront: ${message}`)
|
|
196
|
+
process.exitCode = 1
|
|
197
|
+
}
|
|
198
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>Flamefront app</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="root"></div>
|
|
10
|
+
<script type="module" src="/src/main.ts"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "__PACKAGE_NAME__",
|
|
3
|
+
"private": true,
|
|
4
|
+
"type": "module",
|
|
5
|
+
"engines": {
|
|
6
|
+
"node": ">=26.0.0"
|
|
7
|
+
},
|
|
8
|
+
"scripts": {
|
|
9
|
+
"dev": "ff dev",
|
|
10
|
+
"build": "ff build",
|
|
11
|
+
"preview": "ff preview",
|
|
12
|
+
"routes": "ff routes",
|
|
13
|
+
"typegen": "ff typegen",
|
|
14
|
+
"typecheck": "tsc"
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@octanejs/remix-router": "0.1.48",
|
|
18
|
+
"@octanejs/vite-plugin": "0.1.54",
|
|
19
|
+
"flamefront": "^__FLAMEFRONT_VERSION__",
|
|
20
|
+
"octane": "0.2.7"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@types/node": "26.5.1",
|
|
24
|
+
"typescript": "5.9.3",
|
|
25
|
+
"vite": "8.3.0"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { useLoaderData } from "@octanejs/remix-router"
|
|
2
|
+
import type { LoaderArgs } from "flamefront/server"
|
|
3
|
+
|
|
4
|
+
export async function loader({ request }: LoaderArgs<"/">) {
|
|
5
|
+
return { pathname: new URL(request.url).pathname }
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export default function HomePage() @{
|
|
9
|
+
const data = useLoaderData<typeof loader>()
|
|
10
|
+
|
|
11
|
+
<main>
|
|
12
|
+
<p className="eyebrow">Server-rendered with Flamefront</p>
|
|
13
|
+
<h1>Your app is ready.</h1>
|
|
14
|
+
<p>The loader handled {data.pathname}</p>
|
|
15
|
+
</main>
|
|
16
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { importRoute } from "virtual:flamefront/server-routes"
|
|
2
|
+
import { createServerEntry } from "flamefront/entry"
|
|
3
|
+
import { createOctaneDocuments } from "flamefront/octane"
|
|
4
|
+
import { createRouteRuntime } from "flamefront/server"
|
|
5
|
+
import { app } from "./app.ts"
|
|
6
|
+
|
|
7
|
+
const runtime = createRouteRuntime({ app, importRoute })
|
|
8
|
+
const documents = createOctaneDocuments({ app, runtime })
|
|
9
|
+
|
|
10
|
+
export default createServerEntry({
|
|
11
|
+
app,
|
|
12
|
+
documents,
|
|
13
|
+
assets: {
|
|
14
|
+
clientDirectory: new URL("../client/", import.meta.url),
|
|
15
|
+
},
|
|
16
|
+
})
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
declare module "*.tsrx" {
|
|
2
|
+
const Component: unknown
|
|
3
|
+
|
|
4
|
+
export default Component
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
declare module "virtual:flamefront/remix-routes" {
|
|
8
|
+
import type { RouteObject } from "@octanejs/remix-router"
|
|
9
|
+
import type { RouterDocument as RouterDocumentComponent } from "flamefront/octane"
|
|
10
|
+
import type {
|
|
11
|
+
GeneratedRouteMetadata,
|
|
12
|
+
NormalizedRoutingOptions,
|
|
13
|
+
} from "flamefront"
|
|
14
|
+
|
|
15
|
+
export const RouterDocument: RouterDocumentComponent
|
|
16
|
+
export const routes: RouteObject[]
|
|
17
|
+
export const routing: NormalizedRoutingOptions
|
|
18
|
+
export const routeMetadata: readonly GeneratedRouteMetadata[]
|
|
19
|
+
export function preloadRoute(entry: string): Promise<void>
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
declare module "virtual:flamefront/server-routes" {
|
|
23
|
+
import type { RouteModule } from "flamefront/server"
|
|
24
|
+
|
|
25
|
+
export function importRoute(entry: string): Promise<RouteModule>
|
|
26
|
+
export function loadActions(): Promise<void>
|
|
27
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
color: #231a14;
|
|
3
|
+
background: #fff8ed;
|
|
4
|
+
font-family: ui-sans-serif, system-ui, sans-serif;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
body {
|
|
8
|
+
margin: 0;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.app-shell {
|
|
12
|
+
width: min(42rem, calc(100% - 3rem));
|
|
13
|
+
margin: 0 auto;
|
|
14
|
+
padding: 3rem 0;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
header,
|
|
18
|
+
.eyebrow {
|
|
19
|
+
color: #b33a16;
|
|
20
|
+
font-weight: 700;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
main {
|
|
24
|
+
padding-top: 6rem;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
h1 {
|
|
28
|
+
margin: 0.25rem 0;
|
|
29
|
+
font-size: clamp(2.5rem, 8vw, 5rem);
|
|
30
|
+
line-height: 0.95;
|
|
31
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"allowImportingTsExtensions": true,
|
|
4
|
+
"jsx": "react-jsx",
|
|
5
|
+
"jsxImportSource": "octane",
|
|
6
|
+
"module": "ESNext",
|
|
7
|
+
"moduleResolution": "Bundler",
|
|
8
|
+
"noEmit": true,
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
"strict": true,
|
|
11
|
+
"target": "ESNext",
|
|
12
|
+
"types": ["node", "vite/client"]
|
|
13
|
+
},
|
|
14
|
+
"include": ["src", ".flamefront/types"]
|
|
15
|
+
}
|
package/readme.md
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
Coming soon...
|