mirror of
https://github.com/denoland/deno.git
synced 2024-12-20 14:24:48 -05:00
d17f4590a2
This commit adds support for `deno init --npm <package>`. Running this will actually call to `npm:create-<package>` package that is equivalent to running `npm create <package>`. User will be prompted if they want to allow all permissions and lifecycle scripts to be executed. Closes https://github.com/denoland/deno/issues/26461 --------- Signed-off-by: Bartek Iwańczuk <biwanczuk@gmail.com> Co-authored-by: crowlkats <crowlkats@toaxl.com> Co-authored-by: David Sherret <dsherret@users.noreply.github.com>
336 lines
8.2 KiB
Rust
336 lines
8.2 KiB
Rust
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
||
|
||
use crate::args::DenoSubcommand;
|
||
use crate::args::Flags;
|
||
use crate::args::InitFlags;
|
||
use crate::args::PackagesAllowedScripts;
|
||
use crate::args::PermissionFlags;
|
||
use crate::args::RunFlags;
|
||
use crate::colors;
|
||
use color_print::cformat;
|
||
use color_print::cstr;
|
||
use deno_core::anyhow::Context;
|
||
use deno_core::error::AnyError;
|
||
use deno_core::serde_json::json;
|
||
use deno_runtime::WorkerExecutionMode;
|
||
use log::info;
|
||
use std::io::IsTerminal;
|
||
use std::io::Write;
|
||
use std::path::Path;
|
||
|
||
pub async fn init_project(init_flags: InitFlags) -> Result<i32, AnyError> {
|
||
if let Some(package) = &init_flags.package {
|
||
return init_npm(package, init_flags.package_args).await;
|
||
}
|
||
|
||
let cwd =
|
||
std::env::current_dir().context("Can't read current working directory.")?;
|
||
let dir = if let Some(dir) = &init_flags.dir {
|
||
let dir = cwd.join(dir);
|
||
std::fs::create_dir_all(&dir)?;
|
||
dir
|
||
} else {
|
||
cwd
|
||
};
|
||
|
||
if init_flags.serve {
|
||
create_file(
|
||
&dir,
|
||
"main.ts",
|
||
r#"import { serveDir } from "@std/http";
|
||
|
||
const userPagePattern = new URLPattern({ pathname: "/users/:id" });
|
||
const staticPathPattern = new URLPattern({ pathname: "/static/*" });
|
||
|
||
export default {
|
||
fetch(req) {
|
||
const url = new URL(req.url);
|
||
|
||
if (url.pathname === "/") {
|
||
return new Response("Home page");
|
||
}
|
||
|
||
const userPageMatch = userPagePattern.exec(url);
|
||
if (userPageMatch) {
|
||
return new Response(userPageMatch.pathname.groups.id);
|
||
}
|
||
|
||
if (staticPathPattern.test(url)) {
|
||
return serveDir(req);
|
||
}
|
||
|
||
return new Response("Not found", { status: 404 });
|
||
},
|
||
} 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/hello.js");
|
||
const res = await server.fetch(req);
|
||
assertEquals(await res.text(), 'console.log("Hello, world!");\n');
|
||
assertEquals(res.headers.get("content-type"), "text/javascript; charset=UTF-8");
|
||
});
|
||
"#,
|
||
)?;
|
||
|
||
let static_dir = dir.join("static");
|
||
std::fs::create_dir_all(&static_dir)?;
|
||
create_file(
|
||
&static_dir,
|
||
"hello.js",
|
||
r#"console.log("Hello, world!");
|
||
"#,
|
||
)?;
|
||
|
||
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()
|
||
.unwrap_or_else(|| dir.as_os_str())
|
||
.to_str()
|
||
.unwrap();
|
||
|
||
create_file(
|
||
&dir,
|
||
"mod.ts",
|
||
r#"export function add(a: number, b: number): number {
|
||
return a + b;
|
||
}
|
||
"#,
|
||
)?;
|
||
create_file(
|
||
&dir,
|
||
"mod_test.ts",
|
||
r#"import { assertEquals } from "@std/assert";
|
||
import { add } from "./mod.ts";
|
||
|
||
Deno.test(function addTest() {
|
||
assertEquals(add(2, 3), 5);
|
||
});
|
||
"#,
|
||
)?;
|
||
|
||
create_json_file(
|
||
&dir,
|
||
"deno.json",
|
||
&json!({
|
||
"name": project_name,
|
||
"version": "0.1.0",
|
||
"exports": "./mod.ts",
|
||
"tasks": {
|
||
"dev": "deno test --watch mod.ts"
|
||
},
|
||
"license": "MIT",
|
||
"imports": {
|
||
"@std/assert": "jsr:@std/assert@1"
|
||
},
|
||
}),
|
||
)?;
|
||
} else {
|
||
create_file(
|
||
&dir,
|
||
"main.ts",
|
||
r#"export function add(a: number, b: number): number {
|
||
return a + b;
|
||
}
|
||
|
||
// Learn more at https://docs.deno.com/runtime/manual/examples/module_metadata#concepts
|
||
if (import.meta.main) {
|
||
console.log("Add 2 + 3 =", add(2, 3));
|
||
}
|
||
"#,
|
||
)?;
|
||
create_file(
|
||
&dir,
|
||
"main_test.ts",
|
||
r#"import { assertEquals } from "@std/assert";
|
||
import { add } from "./main.ts";
|
||
|
||
Deno.test(function addTest() {
|
||
assertEquals(add(2, 3), 5);
|
||
});
|
||
"#,
|
||
)?;
|
||
|
||
create_json_file(
|
||
&dir,
|
||
"deno.json",
|
||
&json!({
|
||
"tasks": {
|
||
"dev": "deno run --watch main.ts"
|
||
},
|
||
"imports": {
|
||
"@std/assert": "jsr:@std/assert@1"
|
||
}
|
||
}),
|
||
)?;
|
||
}
|
||
|
||
info!("✅ {}", colors::green("Project initialized"));
|
||
info!("");
|
||
info!("{}", colors::gray("Run these commands to get started"));
|
||
info!("");
|
||
if let Some(dir) = init_flags.dir {
|
||
info!(" cd {}", dir);
|
||
info!("");
|
||
}
|
||
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 test -R");
|
||
} else if init_flags.lib {
|
||
info!(" {}", colors::gray("# Run the tests"));
|
||
info!(" deno test");
|
||
info!("");
|
||
info!(
|
||
" {}",
|
||
colors::gray("# Run the tests and watch for file changes")
|
||
);
|
||
info!(" deno task dev");
|
||
info!("");
|
||
info!(" {}", colors::gray("# Publish to JSR (dry run)"));
|
||
info!(" deno publish --dry-run");
|
||
} else {
|
||
info!(" {}", colors::gray("# Run the program"));
|
||
info!(" deno run main.ts");
|
||
info!("");
|
||
info!(
|
||
" {}",
|
||
colors::gray("# Run the program and watch for file changes")
|
||
);
|
||
info!(" deno task dev");
|
||
info!("");
|
||
info!(" {}", colors::gray("# Run the tests"));
|
||
info!(" deno test");
|
||
}
|
||
Ok(0)
|
||
}
|
||
|
||
async fn init_npm(name: &str, args: Vec<String>) -> Result<i32, AnyError> {
|
||
let script_name = format!("npm:create-{}", name);
|
||
|
||
fn print_manual_usage(script_name: &str, args: &[String]) -> i32 {
|
||
log::info!("{}", cformat!("You can initialize project manually by running <u>deno run {} {}</> and applying desired permissions.", script_name, args.join(" ")));
|
||
1
|
||
}
|
||
|
||
if std::io::stdin().is_terminal() {
|
||
log::info!(
|
||
cstr!("⚠️ Do you fully trust <y>{}</> package? Deno will invoke code from it with all permissions. Do you want to continue? <p(245)>[y/n]</>"),
|
||
script_name
|
||
);
|
||
loop {
|
||
let _ = std::io::stdout().write(b"> ")?;
|
||
std::io::stdout().flush()?;
|
||
let mut answer = String::new();
|
||
if std::io::stdin().read_line(&mut answer).is_ok() {
|
||
let answer = answer.trim().to_ascii_lowercase();
|
||
if answer != "y" {
|
||
return Ok(print_manual_usage(&script_name, &args));
|
||
} else {
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
} else {
|
||
return Ok(print_manual_usage(&script_name, &args));
|
||
}
|
||
|
||
let new_flags = Flags {
|
||
permissions: PermissionFlags {
|
||
allow_all: true,
|
||
..Default::default()
|
||
},
|
||
allow_scripts: PackagesAllowedScripts::All,
|
||
argv: args,
|
||
subcommand: DenoSubcommand::Run(RunFlags {
|
||
script: script_name,
|
||
..Default::default()
|
||
}),
|
||
..Default::default()
|
||
};
|
||
crate::tools::run::run_script(
|
||
WorkerExecutionMode::Run,
|
||
new_flags.into(),
|
||
None,
|
||
)
|
||
.await
|
||
}
|
||
|
||
fn create_json_file(
|
||
dir: &Path,
|
||
filename: &str,
|
||
value: &deno_core::serde_json::Value,
|
||
) -> Result<(), AnyError> {
|
||
let mut text = deno_core::serde_json::to_string_pretty(value)?;
|
||
text.push('\n');
|
||
create_file(dir, filename, &text)
|
||
}
|
||
|
||
fn create_file(
|
||
dir: &Path,
|
||
filename: &str,
|
||
content: &str,
|
||
) -> Result<(), AnyError> {
|
||
let path = dir.join(filename);
|
||
if path.exists() {
|
||
info!(
|
||
"ℹ️ {}",
|
||
colors::gray(format!("Skipped creating {filename} as it already exists"))
|
||
);
|
||
Ok(())
|
||
} else {
|
||
let mut file = std::fs::OpenOptions::new()
|
||
.write(true)
|
||
.create_new(true)
|
||
.open(path)
|
||
.with_context(|| format!("Failed to create {filename} file"))?;
|
||
file.write_all(content.as_bytes())?;
|
||
Ok(())
|
||
}
|
||
}
|