Skip to content

Commit

Permalink
C3-workers-assets-experimental-templates (#6678)
Browse files Browse the repository at this point in the history
* feat: add --experimental flag to allow access to experimental templates

* fix: do not crash if the chosen framework does not exist

* fix: offer to deploy workers with assets and no bindings

* feat: add experimental qwik workers + assets template

* feat: add experimental svelte workers + assets template

* feat: add experimental remix workers + assets template

* feat: add experimental astro workers + assets template

* feat: add hello world with assets template

* feat: add experimental solid workers + assets template

* feat: add experimental nuxt workers + assets template

* feat: filter the C3 help info when in experimental mode

* fix: mark new Workers + Assets experimental templates as "workers" platform

This avoids C3 from trying to create a Pages project for them.
Needed a bit of refactoring to ensure that these new templates still work even if not marked as "pages".

* feat: add experimental angular workers + assets template

* refactor: use mapped paths to import templates

* feat: add experimental docusaurus workers + assets template

* feat: add experimental gatsby workers + assets template

* add changesets
  • Loading branch information
petebacondarwin authored Sep 12, 2024
1 parent 85caaf2 commit 40ecf47
Show file tree
Hide file tree
Showing 104 changed files with 2,750 additions and 204 deletions.
5 changes: 5 additions & 0 deletions .changeset/silly-taxis-sort.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"create-cloudflare": patch
---

fix: do not crash if the chosen framework does not exist
5 changes: 5 additions & 0 deletions .changeset/stale-students-crash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"create-cloudflare": minor
---

feat: add experimental mode and associated workers + assets templates
13 changes: 7 additions & 6 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,15 @@ CHANGELOG.md
# In the C3 templates, in particular framework templates, we want to be able to
# use any format that the framework authors prefer/use in their own templates,
# so let's ignore all the c3 template files, exlcuding the c3.ts ones, and tests
packages/create-cloudflare/templates/**/*.*
!packages/create-cloudflare/templates/**/c3.ts
!packages/create-cloudflare/templates/**/test/**/*.ts
!packages/create-cloudflare/templates/**/test/**/*.js
packages/create-cloudflare/templates*/**/*.*
!packages/create-cloudflare/templates*/**/c3.ts
!packages/create-cloudflare/templates*/**/test/**/*.ts
!packages/create-cloudflare/templates*/**/test/**/*.js
# Format the hello-world template (negate previous exclusion above) for best practices, since we control these
# but still exclude the worker-configuration.d.ts file, since it's generated
!packages/create-cloudflare/templates/hello-world/**/*.*
packages/create-cloudflare/templates/hello-world/**/worker-configuration.d.ts
!packages/create-cloudflare/templates*/hello-world/**/*.*
packages/create-cloudflare/templates*/hello-world/**/worker-configuration.d.ts

# dist-functions are generated in the fixtures/vitest-pool-workers-examples/pages-functions-unit-integration-self folder
dist-functions

Expand Down
2 changes: 1 addition & 1 deletion packages/cli/interactive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ type Renderer = (
const renderSubmit = (config: PromptConfig, value: string) => {
const { question, label } = config;

if (config.type !== "confirm" && value.length === 0) {
if (config.type !== "confirm" && !value) {
return [`${leftT} ${question} ${dim("(skipped)")}`, `${grayBar}`];
}

Expand Down
3 changes: 2 additions & 1 deletion packages/create-cloudflare/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ module.exports = {
"dist",
"scripts",
"e2e-tests/fixtures/*",
"**/templates*/**/*.*",
// template files are ignored by the eslint-config-worker configuration
// we do however want the c3 files to be linted
"!**/templates/**/c3.ts",
"!**/templates*/**/c3.ts",
],
};
4 changes: 3 additions & 1 deletion packages/create-cloudflare/e2e-tests/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
test,
} from "vitest";
import { version } from "../package.json";
import { frameworkToTest } from "./frameworkToTest";
import { getFrameworkToTest } from "./frameworkToTest";
import {
createTestLogStream,
isQuarantineMode,
Expand All @@ -21,6 +21,8 @@ import {
import type { WriteStream } from "fs";
import type { Suite } from "vitest";

const frameworkToTest = getFrameworkToTest({ experimental: false });

// Note: skipIf(frameworkToTest) makes it so that all the basic C3 functionality
// tests are skipped in case we are testing a specific framework
describe.skipIf(frameworkToTest || isQuarantineMode())(
Expand Down
37 changes: 17 additions & 20 deletions packages/create-cloudflare/e2e-tests/frameworkToTest.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,22 @@
import { frameworkCliMap } from "../src/frameworks/package.json";
import { getFrameworkMap } from "../src/templates";

let targetFramework = undefined;

const envCliToTest = process.env.FRAMEWORK_CLI_TO_TEST;
/**
* Get the name of the framework to test or undefined if not focussing on a single framework.
*/
export function getFrameworkToTest({ experimental = false }) {
const envCliToTest = process.env.FRAMEWORK_CLI_TO_TEST;
if (!envCliToTest) {
return undefined;
}

if (envCliToTest) {
for (const [framework, cli] of Object.entries(frameworkCliMap)) {
if (cli === envCliToTest) {
targetFramework = framework;
const frameworks = getFrameworkMap({ experimental });
for (const [framework, { frameworkCli }] of Object.entries(frameworks)) {
if (frameworkCli === envCliToTest) {
return framework;
}
}
if (!targetFramework) {
throw new Error(
`Specified cli doesn't exist in framework map: ${envCliToTest}`,
);
}
}

/**
* In case the e2e run is supposed to only test a single framework
* the framework's name is set as the value of this variable, for standard
* runs this variable's value is `undefined`
*/
export const frameworkToTest = targetFramework;
throw new Error(
`Specified cli doesn't exist in framework map: ${envCliToTest}`,
);
}
15 changes: 8 additions & 7 deletions packages/create-cloudflare/e2e-tests/frameworks.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
} from "vitest";
import { deleteProject, deleteWorker } from "../scripts/common";
import { getFrameworkMap } from "../src/templates";
import { frameworkToTest } from "./frameworkToTest";
import { getFrameworkToTest } from "./frameworkToTest";
import {
createTestLogStream,
getDiffsPath,
Expand All @@ -31,7 +31,7 @@ import {
testProjectDir,
waitForExit,
} from "./helpers";
import type { FrameworkMap, FrameworkName } from "../src/templates";
import type { TemplateMap } from "../src/templates";
import type { RunnerConfig } from "./helpers";
import type { WriteStream } from "fs";
import type { Suite } from "vitest";
Expand Down Expand Up @@ -368,11 +368,11 @@ const frameworkTests: Record<string, FrameworkTestConfig> = {
};

describe.concurrent(`E2E: Web frameworks`, () => {
let frameworkMap: FrameworkMap;
let frameworkMap: TemplateMap;
let logStream: WriteStream;

beforeAll(async (ctx) => {
frameworkMap = await getFrameworkMap();
frameworkMap = getFrameworkMap({ experimental: false });
recreateLogFolder(ctx as Suite);
recreateDiffsFolder();
});
Expand All @@ -394,6 +394,7 @@ describe.concurrent(`E2E: Web frameworks`, () => {
// If the framework in question is being run in isolation, always run it.
// Otherwise, only run the test if it's configured `quarantine` value matches
// what is set in E2E_QUARANTINE
const frameworkToTest = getFrameworkToTest({ experimental: false });
let shouldRun = frameworkToTest
? frameworkToTest === framework
: quarantineModeMatch;
Expand All @@ -409,7 +410,7 @@ describe.concurrent(`E2E: Web frameworks`, () => {
const { getPath, getName, clean } = testProjectDir("pages");
const projectPath = getPath(framework);
const projectName = getName(framework);
const frameworkConfig = frameworkMap[framework as FrameworkName];
const frameworkConfig = frameworkMap[framework];

const { promptHandlers, verifyDeploy, flags } =
frameworkTests[framework];
Expand Down Expand Up @@ -575,8 +576,8 @@ const verifyDevScript = async (
return;
}

const frameworkMap = await getFrameworkMap();
const template = frameworkMap[framework as FrameworkName];
const frameworkMap = getFrameworkMap({ experimental: false });
const template = frameworkMap[framework];

// Run the devserver on a random port to avoid colliding with other tests
const TEST_PORT = Math.ceil(Math.random() * 1000) + 20000;
Expand Down
8 changes: 6 additions & 2 deletions packages/create-cloudflare/e2e-tests/workers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { sleep } from "helpers/sleep";
import { fetch } from "undici";
import { beforeAll, beforeEach, describe, expect, test } from "vitest";
import { deleteWorker } from "../scripts/common";
import { frameworkToTest } from "./frameworkToTest";
import { getFrameworkToTest } from "./frameworkToTest";
import {
createTestLogStream,
isQuarantineMode,
Expand Down Expand Up @@ -63,7 +63,11 @@ const workerTemplates: WorkerTestConfig[] = [
];

describe
.skipIf(frameworkToTest || isQuarantineMode() || process.platform === "win32")
.skipIf(
getFrameworkToTest({ experimental: false }) ||
isQuarantineMode() ||
process.platform === "win32",
)
.concurrent(`E2E: Workers templates`, () => {
let logStream: WriteStream;

Expand Down
3 changes: 2 additions & 1 deletion packages/create-cloudflare/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
"bin": "./dist/cli.js",
"files": [
"dist",
"templates"
"templates",
"templates-experimental"
],
"scripts": {
"build": "node -r esbuild-register scripts/build.ts",
Expand Down
2 changes: 1 addition & 1 deletion packages/create-cloudflare/scripts/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const run = async () => {
// The latter has been added to the project's .gitignore file
// This renaming will be reversed when each template is used
// We can continue to author ".gitignore" files in each template
for (const filepath of glob.sync("templates/**/.gitignore")) {
for (const filepath of glob.sync("templates*/**/.gitignore")) {
await cp(filepath, filepath.replace(".gitignore", "__dot__gitignore"));
}
};
Expand Down
35 changes: 34 additions & 1 deletion packages/create-cloudflare/src/__tests__/deploy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ describe("deploy helpers", async () => {
await expect(offerToDeploy(ctx)).resolves.toBe(true);
});

test("project is undeployable", async () => {
test("project is undeployable (simple binding)", async () => {
const ctx = createTestContext();
// Can't deploy things with bindings (yet!)
vi.mocked(readFile).mockReturnValue(`binding = "MY_QUEUE"`);
Expand All @@ -60,6 +60,39 @@ describe("deploy helpers", async () => {
expect(wranglerLogin).not.toHaveBeenCalled();
});

test("project is undeployable (complex binding)", async () => {
const ctx = createTestContext();
// Can't deploy things with bindings (yet!)
vi.mocked(readFile).mockReturnValue(`
experimental_assets = { directory = "./dist", binding = "ASSETS" }
[[durable_objects.bindings]]
name = "MY_DURABLE_OBJECT"
class_name = "MyDurableObject"
`);

await expect(offerToDeploy(ctx)).resolves.toBe(false);
expect(processArgument).toHaveBeenCalledOnce();
expect(ctx.args.deploy).toBe(false);
expect(wranglerLogin).not.toHaveBeenCalled();
});

test("assets project is deployable (no other bindings)", async () => {
const ctx = createTestContext();
vi.mocked(readFile).mockReturnValue(`
experimental_assets = { directory = "./dist", binding = "ASSETS" }
`);
// mock the user selecting yes when asked to deploy
vi.mocked(processArgument).mockResolvedValueOnce(true);
// mock a successful wrangler login
vi.mocked(wranglerLogin).mockResolvedValueOnce(true);

await expect(offerToDeploy(ctx)).resolves.toBe(true);
expect(processArgument).toHaveBeenCalledOnce();
expect(ctx.args.deploy).toBe(true);
expect(wranglerLogin).toHaveBeenCalled();
});

test("--no-deploy from command line", async () => {
const ctx = createTestContext();
ctx.args.deploy = false;
Expand Down
30 changes: 25 additions & 5 deletions packages/create-cloudflare/src/deploy.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { crash, startSection, updateStatus } from "@cloudflare/cli";
import { processArgument } from "@cloudflare/cli/args";
import { blue, brandColor, dim } from "@cloudflare/cli/colors";
import TOML from "@iarna/toml";
import { C3_DEFAULTS, openInBrowser } from "helpers/cli";
import { quoteShellArgs, runCommand } from "helpers/command";
import { detectPackageManager } from "helpers/packageManagers";
Expand Down Expand Up @@ -67,12 +68,11 @@ const isDeployable = async (ctx: C3Context) => {
return true;
}

const wranglerToml = readWranglerToml(ctx);
if (wranglerToml.match(/(?<!#\s*)bindings?\s*=.*/m)) {
return false;
}
const wranglerTomlStr = readWranglerToml(ctx);

return true;
const wranglerToml = TOML.parse(wranglerTomlStr.replace(/\r\n/g, "\n"));

return !hasBinding(wranglerToml);
};

export const runDeploy = async (ctx: C3Context) => {
Expand Down Expand Up @@ -139,3 +139,23 @@ export const maybeOpenBrowser = async (ctx: C3Context) => {
}
}
};

/**
* Recursively search the properties of node for a binding.
*/
export const hasBinding = (node: unknown): boolean => {
if (typeof node !== "object" || node === null) {
return false;
}
for (const key of Object.keys(node)) {
if (key === "experimental_assets" || key === "assets") {
// Properties called "binding" within "assets" do not count as bindings.
continue;
}
if (key === "binding" || key === "bindings") {
return true;
}
return hasBinding(node[key as keyof typeof node]);
}
return false;
};
11 changes: 4 additions & 7 deletions packages/create-cloudflare/src/frameworks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,17 @@ import { dim } from "@cloudflare/cli/colors";
import { quoteShellArgs, runCommand } from "helpers/command";
import { detectPackageManager } from "helpers/packageManagers";
import { isInsideGitRepo } from "../git";
import clisPackageJson from "./package.json";
import frameworksPackageJson from "./package.json";
import type { C3Context } from "types";

export const getFrameworkCli = (ctx: C3Context, withVersion = true) => {
if (!ctx.template) {
return crash("Framework not specified.");
}

const framework = ctx.template
.id as keyof typeof clisPackageJson.frameworkCliMap;
const frameworkCli = clisPackageJson.frameworkCliMap[
framework
] as keyof typeof clisPackageJson.dependencies;
const version = clisPackageJson.dependencies[frameworkCli];
const frameworkCli = ctx.template
.frameworkCli as keyof typeof frameworksPackageJson.dependencies;
const version = frameworksPackageJson.dependencies[frameworkCli];
return withVersion ? `${frameworkCli}@${version}` : frameworkCli;
};

Expand Down
23 changes: 3 additions & 20 deletions packages/create-cloudflare/src/frameworks/package.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
{
"name": "frameworks_clis_info",
"description": [
"this package.json is only used to keep track of the frameworks cli dependencies",
"so that we can use dependabot to update these dependencies automatically",
"additionally it also contains a map that maps frameworks to their respective clis"
"info": [
"This package.json is only used to keep track of the frameworks cli dependencies",
"so that we can use dependabot to update these dependencies automatically."
],
"dependencies": {
"create-astro": "4.8.0",
Expand All @@ -20,21 +19,5 @@
"create-vue": "3.10.4",
"gatsby": "5.13.7",
"nuxi": "3.12.0"
},
"frameworkCliMap": {
"analog": "create-analog",
"angular": "@angular/create",
"astro": "create-astro",
"docusaurus": "create-docusaurus",
"gatsby": "gatsby",
"hono": "create-hono",
"next": "create-next-app",
"nuxt": "nuxi",
"qwik": "create-qwik",
"react": "create-vite",
"remix": "create-remix",
"solid": "create-solid",
"svelte": "create-svelte",
"vue": "create-vue"
}
}
3 changes: 3 additions & 0 deletions packages/create-cloudflare/src/frameworks/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "../../tsconfig.json"
}
Loading

0 comments on commit 40ecf47

Please sign in to comment.