Skip to content

Commit

Permalink
Add basic template linting
Browse files Browse the repository at this point in the history
- Add eslint and fix all existing problems
- Add linting functionality to CLI to enforce template conventions
  • Loading branch information
maxwellpeterson committed Nov 22, 2024
1 parent 0f32b6e commit a780108
Show file tree
Hide file tree
Showing 18 changed files with 1,270 additions and 36 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/branches.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,8 @@ jobs:
with:
node-version: "20.x"
- run: npm ci
- run: npm run build:cli
# Need to install a second time to get the CLI build linked up in the
# right place.
- run: npm ci
- run: npm run check:ci
4 changes: 4 additions & 0 deletions .github/workflows/main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ jobs:
with:
node-version: "20.x"
- run: npm ci
- run: npm run build:cli
# Need to install a second time to get the CLI build linked up in the
# right place.
- run: npm ci
- run: npm run check:ci
- run: npm run deploy
- run: npm run upload
19 changes: 17 additions & 2 deletions cli/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,34 @@
import { Command } from "commander";
import { upload } from "./upload";
import { lint } from "./lint";

const program = new Command();

program.name("cli").description("A handy CLI for developing templates.");
program.name("cli").description("a handy CLI for developing templates");

program
.command("upload")
.description("upload templates to the templates API")
.argument(
"[path-to-templates]",
"path to directory containing templates",
".",
)
.action((templateDirectory) =>
.action((templateDirectory: string) =>
upload({ templateDirectory, apiBaseUrl: "TODO" }),
);

program
.command("lint")
.description("find and fix template style problems")
.argument(
"[path-to-templates]",
"path to directory containing templates",
".",
)
.option("--fix", "fix problems that can be automatically fixed")
.action((templateDirectory: string, options: { fix: boolean }) => {
lint({ templateDirectory, fix: options.fix });
});

program.parse();
68 changes: 68 additions & 0 deletions cli/src/lint.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import path from "node:path";
import { getTemplatePaths, readJSON, 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),
);
if (results.length > 0) {
results.forEach(({ filePath, problems }) => {
console.error(`Problems with ${filePath}`);
problems.forEach((problem) => {
console.log(` - ${problem}`);
});
});
process.exit(1);
}
}
const CHECKS = {
"wrangler.json": [lintWrangler],
};
const TARGET_COMPATIBILITY_DATE = "2024-11-01";

type FileDiagnostic = {
filePath: string;
problems: string[];
};

function lintTemplate(templatePath: string, fix: boolean): FileDiagnostic[] {
return Object.entries(CHECKS).flatMap(([file, linters]) => {
const filePath = path.join(templatePath, file);
const problems = linters.flatMap((linter) => linter(filePath, fix));
return problems.length > 0 ? [{ filePath, problems }] : [];
});
}

function lintWrangler(filePath: string, fix: boolean): string[] {
const wrangler = readJSON(filePath) as {
compatibility_date?: string;
observability?: { enabled: boolean };
upload_source_maps?: boolean;
};
if (fix) {
wrangler.compatibility_date = TARGET_COMPATIBILITY_DATE;
wrangler.observability = { enabled: true };
wrangler.upload_source_maps = true;
writeJSON(filePath, wrangler);
return [];
}
const problems = [];
if (wrangler.compatibility_date !== TARGET_COMPATIBILITY_DATE) {
problems.push(
`"compatibility_date" should be set to "${TARGET_COMPATIBILITY_DATE}"`,
);
}
if (wrangler.observability?.enabled !== true) {
problems.push(`"observability" should be set to { "enabled": true }`);
}
if (wrangler.upload_source_maps !== true) {
problems.push(`"upload_source_maps" should be set to true`);
}
return problems;
}
12 changes: 2 additions & 10 deletions cli/src/upload.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,15 @@
import fs from "node:fs";
import path from "node:path";
import subprocess from "node:child_process";

const TEMPLATE_DIRECTORY_SUFFIX = "-template";
import { getTemplatePaths } from "./util";

export type UploadConfig = {
templateDirectory: string;
apiBaseUrl: string;
};

export async function upload(config: UploadConfig) {
const templatePaths = fs
.readdirSync(config.templateDirectory)
.filter(
(file) =>
file.endsWith(TEMPLATE_DIRECTORY_SUFFIX) &&
fs.statSync(file).isDirectory(),
)
.map((template) => path.join(config.templateDirectory, template));
const templatePaths = getTemplatePaths(config.templateDirectory);
const results = await Promise.allSettled(
templatePaths.map((templatePath) => uploadTemplate(templatePath, config)),
);
Expand Down
23 changes: 23 additions & 0 deletions cli/src/util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import fs from "node:fs";
import path from "node:path";

const TEMPLATE_DIRECTORY_SUFFIX = "-template";

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

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

export function writeJSON(filePath: string, object: unknown) {
fs.writeFileSync(filePath, JSON.stringify(object, undefined, 2) + "\n");
}
2 changes: 1 addition & 1 deletion d1-template/wrangler.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"compatibility_date": "2024-11-15",
"compatibility_date": "2024-11-01",
"main": "src/index.ts",
"name": "d1-template",
"upload_source_maps": true,
Expand Down
23 changes: 23 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// @ts-check

import eslint from "@eslint/js";
import tseslint from "typescript-eslint";

export default tseslint.config(
eslint.configs.recommended,
...tseslint.configs.strictTypeChecked,
{
languageOptions: {
parserOptions: {
project: ["./*-template/tsconfig.json", "./cli/tsconfig.json"],
tsconfigRootDir: import.meta.dirname,
},
},
rules: {
"@typescript-eslint/restrict-template-expressions": "off",
},
},
{
ignores: ["**/*.js", "**/*.mjs"],
},
);
2 changes: 1 addition & 1 deletion image-classification-template/wrangler.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"compatibility_date": "2024-11-15",
"compatibility_date": "2024-11-01",
"main": "src/index.ts",
"name": "image-classification-template",
"upload_source_maps": true,
Expand Down
4 changes: 2 additions & 2 deletions llm-template/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
export default {
async fetch(request, env) {
// prompt - simple completion style input
let simple = {
const simple = {
prompt: "Tell me a joke about Cloudflare",
};
let response = await env.AI.run("@cf/meta/llama-3-8b-instruct", simple);
const response = await env.AI.run("@cf/meta/llama-3-8b-instruct", simple);

return Response.json({ inputs: simple, response });
},
Expand Down
2 changes: 1 addition & 1 deletion llm-template/wrangler.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"compatibility_date": "2024-11-15",
"compatibility_date": "2024-11-01",
"main": "src/index.ts",
"name": "llm-template",
"upload_source_maps": true,
Expand Down
Loading

0 comments on commit a780108

Please sign in to comment.