FUTURE: deno install changes (#23498)

This PR implements the changes we plan to make to `deno install` in deno
2.0.
- `deno install` without arguments caches dependencies from
`package.json` / `deno.json` and sets up the `node_modules` folder
- `deno install <pkg>` adds the package to the config file (either
`package.json` or `deno.json`), i.e. it aliases `deno add`
- `deno add` can also add deps to `package.json` (this is gated behind
`DENO_FUTURE` due to uncertainty around handling projects with both
`deno.json` and `package.json`)
- `deno install -g <bin>` installs a package as a globally available
binary (the same as `deno install <bin>` in 1.0)

---------

Co-authored-by: Nathan Whitaker <nathan@deno.com>
This commit is contained in:
Bartek Iwańczuk 2024-05-08 20:34:46 +01:00 committed by GitHub
parent 525b3c8d74
commit 4e23a5b1fc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
34 changed files with 642 additions and 106 deletions

6
Cargo.lock generated
View File

@ -5900,12 +5900,12 @@ dependencies = [
[[package]]
name = "socket2"
version = "0.5.6"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871"
checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9"
dependencies = [
"libc",
"windows-sys 0.52.0",
"windows-sys 0.48.0",
]
[[package]]

View File

@ -38,6 +38,7 @@ use crate::args::resolve_no_prompt;
use crate::util::fs::canonicalize_path;
use super::flags_net;
use super::DENO_FUTURE;
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct FileFlags {
@ -195,7 +196,7 @@ pub struct InstallFlagsGlobal {
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum InstallKind {
#[allow(unused)]
Local,
Local(Option<AddFlags>),
Global(InstallFlagsGlobal),
}
@ -913,11 +914,18 @@ impl Flags {
}
Task(_) | Check(_) | Coverage(_) | Cache(_) | Info(_) | Eval(_)
| Test(_) | Bench(_) | Repl(_) | Compile(_) | Publish(_) => {
std::env::current_dir().ok()
Some(current_dir.to_path_buf())
}
Add(_) | Bundle(_) | Completions(_) | Doc(_) | Fmt(_) | Init(_)
| Install(_) | Uninstall(_) | Jupyter(_) | Lsp | Lint(_) | Types
| Upgrade(_) | Vendor(_) => None,
| Uninstall(_) | Jupyter(_) | Lsp | Lint(_) | Types | Upgrade(_)
| Vendor(_) => None,
Install(_) => {
if *DENO_FUTURE {
Some(current_dir.to_path_buf())
} else {
None
}
}
}
}
@ -1313,7 +1321,11 @@ fn clap_root() -> Command {
.subcommand(fmt_subcommand())
.subcommand(init_subcommand())
.subcommand(info_subcommand())
.subcommand(install_subcommand())
.subcommand(if *DENO_FUTURE {
future_install_subcommand()
} else {
install_subcommand()
})
.subcommand(jupyter_subcommand())
.subcommand(uninstall_subcommand())
.subcommand(lsp_subcommand())
@ -2047,11 +2059,73 @@ TypeScript compiler cache: Subdirectory containing TS compiler output.",
))
}
fn install_subcommand() -> Command {
fn install_args(cmd: Command, deno_future: bool) -> Command {
let cmd = if deno_future {
cmd.arg(
Arg::new("cmd")
.required_if_eq("global", "true")
.num_args(1..)
.value_hint(ValueHint::FilePath),
)
} else {
cmd.arg(
Arg::new("cmd")
.required(true)
.num_args(1..)
.value_hint(ValueHint::FilePath),
)
};
cmd
.arg(
Arg::new("name")
.long("name")
.short('n')
.help("Executable file name")
.required(false),
)
.arg(
Arg::new("root")
.long("root")
.help("Installation root")
.value_hint(ValueHint::DirPath),
)
.arg(
Arg::new("force")
.long("force")
.short('f')
.help("Forcefully overwrite existing installation")
.action(ArgAction::SetTrue),
)
.arg(
Arg::new("global")
.long("global")
.short('g')
.help("Install a package or script as a globally available executable")
.action(ArgAction::SetTrue),
)
.arg(env_file_arg())
}
fn future_install_subcommand() -> Command {
Command::new("install")
.about("Install script as an executable")
.visible_alias("i")
.about("Install dependencies")
.long_about(
"Installs a script as an executable in the installation root's bin directory.
"Installs dependencies either in the local project or globally to a bin directory.
Local installation
-------------------
If the --global flag is not set, adds dependencies to the local project's configuration
(package.json / deno.json) and installs them in the package cache. If no dependency
is specified, installs all dependencies listed in package.json.
deno install
deno install @std/bytes
deno install npm:chalk
Global installation
-------------------
If the --global flag is set, installs a script as an executable in the installation root's bin directory.
deno install --global --allow-net --allow-read jsr:@std/http/file-server
deno install -g https://examples.deno.land/color-logging.ts
@ -2078,34 +2152,47 @@ The installation root is determined, in order of precedence:
- $HOME/.deno
These must be added to the path manually if required.")
.defer(|cmd| runtime_args(cmd, true, true).arg(Arg::new("cmd").required(true).num_args(1..).value_hint(ValueHint::FilePath))
.arg(check_arg(true))
.arg(
Arg::new("name")
.long("name")
.short('n')
.help("Executable file name")
.required(false))
.arg(
Arg::new("root")
.long("root")
.help("Installation root")
.value_hint(ValueHint::DirPath))
.arg(
Arg::new("force")
.long("force")
.short('f')
.help("Forcefully overwrite existing installation")
.action(ArgAction::SetTrue))
)
.arg(
Arg::new("global")
.long("global")
.short('g')
.help("Install a package or script as a globally available executable")
.action(ArgAction::SetTrue)
)
.arg(env_file_arg())
.defer(|cmd| {
let cmd = runtime_args(cmd, true, true).arg(check_arg(true));
install_args(cmd, true)
})
}
fn install_subcommand() -> Command {
Command::new("install")
.about("Install script as an executable")
.long_about(
"Installs a script as an executable in the installation root's bin directory.
deno install --global --allow-net --allow-read jsr:@std/http/file-server
deno install -g https://examples.deno.land/color-logging.ts
To change the executable name, use -n/--name:
deno install -g --allow-net --allow-read -n serve jsr:@std/http/file-server
The executable name is inferred by default:
- Attempt to take the file stem of the URL path. The above example would
become 'file_server'.
- If the file stem is something generic like 'main', 'mod', 'index' or 'cli',
and the path has no parent, take the file name of the parent path. Otherwise
settle with the generic name.
- If the resulting name has an '@...' suffix, strip it.
To change the installation root, use --root:
deno install -g --allow-net --allow-read --root /usr/local jsr:@std/http/file-server
The installation root is determined, in order of precedence:
- --root option
- DENO_INSTALL_ROOT environment variable
- $HOME/.deno
These must be added to the path manually if required.")
.defer(|cmd| {
let cmd = runtime_args(cmd, true, true).arg(check_arg(true));
install_args(cmd, false)
})
}
fn jupyter_subcommand() -> Command {
@ -3555,8 +3642,17 @@ fn unsafely_ignore_certificate_errors_arg() -> Arg {
}
fn add_parse(flags: &mut Flags, matches: &mut ArgMatches) {
let packages = matches.remove_many::<String>("packages").unwrap().collect();
flags.subcommand = DenoSubcommand::Add(AddFlags { packages });
flags.subcommand = DenoSubcommand::Add(add_parse_inner(matches, None));
}
fn add_parse_inner(
matches: &mut ArgMatches,
packages: Option<clap::parser::Values<String>>,
) -> AddFlags {
let packages = packages
.unwrap_or_else(|| matches.remove_many::<String>("packages").unwrap())
.collect();
AddFlags { packages }
}
fn bench_parse(flags: &mut Flags, matches: &mut ArgMatches) {
@ -3871,28 +3967,37 @@ fn info_parse(flags: &mut Flags, matches: &mut ArgMatches) {
fn install_parse(flags: &mut Flags, matches: &mut ArgMatches) {
runtime_args_parse(flags, matches, true, true);
let root = matches.remove_one::<String>("root");
let force = matches.get_flag("force");
let global = matches.get_flag("global");
let name = matches.remove_one::<String>("name");
let mut cmd_values = matches.remove_many::<String>("cmd").unwrap();
if global || !*DENO_FUTURE {
let root = matches.remove_one::<String>("root");
let force = matches.get_flag("force");
let name = matches.remove_one::<String>("name");
let mut cmd_values =
matches.remove_many::<String>("cmd").unwrap_or_default();
let module_url = cmd_values.next().unwrap();
let args = cmd_values.collect();
let module_url = cmd_values.next().unwrap();
let args = cmd_values.collect();
flags.subcommand = DenoSubcommand::Install(InstallFlags {
// TODO(bartlomieju): remove once `deno install` supports both local and
// global installs
global,
kind: InstallKind::Global(InstallFlagsGlobal {
name,
module_url,
args,
root,
force,
}),
});
flags.subcommand = DenoSubcommand::Install(InstallFlags {
// TODO(bartlomieju): remove for 2.0
global,
kind: InstallKind::Global(InstallFlagsGlobal {
name,
module_url,
args,
root,
force,
}),
});
} else {
let local_flags = matches
.remove_many("cmd")
.map(|packages| add_parse_inner(matches, Some(packages)));
flags.subcommand = DenoSubcommand::Install(InstallFlags {
global,
kind: InstallKind::Local(local_flags),
})
}
}
fn jupyter_parse(flags: &mut Flags, matches: &mut ArgMatches) {

View File

@ -408,7 +408,8 @@ impl CliFactory {
.npm_resolver
.get_or_try_init_async(async {
let fs = self.fs();
create_cli_npm_resolver(if self.options.use_byonm() {
// For `deno install` we want to force the managed resolver so it can set up `node_modules/` directory.
create_cli_npm_resolver(if self.options.use_byonm() && !matches!(self.options.sub_command(), DenoSubcommand::Install(_)) {
CliNpmResolverCreateOptions::Byonm(CliNpmResolverByonmCreateOptions {
fs: fs.clone(),
root_node_modules_dir: match self.options.node_modules_dir_path() {

View File

@ -8,6 +8,7 @@ use crate::cache::CodeCache;
use crate::cache::ModuleInfoCache;
use crate::cache::ParsedSourceCache;
use crate::emit::Emitter;
use crate::factory::CliFactory;
use crate::graph_util::graph_lock_or_exit;
use crate::graph_util::CreateGraphOptions;
use crate::graph_util::ModuleGraphBuilder;
@ -64,6 +65,40 @@ use std::rc::Rc;
use std::str;
use std::sync::Arc;
pub async fn load_top_level_deps(factory: &CliFactory) -> Result<(), AnyError> {
let npm_resolver = factory.npm_resolver().await?;
if let Some(npm_resolver) = npm_resolver.as_managed() {
npm_resolver.ensure_top_level_package_json_install().await?;
npm_resolver.resolve_pending().await?;
}
// cache as many entries in the import map as we can
if let Some(import_map) = factory.maybe_import_map().await? {
let roots = import_map
.imports()
.entries()
.filter_map(|entry| {
if entry.key.ends_with('/') {
None
} else {
entry.value.cloned()
}
})
.collect();
factory
.module_load_preparer()
.await?
.prepare_module_load(
roots,
false,
factory.cli_options().ts_type_lib_window(),
deno_runtime::permissions::PermissionsContainer::allow_all(),
)
.await?;
}
Ok(())
}
pub struct ModuleLoadPreparer {
options: Arc<CliOptions>,
graph_container: Arc<ModuleGraphContainer>,

View File

@ -1,6 +1,7 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
use crate::args::resolve_no_prompt;
use crate::args::AddFlags;
use crate::args::CaData;
use crate::args::Flags;
use crate::args::InstallFlags;
@ -253,6 +254,20 @@ pub fn uninstall(uninstall_flags: UninstallFlags) -> Result<(), AnyError> {
Ok(())
}
async fn install_local(
flags: Flags,
maybe_add_flags: Option<AddFlags>,
) -> Result<(), AnyError> {
if let Some(add_flags) = maybe_add_flags {
return super::registry::add(flags, add_flags).await;
}
let factory = CliFactory::from_flags(flags)?;
crate::module_loader::load_top_level_deps(&factory).await?;
Ok(())
}
pub async fn install_command(
flags: Flags,
install_flags: InstallFlags,
@ -263,7 +278,9 @@ pub async fn install_command(
let install_flags_global = match install_flags.kind {
InstallKind::Global(flags) => flags,
InstallKind::Local => unreachable!(),
InstallKind::Local(maybe_add_flags) => {
return install_local(flags, maybe_add_flags).await
}
};
// ensure the module is cached

View File

@ -1,19 +1,23 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
use std::collections::HashMap;
use std::borrow::Cow;
use std::path::PathBuf;
use std::sync::Arc;
use deno_ast::TextChange;
use deno_config::FmtOptionsConfig;
use deno_core::anyhow::anyhow;
use deno_core::anyhow::bail;
use deno_core::anyhow::Context;
use deno_core::error::AnyError;
use deno_core::futures::FutureExt;
use deno_core::futures::StreamExt;
use deno_core::serde_json;
use deno_core::ModuleSpecifier;
use deno_runtime::deno_node;
use deno_semver::jsr::JsrPackageReqReference;
use deno_semver::npm::NpmPackageReqReference;
use indexmap::IndexMap;
use jsonc_parser::ast::ObjectProp;
use jsonc_parser::ast::Value;
@ -25,22 +29,164 @@ use crate::file_fetcher::FileFetcher;
use crate::jsr::JsrFetchResolver;
use crate::npm::NpmFetchResolver;
enum DenoConfigFormat {
Json,
Jsonc,
}
impl DenoConfigFormat {
fn from_specifier(spec: &ModuleSpecifier) -> Result<Self, AnyError> {
let file_name = spec
.path_segments()
.ok_or_else(|| anyhow!("Empty path in deno config specifier: {spec}"))?
.last()
.unwrap();
match file_name {
"deno.json" => Ok(Self::Json),
"deno.jsonc" => Ok(Self::Jsonc),
_ => bail!("Unsupported deno config file: {file_name}"),
}
}
}
enum DenoOrPackageJson {
Deno(deno_config::ConfigFile, DenoConfigFormat),
Npm(deno_node::PackageJson, Option<FmtOptionsConfig>),
}
impl DenoOrPackageJson {
fn specifier(&self) -> Cow<ModuleSpecifier> {
match self {
Self::Deno(d, ..) => Cow::Borrowed(&d.specifier),
Self::Npm(n, ..) => Cow::Owned(n.specifier()),
}
}
/// Returns the existing imports/dependencies from the config.
fn existing_imports(&self) -> Result<IndexMap<String, String>, AnyError> {
match self {
DenoOrPackageJson::Deno(deno, ..) => {
if let Some(imports) = deno.json.imports.clone() {
match serde_json::from_value(imports) {
Ok(map) => Ok(map),
Err(err) => {
bail!("Malformed \"imports\" configuration: {err}")
}
}
} else {
Ok(Default::default())
}
}
DenoOrPackageJson::Npm(npm, ..) => {
Ok(npm.dependencies.clone().unwrap_or_default())
}
}
}
fn fmt_options(&self) -> FmtOptionsConfig {
match self {
DenoOrPackageJson::Deno(deno, ..) => deno
.to_fmt_config()
.ok()
.flatten()
.map(|f| f.options)
.unwrap_or_default(),
DenoOrPackageJson::Npm(_, config) => config.clone().unwrap_or_default(),
}
}
fn imports_key(&self) -> &'static str {
match self {
DenoOrPackageJson::Deno(..) => "imports",
DenoOrPackageJson::Npm(..) => "dependencies",
}
}
fn file_name(&self) -> &'static str {
match self {
DenoOrPackageJson::Deno(_, format) => match format {
DenoConfigFormat::Json => "deno.json",
DenoConfigFormat::Jsonc => "deno.jsonc",
},
DenoOrPackageJson::Npm(..) => "package.json",
}
}
fn is_npm(&self) -> bool {
matches!(self, Self::Npm(..))
}
/// Get the preferred config file to operate on
/// given the flags. If no config file is present,
/// creates a `deno.json` file - in this case
/// we also return a new `CliFactory` that knows about
/// the new config
fn from_flags(flags: Flags) -> Result<(Self, CliFactory), AnyError> {
let factory = CliFactory::from_flags(flags.clone())?;
let options = factory.cli_options().clone();
match (options.maybe_config_file(), options.maybe_package_json()) {
// when both are present, for now,
// default to deno.json
(Some(deno), Some(_) | None) => Ok((
DenoOrPackageJson::Deno(
deno.clone(),
DenoConfigFormat::from_specifier(&deno.specifier)?,
),
factory,
)),
(None, Some(package_json)) if options.enable_future_features() => {
Ok((DenoOrPackageJson::Npm(package_json.clone(), None), factory))
}
(None, Some(_) | None) => {
std::fs::write(options.initial_cwd().join("deno.json"), "{}\n")
.context("Failed to create deno.json file")?;
log::info!("Created deno.json configuration file.");
let new_factory = CliFactory::from_flags(flags.clone())?;
let new_options = new_factory.cli_options().clone();
Ok((
DenoOrPackageJson::Deno(
new_options
.maybe_config_file()
.as_ref()
.ok_or_else(|| {
anyhow!("config not found, but it was just created")
})?
.clone(),
DenoConfigFormat::Json,
),
new_factory,
))
}
}
}
}
fn package_json_dependency_entry(
selected: SelectedPackage,
) -> (String, String) {
if let Some(npm_package) = selected.package_name.strip_prefix("npm:") {
(npm_package.into(), selected.version_req)
} else if let Some(jsr_package) = selected.package_name.strip_prefix("jsr:") {
let jsr_package = jsr_package.strip_prefix('@').unwrap_or(jsr_package);
let scope_replaced = jsr_package.replace('/', "__");
let version_req =
format!("npm:@jsr/{scope_replaced}@{}", selected.version_req);
(selected.import_name, version_req)
} else {
(selected.package_name, selected.version_req)
}
}
pub async fn add(flags: Flags, add_flags: AddFlags) -> Result<(), AnyError> {
let cli_factory = CliFactory::from_flags(flags.clone())?;
let cli_options = cli_factory.cli_options();
let (config_file, cli_factory) =
DenoOrPackageJson::from_flags(flags.clone())?;
let Some(config_file) = cli_options.maybe_config_file() else {
tokio::fs::write(cli_options.initial_cwd().join("deno.json"), "{}\n")
.await
.context("Failed to create deno.json file")?;
log::info!("Created deno.json configuration file.");
return add(flags, add_flags).boxed_local().await;
};
if config_file.specifier.scheme() != "file" {
let config_specifier = config_file.specifier();
if config_specifier.scheme() != "file" {
bail!("Can't add dependencies to a remote configuration file");
}
let config_file_path = config_file.specifier.to_file_path().unwrap();
let config_file_path = config_specifier.to_file_path().unwrap();
let http_client = cli_factory.http_client();
@ -49,10 +195,13 @@ pub async fn add(flags: Flags, add_flags: AddFlags) -> Result<(), AnyError> {
for package_name in add_flags.packages.iter() {
let req = if package_name.starts_with("npm:") {
let pkg_req = NpmPackageReqReference::from_str(package_name)
.with_context(|| {
format!("Failed to parse package required: {}", package_name)
})?;
let pkg_req = NpmPackageReqReference::from_str(&format!(
"npm:{}",
package_name.strip_prefix("npm:").unwrap_or(package_name)
))
.with_context(|| {
format!("Failed to parse package required: {}", package_name)
})?;
AddPackageReq::Npm(pkg_req)
} else {
let pkg_req = JsrPackageReqReference::from_str(&format!(
@ -128,16 +277,9 @@ pub async fn add(flags: Flags, add_flags: AddFlags) -> Result<(), AnyError> {
_ => bail!("Failed updating config file due to no object."),
};
let mut existing_imports =
if let Some(imports) = config_file.json.imports.clone() {
match serde_json::from_value::<HashMap<String, String>>(imports) {
Ok(i) => i,
Err(_) => bail!("Malformed \"imports\" configuration"),
}
} else {
HashMap::default()
};
let mut existing_imports = config_file.existing_imports()?;
let is_npm = config_file.is_npm();
for selected_package in selected_packages {
log::info!(
"Add {} - {}@{}",
@ -145,13 +287,19 @@ pub async fn add(flags: Flags, add_flags: AddFlags) -> Result<(), AnyError> {
selected_package.package_name,
selected_package.version_req
);
existing_imports.insert(
selected_package.import_name,
format!(
"{}@{}",
selected_package.package_name, selected_package.version_req
),
);
if is_npm {
let (name, version) = package_json_dependency_entry(selected_package);
existing_imports.insert(name, version)
} else {
existing_imports.insert(
selected_package.import_name,
format!(
"{}@{}",
selected_package.package_name, selected_package.version_req
),
)
};
}
let mut import_list: Vec<(String, String)> =
existing_imports.into_iter().collect();
@ -159,25 +307,29 @@ pub async fn add(flags: Flags, add_flags: AddFlags) -> Result<(), AnyError> {
import_list.sort_by(|(k1, _), (k2, _)| k1.cmp(k2));
let generated_imports = generate_imports(import_list);
let fmt_config_options = config_file
.to_fmt_config()
.ok()
.flatten()
.map(|config| config.options)
.unwrap_or_default();
let fmt_config_options = config_file.fmt_options();
let new_text = update_config_file_content(
obj,
&config_file_contents,
generated_imports,
fmt_config_options,
config_file.imports_key(),
config_file.file_name(),
);
tokio::fs::write(&config_file_path, new_text)
.await
.context("Failed to update configuration file")?;
// TODO(bartlomieju): we should now cache the imports from the config file.
// TODO(bartlomieju): we should now cache the imports from the deno.json.
// make a new CliFactory to pick up the updated config file
let cli_factory = CliFactory::from_flags(flags)?;
// cache deps
if cli_factory.cli_options().enable_future_features() {
crate::module_loader::load_top_level_deps(&cli_factory).await?;
}
Ok(())
}
@ -259,10 +411,12 @@ fn update_config_file_content(
config_file_contents: &str,
generated_imports: String,
fmt_options: FmtOptionsConfig,
imports_key: &str,
file_name: &str,
) -> String {
let mut text_changes = vec![];
match obj.get("imports") {
match obj.get(imports_key) {
Some(ObjectProp {
value: Value::Object(lit),
..
@ -282,10 +436,10 @@ fn update_config_file_content(
// "<package_name>": "<registry>:<package_name>@<semver>"
// }
// }
new_text: format!("\"imports\": {{\n {} }}", generated_imports),
new_text: format!("\"{imports_key}\": {{\n {generated_imports} }}"),
})
}
// we verified the shape of `imports` above
// we verified the shape of `imports`/`dependencies` above
Some(_) => unreachable!(),
}
@ -293,7 +447,7 @@ fn update_config_file_content(
deno_ast::apply_text_changes(config_file_contents, text_changes);
crate::tools::fmt::format_json(
&PathBuf::from("deno.json"),
&PathBuf::from(file_name),
&new_text,
&fmt_options,
)

View File

@ -0,0 +1,16 @@
{
"tempDir": true,
"envs": {
"DENO_FUTURE": "1"
},
"steps": [
{
"args": "install --global --root ./bins --name deno-test-bin ./main.js",
"output": "install.out"
},
{
"args": "run -A assert.js",
"output": ""
}
]
}

View File

@ -0,0 +1,11 @@
const dirs = Deno.readDir("./bins/bin");
let found = false;
for await (const entry of dirs) {
if (entry.name.includes("deno-test-bin")) {
found = true;
}
}
if (!found) {
throw new Error("Failed to find test bin");
}

View File

@ -0,0 +1,5 @@
Download http://localhost:4260/@denotest/esm-basic
Download http://localhost:4260/@denotest/esm-basic/1.0.0.tgz
Initialize @denotest/esm-basic@1.0.0
✅ Successfully installed deno-test-bin[WILDCARD]
[WILDCARD]

View File

@ -0,0 +1,3 @@
import { setValue } from "@denotest/esm-basic";
setValue(5);

View File

@ -0,0 +1,7 @@
{
"name": "deno-test-bin",
"dependencies": {
"@denotest/esm-basic": "*"
},
"type": "module"
}

View File

@ -0,0 +1,24 @@
{
"tempDir": true,
"envs": {
"DENO_FUTURE": "1"
},
"steps": [
{
"args": "install npm:@denotest/esm-basic",
"output": "install.out"
},
{
// make sure the dep got cached
"args": "run --cached-only main.js",
"output": ""
},
{
"args": [
"eval",
"console.log(Deno.readTextFileSync('deno.json').trim())"
],
"output": "deno.json.out"
}
]
}

View File

@ -0,0 +1 @@
{}

View File

@ -0,0 +1,5 @@
{
"imports": {
"@denotest/esm-basic": "npm:@denotest/esm-basic@^1.0.0"
}
}

View File

@ -0,0 +1,4 @@
⚠️ `deno install` behavior will change in Deno 2. To preserve the current behavior use the `-g` or `--global` flag.
Add @denotest/esm-basic - npm:@denotest/esm-basic@^1.0.0
Download http://localhost:4260/@denotest/esm-basic
Download http://localhost:4260/@denotest/esm-basic/1.0.0.tgz

View File

@ -0,0 +1,2 @@
import { setValue } from "@denotest/esm-basic";
setValue(5);

View File

@ -0,0 +1,25 @@
{
"tempDir": true,
"envs": {
"DENO_FUTURE": "1"
},
"steps": [
{
"args": "install npm:@denotest/esm-basic",
"output": "install.out"
},
{
// make sure the dep got cached
"args": "run --cached-only main.js",
"exitCode": 0,
"output": ""
},
{
"args": [
"eval",
"console.log(Deno.readTextFileSync('package.json').trim())"
],
"output": "package.json.out"
}
]
}

View File

@ -0,0 +1,5 @@
⚠️ `deno install` behavior will change in Deno 2. To preserve the current behavior use the `-g` or `--global` flag.
Add @denotest/esm-basic - npm:@denotest/esm-basic@^1.0.0
Download http://localhost:4260/@denotest/esm-basic
Download http://localhost:4260/@denotest/esm-basic/1.0.0.tgz
Initialize @denotest/esm-basic@1.0.0

View File

@ -0,0 +1,2 @@
import { setValue } from "@denotest/esm-basic";
setValue(5);

View File

@ -0,0 +1,3 @@
{
"dependencies": {}
}

View File

@ -0,0 +1,3 @@
{
"dependencies": { "@denotest/esm-basic": "^1.0.0" }
}

View File

@ -0,0 +1,17 @@
{
"tempDir": true,
"envs": {
"DENO_FUTURE": "1"
},
"steps": [
{
"args": "install",
"output": "install.out"
},
{
// ensure deps are actually cached
"args": "run --cached-only main.js",
"output": ""
}
]
}

View File

@ -0,0 +1,8 @@
{
"imports": {
"@std/fs/": "https://deno.land/std@0.224.0/fs/",
"@denotest/esm-basic": "npm:@denotest/esm-basic@^1.0.0",
"@denotest/add": "jsr:@denotest/add",
"test-http": "http://localhost:4545/v1/extensionless"
}
}

View File

@ -0,0 +1,10 @@
⚠️ `deno install` behavior will change in Deno 2. To preserve the current behavior use the `-g` or `--global` flag.
Download http://localhost:4545/v1/extensionless
Download http://localhost:4545/subdir/mod1.ts
Download http://localhost:4545/subdir/subdir2/mod2.ts
Download http://localhost:4545/subdir/print_hello.ts
Download http://127.0.0.1:4250/@denotest/add/meta.json
Download http://127.0.0.1:4250/@denotest/add/1.0.0_meta.json
Download http://127.0.0.1:4250/@denotest/add/1.0.0/mod.ts
Download http://localhost:4260/@denotest/esm-basic
Download http://localhost:4260/@denotest/esm-basic/1.0.0.tgz

View File

@ -0,0 +1,6 @@
import { setValue } from "@denotest/esm-basic";
import { add } from "@denotest/add";
import { returnsHi } from "test-http";
setValue(5);
returnsHi();
add(2, 2);

View File

@ -0,0 +1,17 @@
{
"tempDir": true,
"envs": {
"DENO_FUTURE": "1"
},
"steps": [
{
"args": "install",
"output": "install.out"
},
{
// ensure deps are actually cached
"args": "run --cached-only main.js",
"output": ""
}
]
}

View File

@ -0,0 +1,4 @@
⚠️ `deno install` behavior will change in Deno 2. To preserve the current behavior use the `-g` or `--global` flag.
Download http://localhost:4260/@denotest/esm-basic
Download http://localhost:4260/@denotest/esm-basic/1.0.0.tgz
Initialize @denotest/esm-basic@1.0.0

View File

@ -0,0 +1,3 @@
import { setValue } from "@denotest/esm-basic";
setValue(5);

View File

@ -0,0 +1,5 @@
{
"dependencies": {
"@denotest/esm-basic": "*"
}
}

View File

@ -0,0 +1,13 @@
{
"tempDir": true,
"steps": [
{
"args": "install --root ./bins --name deno-test-bin ./main.js",
"output": "install.out"
},
{
"args": "run -A ./assert.js",
"output": ""
}
]
}

View File

@ -0,0 +1,11 @@
const dirs = Deno.readDir("./bins/bin");
let found = false;
for await (const entry of dirs) {
if (entry.name.includes("deno-test-bin")) {
found = true;
}
}
if (!found) {
throw new Error("Failed to find test bin");
}

View File

@ -0,0 +1,5 @@
⚠️ `deno install` behavior will change in Deno 2. To preserve the current behavior use the `-g` or `--global` flag.
Download http://localhost:4260/@denotest/esm-basic
Download http://localhost:4260/@denotest/esm-basic/1.0.0.tgz
✅ Successfully installed deno-test-bin[WILDCARD]
[WILDCARD]

View File

@ -0,0 +1,3 @@
import { setValue } from "npm:@denotest/esm-basic";
setValue(5);

View File

@ -0,0 +1,6 @@
{
"name": "deno-test-bin",
"dependencies": {
"@denotest/esm-basic": "*"
}
}