mirror of
https://github.com/denoland/deno.git
synced 2024-12-24 08:09:08 -05:00
feat(publish): give diagnostic on invalid package files (#22082)
This commit is contained in:
parent
fc176c4dea
commit
52ad1ef154
16 changed files with 441 additions and 86 deletions
|
@ -4,6 +4,7 @@ use std::borrow::Cow;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
use std::fmt::Write as _;
|
use std::fmt::Write as _;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use deno_ast::ModuleSpecifier;
|
use deno_ast::ModuleSpecifier;
|
||||||
use deno_ast::SourcePos;
|
use deno_ast::SourcePos;
|
||||||
|
@ -61,17 +62,19 @@ impl DiagnosticSourcePos {
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum DiagnosticLocation<'a> {
|
pub enum DiagnosticLocation<'a> {
|
||||||
/// The diagnostic is relevant to an entire file.
|
/// The diagnostic is relevant to a specific path.
|
||||||
File {
|
Path { path: PathBuf },
|
||||||
|
/// The diagnostic is relevant to an entire module.
|
||||||
|
Module {
|
||||||
/// The specifier of the module that contains the diagnostic.
|
/// The specifier of the module that contains the diagnostic.
|
||||||
specifier: Cow<'a, ModuleSpecifier>,
|
specifier: Cow<'a, ModuleSpecifier>,
|
||||||
},
|
},
|
||||||
/// The diagnostic is relevant to a specific position in a file.
|
/// The diagnostic is relevant to a specific position in a module.
|
||||||
///
|
///
|
||||||
/// This variant will get the relevant `SouceTextInfo` from the cache using
|
/// This variant will get the relevant `SouceTextInfo` from the cache using
|
||||||
/// the given specifier, and will then calculate the line and column numbers
|
/// the given specifier, and will then calculate the line and column numbers
|
||||||
/// from the given `SourcePos`.
|
/// from the given `SourcePos`.
|
||||||
PositionInFile {
|
ModulePosition {
|
||||||
/// The specifier of the module that contains the diagnostic.
|
/// The specifier of the module that contains the diagnostic.
|
||||||
specifier: Cow<'a, ModuleSpecifier>,
|
specifier: Cow<'a, ModuleSpecifier>,
|
||||||
/// The source position of the diagnostic.
|
/// The source position of the diagnostic.
|
||||||
|
@ -80,13 +83,6 @@ pub enum DiagnosticLocation<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> DiagnosticLocation<'a> {
|
impl<'a> DiagnosticLocation<'a> {
|
||||||
fn specifier(&self) -> &ModuleSpecifier {
|
|
||||||
match self {
|
|
||||||
DiagnosticLocation::File { specifier } => specifier,
|
|
||||||
DiagnosticLocation::PositionInFile { specifier, .. } => specifier,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return the line and column number of the diagnostic.
|
/// Return the line and column number of the diagnostic.
|
||||||
///
|
///
|
||||||
/// The line number is 1-indexed.
|
/// The line number is 1-indexed.
|
||||||
|
@ -97,8 +93,9 @@ impl<'a> DiagnosticLocation<'a> {
|
||||||
/// everyone uses VS Code. :)
|
/// everyone uses VS Code. :)
|
||||||
fn position(&self, sources: &dyn SourceTextStore) -> Option<(usize, usize)> {
|
fn position(&self, sources: &dyn SourceTextStore) -> Option<(usize, usize)> {
|
||||||
match self {
|
match self {
|
||||||
DiagnosticLocation::File { .. } => None,
|
DiagnosticLocation::Path { .. } => None,
|
||||||
DiagnosticLocation::PositionInFile {
|
DiagnosticLocation::Module { .. } => None,
|
||||||
|
DiagnosticLocation::ModulePosition {
|
||||||
specifier,
|
specifier,
|
||||||
source_pos,
|
source_pos,
|
||||||
} => {
|
} => {
|
||||||
|
@ -384,7 +381,7 @@ fn print_diagnostic(
|
||||||
write!(
|
write!(
|
||||||
io,
|
io,
|
||||||
"{}",
|
"{}",
|
||||||
colors::yellow(format_args!("warning[{}]", diagnostic.code()))
|
colors::yellow_bold(format_args!("warning[{}]", diagnostic.code()))
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -410,11 +407,18 @@ fn print_diagnostic(
|
||||||
RepeatingCharFmt(' ', max_line_number_digits as usize),
|
RepeatingCharFmt(' ', max_line_number_digits as usize),
|
||||||
colors::intense_blue("-->"),
|
colors::intense_blue("-->"),
|
||||||
)?;
|
)?;
|
||||||
let location_specifier = location.specifier();
|
match &location {
|
||||||
if let Ok(path) = location_specifier.to_file_path() {
|
DiagnosticLocation::Path { path } => {
|
||||||
write!(io, " {}", colors::cyan(path.display()))?;
|
write!(io, " {}", colors::cyan(path.display()))?;
|
||||||
} else {
|
}
|
||||||
write!(io, " {}", colors::cyan(location_specifier.as_str()))?;
|
DiagnosticLocation::Module { specifier }
|
||||||
|
| DiagnosticLocation::ModulePosition { specifier, .. } => {
|
||||||
|
if let Ok(path) = specifier.to_file_path() {
|
||||||
|
write!(io, " {}", colors::cyan(path.display()))?;
|
||||||
|
} else {
|
||||||
|
write!(io, " {}", colors::cyan(specifier.as_str()))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if let Some((line, column)) = location.position(sources) {
|
if let Some((line, column)) = location.position(sources) {
|
||||||
write!(
|
write!(
|
||||||
|
@ -614,7 +618,7 @@ mod tests {
|
||||||
specifier: specifier.clone(),
|
specifier: specifier.clone(),
|
||||||
text_info,
|
text_info,
|
||||||
};
|
};
|
||||||
let location = super::DiagnosticLocation::PositionInFile {
|
let location = super::DiagnosticLocation::ModulePosition {
|
||||||
specifier: Cow::Borrowed(&specifier),
|
specifier: Cow::Borrowed(&specifier),
|
||||||
source_pos: super::DiagnosticSourcePos::SourcePos(pos),
|
source_pos: super::DiagnosticSourcePos::SourcePos(pos),
|
||||||
};
|
};
|
||||||
|
@ -631,7 +635,7 @@ mod tests {
|
||||||
specifier: specifier.clone(),
|
specifier: specifier.clone(),
|
||||||
text_info,
|
text_info,
|
||||||
};
|
};
|
||||||
let location = super::DiagnosticLocation::PositionInFile {
|
let location = super::DiagnosticLocation::ModulePosition {
|
||||||
specifier: Cow::Borrowed(&specifier),
|
specifier: Cow::Borrowed(&specifier),
|
||||||
source_pos: super::DiagnosticSourcePos::SourcePos(pos),
|
source_pos: super::DiagnosticSourcePos::SourcePos(pos),
|
||||||
};
|
};
|
||||||
|
|
|
@ -25,101 +25,95 @@ itest!(missing_deno_json {
|
||||||
args: "publish --token 'sadfasdf'",
|
args: "publish --token 'sadfasdf'",
|
||||||
output: "publish/missing_deno_json.out",
|
output: "publish/missing_deno_json.out",
|
||||||
cwd: Some("publish/missing_deno_json"),
|
cwd: Some("publish/missing_deno_json"),
|
||||||
copy_temp_dir: Some("publish/missing_deno_json"),
|
|
||||||
exit_code: 1,
|
exit_code: 1,
|
||||||
temp_cwd: true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
itest!(invalid_fast_check {
|
itest!(invalid_fast_check {
|
||||||
args: "publish --token 'sadfasdf'",
|
args: "publish --token 'sadfasdf'",
|
||||||
output: "publish/invalid_fast_check.out",
|
output: "publish/invalid_fast_check.out",
|
||||||
cwd: Some("publish/invalid_fast_check"),
|
cwd: Some("publish/invalid_fast_check"),
|
||||||
copy_temp_dir: Some("publish/invalid_fast_check"),
|
|
||||||
exit_code: 1,
|
exit_code: 1,
|
||||||
temp_cwd: true,
|
});
|
||||||
|
|
||||||
|
itest!(invalid_path {
|
||||||
|
args: "publish --token 'sadfasdf'",
|
||||||
|
output: "publish/invalid_path.out",
|
||||||
|
cwd: Some("publish/invalid_path"),
|
||||||
|
exit_code: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
itest!(symlink {
|
||||||
|
args: "publish --token 'sadfasdf' --dry-run",
|
||||||
|
output: "publish/symlink.out",
|
||||||
|
cwd: Some("publish/symlink"),
|
||||||
|
exit_code: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
itest!(javascript_missing_decl_file {
|
itest!(javascript_missing_decl_file {
|
||||||
args: "publish --token 'sadfasdf'",
|
args: "publish --token 'sadfasdf'",
|
||||||
output: "publish/javascript_missing_decl_file.out",
|
output: "publish/javascript_missing_decl_file.out",
|
||||||
cwd: Some("publish/javascript_missing_decl_file"),
|
cwd: Some("publish/javascript_missing_decl_file"),
|
||||||
copy_temp_dir: Some("publish/javascript_missing_decl_file"),
|
|
||||||
envs: env_vars_for_registry(),
|
envs: env_vars_for_registry(),
|
||||||
exit_code: 0,
|
exit_code: 0,
|
||||||
http_server: true,
|
http_server: true,
|
||||||
temp_cwd: true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
itest!(unanalyzable_dynamic_import {
|
itest!(unanalyzable_dynamic_import {
|
||||||
args: "publish --token 'sadfasdf'",
|
args: "publish --token 'sadfasdf'",
|
||||||
output: "publish/unanalyzable_dynamic_import.out",
|
output: "publish/unanalyzable_dynamic_import.out",
|
||||||
cwd: Some("publish/unanalyzable_dynamic_import"),
|
cwd: Some("publish/unanalyzable_dynamic_import"),
|
||||||
copy_temp_dir: Some("publish/unanalyzable_dynamic_import"),
|
|
||||||
envs: env_vars_for_registry(),
|
envs: env_vars_for_registry(),
|
||||||
exit_code: 0,
|
exit_code: 0,
|
||||||
http_server: true,
|
http_server: true,
|
||||||
temp_cwd: true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
itest!(javascript_decl_file {
|
itest!(javascript_decl_file {
|
||||||
args: "publish --token 'sadfasdf'",
|
args: "publish --token 'sadfasdf'",
|
||||||
output: "publish/javascript_decl_file.out",
|
output: "publish/javascript_decl_file.out",
|
||||||
cwd: Some("publish/javascript_decl_file"),
|
cwd: Some("publish/javascript_decl_file"),
|
||||||
copy_temp_dir: Some("publish/javascript_decl_file"),
|
|
||||||
envs: env_vars_for_registry(),
|
envs: env_vars_for_registry(),
|
||||||
http_server: true,
|
http_server: true,
|
||||||
exit_code: 0,
|
exit_code: 0,
|
||||||
temp_cwd: true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
itest!(successful {
|
itest!(successful {
|
||||||
args: "publish --token 'sadfasdf'",
|
args: "publish --token 'sadfasdf'",
|
||||||
output: "publish/successful.out",
|
output: "publish/successful.out",
|
||||||
cwd: Some("publish/successful"),
|
cwd: Some("publish/successful"),
|
||||||
copy_temp_dir: Some("publish/successful"),
|
|
||||||
envs: env_vars_for_registry(),
|
envs: env_vars_for_registry(),
|
||||||
http_server: true,
|
http_server: true,
|
||||||
temp_cwd: true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
itest!(config_file_jsonc {
|
itest!(config_file_jsonc {
|
||||||
args: "publish --token 'sadfasdf'",
|
args: "publish --token 'sadfasdf'",
|
||||||
output: "publish/deno_jsonc.out",
|
output: "publish/deno_jsonc.out",
|
||||||
cwd: Some("publish/deno_jsonc"),
|
cwd: Some("publish/deno_jsonc"),
|
||||||
copy_temp_dir: Some("publish/deno_jsonc"),
|
|
||||||
envs: env_vars_for_registry(),
|
envs: env_vars_for_registry(),
|
||||||
http_server: true,
|
http_server: true,
|
||||||
temp_cwd: true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
itest!(workspace_all {
|
itest!(workspace_all {
|
||||||
args: "publish --token 'sadfasdf'",
|
args: "publish --token 'sadfasdf'",
|
||||||
output: "publish/workspace.out",
|
output: "publish/workspace.out",
|
||||||
cwd: Some("publish/workspace"),
|
cwd: Some("publish/workspace"),
|
||||||
copy_temp_dir: Some("publish/workspace"),
|
|
||||||
envs: env_vars_for_registry(),
|
envs: env_vars_for_registry(),
|
||||||
http_server: true,
|
http_server: true,
|
||||||
temp_cwd: true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
itest!(workspace_individual {
|
itest!(workspace_individual {
|
||||||
args: "publish --token 'sadfasdf'",
|
args: "publish --token 'sadfasdf'",
|
||||||
output: "publish/workspace_individual.out",
|
output: "publish/workspace_individual.out",
|
||||||
cwd: Some("publish/workspace/bar"),
|
cwd: Some("publish/workspace/bar"),
|
||||||
copy_temp_dir: Some("publish/workspace"),
|
|
||||||
envs: env_vars_for_registry(),
|
envs: env_vars_for_registry(),
|
||||||
http_server: true,
|
http_server: true,
|
||||||
temp_cwd: true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
itest!(dry_run {
|
itest!(dry_run {
|
||||||
args: "publish --token 'sadfasdf' --dry-run",
|
args: "publish --token 'sadfasdf' --dry-run",
|
||||||
cwd: Some("publish/successful"),
|
cwd: Some("publish/successful"),
|
||||||
copy_temp_dir: Some("publish/successful"),
|
|
||||||
output: "publish/dry_run.out",
|
output: "publish/dry_run.out",
|
||||||
envs: env_vars_for_registry(),
|
envs: env_vars_for_registry(),
|
||||||
http_server: true,
|
http_server: true,
|
||||||
temp_cwd: true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
11
cli/tests/testdata/publish/invalid_path.out
vendored
Normal file
11
cli/tests/testdata/publish/invalid_path.out
vendored
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
Checking fast check type graph for errors...
|
||||||
|
Ensuring type checks...
|
||||||
|
Check file://[WILDCARD]mod.ts
|
||||||
|
error[invalid-path]: package path must not contain whitespace (found ' ')
|
||||||
|
--> [WILDCARD]path with spaces.txt
|
||||||
|
= hint: rename or remove the file, or add it to 'publish.exclude' in the config file
|
||||||
|
|
||||||
|
info: to portably support all platforms, including windows, the allowed characters in package paths are limited
|
||||||
|
docs: https://jsr.io/go/invalid-path
|
||||||
|
|
||||||
|
error: Found 1 problem
|
7
cli/tests/testdata/publish/invalid_path/deno.json
vendored
Normal file
7
cli/tests/testdata/publish/invalid_path/deno.json
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"name": "@foo/bar",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"exports": {
|
||||||
|
".": "./mod.ts"
|
||||||
|
}
|
||||||
|
}
|
3
cli/tests/testdata/publish/invalid_path/mod.ts
vendored
Normal file
3
cli/tests/testdata/publish/invalid_path/mod.ts
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export function foobar(): string {
|
||||||
|
return "string";
|
||||||
|
}
|
0
cli/tests/testdata/publish/invalid_path/path with spaces.txt
vendored
Normal file
0
cli/tests/testdata/publish/invalid_path/path with spaces.txt
vendored
Normal file
11
cli/tests/testdata/publish/symlink.out
vendored
Normal file
11
cli/tests/testdata/publish/symlink.out
vendored
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
Checking fast check type graph for errors...
|
||||||
|
Ensuring type checks...
|
||||||
|
Check [WILDCARD]mod.ts
|
||||||
|
warning[unsupported-file-type]: unsupported file type 'symlink'
|
||||||
|
--> [WILDCARD]symlink
|
||||||
|
= hint: remove the file, or add it to 'publish.exclude' in the config file
|
||||||
|
|
||||||
|
info: only files and directories are supported
|
||||||
|
info: the file was ignored and will not be published
|
||||||
|
|
||||||
|
Warning Aborting due to --dry-run
|
7
cli/tests/testdata/publish/symlink/deno.json
vendored
Normal file
7
cli/tests/testdata/publish/symlink/deno.json
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"name": "@foo/bar",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"exports": {
|
||||||
|
".": "./mod.ts"
|
||||||
|
}
|
||||||
|
}
|
3
cli/tests/testdata/publish/symlink/mod.ts
vendored
Normal file
3
cli/tests/testdata/publish/symlink/mod.ts
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export function foobar(): string {
|
||||||
|
return "string";
|
||||||
|
}
|
1
cli/tests/testdata/publish/symlink/symlink
vendored
Symbolic link
1
cli/tests/testdata/publish/symlink/symlink
vendored
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
./mod.ts
|
|
@ -339,7 +339,7 @@ impl Diagnostic for DocDiagnostic {
|
||||||
|
|
||||||
fn location(&self) -> DiagnosticLocation {
|
fn location(&self) -> DiagnosticLocation {
|
||||||
let specifier = Url::parse(&self.location.filename).unwrap();
|
let specifier = Url::parse(&self.location.filename).unwrap();
|
||||||
DiagnosticLocation::PositionInFile {
|
DiagnosticLocation::ModulePosition {
|
||||||
specifier: Cow::Owned(specifier),
|
specifier: Cow::Owned(specifier),
|
||||||
source_pos: DiagnosticSourcePos::ByteIndex(self.location.byte_index),
|
source_pos: DiagnosticSourcePos::ByteIndex(self.location.byte_index),
|
||||||
}
|
}
|
||||||
|
|
|
@ -374,7 +374,7 @@ impl Diagnostic for LintDiagnostic {
|
||||||
|
|
||||||
fn location(&self) -> DiagnosticLocation {
|
fn location(&self) -> DiagnosticLocation {
|
||||||
let specifier = url::Url::from_file_path(&self.filename).unwrap();
|
let specifier = url::Url::from_file_path(&self.filename).unwrap();
|
||||||
DiagnosticLocation::PositionInFile {
|
DiagnosticLocation::ModulePosition {
|
||||||
specifier: Cow::Owned(specifier),
|
specifier: Cow::Owned(specifier),
|
||||||
source_pos: DiagnosticSourcePos::ByteIndex(self.range.start.byte_index),
|
source_pos: DiagnosticSourcePos::ByteIndex(self.range.start.byte_index),
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
|
use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
|
||||||
|
@ -10,6 +11,7 @@ use deno_core::anyhow::anyhow;
|
||||||
use deno_core::error::AnyError;
|
use deno_core::error::AnyError;
|
||||||
use deno_graph::FastCheckDiagnostic;
|
use deno_graph::FastCheckDiagnostic;
|
||||||
use deno_graph::ParsedSourceStore;
|
use deno_graph::ParsedSourceStore;
|
||||||
|
use lsp_types::Url;
|
||||||
|
|
||||||
use crate::diagnostics::Diagnostic;
|
use crate::diagnostics::Diagnostic;
|
||||||
use crate::diagnostics::DiagnosticLevel;
|
use crate::diagnostics::DiagnosticLevel;
|
||||||
|
@ -61,6 +63,9 @@ impl PublishDiagnosticsCollector {
|
||||||
pub enum PublishDiagnostic {
|
pub enum PublishDiagnostic {
|
||||||
FastCheck(FastCheckDiagnostic),
|
FastCheck(FastCheckDiagnostic),
|
||||||
ImportMapUnfurl(ImportMapUnfurlDiagnostic),
|
ImportMapUnfurl(ImportMapUnfurlDiagnostic),
|
||||||
|
InvalidPath { path: PathBuf, message: String },
|
||||||
|
DuplicatePath { path: PathBuf },
|
||||||
|
UnsupportedFileType { specifier: Url, kind: String },
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Diagnostic for PublishDiagnostic {
|
impl Diagnostic for PublishDiagnostic {
|
||||||
|
@ -71,6 +76,9 @@ impl Diagnostic for PublishDiagnostic {
|
||||||
) => DiagnosticLevel::Warning,
|
) => DiagnosticLevel::Warning,
|
||||||
PublishDiagnostic::FastCheck(_) => DiagnosticLevel::Error,
|
PublishDiagnostic::FastCheck(_) => DiagnosticLevel::Error,
|
||||||
PublishDiagnostic::ImportMapUnfurl(_) => DiagnosticLevel::Warning,
|
PublishDiagnostic::ImportMapUnfurl(_) => DiagnosticLevel::Warning,
|
||||||
|
PublishDiagnostic::InvalidPath { .. } => DiagnosticLevel::Error,
|
||||||
|
PublishDiagnostic::DuplicatePath { .. } => DiagnosticLevel::Error,
|
||||||
|
PublishDiagnostic::UnsupportedFileType { .. } => DiagnosticLevel::Warning,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,6 +86,11 @@ impl Diagnostic for PublishDiagnostic {
|
||||||
match &self {
|
match &self {
|
||||||
PublishDiagnostic::FastCheck(diagnostic) => diagnostic.code(),
|
PublishDiagnostic::FastCheck(diagnostic) => diagnostic.code(),
|
||||||
PublishDiagnostic::ImportMapUnfurl(diagnostic) => diagnostic.code(),
|
PublishDiagnostic::ImportMapUnfurl(diagnostic) => diagnostic.code(),
|
||||||
|
PublishDiagnostic::InvalidPath { .. } => "invalid-path",
|
||||||
|
PublishDiagnostic::DuplicatePath { .. } => {
|
||||||
|
"case-insensitive-duplicate-path"
|
||||||
|
}
|
||||||
|
PublishDiagnostic::UnsupportedFileType { .. } => "unsupported-file-type",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,17 +102,26 @@ impl Diagnostic for PublishDiagnostic {
|
||||||
PublishDiagnostic::ImportMapUnfurl(diagnostic) => {
|
PublishDiagnostic::ImportMapUnfurl(diagnostic) => {
|
||||||
Cow::Borrowed(diagnostic.message())
|
Cow::Borrowed(diagnostic.message())
|
||||||
}
|
}
|
||||||
|
PublishDiagnostic::InvalidPath { message, .. } => {
|
||||||
|
Cow::Borrowed(message.as_str())
|
||||||
|
}
|
||||||
|
PublishDiagnostic::DuplicatePath { .. } => {
|
||||||
|
Cow::Borrowed("package path is a case insensitive duplicate of another path in the package")
|
||||||
|
}
|
||||||
|
PublishDiagnostic::UnsupportedFileType { kind, .. } => {
|
||||||
|
Cow::Owned(format!("unsupported file type '{kind}'",))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn location(&self) -> DiagnosticLocation {
|
fn location(&self) -> DiagnosticLocation {
|
||||||
match &self {
|
match &self {
|
||||||
PublishDiagnostic::FastCheck(diagnostic) => match diagnostic.range() {
|
PublishDiagnostic::FastCheck(diagnostic) => match diagnostic.range() {
|
||||||
Some(range) => DiagnosticLocation::PositionInFile {
|
Some(range) => DiagnosticLocation::ModulePosition {
|
||||||
specifier: Cow::Borrowed(diagnostic.specifier()),
|
specifier: Cow::Borrowed(diagnostic.specifier()),
|
||||||
source_pos: DiagnosticSourcePos::SourcePos(range.range.start),
|
source_pos: DiagnosticSourcePos::SourcePos(range.range.start),
|
||||||
},
|
},
|
||||||
None => DiagnosticLocation::File {
|
None => DiagnosticLocation::Module {
|
||||||
specifier: Cow::Borrowed(diagnostic.specifier()),
|
specifier: Cow::Borrowed(diagnostic.specifier()),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -107,11 +129,22 @@ impl Diagnostic for PublishDiagnostic {
|
||||||
ImportMapUnfurlDiagnostic::UnanalyzableDynamicImport {
|
ImportMapUnfurlDiagnostic::UnanalyzableDynamicImport {
|
||||||
specifier,
|
specifier,
|
||||||
range,
|
range,
|
||||||
} => DiagnosticLocation::PositionInFile {
|
} => DiagnosticLocation::ModulePosition {
|
||||||
specifier: Cow::Borrowed(specifier),
|
specifier: Cow::Borrowed(specifier),
|
||||||
source_pos: DiagnosticSourcePos::SourcePos(range.start),
|
source_pos: DiagnosticSourcePos::SourcePos(range.start),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
PublishDiagnostic::InvalidPath { path, .. } => {
|
||||||
|
DiagnosticLocation::Path { path: path.clone() }
|
||||||
|
}
|
||||||
|
PublishDiagnostic::DuplicatePath { path, .. } => {
|
||||||
|
DiagnosticLocation::Path { path: path.clone() }
|
||||||
|
}
|
||||||
|
PublishDiagnostic::UnsupportedFileType { specifier, .. } => {
|
||||||
|
DiagnosticLocation::Module {
|
||||||
|
specifier: Cow::Borrowed(specifier),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -148,6 +181,9 @@ impl Diagnostic for PublishDiagnostic {
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
PublishDiagnostic::InvalidPath { .. } => None,
|
||||||
|
PublishDiagnostic::DuplicatePath { .. } => None,
|
||||||
|
PublishDiagnostic::UnsupportedFileType { .. } => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -155,6 +191,15 @@ impl Diagnostic for PublishDiagnostic {
|
||||||
match &self {
|
match &self {
|
||||||
PublishDiagnostic::FastCheck(diagnostic) => Some(diagnostic.fix_hint()),
|
PublishDiagnostic::FastCheck(diagnostic) => Some(diagnostic.fix_hint()),
|
||||||
PublishDiagnostic::ImportMapUnfurl(_) => None,
|
PublishDiagnostic::ImportMapUnfurl(_) => None,
|
||||||
|
PublishDiagnostic::InvalidPath { .. } => Some(
|
||||||
|
"rename or remove the file, or add it to 'publish.exclude' in the config file",
|
||||||
|
),
|
||||||
|
PublishDiagnostic::DuplicatePath { .. } => Some(
|
||||||
|
"rename or remove the file",
|
||||||
|
),
|
||||||
|
PublishDiagnostic::UnsupportedFileType { .. } => Some(
|
||||||
|
"remove the file, or add it to 'publish.exclude' in the config file",
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -179,6 +224,16 @@ impl Diagnostic for PublishDiagnostic {
|
||||||
Cow::Borrowed("make sure the dynamic import is resolvable at runtime without an import map")
|
Cow::Borrowed("make sure the dynamic import is resolvable at runtime without an import map")
|
||||||
]),
|
]),
|
||||||
},
|
},
|
||||||
|
PublishDiagnostic::InvalidPath { .. } => Cow::Borrowed(&[
|
||||||
|
Cow::Borrowed("to portably support all platforms, including windows, the allowed characters in package paths are limited"),
|
||||||
|
]),
|
||||||
|
PublishDiagnostic::DuplicatePath { .. } => Cow::Borrowed(&[
|
||||||
|
Cow::Borrowed("to support case insensitive file systems, no two package paths may differ only by case"),
|
||||||
|
]),
|
||||||
|
PublishDiagnostic::UnsupportedFileType { .. } => Cow::Borrowed(&[
|
||||||
|
Cow::Borrowed("only files and directories are supported"),
|
||||||
|
Cow::Borrowed("the file was ignored and will not be published")
|
||||||
|
]),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -190,6 +245,13 @@ impl Diagnostic for PublishDiagnostic {
|
||||||
PublishDiagnostic::ImportMapUnfurl(diagnostic) => match diagnostic {
|
PublishDiagnostic::ImportMapUnfurl(diagnostic) => match diagnostic {
|
||||||
ImportMapUnfurlDiagnostic::UnanalyzableDynamicImport { .. } => None,
|
ImportMapUnfurlDiagnostic::UnanalyzableDynamicImport { .. } => None,
|
||||||
},
|
},
|
||||||
|
PublishDiagnostic::InvalidPath { .. } => {
|
||||||
|
Some("https://jsr.io/go/invalid-path".to_owned())
|
||||||
|
}
|
||||||
|
PublishDiagnostic::DuplicatePath { .. } => {
|
||||||
|
Some("https://jsr.io/go/case-insensitive-duplicate-path".to_owned())
|
||||||
|
}
|
||||||
|
PublishDiagnostic::UnsupportedFileType { .. } => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,6 +44,7 @@ mod api;
|
||||||
mod auth;
|
mod auth;
|
||||||
mod diagnostics;
|
mod diagnostics;
|
||||||
mod graph;
|
mod graph;
|
||||||
|
mod paths;
|
||||||
mod publish_order;
|
mod publish_order;
|
||||||
mod tar;
|
mod tar;
|
||||||
|
|
||||||
|
@ -474,7 +475,7 @@ async fn perform_publish(
|
||||||
log::debug!(
|
log::debug!(
|
||||||
" Tarball file {} {}",
|
" Tarball file {} {}",
|
||||||
human_size(file.size as f64),
|
human_size(file.size as f64),
|
||||||
file.path.display()
|
file.specifier
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
198
cli/tools/registry/paths.rs
Normal file
198
cli/tools/registry/paths.rs
Normal file
|
@ -0,0 +1,198 @@
|
||||||
|
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
||||||
|
|
||||||
|
// Validation logic in this file is shared with registry/api/src/ids.rs
|
||||||
|
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
/// A package path, like '/foo' or '/foo/bar'. The path is prefixed with a slash
|
||||||
|
/// and does not end with a slash.
|
||||||
|
///
|
||||||
|
/// The path must not contain any double slashes, dot segments, or dot dot
|
||||||
|
/// segments.
|
||||||
|
///
|
||||||
|
/// The path must be less than 160 characters long, including the slash prefix.
|
||||||
|
///
|
||||||
|
/// The path must not contain any windows reserved characters, like CON, PRN,
|
||||||
|
/// AUX, NUL, or COM1.
|
||||||
|
///
|
||||||
|
/// The path must not contain any windows path separators, like backslash or
|
||||||
|
/// colon.
|
||||||
|
///
|
||||||
|
/// The path must only contain ascii alphanumeric characters, and the characters
|
||||||
|
/// '$', '(', ')', '+', '-', '.', '@', '[', ']', '_', '{', '}', '~'.
|
||||||
|
///
|
||||||
|
/// Path's are case sensitive, but comparisons and hashing are case insensitive.
|
||||||
|
/// This matches the behaviour of the Windows FS APIs.
|
||||||
|
#[derive(Clone, Default)]
|
||||||
|
pub struct PackagePath {
|
||||||
|
path: String,
|
||||||
|
lower: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for PackagePath {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
let self_lower = self.lower.as_ref().unwrap_or(&self.path);
|
||||||
|
let other_lower = other.lower.as_ref().unwrap_or(&other.path);
|
||||||
|
self_lower == other_lower
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for PackagePath {}
|
||||||
|
|
||||||
|
impl std::hash::Hash for PackagePath {
|
||||||
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||||
|
let lower = self.lower.as_ref().unwrap_or(&self.path);
|
||||||
|
lower.hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PackagePath {
|
||||||
|
pub fn new(path: String) -> Result<Self, PackagePathValidationError> {
|
||||||
|
let len = path.len();
|
||||||
|
if len > 160 {
|
||||||
|
return Err(PackagePathValidationError::TooLong(len));
|
||||||
|
}
|
||||||
|
|
||||||
|
if len == 0 {
|
||||||
|
return Err(PackagePathValidationError::MissingPrefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut components = path.split('/').peekable();
|
||||||
|
let Some("") = components.next() else {
|
||||||
|
return Err(PackagePathValidationError::MissingPrefix);
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut has_upper = false;
|
||||||
|
let mut valid_char_mapper = |c: char| {
|
||||||
|
if c.is_ascii_uppercase() {
|
||||||
|
has_upper = true;
|
||||||
|
}
|
||||||
|
valid_char(c)
|
||||||
|
};
|
||||||
|
while let Some(component) = components.next() {
|
||||||
|
if component.is_empty() {
|
||||||
|
if components.peek().is_none() {
|
||||||
|
return Err(PackagePathValidationError::TrailingSlash);
|
||||||
|
}
|
||||||
|
return Err(PackagePathValidationError::EmptyComponent);
|
||||||
|
}
|
||||||
|
|
||||||
|
if component == "." || component == ".." {
|
||||||
|
return Err(PackagePathValidationError::DotSegment);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(err) = component.chars().find_map(&mut valid_char_mapper) {
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
let basename = match component.rsplit_once('.') {
|
||||||
|
Some((_, "")) => {
|
||||||
|
return Err(PackagePathValidationError::TrailingDot(
|
||||||
|
component.to_owned(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Some((basename, _)) => basename,
|
||||||
|
None => component,
|
||||||
|
};
|
||||||
|
|
||||||
|
let lower_basename = basename.to_ascii_lowercase();
|
||||||
|
if WINDOWS_RESERVED_NAMES
|
||||||
|
.binary_search(&&*lower_basename)
|
||||||
|
.is_ok()
|
||||||
|
{
|
||||||
|
return Err(PackagePathValidationError::ReservedName(
|
||||||
|
component.to_owned(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let lower = has_upper.then(|| path.to_ascii_lowercase());
|
||||||
|
|
||||||
|
Ok(Self { path, lower })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const WINDOWS_RESERVED_NAMES: [&str; 22] = [
|
||||||
|
"aux", "com1", "com2", "com3", "com4", "com5", "com6", "com7", "com8",
|
||||||
|
"com9", "con", "lpt1", "lpt2", "lpt3", "lpt4", "lpt5", "lpt6", "lpt7",
|
||||||
|
"lpt8", "lpt9", "nul", "prn",
|
||||||
|
];
|
||||||
|
|
||||||
|
fn valid_char(c: char) -> Option<PackagePathValidationError> {
|
||||||
|
match c {
|
||||||
|
'a'..='z'
|
||||||
|
| 'A'..='Z'
|
||||||
|
| '0'..='9'
|
||||||
|
| '$'
|
||||||
|
| '('
|
||||||
|
| ')'
|
||||||
|
| '+'
|
||||||
|
| '-'
|
||||||
|
| '.'
|
||||||
|
| '@'
|
||||||
|
| '['
|
||||||
|
| ']'
|
||||||
|
| '_'
|
||||||
|
| '{'
|
||||||
|
| '}'
|
||||||
|
| '~' => None,
|
||||||
|
// informative error messages for some invalid characters
|
||||||
|
'\\' | ':' => Some(
|
||||||
|
PackagePathValidationError::InvalidWindowsPathSeparatorChar(c),
|
||||||
|
),
|
||||||
|
'<' | '>' | '"' | '|' | '?' | '*' => {
|
||||||
|
Some(PackagePathValidationError::InvalidWindowsChar(c))
|
||||||
|
}
|
||||||
|
' ' | '\t' | '\n' | '\r' => {
|
||||||
|
Some(PackagePathValidationError::InvalidWhitespace(c))
|
||||||
|
}
|
||||||
|
'%' | '#' => Some(PackagePathValidationError::InvalidSpecialUrlChar(c)),
|
||||||
|
// other invalid characters
|
||||||
|
c => Some(PackagePathValidationError::InvalidOtherChar(c)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Error)]
|
||||||
|
pub enum PackagePathValidationError {
|
||||||
|
#[error("package path must be at most 160 characters long, but is {0} characters long")]
|
||||||
|
TooLong(usize),
|
||||||
|
|
||||||
|
#[error("package path must be prefixed with a slash")]
|
||||||
|
MissingPrefix,
|
||||||
|
|
||||||
|
#[error("package path must not end with a slash")]
|
||||||
|
TrailingSlash,
|
||||||
|
|
||||||
|
#[error("package path must not contain empty components")]
|
||||||
|
EmptyComponent,
|
||||||
|
|
||||||
|
#[error("package path must not contain dot segments like '.' or '..'")]
|
||||||
|
DotSegment,
|
||||||
|
|
||||||
|
#[error(
|
||||||
|
"package path must not contain windows reserved names like 'CON' or 'PRN' (found '{0}')"
|
||||||
|
)]
|
||||||
|
ReservedName(String),
|
||||||
|
|
||||||
|
#[error("path segment must not end in a dot (found '{0}')")]
|
||||||
|
TrailingDot(String),
|
||||||
|
|
||||||
|
#[error(
|
||||||
|
"package path must not contain windows path separators like '\\' or ':' (found '{0}')"
|
||||||
|
)]
|
||||||
|
InvalidWindowsPathSeparatorChar(char),
|
||||||
|
|
||||||
|
#[error(
|
||||||
|
"package path must not contain windows reserved characters like '<', '>', '\"', '|', '?', or '*' (found '{0}')"
|
||||||
|
)]
|
||||||
|
InvalidWindowsChar(char),
|
||||||
|
|
||||||
|
#[error("package path must not contain whitespace (found '{}')", .0.escape_debug())]
|
||||||
|
InvalidWhitespace(char),
|
||||||
|
|
||||||
|
#[error("package path must not contain special URL characters (found '{}')", .0.escape_debug())]
|
||||||
|
InvalidSpecialUrlChar(char),
|
||||||
|
|
||||||
|
#[error("package path must not contain invalid characters (found '{}')", .0.escape_debug())]
|
||||||
|
InvalidOtherChar(char),
|
||||||
|
}
|
|
@ -2,17 +2,18 @@
|
||||||
|
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use deno_config::glob::FilePatterns;
|
use deno_config::glob::FilePatterns;
|
||||||
use deno_core::anyhow;
|
|
||||||
use deno_core::anyhow::Context;
|
use deno_core::anyhow::Context;
|
||||||
use deno_core::error::AnyError;
|
use deno_core::error::AnyError;
|
||||||
use deno_core::url::Url;
|
use deno_core::url::Url;
|
||||||
use sha2::Digest;
|
use sha2::Digest;
|
||||||
|
use std::collections::HashSet;
|
||||||
|
use std::ffi::OsStr;
|
||||||
use std::fmt::Write as FmtWrite;
|
use std::fmt::Write as FmtWrite;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::path::PathBuf;
|
|
||||||
use tar::Header;
|
use tar::Header;
|
||||||
|
|
||||||
|
use crate::tools::registry::paths::PackagePath;
|
||||||
use crate::util::import_map::ImportMapUnfurler;
|
use crate::util::import_map::ImportMapUnfurler;
|
||||||
|
|
||||||
use super::diagnostics::PublishDiagnostic;
|
use super::diagnostics::PublishDiagnostic;
|
||||||
|
@ -20,14 +21,13 @@ use super::diagnostics::PublishDiagnosticsCollector;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct PublishableTarballFile {
|
pub struct PublishableTarballFile {
|
||||||
pub path: PathBuf,
|
pub specifier: Url,
|
||||||
pub size: usize,
|
pub size: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct PublishableTarball {
|
pub struct PublishableTarball {
|
||||||
pub files: Vec<PublishableTarballFile>,
|
pub files: Vec<PublishableTarballFile>,
|
||||||
pub diagnostics: Vec<String>,
|
|
||||||
pub hash: String,
|
pub hash: String,
|
||||||
pub bytes: Bytes,
|
pub bytes: Bytes,
|
||||||
}
|
}
|
||||||
|
@ -40,67 +40,121 @@ pub fn create_gzipped_tarball(
|
||||||
file_patterns: Option<FilePatterns>,
|
file_patterns: Option<FilePatterns>,
|
||||||
) -> Result<PublishableTarball, AnyError> {
|
) -> Result<PublishableTarball, AnyError> {
|
||||||
let mut tar = TarGzArchive::new();
|
let mut tar = TarGzArchive::new();
|
||||||
let mut diagnostics = vec![];
|
|
||||||
let mut files = vec![];
|
let mut files = vec![];
|
||||||
|
|
||||||
|
let mut paths = HashSet::new();
|
||||||
|
|
||||||
let mut iterator = walkdir::WalkDir::new(dir).follow_links(false).into_iter();
|
let mut iterator = walkdir::WalkDir::new(dir).follow_links(false).into_iter();
|
||||||
while let Some(entry) = iterator.next() {
|
while let Some(entry) = iterator.next() {
|
||||||
let entry = entry?;
|
let entry = entry?;
|
||||||
|
|
||||||
if let Some(file_patterns) = &file_patterns {
|
let path = entry.path();
|
||||||
if !file_patterns.matches_path(entry.path()) {
|
let file_type = entry.file_type();
|
||||||
if entry.file_type().is_dir() {
|
|
||||||
iterator.skip_current_dir();
|
let matches_pattern = file_patterns
|
||||||
}
|
.as_ref()
|
||||||
continue;
|
.map(|p| p.matches_path(path))
|
||||||
|
.unwrap_or(true);
|
||||||
|
if !matches_pattern
|
||||||
|
|| path.file_name() == Some(OsStr::new(".git"))
|
||||||
|
|| path.file_name() == Some(OsStr::new("node_modules"))
|
||||||
|
{
|
||||||
|
if file_type.is_dir() {
|
||||||
|
iterator.skip_current_dir();
|
||||||
}
|
}
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if entry.file_type().is_file() {
|
let Ok(specifier) = Url::from_file_path(path) else {
|
||||||
let url = Url::from_file_path(entry.path())
|
diagnostics_collector
|
||||||
.map_err(|_| anyhow::anyhow!("Unable to convert path to url"))?;
|
.to_owned()
|
||||||
let relative_path = entry
|
.push(PublishDiagnostic::InvalidPath {
|
||||||
.path()
|
path: path.to_path_buf(),
|
||||||
.strip_prefix(dir)
|
message: "unable to convert path to url".to_string(),
|
||||||
.map_err(|err| anyhow::anyhow!("Unable to strip prefix: {err:#}"))?;
|
});
|
||||||
let relative_path_str = relative_path.to_str().ok_or_else(|| {
|
continue;
|
||||||
anyhow::anyhow!(
|
};
|
||||||
"Unable to convert path to string '{}'",
|
|
||||||
relative_path.display()
|
if file_type.is_file() {
|
||||||
)
|
let Ok(relative_path) = path.strip_prefix(dir) else {
|
||||||
})?;
|
diagnostics_collector
|
||||||
let data = std::fs::read(entry.path()).with_context(|| {
|
.to_owned()
|
||||||
|
.push(PublishDiagnostic::InvalidPath {
|
||||||
|
path: path.to_path_buf(),
|
||||||
|
message: "path is not in publish directory".to_string(),
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
let path_str = relative_path.components().fold(
|
||||||
|
"".to_string(),
|
||||||
|
|mut path, component| {
|
||||||
|
path.push('/');
|
||||||
|
match component {
|
||||||
|
std::path::Component::Normal(normal) => {
|
||||||
|
path.push_str(&normal.to_string_lossy())
|
||||||
|
}
|
||||||
|
std::path::Component::CurDir => path.push('.'),
|
||||||
|
std::path::Component::ParentDir => path.push_str(".."),
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
path
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
match PackagePath::new(path_str.clone()) {
|
||||||
|
Ok(package_path) => {
|
||||||
|
if !paths.insert(package_path) {
|
||||||
|
diagnostics_collector.to_owned().push(
|
||||||
|
PublishDiagnostic::DuplicatePath {
|
||||||
|
path: path.to_path_buf(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
diagnostics_collector.to_owned().push(
|
||||||
|
PublishDiagnostic::InvalidPath {
|
||||||
|
path: path.to_path_buf(),
|
||||||
|
message: err.to_string(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = std::fs::read(path).with_context(|| {
|
||||||
format!("Unable to read file '{}'", entry.path().display())
|
format!("Unable to read file '{}'", entry.path().display())
|
||||||
})?;
|
})?;
|
||||||
files.push(PublishableTarballFile {
|
files.push(PublishableTarballFile {
|
||||||
path: relative_path.to_path_buf(),
|
specifier: specifier.clone(),
|
||||||
size: data.len(),
|
size: data.len(),
|
||||||
});
|
});
|
||||||
let content = match source_cache.get_parsed_source(&url) {
|
let content = match source_cache.get_parsed_source(&specifier) {
|
||||||
Some(parsed_source) => {
|
Some(parsed_source) => {
|
||||||
let mut reporter = |diagnostic| {
|
let mut reporter = |diagnostic| {
|
||||||
diagnostics_collector
|
diagnostics_collector
|
||||||
.push(PublishDiagnostic::ImportMapUnfurl(diagnostic));
|
.push(PublishDiagnostic::ImportMapUnfurl(diagnostic));
|
||||||
};
|
};
|
||||||
let content = unfurler.unfurl(&url, &parsed_source, &mut reporter);
|
let content =
|
||||||
|
unfurler.unfurl(&specifier, &parsed_source, &mut reporter);
|
||||||
content.into_bytes()
|
content.into_bytes()
|
||||||
}
|
}
|
||||||
None => data,
|
None => data,
|
||||||
};
|
};
|
||||||
tar
|
tar
|
||||||
.add_file(relative_path_str.to_string(), &content)
|
.add_file(format!(".{}", path_str), &content)
|
||||||
.with_context(|| {
|
.with_context(|| {
|
||||||
format!("Unable to add file to tarball '{}'", entry.path().display())
|
format!("Unable to add file to tarball '{}'", entry.path().display())
|
||||||
})?;
|
})?;
|
||||||
} else if entry.file_type().is_dir() {
|
} else if !file_type.is_dir() {
|
||||||
if entry.file_name() == ".git" || entry.file_name() == "node_modules" {
|
diagnostics_collector.push(PublishDiagnostic::UnsupportedFileType {
|
||||||
iterator.skip_current_dir();
|
specifier,
|
||||||
}
|
kind: if file_type.is_symlink() {
|
||||||
} else {
|
"symlink".to_owned()
|
||||||
diagnostics.push(format!(
|
} else {
|
||||||
"Unsupported file type at path '{}'",
|
format!("{file_type:?}")
|
||||||
entry.path().display()
|
},
|
||||||
));
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,7 +167,6 @@ pub fn create_gzipped_tarball(
|
||||||
|
|
||||||
Ok(PublishableTarball {
|
Ok(PublishableTarball {
|
||||||
files,
|
files,
|
||||||
diagnostics,
|
|
||||||
hash,
|
hash,
|
||||||
bytes: Bytes::from(v),
|
bytes: Bytes::from(v),
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in a new issue