Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

C3-workers-assets-experimental-templates #6678

Merged
merged 17 commits into from
Sep 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
15af87d
feat: add --experimental flag to allow access to experimental templates
petebacondarwin Sep 11, 2024
115adbb
fix: do not crash if the chosen framework does not exist
petebacondarwin Sep 11, 2024
27dc0a0
fix: offer to deploy workers with assets and no bindings
petebacondarwin Sep 11, 2024
cb2626a
feat: add experimental qwik workers + assets template
petebacondarwin Sep 11, 2024
7dab93d
feat: add experimental svelte workers + assets template
petebacondarwin Sep 11, 2024
da5aa7b
feat: add experimental remix workers + assets template
petebacondarwin Sep 11, 2024
68cfb80
feat: add experimental astro workers + assets template
petebacondarwin Sep 11, 2024
907cdac
feat: add hello world with assets template
petebacondarwin Sep 11, 2024
210ada4
feat: add experimental solid workers + assets template
petebacondarwin Sep 11, 2024
59a59ca
feat: add experimental nuxt workers + assets template
petebacondarwin Sep 11, 2024
343416f
feat: filter the C3 help info when in experimental mode
petebacondarwin Sep 11, 2024
bf1267f
fix: mark new Workers + Assets experimental templates as "workers" pl…
petebacondarwin Sep 11, 2024
e08ec14
feat: add experimental angular workers + assets template
petebacondarwin Sep 11, 2024
47fe082
refactor: use mapped paths to import templates
petebacondarwin Sep 12, 2024
ce46e04
feat: add experimental docusaurus workers + assets template
petebacondarwin Sep 12, 2024
7ac13a6
feat: add experimental gatsby workers + assets template
petebacondarwin Sep 12, 2024
1ab2c79
add changesets
petebacondarwin Sep 12, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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]]
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Arguably Durable Objects can be deployed if they have migrations setup.
But the previous state of affairs didn't support that, so it is out of scope for this PR to change that.

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)) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This approach was too simplistic because it also blocked Workers that have assets bindings, which can be deployed straight up.

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;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The source of truth for mapping between frameworks and their CLI has moved to the c3.ts template so that is all kept in one place, allowing us to remove that map from the frameworks/package.json

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"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file is needed so that VS Code (eslint) knows that the TS files in this folder are configured by TypeScript. The package.json in this directory makes eslint think this is a separate TS project.

}
Loading
Loading