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 struct InitFlags {
|
||||||
pub dir: Option<String>,
|
pub dir: Option<String>,
|
||||||
pub lib: bool,
|
pub lib: bool,
|
||||||
|
pub serve: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
@ -2121,6 +2122,14 @@ fn init_subcommand() -> Command {
|
||||||
.required(false)
|
.required(false)
|
||||||
.action(ArgAction::SetTrue),
|
.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 {
|
flags.subcommand = DenoSubcommand::Init(InitFlags {
|
||||||
dir: matches.remove_one::<String>("dir"),
|
dir: matches.remove_one::<String>("dir"),
|
||||||
lib: matches.get_flag("lib"),
|
lib: matches.get_flag("lib"),
|
||||||
|
serve: matches.get_flag("serve"),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10026,7 +10036,8 @@ mod tests {
|
||||||
Flags {
|
Flags {
|
||||||
subcommand: DenoSubcommand::Init(InitFlags {
|
subcommand: DenoSubcommand::Init(InitFlags {
|
||||||
dir: None,
|
dir: None,
|
||||||
lib: false
|
lib: false,
|
||||||
|
serve: false,
|
||||||
}),
|
}),
|
||||||
..Flags::default()
|
..Flags::default()
|
||||||
}
|
}
|
||||||
|
@ -10038,7 +10049,8 @@ mod tests {
|
||||||
Flags {
|
Flags {
|
||||||
subcommand: DenoSubcommand::Init(InitFlags {
|
subcommand: DenoSubcommand::Init(InitFlags {
|
||||||
dir: Some(String::from("foo")),
|
dir: Some(String::from("foo")),
|
||||||
lib: false
|
lib: false,
|
||||||
|
serve: false,
|
||||||
}),
|
}),
|
||||||
..Flags::default()
|
..Flags::default()
|
||||||
}
|
}
|
||||||
|
@ -10050,7 +10062,8 @@ mod tests {
|
||||||
Flags {
|
Flags {
|
||||||
subcommand: DenoSubcommand::Init(InitFlags {
|
subcommand: DenoSubcommand::Init(InitFlags {
|
||||||
dir: None,
|
dir: None,
|
||||||
lib: false
|
lib: false,
|
||||||
|
serve: false,
|
||||||
}),
|
}),
|
||||||
log_level: Some(Level::Error),
|
log_level: Some(Level::Error),
|
||||||
..Flags::default()
|
..Flags::default()
|
||||||
|
@ -10063,7 +10076,21 @@ mod tests {
|
||||||
Flags {
|
Flags {
|
||||||
subcommand: DenoSubcommand::Init(InitFlags {
|
subcommand: DenoSubcommand::Init(InitFlags {
|
||||||
dir: None,
|
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()
|
..Flags::default()
|
||||||
}
|
}
|
||||||
|
@ -10075,7 +10102,8 @@ mod tests {
|
||||||
Flags {
|
Flags {
|
||||||
subcommand: DenoSubcommand::Init(InitFlags {
|
subcommand: DenoSubcommand::Init(InitFlags {
|
||||||
dir: Some(String::from("foo")),
|
dir: Some(String::from("foo")),
|
||||||
lib: true
|
lib: true,
|
||||||
|
serve: false,
|
||||||
}),
|
}),
|
||||||
..Flags::default()
|
..Flags::default()
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,87 @@ pub fn init_project(init_flags: InitFlags) -> Result<(), AnyError> {
|
||||||
cwd
|
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
|
// Extract the directory name to use as the project name
|
||||||
let project_name = dir
|
let project_name = dir
|
||||||
.file_name()
|
.file_name()
|
||||||
|
@ -111,7 +191,19 @@ Deno.test(function addTest() {
|
||||||
info!(" cd {}", dir);
|
info!(" cd {}", dir);
|
||||||
info!("");
|
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!(" {}", colors::gray("# Run the tests"));
|
||||||
info!(" deno test");
|
info!(" deno test");
|
||||||
info!("");
|
info!("");
|
||||||
|
|
|
@ -170,3 +170,50 @@ Run these commands to get started
|
||||||
output.assert_exit_code(0);
|
output.assert_exit_code(0);
|
||||||
output.assert_matches_text("Log from main.ts that already exists\n");
|
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