create-better-fullstack 1.4.15 → 1.5.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.
- package/README.md +1 -0
- package/dist/cli.mjs +1 -1
- package/dist/index.d.mts +7 -0
- package/dist/index.mjs +1 -1
- package/dist/{src-D-YGZXlq.mjs → src-DaUU4ThO.mjs} +254 -14
- package/package.json +32 -7
package/README.md
CHANGED
|
@@ -38,6 +38,7 @@ Configure your stack visually — pick every option from a UI, preview your choi
|
|
|
38
38
|
--yolo # Scaffold a random stack — good for exploring
|
|
39
39
|
--template <name> # Use a preset (t3, mern, pern, uniwind)
|
|
40
40
|
--ecosystem <lang> # Start in rust, python, or go mode
|
|
41
|
+
--version-channel # Dependency channel: stable, latest, beta
|
|
41
42
|
--no-git # Skip git initialization
|
|
42
43
|
--no-install # Skip dependency installation
|
|
43
44
|
--package-manager # Package manager (bun, pnpm, npm, yarn)
|
package/dist/cli.mjs
CHANGED
package/dist/index.d.mts
CHANGED
|
@@ -278,6 +278,11 @@ declare const router: {
|
|
|
278
278
|
yarn: "yarn";
|
|
279
279
|
}>>;
|
|
280
280
|
install: z.ZodOptional<z.ZodBoolean>;
|
|
281
|
+
versionChannel: z.ZodOptional<z.ZodEnum<{
|
|
282
|
+
stable: "stable";
|
|
283
|
+
latest: "latest";
|
|
284
|
+
beta: "beta";
|
|
285
|
+
}>>;
|
|
281
286
|
dbSetup: z.ZodOptional<z.ZodEnum<{
|
|
282
287
|
none: "none";
|
|
283
288
|
turso: "turso";
|
|
@@ -543,6 +548,7 @@ declare const router: {
|
|
|
543
548
|
payments: "none" | "polar" | "stripe" | "lemon-squeezy" | "paddle" | "dodo";
|
|
544
549
|
git: boolean;
|
|
545
550
|
packageManager: "bun" | "npm" | "pnpm" | "yarn";
|
|
551
|
+
versionChannel: "stable" | "latest" | "beta";
|
|
546
552
|
install: boolean;
|
|
547
553
|
dbSetup: "none" | "turso" | "neon" | "prisma-postgres" | "planetscale" | "mongodb-atlas" | "supabase" | "upstash" | "d1" | "docker";
|
|
548
554
|
api: "none" | "trpc" | "orpc" | "ts-rest" | "garph";
|
|
@@ -629,6 +635,7 @@ declare const router: {
|
|
|
629
635
|
payments: "none" | "polar" | "stripe" | "lemon-squeezy" | "paddle" | "dodo";
|
|
630
636
|
git: boolean;
|
|
631
637
|
packageManager: "bun" | "npm" | "pnpm" | "yarn";
|
|
638
|
+
versionChannel: "stable" | "latest" | "beta";
|
|
632
639
|
install: boolean;
|
|
633
640
|
dbSetup: "none" | "turso" | "neon" | "prisma-postgres" | "planetscale" | "mongodb-atlas" | "supabase" | "upstash" | "d1" | "docker";
|
|
634
641
|
api: "none" | "trpc" | "orpc" | "ts-rest" | "garph";
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as builder, c as createVirtual, d as history, f as router, i as add, l as docs, n as TEMPLATE_COUNT, o as create, p as sponsors, r as VirtualFileSystem, s as createBtsCli, t as EMBEDDED_TEMPLATES, u as generateVirtualProject } from "./src-
|
|
2
|
+
import { a as builder, c as createVirtual, d as history, f as router, i as add, l as docs, n as TEMPLATE_COUNT, o as create, p as sponsors, r as VirtualFileSystem, s as createBtsCli, t as EMBEDDED_TEMPLATES, u as generateVirtualProject } from "./src-DaUU4ThO.mjs";
|
|
3
3
|
|
|
4
4
|
export { EMBEDDED_TEMPLATES, TEMPLATE_COUNT, VirtualFileSystem, add, builder, create, createBtsCli, createVirtual, docs, generateVirtualProject, history, router, sponsors };
|
|
@@ -9,7 +9,7 @@ import envPaths from "env-paths";
|
|
|
9
9
|
import fs from "fs-extra";
|
|
10
10
|
import path from "node:path";
|
|
11
11
|
import { fileURLToPath } from "node:url";
|
|
12
|
-
import { ECOSYSTEM_GROUPS, EMBEDDED_TEMPLATES, EMBEDDED_TEMPLATES as EMBEDDED_TEMPLATES$1, TEMPLATE_COUNT, VirtualFileSystem, VirtualFileSystem as VirtualFileSystem$1, checkAllVersions, dependencyVersionMap, generateCliReport, generateVirtualProject, generateVirtualProject as generateVirtualProject$1, listEcosystems, processAddonTemplates, processAddonsDeps } from "@better-fullstack/template-generator";
|
|
12
|
+
import { ECOSYSTEM_GROUPS, EMBEDDED_TEMPLATES, EMBEDDED_TEMPLATES as EMBEDDED_TEMPLATES$1, TEMPLATE_COUNT, VirtualFileSystem, VirtualFileSystem as VirtualFileSystem$1, checkAllVersions, dependencyVersionMap, generateCliReport, generateVirtualProject, generateVirtualProject as generateVirtualProject$1, listEcosystems, processAddonTemplates, processAddonsDeps, validatePreflightConfig } from "@better-fullstack/template-generator";
|
|
13
13
|
import gradient from "gradient-string";
|
|
14
14
|
import path$1 from "path";
|
|
15
15
|
import { writeTreeToFilesystem } from "@better-fullstack/template-generator/fs-writer";
|
|
@@ -67,6 +67,7 @@ const DEFAULT_CONFIG_BASE = {
|
|
|
67
67
|
examples: [],
|
|
68
68
|
git: true,
|
|
69
69
|
install: true,
|
|
70
|
+
versionChannel: "stable",
|
|
70
71
|
dbSetup: "none",
|
|
71
72
|
backend: "hono",
|
|
72
73
|
runtime: "bun",
|
|
@@ -1288,10 +1289,9 @@ const ADDON_GROUPS = {
|
|
|
1288
1289
|
"tauri",
|
|
1289
1290
|
"opentui",
|
|
1290
1291
|
"wxt",
|
|
1291
|
-
"ruler"
|
|
1292
|
-
"msw",
|
|
1293
|
-
"storybook"
|
|
1292
|
+
"ruler"
|
|
1294
1293
|
],
|
|
1294
|
+
Integrations: ["msw", "storybook"],
|
|
1295
1295
|
"AI Agents": ["mcp", "skills"],
|
|
1296
1296
|
TanStack: [
|
|
1297
1297
|
"tanstack-query",
|
|
@@ -1308,6 +1308,7 @@ async function getAddonsChoice(addons, frontends, auth) {
|
|
|
1308
1308
|
Tooling: [],
|
|
1309
1309
|
Documentation: [],
|
|
1310
1310
|
Extensions: [],
|
|
1311
|
+
Integrations: [],
|
|
1311
1312
|
"AI Agents": [],
|
|
1312
1313
|
TanStack: []
|
|
1313
1314
|
};
|
|
@@ -1324,6 +1325,7 @@ async function getAddonsChoice(addons, frontends, auth) {
|
|
|
1324
1325
|
if (ADDON_GROUPS.Tooling.includes(addon)) groupedOptions.Tooling.push(option);
|
|
1325
1326
|
else if (ADDON_GROUPS.Documentation.includes(addon)) groupedOptions.Documentation.push(option);
|
|
1326
1327
|
else if (ADDON_GROUPS.Extensions.includes(addon)) groupedOptions.Extensions.push(option);
|
|
1328
|
+
else if (ADDON_GROUPS.Integrations.includes(addon)) groupedOptions.Integrations.push(option);
|
|
1327
1329
|
else if (ADDON_GROUPS["AI Agents"].includes(addon)) groupedOptions["AI Agents"].push(option);
|
|
1328
1330
|
else if (ADDON_GROUPS.TanStack.includes(addon)) groupedOptions.TanStack.push(option);
|
|
1329
1331
|
}
|
|
@@ -1350,6 +1352,7 @@ async function getAddonsToAdd(frontend, existingAddons = [], auth) {
|
|
|
1350
1352
|
Tooling: [],
|
|
1351
1353
|
Documentation: [],
|
|
1352
1354
|
Extensions: [],
|
|
1355
|
+
Integrations: [],
|
|
1353
1356
|
"AI Agents": [],
|
|
1354
1357
|
TanStack: []
|
|
1355
1358
|
};
|
|
@@ -1365,6 +1368,7 @@ async function getAddonsToAdd(frontend, existingAddons = [], auth) {
|
|
|
1365
1368
|
if (ADDON_GROUPS.Tooling.includes(addon)) groupedOptions.Tooling.push(option);
|
|
1366
1369
|
else if (ADDON_GROUPS.Documentation.includes(addon)) groupedOptions.Documentation.push(option);
|
|
1367
1370
|
else if (ADDON_GROUPS.Extensions.includes(addon)) groupedOptions.Extensions.push(option);
|
|
1371
|
+
else if (ADDON_GROUPS.Integrations.includes(addon)) groupedOptions.Integrations.push(option);
|
|
1368
1372
|
else if (ADDON_GROUPS["AI Agents"].includes(addon)) groupedOptions["AI Agents"].push(option);
|
|
1369
1373
|
else if (ADDON_GROUPS.TanStack.includes(addon)) groupedOptions.TanStack.push(option);
|
|
1370
1374
|
}
|
|
@@ -1413,6 +1417,7 @@ async function writeBtsConfig(projectConfig) {
|
|
|
1413
1417
|
forms: projectConfig.forms,
|
|
1414
1418
|
testing: projectConfig.testing,
|
|
1415
1419
|
packageManager: projectConfig.packageManager,
|
|
1420
|
+
versionChannel: projectConfig.versionChannel,
|
|
1416
1421
|
dbSetup: projectConfig.dbSetup,
|
|
1417
1422
|
api: projectConfig.api,
|
|
1418
1423
|
webDeploy: projectConfig.webDeploy,
|
|
@@ -1472,6 +1477,7 @@ async function writeBtsConfig(projectConfig) {
|
|
|
1472
1477
|
forms: btsConfig.forms,
|
|
1473
1478
|
testing: btsConfig.testing,
|
|
1474
1479
|
packageManager: btsConfig.packageManager,
|
|
1480
|
+
versionChannel: btsConfig.versionChannel,
|
|
1475
1481
|
dbSetup: btsConfig.dbSetup,
|
|
1476
1482
|
api: btsConfig.api,
|
|
1477
1483
|
webDeploy: btsConfig.webDeploy,
|
|
@@ -1558,6 +1564,166 @@ async function updateBtsConfig(projectDir, updates) {
|
|
|
1558
1564
|
} catch {}
|
|
1559
1565
|
}
|
|
1560
1566
|
|
|
1567
|
+
//#endregion
|
|
1568
|
+
//#region src/utils/dependency-version-channel.ts
|
|
1569
|
+
const VERSION_CACHE = /* @__PURE__ */ new Map();
|
|
1570
|
+
const PRERELEASE_TAG_PRIORITY = [
|
|
1571
|
+
"beta",
|
|
1572
|
+
"next",
|
|
1573
|
+
"rc",
|
|
1574
|
+
"canary",
|
|
1575
|
+
"alpha"
|
|
1576
|
+
];
|
|
1577
|
+
const REGISTRY_FETCH_TIMEOUT_MS = 1e4;
|
|
1578
|
+
const REGISTRY_CONCURRENCY = 10;
|
|
1579
|
+
function mapWithConcurrency(items, fn, concurrency) {
|
|
1580
|
+
const results = Array.from({ length: items.length });
|
|
1581
|
+
let index = 0;
|
|
1582
|
+
async function worker() {
|
|
1583
|
+
while (index < items.length) {
|
|
1584
|
+
const i = index++;
|
|
1585
|
+
results[i] = await fn(items[i], i);
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
return Promise.all(Array.from({ length: Math.min(concurrency, items.length) }, () => worker())).then(() => results);
|
|
1589
|
+
}
|
|
1590
|
+
function parseVersion(value) {
|
|
1591
|
+
const normalized = value.replace(/^[^\d]*/, "");
|
|
1592
|
+
const dashIdx = normalized.indexOf("-");
|
|
1593
|
+
const base = dashIdx === -1 ? normalized : normalized.slice(0, dashIdx);
|
|
1594
|
+
const prerelease = dashIdx === -1 ? "" : normalized.slice(dashIdx + 1);
|
|
1595
|
+
const [major = "0", minor = "0", patch = "0"] = base.split(".");
|
|
1596
|
+
return {
|
|
1597
|
+
major: Number(major) || 0,
|
|
1598
|
+
minor: Number(minor) || 0,
|
|
1599
|
+
patch: Number(patch) || 0,
|
|
1600
|
+
prerelease: prerelease ? prerelease.split(/[.-]/).map((part) => /^\d+$/.test(part) ? Number(part) : part) : []
|
|
1601
|
+
};
|
|
1602
|
+
}
|
|
1603
|
+
function compareVersions(a, b) {
|
|
1604
|
+
const left = parseVersion(a);
|
|
1605
|
+
const right = parseVersion(b);
|
|
1606
|
+
if (left.major !== right.major) return left.major - right.major;
|
|
1607
|
+
if (left.minor !== right.minor) return left.minor - right.minor;
|
|
1608
|
+
if (left.patch !== right.patch) return left.patch - right.patch;
|
|
1609
|
+
if (left.prerelease.length === 0 && right.prerelease.length === 0) return 0;
|
|
1610
|
+
if (left.prerelease.length === 0) return 1;
|
|
1611
|
+
if (right.prerelease.length === 0) return -1;
|
|
1612
|
+
const maxLength = Math.max(left.prerelease.length, right.prerelease.length);
|
|
1613
|
+
for (let index = 0; index < maxLength; index++) {
|
|
1614
|
+
const leftPart = left.prerelease[index];
|
|
1615
|
+
const rightPart = right.prerelease[index];
|
|
1616
|
+
if (leftPart === void 0) return -1;
|
|
1617
|
+
if (rightPart === void 0) return 1;
|
|
1618
|
+
if (leftPart === rightPart) continue;
|
|
1619
|
+
if (typeof leftPart === "number" && typeof rightPart === "number") return leftPart - rightPart;
|
|
1620
|
+
if (typeof leftPart === "number") return -1;
|
|
1621
|
+
if (typeof rightPart === "number") return 1;
|
|
1622
|
+
return leftPart.localeCompare(rightPart);
|
|
1623
|
+
}
|
|
1624
|
+
return 0;
|
|
1625
|
+
}
|
|
1626
|
+
function isPrerelease(version) {
|
|
1627
|
+
return /-(alpha|beta|rc|next|canary)/i.test(version);
|
|
1628
|
+
}
|
|
1629
|
+
function getVersionPrefix(version) {
|
|
1630
|
+
return version.match(/^[^\d]*/)?.[0] ?? "";
|
|
1631
|
+
}
|
|
1632
|
+
function applyVersionPrefix(currentVersion, resolvedVersion) {
|
|
1633
|
+
return `${getVersionPrefix(currentVersion)}${resolvedVersion}`;
|
|
1634
|
+
}
|
|
1635
|
+
function isRegistrySemverSpec(version) {
|
|
1636
|
+
return /^[~^]?\d/.test(version);
|
|
1637
|
+
}
|
|
1638
|
+
async function fetchPackageInfo(packageName) {
|
|
1639
|
+
const cached = VERSION_CACHE.get(packageName);
|
|
1640
|
+
if (cached) return cached;
|
|
1641
|
+
const encodedName = encodeURIComponent(packageName).replace("%40", "@");
|
|
1642
|
+
const response = await fetch(`https://registry.npmjs.org/${encodedName}`, {
|
|
1643
|
+
headers: { Accept: "application/vnd.npm.install-v1+json" },
|
|
1644
|
+
signal: AbortSignal.timeout(REGISTRY_FETCH_TIMEOUT_MS)
|
|
1645
|
+
});
|
|
1646
|
+
if (!response.ok) throw new Error(`Package ${packageName} not found (${response.status})`);
|
|
1647
|
+
const raw = await response.json();
|
|
1648
|
+
const data = {
|
|
1649
|
+
"dist-tags": raw["dist-tags"],
|
|
1650
|
+
versions: raw["versions"]
|
|
1651
|
+
};
|
|
1652
|
+
VERSION_CACHE.set(packageName, data);
|
|
1653
|
+
return data;
|
|
1654
|
+
}
|
|
1655
|
+
function selectRegistryVersionForChannel(packageInfo, channel) {
|
|
1656
|
+
const tags = packageInfo["dist-tags"] ?? {};
|
|
1657
|
+
if (channel === "latest") return tags.latest ?? null;
|
|
1658
|
+
for (const tag of PRERELEASE_TAG_PRIORITY) if (tags[tag]) return tags[tag];
|
|
1659
|
+
const prereleases = Object.keys(packageInfo.versions ?? {}).filter(isPrerelease);
|
|
1660
|
+
if (prereleases.length > 0) return prereleases.sort((left, right) => compareVersions(right, left))[0] ?? null;
|
|
1661
|
+
return tags.latest ?? null;
|
|
1662
|
+
}
|
|
1663
|
+
async function resolveRegistryVersion(packageName, channel) {
|
|
1664
|
+
const version = selectRegistryVersionForChannel(await fetchPackageInfo(packageName), channel);
|
|
1665
|
+
if (!version) throw new Error(`No ${channel} version available for ${packageName}`);
|
|
1666
|
+
return version;
|
|
1667
|
+
}
|
|
1668
|
+
async function collectPackageJsonPaths$1(projectDir) {
|
|
1669
|
+
const results = [];
|
|
1670
|
+
async function walk(currentDir) {
|
|
1671
|
+
const entries = await fs.readdir(currentDir, { withFileTypes: true });
|
|
1672
|
+
for (const entry of entries) {
|
|
1673
|
+
if (entry.name === "node_modules" || entry.name === ".git" || entry.name === ".turbo") continue;
|
|
1674
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
1675
|
+
if (entry.isDirectory()) {
|
|
1676
|
+
await walk(fullPath);
|
|
1677
|
+
continue;
|
|
1678
|
+
}
|
|
1679
|
+
if (entry.isFile() && entry.name === "package.json") results.push(fullPath);
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
await walk(projectDir);
|
|
1683
|
+
return results.sort();
|
|
1684
|
+
}
|
|
1685
|
+
async function applyDependencyVersionChannel(projectDir, channel) {
|
|
1686
|
+
if (channel === "stable") return;
|
|
1687
|
+
const packageJsonPaths = await collectPackageJsonPaths$1(projectDir);
|
|
1688
|
+
if (packageJsonPaths.length === 0) return;
|
|
1689
|
+
const packageNames = /* @__PURE__ */ new Set();
|
|
1690
|
+
for (const packageJsonPath of packageJsonPaths) {
|
|
1691
|
+
const packageJson = await fs.readJson(packageJsonPath);
|
|
1692
|
+
for (const [depName, depVersion] of Object.entries(packageJson.dependencies ?? {})) if (typeof depVersion === "string" && isRegistrySemverSpec(depVersion)) packageNames.add(depName);
|
|
1693
|
+
for (const [depName, depVersion] of Object.entries(packageJson.devDependencies ?? {})) if (typeof depVersion === "string" && isRegistrySemverSpec(depVersion)) packageNames.add(depName);
|
|
1694
|
+
}
|
|
1695
|
+
if (packageNames.size === 0) return;
|
|
1696
|
+
const resolvedVersions = /* @__PURE__ */ new Map();
|
|
1697
|
+
await mapWithConcurrency([...packageNames], async (packageName) => {
|
|
1698
|
+
try {
|
|
1699
|
+
const resolvedVersion = await resolveRegistryVersion(packageName, channel);
|
|
1700
|
+
resolvedVersions.set(packageName, resolvedVersion);
|
|
1701
|
+
} catch (error) {
|
|
1702
|
+
log.warn(`Failed to resolve ${channel} version for ${packageName}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1703
|
+
}
|
|
1704
|
+
}, REGISTRY_CONCURRENCY);
|
|
1705
|
+
if (resolvedVersions.size === 0) return;
|
|
1706
|
+
for (const packageJsonPath of packageJsonPaths) {
|
|
1707
|
+
const packageJson = await fs.readJson(packageJsonPath);
|
|
1708
|
+
let changed = false;
|
|
1709
|
+
for (const sectionName of ["dependencies", "devDependencies"]) {
|
|
1710
|
+
const section = packageJson[sectionName];
|
|
1711
|
+
if (!section) continue;
|
|
1712
|
+
for (const [packageName, currentVersion] of Object.entries(section)) {
|
|
1713
|
+
if (!isRegistrySemverSpec(currentVersion)) continue;
|
|
1714
|
+
const resolvedVersion = resolvedVersions.get(packageName);
|
|
1715
|
+
if (!resolvedVersion) continue;
|
|
1716
|
+
const nextVersion = applyVersionPrefix(currentVersion, resolvedVersion);
|
|
1717
|
+
if (nextVersion !== currentVersion) {
|
|
1718
|
+
section[packageName] = nextVersion;
|
|
1719
|
+
changed = true;
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
}
|
|
1723
|
+
if (changed) await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1561
1727
|
//#endregion
|
|
1562
1728
|
//#region src/utils/add-package-deps.ts
|
|
1563
1729
|
const addPackageDependency = async (opts) => {
|
|
@@ -2946,6 +3112,7 @@ async function addHandlerInternal(input) {
|
|
|
2946
3112
|
config
|
|
2947
3113
|
}, projectDir);
|
|
2948
3114
|
await setupAddons(config);
|
|
3115
|
+
await applyDependencyVersionChannel(projectDir, config.versionChannel);
|
|
2949
3116
|
const configUpdates = { addons: [...new Set([...existingAddons, ...addonsToAdd])] };
|
|
2950
3117
|
if (input.webDeploy !== void 0) configUpdates.webDeploy = input.webDeploy;
|
|
2951
3118
|
if (input.serverDeploy !== void 0) configUpdates.serverDeploy = input.serverDeploy;
|
|
@@ -5985,6 +6152,35 @@ async function getProjectName(initialName) {
|
|
|
5985
6152
|
return projectPath;
|
|
5986
6153
|
}
|
|
5987
6154
|
|
|
6155
|
+
//#endregion
|
|
6156
|
+
//#region src/prompts/version-channel.ts
|
|
6157
|
+
async function getVersionChannelChoice(versionChannel) {
|
|
6158
|
+
if (versionChannel !== void 0) return versionChannel;
|
|
6159
|
+
const response = await navigableSelect({
|
|
6160
|
+
message: "Choose dependency version channel",
|
|
6161
|
+
options: [
|
|
6162
|
+
{
|
|
6163
|
+
value: "stable",
|
|
6164
|
+
label: "Stable",
|
|
6165
|
+
hint: "Use Better Fullstack's curated pinned versions"
|
|
6166
|
+
},
|
|
6167
|
+
{
|
|
6168
|
+
value: "latest",
|
|
6169
|
+
label: "Latest",
|
|
6170
|
+
hint: "Resolve current npm latest tags during scaffolding"
|
|
6171
|
+
},
|
|
6172
|
+
{
|
|
6173
|
+
value: "beta",
|
|
6174
|
+
label: "Beta",
|
|
6175
|
+
hint: "Prefer npm beta/next prerelease tags when available"
|
|
6176
|
+
}
|
|
6177
|
+
],
|
|
6178
|
+
initialValue: "stable"
|
|
6179
|
+
});
|
|
6180
|
+
if (isCancel$1(response)) return exitCancelled("Operation cancelled");
|
|
6181
|
+
return response;
|
|
6182
|
+
}
|
|
6183
|
+
|
|
5988
6184
|
//#endregion
|
|
5989
6185
|
//#region src/utils/telemetry.ts
|
|
5990
6186
|
/**
|
|
@@ -6077,6 +6273,7 @@ function displayConfig(config) {
|
|
|
6077
6273
|
configDisplay.push(`${pc.blue("Git Init:")} ${gitText}`);
|
|
6078
6274
|
}
|
|
6079
6275
|
if (config.packageManager !== void 0) configDisplay.push(`${pc.blue("Package Manager:")} ${String(config.packageManager)}`);
|
|
6276
|
+
if (config.versionChannel !== void 0) configDisplay.push(`${pc.blue("Version Channel:")} ${String(config.versionChannel)}`);
|
|
6080
6277
|
if (config.install !== void 0) {
|
|
6081
6278
|
const installText = typeof config.install === "boolean" ? config.install ? "Yes" : "No" : String(config.install);
|
|
6082
6279
|
configDisplay.push(`${pc.blue("Install Dependencies:")} ${installText}`);
|
|
@@ -6109,6 +6306,7 @@ function appendCommonFlags(flags, config) {
|
|
|
6109
6306
|
else flags.push("--ai-docs none");
|
|
6110
6307
|
flags.push(config.git ? "--git" : "--no-git");
|
|
6111
6308
|
flags.push(`--package-manager ${config.packageManager}`);
|
|
6309
|
+
if (config.versionChannel !== "stable") flags.push(`--version-channel ${config.versionChannel}`);
|
|
6112
6310
|
flags.push(config.install ? "--install" : "--no-install");
|
|
6113
6311
|
}
|
|
6114
6312
|
function appendSharedNonTypeScriptFlags(flags, config) {
|
|
@@ -6432,6 +6630,7 @@ function processFlags(options, projectName) {
|
|
|
6432
6630
|
if (options.runtime) config.runtime = options.runtime;
|
|
6433
6631
|
if (options.dbSetup) config.dbSetup = options.dbSetup;
|
|
6434
6632
|
if (options.packageManager) config.packageManager = options.packageManager;
|
|
6633
|
+
if (options.versionChannel) config.versionChannel = options.versionChannel;
|
|
6435
6634
|
if (options.webDeploy) config.webDeploy = options.webDeploy;
|
|
6436
6635
|
if (options.serverDeploy) config.serverDeploy = options.serverDeploy;
|
|
6437
6636
|
const derivedName = deriveProjectName(projectName, options.projectDirectory);
|
|
@@ -7053,6 +7252,26 @@ function validateConfigCompatibility(config, providedFlags, options) {
|
|
|
7053
7252
|
else validateConfigForProgrammaticUse(config);
|
|
7054
7253
|
}
|
|
7055
7254
|
|
|
7255
|
+
//#endregion
|
|
7256
|
+
//#region src/utils/preflight-display.ts
|
|
7257
|
+
function displayPreflightWarnings({ warnings }) {
|
|
7258
|
+
if (warnings.length === 0) return;
|
|
7259
|
+
const count = warnings.length;
|
|
7260
|
+
const lines = [pc.bold(pc.yellow(`${count} feature${count > 1 ? "s" : ""} will not generate templates:`)), ""];
|
|
7261
|
+
warnings.forEach((w, i) => {
|
|
7262
|
+
const selected = Array.isArray(w.selectedValue) ? w.selectedValue.join(", ") : w.selectedValue;
|
|
7263
|
+
lines.push(` ${pc.yellow(`${i + 1}.`)} ${pc.bold(w.featureDisplayName)} ${pc.dim(`(${selected})`)}`);
|
|
7264
|
+
lines.push(` ${w.reason}`);
|
|
7265
|
+
w.suggestions.forEach((s) => lines.push(` ${pc.green("•")} ${s}`));
|
|
7266
|
+
if (i < count - 1) lines.push("");
|
|
7267
|
+
});
|
|
7268
|
+
consola.box({
|
|
7269
|
+
title: pc.yellow("Pre-flight Check"),
|
|
7270
|
+
message: lines.join("\n"),
|
|
7271
|
+
style: { borderColor: "yellow" }
|
|
7272
|
+
});
|
|
7273
|
+
}
|
|
7274
|
+
|
|
7056
7275
|
//#endregion
|
|
7057
7276
|
//#region src/utils/file-formatter.ts
|
|
7058
7277
|
const formatOptions = {
|
|
@@ -8342,8 +8561,9 @@ async function displayPostInstallInstructions(config) {
|
|
|
8342
8561
|
if (polarInstructions) output += `\n${polarInstructions.trim()}\n`;
|
|
8343
8562
|
if (noOrmWarning) output += `\n${noOrmWarning.trim()}\n`;
|
|
8344
8563
|
if (bunWebNativeWarning) output += `\n${bunWebNativeWarning.trim()}\n`;
|
|
8345
|
-
output += `\n${pc.bold("
|
|
8346
|
-
output += pc.cyan("https://github.com/Marve10s/Better-Fullstack")
|
|
8564
|
+
output += `\n${pc.bold("Enjoying Better Fullstack?")} Help us grow — star the repo!\n`;
|
|
8565
|
+
output += `${pc.cyan("https://github.com/Marve10s/Better-Fullstack")}\n`;
|
|
8566
|
+
output += pc.dim("Your star helps other developers discover the project.");
|
|
8347
8567
|
consola$1.box(output);
|
|
8348
8568
|
}
|
|
8349
8569
|
function getNativeInstructions(isConvex, isBackendSelf, frontend, runCmd) {
|
|
@@ -8481,8 +8701,9 @@ function displayRustInstructions(config) {
|
|
|
8481
8701
|
output += `${pc.cyan("•")} Check: cargo check\n`;
|
|
8482
8702
|
output += `${pc.cyan("•")} Format: cargo fmt\n`;
|
|
8483
8703
|
output += `${pc.cyan("•")} Lint: cargo clippy\n`;
|
|
8484
|
-
output += `\n${pc.bold("
|
|
8485
|
-
output += pc.cyan("https://github.com/Marve10s/Better-Fullstack")
|
|
8704
|
+
output += `\n${pc.bold("Enjoying Better Fullstack?")} Help us grow — star the repo!\n`;
|
|
8705
|
+
output += `${pc.cyan("https://github.com/Marve10s/Better-Fullstack")}\n`;
|
|
8706
|
+
output += pc.dim("Your star helps other developers discover the project.");
|
|
8486
8707
|
consola$1.box(output);
|
|
8487
8708
|
}
|
|
8488
8709
|
function displayGoInstructions(config) {
|
|
@@ -8517,8 +8738,9 @@ function displayGoInstructions(config) {
|
|
|
8517
8738
|
output += `\n${pc.bold("Your project will be available at:")}\n`;
|
|
8518
8739
|
output += `${pc.cyan("•")} API: http://localhost:8080\n`;
|
|
8519
8740
|
if (goApi === "grpc-go") output += `${pc.cyan("•")} gRPC: localhost:50051\n`;
|
|
8520
|
-
output += `\n${pc.bold("
|
|
8521
|
-
output += pc.cyan("https://github.com/Marve10s/Better-Fullstack")
|
|
8741
|
+
output += `\n${pc.bold("Enjoying Better Fullstack?")} Help us grow — star the repo!\n`;
|
|
8742
|
+
output += `${pc.cyan("https://github.com/Marve10s/Better-Fullstack")}\n`;
|
|
8743
|
+
output += pc.dim("Your star helps other developers discover the project.");
|
|
8522
8744
|
consola$1.box(output);
|
|
8523
8745
|
}
|
|
8524
8746
|
function displayPythonInstructions(config) {
|
|
@@ -8562,8 +8784,9 @@ function displayPythonInstructions(config) {
|
|
|
8562
8784
|
output += `${pc.cyan("•")} Lint: uv run ruff check .\n`;
|
|
8563
8785
|
output += `\n${pc.bold("Your project will be available at:")}\n`;
|
|
8564
8786
|
output += `${pc.cyan("•")} API: http://localhost:8000\n`;
|
|
8565
|
-
output += `\n${pc.bold("
|
|
8566
|
-
output += pc.cyan("https://github.com/Marve10s/Better-Fullstack")
|
|
8787
|
+
output += `\n${pc.bold("Enjoying Better Fullstack?")} Help us grow — star the repo!\n`;
|
|
8788
|
+
output += `${pc.cyan("https://github.com/Marve10s/Better-Fullstack")}\n`;
|
|
8789
|
+
output += pc.dim("Your star helps other developers discover the project.");
|
|
8567
8790
|
consola$1.box(output);
|
|
8568
8791
|
}
|
|
8569
8792
|
|
|
@@ -8583,6 +8806,7 @@ async function createProject(options, cliInput = {}) {
|
|
|
8583
8806
|
await setPackageManagerVersion(projectDir, options.packageManager);
|
|
8584
8807
|
if (!isConvex && options.database !== "none") await setupDatabase(options, cliInput);
|
|
8585
8808
|
if (options.addons.length > 0 && options.addons[0] !== "none") await setupAddons(options);
|
|
8809
|
+
await applyDependencyVersionChannel(projectDir, options.versionChannel);
|
|
8586
8810
|
await writeBtsConfig(options);
|
|
8587
8811
|
await formatProject(projectDir);
|
|
8588
8812
|
if (!isSilent()) log.success("Project template successfully scaffolded!");
|
|
@@ -8627,6 +8851,10 @@ async function setPackageManagerVersion(projectDir, packageManager) {
|
|
|
8627
8851
|
|
|
8628
8852
|
//#endregion
|
|
8629
8853
|
//#region src/helpers/core/command-handlers.ts
|
|
8854
|
+
function shouldPromptForVersionChannel(input) {
|
|
8855
|
+
if (input.yes || input.versionChannel !== void 0 || isSilent()) return false;
|
|
8856
|
+
return process.stdin.isTTY === true && process.stdout.isTTY === true && process.env.CI !== "true";
|
|
8857
|
+
}
|
|
8630
8858
|
async function createProjectHandler(input, options = {}) {
|
|
8631
8859
|
const { silent = false } = options;
|
|
8632
8860
|
return runWithContextAsync({ silent }, async () => {
|
|
@@ -8648,6 +8876,7 @@ async function createProjectHandler(input, options = {}) {
|
|
|
8648
8876
|
}
|
|
8649
8877
|
currentPathInput = defaultName;
|
|
8650
8878
|
} else currentPathInput = await getProjectName(input.projectName);
|
|
8879
|
+
const versionChannel = shouldPromptForVersionChannel(input) ? await getVersionChannelChoice() : input.versionChannel ?? "stable";
|
|
8651
8880
|
let finalPathInput;
|
|
8652
8881
|
let shouldClearDirectory;
|
|
8653
8882
|
try {
|
|
@@ -8683,6 +8912,7 @@ async function createProjectHandler(input, options = {}) {
|
|
|
8683
8912
|
effect: "none",
|
|
8684
8913
|
git: false,
|
|
8685
8914
|
packageManager: "npm",
|
|
8915
|
+
versionChannel: "stable",
|
|
8686
8916
|
install: false,
|
|
8687
8917
|
dbSetup: "none",
|
|
8688
8918
|
api: "none",
|
|
@@ -8767,9 +8997,12 @@ async function createProjectHandler(input, options = {}) {
|
|
|
8767
8997
|
...flagConfig,
|
|
8768
8998
|
projectName: finalBaseName,
|
|
8769
8999
|
projectDir: finalResolvedPath,
|
|
8770
|
-
relativePath: finalPathInput
|
|
9000
|
+
relativePath: finalPathInput,
|
|
9001
|
+
versionChannel
|
|
8771
9002
|
};
|
|
8772
9003
|
validateConfigCompatibility(config, providedFlags, cliInput);
|
|
9004
|
+
const yesPreflight = validatePreflightConfig(config);
|
|
9005
|
+
if (yesPreflight.hasWarnings && !isSilent()) displayPreflightWarnings(yesPreflight);
|
|
8773
9006
|
if (!isSilent()) {
|
|
8774
9007
|
log.info(pc.yellow("Using default/flag options (config prompts skipped):"));
|
|
8775
9008
|
log.message(displayConfig(config));
|
|
@@ -8782,8 +9015,13 @@ async function createProjectHandler(input, options = {}) {
|
|
|
8782
9015
|
log.message(displayConfig(otherFlags));
|
|
8783
9016
|
log.message("");
|
|
8784
9017
|
}
|
|
8785
|
-
config =
|
|
9018
|
+
config = {
|
|
9019
|
+
...await gatherConfig(flagConfig, finalBaseName, finalResolvedPath, finalPathInput),
|
|
9020
|
+
versionChannel
|
|
9021
|
+
};
|
|
8786
9022
|
}
|
|
9023
|
+
const preflight = validatePreflightConfig(config);
|
|
9024
|
+
if (preflight.hasWarnings && !isSilent()) displayPreflightWarnings(preflight);
|
|
8787
9025
|
await createProject(config, { manualDb: cliInput.manualDb ?? input.manualDb });
|
|
8788
9026
|
const reproducibleCommand = generateReproducibleCommand(config);
|
|
8789
9027
|
if (!isSilent()) log.success(pc.blue(`You can reproduce this setup with the following command:\n${reproducibleCommand}`));
|
|
@@ -8974,6 +9212,7 @@ const router = os.router({
|
|
|
8974
9212
|
git: z.boolean().optional(),
|
|
8975
9213
|
packageManager: types_exports.PackageManagerSchema.optional(),
|
|
8976
9214
|
install: z.boolean().optional(),
|
|
9215
|
+
versionChannel: types_exports.VersionChannelSchema.optional().describe("Dependency version channel (stable, latest, beta)"),
|
|
8977
9216
|
dbSetup: types_exports.DatabaseSetupSchema.optional(),
|
|
8978
9217
|
backend: types_exports.BackendSchema.optional(),
|
|
8979
9218
|
runtime: types_exports.RuntimeSchema.optional(),
|
|
@@ -9198,6 +9437,7 @@ async function createVirtual(options) {
|
|
|
9198
9437
|
effect: options.effect || "none",
|
|
9199
9438
|
git: options.git ?? false,
|
|
9200
9439
|
packageManager: options.packageManager || "bun",
|
|
9440
|
+
versionChannel: options.versionChannel || "stable",
|
|
9201
9441
|
install: false,
|
|
9202
9442
|
dbSetup: options.dbSetup || "none",
|
|
9203
9443
|
api: options.api || "trpc",
|
package/package.json
CHANGED
|
@@ -1,30 +1,51 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-better-fullstack",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "Scaffold production-ready fullstack apps in seconds. Pick your stack from 270+ options — the CLI wires everything together.",
|
|
5
5
|
"keywords": [
|
|
6
|
+
"angular",
|
|
7
|
+
"astro",
|
|
8
|
+
"authentication",
|
|
6
9
|
"better-auth",
|
|
7
10
|
"better-fullstack",
|
|
8
11
|
"biome",
|
|
9
12
|
"boilerplate",
|
|
13
|
+
"bun",
|
|
10
14
|
"cli",
|
|
15
|
+
"create",
|
|
16
|
+
"create-app",
|
|
17
|
+
"desktop",
|
|
18
|
+
"developer-tools",
|
|
19
|
+
"docker",
|
|
11
20
|
"drizzle",
|
|
12
21
|
"elysia",
|
|
13
22
|
"expo",
|
|
23
|
+
"fastapi",
|
|
14
24
|
"fullstack",
|
|
15
25
|
"go",
|
|
16
26
|
"golang",
|
|
17
27
|
"hono",
|
|
28
|
+
"mobile",
|
|
18
29
|
"monorepo",
|
|
30
|
+
"nextjs",
|
|
31
|
+
"nuxt",
|
|
32
|
+
"payments",
|
|
33
|
+
"pnpm",
|
|
19
34
|
"prisma",
|
|
35
|
+
"project-generator",
|
|
20
36
|
"pwa",
|
|
21
37
|
"python",
|
|
22
38
|
"react",
|
|
23
39
|
"react-native",
|
|
24
40
|
"rust",
|
|
41
|
+
"scaffold",
|
|
25
42
|
"scaffolding",
|
|
26
43
|
"shadcn",
|
|
44
|
+
"solid",
|
|
27
45
|
"starter",
|
|
46
|
+
"starter-kit",
|
|
47
|
+
"svelte",
|
|
48
|
+
"sveltekit",
|
|
28
49
|
"tailwind",
|
|
29
50
|
"tanstack",
|
|
30
51
|
"tauri",
|
|
@@ -33,6 +54,9 @@
|
|
|
33
54
|
"turborepo",
|
|
34
55
|
"type-safety",
|
|
35
56
|
"typescript",
|
|
57
|
+
"vite",
|
|
58
|
+
"vue",
|
|
59
|
+
"web-app",
|
|
36
60
|
"yarn"
|
|
37
61
|
],
|
|
38
62
|
"homepage": "https://better-fullstack.dev/",
|
|
@@ -75,7 +99,8 @@
|
|
|
75
99
|
"test:watch": "bun test --watch",
|
|
76
100
|
"test:coverage": "bun test --coverage",
|
|
77
101
|
"test:ci": "CI=1 bun test --bail=5",
|
|
78
|
-
"test:e2e": "E2E=1 bun test test/e2e/e2e.e2e.ts",
|
|
102
|
+
"test:e2e": "E2E=1 bun test test/e2e/e2e.e2e.ts --timeout 600000",
|
|
103
|
+
"test:integration": "bun test test/e2e/cli-interaction.test.ts test/e2e/cli-binary.test.ts",
|
|
79
104
|
"test:astro-combos": "bun run scripts/test-astro-combinations.ts",
|
|
80
105
|
"test:matrix": "MATRIX_MODE=batched bun test ./test/matrix/matrix-test.test.ts",
|
|
81
106
|
"test:matrix:fast": "MATRIX_MODE=sample MATRIX_SAMPLE=0.1 bun test ./test/matrix/matrix-test.test.ts",
|
|
@@ -83,11 +108,11 @@
|
|
|
83
108
|
"prepublishOnly": "npm run build"
|
|
84
109
|
},
|
|
85
110
|
"dependencies": {
|
|
86
|
-
"@better-fullstack/template-generator": "^1.
|
|
87
|
-
"@better-fullstack/types": "^1.
|
|
111
|
+
"@better-fullstack/template-generator": "^1.5.0",
|
|
112
|
+
"@better-fullstack/types": "^1.5.0",
|
|
88
113
|
"@clack/core": "^0.5.0",
|
|
89
114
|
"@clack/prompts": "^1.1.0",
|
|
90
|
-
"@orpc/server": "^1.13.
|
|
115
|
+
"@orpc/server": "^1.13.9",
|
|
91
116
|
"consola": "^3.4.2",
|
|
92
117
|
"env-paths": "^4.0.0",
|
|
93
118
|
"execa": "^9.6.1",
|
|
@@ -100,11 +125,11 @@
|
|
|
100
125
|
"tinyglobby": "^0.2.15",
|
|
101
126
|
"trpc-cli": "^0.12.1",
|
|
102
127
|
"ts-morph": "^27.0.2",
|
|
103
|
-
"yaml": "^2.8.
|
|
128
|
+
"yaml": "^2.8.3",
|
|
104
129
|
"zod": "^4.3.6"
|
|
105
130
|
},
|
|
106
131
|
"devDependencies": {
|
|
107
|
-
"@types/bun": "^1.3.
|
|
132
|
+
"@types/bun": "^1.3.11",
|
|
108
133
|
"@types/fs-extra": "^11.0.4",
|
|
109
134
|
"@types/node": "^25.5.0",
|
|
110
135
|
"publint": "^0.3.18",
|