summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cli/args/flags.rs38
-rw-r--r--cli/tools/init/mod.rs96
-rw-r--r--tests/integration/init_tests.rs47
-rw-r--r--tests/registry/jsr/@std/http/1.0.0/mod.ts116
-rw-r--r--tests/registry/jsr/@std/http/1.0.0_meta.json5
-rw-r--r--tests/registry/jsr/@std/http/meta.json8
6 files changed, 303 insertions, 7 deletions
diff --git a/cli/args/flags.rs b/cli/args/flags.rs
index 1053e8f3f..e28ce549b 100644
--- a/cli/args/flags.rs
+++ b/cli/args/flags.rs
@@ -214,6 +214,7 @@ impl FmtFlags {
pub struct InitFlags {
pub dir: Option<String>,
pub lib: bool,
+ pub serve: bool,
}
#[derive(Clone, Debug, Eq, PartialEq)]
@@ -2121,6 +2122,14 @@ fn init_subcommand() -> Command {
.required(false)
.action(ArgAction::SetTrue),
)
+ .arg(
+ Arg::new("serve")
+ .long("serve")
+ .long_help("Generate an example project for `deno serve`")
+ .conflicts_with("lib")
+ .required(false)
+ .action(ArgAction::SetTrue),
+ )
})
}
@@ -4174,6 +4183,7 @@ fn init_parse(flags: &mut Flags, matches: &mut ArgMatches) {
flags.subcommand = DenoSubcommand::Init(InitFlags {
dir: matches.remove_one::<String>("dir"),
lib: matches.get_flag("lib"),
+ serve: matches.get_flag("serve"),
});
}
@@ -10026,7 +10036,8 @@ mod tests {
Flags {
subcommand: DenoSubcommand::Init(InitFlags {
dir: None,
- lib: false
+ lib: false,
+ serve: false,
}),
..Flags::default()
}
@@ -10038,7 +10049,8 @@ mod tests {
Flags {
subcommand: DenoSubcommand::Init(InitFlags {
dir: Some(String::from("foo")),
- lib: false
+ lib: false,
+ serve: false,
}),
..Flags::default()
}
@@ -10050,7 +10062,8 @@ mod tests {
Flags {
subcommand: DenoSubcommand::Init(InitFlags {
dir: None,
- lib: false
+ lib: false,
+ serve: false,
}),
log_level: Some(Level::Error),
..Flags::default()
@@ -10063,7 +10076,21 @@ mod tests {
Flags {
subcommand: DenoSubcommand::Init(InitFlags {
dir: None,
- lib: true
+ lib: true,
+ serve: false,
+ }),
+ ..Flags::default()
+ }
+ );
+
+ let r = flags_from_vec(svec!["deno", "init", "--serve"]);
+ assert_eq!(
+ r.unwrap(),
+ Flags {
+ subcommand: DenoSubcommand::Init(InitFlags {
+ dir: None,
+ lib: false,
+ serve: true,
}),
..Flags::default()
}
@@ -10075,7 +10102,8 @@ mod tests {
Flags {
subcommand: DenoSubcommand::Init(InitFlags {
dir: Some(String::from("foo")),
- lib: true
+ lib: true,
+ serve: false,
}),
..Flags::default()
}
diff --git a/cli/tools/init/mod.rs b/cli/tools/init/mod.rs
index c62d93289..6d442198e 100644
--- a/cli/tools/init/mod.rs
+++ b/cli/tools/init/mod.rs
@@ -20,7 +20,87 @@ pub fn init_project(init_flags: InitFlags) -> Result<(), AnyError> {
cwd
};
- if init_flags.lib {
+ if init_flags.serve {
+ create_file(
+ &dir,
+ "main.ts",
+ r#"import { type Route, route, serveDir } from "@std/http";
+
+const routes: Route[] = [
+ {
+ pattern: new URLPattern({ pathname: "/" }),
+ handler: () => new Response("Home page"),
+ },
+ {
+ pattern: new URLPattern({ pathname: "/users/:id" }),
+ handler: (_req, _info, params) => new Response(params?.pathname.groups.id),
+ },
+ {
+ pattern: new URLPattern({ pathname: "/static/*" }),
+ handler: (req) => serveDir(req, { urlRoot: "./" }),
+ },
+];
+
+function defaultHandler(_req: Request) {
+ return new Response("Not found", { status: 404 });
+}
+
+const handler = route(routes, defaultHandler);
+
+export default {
+ fetch(req) {
+ return handler(req);
+ },
+} satisfies Deno.ServeDefaultExport;
+
+"#,
+ )?;
+ create_file(
+ &dir,
+ "main_test.ts",
+ r#"import { assertEquals } from "@std/assert";
+import server from "./main.ts";
+
+Deno.test(async function serverFetch() {
+ const req = new Request("https://deno.land");
+ const res = await server.fetch(req);
+ assertEquals(await res.text(), "Home page");
+});
+
+Deno.test(async function serverFetchNotFound() {
+ const req = new Request("https://deno.land/404");
+ const res = await server.fetch(req);
+ assertEquals(res.status, 404);
+});
+
+Deno.test(async function serverFetchUsers() {
+ const req = new Request("https://deno.land/users/123");
+ const res = await server.fetch(req);
+ assertEquals(await res.text(), "123");
+});
+
+Deno.test(async function serverFetchStatic() {
+ const req = new Request("https://deno.land/static/main.ts");
+ const res = await server.fetch(req);
+ assertEquals(res.headers.get("content-type"), "text/plain;charset=UTF-8");
+});
+"#,
+ )?;
+
+ create_json_file(
+ &dir,
+ "deno.json",
+ &json!({
+ "tasks": {
+ "dev": "deno serve --watch -R main.ts",
+ },
+ "imports": {
+ "@std/assert": "jsr:@std/assert@1",
+ "@std/http": "jsr:@std/http@1",
+ }
+ }),
+ )?;
+ } else if init_flags.lib {
// Extract the directory name to use as the project name
let project_name = dir
.file_name()
@@ -111,7 +191,19 @@ Deno.test(function addTest() {
info!(" cd {}", dir);
info!("");
}
- if init_flags.lib {
+ if init_flags.serve {
+ info!(" {}", colors::gray("# Run the server"));
+ info!(" deno serve -R main.ts");
+ info!("");
+ info!(
+ " {}",
+ colors::gray("# Run the server and watch for file changes")
+ );
+ info!(" deno task dev");
+ info!("");
+ info!(" {}", colors::gray("# Run the tests"));
+ info!(" deno -R test");
+ } else if init_flags.lib {
info!(" {}", colors::gray("# Run the tests"));
info!(" deno test");
info!("");
diff --git a/tests/integration/init_tests.rs b/tests/integration/init_tests.rs
index 65a57eeea..757dcb021 100644
--- a/tests/integration/init_tests.rs
+++ b/tests/integration/init_tests.rs
@@ -170,3 +170,50 @@ Run these commands to get started
output.assert_exit_code(0);
output.assert_matches_text("Log from main.ts that already exists\n");
}
+
+#[tokio::test]
+async fn init_subcommand_serve() {
+ let context = TestContextBuilder::for_jsr().use_temp_cwd().build();
+ let cwd = context.temp_dir().path();
+
+ let output = context
+ .new_command()
+ .args("init --serve")
+ .split_output()
+ .run();
+
+ output.assert_exit_code(0);
+
+ let stderr = output.stderr();
+ assert_contains!(stderr, "Project initialized");
+ assert_contains!(stderr, "deno serve -R main.ts");
+ assert_contains!(stderr, "deno task dev");
+ assert_contains!(stderr, "deno -R test");
+
+ assert!(cwd.join("deno.json").exists());
+
+ let mut child = context
+ .new_command()
+ .env("NO_COLOR", "1")
+ .args("serve -R --port 9500 main.ts")
+ .spawn_with_piped_output();
+
+ tokio::time::sleep(std::time::Duration::from_millis(1000)).await;
+ let resp = reqwest::get("http://127.0.0.1:9500").await.unwrap();
+
+ let body = resp.text().await.unwrap();
+ assert_eq!(body, "Home page");
+
+ let _ = child.kill();
+
+ let output = context
+ .new_command()
+ .env("NO_COLOR", "1")
+ .args("-R test")
+ .split_output()
+ .run();
+
+ output.assert_exit_code(0);
+ assert_contains!(output.stdout(), "4 passed");
+ output.skip_output_check();
+}
diff --git a/tests/registry/jsr/@std/http/1.0.0/mod.ts b/tests/registry/jsr/@std/http/1.0.0/mod.ts
new file mode 100644
index 000000000..0a0e82847
--- /dev/null
+++ b/tests/registry/jsr/@std/http/1.0.0/mod.ts
@@ -0,0 +1,116 @@
+// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
+
+/**
+ * Request handler for {@linkcode Route}.
+ *
+ * > [!WARNING]
+ * > **UNSTABLE**: New API, yet to be vetted.
+ *
+ * @experimental
+ *
+ * Extends {@linkcode Deno.ServeHandlerInfo} by adding adding a `params` argument.
+ *
+ * @param request Request
+ * @param info Request info
+ * @param params URL pattern result
+ */
+export type Handler = (
+ request: Request,
+ info?: Deno.ServeHandlerInfo,
+ params?: URLPatternResult | null,
+) => Response | Promise<Response>;
+
+/**
+ * Route configuration for {@linkcode route}.
+ *
+ * > [!WARNING]
+ * > **UNSTABLE**: New API, yet to be vetted.
+ *
+ * @experimental
+ */
+export interface Route {
+ /**
+ * Request URL pattern.
+ */
+ pattern: URLPattern;
+ /**
+ * Request method.
+ *
+ * @default {"GET"}
+ */
+ method?: string;
+ /**
+ * Request handler.
+ */
+ handler: Handler;
+}
+
+/**
+ * Routes requests to different handlers based on the request path and method.
+ *
+ * > [!WARNING]
+ * > **UNSTABLE**: New API, yet to be vetted.
+ *
+ * @experimental
+ *
+ * @example Usage
+ * ```ts no-eval
+ * import { route, type Route } from "@std/http/route";
+ * import { serveDir } from "@std/http/file-server";
+ *
+ * const routes: Route[] = [
+ * {
+ * pattern: new URLPattern({ pathname: "/about" }),
+ * handler: () => new Response("About page"),
+ * },
+ * {
+ * pattern: new URLPattern({ pathname: "/users/:id" }),
+ * handler: (_req, _info, params) => new Response(params?.pathname.groups.id),
+ * },
+ * {
+ * pattern: new URLPattern({ pathname: "/static/*" }),
+ * handler: (req: Request) => serveDir(req)
+ * }
+ * ];
+ *
+ * function defaultHandler(_req: Request) {
+ * return new Response("Not found", { status: 404 });
+ * }
+ *
+ * Deno.serve(route(routes, defaultHandler));
+ * ```
+ *
+ * @param routes Route configurations
+ * @param defaultHandler Default request handler that's returned when no route
+ * matches the given request. Serving HTTP 404 Not Found or 405 Method Not
+ * Allowed response can be done in this function.
+ * @returns Request handler
+ */
+export function route(
+ routes: Route[],
+ defaultHandler: (
+ request: Request,
+ info?: Deno.ServeHandlerInfo,
+ ) => Response | Promise<Response>,
+): (
+ request: Request,
+ info?: Deno.ServeHandlerInfo,
+) => Response | Promise<Response> {
+ // TODO(iuioiua): Use `URLPatternList` once available (https://github.com/whatwg/urlpattern/pull/166)
+ return (request: Request, info?: Deno.ServeHandlerInfo) => {
+ for (const route of routes) {
+ const match = route.pattern.exec(request.url);
+ if (match) return route.handler(request, info, match);
+ }
+ return defaultHandler(request, info);
+ };
+}
+
+interface ServeDirOptions {
+ urlRoot?: string;
+}
+
+// NOTE(bartlomieju): not important, just for testing
+export async function serveDir(req: Request, opts: ServeDirOptions = {}): Response | Promise<Response> {
+ return new Response("hello world")
+}
diff --git a/tests/registry/jsr/@std/http/1.0.0_meta.json b/tests/registry/jsr/@std/http/1.0.0_meta.json
new file mode 100644
index 000000000..9b5dd9d0a
--- /dev/null
+++ b/tests/registry/jsr/@std/http/1.0.0_meta.json
@@ -0,0 +1,5 @@
+{
+ "exports": {
+ ".": "./mod.ts"
+ }
+} \ No newline at end of file
diff --git a/tests/registry/jsr/@std/http/meta.json b/tests/registry/jsr/@std/http/meta.json
new file mode 100644
index 000000000..a10332638
--- /dev/null
+++ b/tests/registry/jsr/@std/http/meta.json
@@ -0,0 +1,8 @@
+{
+ "scope": "std",
+ "name": "http",
+ "latest": "1.0.0",
+ "versions": {
+ "1.0.0": {}
+ }
+}