Skip to content

Commit

Permalink
Add durable-chat-template
Browse files Browse the repository at this point in the history
- Adapted from https://github.com/threepointone/durable-chat

Non-template changes:
- Add wrangler.toml to wrangler.json conversion to CLI
  • Loading branch information
maxwellpeterson committed Dec 3, 2024
1 parent 42cf96e commit d3e61a3
Show file tree
Hide file tree
Showing 21 changed files with 4,835 additions and 653 deletions.
3 changes: 2 additions & 1 deletion cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
"description": "A handy CLI for developing templates.",
"bin": "out/cli.js",
"dependencies": {
"commander": "12.1.0"
"commander": "12.1.0",
"toml": "3.0.0"
},
"devDependencies": {
"@types/node": "22.9.1",
Expand Down
61 changes: 49 additions & 12 deletions cli/src/lint.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import fs from "node:fs";
import path from "node:path";
import { getTemplatePaths, readJSON, writeJSON } from "./util";
import { getTemplates, readJson, readToml, Template, writeJson } from "./util";

export type LintConfig = {
templateDirectory: string;
fix: boolean;
};

export function lint(config: LintConfig) {
const templatePaths = getTemplatePaths(config.templateDirectory);
const results = templatePaths.flatMap((templatePath) =>
lintTemplate(templatePath, config.fix),
const templates = getTemplates(config.templateDirectory);
const results = templates.flatMap((template) =>
lintTemplate(template, config.fix),
);
if (results.length > 0) {
results.forEach(({ filePath, problems }) => {
Expand All @@ -22,7 +23,8 @@ export function lint(config: LintConfig) {
}
}
const CHECKS = {
"wrangler.json": [lintWrangler],
"wrangler.toml": [lintWranglerToml],
"wrangler.json": [lintWranglerJson],
};
const TARGET_COMPATIBILITY_DATE = "2024-11-01";

Expand All @@ -31,25 +33,57 @@ type FileDiagnostic = {
problems: string[];
};

function lintTemplate(templatePath: string, fix: boolean): FileDiagnostic[] {
function lintTemplate(template: Template, fix: boolean): FileDiagnostic[] {
return Object.entries(CHECKS).flatMap(([file, linters]) => {
const filePath = path.join(templatePath, file);
const problems = linters.flatMap((linter) => linter(filePath, fix));
const filePath = path.join(template.path, file);
const problems = linters.flatMap((linter) =>
linter(template, filePath, fix),
);
return problems.length > 0 ? [{ filePath, problems }] : [];
});
}
function lintWranglerToml(
template: Template,
filePath: string,
fix: boolean,
): string[] {
if (!fs.existsSync(filePath)) {
// wrangler.toml shouldn't exist, since we use wrangler.json instead.
return [];
}
const jsonPath = filePath.replace(/\.toml$/, ".json");
if (fix && !fs.existsSync(jsonPath)) {
// Convert wrangler.toml to wrangler.json if wrangler.json does not already
// exist.
writeJson(jsonPath, readToml(filePath));
fs.unlinkSync(filePath);
return [];
}
return [`Found ${filePath}. Use wrangler.json instead of wrangler.toml!`];
}

function lintWrangler(filePath: string, fix: boolean): string[] {
const wrangler = readJSON(filePath) as {
function lintWranglerJson(
template: Template,
filePath: string,
fix: boolean,
): string[] {
if (!fs.existsSync(filePath)) {
return [
`Expected ${filePath} to exist. Use wrangler.json instead of wrangler.toml!`,
];
}
const wrangler = readJson(filePath) as {
compatibility_date?: string;
observability?: { enabled: boolean };
observability?: { enabled?: boolean };
upload_source_maps?: boolean;
name?: string;
};
if (fix) {
wrangler.compatibility_date = TARGET_COMPATIBILITY_DATE;
wrangler.observability = { enabled: true };
wrangler.upload_source_maps = true;
writeJSON(filePath, wrangler);
wrangler.name = template.name;
writeJson(filePath, wrangler);
return [];
}
const problems = [];
Expand All @@ -64,5 +98,8 @@ function lintWrangler(filePath: string, fix: boolean): string[] {
if (wrangler.upload_source_maps !== true) {
problems.push(`"upload_source_maps" should be set to true`);
}
if (wrangler.name !== template.name) {
problems.push(`"name" should be set to "${template.name}"`);
}
return problems;
}
10 changes: 5 additions & 5 deletions cli/src/upload.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import fs from "node:fs";
import path from "node:path";
import subprocess from "node:child_process";
import { getTemplatePaths } from "./util";
import { getTemplates } from "./util";

export type UploadConfig = {
templateDirectory: string;
Expand All @@ -13,13 +13,13 @@ export type UploadConfig = {
};

export async function upload(config: UploadConfig) {
const templatePaths = getTemplatePaths(config.templateDirectory);
const templates = getTemplates(config.templateDirectory);
const errors = [];
for (const templatePath of templatePaths) {
for (const { path } of templates) {
try {
await uploadTemplate(templatePath, config);
await uploadTemplate(path, config);
} catch (e) {
errors.push(`Upload ${templatePath} failed: ${e}`);
errors.push(`Upload ${path} failed: ${e}`);
}
}
if (errors.length > 0) {
Expand Down
18 changes: 14 additions & 4 deletions cli/src/util.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,33 @@
import fs from "node:fs";
import path from "node:path";
import toml from "toml";

const TEMPLATE_DIRECTORY_SUFFIX = "-template";

export function getTemplatePaths(templateDirectory: string): string[] {
export type Template = { name: string; path: string };

export function getTemplates(templateDirectory: string): Template[] {
return fs
.readdirSync(templateDirectory)
.filter(
(file) =>
file.endsWith(TEMPLATE_DIRECTORY_SUFFIX) &&
fs.statSync(file).isDirectory(),
)
.map((template) => path.join(templateDirectory, template));
.map((name) => ({
name,
path: path.join(templateDirectory, name),
}));
}

export function readToml(filePath: string): unknown {
return toml.parse(fs.readFileSync(filePath, { encoding: "utf-8" }));
}

export function readJSON(filePath: string): unknown {
export function readJson(filePath: string): unknown {
return JSON.parse(fs.readFileSync(filePath, { encoding: "utf-8" }));
}

export function writeJSON(filePath: string, object: unknown) {
export function writeJson(filePath: string, object: unknown) {
fs.writeFileSync(filePath, JSON.stringify(object, undefined, 2) + "\n");
}
15 changes: 15 additions & 0 deletions durable-chat-template/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Durable Chat App

Chat with an AI assistant backed by a Durable Object.

## Develop Locally

Use this template with [C3](https://developers.cloudflare.com/pages/get-started/c3/) (the `create-cloudflare` CLI):

```
npm create cloudflare@latest -- --template=cloudflare/templates/durable-chat-template
```

## Preview Deployment

A live public deployment of this template is available at [https://durable-chat-template.templates.workers.dev](https://durable-chat-template.templates.workers.dev)
40 changes: 40 additions & 0 deletions durable-chat-template/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"name": "durable-chat-template",
"description": "Chat with an AI assistant backed by a Durable Object.",
"cloudflare": {
"label": "Durable Chat App",
"products": [
"Workers",
"Durable Objects",
"AI"
],
"categories": [
"ai"
],
"icon_urls": [
"https://imagedelivery.net/wSMYJvS3Xw-n339CbDyDIA/5ca0ca32-e897-4699-d4c1-6b680512f000/public"
]
},
"dependencies": {
"eventsource-parser": "3.0.0",
"nanoid": "5.0.8",
"partyserver": "0.0.57",
"partysocket": "1.0.2",
"react": "18.3.1",
"react-dom": "18.3.1"
},
"devDependencies": {
"@cloudflare/workers-types": "4.20241112.0",
"@types/react": "18.3.12",
"@types/react-dom": "18.3.1",
"esbuild": "0.24.0",
"typescript": "5.6.3",
"wrangler": "3.87.0"
},
"scripts": {
"check": "tsc && wrangler --experimental-json-config deploy --dry-run",
"deploy": "wrangler --experimental-json-config deploy",
"dev": "wrangler --experimental-json-config dev",
"types": "wrangler --experimental-json-config types ./src/server/worker-configuration.d.ts"
}
}
Loading

0 comments on commit d3e61a3

Please sign in to comment.