create-spacefn 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/bin/create-space-app.js +4 -0
  2. package/dist/index.d.ts +2 -0
  3. package/dist/index.js +129 -0
  4. package/dist/templates/minimal/configs/oxfmt.ts +8 -0
  5. package/dist/templates/minimal/configs/oxlint.ts +8 -0
  6. package/dist/templates/minimal/package.json +18 -0
  7. package/dist/templates/minimal/src/main.ts +6 -0
  8. package/dist/templates/minimal/src/pages/index.page.ts +9 -0
  9. package/dist/templates/minimal/src/pages/index.server.ts +5 -0
  10. package/dist/templates/minimal/src/pages/index.test.ts +22 -0
  11. package/dist/templates/minimal/src/routes/index.ts +14 -0
  12. package/dist/templates/minimal/tsconfig.json +14 -0
  13. package/dist/templates/minimal/vite.config.ts +9 -0
  14. package/dist/templates/minimal/vitest.config.ts +7 -0
  15. package/dist/templates/minimal/wrangler.toml +1 -0
  16. package/package.json +23 -0
  17. package/src/index.ts +169 -0
  18. package/src/templates/minimal/configs/oxfmt.ts +8 -0
  19. package/src/templates/minimal/configs/oxlint.ts +8 -0
  20. package/src/templates/minimal/package.json +18 -0
  21. package/src/templates/minimal/src/main.ts +6 -0
  22. package/src/templates/minimal/src/pages/index.page.ts +9 -0
  23. package/src/templates/minimal/src/pages/index.server.ts +5 -0
  24. package/src/templates/minimal/src/pages/index.test.ts +22 -0
  25. package/src/templates/minimal/src/routes/index.ts +14 -0
  26. package/src/templates/minimal/tsconfig.json +14 -0
  27. package/src/templates/minimal/vite.config.ts +9 -0
  28. package/src/templates/minimal/vitest.config.ts +7 -0
  29. package/src/templates/minimal/wrangler.toml +1 -0
  30. package/tsconfig.json +15 -0
  31. package/tsup.config.ts +8 -0
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from "node:module";
3
+ const require = createRequire(import.meta.url);
4
+ require("../dist/index.js");
@@ -0,0 +1,2 @@
1
+
2
+ export { }
package/dist/index.js ADDED
@@ -0,0 +1,129 @@
1
+ // src/index.ts
2
+ import { execSync } from "child_process";
3
+ import { existsSync } from "fs";
4
+ import { mkdir, writeFile, cp, readFile } from "fs/promises";
5
+ import * as p from "@clack/prompts";
6
+ import { defineCommand, runMain } from "citty";
7
+ import { resolve, join } from "pathe";
8
+ import pc from "picocolors";
9
+ var TEMPLATES = ["minimal"];
10
+ var TEMPLATE_DIR = join(import.meta.dirname, "templates");
11
+ var PKG_MANAGERS = [
12
+ { value: "pnpm", label: "pnpm", install: "pnpm add", addDev: "pnpm add -D" },
13
+ { value: "npm", label: "npm", install: "npm i", addDev: "npm i -D" },
14
+ { value: "yarn", label: "yarn", install: "yarn add", addDev: "yarn add -D" },
15
+ { value: "bun", label: "bun", install: "bun add", addDev: "bun add -d" }
16
+ ];
17
+ async function writeTemplate(targetDir, template, projectName) {
18
+ const srcDir = join(TEMPLATE_DIR, template);
19
+ await cp(srcDir, targetDir, { recursive: true });
20
+ const files = ["package.json", "wrangler.toml"];
21
+ for (const file of files) {
22
+ const filePath = join(targetDir, file);
23
+ if (existsSync(filePath)) {
24
+ const content = await readFile(filePath, "utf-8");
25
+ await writeFile(filePath, content.replaceAll("{{app_name}}", projectName));
26
+ }
27
+ }
28
+ }
29
+ function install(targetDir, pm, deps, devDeps) {
30
+ const mgr = PKG_MANAGERS.find((m) => m.value === pm);
31
+ if (deps.length > 0) {
32
+ execSync(`${mgr.install} ${deps.join(" ")}`, { cwd: targetDir, stdio: "inherit" });
33
+ }
34
+ if (devDeps.length > 0) {
35
+ execSync(`${mgr.addDev} ${devDeps.join(" ")}`, { cwd: targetDir, stdio: "inherit" });
36
+ }
37
+ }
38
+ var main = defineCommand({
39
+ meta: {
40
+ name: "create-spacefn",
41
+ description: "Scaffold a new Space project",
42
+ version: "0.1.0"
43
+ },
44
+ args: {
45
+ name: {
46
+ type: "positional",
47
+ description: "Project name",
48
+ required: false
49
+ }
50
+ },
51
+ async run({ args }) {
52
+ p.intro(pc.bold(pc.cyan("Create Space App")));
53
+ let projectName = args.name;
54
+ if (!projectName) {
55
+ const name = await p.text({
56
+ message: "Project name?",
57
+ placeholder: "my-space-app",
58
+ validate(value) {
59
+ if (!value) return "Project name is required";
60
+ if (!/^[a-z0-9-_]+$/.test(value))
61
+ return "Use lowercase, numbers, hyphens, or underscores";
62
+ }
63
+ });
64
+ if (p.isCancel(name)) {
65
+ p.cancel("Cancelled");
66
+ process.exit(0);
67
+ }
68
+ projectName = name || "my-space-app";
69
+ }
70
+ const pm = await p.select({
71
+ message: "Package manager?",
72
+ options: PKG_MANAGERS.map((m) => ({
73
+ value: m.value,
74
+ label: m.label
75
+ })),
76
+ initialValue: "npm"
77
+ });
78
+ if (p.isCancel(pm)) {
79
+ p.cancel("Cancelled");
80
+ process.exit(0);
81
+ }
82
+ const template = await p.select({
83
+ message: "Template?",
84
+ options: TEMPLATES.map((t) => ({
85
+ value: t,
86
+ label: t,
87
+ hint: t === "minimal" ? "Basic routing and HTML" : void 0
88
+ })),
89
+ initialValue: "minimal"
90
+ });
91
+ if (p.isCancel(template)) {
92
+ p.cancel("Cancelled");
93
+ process.exit(0);
94
+ }
95
+ const targetDir = resolve(process.cwd(), projectName);
96
+ if (existsSync(targetDir)) {
97
+ p.cancel(`Directory ${projectName} already exists`);
98
+ process.exit(1);
99
+ }
100
+ const s = p.spinner();
101
+ s.start("Creating project...");
102
+ await mkdir(targetDir, { recursive: true });
103
+ await writeTemplate(targetDir, template, projectName);
104
+ s.stop("Project created");
105
+ const installDeps = await p.confirm({
106
+ message: "Install dependencies?",
107
+ initialValue: true
108
+ });
109
+ if (p.isCancel(installDeps)) {
110
+ p.cancel("Cancelled");
111
+ process.exit(0);
112
+ }
113
+ if (installDeps) {
114
+ const s2 = p.spinner();
115
+ s2.start("Installing @space packages...");
116
+ install(
117
+ targetDir,
118
+ pm,
119
+ ["@spacefn/html", "@spacefn/server", "@spacefn/datastar"],
120
+ ["wrangler", "oxlint", "oxfmt", "vite", "vitest"]
121
+ );
122
+ s2.stop("Dependencies installed");
123
+ }
124
+ p.outro(pc.green("Done!"));
125
+ p.note(`cd ${projectName}
126
+ ${pm} dev`, "Next steps");
127
+ }
128
+ });
129
+ runMain(main);
@@ -0,0 +1,8 @@
1
+ import { defineConfig } from "oxfmt";
2
+
3
+ export default defineConfig({
4
+ sortImports: true,
5
+ sortPackageJson: true,
6
+ sortTailwindcss: true,
7
+ trailingComma: "all",
8
+ });
@@ -0,0 +1,8 @@
1
+ import { defineConfig } from "oxlint";
2
+
3
+ export default defineConfig({
4
+ rules: {
5
+ "no-unused-vars": "error",
6
+ "typescript/no-deprecated": "error",
7
+ },
8
+ });
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "{{app_name}}",
3
+ "version": "0.0.1",
4
+ "type": "module",
5
+ "scripts": {
6
+ "dev": "spacefn dev",
7
+ "build": "spacefn build",
8
+ "test": "vitest run",
9
+ "fmt": "oxfmt -c configs/oxfmt.ts",
10
+ "fmt:check": "oxfmt -c configs/oxfmt.ts --check",
11
+ "lint": "oxlint -c configs/oxlint.ts",
12
+ "lint:fix": "oxlint -c configs/oxlint.ts --fix"
13
+ },
14
+ "devDependencies": {
15
+ "@spacefn/server": "latest",
16
+ "vitest": "^3.0.0"
17
+ }
18
+ }
@@ -0,0 +1,6 @@
1
+ import { createServer } from "@spacefn/server";
2
+
3
+ import middlewares from "#space/middlewares";
4
+ import routes from "#space/routes";
5
+
6
+ export default createServer({ routes, middlewares });
@@ -0,0 +1,9 @@
1
+ import { defineComponent, h } from "@spacefn/html";
2
+
3
+ type Props = {
4
+ msg: string;
5
+ };
6
+
7
+ export default defineComponent<Props>((props) => {
8
+ return h.main({}, h.h1({}, props.msg), h.p({}, "Welcome to your Space app"));
9
+ });
@@ -0,0 +1,5 @@
1
+ import { defineLoader } from "@spacefn/server";
2
+
3
+ export const loader = defineLoader(async (_req) => {
4
+ return { msg: "Hello World!" };
5
+ });
@@ -0,0 +1,22 @@
1
+ import { createTestHandler } from "@spacefn/server/test";
2
+ import { describe, it, expect } from "vitest";
3
+
4
+ import Page from "./index.page";
5
+ import { loader } from "./index.server";
6
+
7
+ const handler = createTestHandler({ loader, page: Page });
8
+
9
+ describe("Home page", () => {
10
+ it("renders with loader data", async () => {
11
+ const res = await handler(new Request("http://localhost/"));
12
+ const html = await res.text();
13
+
14
+ expect(res.status).toBe(200);
15
+ expect(html).toContain("Hello World!");
16
+ });
17
+
18
+ it("returns HTML content type", async () => {
19
+ const res = await handler(new Request("http://localhost/"));
20
+ expect(res.headers.get("Content-Type")).toBe("text/html");
21
+ });
22
+ });
@@ -0,0 +1,14 @@
1
+ import { h, render } from "@spacefn/html";
2
+
3
+ export default function () {
4
+ return new Response(
5
+ render(
6
+ h.html(
7
+ {},
8
+ h.head({}, h.title({}, "Home")),
9
+ h.body({}, h.main({}, h.h1({}, "Hello from Space"), h.p({}, "Your app is running."))),
10
+ ),
11
+ ),
12
+ { headers: { "Content-Type": "text/html" } },
13
+ );
14
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "strict": true,
7
+ "esModuleInterop": true,
8
+ "skipLibCheck": true,
9
+ "paths": {
10
+ "#space/*": [".space/*"]
11
+ }
12
+ },
13
+ "include": ["src"]
14
+ }
@@ -0,0 +1,9 @@
1
+ import { space } from "@spacefn/server/vite";
2
+ import { defineConfig } from "vite";
3
+
4
+ export default defineConfig({
5
+ resolve: {
6
+ tsconfigPaths: true,
7
+ },
8
+ plugins: [space()],
9
+ });
@@ -0,0 +1,7 @@
1
+ import { defineConfig } from "vitest/config";
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ include: ["src/**/*.test.ts"],
6
+ },
7
+ });
@@ -0,0 +1 @@
1
+ name = "{{app_name}}"
package/package.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "create-spacefn",
3
+ "version": "0.1.0",
4
+ "bin": {
5
+ "create-spacefn": "./bin/create-space-app.js"
6
+ },
7
+ "type": "module",
8
+ "scripts": {
9
+ "build": "rm -rf dist && tsup && cp -r src/templates dist/templates",
10
+ "typecheck": "tsc --noEmit"
11
+ },
12
+ "dependencies": {
13
+ "@clack/prompts": "^0.9.0",
14
+ "citty": "^0.1.0",
15
+ "pathe": "^2.0.0",
16
+ "picocolors": "^1.1.0"
17
+ },
18
+ "devDependencies": {
19
+ "@types/node": "^22.0.0",
20
+ "tsup": "^8.0.0",
21
+ "typescript": "^5.5.0"
22
+ }
23
+ }
package/src/index.ts ADDED
@@ -0,0 +1,169 @@
1
+ import { execSync } from "node:child_process";
2
+ import { existsSync } from "node:fs";
3
+ // --- Create Space App ---------------------------------------------------------
4
+ // Scaffold a new Space project
5
+ import { mkdir, writeFile, cp, readFile } from "node:fs/promises";
6
+
7
+ import * as p from "@clack/prompts";
8
+ import { defineCommand, runMain } from "citty";
9
+ import { resolve, join } from "pathe";
10
+ import pc from "picocolors";
11
+
12
+ // --- Templates ----------------------------------------------------------------
13
+
14
+ const TEMPLATES = ["minimal"] as const;
15
+ type Template = (typeof TEMPLATES)[number];
16
+
17
+ const TEMPLATE_DIR = join(import.meta.dirname, "templates");
18
+
19
+ // --- Package Manager -----------------------------------------------------------
20
+
21
+ type PackageManager = "pnpm" | "npm" | "yarn" | "bun";
22
+
23
+ const PKG_MANAGERS: { value: PackageManager; label: string; install: string; addDev: string }[] = [
24
+ { value: "pnpm", label: "pnpm", install: "pnpm add", addDev: "pnpm add -D" },
25
+ { value: "npm", label: "npm", install: "npm i", addDev: "npm i -D" },
26
+ { value: "yarn", label: "yarn", install: "yarn add", addDev: "yarn add -D" },
27
+ { value: "bun", label: "bun", install: "bun add", addDev: "bun add -d" },
28
+ ];
29
+
30
+ // --- Helpers ------------------------------------------------------------------
31
+
32
+ async function writeTemplate(
33
+ targetDir: string,
34
+ template: Template,
35
+ projectName: string,
36
+ ): Promise<void> {
37
+ const srcDir = join(TEMPLATE_DIR, template);
38
+ await cp(srcDir, targetDir, { recursive: true });
39
+
40
+ // Replace {{app_name}} placeholder in template files
41
+ const files = ["package.json", "wrangler.toml"];
42
+ for (const file of files) {
43
+ const filePath = join(targetDir, file);
44
+ if (existsSync(filePath)) {
45
+ const content = await readFile(filePath, "utf-8");
46
+ await writeFile(filePath, content.replaceAll("{{app_name}}", projectName));
47
+ }
48
+ }
49
+ }
50
+
51
+ function install(targetDir: string, pm: PackageManager, deps: string[], devDeps: string[]): void {
52
+ const mgr = PKG_MANAGERS.find((m) => m.value === pm)!;
53
+ if (deps.length > 0) {
54
+ execSync(`${mgr.install} ${deps.join(" ")}`, { cwd: targetDir, stdio: "inherit" });
55
+ }
56
+ if (devDeps.length > 0) {
57
+ execSync(`${mgr.addDev} ${devDeps.join(" ")}`, { cwd: targetDir, stdio: "inherit" });
58
+ }
59
+ }
60
+
61
+ // --- CLI ----------------------------------------------------------------------
62
+
63
+ const main = defineCommand({
64
+ meta: {
65
+ name: "create-spacefn",
66
+ description: "Scaffold a new Space project",
67
+ version: "0.1.0",
68
+ },
69
+ args: {
70
+ name: {
71
+ type: "positional",
72
+ description: "Project name",
73
+ required: false,
74
+ },
75
+ },
76
+ async run({ args }) {
77
+ p.intro(pc.bold(pc.cyan("Create Space App")));
78
+
79
+ // Project name
80
+ let projectName = args.name as string;
81
+ if (!projectName) {
82
+ const name = await p.text({
83
+ message: "Project name?",
84
+ placeholder: "my-space-app",
85
+ validate(value) {
86
+ if (!value) return "Project name is required";
87
+ if (!/^[a-z0-9-_]+$/.test(value))
88
+ return "Use lowercase, numbers, hyphens, or underscores";
89
+ },
90
+ });
91
+ if (p.isCancel(name)) {
92
+ p.cancel("Cancelled");
93
+ process.exit(0);
94
+ }
95
+ projectName = name || "my-space-app";
96
+ }
97
+
98
+ // Package manager
99
+ const pm = await p.select({
100
+ message: "Package manager?",
101
+ options: PKG_MANAGERS.map((m) => ({
102
+ value: m.value,
103
+ label: m.label,
104
+ })),
105
+ initialValue: "npm",
106
+ });
107
+ if (p.isCancel(pm)) {
108
+ p.cancel("Cancelled");
109
+ process.exit(0);
110
+ }
111
+
112
+ // Template
113
+ const template = await p.select({
114
+ message: "Template?",
115
+ options: TEMPLATES.map((t) => ({
116
+ value: t,
117
+ label: t,
118
+ hint: t === "minimal" ? "Basic routing and HTML" : undefined,
119
+ })),
120
+ initialValue: "minimal",
121
+ });
122
+ if (p.isCancel(template)) {
123
+ p.cancel("Cancelled");
124
+ process.exit(0);
125
+ }
126
+
127
+ // Target directory
128
+ const targetDir = resolve(process.cwd(), projectName);
129
+ if (existsSync(targetDir)) {
130
+ p.cancel(`Directory ${projectName} already exists`);
131
+ process.exit(1);
132
+ }
133
+
134
+ // Create project
135
+ const s = p.spinner();
136
+ s.start("Creating project...");
137
+ await mkdir(targetDir, { recursive: true });
138
+ await writeTemplate(targetDir, template as Template, projectName);
139
+ s.stop("Project created");
140
+
141
+ // Install dependencies
142
+ const installDeps = await p.confirm({
143
+ message: "Install dependencies?",
144
+ initialValue: true,
145
+ });
146
+ if (p.isCancel(installDeps)) {
147
+ p.cancel("Cancelled");
148
+ process.exit(0);
149
+ }
150
+
151
+ if (installDeps) {
152
+ const s2 = p.spinner();
153
+ s2.start("Installing @space packages...");
154
+ install(
155
+ targetDir,
156
+ pm as PackageManager,
157
+ ["@spacefn/html", "@spacefn/server", "@spacefn/datastar"],
158
+ ["wrangler", "oxlint", "oxfmt", "vite", "vitest"],
159
+ );
160
+ s2.stop("Dependencies installed");
161
+ }
162
+
163
+ // Done
164
+ p.outro(pc.green("Done!"));
165
+ p.note(`cd ${projectName}\n${pm} dev`, "Next steps");
166
+ },
167
+ });
168
+
169
+ runMain(main);
@@ -0,0 +1,8 @@
1
+ import { defineConfig } from "oxfmt";
2
+
3
+ export default defineConfig({
4
+ sortImports: true,
5
+ sortPackageJson: true,
6
+ sortTailwindcss: true,
7
+ trailingComma: "all",
8
+ });
@@ -0,0 +1,8 @@
1
+ import { defineConfig } from "oxlint";
2
+
3
+ export default defineConfig({
4
+ rules: {
5
+ "no-unused-vars": "error",
6
+ "typescript/no-deprecated": "error",
7
+ },
8
+ });
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "{{app_name}}",
3
+ "version": "0.0.1",
4
+ "type": "module",
5
+ "scripts": {
6
+ "dev": "spacefn dev",
7
+ "build": "spacefn build",
8
+ "test": "vitest run",
9
+ "fmt": "oxfmt -c configs/oxfmt.ts",
10
+ "fmt:check": "oxfmt -c configs/oxfmt.ts --check",
11
+ "lint": "oxlint -c configs/oxlint.ts",
12
+ "lint:fix": "oxlint -c configs/oxlint.ts --fix"
13
+ },
14
+ "devDependencies": {
15
+ "@spacefn/server": "latest",
16
+ "vitest": "^3.0.0"
17
+ }
18
+ }
@@ -0,0 +1,6 @@
1
+ import { createServer } from "@spacefn/server";
2
+
3
+ import middlewares from "#space/middlewares";
4
+ import routes from "#space/routes";
5
+
6
+ export default createServer({ routes, middlewares });
@@ -0,0 +1,9 @@
1
+ import { defineComponent, h } from "@spacefn/html";
2
+
3
+ type Props = {
4
+ msg: string;
5
+ };
6
+
7
+ export default defineComponent<Props>((props) => {
8
+ return h.main({}, h.h1({}, props.msg), h.p({}, "Welcome to your Space app"));
9
+ });
@@ -0,0 +1,5 @@
1
+ import { defineLoader } from "@spacefn/server";
2
+
3
+ export const loader = defineLoader(async (_req) => {
4
+ return { msg: "Hello World!" };
5
+ });
@@ -0,0 +1,22 @@
1
+ import { createTestHandler } from "@spacefn/server/test";
2
+ import { describe, it, expect } from "vitest";
3
+
4
+ import Page from "./index.page";
5
+ import { loader } from "./index.server";
6
+
7
+ const handler = createTestHandler({ loader, page: Page });
8
+
9
+ describe("Home page", () => {
10
+ it("renders with loader data", async () => {
11
+ const res = await handler(new Request("http://localhost/"));
12
+ const html = await res.text();
13
+
14
+ expect(res.status).toBe(200);
15
+ expect(html).toContain("Hello World!");
16
+ });
17
+
18
+ it("returns HTML content type", async () => {
19
+ const res = await handler(new Request("http://localhost/"));
20
+ expect(res.headers.get("Content-Type")).toBe("text/html");
21
+ });
22
+ });
@@ -0,0 +1,14 @@
1
+ import { h, render } from "@spacefn/html";
2
+
3
+ export default function () {
4
+ return new Response(
5
+ render(
6
+ h.html(
7
+ {},
8
+ h.head({}, h.title({}, "Home")),
9
+ h.body({}, h.main({}, h.h1({}, "Hello from Space"), h.p({}, "Your app is running."))),
10
+ ),
11
+ ),
12
+ { headers: { "Content-Type": "text/html" } },
13
+ );
14
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "strict": true,
7
+ "esModuleInterop": true,
8
+ "skipLibCheck": true,
9
+ "paths": {
10
+ "#space/*": [".space/*"]
11
+ }
12
+ },
13
+ "include": ["src"]
14
+ }
@@ -0,0 +1,9 @@
1
+ import { space } from "@spacefn/server/vite";
2
+ import { defineConfig } from "vite";
3
+
4
+ export default defineConfig({
5
+ resolve: {
6
+ tsconfigPaths: true,
7
+ },
8
+ plugins: [space()],
9
+ });
@@ -0,0 +1,7 @@
1
+ import { defineConfig } from "vitest/config";
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ include: ["src/**/*.test.ts"],
6
+ },
7
+ });
@@ -0,0 +1 @@
1
+ name = "{{app_name}}"
package/tsconfig.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "strict": true,
7
+ "esModuleInterop": true,
8
+ "skipLibCheck": true,
9
+ "declaration": true,
10
+ "outDir": "./dist",
11
+ "types": ["node"]
12
+ },
13
+ "include": ["src"],
14
+ "exclude": ["src/templates"]
15
+ }
package/tsup.config.ts ADDED
@@ -0,0 +1,8 @@
1
+ import { defineConfig } from "tsup";
2
+
3
+ export default defineConfig({
4
+ entry: ["src/index.ts"],
5
+ format: ["esm"],
6
+ dts: true,
7
+ clean: true,
8
+ });