mirror of
https://github.com/denoland/deno.git
synced 2024-11-21 15:04:11 -05:00
feat: deno init --serve
(#24897)
This commit adds "--serve" flag to "deno init" subcommand, that provides a template for quick starting a project using "deno serve". --------- Co-authored-by: Asher Gomez <ashersaupingomez@gmail.com>
This commit is contained in:
parent
3f692bed0a
commit
ffe1bfd54c
6 changed files with 303 additions and 7 deletions
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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!("");
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
116
tests/registry/jsr/@std/http/1.0.0/mod.ts
Normal file
116
tests/registry/jsr/@std/http/1.0.0/mod.ts
Normal file
|
@ -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")
|
||||
}
|
5
tests/registry/jsr/@std/http/1.0.0_meta.json
Normal file
5
tests/registry/jsr/@std/http/1.0.0_meta.json
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"exports": {
|
||||
".": "./mod.ts"
|
||||
}
|
||||
}
|
8
tests/registry/jsr/@std/http/meta.json
Normal file
8
tests/registry/jsr/@std/http/meta.json
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"scope": "std",
|
||||
"name": "http",
|
||||
"latest": "1.0.0",
|
||||
"versions": {
|
||||
"1.0.0": {}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue