create-shipkit 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 (3) hide show
  1. package/README.md +50 -0
  2. package/index.js +162 -0
  3. package/package.json +31 -0
package/README.md ADDED
@@ -0,0 +1,50 @@
1
+ # create-shipkit
2
+
3
+ Scaffold the [Skippie hackathon starter](https://github.com/S-kkipie/hackaton-starter)
4
+ — Next 16 + Elysia + Better Auth + Drizzle/Postgres with a `Project` CRUD
5
+ reference domain (server-driven data-table + TanStack Form).
6
+
7
+ ## Usage
8
+
9
+ ```bash
10
+ npx create-shipkit my-app
11
+ # or
12
+ npm create shipkit my-app
13
+ # or
14
+ pnpm create shipkit my-app
15
+ ```
16
+
17
+ Then:
18
+
19
+ ```bash
20
+ cd my-app
21
+ pnpm install
22
+ cp .env.example .env # set DATABASE_URL, BETTER_AUTH_SECRET, NEXT_PUBLIC_APP_URL
23
+ pnpm db:migrate
24
+ pnpm dev
25
+ ```
26
+
27
+ `BETTER_AUTH_SECRET`: `openssl rand -base64 32`. `DATABASE_URL`: any Postgres
28
+ (Supabase, Neon, local).
29
+
30
+ ## What it does
31
+
32
+ - Shallow-clones the template repo (`git clone --depth 1`).
33
+ - Strips the scaffolder + internal docs (`cli/`, `docs/superpowers/`); keeps
34
+ `docs/code-review/` conventions.
35
+ - Renames the project in `package.json`.
36
+ - Re-initializes git with a fresh initial commit.
37
+
38
+ Requires `git` and Node ≥ 18. **Zero runtime dependencies** (instant `npx`).
39
+
40
+ ## Publishing (maintainer)
41
+
42
+ ```bash
43
+ cd cli/create-shipkit
44
+ npm login
45
+ npm publish --access public
46
+ ```
47
+
48
+ The npm package name is `create-shipkit`, which is what enables
49
+ `npm create shipkit` / `npx create-shipkit`. Bump `version` in
50
+ `package.json` before each publish.
package/index.js ADDED
@@ -0,0 +1,162 @@
1
+ #!/usr/bin/env node
2
+ // create-shipkit — scaffold the Skippie hackathon starter.
3
+ // Zero runtime dependencies: shallow-clones the template repo, strips the
4
+ // scaffolder + internal docs, renames the project, and re-inits git.
5
+
6
+ import { execFileSync } from "node:child_process";
7
+ import fs from "node:fs";
8
+ import path from "node:path";
9
+ import { stdin, stdout } from "node:process";
10
+ import readline from "node:readline/promises";
11
+
12
+ const REPO = "https://github.com/S-kkipie/hackaton-starter.git";
13
+
14
+ // Paths (relative to the scaffolded project) that are template-internal and
15
+ // must not ship in a user's project.
16
+ const STRIP = [".git", "cli", "docs/superpowers", ".superpowers"];
17
+
18
+ const c = {
19
+ reset: "\x1b[0m",
20
+ bold: "\x1b[1m",
21
+ dim: "\x1b[2m",
22
+ green: "\x1b[32m",
23
+ cyan: "\x1b[36m",
24
+ red: "\x1b[31m",
25
+ yellow: "\x1b[33m",
26
+ };
27
+ const color = (code, s) => `${code}${s}${c.reset}`;
28
+
29
+ function die(msg) {
30
+ console.error(`\n${color(c.red, "✖")} ${msg}\n`);
31
+ process.exit(1);
32
+ }
33
+
34
+ function toPackageName(name) {
35
+ return (
36
+ name
37
+ .trim()
38
+ .toLowerCase()
39
+ .replace(/[^a-z0-9-~]+/g, "-")
40
+ .replace(/^-+|-+$/g, "") || "hackaton-app"
41
+ );
42
+ }
43
+
44
+ async function resolveTargetDir() {
45
+ const arg = process.argv[2];
46
+ if (arg) return arg;
47
+ if (!stdin.isTTY) die("Usage: create-shipkit <project-directory>");
48
+ const rl = readline.createInterface({ input: stdin, output: stdout });
49
+ const answer = await rl.question(
50
+ `${color(c.cyan, "?")} Project directory: ${color(c.dim, "(my-hackaton-app)")} `,
51
+ );
52
+ rl.close();
53
+ return answer.trim() || "my-hackaton-app";
54
+ }
55
+
56
+ function assertGit() {
57
+ try {
58
+ execFileSync("git", ["--version"], { stdio: "ignore" });
59
+ } catch {
60
+ die("git is required but was not found on your PATH.");
61
+ }
62
+ }
63
+
64
+ function main() {
65
+ return resolveTargetDir().then((targetArg) => {
66
+ const targetDir = path.resolve(process.cwd(), targetArg);
67
+ const appName = toPackageName(path.basename(targetDir));
68
+
69
+ if (fs.existsSync(targetDir) && fs.readdirSync(targetDir).length > 0) {
70
+ die(
71
+ `Target directory "${targetArg}" already exists and is not empty.`,
72
+ );
73
+ }
74
+
75
+ assertGit();
76
+
77
+ console.log(
78
+ `\n${color(c.bold, "create-shipkit")} — scaffolding ${color(c.cyan, appName)}\n`,
79
+ );
80
+
81
+ // 1. Shallow clone the template.
82
+ console.log(`${color(c.dim, "›")} Downloading template…`);
83
+ try {
84
+ execFileSync(
85
+ "git",
86
+ ["clone", "--depth", "1", "--quiet", REPO, targetDir],
87
+ { stdio: "inherit" },
88
+ );
89
+ } catch {
90
+ die(
91
+ "Failed to clone the template. Check your network and that the repo is reachable.",
92
+ );
93
+ }
94
+
95
+ // 2. Strip template-internal files.
96
+ for (const rel of STRIP) {
97
+ fs.rmSync(path.join(targetDir, rel), {
98
+ recursive: true,
99
+ force: true,
100
+ });
101
+ }
102
+
103
+ // 3. Rename the project in package.json.
104
+ const pkgPath = path.join(targetDir, "package.json");
105
+ try {
106
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
107
+ pkg.name = appName;
108
+ pkg.version = "0.1.0";
109
+ fs.writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 4)}\n`);
110
+ } catch {
111
+ console.warn(
112
+ `${color(c.yellow, "!")} Could not rewrite package.json name — do it manually.`,
113
+ );
114
+ }
115
+
116
+ // 4. Fresh git history + initial commit (best-effort: a missing git
117
+ // identity just leaves the repo uncommitted for the user to commit).
118
+ try {
119
+ execFileSync("git", ["init", "--quiet"], {
120
+ cwd: targetDir,
121
+ stdio: "ignore",
122
+ });
123
+ execFileSync("git", ["add", "-A"], {
124
+ cwd: targetDir,
125
+ stdio: "ignore",
126
+ });
127
+ execFileSync(
128
+ "git",
129
+ [
130
+ "commit",
131
+ "--quiet",
132
+ "-m",
133
+ "Initial commit from create-shipkit",
134
+ ],
135
+ { cwd: targetDir, stdio: "ignore" },
136
+ );
137
+ } catch {
138
+ // non-fatal — user can commit themselves
139
+ }
140
+
141
+ // 5. Next steps.
142
+ const rel = path.relative(process.cwd(), targetDir) || ".";
143
+ console.log(
144
+ `\n${color(c.green, "✔")} Created ${color(c.cyan, appName)} at ${rel}\n`,
145
+ );
146
+ console.log(color(c.bold, "Next steps:"));
147
+ console.log(` ${color(c.cyan, `cd ${rel}`)}`);
148
+ console.log(` ${color(c.cyan, "pnpm install")}`);
149
+ console.log(
150
+ ` ${color(c.cyan, "cp .env.example .env")} ${color(c.dim, "# then set DATABASE_URL, BETTER_AUTH_SECRET, NEXT_PUBLIC_APP_URL")}`,
151
+ );
152
+ console.log(
153
+ ` ${color(c.dim, "#")} BETTER_AUTH_SECRET: ${color(c.cyan, "openssl rand -base64 32")}`,
154
+ );
155
+ console.log(
156
+ ` ${color(c.cyan, "pnpm db:migrate")} ${color(c.dim, "# apply migrations")}`,
157
+ );
158
+ console.log(` ${color(c.cyan, "pnpm dev")}\n`);
159
+ });
160
+ }
161
+
162
+ main().catch((err) => die(err?.message ?? String(err)));
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "create-shipkit",
3
+ "version": "0.1.0",
4
+ "description": "Scaffold a Skippie hackathon starter — Next 16 + Elysia + Better Auth + Drizzle/Postgres, with a Project CRUD reference domain.",
5
+ "type": "module",
6
+ "bin": {
7
+ "create-shipkit": "index.js"
8
+ },
9
+ "files": [
10
+ "index.js"
11
+ ],
12
+ "engines": {
13
+ "node": ">=18"
14
+ },
15
+ "keywords": [
16
+ "create",
17
+ "starter",
18
+ "scaffold",
19
+ "nextjs",
20
+ "elysia",
21
+ "better-auth",
22
+ "drizzle",
23
+ "hackathon"
24
+ ],
25
+ "license": "MIT",
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "https://github.com/S-kkipie/hackaton-starter.git",
29
+ "directory": "cli/create-shipkit"
30
+ }
31
+ }