mirror of
https://github.com/FranP-code/create-better-t-stack.git
synced 2025-10-12 23:52:15 +00:00
add cloudflare workers support for all frontends (#366)
This commit is contained in:
5
.changeset/fuzzy-falcons-rhyme.md
Normal file
5
.changeset/fuzzy-falcons-rhyme.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"create-better-t-stack": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
add cloudflare workers deployment support for next, solid, tanstack-router, react-router, nuxt
|
||||||
@@ -64,6 +64,7 @@
|
|||||||
"picocolors": "^1.1.1",
|
"picocolors": "^1.1.1",
|
||||||
"posthog-node": "^5.1.1",
|
"posthog-node": "^5.1.1",
|
||||||
"trpc-cli": "^0.9.2",
|
"trpc-cli": "^0.9.2",
|
||||||
|
"ts-morph": "^26.0.0",
|
||||||
"zod": "^3.25.67"
|
"zod": "^3.25.67"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ export const DEFAULT_CONFIG: ProjectConfig = {
|
|||||||
backend: "hono",
|
backend: "hono",
|
||||||
runtime: "bun",
|
runtime: "bun",
|
||||||
api: "trpc",
|
api: "trpc",
|
||||||
|
webDeploy: "none",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const dependencyVersionMap = {
|
export const dependencyVersionMap = {
|
||||||
@@ -45,8 +46,8 @@ export const dependencyVersionMap = {
|
|||||||
|
|
||||||
mongoose: "^8.14.0",
|
mongoose: "^8.14.0",
|
||||||
|
|
||||||
"vite-plugin-pwa": "^0.21.2",
|
"vite-plugin-pwa": "^1.0.1",
|
||||||
"@vite-pwa/assets-generator": "^0.2.6",
|
"@vite-pwa/assets-generator": "^1.0.0",
|
||||||
|
|
||||||
"@tauri-apps/cli": "^2.4.0",
|
"@tauri-apps/cli": "^2.4.0",
|
||||||
|
|
||||||
@@ -107,13 +108,17 @@ export const dependencyVersionMap = {
|
|||||||
"@tanstack/solid-query": "^5.75.0",
|
"@tanstack/solid-query": "^5.75.0",
|
||||||
"@tanstack/solid-query-devtools": "^5.75.0",
|
"@tanstack/solid-query-devtools": "^5.75.0",
|
||||||
|
|
||||||
wrangler: "^4.20.0",
|
wrangler: "^4.23.0",
|
||||||
|
"@cloudflare/vite-plugin": "^1.9.0",
|
||||||
|
"@opennextjs/cloudflare": "^1.3.0",
|
||||||
|
"nitro-cloudflare-dev": "^0.2.2",
|
||||||
|
"@sveltejs/adapter-cloudflare": "^7.0.4",
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export type AvailableDependencies = keyof typeof dependencyVersionMap;
|
export type AvailableDependencies = keyof typeof dependencyVersionMap;
|
||||||
|
|
||||||
export const ADDON_COMPATIBILITY = {
|
export const ADDON_COMPATIBILITY = {
|
||||||
pwa: ["tanstack-router", "react-router", "solid"],
|
pwa: ["tanstack-router", "react-router", "solid", "next"],
|
||||||
tauri: ["tanstack-router", "react-router", "nuxt", "svelte", "solid"],
|
tauri: ["tanstack-router", "react-router", "nuxt", "svelte", "solid"],
|
||||||
biome: [],
|
biome: [],
|
||||||
husky: [],
|
husky: [],
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ function exitWithError(message: string): never {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function addAddonsToProject(
|
export async function addAddonsToProject(
|
||||||
input: AddInput & { addons: Addons[] },
|
input: AddInput & { addons: Addons[]; suppressInstallMessage?: boolean },
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const projectDir = input.projectDir || process.cwd();
|
const projectDir = input.projectDir || process.cwd();
|
||||||
@@ -55,6 +55,7 @@ export async function addAddonsToProject(
|
|||||||
install: input.install || false,
|
install: input.install || false,
|
||||||
dbSetup: detectedConfig.dbSetup || "none",
|
dbSetup: detectedConfig.dbSetup || "none",
|
||||||
api: detectedConfig.api || "none",
|
api: detectedConfig.api || "none",
|
||||||
|
webDeploy: detectedConfig.webDeploy || "none",
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const addon of input.addons) {
|
for (const addon of input.addons) {
|
||||||
@@ -88,7 +89,7 @@ export async function addAddonsToProject(
|
|||||||
projectDir,
|
projectDir,
|
||||||
packageManager: config.packageManager,
|
packageManager: config.packageManager,
|
||||||
});
|
});
|
||||||
} else {
|
} else if (!input.suppressInstallMessage) {
|
||||||
log.info(
|
log.info(
|
||||||
pc.yellow(
|
pc.yellow(
|
||||||
`Run ${pc.bold(
|
`Run ${pc.bold(
|
||||||
|
|||||||
113
apps/cli/src/helpers/project-generation/add-deployment.ts
Normal file
113
apps/cli/src/helpers/project-generation/add-deployment.ts
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
import path from "node:path";
|
||||||
|
import { cancel, log } from "@clack/prompts";
|
||||||
|
import pc from "picocolors";
|
||||||
|
import type { AddInput, ProjectConfig, WebDeploy } from "../../types";
|
||||||
|
import { updateBtsConfig } from "../../utils/bts-config";
|
||||||
|
import { setupWebDeploy } from "../setup/web-deploy-setup";
|
||||||
|
import {
|
||||||
|
detectProjectConfig,
|
||||||
|
isBetterTStackProject,
|
||||||
|
} from "./detect-project-config";
|
||||||
|
import { installDependencies } from "./install-dependencies";
|
||||||
|
import { setupDeploymentTemplates } from "./template-manager";
|
||||||
|
|
||||||
|
function exitWithError(message: string): never {
|
||||||
|
cancel(pc.red(message));
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function addDeploymentToProject(
|
||||||
|
input: AddInput & { webDeploy: WebDeploy; suppressInstallMessage?: boolean },
|
||||||
|
): Promise<void> {
|
||||||
|
try {
|
||||||
|
const projectDir = input.projectDir || process.cwd();
|
||||||
|
|
||||||
|
const isBetterTStack = await isBetterTStackProject(projectDir);
|
||||||
|
if (!isBetterTStack) {
|
||||||
|
exitWithError(
|
||||||
|
"This doesn't appear to be a Better-T Stack project. Please run this command from the root of a Better-T Stack project.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const detectedConfig = await detectProjectConfig(projectDir);
|
||||||
|
if (!detectedConfig) {
|
||||||
|
exitWithError(
|
||||||
|
"Could not detect the project configuration. Please ensure this is a valid Better-T Stack project.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (detectedConfig.webDeploy === input.webDeploy) {
|
||||||
|
exitWithError(
|
||||||
|
`${input.webDeploy} deployment is already configured for this project.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.webDeploy === "workers") {
|
||||||
|
const compatibleFrontends = [
|
||||||
|
"tanstack-router",
|
||||||
|
"react-router",
|
||||||
|
"solid",
|
||||||
|
"next",
|
||||||
|
"svelte",
|
||||||
|
];
|
||||||
|
const hasCompatible = detectedConfig.frontend?.some((f) =>
|
||||||
|
compatibleFrontends.includes(f),
|
||||||
|
);
|
||||||
|
if (!hasCompatible) {
|
||||||
|
exitWithError(
|
||||||
|
"Cloudflare Workers deployment requires a compatible web frontend (tanstack-router, react-router, solid, next, or svelte).",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const config: ProjectConfig = {
|
||||||
|
projectName: detectedConfig.projectName || path.basename(projectDir),
|
||||||
|
projectDir,
|
||||||
|
relativePath: ".",
|
||||||
|
database: detectedConfig.database || "none",
|
||||||
|
orm: detectedConfig.orm || "none",
|
||||||
|
backend: detectedConfig.backend || "none",
|
||||||
|
runtime: detectedConfig.runtime || "none",
|
||||||
|
frontend: detectedConfig.frontend || [],
|
||||||
|
addons: detectedConfig.addons || [],
|
||||||
|
examples: detectedConfig.examples || [],
|
||||||
|
auth: detectedConfig.auth || false,
|
||||||
|
git: false,
|
||||||
|
packageManager:
|
||||||
|
input.packageManager || detectedConfig.packageManager || "npm",
|
||||||
|
install: input.install || false,
|
||||||
|
dbSetup: detectedConfig.dbSetup || "none",
|
||||||
|
api: detectedConfig.api || "none",
|
||||||
|
webDeploy: input.webDeploy,
|
||||||
|
};
|
||||||
|
|
||||||
|
log.info(
|
||||||
|
pc.green(
|
||||||
|
`Adding ${input.webDeploy} deployment to ${config.frontend.join("/")}`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
await setupDeploymentTemplates(projectDir, config);
|
||||||
|
await setupWebDeploy(config);
|
||||||
|
|
||||||
|
await updateBtsConfig(projectDir, { webDeploy: input.webDeploy });
|
||||||
|
|
||||||
|
if (config.install) {
|
||||||
|
await installDependencies({
|
||||||
|
projectDir,
|
||||||
|
packageManager: config.packageManager,
|
||||||
|
});
|
||||||
|
} else if (!input.suppressInstallMessage) {
|
||||||
|
log.info(
|
||||||
|
pc.yellow(
|
||||||
|
`Run ${pc.bold(
|
||||||
|
`${config.packageManager} install`,
|
||||||
|
)} to install dependencies`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
const message = error instanceof Error ? error.message : String(error);
|
||||||
|
exitWithError(`Error adding deployment: ${message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ import { DEFAULT_CONFIG } from "../../constants";
|
|||||||
import { getAddonsToAdd } from "../../prompts/addons";
|
import { getAddonsToAdd } from "../../prompts/addons";
|
||||||
import { gatherConfig } from "../../prompts/config-prompts";
|
import { gatherConfig } from "../../prompts/config-prompts";
|
||||||
import { getProjectName } from "../../prompts/project-name";
|
import { getProjectName } from "../../prompts/project-name";
|
||||||
|
import { getDeploymentToAdd } from "../../prompts/web-deploy";
|
||||||
import type { AddInput, CreateInput, ProjectConfig } from "../../types";
|
import type { AddInput, CreateInput, ProjectConfig } from "../../types";
|
||||||
import { trackProjectCreation } from "../../utils/analytics";
|
import { trackProjectCreation } from "../../utils/analytics";
|
||||||
import { displayConfig } from "../../utils/display-config";
|
import { displayConfig } from "../../utils/display-config";
|
||||||
@@ -17,8 +18,10 @@ import {
|
|||||||
import { renderTitle } from "../../utils/render-title";
|
import { renderTitle } from "../../utils/render-title";
|
||||||
import { getProvidedFlags, processAndValidateFlags } from "../../validation";
|
import { getProvidedFlags, processAndValidateFlags } from "../../validation";
|
||||||
import { addAddonsToProject } from "./add-addons";
|
import { addAddonsToProject } from "./add-addons";
|
||||||
|
import { addDeploymentToProject } from "./add-deployment";
|
||||||
import { createProject } from "./create-project";
|
import { createProject } from "./create-project";
|
||||||
import { detectProjectConfig } from "./detect-project-config";
|
import { detectProjectConfig } from "./detect-project-config";
|
||||||
|
import { installDependencies } from "./install-dependencies";
|
||||||
|
|
||||||
export async function createProjectHandler(
|
export async function createProjectHandler(
|
||||||
input: CreateInput & { projectName?: string },
|
input: CreateInput & { projectName?: string },
|
||||||
@@ -135,45 +138,84 @@ export async function createProjectHandler(
|
|||||||
|
|
||||||
export async function addAddonsHandler(input: AddInput): Promise<void> {
|
export async function addAddonsHandler(input: AddInput): Promise<void> {
|
||||||
try {
|
try {
|
||||||
|
const projectDir = input.projectDir || process.cwd();
|
||||||
|
const detectedConfig = await detectProjectConfig(projectDir);
|
||||||
|
|
||||||
|
if (!detectedConfig) {
|
||||||
|
cancel(
|
||||||
|
pc.red(
|
||||||
|
"Could not detect project configuration. Please ensure this is a valid Better-T Stack project.",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
if (!input.addons || input.addons.length === 0) {
|
if (!input.addons || input.addons.length === 0) {
|
||||||
const projectDir = input.projectDir || process.cwd();
|
|
||||||
const detectedConfig = await detectProjectConfig(projectDir);
|
|
||||||
|
|
||||||
if (!detectedConfig) {
|
|
||||||
cancel(
|
|
||||||
pc.red(
|
|
||||||
"Could not detect project configuration. Please ensure this is a valid Better-T Stack project.",
|
|
||||||
),
|
|
||||||
);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const addonsPrompt = await getAddonsToAdd(
|
const addonsPrompt = await getAddonsToAdd(
|
||||||
detectedConfig.frontend || [],
|
detectedConfig.frontend || [],
|
||||||
detectedConfig.addons || [],
|
detectedConfig.addons || [],
|
||||||
);
|
);
|
||||||
|
|
||||||
if (addonsPrompt.length === 0) {
|
if (addonsPrompt.length > 0) {
|
||||||
outro(
|
input.addons = addonsPrompt;
|
||||||
pc.yellow(
|
|
||||||
"No addons to add or all compatible addons are already present.",
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
input.addons = addonsPrompt;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!input.addons || input.addons.length === 0) {
|
if (!input.webDeploy) {
|
||||||
outro(pc.yellow("No addons specified to add."));
|
const deploymentPrompt = await getDeploymentToAdd(
|
||||||
|
detectedConfig.frontend || [],
|
||||||
|
detectedConfig.webDeploy,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (deploymentPrompt !== "none") {
|
||||||
|
input.webDeploy = deploymentPrompt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const packageManager =
|
||||||
|
input.packageManager || detectedConfig.packageManager || "npm";
|
||||||
|
|
||||||
|
let somethingAdded = false;
|
||||||
|
|
||||||
|
if (input.addons && input.addons.length > 0) {
|
||||||
|
await addAddonsToProject({
|
||||||
|
...input,
|
||||||
|
install: false,
|
||||||
|
suppressInstallMessage: true,
|
||||||
|
addons: input.addons,
|
||||||
|
});
|
||||||
|
somethingAdded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.webDeploy && input.webDeploy !== "none") {
|
||||||
|
await addDeploymentToProject({
|
||||||
|
...input,
|
||||||
|
install: false,
|
||||||
|
suppressInstallMessage: true,
|
||||||
|
webDeploy: input.webDeploy,
|
||||||
|
});
|
||||||
|
somethingAdded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!somethingAdded) {
|
||||||
|
outro(pc.yellow("No addons or deployment configurations to add."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await addAddonsToProject({
|
if (input.install) {
|
||||||
...input,
|
await installDependencies({
|
||||||
addons: input.addons,
|
projectDir,
|
||||||
});
|
packageManager,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
log.info(
|
||||||
|
pc.yellow(
|
||||||
|
`Run ${pc.bold(`${packageManager} install`)} to install dependencies`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
outro(pc.green("Add command completed successfully!"));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
generateCloudflareWorkerTypes,
|
generateCloudflareWorkerTypes,
|
||||||
setupRuntime,
|
setupRuntime,
|
||||||
} from "../setup/runtime-setup";
|
} from "../setup/runtime-setup";
|
||||||
|
import { setupWebDeploy } from "../setup/web-deploy-setup";
|
||||||
import { createReadme } from "./create-readme";
|
import { createReadme } from "./create-readme";
|
||||||
import { setupEnvironmentVariables } from "./env-setup";
|
import { setupEnvironmentVariables } from "./env-setup";
|
||||||
import { initializeGit } from "./git";
|
import { initializeGit } from "./git";
|
||||||
@@ -26,6 +27,7 @@ import {
|
|||||||
setupAuthTemplate,
|
setupAuthTemplate,
|
||||||
setupBackendFramework,
|
setupBackendFramework,
|
||||||
setupDbOrmTemplates,
|
setupDbOrmTemplates,
|
||||||
|
setupDeploymentTemplates,
|
||||||
setupExamplesTemplate,
|
setupExamplesTemplate,
|
||||||
setupFrontendTemplates,
|
setupFrontendTemplates,
|
||||||
} from "./template-manager";
|
} from "./template-manager";
|
||||||
@@ -49,6 +51,8 @@ export async function createProject(options: ProjectConfig) {
|
|||||||
}
|
}
|
||||||
await setupAddonsTemplate(projectDir, options);
|
await setupAddonsTemplate(projectDir, options);
|
||||||
|
|
||||||
|
await setupDeploymentTemplates(projectDir, options);
|
||||||
|
|
||||||
await setupApi(options);
|
await setupApi(options);
|
||||||
|
|
||||||
if (!isConvex) {
|
if (!isConvex) {
|
||||||
@@ -70,6 +74,8 @@ export async function createProject(options: ProjectConfig) {
|
|||||||
|
|
||||||
await handleExtras(projectDir, options);
|
await handleExtras(projectDir, options);
|
||||||
|
|
||||||
|
await setupWebDeploy(options);
|
||||||
|
|
||||||
await setupEnvironmentVariables(options);
|
await setupEnvironmentVariables(options);
|
||||||
await updatePackageConfigurations(projectDir, options);
|
await updatePackageConfigurations(projectDir, options);
|
||||||
await createReadme(projectDir, options);
|
await createReadme(projectDir, options);
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ export async function detectProjectConfig(
|
|||||||
packageManager: btsConfig.packageManager,
|
packageManager: btsConfig.packageManager,
|
||||||
dbSetup: btsConfig.dbSetup,
|
dbSetup: btsConfig.dbSetup,
|
||||||
api: btsConfig.api,
|
api: btsConfig.api,
|
||||||
|
webDeploy: btsConfig.webDeploy,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,5 +30,5 @@ export async function initializeGit(
|
|||||||
}
|
}
|
||||||
|
|
||||||
await $({ cwd: projectDir })`git add -A`;
|
await $({ cwd: projectDir })`git add -A`;
|
||||||
await $({ cwd: projectDir })`git commit -m ${"Initial commit"}`;
|
await $({ cwd: projectDir })`git commit -m ${"initial commit"}`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -287,7 +287,7 @@ function getTauriInstructions(runCmd?: string): string {
|
|||||||
function getPwaInstructions(): string {
|
function getPwaInstructions(): string {
|
||||||
return `\n${pc.bold("PWA with React Router v7:")}\n${pc.yellow(
|
return `\n${pc.bold("PWA with React Router v7:")}\n${pc.yellow(
|
||||||
"NOTE:",
|
"NOTE:",
|
||||||
)} There is a known compatibility issue between VitePWA and React Router v7.\nSee: https://github.com/vite-pwa/vite-plugin-pwa/issues/809`;
|
)} There is a known compatibility issue between VitePWA \nand React Router v7.See: https://github.com/vite-pwa/vite-plugin-pwa/issues/809`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getStarlightInstructions(runCmd?: string): string {
|
function getStarlightInstructions(runCmd?: string): string {
|
||||||
|
|||||||
@@ -832,3 +832,47 @@ export async function handleExtras(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function setupDeploymentTemplates(
|
||||||
|
projectDir: string,
|
||||||
|
context: ProjectConfig,
|
||||||
|
): Promise<void> {
|
||||||
|
if (context.webDeploy === "none") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context.webDeploy === "workers") {
|
||||||
|
const webAppDir = path.join(projectDir, "apps/web");
|
||||||
|
if (!(await fs.pathExists(webAppDir))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const frontends = context.frontend;
|
||||||
|
|
||||||
|
const templateMap: Record<string, string> = {
|
||||||
|
"tanstack-router": "react/tanstack-router",
|
||||||
|
"react-router": "react/react-router",
|
||||||
|
solid: "solid",
|
||||||
|
next: "react/next",
|
||||||
|
nuxt: "nuxt",
|
||||||
|
svelte: "svelte",
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const f of frontends) {
|
||||||
|
if (templateMap[f]) {
|
||||||
|
const deployTemplateSrc = path.join(
|
||||||
|
PKG_ROOT,
|
||||||
|
`templates/deploy/web/${templateMap[f]}`,
|
||||||
|
);
|
||||||
|
if (await fs.pathExists(deployTemplateSrc)) {
|
||||||
|
await processAndCopyFiles(
|
||||||
|
"**/*",
|
||||||
|
deployTemplateSrc,
|
||||||
|
webAppDir,
|
||||||
|
context,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import type { Frontend, ProjectConfig } from "../../types";
|
|||||||
import { addPackageDependency } from "../../utils/add-package-deps";
|
import { addPackageDependency } from "../../utils/add-package-deps";
|
||||||
import { setupStarlight } from "./starlight-setup";
|
import { setupStarlight } from "./starlight-setup";
|
||||||
import { setupTauri } from "./tauri-setup";
|
import { setupTauri } from "./tauri-setup";
|
||||||
|
import { addPwaToViteConfig } from "./vite-pwa-setup";
|
||||||
|
|
||||||
export async function setupAddons(config: ProjectConfig, isAddCommand = false) {
|
export async function setupAddons(config: ProjectConfig, isAddCommand = false) {
|
||||||
const { addons, frontend, projectDir } = config;
|
const { addons, frontend, projectDir } = config;
|
||||||
@@ -149,4 +150,10 @@ async function setupPwa(projectDir: string, frontends: Frontend[]) {
|
|||||||
|
|
||||||
await fs.writeJson(clientPackageJsonPath, packageJson, { spaces: 2 });
|
await fs.writeJson(clientPackageJsonPath, packageJson, { spaces: 2 });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const viteConfigTs = path.join(clientPackageDir, "vite.config.ts");
|
||||||
|
|
||||||
|
if (await fs.pathExists(viteConfigTs)) {
|
||||||
|
await addPwaToViteConfig(viteConfigTs, path.basename(projectDir));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
73
apps/cli/src/helpers/setup/vite-pwa-setup.ts
Normal file
73
apps/cli/src/helpers/setup/vite-pwa-setup.ts
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
import {
|
||||||
|
type CallExpression,
|
||||||
|
Node,
|
||||||
|
type ObjectLiteralExpression,
|
||||||
|
SyntaxKind,
|
||||||
|
} from "ts-morph";
|
||||||
|
import { ensureArrayProperty, tsProject } from "../../utils/ts-morph";
|
||||||
|
|
||||||
|
export async function addPwaToViteConfig(
|
||||||
|
viteConfigPath: string,
|
||||||
|
projectName: string,
|
||||||
|
): Promise<void> {
|
||||||
|
const sourceFile = tsProject.addSourceFileAtPathIfExists(viteConfigPath);
|
||||||
|
if (!sourceFile) {
|
||||||
|
throw new Error("vite config not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasImport = sourceFile
|
||||||
|
.getImportDeclarations()
|
||||||
|
.some((imp) => imp.getModuleSpecifierValue() === "vite-plugin-pwa");
|
||||||
|
|
||||||
|
if (!hasImport) {
|
||||||
|
sourceFile.insertImportDeclaration(0, {
|
||||||
|
namedImports: ["VitePWA"],
|
||||||
|
moduleSpecifier: "vite-plugin-pwa",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const defineCall = sourceFile
|
||||||
|
.getDescendantsOfKind(SyntaxKind.CallExpression)
|
||||||
|
.find((expr) => {
|
||||||
|
const expression = expr.getExpression();
|
||||||
|
return (
|
||||||
|
Node.isIdentifier(expression) && expression.getText() === "defineConfig"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!defineCall) {
|
||||||
|
throw new Error("Could not find defineConfig call in vite config");
|
||||||
|
}
|
||||||
|
|
||||||
|
const callExpr = defineCall as CallExpression;
|
||||||
|
const configObject = callExpr.getArguments()[0] as
|
||||||
|
| ObjectLiteralExpression
|
||||||
|
| undefined;
|
||||||
|
if (!configObject) {
|
||||||
|
throw new Error("defineConfig argument is not an object literal");
|
||||||
|
}
|
||||||
|
|
||||||
|
const pluginsArray = ensureArrayProperty(configObject, "plugins");
|
||||||
|
|
||||||
|
const alreadyPresent = pluginsArray
|
||||||
|
.getElements()
|
||||||
|
.some((el) => el.getText().startsWith("VitePWA("));
|
||||||
|
|
||||||
|
if (!alreadyPresent) {
|
||||||
|
pluginsArray.addElement(
|
||||||
|
`VitePWA({
|
||||||
|
registerType: "autoUpdate",
|
||||||
|
manifest: {
|
||||||
|
name: "${projectName}",
|
||||||
|
short_name: "${projectName}",
|
||||||
|
description: "${projectName} - PWA Application",
|
||||||
|
theme_color: "#0c0c0c",
|
||||||
|
},
|
||||||
|
pwaAssets: { disabled: false, config: true },
|
||||||
|
devOptions: { enabled: true },
|
||||||
|
})`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await tsProject.save();
|
||||||
|
}
|
||||||
89
apps/cli/src/helpers/setup/web-deploy-setup.ts
Normal file
89
apps/cli/src/helpers/setup/web-deploy-setup.ts
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
import path from "node:path";
|
||||||
|
import fs from "fs-extra";
|
||||||
|
import type { PackageManager, ProjectConfig } from "../../types";
|
||||||
|
import { addPackageDependency } from "../../utils/add-package-deps";
|
||||||
|
import { setupNuxtWorkersDeploy } from "./workers-nuxt-setup";
|
||||||
|
import { setupSvelteWorkersDeploy } from "./workers-svelte-setup";
|
||||||
|
import { setupWorkersVitePlugin } from "./workers-vite-setup";
|
||||||
|
|
||||||
|
export async function setupWebDeploy(config: ProjectConfig): Promise<void> {
|
||||||
|
const { webDeploy, frontend, projectDir } = config;
|
||||||
|
const { packageManager } = config;
|
||||||
|
|
||||||
|
if (webDeploy === "none") return;
|
||||||
|
|
||||||
|
if (webDeploy !== "workers") return;
|
||||||
|
|
||||||
|
const isNext = frontend.includes("next");
|
||||||
|
const isNuxt = frontend.includes("nuxt");
|
||||||
|
const isSvelte = frontend.includes("svelte");
|
||||||
|
const isTanstackRouter = frontend.includes("tanstack-router");
|
||||||
|
const isReactRouter = frontend.includes("react-router");
|
||||||
|
const isSolid = frontend.includes("solid");
|
||||||
|
|
||||||
|
if (isNext) {
|
||||||
|
await setupNextWorkersDeploy(projectDir, packageManager);
|
||||||
|
} else if (isNuxt) {
|
||||||
|
await setupNuxtWorkersDeploy(projectDir, packageManager);
|
||||||
|
} else if (isSvelte) {
|
||||||
|
await setupSvelteWorkersDeploy(projectDir, packageManager);
|
||||||
|
} else if (isTanstackRouter || isReactRouter || isSolid) {
|
||||||
|
await setupWorkersWebDeploy(projectDir, packageManager);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setupWorkersWebDeploy(
|
||||||
|
projectDir: string,
|
||||||
|
pkgManager: PackageManager,
|
||||||
|
): Promise<void> {
|
||||||
|
const webAppDir = path.join(projectDir, "apps/web");
|
||||||
|
|
||||||
|
if (!(await fs.pathExists(webAppDir))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const packageJsonPath = path.join(webAppDir, "package.json");
|
||||||
|
if (await fs.pathExists(packageJsonPath)) {
|
||||||
|
const packageJson = await fs.readJson(packageJsonPath);
|
||||||
|
|
||||||
|
packageJson.scripts = {
|
||||||
|
...packageJson.scripts,
|
||||||
|
"wrangler:dev": "wrangler dev --port=3001",
|
||||||
|
deploy: `${pkgManager} run build && wrangler deploy`,
|
||||||
|
};
|
||||||
|
|
||||||
|
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
||||||
|
}
|
||||||
|
|
||||||
|
await setupWorkersVitePlugin(projectDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setupNextWorkersDeploy(
|
||||||
|
projectDir: string,
|
||||||
|
_packageManager: PackageManager,
|
||||||
|
): Promise<void> {
|
||||||
|
const webAppDir = path.join(projectDir, "apps/web");
|
||||||
|
if (!(await fs.pathExists(webAppDir))) return;
|
||||||
|
|
||||||
|
await addPackageDependency({
|
||||||
|
dependencies: ["@opennextjs/cloudflare"],
|
||||||
|
devDependencies: ["wrangler"],
|
||||||
|
projectDir: webAppDir,
|
||||||
|
});
|
||||||
|
|
||||||
|
const packageJsonPath = path.join(webAppDir, "package.json");
|
||||||
|
if (await fs.pathExists(packageJsonPath)) {
|
||||||
|
const pkg = await fs.readJson(packageJsonPath);
|
||||||
|
|
||||||
|
pkg.scripts = {
|
||||||
|
...pkg.scripts,
|
||||||
|
preview: "opennextjs-cloudflare build && opennextjs-cloudflare preview",
|
||||||
|
deploy: "opennextjs-cloudflare build && opennextjs-cloudflare deploy",
|
||||||
|
upload: "opennextjs-cloudflare build && opennextjs-cloudflare upload",
|
||||||
|
"cf-typegen":
|
||||||
|
"wrangler types --env-interface CloudflareEnv cloudflare-env.d.ts",
|
||||||
|
};
|
||||||
|
|
||||||
|
await fs.writeJson(packageJsonPath, pkg, { spaces: 2 });
|
||||||
|
}
|
||||||
|
}
|
||||||
112
apps/cli/src/helpers/setup/workers-nuxt-setup.ts
Normal file
112
apps/cli/src/helpers/setup/workers-nuxt-setup.ts
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
import path from "node:path";
|
||||||
|
import fs from "fs-extra";
|
||||||
|
import {
|
||||||
|
type ArrayLiteralExpression,
|
||||||
|
type CallExpression,
|
||||||
|
Node,
|
||||||
|
type ObjectLiteralExpression,
|
||||||
|
type PropertyAssignment,
|
||||||
|
SyntaxKind,
|
||||||
|
} from "ts-morph";
|
||||||
|
import type { PackageManager } from "../../types";
|
||||||
|
import { addPackageDependency } from "../../utils/add-package-deps";
|
||||||
|
import { tsProject } from "../../utils/ts-morph";
|
||||||
|
|
||||||
|
export async function setupNuxtWorkersDeploy(
|
||||||
|
projectDir: string,
|
||||||
|
packageManager: PackageManager,
|
||||||
|
): Promise<void> {
|
||||||
|
const webAppDir = path.join(projectDir, "apps/web");
|
||||||
|
if (!(await fs.pathExists(webAppDir))) return;
|
||||||
|
|
||||||
|
await addPackageDependency({
|
||||||
|
devDependencies: ["nitro-cloudflare-dev", "wrangler"],
|
||||||
|
projectDir: webAppDir,
|
||||||
|
});
|
||||||
|
|
||||||
|
const pkgPath = path.join(webAppDir, "package.json");
|
||||||
|
if (await fs.pathExists(pkgPath)) {
|
||||||
|
const pkg = await fs.readJson(pkgPath);
|
||||||
|
|
||||||
|
pkg.scripts = {
|
||||||
|
...pkg.scripts,
|
||||||
|
deploy: `${packageManager} run build && wrangler deploy`,
|
||||||
|
"cf-typegen": "wrangler types",
|
||||||
|
};
|
||||||
|
await fs.writeJson(pkgPath, pkg, { spaces: 2 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const nuxtConfigPath = path.join(webAppDir, "nuxt.config.ts");
|
||||||
|
if (!(await fs.pathExists(nuxtConfigPath))) return;
|
||||||
|
|
||||||
|
const sourceFile = tsProject.addSourceFileAtPathIfExists(nuxtConfigPath);
|
||||||
|
if (!sourceFile) return;
|
||||||
|
|
||||||
|
const defineCall = sourceFile
|
||||||
|
.getDescendantsOfKind(SyntaxKind.CallExpression)
|
||||||
|
.find((expr) => {
|
||||||
|
const expression = expr.getExpression();
|
||||||
|
return (
|
||||||
|
Node.isIdentifier(expression) &&
|
||||||
|
expression.getText() === "defineNuxtConfig"
|
||||||
|
);
|
||||||
|
}) as CallExpression | undefined;
|
||||||
|
|
||||||
|
if (!defineCall) return;
|
||||||
|
|
||||||
|
const configObj = defineCall.getArguments()[0] as
|
||||||
|
| ObjectLiteralExpression
|
||||||
|
| undefined;
|
||||||
|
if (!configObj) return;
|
||||||
|
|
||||||
|
const today = new Date().toISOString().slice(0, 10);
|
||||||
|
|
||||||
|
const compatProp = configObj.getProperty("compatibilityDate");
|
||||||
|
if (compatProp && compatProp.getKind() === SyntaxKind.PropertyAssignment) {
|
||||||
|
(compatProp as PropertyAssignment).setInitializer(`'${today}'`);
|
||||||
|
} else {
|
||||||
|
configObj.addPropertyAssignment({
|
||||||
|
name: "compatibilityDate",
|
||||||
|
initializer: `'${today}'`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const nitroInitializer = `{
|
||||||
|
preset: "cloudflare_module",
|
||||||
|
cloudflare: {
|
||||||
|
deployConfig: true,
|
||||||
|
nodeCompat: true
|
||||||
|
}
|
||||||
|
}`;
|
||||||
|
const nitroProp = configObj.getProperty("nitro");
|
||||||
|
if (nitroProp && nitroProp.getKind() === SyntaxKind.PropertyAssignment) {
|
||||||
|
(nitroProp as PropertyAssignment).setInitializer(nitroInitializer);
|
||||||
|
} else {
|
||||||
|
configObj.addPropertyAssignment({
|
||||||
|
name: "nitro",
|
||||||
|
initializer: nitroInitializer,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const modulesProp = configObj.getProperty("modules");
|
||||||
|
if (modulesProp && modulesProp.getKind() === SyntaxKind.PropertyAssignment) {
|
||||||
|
const arrayExpr = modulesProp.getFirstDescendantByKind(
|
||||||
|
SyntaxKind.ArrayLiteralExpression,
|
||||||
|
) as ArrayLiteralExpression | undefined;
|
||||||
|
if (arrayExpr) {
|
||||||
|
const alreadyHas = arrayExpr
|
||||||
|
.getElements()
|
||||||
|
.some(
|
||||||
|
(el) => el.getText().replace(/['"`]/g, "") === "nitro-cloudflare-dev",
|
||||||
|
);
|
||||||
|
if (!alreadyHas) arrayExpr.addElement("'nitro-cloudflare-dev'");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
configObj.addPropertyAssignment({
|
||||||
|
name: "modules",
|
||||||
|
initializer: "['nitro-cloudflare-dev']",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await tsProject.save();
|
||||||
|
}
|
||||||
74
apps/cli/src/helpers/setup/workers-svelte-setup.ts
Normal file
74
apps/cli/src/helpers/setup/workers-svelte-setup.ts
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
import path from "node:path";
|
||||||
|
import fs from "fs-extra";
|
||||||
|
import type { ImportDeclaration } from "ts-morph";
|
||||||
|
import type { PackageManager } from "../../types";
|
||||||
|
import { addPackageDependency } from "../../utils/add-package-deps";
|
||||||
|
import { tsProject } from "../../utils/ts-morph";
|
||||||
|
|
||||||
|
export async function setupSvelteWorkersDeploy(
|
||||||
|
projectDir: string,
|
||||||
|
packageManager: PackageManager,
|
||||||
|
): Promise<void> {
|
||||||
|
const webAppDir = path.join(projectDir, "apps/web");
|
||||||
|
if (!(await fs.pathExists(webAppDir))) return;
|
||||||
|
|
||||||
|
await addPackageDependency({
|
||||||
|
devDependencies: ["@sveltejs/adapter-cloudflare", "wrangler"],
|
||||||
|
projectDir: webAppDir,
|
||||||
|
});
|
||||||
|
|
||||||
|
const pkgPath = path.join(webAppDir, "package.json");
|
||||||
|
if (await fs.pathExists(pkgPath)) {
|
||||||
|
const pkg = await fs.readJson(pkgPath);
|
||||||
|
pkg.scripts = {
|
||||||
|
...pkg.scripts,
|
||||||
|
deploy: `${packageManager} run build && wrangler deploy`,
|
||||||
|
"cf-typegen": "wrangler types ./src/worker-configuration.d.ts",
|
||||||
|
};
|
||||||
|
await fs.writeJson(pkgPath, pkg, { spaces: 2 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const possibleConfigFiles = [
|
||||||
|
path.join(webAppDir, "svelte.config.js"),
|
||||||
|
path.join(webAppDir, "svelte.config.ts"),
|
||||||
|
];
|
||||||
|
|
||||||
|
const existingConfigPath = (
|
||||||
|
await Promise.all(
|
||||||
|
possibleConfigFiles.map(async (p) => ((await fs.pathExists(p)) ? p : "")),
|
||||||
|
)
|
||||||
|
).find((p) => p);
|
||||||
|
|
||||||
|
if (existingConfigPath) {
|
||||||
|
const sourceFile =
|
||||||
|
tsProject.addSourceFileAtPathIfExists(existingConfigPath);
|
||||||
|
if (!sourceFile) return;
|
||||||
|
|
||||||
|
const adapterImport = sourceFile
|
||||||
|
.getImportDeclarations()
|
||||||
|
.find((imp: ImportDeclaration) =>
|
||||||
|
["@sveltejs/adapter-auto", "@sveltejs/adapter-node"].includes(
|
||||||
|
imp.getModuleSpecifierValue(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (adapterImport) {
|
||||||
|
adapterImport.setModuleSpecifier("@sveltejs/adapter-cloudflare");
|
||||||
|
} else {
|
||||||
|
const alreadyHasCloudflare = sourceFile
|
||||||
|
.getImportDeclarations()
|
||||||
|
.some(
|
||||||
|
(imp) =>
|
||||||
|
imp.getModuleSpecifierValue() === "@sveltejs/adapter-cloudflare",
|
||||||
|
);
|
||||||
|
if (!alreadyHasCloudflare) {
|
||||||
|
sourceFile.insertImportDeclaration(0, {
|
||||||
|
defaultImport: "adapter",
|
||||||
|
moduleSpecifier: "@sveltejs/adapter-cloudflare",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await tsProject.save();
|
||||||
|
}
|
||||||
|
}
|
||||||
76
apps/cli/src/helpers/setup/workers-vite-setup.ts
Normal file
76
apps/cli/src/helpers/setup/workers-vite-setup.ts
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
import path from "node:path";
|
||||||
|
import fs from "fs-extra";
|
||||||
|
import {
|
||||||
|
type CallExpression,
|
||||||
|
Node,
|
||||||
|
type ObjectLiteralExpression,
|
||||||
|
SyntaxKind,
|
||||||
|
} from "ts-morph";
|
||||||
|
import { addPackageDependency } from "../../utils/add-package-deps";
|
||||||
|
import { ensureArrayProperty, tsProject } from "../../utils/ts-morph";
|
||||||
|
|
||||||
|
export async function setupWorkersVitePlugin(
|
||||||
|
projectDir: string,
|
||||||
|
): Promise<void> {
|
||||||
|
const webAppDir = path.join(projectDir, "apps/web");
|
||||||
|
const viteConfigPath = path.join(webAppDir, "vite.config.ts");
|
||||||
|
|
||||||
|
if (!(await fs.pathExists(viteConfigPath))) {
|
||||||
|
throw new Error("vite.config.ts not found in web app directory");
|
||||||
|
}
|
||||||
|
|
||||||
|
await addPackageDependency({
|
||||||
|
devDependencies: ["@cloudflare/vite-plugin", "wrangler"],
|
||||||
|
projectDir: webAppDir,
|
||||||
|
});
|
||||||
|
|
||||||
|
const sourceFile = tsProject.addSourceFileAtPathIfExists(viteConfigPath);
|
||||||
|
if (!sourceFile) {
|
||||||
|
throw new Error("vite.config.ts not found in web app directory");
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasCloudflareImport = sourceFile
|
||||||
|
.getImportDeclarations()
|
||||||
|
.some((imp) => imp.getModuleSpecifierValue() === "@cloudflare/vite-plugin");
|
||||||
|
|
||||||
|
if (!hasCloudflareImport) {
|
||||||
|
sourceFile.insertImportDeclaration(0, {
|
||||||
|
namedImports: ["cloudflare"],
|
||||||
|
moduleSpecifier: "@cloudflare/vite-plugin",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const defineCall = sourceFile
|
||||||
|
.getDescendantsOfKind(SyntaxKind.CallExpression)
|
||||||
|
.find((expr) => {
|
||||||
|
const expression = expr.getExpression();
|
||||||
|
return (
|
||||||
|
Node.isIdentifier(expression) && expression.getText() === "defineConfig"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!defineCall) {
|
||||||
|
throw new Error("Could not find defineConfig call in vite config");
|
||||||
|
}
|
||||||
|
|
||||||
|
const callExpr = defineCall as CallExpression;
|
||||||
|
const configObject = callExpr.getArguments()[0] as
|
||||||
|
| ObjectLiteralExpression
|
||||||
|
| undefined;
|
||||||
|
|
||||||
|
if (!configObject) {
|
||||||
|
throw new Error("defineConfig argument is not an object literal");
|
||||||
|
}
|
||||||
|
|
||||||
|
const pluginsArray = ensureArrayProperty(configObject, "plugins");
|
||||||
|
|
||||||
|
const hasCloudflarePlugin = pluginsArray
|
||||||
|
.getElements()
|
||||||
|
.some((el) => el.getText().includes("cloudflare("));
|
||||||
|
|
||||||
|
if (!hasCloudflarePlugin) {
|
||||||
|
pluginsArray.addElement("cloudflare()");
|
||||||
|
}
|
||||||
|
|
||||||
|
await tsProject.save();
|
||||||
|
}
|
||||||
@@ -18,6 +18,7 @@ import {
|
|||||||
PackageManagerSchema,
|
PackageManagerSchema,
|
||||||
ProjectNameSchema,
|
ProjectNameSchema,
|
||||||
RuntimeSchema,
|
RuntimeSchema,
|
||||||
|
WebDeploySchema,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
import { getLatestCLIVersion } from "./utils/get-latest-cli-version";
|
import { getLatestCLIVersion } from "./utils/get-latest-cli-version";
|
||||||
import { openUrl } from "./utils/open-url";
|
import { openUrl } from "./utils/open-url";
|
||||||
@@ -55,6 +56,7 @@ const router = t.router({
|
|||||||
backend: BackendSchema.optional(),
|
backend: BackendSchema.optional(),
|
||||||
runtime: RuntimeSchema.optional(),
|
runtime: RuntimeSchema.optional(),
|
||||||
api: APISchema.optional(),
|
api: APISchema.optional(),
|
||||||
|
webDeploy: WebDeploySchema.optional(),
|
||||||
})
|
})
|
||||||
.optional()
|
.optional()
|
||||||
.default({}),
|
.default({}),
|
||||||
@@ -70,19 +72,23 @@ const router = t.router({
|
|||||||
}),
|
}),
|
||||||
add: t.procedure
|
add: t.procedure
|
||||||
.meta({
|
.meta({
|
||||||
description: "Add addons to an existing Better-T Stack project",
|
description:
|
||||||
|
"Add addons or deployment configurations to an existing Better-T Stack project",
|
||||||
})
|
})
|
||||||
.input(
|
.input(
|
||||||
z.tuple([
|
z.tuple([
|
||||||
z
|
z
|
||||||
.object({
|
.object({
|
||||||
addons: z.array(AddonsSchema).optional().default([]),
|
addons: z.array(AddonsSchema).optional().default([]),
|
||||||
|
webDeploy: WebDeploySchema.optional(),
|
||||||
projectDir: z.string().optional(),
|
projectDir: z.string().optional(),
|
||||||
install: z
|
install: z
|
||||||
.boolean()
|
.boolean()
|
||||||
.optional()
|
.optional()
|
||||||
.default(false)
|
.default(false)
|
||||||
.describe("Install dependencies after adding addons"),
|
.describe(
|
||||||
|
"Install dependencies after adding addons or deployment",
|
||||||
|
),
|
||||||
packageManager: PackageManagerSchema.optional(),
|
packageManager: PackageManagerSchema.optional(),
|
||||||
})
|
})
|
||||||
.optional()
|
.optional()
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ export async function getAddonsToAdd(
|
|||||||
const response = await multiselect<Addons>({
|
const response = await multiselect<Addons>({
|
||||||
message: "Select addons",
|
message: "Select addons",
|
||||||
options: options,
|
options: options,
|
||||||
required: true,
|
required: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isCancel(response)) {
|
if (isCancel(response)) {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import type {
|
|||||||
PackageManager,
|
PackageManager,
|
||||||
ProjectConfig,
|
ProjectConfig,
|
||||||
Runtime,
|
Runtime,
|
||||||
|
WebDeploy,
|
||||||
} from "../types";
|
} from "../types";
|
||||||
import { getAddonsChoice } from "./addons";
|
import { getAddonsChoice } from "./addons";
|
||||||
import { getApiChoice } from "./api";
|
import { getApiChoice } from "./api";
|
||||||
@@ -26,6 +27,7 @@ import { getinstallChoice } from "./install";
|
|||||||
import { getORMChoice } from "./orm";
|
import { getORMChoice } from "./orm";
|
||||||
import { getPackageManagerChoice } from "./package-manager";
|
import { getPackageManagerChoice } from "./package-manager";
|
||||||
import { getRuntimeChoice } from "./runtime";
|
import { getRuntimeChoice } from "./runtime";
|
||||||
|
import { getDeploymentChoice } from "./web-deploy";
|
||||||
|
|
||||||
type PromptGroupResults = {
|
type PromptGroupResults = {
|
||||||
frontend: Frontend[];
|
frontend: Frontend[];
|
||||||
@@ -41,6 +43,7 @@ type PromptGroupResults = {
|
|||||||
git: boolean;
|
git: boolean;
|
||||||
packageManager: PackageManager;
|
packageManager: PackageManager;
|
||||||
install: boolean;
|
install: boolean;
|
||||||
|
webDeploy: WebDeploy;
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function gatherConfig(
|
export async function gatherConfig(
|
||||||
@@ -87,6 +90,13 @@ export async function gatherConfig(
|
|||||||
results.backend,
|
results.backend,
|
||||||
results.runtime,
|
results.runtime,
|
||||||
),
|
),
|
||||||
|
webDeploy: ({ results }) =>
|
||||||
|
getDeploymentChoice(
|
||||||
|
flags.webDeploy,
|
||||||
|
results.runtime,
|
||||||
|
results.backend,
|
||||||
|
results.frontend,
|
||||||
|
),
|
||||||
git: () => getGitChoice(flags.git),
|
git: () => getGitChoice(flags.git),
|
||||||
packageManager: () => getPackageManagerChoice(flags.packageManager),
|
packageManager: () => getPackageManagerChoice(flags.packageManager),
|
||||||
install: () => getinstallChoice(flags.install),
|
install: () => getinstallChoice(flags.install),
|
||||||
@@ -107,6 +117,7 @@ export async function gatherConfig(
|
|||||||
result.auth = false;
|
result.auth = false;
|
||||||
result.dbSetup = "none";
|
result.dbSetup = "none";
|
||||||
result.examples = ["todo"];
|
result.examples = ["todo"];
|
||||||
|
result.webDeploy = "none";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result.backend === "none") {
|
if (result.backend === "none") {
|
||||||
@@ -117,6 +128,7 @@ export async function gatherConfig(
|
|||||||
result.auth = false;
|
result.auth = false;
|
||||||
result.dbSetup = "none";
|
result.dbSetup = "none";
|
||||||
result.examples = [];
|
result.examples = [];
|
||||||
|
result.webDeploy = "none";
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -136,5 +148,6 @@ export async function gatherConfig(
|
|||||||
install: result.install,
|
install: result.install,
|
||||||
dbSetup: result.dbSetup,
|
dbSetup: result.dbSetup,
|
||||||
api: result.api,
|
api: result.api,
|
||||||
|
webDeploy: result.webDeploy,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ export async function getRuntimeChoice(
|
|||||||
if (backend === "hono") {
|
if (backend === "hono") {
|
||||||
runtimeOptions.push({
|
runtimeOptions.push({
|
||||||
value: "workers",
|
value: "workers",
|
||||||
label: "Cloudflare Workers (beta)",
|
label: "Cloudflare Workers",
|
||||||
hint: "Edge runtime on Cloudflare's global network",
|
hint: "Edge runtime on Cloudflare's global network",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
122
apps/cli/src/prompts/web-deploy.ts
Normal file
122
apps/cli/src/prompts/web-deploy.ts
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
import { cancel, isCancel, select } from "@clack/prompts";
|
||||||
|
import pc from "picocolors";
|
||||||
|
import { DEFAULT_CONFIG } from "../constants";
|
||||||
|
import type { Backend, Frontend, Runtime, WebDeploy } from "../types";
|
||||||
|
|
||||||
|
const WORKERS_COMPATIBLE_FRONTENDS: Frontend[] = [
|
||||||
|
"tanstack-router",
|
||||||
|
"react-router",
|
||||||
|
"solid",
|
||||||
|
"next",
|
||||||
|
"nuxt",
|
||||||
|
"svelte",
|
||||||
|
];
|
||||||
|
|
||||||
|
type DeploymentOption = {
|
||||||
|
value: WebDeploy;
|
||||||
|
label: string;
|
||||||
|
hint: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
function getDeploymentDisplay(deployment: WebDeploy): {
|
||||||
|
label: string;
|
||||||
|
hint: string;
|
||||||
|
} {
|
||||||
|
if (deployment === "workers") {
|
||||||
|
return {
|
||||||
|
label: "Cloudflare Workers",
|
||||||
|
hint: "Deploy to Cloudflare Workers using Wrangler",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
label: deployment,
|
||||||
|
hint: `Add ${deployment} deployment`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getDeploymentChoice(
|
||||||
|
deployment?: WebDeploy,
|
||||||
|
_runtime?: Runtime,
|
||||||
|
_backend?: Backend,
|
||||||
|
frontend: Frontend[] = [],
|
||||||
|
): Promise<WebDeploy> {
|
||||||
|
if (deployment !== undefined) return deployment;
|
||||||
|
|
||||||
|
const hasCompatibleFrontend = frontend.some((f) =>
|
||||||
|
WORKERS_COMPATIBLE_FRONTENDS.includes(f),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!hasCompatibleFrontend) {
|
||||||
|
return "none";
|
||||||
|
}
|
||||||
|
|
||||||
|
const options: DeploymentOption[] = [
|
||||||
|
{
|
||||||
|
value: "workers",
|
||||||
|
label: "Cloudflare Workers",
|
||||||
|
hint: "Deploy to Cloudflare Workers using Wrangler",
|
||||||
|
},
|
||||||
|
{ value: "none", label: "None", hint: "Manual setup" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const response = await select<WebDeploy>({
|
||||||
|
message: "Select web deployment",
|
||||||
|
options,
|
||||||
|
initialValue: DEFAULT_CONFIG.webDeploy,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isCancel(response)) {
|
||||||
|
cancel(pc.red("Operation cancelled"));
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getDeploymentToAdd(
|
||||||
|
frontend: Frontend[],
|
||||||
|
existingDeployment?: WebDeploy,
|
||||||
|
): Promise<WebDeploy> {
|
||||||
|
const options: DeploymentOption[] = [];
|
||||||
|
|
||||||
|
if (
|
||||||
|
frontend.some((f) => WORKERS_COMPATIBLE_FRONTENDS.includes(f)) &&
|
||||||
|
existingDeployment !== "workers"
|
||||||
|
) {
|
||||||
|
const { label, hint } = getDeploymentDisplay("workers");
|
||||||
|
options.push({
|
||||||
|
value: "workers",
|
||||||
|
label,
|
||||||
|
hint,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existingDeployment && existingDeployment !== "none") {
|
||||||
|
return "none";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.length > 0) {
|
||||||
|
options.push({
|
||||||
|
value: "none",
|
||||||
|
label: "None",
|
||||||
|
hint: "Skip deployment setup",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.length === 0) {
|
||||||
|
return "none";
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await select<WebDeploy>({
|
||||||
|
message: "Select web deployment",
|
||||||
|
options,
|
||||||
|
initialValue: DEFAULT_CONFIG.webDeploy,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isCancel(response)) {
|
||||||
|
cancel(pc.red("Operation cancelled"));
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
@@ -92,6 +92,11 @@ export const ProjectNameSchema = z
|
|||||||
.describe("Project name or path");
|
.describe("Project name or path");
|
||||||
export type ProjectName = z.infer<typeof ProjectNameSchema>;
|
export type ProjectName = z.infer<typeof ProjectNameSchema>;
|
||||||
|
|
||||||
|
export const WebDeploySchema = z
|
||||||
|
.enum(["workers", "none"])
|
||||||
|
.describe("Web deployment");
|
||||||
|
export type WebDeploy = z.infer<typeof WebDeploySchema>;
|
||||||
|
|
||||||
export type CreateInput = {
|
export type CreateInput = {
|
||||||
projectName?: string;
|
projectName?: string;
|
||||||
yes?: boolean;
|
yes?: boolean;
|
||||||
@@ -108,10 +113,12 @@ export type CreateInput = {
|
|||||||
backend?: Backend;
|
backend?: Backend;
|
||||||
runtime?: Runtime;
|
runtime?: Runtime;
|
||||||
api?: API;
|
api?: API;
|
||||||
|
webDeploy?: WebDeploy;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type AddInput = {
|
export type AddInput = {
|
||||||
addons?: Addons[];
|
addons?: Addons[];
|
||||||
|
webDeploy?: WebDeploy;
|
||||||
projectDir?: string;
|
projectDir?: string;
|
||||||
install?: boolean;
|
install?: boolean;
|
||||||
packageManager?: PackageManager;
|
packageManager?: PackageManager;
|
||||||
@@ -138,6 +145,7 @@ export interface ProjectConfig {
|
|||||||
install: boolean;
|
install: boolean;
|
||||||
dbSetup: DatabaseSetup;
|
dbSetup: DatabaseSetup;
|
||||||
api: API;
|
api: API;
|
||||||
|
webDeploy: WebDeploy;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BetterTStackConfig {
|
export interface BetterTStackConfig {
|
||||||
@@ -154,6 +162,7 @@ export interface BetterTStackConfig {
|
|||||||
packageManager: PackageManager;
|
packageManager: PackageManager;
|
||||||
dbSetup: DatabaseSetup;
|
dbSetup: DatabaseSetup;
|
||||||
api: API;
|
api: API;
|
||||||
|
webDeploy: WebDeploy;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AvailablePackageManagers = "npm" | "pnpm" | "bun";
|
export type AvailablePackageManagers = "npm" | "pnpm" | "bun";
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ export async function writeBtsConfig(
|
|||||||
packageManager: projectConfig.packageManager,
|
packageManager: projectConfig.packageManager,
|
||||||
dbSetup: projectConfig.dbSetup,
|
dbSetup: projectConfig.dbSetup,
|
||||||
api: projectConfig.api,
|
api: projectConfig.api,
|
||||||
|
webDeploy: projectConfig.webDeploy,
|
||||||
};
|
};
|
||||||
|
|
||||||
const baseContent = {
|
const baseContent = {
|
||||||
@@ -40,6 +41,7 @@ export async function writeBtsConfig(
|
|||||||
packageManager: btsConfig.packageManager,
|
packageManager: btsConfig.packageManager,
|
||||||
dbSetup: btsConfig.dbSetup,
|
dbSetup: btsConfig.dbSetup,
|
||||||
api: btsConfig.api,
|
api: btsConfig.api,
|
||||||
|
webDeploy: btsConfig.webDeploy,
|
||||||
};
|
};
|
||||||
|
|
||||||
let configContent = JSON.stringify(baseContent);
|
let configContent = JSON.stringify(baseContent);
|
||||||
@@ -91,7 +93,7 @@ export async function readBtsConfig(
|
|||||||
|
|
||||||
export async function updateBtsConfig(
|
export async function updateBtsConfig(
|
||||||
projectDir: string,
|
projectDir: string,
|
||||||
updates: Partial<Pick<BetterTStackConfig, "addons">>,
|
updates: Partial<Pick<BetterTStackConfig, "addons" | "webDeploy">>,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const configPath = path.join(projectDir, BTS_CONFIG_FILE);
|
const configPath = path.join(projectDir, BTS_CONFIG_FILE);
|
||||||
|
|||||||
@@ -101,6 +101,12 @@ export function displayConfig(config: Partial<ProjectConfig>) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (config.webDeploy !== undefined) {
|
||||||
|
configDisplay.push(
|
||||||
|
`${pc.blue("Web Deployment:")} ${String(config.webDeploy)}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (configDisplay.length === 0) {
|
if (configDisplay.length === 0) {
|
||||||
return pc.yellow("No configuration selected.");
|
return pc.yellow("No configuration selected.");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ export function generateReproducibleCommand(config: ProjectConfig): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
flags.push(`--db-setup ${config.dbSetup}`);
|
flags.push(`--db-setup ${config.dbSetup}`);
|
||||||
|
flags.push(`--web-deploy ${config.webDeploy}`);
|
||||||
flags.push(config.git ? "--git" : "--no-git");
|
flags.push(config.git ? "--git" : "--no-git");
|
||||||
flags.push(`--package-manager ${config.packageManager}`);
|
flags.push(`--package-manager ${config.packageManager}`);
|
||||||
flags.push(config.install ? "--install" : "--no-install");
|
flags.push(config.install ? "--install" : "--no-install");
|
||||||
|
|||||||
31
apps/cli/src/utils/ts-morph.ts
Normal file
31
apps/cli/src/utils/ts-morph.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import {
|
||||||
|
type ArrayLiteralExpression,
|
||||||
|
IndentationText,
|
||||||
|
type ObjectLiteralExpression,
|
||||||
|
Project,
|
||||||
|
QuoteKind,
|
||||||
|
SyntaxKind,
|
||||||
|
} from "ts-morph";
|
||||||
|
|
||||||
|
export const tsProject = new Project({
|
||||||
|
useInMemoryFileSystem: false,
|
||||||
|
skipAddingFilesFromTsConfig: true,
|
||||||
|
manipulationSettings: {
|
||||||
|
quoteKind: QuoteKind.Single,
|
||||||
|
indentationText: IndentationText.TwoSpaces,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export function ensureArrayProperty(
|
||||||
|
obj: ObjectLiteralExpression,
|
||||||
|
name: string,
|
||||||
|
): ArrayLiteralExpression {
|
||||||
|
return (obj
|
||||||
|
.getProperty(name)
|
||||||
|
?.getFirstDescendantByKind(SyntaxKind.ArrayLiteralExpression) ??
|
||||||
|
obj
|
||||||
|
.addPropertyAssignment({ name, initializer: "[]" })
|
||||||
|
.getFirstDescendantByKindOrThrow(
|
||||||
|
SyntaxKind.ArrayLiteralExpression,
|
||||||
|
)) as ArrayLiteralExpression;
|
||||||
|
}
|
||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
type ProjectConfig,
|
type ProjectConfig,
|
||||||
ProjectNameSchema,
|
ProjectNameSchema,
|
||||||
type Runtime,
|
type Runtime,
|
||||||
|
type WebDeploy,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
|
|
||||||
export function processAndValidateFlags(
|
export function processAndValidateFlags(
|
||||||
@@ -82,6 +83,10 @@ export function processAndValidateFlags(
|
|||||||
config.packageManager = options.packageManager as PackageManager;
|
config.packageManager = options.packageManager as PackageManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options.webDeploy) {
|
||||||
|
config.webDeploy = options.webDeploy as WebDeploy;
|
||||||
|
}
|
||||||
|
|
||||||
if (projectName) {
|
if (projectName) {
|
||||||
const result = ProjectNameSchema.safeParse(path.basename(projectName));
|
const result = ProjectNameSchema.safeParse(path.basename(projectName));
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
@@ -446,6 +451,24 @@ export function processAndValidateFlags(
|
|||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
config.webDeploy === "workers" &&
|
||||||
|
config.frontend &&
|
||||||
|
config.frontend.length > 0
|
||||||
|
) {
|
||||||
|
const incompatibleFrontends = config.frontend.filter(
|
||||||
|
(f) => f === "tanstack-start",
|
||||||
|
);
|
||||||
|
if (incompatibleFrontends.length > 0) {
|
||||||
|
consola.fatal(
|
||||||
|
`The following frontends are not compatible with '--web-deploy workers': ${incompatibleFrontends.join(
|
||||||
|
", ",
|
||||||
|
)}. Please choose a different frontend or remove '--web-deploy workers'.`,
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
51
apps/cli/templates/deploy/web/nuxt/wrangler.jsonc.hbs
Normal file
51
apps/cli/templates/deploy/web/nuxt/wrangler.jsonc.hbs
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
/**
|
||||||
|
* For more details on how to configure Wrangler, refer to:
|
||||||
|
* https://developers.cloudflare.com/workers/wrangler/configuration/
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
"$schema": "../../node_modules/wrangler/config-schema.json",
|
||||||
|
"name": "{{projectName}}",
|
||||||
|
"main": "./.output/server/index.mjs",
|
||||||
|
"compatibility_date": "2025-07-01",
|
||||||
|
"assets": {
|
||||||
|
"binding": "ASSETS",
|
||||||
|
"directory": "./.output/public/"
|
||||||
|
},
|
||||||
|
"observability": {
|
||||||
|
"enabled": true
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Smart Placement
|
||||||
|
* Docs: https://developers.cloudflare.com/workers/configuration/smart-placement/#smart-placement
|
||||||
|
*/
|
||||||
|
// "placement": { "mode": "smart" },
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bindings
|
||||||
|
* Bindings allow your Worker to interact with resources on the Cloudflare Developer Platform, including
|
||||||
|
* databases, object storage, AI inference, real-time communication and more.
|
||||||
|
* https://developers.cloudflare.com/workers/runtime-apis/bindings/
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Environment Variables
|
||||||
|
* https://developers.cloudflare.com/workers/wrangler/configuration/#environment-variables
|
||||||
|
*/
|
||||||
|
// "vars": { "MY_VARIABLE": "production_value" },
|
||||||
|
/**
|
||||||
|
* Note: Use secrets to store sensitive data.
|
||||||
|
* https://developers.cloudflare.com/workers/configuration/secrets/
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Static Assets
|
||||||
|
* https://developers.cloudflare.com/workers/static-assets/binding/
|
||||||
|
*/
|
||||||
|
// "assets": { "directory": "./public/", "binding": "ASSETS" },
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service Bindings (communicate between multiple Workers)
|
||||||
|
* https://developers.cloudflare.com/workers/wrangler/configuration/#service-bindings
|
||||||
|
*/
|
||||||
|
// "services": [{ "binding": "MY_SERVICE", "service": "my-service" }]
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import { defineCloudflareConfig } from "@opennextjs/cloudflare/config";
|
||||||
|
// import r2IncrementalCache from "@opennextjs/cloudflare/overrides/incremental-cache/r2-incremental-cache";
|
||||||
|
|
||||||
|
export default defineCloudflareConfig({
|
||||||
|
// incrementalCache: r2IncrementalCache,
|
||||||
|
});
|
||||||
22
apps/cli/templates/deploy/web/react/next/wrangler.jsonc.hbs
Normal file
22
apps/cli/templates/deploy/web/react/next/wrangler.jsonc.hbs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"$schema": "../../node_modules/wrangler/config-schema.json",
|
||||||
|
"main": ".open-next/worker.js",
|
||||||
|
"name": "{{projectName}}",
|
||||||
|
"compatibility_date": "2025-07-05",
|
||||||
|
"compatibility_flags": ["nodejs_compat", "global_fetch_strictly_public"],
|
||||||
|
"assets": {
|
||||||
|
"directory": ".open-next/assets",
|
||||||
|
"binding": "ASSETS"
|
||||||
|
},
|
||||||
|
// "r2_buckets": [
|
||||||
|
// // Use R2 incremental cache
|
||||||
|
// // See https://opennext.js.org/cloudflare/caching
|
||||||
|
// {
|
||||||
|
// "binding": "NEXT_INC_CACHE_R2_BUCKET",
|
||||||
|
// // Create the bucket before deploying
|
||||||
|
// // You can change the bucket name if you want
|
||||||
|
// // See https://developers.cloudflare.com/workers/wrangler/commands/#r2-bucket-create
|
||||||
|
// "bucket_name": "cache"
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"$schema": "../../node_modules/wrangler/config-schema.json",
|
||||||
|
"name": "{{projectName}}",
|
||||||
|
"compatibility_date": "2025-04-03",
|
||||||
|
"assets": {
|
||||||
|
"not_found_handling": "single-page-application"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"$schema": "../../node_modules/wrangler/config-schema.json",
|
||||||
|
"name": "{{projectName}}",
|
||||||
|
"compatibility_date": "2025-04-03",
|
||||||
|
"assets": {
|
||||||
|
"not_found_handling": "single-page-application"
|
||||||
|
}
|
||||||
|
}
|
||||||
8
apps/cli/templates/deploy/web/solid/wrangler.jsonc.hbs
Normal file
8
apps/cli/templates/deploy/web/solid/wrangler.jsonc.hbs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"$schema": "../../node_modules/wrangler/config-schema.json",
|
||||||
|
"name": "{{projectName}}",
|
||||||
|
"compatibility_date": "2025-04-03",
|
||||||
|
"assets": {
|
||||||
|
"not_found_handling": "single-page-application"
|
||||||
|
}
|
||||||
|
}
|
||||||
51
apps/cli/templates/deploy/web/svelte/wrangler.jsonc.hbs
Normal file
51
apps/cli/templates/deploy/web/svelte/wrangler.jsonc.hbs
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
/**
|
||||||
|
* For more details on how to configure Wrangler, refer to:
|
||||||
|
* https://developers.cloudflare.com/workers/wrangler/configuration/
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
"$schema": "../../node_modules/wrangler/config-schema.json",
|
||||||
|
"name": "{{projectName}}",
|
||||||
|
"main": ".svelte-kit/cloudflare/_worker.js",
|
||||||
|
"compatibility_date": "2025-07-05",
|
||||||
|
"assets": {
|
||||||
|
"binding": "ASSETS",
|
||||||
|
"directory": ".svelte-kit/cloudflare"
|
||||||
|
},
|
||||||
|
"observability": {
|
||||||
|
"enabled": true
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Smart Placement
|
||||||
|
* Docs: https://developers.cloudflare.com/workers/configuration/smart-placement/#smart-placement
|
||||||
|
*/
|
||||||
|
// "placement": { "mode": "smart" },
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bindings
|
||||||
|
* Bindings allow your Worker to interact with resources on the Cloudflare Developer Platform, including
|
||||||
|
* databases, object storage, AI inference, real-time communication and more.
|
||||||
|
* https://developers.cloudflare.com/workers/runtime-apis/bindings/
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Environment Variables
|
||||||
|
* https://developers.cloudflare.com/workers/wrangler/configuration/#environment-variables
|
||||||
|
*/
|
||||||
|
// "vars": { "MY_VARIABLE": "production_value" },
|
||||||
|
/**
|
||||||
|
* Note: Use secrets to store sensitive data.
|
||||||
|
* https://developers.cloudflare.com/workers/configuration/secrets/
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Static Assets
|
||||||
|
* https://developers.cloudflare.com/workers/static-assets/binding/
|
||||||
|
*/
|
||||||
|
// "assets": { "directory": "./public/", "binding": "ASSETS" },
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service Bindings (communicate between multiple Workers)
|
||||||
|
* https://developers.cloudflare.com/workers/wrangler/configuration/#service-bindings
|
||||||
|
*/
|
||||||
|
// "services": [{ "binding": "MY_SERVICE", "service": "my-service" }]
|
||||||
|
}
|
||||||
@@ -1,6 +1,3 @@
|
|||||||
{{#if (includes addons "pwa")}}
|
|
||||||
import { VitePWA } from "vite-plugin-pwa";
|
|
||||||
{{/if}}
|
|
||||||
import { reactRouter } from "@react-router/dev/vite";
|
import { reactRouter } from "@react-router/dev/vite";
|
||||||
import tailwindcss from "@tailwindcss/vite";
|
import tailwindcss from "@tailwindcss/vite";
|
||||||
import { defineConfig } from "vite";
|
import { defineConfig } from "vite";
|
||||||
@@ -11,23 +8,5 @@ export default defineConfig({
|
|||||||
tailwindcss(),
|
tailwindcss(),
|
||||||
reactRouter(),
|
reactRouter(),
|
||||||
tsconfigPaths(),
|
tsconfigPaths(),
|
||||||
{{#if (includes addons "pwa")}}
|
|
||||||
VitePWA({
|
|
||||||
registerType: "autoUpdate",
|
|
||||||
manifest: {
|
|
||||||
name: "{{projectName}}",
|
|
||||||
short_name: "{{projectName}}",
|
|
||||||
description: "{{projectName}} - PWA Application",
|
|
||||||
theme_color: "#0c0c0c",
|
|
||||||
},
|
|
||||||
pwaAssets: {
|
|
||||||
disabled: false,
|
|
||||||
config: true,
|
|
||||||
},
|
|
||||||
devOptions: {
|
|
||||||
enabled: true,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
{{/if}}
|
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
@@ -1,8 +1,5 @@
|
|||||||
{{#if (includes addons "pwa")}}
|
|
||||||
import { VitePWA } from "vite-plugin-pwa";
|
|
||||||
{{/if}}
|
|
||||||
import tailwindcss from "@tailwindcss/vite";
|
import tailwindcss from "@tailwindcss/vite";
|
||||||
import { TanStackRouterVite } from "@tanstack/router-plugin/vite";
|
import { tanstackRouter } from "@tanstack/router-plugin/vite";
|
||||||
import react from "@vitejs/plugin-react";
|
import react from "@vitejs/plugin-react";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { defineConfig } from "vite";
|
import { defineConfig } from "vite";
|
||||||
@@ -10,26 +7,8 @@ import { defineConfig } from "vite";
|
|||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [
|
plugins: [
|
||||||
tailwindcss(),
|
tailwindcss(),
|
||||||
TanStackRouterVite({}),
|
tanstackRouter({}),
|
||||||
react(),
|
react(),
|
||||||
{{#if (includes addons "pwa")}}
|
|
||||||
VitePWA({
|
|
||||||
registerType: "autoUpdate",
|
|
||||||
manifest: {
|
|
||||||
name: "{{projectName}}",
|
|
||||||
short_name: "{{projectName}}",
|
|
||||||
description: "{{projectName}} - PWA Application",
|
|
||||||
theme_color: "#0c0c0c",
|
|
||||||
},
|
|
||||||
pwaAssets: {
|
|
||||||
disabled: false,
|
|
||||||
config: true,
|
|
||||||
},
|
|
||||||
devOptions: {
|
|
||||||
enabled: true,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
{{/if}}
|
|
||||||
],
|
],
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
|
|||||||
@@ -17,14 +17,14 @@
|
|||||||
"@tanstack/react-start": "^1.121.0-alpha.27",
|
"@tanstack/react-start": "^1.121.0-alpha.27",
|
||||||
"@tanstack/router-plugin": "^1.121.0",
|
"@tanstack/router-plugin": "^1.121.0",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"lucide-react": "^0.473.0",
|
"lucide-react": "^0.525.0",
|
||||||
"next-themes": "^0.4.6",
|
"next-themes": "^0.4.6",
|
||||||
"react": "19.0.0",
|
"react": "19.0.0",
|
||||||
"react-dom": "19.0.0",
|
"react-dom": "19.0.0",
|
||||||
"sonner": "^2.0.3",
|
"sonner": "^2.0.3",
|
||||||
"tailwindcss": "^4.1.3",
|
"tailwindcss": "^4.1.3",
|
||||||
"tailwind-merge": "^2.6.0",
|
"tailwind-merge": "^3.3.1",
|
||||||
"tw-animate-css": "^1.2.5",
|
"tw-animate-css": "^1.2.5",
|
||||||
"vite-tsconfig-paths": "^5.1.4",
|
"vite-tsconfig-paths": "^5.1.4",
|
||||||
"zod": "^3.25.16"
|
"zod": "^3.25.16"
|
||||||
@@ -38,7 +38,7 @@
|
|||||||
"@vitejs/plugin-react": "^4.5.2",
|
"@vitejs/plugin-react": "^4.5.2",
|
||||||
"jsdom": "^26.0.0",
|
"jsdom": "^26.0.0",
|
||||||
"typescript": "^5.7.2",
|
"typescript": "^5.7.2",
|
||||||
"vite": "^6.3.5",
|
"vite": "^7.0.2",
|
||||||
"web-vitals": "^4.2.4"
|
"web-vitals": "^5.0.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -50,3 +50,8 @@ next-env.d.ts
|
|||||||
|
|
||||||
# Other
|
# Other
|
||||||
dev-dist
|
dev-dist
|
||||||
|
|
||||||
|
.wrangler
|
||||||
|
.dev.vars*
|
||||||
|
|
||||||
|
.open-next
|
||||||
|
|||||||
@@ -5,3 +5,6 @@ dist-ssr
|
|||||||
*.local
|
*.local
|
||||||
.env
|
.env
|
||||||
.env.*
|
.env.*
|
||||||
|
|
||||||
|
.wrangler
|
||||||
|
.dev.vars*
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite --port 3001",
|
"dev": "vite --port 3001",
|
||||||
"build": "vite build && tsc",
|
"build": "vite build",
|
||||||
"serve": "vite preview",
|
"serve": "vite preview",
|
||||||
"test": "vitest run"
|
"test": "vitest run"
|
||||||
},
|
},
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"typescript": "^5.7.2",
|
"typescript": "^5.7.2",
|
||||||
"vite": "^6.0.11",
|
"vite": "^7.0.2",
|
||||||
"vite-plugin-solid": "^2.11.2"
|
"vite-plugin-solid": "^2.11.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { routeTree } from "./routeTree.gen";
|
|||||||
import "./styles.css";
|
import "./styles.css";
|
||||||
{{#if (eq api "orpc")}}
|
{{#if (eq api "orpc")}}
|
||||||
import { QueryClientProvider } from "@tanstack/solid-query";
|
import { QueryClientProvider } from "@tanstack/solid-query";
|
||||||
import { queryClient } from "./utils/orpc";
|
import { orpc, queryClient } from "./utils/orpc";
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
@@ -12,6 +12,9 @@ const router = createRouter({
|
|||||||
defaultPreload: "intent",
|
defaultPreload: "intent",
|
||||||
scrollRestoration: true,
|
scrollRestoration: true,
|
||||||
defaultPreloadStaleTime: 0,
|
defaultPreloadStaleTime: 0,
|
||||||
|
{{#if (eq api "orpc")}}
|
||||||
|
context: { orpc, queryClient },
|
||||||
|
{{/if}}
|
||||||
});
|
});
|
||||||
|
|
||||||
declare module "@tanstack/solid-router" {
|
declare module "@tanstack/solid-router" {
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
import { defineConfig } from "vite";
|
|
||||||
import { TanStackRouterVite } from "@tanstack/router-plugin/vite";
|
|
||||||
import solidPlugin from "vite-plugin-solid";
|
|
||||||
import tailwindcss from "@tailwindcss/vite";
|
|
||||||
import path from "node:path";
|
|
||||||
{{#if (includes addons "pwa")}}
|
|
||||||
import { VitePWA } from "vite-plugin-pwa";
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
plugins: [
|
|
||||||
TanStackRouterVite({ target: "solid", autoCodeSplitting: true }),
|
|
||||||
solidPlugin(),
|
|
||||||
tailwindcss(),
|
|
||||||
{{#if (includes addons "pwa")}}
|
|
||||||
VitePWA({
|
|
||||||
registerType: "autoUpdate",
|
|
||||||
manifest: {
|
|
||||||
name: "{{projectName}}",
|
|
||||||
short_name: "{{projectName}}",
|
|
||||||
description: "{{projectName}} - PWA Application",
|
|
||||||
theme_color: "#0c0c0c",
|
|
||||||
},
|
|
||||||
pwaAssets: {
|
|
||||||
disabled: false,
|
|
||||||
config: true,
|
|
||||||
},
|
|
||||||
devOptions: {
|
|
||||||
enabled: true,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
{{/if}}
|
|
||||||
],
|
|
||||||
resolve: {
|
|
||||||
alias: {
|
|
||||||
"@": path.resolve(__dirname, "./src"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
18
apps/cli/templates/frontend/solid/vite.config.ts.hbs
Normal file
18
apps/cli/templates/frontend/solid/vite.config.ts.hbs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { defineConfig } from "vite";
|
||||||
|
import { tanstackRouter } from "@tanstack/router-plugin/vite";
|
||||||
|
import solidPlugin from "vite-plugin-solid";
|
||||||
|
import tailwindcss from "@tailwindcss/vite";
|
||||||
|
import path from "node:path";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [
|
||||||
|
tanstackRouter({ target: "solid", autoCodeSplitting: true }),
|
||||||
|
solidPlugin(),
|
||||||
|
tailwindcss(),
|
||||||
|
],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
"@": path.resolve(__dirname, "./src"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
"tailwindcss": "^4.1.4",
|
"tailwindcss": "^4.1.4",
|
||||||
"typescript": "^5.8.3",
|
"typescript": "^5.8.3",
|
||||||
"@tanstack/svelte-query-devtools": "^5.74.6",
|
"@tanstack/svelte-query-devtools": "^5.74.6",
|
||||||
"vite": "^6.3.3"
|
"vite": "^7.0.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tanstack/svelte-form": "^1.7.0",
|
"@tanstack/svelte-form": "^1.7.0",
|
||||||
|
|||||||
@@ -140013,6 +140013,6 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"lastUpdated": "Jul 1, 2025, 03:45 AM",
|
"lastUpdated": "Jul 1, 2025, 03:45 AM",
|
||||||
"generatedAt": "2025-07-01T04:23:00.971Z",
|
"generatedAt": "2025-07-05T10:12:56.694Z",
|
||||||
"totalRecords": 5637
|
"totalRecords": 5637
|
||||||
}
|
}
|
||||||
@@ -144,6 +144,14 @@
|
|||||||
"none"
|
"none"
|
||||||
],
|
],
|
||||||
"description": "API type"
|
"description": "API type"
|
||||||
|
},
|
||||||
|
"webDeploy": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"workers",
|
||||||
|
"none"
|
||||||
|
],
|
||||||
|
"description": "Web deployment"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
@@ -159,7 +167,8 @@
|
|||||||
"auth",
|
"auth",
|
||||||
"packageManager",
|
"packageManager",
|
||||||
"dbSetup",
|
"dbSetup",
|
||||||
"api"
|
"api",
|
||||||
|
"webDeploy"
|
||||||
],
|
],
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
}
|
}
|
||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
ORMSchema,
|
ORMSchema,
|
||||||
PackageManagerSchema,
|
PackageManagerSchema,
|
||||||
RuntimeSchema,
|
RuntimeSchema,
|
||||||
|
WebDeploySchema,
|
||||||
} from "../../cli/src/types";
|
} from "../../cli/src/types";
|
||||||
|
|
||||||
const DATABASE_VALUES = DatabaseSchema.options;
|
const DATABASE_VALUES = DatabaseSchema.options;
|
||||||
@@ -23,91 +24,97 @@ const EXAMPLES_VALUES = ExamplesSchema.options;
|
|||||||
const PACKAGE_MANAGER_VALUES = PackageManagerSchema.options;
|
const PACKAGE_MANAGER_VALUES = PackageManagerSchema.options;
|
||||||
const DATABASE_SETUP_VALUES = DatabaseSetupSchema.options;
|
const DATABASE_SETUP_VALUES = DatabaseSetupSchema.options;
|
||||||
const API_VALUES = APISchema.options;
|
const API_VALUES = APISchema.options;
|
||||||
|
const WEB_DEPLOY_VALUES = WebDeploySchema.options;
|
||||||
|
|
||||||
const schema = {
|
const configSchema = {
|
||||||
$schema: "http://json-schema.org/draft-07/schema#",
|
$schema: "http://json-schema.org/draft-07/schema#",
|
||||||
$id: "https://better-t-stack.dev/schema.json",
|
$id: "https://better-t-stack.dev/schema.json",
|
||||||
title: "Better-T-Stack Configuration",
|
title: "Better-T-Stack Configuration",
|
||||||
description: "Configuration file for Better-T-Stack projects",
|
description: "Configuration file for Better-T-Stack projects",
|
||||||
type: "object" as const,
|
type: "object",
|
||||||
properties: {
|
properties: {
|
||||||
$schema: {
|
$schema: {
|
||||||
type: "string" as const,
|
type: "string",
|
||||||
description: "JSON Schema reference for validation",
|
description: "JSON Schema reference for validation",
|
||||||
},
|
},
|
||||||
version: {
|
version: {
|
||||||
type: "string" as const,
|
type: "string",
|
||||||
description: "CLI version used to create this project",
|
description: "CLI version used to create this project",
|
||||||
pattern: "^\\d+\\.\\d+\\.\\d+$",
|
pattern: "^\\d+\\.\\d+\\.\\d+$",
|
||||||
},
|
},
|
||||||
createdAt: {
|
createdAt: {
|
||||||
type: "string" as const,
|
type: "string",
|
||||||
format: "date-time" as const,
|
format: "date-time",
|
||||||
description: "Timestamp when the project was created",
|
description: "Timestamp when the project was created",
|
||||||
},
|
},
|
||||||
database: {
|
database: {
|
||||||
type: "string" as const,
|
type: "string",
|
||||||
enum: DATABASE_VALUES,
|
enum: DATABASE_VALUES,
|
||||||
description: DatabaseSchema.description,
|
description: DatabaseSchema.description,
|
||||||
},
|
},
|
||||||
orm: {
|
orm: {
|
||||||
type: "string" as const,
|
type: "string",
|
||||||
enum: ORM_VALUES,
|
enum: ORM_VALUES,
|
||||||
description: ORMSchema.description,
|
description: ORMSchema.description,
|
||||||
},
|
},
|
||||||
backend: {
|
backend: {
|
||||||
type: "string" as const,
|
type: "string",
|
||||||
enum: BACKEND_VALUES,
|
enum: BACKEND_VALUES,
|
||||||
description: BackendSchema.description,
|
description: BackendSchema.description,
|
||||||
},
|
},
|
||||||
runtime: {
|
runtime: {
|
||||||
type: "string" as const,
|
type: "string",
|
||||||
enum: RUNTIME_VALUES,
|
enum: RUNTIME_VALUES,
|
||||||
description: RuntimeSchema.description,
|
description: RuntimeSchema.description,
|
||||||
},
|
},
|
||||||
frontend: {
|
frontend: {
|
||||||
type: "array" as const,
|
type: "array",
|
||||||
items: {
|
items: {
|
||||||
type: "string" as const,
|
type: "string",
|
||||||
enum: FRONTEND_VALUES,
|
enum: FRONTEND_VALUES,
|
||||||
},
|
},
|
||||||
description: FrontendSchema.description,
|
description: FrontendSchema.description,
|
||||||
},
|
},
|
||||||
addons: {
|
addons: {
|
||||||
type: "array" as const,
|
type: "array",
|
||||||
items: {
|
items: {
|
||||||
type: "string" as const,
|
type: "string",
|
||||||
enum: ADDONS_VALUES,
|
enum: ADDONS_VALUES,
|
||||||
},
|
},
|
||||||
description: AddonsSchema.description,
|
description: AddonsSchema.description,
|
||||||
},
|
},
|
||||||
examples: {
|
examples: {
|
||||||
type: "array" as const,
|
type: "array",
|
||||||
items: {
|
items: {
|
||||||
type: "string" as const,
|
type: "string",
|
||||||
enum: EXAMPLES_VALUES,
|
enum: EXAMPLES_VALUES,
|
||||||
},
|
},
|
||||||
description: ExamplesSchema.description,
|
description: ExamplesSchema.description,
|
||||||
},
|
},
|
||||||
auth: {
|
auth: {
|
||||||
type: "boolean" as const,
|
type: "boolean",
|
||||||
description: "Whether authentication is enabled",
|
description: "Whether authentication is enabled",
|
||||||
},
|
},
|
||||||
packageManager: {
|
packageManager: {
|
||||||
type: "string" as const,
|
type: "string",
|
||||||
enum: PACKAGE_MANAGER_VALUES,
|
enum: PACKAGE_MANAGER_VALUES,
|
||||||
description: PackageManagerSchema.description,
|
description: PackageManagerSchema.description,
|
||||||
},
|
},
|
||||||
dbSetup: {
|
dbSetup: {
|
||||||
type: "string" as const,
|
type: "string",
|
||||||
enum: DATABASE_SETUP_VALUES,
|
enum: DATABASE_SETUP_VALUES,
|
||||||
description: DatabaseSetupSchema.description,
|
description: DatabaseSetupSchema.description,
|
||||||
},
|
},
|
||||||
api: {
|
api: {
|
||||||
type: "string" as const,
|
type: "string",
|
||||||
enum: API_VALUES,
|
enum: API_VALUES,
|
||||||
description: APISchema.description,
|
description: APISchema.description,
|
||||||
},
|
},
|
||||||
|
webDeploy: {
|
||||||
|
type: "string",
|
||||||
|
enum: WEB_DEPLOY_VALUES,
|
||||||
|
description: WebDeploySchema.description,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
required: [
|
required: [
|
||||||
"version",
|
"version",
|
||||||
@@ -123,6 +130,7 @@ const schema = {
|
|||||||
"packageManager",
|
"packageManager",
|
||||||
"dbSetup",
|
"dbSetup",
|
||||||
"api",
|
"api",
|
||||||
|
"webDeploy",
|
||||||
],
|
],
|
||||||
additionalProperties: false,
|
additionalProperties: false,
|
||||||
};
|
};
|
||||||
@@ -130,7 +138,11 @@ const schema = {
|
|||||||
async function generateSchema() {
|
async function generateSchema() {
|
||||||
const schemaPath = path.join(process.cwd(), "public", "schema.json");
|
const schemaPath = path.join(process.cwd(), "public", "schema.json");
|
||||||
await fs.ensureDir(path.dirname(schemaPath));
|
await fs.ensureDir(path.dirname(schemaPath));
|
||||||
await fs.writeFile(schemaPath, JSON.stringify(schema, null, 2), "utf-8");
|
await fs.writeFile(
|
||||||
|
schemaPath,
|
||||||
|
JSON.stringify(configSchema, null, 2),
|
||||||
|
"utf-8",
|
||||||
|
);
|
||||||
console.log("✅ Generated schema.json from shared types package");
|
console.log("✅ Generated schema.json from shared types package");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ const CATEGORY_ORDER: Array<keyof typeof TECH_OPTIONS> = [
|
|||||||
"database",
|
"database",
|
||||||
"orm",
|
"orm",
|
||||||
"dbSetup",
|
"dbSetup",
|
||||||
|
"webDeploy",
|
||||||
"auth",
|
"auth",
|
||||||
"packageManager",
|
"packageManager",
|
||||||
"addons",
|
"addons",
|
||||||
@@ -124,6 +125,7 @@ const getBadgeColors = (category: string): string => {
|
|||||||
case "packageManager":
|
case "packageManager":
|
||||||
return "border-orange-300 bg-orange-100 text-orange-800 dark:border-orange-700/30 dark:bg-orange-900/30 dark:text-orange-300";
|
return "border-orange-300 bg-orange-100 text-orange-800 dark:border-orange-700/30 dark:bg-orange-900/30 dark:text-orange-300";
|
||||||
case "git":
|
case "git":
|
||||||
|
case "webDeploy":
|
||||||
case "install":
|
case "install":
|
||||||
return "border-gray-300 bg-gray-100 text-gray-700 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-400";
|
return "border-gray-300 bg-gray-100 text-gray-700 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-400";
|
||||||
default:
|
default:
|
||||||
@@ -800,6 +802,28 @@ const analyzeStackCompatibility = (stack: StackState): CompatibilityResult => {
|
|||||||
if (nextStack.examples.length !== originalExamplesLength)
|
if (nextStack.examples.length !== originalExamplesLength)
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Web deploy compatibility: Workers not supported with TanStack Start
|
||||||
|
if (
|
||||||
|
nextStack.webDeploy === "workers" &&
|
||||||
|
nextStack.webFrontend.includes("tanstack-start")
|
||||||
|
) {
|
||||||
|
notes.webDeploy.notes.push(
|
||||||
|
"Cloudflare Workers deployment is not supported with TanStack Start. It will be set to 'None'.",
|
||||||
|
);
|
||||||
|
notes.webFrontend.notes.push(
|
||||||
|
"TanStack Start is not compatible with Cloudflare Workers deployment.",
|
||||||
|
);
|
||||||
|
notes.webDeploy.hasIssue = true;
|
||||||
|
notes.webFrontend.hasIssue = true;
|
||||||
|
nextStack.webDeploy = "none";
|
||||||
|
changed = true;
|
||||||
|
changes.push({
|
||||||
|
category: "webDeploy",
|
||||||
|
message:
|
||||||
|
"Web deployment set to 'None' (Workers not compatible with TanStack Start)",
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -899,6 +923,13 @@ const generateCommand = (stackState: StackState): string => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
stackState.webDeploy &&
|
||||||
|
!checkDefault("webDeploy", stackState.webDeploy)
|
||||||
|
) {
|
||||||
|
flags.push(`--web-deploy ${stackState.webDeploy}`);
|
||||||
|
}
|
||||||
|
|
||||||
if (!checkDefault("install", stackState.install)) {
|
if (!checkDefault("install", stackState.install)) {
|
||||||
if (stackState.install === "false" && DEFAULT_STACK.install === "true") {
|
if (stackState.install === "false" && DEFAULT_STACK.install === "true") {
|
||||||
flags.push("--no-install");
|
flags.push("--no-install");
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ export const TECH_OPTIONS = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "workers",
|
id: "workers",
|
||||||
name: "Cloudflare Workers (beta)",
|
name: "Cloudflare Workers",
|
||||||
description: "Serverless runtime for the edge",
|
description: "Serverless runtime for the edge",
|
||||||
icon: "/icon/workers.svg",
|
icon: "/icon/workers.svg",
|
||||||
color: "from-orange-400 to-orange-600",
|
color: "from-orange-400 to-orange-600",
|
||||||
@@ -320,6 +320,23 @@ export const TECH_OPTIONS = {
|
|||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
webDeploy: [
|
||||||
|
{
|
||||||
|
id: "workers",
|
||||||
|
name: "Cloudflare Workers",
|
||||||
|
description: "Deploy to Cloudflare Workers",
|
||||||
|
icon: "/icon/workers.svg",
|
||||||
|
color: "from-orange-400 to-orange-600",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "none",
|
||||||
|
name: "No Deployment",
|
||||||
|
description: "Skip deployment configuration",
|
||||||
|
icon: "",
|
||||||
|
color: "from-gray-400 to-gray-600",
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
auth: [
|
auth: [
|
||||||
{
|
{
|
||||||
id: "true",
|
id: "true",
|
||||||
@@ -594,6 +611,7 @@ export type StackState = {
|
|||||||
git: string;
|
git: string;
|
||||||
install: string;
|
install: string;
|
||||||
api: string;
|
api: string;
|
||||||
|
webDeploy: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DEFAULT_STACK: StackState = {
|
export const DEFAULT_STACK: StackState = {
|
||||||
@@ -612,6 +630,7 @@ export const DEFAULT_STACK: StackState = {
|
|||||||
git: "true",
|
git: "true",
|
||||||
install: "true",
|
install: "true",
|
||||||
api: "trpc",
|
api: "trpc",
|
||||||
|
webDeploy: "none",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const isStackDefault = <K extends keyof StackState>(
|
export const isStackDefault = <K extends keyof StackState>(
|
||||||
|
|||||||
@@ -51,6 +51,9 @@ export const stackParsers = {
|
|||||||
"true",
|
"true",
|
||||||
"false",
|
"false",
|
||||||
]).withDefault(DEFAULT_STACK.install),
|
]).withDefault(DEFAULT_STACK.install),
|
||||||
|
webDeploy: parseAsStringEnum<StackState["webDeploy"]>(
|
||||||
|
getValidIds("webDeploy"),
|
||||||
|
).withDefault(DEFAULT_STACK.webDeploy),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const stackUrlKeys: UrlKeys<typeof stackParsers> = {
|
export const stackUrlKeys: UrlKeys<typeof stackParsers> = {
|
||||||
@@ -69,6 +72,7 @@ export const stackUrlKeys: UrlKeys<typeof stackParsers> = {
|
|||||||
examples: "ex",
|
examples: "ex",
|
||||||
git: "git",
|
git: "git",
|
||||||
install: "i",
|
install: "i",
|
||||||
|
webDeploy: "wd",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const stackQueryStatesOptions = {
|
export const stackQueryStatesOptions = {
|
||||||
|
|||||||
15
bun.lock
15
bun.lock
@@ -30,6 +30,7 @@
|
|||||||
"picocolors": "^1.1.1",
|
"picocolors": "^1.1.1",
|
||||||
"posthog-node": "^5.1.1",
|
"posthog-node": "^5.1.1",
|
||||||
"trpc-cli": "^0.9.2",
|
"trpc-cli": "^0.9.2",
|
||||||
|
"ts-morph": "^26.0.0",
|
||||||
"zod": "^3.25.67",
|
"zod": "^3.25.67",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -287,6 +288,10 @@
|
|||||||
|
|
||||||
"@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.34.2", "", { "os": "win32", "cpu": "x64" }, "sha512-aUdT6zEYtDKCaxkofmmJDJYGCf0+pJg3eU9/oBuqvEeoB9dKI6ZLc/1iLJCTuJQDO4ptntAlkUmHgGjyuobZbw=="],
|
"@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.34.2", "", { "os": "win32", "cpu": "x64" }, "sha512-aUdT6zEYtDKCaxkofmmJDJYGCf0+pJg3eU9/oBuqvEeoB9dKI6ZLc/1iLJCTuJQDO4ptntAlkUmHgGjyuobZbw=="],
|
||||||
|
|
||||||
|
"@isaacs/balanced-match": ["@isaacs/balanced-match@4.0.1", "", {}, "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ=="],
|
||||||
|
|
||||||
|
"@isaacs/brace-expansion": ["@isaacs/brace-expansion@5.0.0", "", { "dependencies": { "@isaacs/balanced-match": "^4.0.1" } }, "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA=="],
|
||||||
|
|
||||||
"@isaacs/fs-minipass": ["@isaacs/fs-minipass@4.0.1", "", { "dependencies": { "minipass": "^7.0.4" } }, "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w=="],
|
"@isaacs/fs-minipass": ["@isaacs/fs-minipass@4.0.1", "", { "dependencies": { "minipass": "^7.0.4" } }, "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w=="],
|
||||||
|
|
||||||
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.8", "", { "dependencies": { "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA=="],
|
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.8", "", { "dependencies": { "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA=="],
|
||||||
@@ -553,6 +558,8 @@
|
|||||||
|
|
||||||
"@trpc/server": ["@trpc/server@11.4.2", "", { "peerDependencies": { "typescript": ">=5.7.2" } }, "sha512-THyq/V5bSFDHeWEAk6LqHF0IVTGk6voGwWsFEipzRRKOWWMIZINCsKZ4cISG6kWO2X9jBfMWv/S2o9hnC0zQ0w=="],
|
"@trpc/server": ["@trpc/server@11.4.2", "", { "peerDependencies": { "typescript": ">=5.7.2" } }, "sha512-THyq/V5bSFDHeWEAk6LqHF0IVTGk6voGwWsFEipzRRKOWWMIZINCsKZ4cISG6kWO2X9jBfMWv/S2o9hnC0zQ0w=="],
|
||||||
|
|
||||||
|
"@ts-morph/common": ["@ts-morph/common@0.27.0", "", { "dependencies": { "fast-glob": "^3.3.3", "minimatch": "^10.0.1", "path-browserify": "^1.0.1" } }, "sha512-Wf29UqxWDpc+i61k3oIOzcUfQt79PIT9y/MWfAGlrkjg6lBC1hwDECLXPVJAhWjiGbfBCxZd65F/LIZF3+jeJQ=="],
|
||||||
|
|
||||||
"@tybys/wasm-util": ["@tybys/wasm-util@0.9.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw=="],
|
"@tybys/wasm-util": ["@tybys/wasm-util@0.9.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw=="],
|
||||||
|
|
||||||
"@types/d3-array": ["@types/d3-array@3.2.1", "", {}, "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg=="],
|
"@types/d3-array": ["@types/d3-array@3.2.1", "", {}, "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg=="],
|
||||||
@@ -781,6 +788,8 @@
|
|||||||
|
|
||||||
"clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
|
"clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
|
||||||
|
|
||||||
|
"code-block-writer": ["code-block-writer@13.0.3", "", {}, "sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg=="],
|
||||||
|
|
||||||
"collapse-white-space": ["collapse-white-space@2.1.0", "", {}, "sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw=="],
|
"collapse-white-space": ["collapse-white-space@2.1.0", "", {}, "sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw=="],
|
||||||
|
|
||||||
"color": ["color@4.2.3", "", { "dependencies": { "color-convert": "^2.0.1", "color-string": "^1.9.0" } }, "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A=="],
|
"color": ["color@4.2.3", "", { "dependencies": { "color-convert": "^2.0.1", "color-string": "^1.9.0" } }, "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A=="],
|
||||||
@@ -1475,6 +1484,8 @@
|
|||||||
|
|
||||||
"parse-ms": ["parse-ms@4.0.0", "", {}, "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw=="],
|
"parse-ms": ["parse-ms@4.0.0", "", {}, "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw=="],
|
||||||
|
|
||||||
|
"path-browserify": ["path-browserify@1.0.1", "", {}, "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g=="],
|
||||||
|
|
||||||
"path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
|
"path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
|
||||||
|
|
||||||
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
|
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
|
||||||
@@ -1735,6 +1746,8 @@
|
|||||||
|
|
||||||
"ts-api-utils": ["ts-api-utils@2.1.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ=="],
|
"ts-api-utils": ["ts-api-utils@2.1.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ=="],
|
||||||
|
|
||||||
|
"ts-morph": ["ts-morph@26.0.0", "", { "dependencies": { "@ts-morph/common": "~0.27.0", "code-block-writer": "^13.0.3" } }, "sha512-ztMO++owQnz8c/gIENcM9XfCEzgoGphTv+nKpYNM1bgsdOVC/jRZuEBf6N+mLLDNg68Kl+GgUZfOySaRiG1/Ug=="],
|
||||||
|
|
||||||
"tsconfig-paths": ["tsconfig-paths@3.15.0", "", { "dependencies": { "@types/json5": "^0.0.29", "json5": "^1.0.2", "minimist": "^1.2.6", "strip-bom": "^3.0.0" } }, "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg=="],
|
"tsconfig-paths": ["tsconfig-paths@3.15.0", "", { "dependencies": { "@types/json5": "^0.0.29", "json5": "^1.0.2", "minimist": "^1.2.6", "strip-bom": "^3.0.0" } }, "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg=="],
|
||||||
|
|
||||||
"tsdown": ["tsdown@0.12.8", "", { "dependencies": { "ansis": "^4.1.0", "cac": "^6.7.14", "chokidar": "^4.0.3", "debug": "^4.4.1", "diff": "^8.0.2", "empathic": "^1.1.0", "hookable": "^5.5.3", "rolldown": "1.0.0-beta.15", "rolldown-plugin-dts": "^0.13.11", "semver": "^7.7.2", "tinyexec": "^1.0.1", "tinyglobby": "^0.2.14", "unconfig": "^7.3.2" }, "peerDependencies": { "@arethetypeswrong/core": "^0.18.1", "publint": "^0.3.0", "typescript": "^5.0.0", "unplugin-lightningcss": "^0.4.0", "unplugin-unused": "^0.5.0" }, "optionalPeers": ["@arethetypeswrong/core", "publint", "typescript", "unplugin-lightningcss", "unplugin-unused"], "bin": { "tsdown": "dist/run.mjs" } }, "sha512-niHeVcFCNjvVZYVGTeoM4BF+/DWxP8pFH2tUs71sEKYdcKtJIbkSdEmtxByaRZeMgwVbVgPb8nv9i9okVwFLAA=="],
|
"tsdown": ["tsdown@0.12.8", "", { "dependencies": { "ansis": "^4.1.0", "cac": "^6.7.14", "chokidar": "^4.0.3", "debug": "^4.4.1", "diff": "^8.0.2", "empathic": "^1.1.0", "hookable": "^5.5.3", "rolldown": "1.0.0-beta.15", "rolldown-plugin-dts": "^0.13.11", "semver": "^7.7.2", "tinyexec": "^1.0.1", "tinyglobby": "^0.2.14", "unconfig": "^7.3.2" }, "peerDependencies": { "@arethetypeswrong/core": "^0.18.1", "publint": "^0.3.0", "typescript": "^5.0.0", "unplugin-lightningcss": "^0.4.0", "unplugin-unused": "^0.5.0" }, "optionalPeers": ["@arethetypeswrong/core", "publint", "typescript", "unplugin-lightningcss", "unplugin-unused"], "bin": { "tsdown": "dist/run.mjs" } }, "sha512-niHeVcFCNjvVZYVGTeoM4BF+/DWxP8pFH2tUs71sEKYdcKtJIbkSdEmtxByaRZeMgwVbVgPb8nv9i9okVwFLAA=="],
|
||||||
@@ -1895,6 +1908,8 @@
|
|||||||
|
|
||||||
"@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
"@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||||
|
|
||||||
|
"@ts-morph/common/minimatch": ["minimatch@10.0.3", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw=="],
|
||||||
|
|
||||||
"@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
|
"@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
|
||||||
|
|
||||||
"create-better-t-stack/typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
|
"create-better-t-stack/typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
|
||||||
|
|||||||
Reference in New Issue
Block a user