create-volt 0.1.0 → 0.3.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 +25 -6
  2. package/index.js +104 -6
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -27,12 +27,31 @@ Edit `public/app.js` and save — the page hot-reloads itself.
27
27
 
28
28
  ## Options
29
29
 
30
- | Flag | Effect |
31
- | ---------------- | ------------------------------------------------------- |
32
- | `--skip-install` | Don't run the install step (scaffold files only) |
33
- | `--force` | Scaffold into an existing non-empty directory |
34
- | `-h`, `--help` | Show help |
35
- | `-v`, `--version`| Print the create-volt version |
30
+ | Flag | Effect |
31
+ | ----------------- | ------------------------------------------------------- |
32
+ | `--port <number>` | Dev port for the app (default: derived from today's date)|
33
+ | `--skip-install` | Don't run the install step (scaffold files only) |
34
+ | `--no-git` | Don't initialize a git repository |
35
+ | `--dry-run` | Show what would be created without writing anything |
36
+ | `--force` | Scaffold into an existing non-empty directory |
37
+ | `-h`, `--help` | Show help |
38
+ | `-v`, `--version` | Print the create-volt version |
39
+
40
+ By default the new project is initialized as a git repository with one initial
41
+ commit (skip with `--no-git`).
42
+
43
+ ### Dev port
44
+
45
+ Each app's dev port is baked into its `server.js`. By default it's derived from
46
+ **today's date** — two-digit year + month + two-digit day (e.g. `2026-06-28` →
47
+ `26628`) — so apps created on different days never collide. Scaffolding more than
48
+ one app on the same day? Give them distinct ports with `--port`:
49
+
50
+ ```bash
51
+ npm create volt@latest api-app -- --port 26630
52
+ ```
53
+
54
+ The runtime `PORT` env var still overrides it at launch.
36
55
 
37
56
  The installer auto-detects whichever package manager invoked it
38
57
  (npm / pnpm / yarn / bun) and uses it for `install`.
package/index.js CHANGED
@@ -36,7 +36,10 @@ ${bold("Usage")}
36
36
  npx create-volt <project-directory> [options]
37
37
 
38
38
  ${bold("Options")}
