@pylonsync/create-pylon 0.3.277 → 0.3.279
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/create-pylon.js +89 -39
- package/package.json +4 -1
package/bin/create-pylon.js
CHANGED
|
@@ -42,7 +42,12 @@ import { stdin, stdout, exit, argv, cwd } from "node:process";
|
|
|
42
42
|
// ---------------------------------------------------------------------------
|
|
43
43
|
|
|
44
44
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
45
|
-
|
|
45
|
+
// PYLON_CREATE_TEMPLATES_DIR overrides the template source dir. Only used by
|
|
46
|
+
// the scaffolder's own tests (to point at a fixture / empty dir and exercise
|
|
47
|
+
// the missing-template guard); undocumented + irrelevant in normal use.
|
|
48
|
+
const TEMPLATES = process.env.PYLON_CREATE_TEMPLATES_DIR
|
|
49
|
+
? resolve(process.env.PYLON_CREATE_TEMPLATES_DIR)
|
|
50
|
+
: resolve(HERE, "..", "templates");
|
|
46
51
|
|
|
47
52
|
// ---------------------------------------------------------------------------
|
|
48
53
|
// Version pin — every generated dep references this version of @pylonsync/*.
|
|
@@ -257,22 +262,35 @@ Examples:
|
|
|
257
262
|
}
|
|
258
263
|
|
|
259
264
|
const rl = createInterface({ input: stdin, output: stdout });
|
|
265
|
+
// Non-interactive stdin (CI, piped, `| npm create …`): never block on a prompt.
|
|
266
|
+
// Fall back to the same defaults the interactive path uses. The skill-install
|
|
267
|
+
// below already gates on `stdin.isTTY`; the INPUT prompts must too — without
|
|
268
|
+
// this, `npm create @pylonsync/pylon my-app` (or any partial-flag invocation)
|
|
269
|
+
// HANGS forever in CI waiting on input that never comes.
|
|
270
|
+
const isInteractive = !!stdin.isTTY;
|
|
260
271
|
if (!projectName) {
|
|
261
|
-
projectName =
|
|
272
|
+
projectName =
|
|
273
|
+
(isInteractive ? (await rl.question("Project name: ")).trim() : "") ||
|
|
274
|
+
"my-pylon-app";
|
|
262
275
|
}
|
|
263
276
|
if (!flags.template) {
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
277
|
+
if (isInteractive) {
|
|
278
|
+
const lines = Object.entries(TEMPLATE_REGISTRY)
|
|
279
|
+
.map(([k, v]) => ` ${k.padEnd(10)} ${v.blurb}`)
|
|
280
|
+
.join("\n");
|
|
281
|
+
process.stdout.write(`\n${lines}\n`);
|
|
282
|
+
const ans = (
|
|
283
|
+
await rl.question(
|
|
284
|
+
`Template (${TEMPLATES_AVAILABLE.join(", ")}) [default]: `,
|
|
285
|
+
)
|
|
271
286
|
)
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
.
|
|
275
|
-
|
|
287
|
+
.trim()
|
|
288
|
+
.toLowerCase();
|
|
289
|
+
flags.template = TEMPLATES_AVAILABLE.includes(ans) ? ans : "default";
|
|
290
|
+
} else {
|
|
291
|
+
console.log("Non-interactive stdin — using --template default.");
|
|
292
|
+
flags.template = "default";
|
|
293
|
+
}
|
|
276
294
|
}
|
|
277
295
|
// `ssr` was the original name of the default template; keep it working as a
|
|
278
296
|
// quiet alias so older `--template ssr` invocations don't break.
|
|
@@ -286,32 +304,44 @@ if (flags.template === "saas") flags.template = "default";
|
|
|
286
304
|
const isUnified = TEMPLATE_REGISTRY[flags.template]?.unified === true;
|
|
287
305
|
if (!isUnified && !flags.platforms) {
|
|
288
306
|
const supported = TEMPLATE_REGISTRY[flags.template].platforms.join(", ");
|
|
289
|
-
const ans =
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
307
|
+
const ans = isInteractive
|
|
308
|
+
? (
|
|
309
|
+
await rl.question(
|
|
310
|
+
`Platforms for ${flags.template} (${supported}, comma-separated) [web]: `,
|
|
311
|
+
)
|
|
312
|
+
).trim()
|
|
313
|
+
: "";
|
|
294
314
|
flags.platforms = ans || "web";
|
|
295
315
|
}
|
|
296
316
|
if (!flags.pm) {
|
|
297
317
|
const detected = detectPackageManager();
|
|
298
318
|
const def = detected ?? "bun";
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
319
|
+
if (isInteractive) {
|
|
320
|
+
const choice = (
|
|
321
|
+
await rl.question(`Package manager (bun, pnpm, yarn, npm) [${def}]: `)
|
|
322
|
+
)
|
|
323
|
+
.trim()
|
|
324
|
+
.toLowerCase();
|
|
325
|
+
flags.pm = ["bun", "pnpm", "yarn", "npm"].includes(choice) ? choice : def;
|
|
326
|
+
} else {
|
|
327
|
+
flags.pm = def;
|
|
328
|
+
}
|
|
305
329
|
}
|
|
306
330
|
if (flags.skill === undefined) {
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
331
|
+
if (isInteractive) {
|
|
332
|
+
const ans = (
|
|
333
|
+
await rl.question(
|
|
334
|
+
"Add the Pylon skill to your coding agent (Claude Code / Codex / Cursor)? [Y/n]: ",
|
|
335
|
+
)
|
|
310
336
|
)
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
.
|
|
314
|
-
|
|
337
|
+
.trim()
|
|
338
|
+
.toLowerCase();
|
|
339
|
+
flags.skill = ans !== "n" && ans !== "no";
|
|
340
|
+
} else {
|
|
341
|
+
// Non-interactive: can't prompt. The actual install is TTY-gated below,
|
|
342
|
+
// so this only controls whether the footer prints the one-liner hint.
|
|
343
|
+
flags.skill = true;
|
|
344
|
+
}
|
|
315
345
|
}
|
|
316
346
|
rl.close();
|
|
317
347
|
|
|
@@ -462,6 +492,26 @@ function copyTemplate(srcSubpath, destSubpath = "") {
|
|
|
462
492
|
return true;
|
|
463
493
|
}
|
|
464
494
|
|
|
495
|
+
// Like copyTemplate but FATAL when the source is missing. A required
|
|
496
|
+
// template dir that isn't on disk means a corrupt/partial create-pylon
|
|
497
|
+
// install (or a published tarball that dropped files) — historically this
|
|
498
|
+
// scaffolded an EMPTY project and STILL printed "✓ Created", sending the
|
|
499
|
+
// user to a dead `pylon dev` with no clue why. Fail loud + actionable
|
|
500
|
+
// instead. (The @pylonsync/client publish-drop that broke the default
|
|
501
|
+
// scaffold is exactly this failure mode.)
|
|
502
|
+
function mustCopy(srcSubpath, destSubpath = "", label = srcSubpath) {
|
|
503
|
+
if (!copyTemplate(srcSubpath, destSubpath)) {
|
|
504
|
+
console.error(
|
|
505
|
+
`\nError: template files for "${label}" are missing from this install\n` +
|
|
506
|
+
` (expected ${join(TEMPLATES, srcSubpath)}).\n` +
|
|
507
|
+
` This usually means a corrupt or partial create-pylon install.\n` +
|
|
508
|
+
` Re-run with @latest: npm create @pylonsync/pylon@latest\n` +
|
|
509
|
+
` or report it: https://github.com/pylonsync/pylon/issues\n`,
|
|
510
|
+
);
|
|
511
|
+
exit(1);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
465
515
|
// ---------------------------------------------------------------------------
|
|
466
516
|
// Apply templates in order:
|
|
467
517
|
// 1. _root — gitignore, env.example, README
|
|
@@ -475,24 +525,24 @@ if (isUnified) {
|
|
|
475
525
|
// Single unified app: app.ts + app/ routes + functions/, served by
|
|
476
526
|
// `pylon dev` (frontend + API, one port). The template ships its own
|
|
477
527
|
// package.json — no monorepo root, no turbo, no workspaces.
|
|
478
|
-
|
|
528
|
+
mustCopy(flags.template);
|
|
479
529
|
} else {
|
|
480
|
-
|
|
481
|
-
|
|
530
|
+
mustCopy("_root");
|
|
531
|
+
mustCopy(`backend/${flags.template}`);
|
|
482
532
|
|
|
483
533
|
// `web` (Next.js) and `vite` are alternative web-frontend toolchains;
|
|
484
534
|
// the mutex check above guarantees at most one of them is set. Either
|
|
485
535
|
// way we also pull in packages/ui so the shared primitives are present.
|
|
486
536
|
if (platforms.includes("web")) {
|
|
487
|
-
|
|
488
|
-
|
|
537
|
+
mustCopy("ui");
|
|
538
|
+
mustCopy(`web/${flags.template}`);
|
|
489
539
|
}
|
|
490
540
|
if (platforms.includes("vite")) {
|
|
491
|
-
|
|
492
|
-
|
|
541
|
+
mustCopy("ui");
|
|
542
|
+
mustCopy(`vite/${flags.template}`);
|
|
493
543
|
}
|
|
494
544
|
for (const p of ["ios", "mac", "expo"]) {
|
|
495
|
-
if (platforms.includes(p))
|
|
545
|
+
if (platforms.includes(p)) mustCopy(`${p}/${flags.template}`);
|
|
496
546
|
}
|
|
497
547
|
}
|
|
498
548
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pylonsync/create-pylon",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.279",
|
|
4
4
|
"description": "Scaffold a new Pylon app — realtime backend + web/mobile/expo frontends in one command. Run via `npm create @pylonsync/pylon@latest`.",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -9,6 +9,9 @@
|
|
|
9
9
|
"bin": {
|
|
10
10
|
"create-pylon": "./bin/create-pylon.js"
|
|
11
11
|
},
|
|
12
|
+
"scripts": {
|
|
13
|
+
"test": "node --test"
|
|
14
|
+
},
|
|
12
15
|
"files": [
|
|
13
16
|
"bin",
|
|
14
17
|
"templates"
|