Skip to content

Commit bc62d24

Browse files
C3: add vite+react templates with tests (#8013)
* chore: C3 - use comment-json to parse and stringify JSON files This makes it possible to preserve comments in JSON(C) files that contain them. * test: C3 - tiny refactoring of workers tests for wrangler config existence * feat: C3 - add experimental Vite+workerd on Workers+Assets template * test * add changeset * fixup! test * avoiding the grammar police * fix vitest warnings * tighten up c3 turbo inputs * improve user agent spoofing * skip react on yarn The create-vite app wants to process the npm_config_user_agent to get the version of yarn, but for some reason this gets stripped by the fact we are faking it and actually running pnpx. * further fixups * update template files to match target formatting * fix broken lock file * extend the 'waitForReady' timeout to avoid flakes on Windows * re-enable yarn tests for react+vite C3 template * add Cloudflare to the React+Vite framework starter * fix indentation * only override npm_config_user_agent for yarn if it is not already a yarn user agent * fix test expectations * fix e2e tests * fix e2e tests * fix up hono e2e test * fix up docusaurus e2e tests * maybe fix qwik * fixup! maybe fix qwik
1 parent c7bc2ba commit bc62d24

File tree

32 files changed

+591
-86
lines changed

32 files changed

+591
-86
lines changed

.changeset/nine-states-call.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"create-cloudflare": patch
3+
---
4+
5+
add experimental React templates using the Cloudflare Vite plugin

packages/create-cloudflare/e2e-tests/frameworks.test.ts

Lines changed: 51 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import assert from "assert";
12
import { existsSync } from "fs";
23
import { cp } from "fs/promises";
34
import { join } from "path";
@@ -101,6 +102,31 @@ function getFrameworkTests(opts: {
101102
},
102103
flags: ["--style", "sass"],
103104
},
105+
react: {
106+
promptHandlers: [
107+
{
108+
matcher: /Select a variant:/,
109+
input: [keys.enter],
110+
},
111+
],
112+
unsupportedOSs: ["win32"],
113+
testCommitMessage: true,
114+
verifyDeploy: {
115+
route: "/",
116+
// Note that this is the text in the static HTML that is returned
117+
// This React SPA will change this at runtime but we are only making a fetch request
118+
// not actually running the client side JS.
119+
expectedText: "Vite + React + TS",
120+
},
121+
verifyPreview: {
122+
route: "/",
123+
previewArgs: ["--host=127.0.0.1"],
124+
// Note that this is the text in the static HTML that is returned
125+
// This React SPA will change this at runtime but we are only making a fetch request
126+
// not actually running the client side JS.
127+
expectedText: "Vite + React + TS",
128+
},
129+
},
104130
gatsby: {
105131
unsupportedPms: ["bun", "pnpm"],
106132
promptHandlers: [
@@ -124,11 +150,11 @@ function getFrameworkTests(opts: {
124150
testCommitMessage: true,
125151
unsupportedOSs: ["win32"],
126152
verifyDeploy: {
127-
route: "/",
153+
route: "/message",
128154
expectedText: "Hello Hono!",
129155
},
130156
verifyPreview: {
131-
route: "/",
157+
route: "/message",
132158
expectedText: "Hello Hono!",
133159
},
134160
promptHandlers: [
@@ -145,6 +171,7 @@ function getFrameworkTests(opts: {
145171
input: [keys.enter],
146172
},
147173
],
174+
flags: [],
148175
testCommitMessage: true,
149176
unsupportedOSs: ["win32"],
150177
unsupportedPms: ["yarn"],
@@ -544,7 +571,6 @@ function getFrameworkTests(opts: {
544571
],
545572
testCommitMessage: true,
546573
unsupportedOSs: ["win32"],
547-
unsupportedPms: ["yarn"],
548574
timeout: LONG_TIMEOUT,
549575
verifyDeploy: {
550576
route: "/",
@@ -651,6 +677,10 @@ describe.concurrent(
651677

652678
test({ experimental }).runIf(shouldRunTest(frameworkId, testConfig))(
653679
frameworkId,
680+
{
681+
retry: TEST_RETRIES,
682+
timeout: testConfig.timeout || TEST_TIMEOUT,
683+
},
654684
async ({ logStream, project }) => {
655685
if (!testConfig.verifyDeploy) {
656686
expect(
@@ -734,10 +764,6 @@ describe.concurrent(
734764
}
735765
}
736766
},
737-
{
738-
retry: TEST_RETRIES,
739-
timeout: testConfig.timeout || TEST_TIMEOUT,
740-
},
741767
);
742768
});
743769
},
@@ -760,6 +786,7 @@ const runCli = async (
760786
framework,
761787
NO_DEPLOY ? "--no-deploy" : "--deploy",
762788
"--no-open",
789+
"--no-auto-update",
763790
];
764791

765792
args.push(...argv);
@@ -783,28 +810,29 @@ const runCli = async (
783810
};
784811

785812
/**
786-
* Either update or create a wrangler.toml to include a `TEST` var.
813+
* Either update or create a wrangler configuration file to include a `TEST` var.
787814
*
788-
* This is rather than having a wrangler.toml in the e2e test's fixture folder,
815+
* This is rather than having a wrangler configuration file in the e2e test's fixture folder,
789816
* which overwrites any that comes from the framework's template.
790817
*/
791818
const addTestVarsToWranglerToml = async (projectPath: string) => {
792819
const wranglerTomlPath = join(projectPath, "wrangler.toml");
793820
const wranglerJsoncPath = join(projectPath, "wrangler.jsonc");
821+
794822
if (existsSync(wranglerTomlPath)) {
795823
const wranglerToml = readToml(wranglerTomlPath);
796-
// Add a TEST var to the wrangler.toml
797824
wranglerToml.vars ??= {};
798825
(wranglerToml.vars as JsonMap).TEST = "C3_TEST";
799826

800827
writeToml(wranglerTomlPath, wranglerToml);
801828
} else if (existsSync(wranglerJsoncPath)) {
802-
const wranglerJson = readJSON(wranglerJsoncPath);
803-
// Add a TEST var to the wrangler.toml
804-
wranglerJson.vars ??= {};
805-
wranglerJson.vars.TEST = "C3_TEST";
829+
const wranglerJsonc = readJSON(wranglerJsoncPath) as {
830+
vars: Record<string, string>;
831+
};
832+
wranglerJsonc.vars ??= {};
833+
wranglerJsonc.vars.TEST = "C3_TEST";
806834

807-
writeJSON(wranglerJsoncPath, wranglerJson);
835+
writeJSON(wranglerJsoncPath, wranglerJsonc);
808836
}
809837
};
810838

@@ -841,10 +869,16 @@ const verifyPreviewScript = async (
841869
projectPath: string,
842870
logStream: Writable,
843871
) => {
844-
if (!verifyPreview || !previewScript) {
872+
if (!verifyPreview) {
845873
return;
846874
}
847875

876+
assert(
877+
previewScript,
878+
"Expected a preview script is we are verifying the preview in " +
879+
projectPath,
880+
);
881+
848882
// Run the dev-server on a random port to avoid colliding with other tests
849883
const TEST_PORT = Math.ceil(Math.random() * 1000) + 20000;
850884

@@ -856,6 +890,7 @@ const verifyPreviewScript = async (
856890
...(pm === "npm" ? ["--"] : []),
857891
"--port",
858892
`${TEST_PORT}`,
893+
...(verifyPreview.previewArgs ?? []),
859894
],
860895
{
861896
cwd: projectPath,

packages/create-cloudflare/e2e-tests/helpers.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ export type RunnerConfig = {
8484
* Specifies whether to run the preview script for the project and assert the response from the specified route.
8585
*/
8686
verifyPreview: null | {
87+
previewArgs?: string[];
8788
route: string;
8889
expectedText: string;
8990
};

packages/create-cloudflare/e2e-tests/workers.test.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { existsSync } from "fs";
12
import { join } from "path";
23
import { readJSON, readToml } from "helpers/files";
34
import { detectPackageManager } from "helpers/packageManagers";
@@ -177,6 +178,7 @@ describe
177178
const name = testConfig.name ?? testConfig.template;
178179
test({ experimental })(
179180
name,
181+
{ retry: 1, timeout: testConfig.timeout || TEST_TIMEOUT },
180182
async ({ project, logStream }) => {
181183
try {
182184
const deployedUrl = await runCli(
@@ -197,18 +199,20 @@ describe
197199
const tomlPath = join(project.path, "wrangler.toml");
198200
const jsoncPath = join(project.path, "wrangler.jsonc");
199201

200-
try {
201-
expect(jsoncPath).toExist();
202+
if (existsSync(jsoncPath)) {
202203
const config = readJSON(jsoncPath) as { main?: string };
203204
if (config.main) {
204205
expect(join(project.path, config.main)).toExist();
205206
}
206-
} catch (e) {
207-
expect(tomlPath).toExist();
207+
} else if (existsSync(tomlPath)) {
208208
const config = readToml(tomlPath) as { main?: string };
209209
if (config.main) {
210210
expect(join(project.path, config.main)).toExist();
211211
}
212+
} else {
213+
expect.fail(
214+
`Expected at least one of "${jsoncPath}" or "${tomlPath}" to exist.`,
215+
);
212216
}
213217

214218
const { verifyDeploy, verifyTest } = testConfig;
@@ -227,7 +231,6 @@ describe
227231
await deleteWorker(project.name);
228232
}
229233
},
230-
{ retry: 1, timeout: testConfig.timeout || TEST_TIMEOUT },
231234
);
232235
});
233236
});

packages/create-cloudflare/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
"@typescript-eslint/parser": "^6.9.0",
6767
"chalk": "^5.2.0",
6868
"command-exists": "^1.2.9",
69+
"comment-json": "^4.2.5",
6970
"cross-spawn": "^7.0.3",
7071
"deepmerge": "^4.3.1",
7172
"degit": "^2.8.4",

packages/create-cloudflare/src/frameworks/__tests__/index.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ describe("frameworks", () => {
3131
pm: "yarn",
3232
pmCmd: "npx",
3333
env: {
34-
npm_config_user_agent: "yarn",
34+
npm_config_user_agent: "yarn/1.22.22",
3535
},
3636
},
3737
{

packages/create-cloudflare/src/frameworks/index.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,10 @@ export const runFrameworkGenerator = async (ctx: C3Context, args: string[]) => {
3232
// So to retain the ability to lock versions we run it with `npx` and spoof
3333
// the user agent so scaffolding tools treat the invocation like yarn
3434
const cmd = [...(npm === "yarn" ? ["npx"] : dlx), cli, ...args];
35-
const env = npm === "yarn" ? { npm_config_user_agent: "yarn" } : {};
35+
const env =
36+
npm === "yarn" && !process.env.npm_config_user_agent?.startsWith("yarn")
37+
? { npm_config_user_agent: "yarn/1.22.22" }
38+
: {};
3639

3740
if (ctx.args.additionalArgs?.length) {
3841
cmd.push(...ctx.args.additionalArgs);

packages/create-cloudflare/src/helpers/files.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import fs, { existsSync, statSync } from "fs";
22
import { join } from "path";
33
import TOML from "@iarna/toml";
4-
import { parse } from "jsonc-parser";
4+
import { parse, stringify } from "comment-json";
55
import type { JsonMap } from "@iarna/toml";
6-
import type { C3Context } from "types";
6+
import type { C3Context, PackageJson } from "types";
77

88
export const copyFile = (path: string, dest: string) => {
99
try {
@@ -57,7 +57,7 @@ export const directoryExists = (path: string): boolean => {
5757
}
5858
};
5959

60-
export const readJSON = (path: string) => {
60+
export const readJSON = (path: string): unknown => {
6161
const contents = readFile(path);
6262
return contents ? parse(contents) : contents;
6363
};
@@ -69,10 +69,10 @@ export const readToml = (path: string) => {
6969

7070
export const writeJSON = (
7171
path: string,
72-
object: object,
72+
object: unknown,
7373
stringifySpace = "\t",
7474
) => {
75-
writeFile(path, JSON.stringify(object, null, stringifySpace));
75+
writeFile(path, stringify(object, null, stringifySpace));
7676
};
7777

7878
export const writeToml = (path: string, object: JsonMap) => {
@@ -136,8 +136,8 @@ export const usesEslint = (ctx: C3Context): EslintUsageInfo => {
136136
}
137137

138138
try {
139-
const pkgJson = readJSON(`${ctx.project.path}/package.json`);
140-
if (pkgJson.eslintConfig) {
139+
const pkgJson = readJSON(`${ctx.project.path}/package.json`) as PackageJson;
140+
if (pkgJson?.eslintConfig) {
141141
return {
142142
used: true,
143143
configType: "package.json",

packages/create-cloudflare/src/helpers/packageManagers.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export const detectPackageManager = () => {
2525
if (process.env.TEST_PM && process.env.TEST_PM_VERSION) {
2626
name = process.env.TEST_PM as PmName;
2727
version = process.env.TEST_PM_VERSION;
28-
process.env.npm_config_user_agent = name;
28+
process.env.npm_config_user_agent = `${name}/${version}`;
2929
}
3030

3131
switch (name) {

packages/create-cloudflare/src/templates.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import honoTemplateExperimental from "templates-experimental/hono/c3";
2929
import nextTemplateExperimental from "templates-experimental/next/c3";
3030
import nuxtTemplateExperimental from "templates-experimental/nuxt/c3";
3131
import qwikTemplateExperimental from "templates-experimental/qwik/c3";
32+
import reactTemplateExperimental from "templates-experimental/react/c3";
3233
import remixTemplateExperimental from "templates-experimental/remix/c3";
3334
import solidTemplateExperimental from "templates-experimental/solid/c3";
3435
import svelteTemplateExperimental from "templates-experimental/svelte/c3";
@@ -177,6 +178,7 @@ export function getFrameworkMap({ experimental = false }): TemplateMap {
177178
next: nextTemplateExperimental,
178179
nuxt: nuxtTemplateExperimental,
179180
qwik: qwikTemplateExperimental,
181+
react: reactTemplateExperimental,
180182
remix: remixTemplateExperimental,
181183
solid: solidTemplateExperimental,
182184
svelte: svelteTemplateExperimental,
@@ -717,7 +719,7 @@ export const updatePackageName = async (ctx: C3Context) => {
717719
// Update package.json with project name
718720
const placeholderNames = ["<TBD>", "TBD", ""];
719721
const pkgJsonPath = resolve(ctx.project.path, "package.json");
720-
const pkgJson = readJSON(pkgJsonPath);
722+
const pkgJson = readJSON(pkgJsonPath) as PackageJson;
721723

722724
if (!placeholderNames.includes(pkgJson.name)) {
723725
return;
@@ -741,11 +743,11 @@ export const updatePackageScripts = async (ctx: C3Context) => {
741743
s.start("Updating `package.json` scripts");
742744

743745
const pkgJsonPath = resolve(ctx.project.path, "package.json");
744-
let pkgJson = readJSON(pkgJsonPath);
746+
let pkgJson = readJSON(pkgJsonPath) as PackageJson;
745747

746748
// Run any transformers defined by the template
747749
const transformed = await ctx.template.transformPackageJson(pkgJson, ctx);
748-
pkgJson = deepmerge(pkgJson, transformed);
750+
pkgJson = deepmerge(pkgJson, transformed as PackageJson);
749751

750752
writeJSON(pkgJsonPath, pkgJson);
751753
s.stop(`${brandColor("updated")} ${dim("`package.json`")}`);

0 commit comments

Comments
 (0)