39
+ --port <number> Dev port for the app (default: derived from today's date)
39
40
  --skip-install Don't run the package manager install step
41
+ --no-git Don't initialize a git repository
42
+ --dry-run Show what would be created without writing anything
40
43
  --force Scaffold into an existing non-empty directory
41
44
  -h, --help Show this help
42
45
  -v, --version Show the create-volt version
@@ -46,10 +49,18 @@ ${bold("Example")}
46
49
  cd my-app && npm run dev
47
50
  `;
48
51
 
49
- // --- arg parsing ---
52
+ // --- arg parsing (supports `--port <n>` and `--port=<n>`) ---
50
53
  const argv = process.argv.slice(2);
51
- const flags = new Set(argv.filter((a) => a.startsWith("-")));
52
- const positionals = argv.filter((a) => !a.startsWith("-"));
54
+ const flags = new Set();
55
+ const positionals = [];
56
+ let portArg = null;
57
+ for (let i = 0; i < argv.length; i++) {
58
+ const a = argv[i];
59
+ if (a === "--port") portArg = argv[++i];
60
+ else if (a.startsWith("--port=")) portArg = a.slice("--port=".length);
61
+ else if (a.startsWith("-")) flags.add(a);
62
+ else positionals.push(a);
63
+ }
53
64
 
54
65
  if (flags.has("-h") || flags.has("--help")) {
55
66
  console.log(HELP);
@@ -62,6 +73,30 @@ if (flags.has("-v") || flags.has("--version")) {
62
73
 
63
74
  const skipInstall = flags.has("--skip-install");
64
75
  const force = flags.has("--force");
76
+ const dryRun = flags.has("--dry-run");
77
+ const noGit = flags.has("--no-git");
78
+
79
+ // Resolve the dev port: --port wins, else derive it from today's date as
80
+ // two-digit-year + month (no leading zero) + two-digit-day (house convention),
81
+ // so apps scaffolded on different days don't collide on the same port.
82
+ function datePort(d) {
83
+ const yy = String(d.getFullYear()).slice(-2);
84
+ const mm = String(d.getMonth() + 1);
85
+ const dd = String(d.getDate()).padStart(2, "0");
86
+ return Number(`${yy}${mm}${dd}`);
87
+ }
88
+ let port;
89
+ if (portArg != null) {
90
+ port = Number(portArg);
91
+ if (!Number.isInteger(port) || port < 1 || port > 65535) {
92
+ die(`Invalid --port "${portArg}" — use a whole number between 1 and 65535.`);
93
+ }
94
+ } else {
95
+ port = datePort(new Date());
96
+ if (port > 65535) {
97
+ die(`The date-derived port (${port}) is above 65535 — pass --port <1-65535>.`);
98
+ }
99
+ }
65
100
 
66
101
  const rawName = positionals[0];
67
102
  if (!rawName) die(`Please specify a project directory:\n ${cyan("npm create volt@latest")} ${green("<project-directory>")}`);
@@ -79,16 +114,43 @@ const targetDir = path.resolve(process.cwd(), projectName);
79
114
  const appName = path.basename(targetDir);
80
115
  const templateDir = path.join(__dirname, "template");
81
116
 
117
+ // List every file in the template, relative to its root (for dry-run preview).
118
+ function listTemplateFiles(dir, base = dir) {
119
+ const out = [];
120
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
121
+ const full = path.join(dir, entry.name);
122
+ if (entry.isDirectory()) out.push(...listTemplateFiles(full, base));
123
+ else out.push(path.relative(base, full));
124
+ }
125
+ return out;
126
+ }
127
+
82
128
  // --- target directory checks ---
83
129
  if (fs.existsSync(targetDir)) {
84
130
  const existing = fs.readdirSync(targetDir).filter((f) => f !== ".git");
85
131
  if (existing.length && !force) {
86
132
  die(`Directory ${green(projectName)} already exists and is not empty.\n Pass ${cyan("--force")} to scaffold into it anyway.`);
87
133
  }
88
- } else {
89
- fs.mkdirSync(targetDir, { recursive: true });
90
134
  }
91
135
 
136
+ // --- dry run: print the plan and exit without writing anything ---
137
+ if (dryRun) {
138
+ console.log(`\n${bold("⚡ Dry run")} — would create a Volt app in ${cyan(targetDir)}\n`);
139
+ console.log("Would write:");
140
+ for (const f of listTemplateFiles(templateDir).sort()) {
141
+ // the shipped "gitignore" is renamed to ".gitignore" on scaffold
142
+ console.log(" " + dim(f === "gitignore" ? ".gitignore" : f));
143
+ }
144
+ console.log("");
145
+ console.log(dim(`Would ${skipInstall ? "skip dependency install" : "install dependencies"}.`));
146
+ console.log(dim(`Would ${noGit ? "skip git init" : "initialize a git repository with an initial commit"}.`));
147
+ console.log(dim(`Would set the dev port to ${port}.`));
148
+ console.log(`\n${green("✔")} Dry run complete — nothing was written.\n`);
149
+ process.exit(0);
150
+ }
151
+
152
+ if (!fs.existsSync(targetDir)) fs.mkdirSync(targetDir, { recursive: true });
153
+
92
154
  console.log(`\n${bold("⚡ Creating a new Volt app in")} ${cyan(targetDir)}\n`);
93
155
 
94
156
  // --- copy the template tree (recursive, cross-platform) ---
@@ -107,6 +169,17 @@ const appPkg = JSON.parse(fs.readFileSync(appPkgPath, "utf8"));
107
169
  appPkg.name = appName;
108
170
  fs.writeFileSync(appPkgPath, JSON.stringify(appPkg, null, 2) + "\n");
109
171
 
172
+ // --- stamp the chosen dev port into server.js + README ---
173
+ const serverPath = path.join(targetDir, "server.js");
174
+ fs.writeFileSync(
175
+ serverPath,
176
+ fs.readFileSync(serverPath, "utf8").replace(/(Number\(process\.env\.PORT\)\s*\|\|\s*)\d+/, `$1${port}`),
177
+ );
178
+ const appReadme = path.join(targetDir, "README.md");
179
+ if (fs.existsSync(appReadme)) {
180
+ fs.writeFileSync(appReadme, fs.readFileSync(appReadme, "utf8").replace(/localhost:\d+/g, `localhost:${port}`));
181
+ }
182
+
110
183
  const created = [
111
184
  "public/volt.js — the Volt library (no build step)",
112
185
  "public/app.js — your app (Counter + Todos demo)",
@@ -143,6 +216,31 @@ if (!skipInstall) {
143
216
  }
144
217
  }
145
218
 
219
+ // --- initialize a git repository (unless skipped or already inside one) ---
220
+ if (!noGit) {
221
+ const git = (gitArgs) =>
222
+ spawnSync("git", gitArgs, {
223
+ cwd: targetDir,
224
+ stdio: "ignore",
225
+ shell: process.platform === "win32",
226
+ });
227
+ const gitAvailable = git(["--version"]).status === 0;
228
+ const insideRepo = gitAvailable && git(["rev-parse", "--is-inside-work-tree"]).status === 0;
229
+ if (gitAvailable && !insideRepo) {
230
+ const initOk = git(["init", "-b", "main"]).status === 0 || git(["init"]).status === 0;
231
+ if (initOk) {
232
+ git(["add", "-A"]);
233
+ const committed = git(["commit", "-m", "Initial commit from create-volt"]).status === 0;
234
+ console.log(
235
+ green("✔") +
236
+ (committed
237
+ ? " Initialized a git repository (1 commit)\n"
238
+ : " Initialized a git repository (commit skipped — set git user.name/email)\n"),
239
+ );
240
+ }
241
+ }
242
+ }
243
+
146
244
  // --- next steps ---
147
245
  const runCmd = pm === "npm" ? "npm run dev" : `${pm} dev`;
148
246
  const installCmd = pm === "yarn" ? "yarn" : `${pm} install`;
@@ -150,4 +248,4 @@ console.log(`\n${green("✔")} ${bold("Done!")} Next steps:\n`);
150
248
  console.log(` ${cyan("cd")} ${projectName}`);
151
249
  if (!installed) console.log(` ${cyan(installCmd)}`);
152
250
  console.log(` ${cyan(runCmd)}`);
153
- console.log(`\nThen open ${cyan("http://localhost:26628")} and edit ${bold("public/app.js")}.\n`);
251
+ console.log(`\nThen open ${cyan("http://localhost:" + port)} and edit ${bold("public/app.js")}.\n`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-volt",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Scaffold a new Volt app — no-build, signals-based UI with Socket.io hot reload.",
5
5
  "type": "module",
6
6
  "bin": {