mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 02:11:53 +00:00
Fix/making js work (#1456)
* Feat: js action wip: Getting async js feat: Have execute get action config feat: Read + Write chore: Add typing for globals chore: Change the default path, include error on missing function, and add json File Read Write chore: Change the default path, include error on missing function, and add json File Read Write wip: Fix the unit test wip: Fix the unit test feat: module loading * fix: Change the source + add input * fix: Change the source + add input wip: Fix missing js files during running fix: Change the source + add input wip: Fix missing js files during running * fix: other paths * feat: Build the arm js snapshot * fix: test with more * chore: Make the is_subset a result
This commit is contained in:
6
Makefile
6
Makefile
@@ -1,6 +1,7 @@
|
||||
EMBASSY_BINS := backend/target/aarch64-unknown-linux-gnu/release/embassyd backend/target/aarch64-unknown-linux-gnu/release/embassy-init backend/target/aarch64-unknown-linux-gnu/release/embassy-cli backend/target/aarch64-unknown-linux-gnu/release/embassy-sdk
|
||||
EMBASSY_UIS := frontend/dist/ui frontend/dist/setup-wizard frontend/dist/diagnostic-ui
|
||||
EMBASSY_SRC := raspios.img product_key.txt $(EMBASSY_BINS) backend/embassyd.service backend/embassy-init.service $(EMBASSY_UIS) $(shell find build)
|
||||
EMBASSY_V8_SNAPSHOTS := backend/src/procedure/js_scripts/ARM_JS_SNAPSHOT.bin
|
||||
COMPAT_SRC := $(shell find system-images/compat/src)
|
||||
UTILS_SRC := $(shell find system-images/utils/Dockerfile)
|
||||
BACKEND_SRC := $(shell find backend/src) $(shell find patch-db/*/src) $(shell find rpc-toolkit/*/src) backend/Cargo.toml backend/Cargo.lock
|
||||
@@ -28,7 +29,7 @@ clean:
|
||||
rm -rf patch-db/client/node_modules
|
||||
rm -rf patch-db/client/dist
|
||||
|
||||
eos.img: $(EMBASSY_SRC) system-images/compat/compat.tar system-images/utils/utils.tar
|
||||
eos.img: $(EMBASSY_SRC) system-images/compat/compat.tar system-images/utils/utils.tar $(EMBASSY_V8_SNAPSHOTS)
|
||||
! test -f eos.img || rm eos.img
|
||||
if [ "$(NO_KEY)" = "1" ]; then NO_KEY=1 ./build/make-image.sh; else ./build/make-image.sh; fi
|
||||
|
||||
@@ -50,6 +51,9 @@ product_key.txt:
|
||||
if [ "$(KEY)" != "" ]; then $(shell which echo) -n "$(KEY)" > product_key.txt; fi
|
||||
echo >> product_key.txt
|
||||
|
||||
$(EMBASSY_V8_SNAPSHOTS): $(BACKEND_SRC) $(EMBASSY_BINS)
|
||||
cd backend && ./build-arm-v8-snapshot.sh
|
||||
|
||||
$(EMBASSY_BINS): $(BACKEND_SRC)
|
||||
cd backend && ./build-prod.sh
|
||||
|
||||
|
||||
13
backend/Cargo.lock
generated
13
backend/Cargo.lock
generated
@@ -823,9 +823,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deno_core"
|
||||
version = "0.134.0"
|
||||
version = "0.135.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "339890ba27bf35d5185400bfce383cbdcba49c2a38f0c819a144d6d710a1e34c"
|
||||
checksum = "32cd837520179a6f8063fe542b98dacec14b44ce647990be476b6eca8e6125e5"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"deno_ops",
|
||||
@@ -846,9 +846,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "deno_ops"
|
||||
version = "0.12.0"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1cb254e9a65955eddbaaa23298cb6ec23902984cadc550e75c16245a9ce16cc2"
|
||||
checksum = "f6ab6a5a7c3d5b9fbd43064996bbe61799db5e0bfb0f46672b2f85c0192d15a9"
|
||||
dependencies = [
|
||||
"proc-macro-crate",
|
||||
"proc-macro2 1.0.36",
|
||||
@@ -3247,10 +3247,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_v8"
|
||||
version = "0.45.0"
|
||||
version = "0.46.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "898109a3146fe6ef36047584e13fa32630d01ddd090977a39f2fe19de7b293fa"
|
||||
checksum = "f7797d56c9575ced9175e22366e5bd4cc8f3d571cd8c75be510f410ab97a54f6"
|
||||
dependencies = [
|
||||
"bytes 1.1.0",
|
||||
"derive_more",
|
||||
"serde",
|
||||
"v8",
|
||||
|
||||
@@ -24,6 +24,10 @@ path = "src/lib.rs"
|
||||
name = "embassyd"
|
||||
path = "src/bin/embassyd.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "create-js-snapshots"
|
||||
path = "src/bin/build-js-snapshots.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "embassy-init"
|
||||
path = "src/bin/embassy-init.rs"
|
||||
@@ -61,8 +65,8 @@ color-eyre = "0.5"
|
||||
cookie_store = "0.15.0"
|
||||
digest = "0.9.0"
|
||||
divrem = "1.0.0"
|
||||
deno_core = "0.134.0"
|
||||
deno_ast = {version="0.14.0", features = ["transpiling"]}
|
||||
deno_core = "0.135.0"
|
||||
deno_ast = {version="0.14.1", features = ["transpiling"]}
|
||||
ed25519-dalek = { version = "1.0.1", features = ["serde"] }
|
||||
emver = { version = "0.1.6", features = ["serde"] }
|
||||
fd-lock-rs = "0.1.3"
|
||||
|
||||
13
backend/build-arm-v8-snapshot.sh
Executable file
13
backend/build-arm-v8-snapshot.sh
Executable file
@@ -0,0 +1,13 @@
|
||||
#!/bin/bash
|
||||
# Reason for this being is that we need to create a snapshot for the deno runtime. It wants to pull 3 files from build, and during the creation it gets embedded, but for some
|
||||
# reason during the actual runtime it is looking for them. So this will create a docker in arm that creates the snaphot needed for the arm
|
||||
set -e
|
||||
|
||||
if [ "$0" != "./build-arm-v8-snapshot.sh" ]; then
|
||||
>&2 echo "Must be run from backend directory"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Creating Arm v8 Snapshot"
|
||||
docker run --platform linux/arm64/v8 --mount type=bind,src=$(pwd),dst=/mnt arm64v8/ubuntu:20.04 /bin/sh -c "cd /mnt && /mnt/target/aarch64-unknown-linux-gnu/release/create-js-snapshots"
|
||||
mv JS_SNAPSHOT.bin src/procedure/js_scripts/ARM_JS_SNAPSHOT.bin
|
||||
12
backend/src/bin/build-js-snapshots.rs
Normal file
12
backend/src/bin/build-js-snapshots.rs
Normal file
@@ -0,0 +1,12 @@
|
||||
fn main() {
|
||||
let mut runtime = deno_core::JsRuntime::new(deno_core::RuntimeOptions {
|
||||
will_snapshot: true,
|
||||
..Default::default()
|
||||
});
|
||||
let snapshot = runtime.snapshot();
|
||||
|
||||
let snapshot_slice: &[u8] = &*snapshot;
|
||||
println!("Snapshot size: {}", snapshot_slice.len());
|
||||
|
||||
std::fs::write("JS_SNAPSHOT.bin", snapshot_slice).unwrap();
|
||||
}
|
||||
@@ -187,7 +187,6 @@ impl ConfigGetReceipts {
|
||||
locks: &mut Vec<LockTargetId>,
|
||||
id: &PackageId,
|
||||
) -> impl FnOnce(&Verifier) -> Result<Self, Error> {
|
||||
|
||||
let manifest_version = crate::db::DatabaseModel::new()
|
||||
.package_data()
|
||||
.idx_model(id)
|
||||
@@ -680,6 +679,7 @@ pub fn configure_rec<'a, Db: DbHandle>(
|
||||
dependent,
|
||||
&manifest.version,
|
||||
&manifest.volumes,
|
||||
dependent,
|
||||
&config,
|
||||
)
|
||||
.await?
|
||||
|
||||
@@ -14,7 +14,6 @@ use rpc_toolkit::command;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tracing::instrument;
|
||||
|
||||
use crate::config::action::{ConfigActions, ConfigRes};
|
||||
use crate::config::spec::PackagePointerSpec;
|
||||
use crate::config::{not_found, Config, ConfigReceipts, ConfigSpec};
|
||||
use crate::context::RpcContext;
|
||||
@@ -27,6 +26,10 @@ use crate::util::serde::display_serializable;
|
||||
use crate::util::{display_none, Version};
|
||||
use crate::volume::Volumes;
|
||||
use crate::Error;
|
||||
use crate::{
|
||||
config::action::{ConfigActions, ConfigRes},
|
||||
procedure::ProcedureName,
|
||||
};
|
||||
|
||||
#[command(subcommands(configure))]
|
||||
pub fn dependency() -> Result<(), Error> {
|
||||
@@ -257,6 +260,7 @@ impl DependencyError {
|
||||
id,
|
||||
&dependent_manifest.version,
|
||||
&dependent_manifest.volumes,
|
||||
dependency,
|
||||
&dependency_config,
|
||||
)
|
||||
.await?
|
||||
@@ -496,6 +500,7 @@ impl DependencyConfig {
|
||||
dependent_id: &PackageId,
|
||||
dependent_version: &Version,
|
||||
dependent_volumes: &Volumes,
|
||||
dependency_id: &PackageId,
|
||||
dependency_config: &Config,
|
||||
) -> Result<Result<NoOutput, String>, Error> {
|
||||
Ok(self
|
||||
@@ -507,6 +512,7 @@ impl DependencyConfig {
|
||||
dependent_volumes,
|
||||
Some(dependency_config),
|
||||
None,
|
||||
ProcedureName::Check(dependency_id.clone()),
|
||||
)
|
||||
.await?
|
||||
.map_err(|(_, e)| e))
|
||||
@@ -527,6 +533,7 @@ impl DependencyConfig {
|
||||
dependent_volumes,
|
||||
Some(old),
|
||||
None,
|
||||
ProcedureName::AutoConfig(dependent_id.clone()),
|
||||
)
|
||||
.await?
|
||||
.map_err(|e| Error::new(eyre!("{}", e.1), crate::ErrorKind::AutoConfigure))
|
||||
@@ -730,6 +737,7 @@ pub async fn configure_logic(
|
||||
&pkg_volumes,
|
||||
Some(&old_config),
|
||||
None,
|
||||
ProcedureName::AutoConfig(dependency_id.clone()),
|
||||
)
|
||||
.await?
|
||||
.map_err(|e| Error::new(eyre!("{}", e.1), crate::ErrorKind::AutoConfigure))?;
|
||||
|
||||
0
backend/src/procedure/build.rs
Normal file
0
backend/src/procedure/build.rs
Normal file
@@ -30,7 +30,7 @@ pub enum JsError {
|
||||
pub struct JsProcedure {}
|
||||
|
||||
impl JsProcedure {
|
||||
pub fn validate(&self, volumes: &Volumes) -> Result<(), color_eyre::eyre::Report> {
|
||||
pub fn validate(&self, _volumes: &Volumes) -> Result<(), color_eyre::eyre::Report> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -53,7 +53,6 @@ impl JsProcedure {
|
||||
volumes.clone(),
|
||||
)
|
||||
.await?
|
||||
.with_effects()
|
||||
.run_action(name, input);
|
||||
let output: O = match timeout {
|
||||
Some(timeout_duration) => tokio::time::timeout(timeout_duration, running_action)
|
||||
@@ -76,6 +75,7 @@ impl JsProcedure {
|
||||
volumes: &Volumes,
|
||||
input: Option<I>,
|
||||
timeout: Option<Duration>,
|
||||
name: ProcedureName,
|
||||
) -> Result<Result<O, (i32, String)>, Error> {
|
||||
Ok(async move {
|
||||
let running_action = JsExecutionEnvironment::load_from_package(
|
||||
@@ -86,7 +86,7 @@ impl JsProcedure {
|
||||
)
|
||||
.await?
|
||||
.read_only_effects()
|
||||
.run_action(ProcedureName::GetConfig, input);
|
||||
.run_action(name, input);
|
||||
let output: O = match timeout {
|
||||
Some(timeout_duration) => tokio::time::timeout(timeout_duration, running_action)
|
||||
.await
|
||||
@@ -111,6 +111,7 @@ mod js_runtime {
|
||||
use deno_core::ModuleSpecifier;
|
||||
use deno_core::ModuleType;
|
||||
use deno_core::RuntimeOptions;
|
||||
use deno_core::Snapshot;
|
||||
use deno_core::{Extension, OpDecl};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
@@ -120,11 +121,16 @@ mod js_runtime {
|
||||
|
||||
use crate::s9pk::manifest::PackageId;
|
||||
use crate::util::Version;
|
||||
use crate::volume::{script_dir, Volumes};
|
||||
use crate::volume::script_dir;
|
||||
use crate::volume::Volumes;
|
||||
|
||||
use super::super::ProcedureName;
|
||||
use super::{JsCode, JsError};
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
const SNAPSHOT_BYTES: &[u8] = include_bytes!("./js_scripts/JS_SNAPSHOT.bin");
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
const SNAPSHOT_BYTES: &[u8] = include_bytes!("./js_scripts/ARM_JS_SNAPSHOT.bin");
|
||||
#[derive(Clone, Deserialize, Serialize)]
|
||||
struct JsContext {
|
||||
sandboxed: bool,
|
||||
@@ -212,7 +218,6 @@ mod js_runtime {
|
||||
module_loader: ModsLoader,
|
||||
package_id: PackageId,
|
||||
version: Version,
|
||||
operations: Vec<OpDecl>,
|
||||
volumes: Arc<Volumes>,
|
||||
}
|
||||
|
||||
@@ -230,6 +235,7 @@ mod js_runtime {
|
||||
let mut file = match tokio::fs::File::open(file_path.clone()).await {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
tracing::debug!("path: {:?}", file_path);
|
||||
tracing::debug!("{:?}", e);
|
||||
return Err((
|
||||
JsError::FileSystem,
|
||||
@@ -250,7 +256,6 @@ mod js_runtime {
|
||||
Ok(Self {
|
||||
base_directory: base_directory.to_owned(),
|
||||
module_loader: ModsLoader { code: js_code },
|
||||
operations: Default::default(),
|
||||
package_id: package_id.clone(),
|
||||
version: version.clone(),
|
||||
volumes: Arc::new(volumes),
|
||||
@@ -259,29 +264,9 @@ mod js_runtime {
|
||||
}
|
||||
pub fn read_only_effects(mut self) -> Self {
|
||||
self.sandboxed = true;
|
||||
self.with_effects()
|
||||
}
|
||||
|
||||
pub fn with_effects(mut self) -> Self {
|
||||
self.operations = vec![
|
||||
fns::read_file::decl(),
|
||||
fns::write_file::decl(),
|
||||
fns::create_dir::decl(),
|
||||
fns::remove_dir::decl(),
|
||||
fns::get_context::decl(),
|
||||
fns::log_trace::decl(),
|
||||
fns::log_warn::decl(),
|
||||
fns::log_error::decl(),
|
||||
fns::log_debug::decl(),
|
||||
fns::log_info::decl(),
|
||||
fns::current_function::decl(),
|
||||
fns::set_value::decl(),
|
||||
fns::remove_file::decl(),
|
||||
fns::is_sandboxed::decl(),
|
||||
fns::get_input::decl(),
|
||||
];
|
||||
self
|
||||
}
|
||||
|
||||
pub async fn run_action<I: Serialize, O: for<'de> Deserialize<'de>>(
|
||||
self,
|
||||
procedure_name: ProcedureName,
|
||||
@@ -318,6 +303,25 @@ mod js_runtime {
|
||||
}
|
||||
}
|
||||
}
|
||||
fn declarations() -> Vec<OpDecl> {
|
||||
vec![
|
||||
fns::read_file::decl(),
|
||||
fns::write_file::decl(),
|
||||
fns::remove_file::decl(),
|
||||
fns::create_dir::decl(),
|
||||
fns::remove_dir::decl(),
|
||||
fns::current_function::decl(),
|
||||
fns::log_trace::decl(),
|
||||
fns::log_warn::decl(),
|
||||
fns::log_error::decl(),
|
||||
fns::log_debug::decl(),
|
||||
fns::log_info::decl(),
|
||||
fns::get_context::decl(),
|
||||
fns::get_input::decl(),
|
||||
fns::set_value::decl(),
|
||||
fns::is_sandboxed::decl(),
|
||||
]
|
||||
}
|
||||
|
||||
fn execute(
|
||||
&self,
|
||||
@@ -337,7 +341,7 @@ mod js_runtime {
|
||||
input,
|
||||
};
|
||||
let ext = Extension::builder()
|
||||
.ops(self.operations.clone())
|
||||
.ops(Self::declarations())
|
||||
.state(move |state| {
|
||||
state.put(ext_answer_state.clone());
|
||||
state.put(js_ctx.clone());
|
||||
@@ -346,11 +350,13 @@ mod js_runtime {
|
||||
.build();
|
||||
|
||||
let loader = std::rc::Rc::new(self.module_loader.clone());
|
||||
let mut runtime = JsRuntime::new(RuntimeOptions {
|
||||
let runtime_options = RuntimeOptions {
|
||||
module_loader: Some(loader),
|
||||
extensions: vec![ext],
|
||||
startup_snapshot: Some(Snapshot::Static(SNAPSHOT_BYTES)),
|
||||
..Default::default()
|
||||
});
|
||||
};
|
||||
let mut runtime = JsRuntime::new(runtime_options);
|
||||
|
||||
let future = async move {
|
||||
let mod_id = runtime
|
||||
@@ -358,8 +364,8 @@ mod js_runtime {
|
||||
.await?;
|
||||
let evaluated = runtime.mod_evaluate(mod_id);
|
||||
let res = runtime.run_event_loop(false).await;
|
||||
evaluated.await??;
|
||||
res?;
|
||||
evaluated.await??;
|
||||
Ok::<_, AnyError>(())
|
||||
};
|
||||
|
||||
@@ -367,7 +373,7 @@ mod js_runtime {
|
||||
.block_on(future)
|
||||
.map_err(|e| {
|
||||
tracing::debug!("{:?}", e);
|
||||
(JsError::Javascript, format!("Execution error: {}", e))
|
||||
(JsError::Javascript, format!("{}", e))
|
||||
})?;
|
||||
|
||||
let answer = answer_state.0.lock().clone();
|
||||
@@ -377,10 +383,17 @@ mod js_runtime {
|
||||
|
||||
/// Note: Make sure that we have the assumption that all these methods are callable at any time, and all call restrictions should be in rust
|
||||
mod fns {
|
||||
use deno_core::{anyhow::bail, error::AnyError, *};
|
||||
use deno_core::{
|
||||
anyhow::{anyhow, bail},
|
||||
error::AnyError,
|
||||
*,
|
||||
};
|
||||
use serde_json::Value;
|
||||
|
||||
use std::{convert::TryFrom, path::PathBuf};
|
||||
use std::{
|
||||
convert::TryFrom,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use crate::volume::VolumeId;
|
||||
|
||||
@@ -402,8 +415,12 @@ mod js_runtime {
|
||||
volume.path_for(&ctx.datadir, &ctx.package_id, &ctx.version, &volume_id);
|
||||
//get_path_for in volume.rs
|
||||
let new_file = volume_path.join(path_in);
|
||||
if !new_file.starts_with(volume_path) {
|
||||
bail!("Path has broken away from parent");
|
||||
if !is_subset(&volume_path, &new_file).await? {
|
||||
bail!(
|
||||
"Path '{}' has broken away from parent '{}'",
|
||||
new_file.to_string_lossy(),
|
||||
volume_path.to_string_lossy(),
|
||||
);
|
||||
}
|
||||
let answer = tokio::fs::read_to_string(new_file).await?;
|
||||
Ok(answer)
|
||||
@@ -429,10 +446,18 @@ mod js_runtime {
|
||||
}
|
||||
let volume_path =
|
||||
volume.path_for(&ctx.datadir, &ctx.package_id, &ctx.version, &volume_id);
|
||||
|
||||
let new_file = volume_path.join(path_in);
|
||||
let parent_new_file = new_file
|
||||
.parent()
|
||||
.ok_or_else(|| anyhow!("Expecting that file is not root"))?;
|
||||
// With the volume check
|
||||
if !new_file.starts_with(volume_path) {
|
||||
bail!("Path has broken away from parent");
|
||||
if !is_subset(&volume_path, &parent_new_file).await? {
|
||||
bail!(
|
||||
"Path '{}' has broken away from parent '{}'",
|
||||
new_file.to_string_lossy(),
|
||||
volume_path.to_string_lossy(),
|
||||
);
|
||||
}
|
||||
tokio::fs::write(new_file, write).await?;
|
||||
Ok(())
|
||||
@@ -457,10 +482,14 @@ mod js_runtime {
|
||||
}
|
||||
let volume_path =
|
||||
volume.path_for(&ctx.datadir, &ctx.package_id, &ctx.version, &volume_id);
|
||||
let new_file = volume_path.clone().join(path_in);
|
||||
let new_file = volume_path.join(path_in);
|
||||
// With the volume check
|
||||
if !new_file.starts_with(volume_path) {
|
||||
bail!("Path has broken away from parent");
|
||||
if !is_subset(&volume_path, &new_file).await? {
|
||||
bail!(
|
||||
"Path '{}' has broken away from parent '{}'",
|
||||
new_file.to_string_lossy(),
|
||||
volume_path.to_string_lossy(),
|
||||
);
|
||||
}
|
||||
tokio::fs::remove_file(new_file).await?;
|
||||
Ok(())
|
||||
@@ -485,10 +514,14 @@ mod js_runtime {
|
||||
}
|
||||
let volume_path =
|
||||
volume.path_for(&ctx.datadir, &ctx.package_id, &ctx.version, &volume_id);
|
||||
let new_file = volume_path.clone().join(path_in);
|
||||
let new_file = volume_path.join(path_in);
|
||||
// With the volume check
|
||||
if !new_file.starts_with(volume_path) {
|
||||
bail!("Path has broken away from parent");
|
||||
if !is_subset(&volume_path, &new_file).await? {
|
||||
bail!(
|
||||
"Path '{}' has broken away from parent '{}'",
|
||||
new_file.to_string_lossy(),
|
||||
volume_path.to_string_lossy(),
|
||||
);
|
||||
}
|
||||
tokio::fs::remove_dir_all(new_file).await?;
|
||||
Ok(())
|
||||
@@ -513,10 +546,17 @@ mod js_runtime {
|
||||
}
|
||||
let volume_path =
|
||||
volume.path_for(&ctx.datadir, &ctx.package_id, &ctx.version, &volume_id);
|
||||
let new_file = volume_path.clone().join(path_in);
|
||||
let new_file = volume_path.join(path_in);
|
||||
let parent_new_file = new_file
|
||||
.parent()
|
||||
.ok_or_else(|| anyhow!("Expecting that file is not root"))?;
|
||||
// With the volume check
|
||||
if !new_file.starts_with(volume_path) {
|
||||
bail!("Path has broken away from parent");
|
||||
if !is_subset(&volume_path, &parent_new_file).await? {
|
||||
bail!(
|
||||
"Path '{}' has broken away from parent '{}'",
|
||||
new_file.to_string_lossy(),
|
||||
volume_path.to_string_lossy(),
|
||||
);
|
||||
}
|
||||
tokio::fs::create_dir_all(new_file).await?;
|
||||
Ok(())
|
||||
@@ -605,12 +645,24 @@ mod js_runtime {
|
||||
let ctx = state.borrow::<JsContext>();
|
||||
Ok(ctx.sandboxed)
|
||||
}
|
||||
|
||||
/// We need to make sure that during the file accessing, we don't reach beyond our scope of control
|
||||
async fn is_subset(parent: impl AsRef<Path>, child: impl AsRef<Path>) -> Result<bool, AnyError> {
|
||||
let child = tokio::fs::canonicalize(child).await?;
|
||||
let parent = tokio::fs::canonicalize(parent).await?;
|
||||
Ok(child.starts_with(parent))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn js_action_execute() {
|
||||
let js_action = JsProcedure {};
|
||||
let path: PathBuf = "test/js_action_execute/".parse().unwrap();
|
||||
let path: PathBuf = "test/js_action_execute/"
|
||||
.parse::<PathBuf>()
|
||||
.unwrap()
|
||||
.canonicalize()
|
||||
.unwrap();
|
||||
let package_id = "test-package".parse().unwrap();
|
||||
let package_version: Version = "0.3.0.3".parse().unwrap();
|
||||
let name = ProcedureName::GetConfig;
|
||||
@@ -631,7 +683,7 @@ async fn js_action_execute() {
|
||||
}))
|
||||
.unwrap();
|
||||
let input: Option<serde_json::Value> = Some(serde_json::json!({"test":123}));
|
||||
let timeout = None;
|
||||
let timeout = Some(Duration::from_secs(10));
|
||||
let _output: crate::config::action::ConfigRes = js_action
|
||||
.execute(
|
||||
&path,
|
||||
|
||||
BIN
backend/src/procedure/js_scripts/ARM_JS_SNAPSHOT.bin
Normal file
BIN
backend/src/procedure/js_scripts/ARM_JS_SNAPSHOT.bin
Normal file
Binary file not shown.
BIN
backend/src/procedure/js_scripts/JS_SNAPSHOT.bin
Normal file
BIN
backend/src/procedure/js_scripts/JS_SNAPSHOT.bin
Normal file
Binary file not shown.
@@ -1,6 +1,6 @@
|
||||
//@ts-check
|
||||
// @ts-ignore
|
||||
import Deno from "/deno_global.js";
|
||||
import Deno from "/deno_global.js";
|
||||
// @ts-ignore
|
||||
import * as mainModule from "/embassy.js";
|
||||
/**
|
||||
@@ -23,36 +23,28 @@ function jsonPointerValue(obj, pointer) {
|
||||
// @ts-ignore
|
||||
const context = Deno.core.opSync("get_context");
|
||||
// @ts-ignore
|
||||
const writeFile = ({ path, volumeId, toWrite }) =>
|
||||
Deno.core.opAsync("write_file", context, volumeId, path, toWrite);
|
||||
const writeFile = ({ path, volumeId, toWrite }) => Deno.core.opAsync("write_file", context, volumeId, path, toWrite);
|
||||
|
||||
// @ts-ignore
|
||||
const readFile = ({ volumeId, path }) =>
|
||||
Deno.core.opAsync("read_file", context, volumeId, path);
|
||||
const readFile = ({ volumeId, path }) => Deno.core.opAsync("read_file", context, volumeId, path);
|
||||
// @ts-ignore
|
||||
const removeFile = ({ volumeId, path }) =>
|
||||
Deno.core.opAsync("remove_file", context, volumeId, path);
|
||||
const removeFile = ({ volumeId, path }) => Deno.core.opAsync("remove_file", context, volumeId, path);
|
||||
// @ts-ignore
|
||||
const isSandboxed = () => Deno.core.opSync("is_sandboxed");
|
||||
|
||||
// @ts-ignore
|
||||
const writeJsonFile = ({ volumeId, path, toWrite }) =>
|
||||
Deno.core.opAsync(
|
||||
"write_file",
|
||||
context,
|
||||
writeFile({
|
||||
volumeId,
|
||||
path,
|
||||
JSON.stringify(toWrite)
|
||||
);
|
||||
toWrite: JSON.stringify(toWrite),
|
||||
});
|
||||
// @ts-ignore
|
||||
const readJsonFile = ({ volumeId, path }) =>
|
||||
JSON.parse(Deno.core.opAsync("read_file", context, volumeId, path));
|
||||
const readJsonFile = async ({ volumeId, path }) => JSON.parse(await readFile({ volumeId, path }));
|
||||
// @ts-ignore
|
||||
const createDir = ({ volumeId, path }) =>
|
||||
Deno.core.opAsync("create_dir", context, volumeId, path);
|
||||
const createDir = ({ volumeId, path }) => Deno.core.opAsync("create_dir", context, volumeId, path);
|
||||
// @ts-ignore
|
||||
const removeDir = ({ volumeId, path }) =>
|
||||
Deno.core.opAsync("remove_dir", context, volumeId, path);
|
||||
const removeDir = ({ volumeId, path }) => Deno.core.opAsync("remove_dir", context, volumeId, path);
|
||||
// @ts-ignore
|
||||
const trace = (x) => Deno.core.opSync("log_trace", x);
|
||||
// @ts-ignore
|
||||
@@ -86,14 +78,11 @@ const effects = {
|
||||
removeDir,
|
||||
};
|
||||
|
||||
const runFunction = jsonPointerValue(
|
||||
mainModule,
|
||||
currentFunction
|
||||
);
|
||||
const runFunction = jsonPointerValue(mainModule, currentFunction);
|
||||
(async () => {
|
||||
if (typeof runFunction !== "function") {
|
||||
error(`Expecting ${{ currentFunction }} to be a function`);
|
||||
throw new Error(`Expecting ${{ currentFunction }} to be a function`);
|
||||
error(`Expecting ${ currentFunction } to be a function`);
|
||||
throw new Error(`Expecting ${ currentFunction } to be a function`);
|
||||
}
|
||||
const answer = await runFunction(effects, input);
|
||||
setState(answer);
|
||||
|
||||
@@ -28,6 +28,8 @@ pub enum ProcedureName {
|
||||
SetConfig,
|
||||
Migration,
|
||||
Properties,
|
||||
Check(PackageId),
|
||||
AutoConfig(PackageId),
|
||||
Health(HealthCheckId),
|
||||
Action(ActionId),
|
||||
}
|
||||
@@ -44,11 +46,13 @@ impl ProcedureName {
|
||||
ProcedureName::Properties => Some(format!("Properties-{}", rand::random::<u64>())),
|
||||
ProcedureName::Health(id) => Some(format!("{}Health", id)),
|
||||
ProcedureName::Action(id) => Some(format!("{}Action", id)),
|
||||
ProcedureName::Check(_) => None,
|
||||
ProcedureName::AutoConfig(_) => None,
|
||||
}
|
||||
}
|
||||
fn js_function_name(&self) -> String {
|
||||
match self {
|
||||
ProcedureName::Main => todo!(),
|
||||
ProcedureName::Main => "/main".to_string(),
|
||||
ProcedureName::CreateBackup => "/createBackup".to_string(),
|
||||
ProcedureName::RestoreBackup => "/restoreBackup".to_string(),
|
||||
ProcedureName::GetConfig => "/getConfig".to_string(),
|
||||
@@ -57,6 +61,8 @@ impl ProcedureName {
|
||||
ProcedureName::Properties => "/properties".to_string(),
|
||||
ProcedureName::Health(id) => format!("/health/{}", id),
|
||||
ProcedureName::Action(id) => format!("/action/{}", id),
|
||||
ProcedureName::Check(id) => format!("/dependencies/{}/check", id),
|
||||
ProcedureName::AutoConfig(id) => format!("/dependencies/{}/autoConfigure", id),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -136,6 +142,7 @@ impl PackageProcedure {
|
||||
volumes: &Volumes,
|
||||
input: Option<I>,
|
||||
timeout: Option<Duration>,
|
||||
name: ProcedureName,
|
||||
) -> Result<Result<O, (i32, String)>, Error> {
|
||||
match self {
|
||||
PackageProcedure::Docker(procedure) => {
|
||||
@@ -145,7 +152,7 @@ impl PackageProcedure {
|
||||
}
|
||||
PackageProcedure::Script(procedure) => {
|
||||
procedure
|
||||
.sandboxed(ctx, pkg_id, pkg_version, volumes, input, timeout)
|
||||
.sandboxed(ctx, pkg_id, pkg_version, volumes, input, timeout, name)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,19 +10,53 @@ export function properties() {
|
||||
* @returns {Promise<import('./types').ConfigRes>}
|
||||
*/
|
||||
export async function getConfig(effects) {
|
||||
try{
|
||||
await effects.writeFile({
|
||||
path: "./test.log",
|
||||
path: "../test.log",
|
||||
toWrite: "This is a test",
|
||||
volumeId: 'main',
|
||||
});
|
||||
throw new Error("Expecting that the ../test.log should not be a valid path since we are breaking out of the parent")
|
||||
} catch(e) {}
|
||||
try{
|
||||
await effects.writeFile({
|
||||
path: "./hack_back/broken.log",
|
||||
toWrite: "This is a test",
|
||||
volumeId: 'main',
|
||||
});
|
||||
throw new Error("Expecting that using a symlink to break out of parent still fails for writing")
|
||||
} catch(e) {}
|
||||
try{
|
||||
await effects.createDir({
|
||||
path: "./hack_back/broken_dir",
|
||||
volumeId: 'main',
|
||||
});
|
||||
throw new Error("Expecting that using a symlink to break out of parent still fails for writing dir")
|
||||
} catch(e) {}
|
||||
try{
|
||||
await effects.readFile({
|
||||
path: "./hack_back/data/bad_file.txt",
|
||||
volumeId: 'main',
|
||||
});
|
||||
throw new Error("Expecting that using a symlink to break out of parent still fails for reading")
|
||||
} catch(e) {}
|
||||
|
||||
// Testing dir, create + delete
|
||||
await effects.createDir({
|
||||
path: "./testing",
|
||||
volumeId: 'main',});
|
||||
await effects.writeFile({
|
||||
await effects.writeJsonFile({
|
||||
path: "./testing/test2.log",
|
||||
toWrite: "This is a test",
|
||||
toWrite: {value: "This is a test"},
|
||||
volumeId: 'main',
|
||||
});
|
||||
|
||||
(await effects.readJsonFile({
|
||||
path: "./testing/test2.log",
|
||||
volumeId: 'main',
|
||||
// @ts-ignore
|
||||
})).value;
|
||||
|
||||
await effects.removeFile({
|
||||
path: "./testing/test2.log",
|
||||
volumeId: 'main',
|
||||
@@ -30,10 +64,20 @@ export async function getConfig(effects) {
|
||||
await effects.removeDir({
|
||||
path: "./testing",
|
||||
volumeId: 'main',});
|
||||
|
||||
|
||||
// Testing reading + writing
|
||||
await effects.writeFile({
|
||||
path: "./test.log",
|
||||
toWrite: "This is a test",
|
||||
volumeId: 'main',
|
||||
});
|
||||
|
||||
effects.debug(`Read results are ${await effects.readFile({
|
||||
path: "./test.log",
|
||||
volumeId: 'main',
|
||||
})}`)
|
||||
// Testing loging
|
||||
effects.trace('trace')
|
||||
effects.debug('debug')
|
||||
effects.warn('warn')
|
||||
@@ -587,6 +631,7 @@ export async function getConfig(effects) {
|
||||
export async function setConfig(effects, input) {
|
||||
|
||||
return {
|
||||
signal: "SIGTERM",
|
||||
"depends-on": {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,191 +1,266 @@
|
||||
export type Effects = {
|
||||
writeFile(input: {path: string, volumeId: string, toWrite: string}): Promise<void>,
|
||||
readFile(input: {volumeId: string,path: string}): Promise<string>,
|
||||
createDir(input: {volumeId: string,path: string}): Promise<string>,
|
||||
removeDir(input: {volumeId: string,path: string}): Promise<string>,
|
||||
removeFile(input: {volumeId: string,path: string}): Promise<void>,
|
||||
writeJsonFile(input: {volumeId: string,path: string, toWrite: object}): void,
|
||||
readJsonFile(input:{volumeId: string,path: string}): object,
|
||||
trace(whatToPrin: string),
|
||||
warn(whatToPrin: string),
|
||||
error(whatToPrin: string),
|
||||
debug(whatToPrin: string),
|
||||
info(whatToPrin: string),
|
||||
is_sandboxed(): boolean,
|
||||
}
|
||||
writeFile(input: { path: string; volumeId: string; toWrite: string }): Promise<void>;
|
||||
readFile(input: { volumeId: string; path: string }): Promise<string>;
|
||||
createDir(input: { volumeId: string; path: string }): Promise<string>;
|
||||
removeDir(input: { volumeId: string; path: string }): Promise<string>;
|
||||
removeFile(input: { volumeId: string; path: string }): Promise<void>;
|
||||
writeJsonFile(input: { volumeId: string; path: string; toWrite: object }): Promise<void>;
|
||||
readJsonFile(input: { volumeId: string; path: string }): Promise<object>;
|
||||
trace(whatToPrin: string): void;
|
||||
warn(whatToPrin: string): void;
|
||||
error(whatToPrin: string): void;
|
||||
debug(whatToPrin: string): void;
|
||||
info(whatToPrin: string): void;
|
||||
is_sandboxed(): boolean;
|
||||
};
|
||||
|
||||
export type ActionResult = {
|
||||
version: "0",
|
||||
message: string,
|
||||
value?: string,
|
||||
copyable: boolean,
|
||||
qr: boolean,
|
||||
}
|
||||
version: "0";
|
||||
message: string;
|
||||
value?: string;
|
||||
copyable: boolean;
|
||||
qr: boolean;
|
||||
};
|
||||
|
||||
export type ConfigRes = {
|
||||
config?: Config,
|
||||
spec: ConfigSpec,
|
||||
}
|
||||
config?: Config;
|
||||
spec: ConfigSpec;
|
||||
};
|
||||
export type Config = {
|
||||
[value: string]: any
|
||||
}
|
||||
[value: string]: any;
|
||||
};
|
||||
|
||||
export type ConfigSpec = {
|
||||
[value: string]: ValueSpecAny
|
||||
}
|
||||
export type WithDefault<T, Default> = T & {
|
||||
default?: Default
|
||||
}
|
||||
export type ConfigSpec = {
|
||||
[value: string]: ValueSpecAny;
|
||||
};
|
||||
export type WithDefault<T, Default> = T & {
|
||||
default?: Default;
|
||||
};
|
||||
|
||||
export type WithDescription<T> = T & {
|
||||
description?: String,
|
||||
name: string,
|
||||
warning?: string,
|
||||
}
|
||||
description?: String;
|
||||
name: string;
|
||||
warning?: string;
|
||||
};
|
||||
|
||||
export type ListSpec<T> = {
|
||||
spec: T,
|
||||
range: string
|
||||
}
|
||||
spec: T;
|
||||
range: string;
|
||||
};
|
||||
|
||||
export type Tag<T extends string, V> = V & {
|
||||
type: T
|
||||
}
|
||||
type: T;
|
||||
};
|
||||
|
||||
export type Subtype<T extends string, V> = V & {
|
||||
subtype: T
|
||||
}
|
||||
subtype: T;
|
||||
};
|
||||
|
||||
export type Target<T extends string, V> = V & {
|
||||
"target": T
|
||||
}
|
||||
target: T;
|
||||
};
|
||||
|
||||
export type UniqueBy =
|
||||
|{
|
||||
any: UniqueBy[],
|
||||
}
|
||||
| {
|
||||
all: UniqueBy[]
|
||||
}
|
||||
| string
|
||||
| null
|
||||
| {
|
||||
any: UniqueBy[];
|
||||
}
|
||||
| {
|
||||
all: UniqueBy[];
|
||||
}
|
||||
| string
|
||||
| null;
|
||||
|
||||
export type WithNullable<T> = T & {
|
||||
nullable: boolean
|
||||
}
|
||||
export type DefaultString = String | {
|
||||
charset?: string,
|
||||
len: number
|
||||
}
|
||||
nullable: boolean;
|
||||
};
|
||||
export type DefaultString =
|
||||
| String
|
||||
| {
|
||||
charset?: string;
|
||||
len: number;
|
||||
};
|
||||
|
||||
export type ValueSpecString= ({} | {
|
||||
pattern: string,
|
||||
'pattern-description': string
|
||||
}) & {
|
||||
copyable?: boolean,
|
||||
masked?: boolean,
|
||||
placeholder?: string
|
||||
|
||||
}
|
||||
export type ValueSpecString = (
|
||||
| {}
|
||||
| {
|
||||
pattern: string;
|
||||
"pattern-description": string;
|
||||
}
|
||||
) & {
|
||||
copyable?: boolean;
|
||||
masked?: boolean;
|
||||
placeholder?: string;
|
||||
};
|
||||
export type ValueSpecNumber = {
|
||||
range?: string,
|
||||
integral?: boolean,
|
||||
units?: string,
|
||||
placeholder?: number,
|
||||
}
|
||||
export type ValueSpecBoolean = {}
|
||||
export type ValueSpecAny =
|
||||
| Tag<'boolean', WithDescription<WithDefault<ValueSpecBoolean, boolean>>>
|
||||
| Tag<'string', WithDescription<WithDefault<WithNullable<ValueSpecString>, DefaultString>>>
|
||||
| Tag<'number', WithDescription<WithDefault<WithNullable<ValueSpecNumber>, number>>>
|
||||
| Tag<'enum', WithDescription<WithDefault<{
|
||||
values: string[],
|
||||
"value-names": {
|
||||
[key: string]: string
|
||||
}
|
||||
}, string>>>
|
||||
| Tag<'list', ValueSpecList>
|
||||
| Tag<'object', WithDescription<WithDefault<ValueSpecObject, Config>>>
|
||||
| Tag<'union', WithDescription<WithDefault<ValueSpecUnion, string>>>
|
||||
| Tag<'pointer', WithDescription<
|
||||
| Subtype<'package',
|
||||
| Target<'tor-key', {
|
||||
'package-id': string
|
||||
interface: string
|
||||
}>
|
||||
| Target<'tor-address', {
|
||||
'package-id': string,
|
||||
interface: string
|
||||
} >
|
||||
| Target<'lan-address',{
|
||||
'package-id': string,
|
||||
interface: string
|
||||
} >
|
||||
| Target<'config', {
|
||||
'package-id': string,
|
||||
selector: string,
|
||||
multi: boolean
|
||||
}>
|
||||
range?: string;
|
||||
integral?: boolean;
|
||||
units?: string;
|
||||
placeholder?: number;
|
||||
};
|
||||
export type ValueSpecBoolean = {};
|
||||
export type ValueSpecAny =
|
||||
| Tag<"boolean", WithDescription<WithDefault<ValueSpecBoolean, boolean>>>
|
||||
| Tag<"string", WithDescription<WithDefault<WithNullable<ValueSpecString>, DefaultString>>>
|
||||
| Tag<"number", WithDescription<WithDefault<WithNullable<ValueSpecNumber>, number>>>
|
||||
| Tag<
|
||||
"enum",
|
||||
WithDescription<
|
||||
WithDefault<
|
||||
{
|
||||
values: string[];
|
||||
"value-names": {
|
||||
[key: string]: string;
|
||||
};
|
||||
},
|
||||
string
|
||||
>
|
||||
>
|
||||
>
|
||||
| Subtype<'system', {}>
|
||||
>>
|
||||
| Tag<"list", ValueSpecList>
|
||||
| Tag<"object", WithDescription<WithDefault<ValueSpecObject, Config>>>
|
||||
| Tag<"union", WithDescription<WithDefault<ValueSpecUnion, string>>>
|
||||
| Tag<
|
||||
"pointer",
|
||||
WithDescription<
|
||||
| Subtype<
|
||||
"package",
|
||||
| Target<
|
||||
"tor-key",
|
||||
{
|
||||
"package-id": string;
|
||||
interface: string;
|
||||
}
|
||||
>
|
||||
| Target<
|
||||
"tor-address",
|
||||
{
|
||||
"package-id": string;
|
||||
interface: string;
|
||||
}
|
||||
>
|
||||
| Target<
|
||||
"lan-address",
|
||||
{
|
||||
"package-id": string;
|
||||
interface: string;
|
||||
}
|
||||
>
|
||||
| Target<
|
||||
"config",
|
||||
{
|
||||
"package-id": string;
|
||||
selector: string;
|
||||
multi: boolean;
|
||||
}
|
||||
>
|
||||
>
|
||||
| Subtype<"system", {}>
|
||||
>
|
||||
>;
|
||||
export type ValueSpecUnion = {
|
||||
tag: {
|
||||
id: string,
|
||||
name: string,
|
||||
description?: string,
|
||||
"variant-names": {
|
||||
[key: string]: string,
|
||||
}
|
||||
},
|
||||
variants: {
|
||||
[key: string]: ConfigSpec
|
||||
},
|
||||
"display-as"?: string,
|
||||
"unique-by"?: UniqueBy
|
||||
|
||||
}
|
||||
tag: {
|
||||
id: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
"variant-names": {
|
||||
[key: string]: string;
|
||||
};
|
||||
};
|
||||
variants: {
|
||||
[key: string]: ConfigSpec;
|
||||
};
|
||||
"display-as"?: string;
|
||||
"unique-by"?: UniqueBy;
|
||||
};
|
||||
export type ValueSpecObject = {
|
||||
spec : ConfigSpec,
|
||||
'display-as'?: string,
|
||||
"unique-by"?: UniqueBy
|
||||
|
||||
}
|
||||
export type ValueSpecList =
|
||||
| Subtype<'boolean', WithDescription<WithDefault<ListSpec<ValueSpecBoolean>, boolean>>>
|
||||
| Subtype<'string', WithDescription<WithDefault<ListSpec<ValueSpecString>, string>>>
|
||||
| Subtype<'number', WithDescription<WithDefault<ListSpec<ValueSpecNumber>, number>>>
|
||||
| Subtype<'enum', WithDescription<WithDefault<{
|
||||
values: string[],
|
||||
"value-names": {
|
||||
[key: string]: string
|
||||
}
|
||||
}, string>>>
|
||||
spec: ConfigSpec;
|
||||
"display-as"?: string;
|
||||
"unique-by"?: UniqueBy;
|
||||
};
|
||||
export type ValueSpecList =
|
||||
| Subtype<"boolean", WithDescription<WithDefault<ListSpec<ValueSpecBoolean>, boolean>>>
|
||||
| Subtype<"string", WithDescription<WithDefault<ListSpec<ValueSpecString>, string>>>
|
||||
| Subtype<"number", WithDescription<WithDefault<ListSpec<ValueSpecNumber>, number>>>
|
||||
| Subtype<
|
||||
"enum",
|
||||
WithDescription<
|
||||
WithDefault<
|
||||
{
|
||||
values: string[];
|
||||
"value-names": {
|
||||
[key: string]: string;
|
||||
};
|
||||
},
|
||||
string
|
||||
>
|
||||
>
|
||||
>;
|
||||
|
||||
export type SetResult = {
|
||||
signal?: string,
|
||||
'depends-on': {
|
||||
[packageId: string]: string[]
|
||||
}
|
||||
}
|
||||
signal:
|
||||
| "SIGTERM"
|
||||
| "SIGHUP"
|
||||
| "SIGINT"
|
||||
| "SIGQUIT"
|
||||
| "SIGILL"
|
||||
| "SIGTRAP"
|
||||
| "SIGABRT"
|
||||
| "SIGBUS"
|
||||
| "SIGFPE"
|
||||
| "SIGKILL"
|
||||
| "SIGUSR1"
|
||||
| "SIGSEGV"
|
||||
| "SIGUSR2"
|
||||
| "SIGPIPE"
|
||||
| "SIGALRM"
|
||||
| "SIGSTKFLT"
|
||||
| "SIGCHLD"
|
||||
| "SIGCONT"
|
||||
| "SIGSTOP"
|
||||
| "SIGTSTP"
|
||||
| "SIGTTIN"
|
||||
| "SIGTTOU"
|
||||
| "SIGURG"
|
||||
| "SIGXCPU"
|
||||
| "SIGXFSZ"
|
||||
| "SIGVTALRM"
|
||||
| "SIGPROF"
|
||||
| "SIGWINCH"
|
||||
| "SIGIO"
|
||||
| "SIGPWR"
|
||||
| "SIGSYS"
|
||||
| "SIGEMT"
|
||||
| "SIGINFO";
|
||||
"depends-on": {
|
||||
[packageId: string]: string[];
|
||||
};
|
||||
};
|
||||
export type PackagePropertiesV2 = {
|
||||
[name: string]: PackagePropertyObject | PackagePropertyString
|
||||
}
|
||||
[name: string]: PackagePropertyObject | PackagePropertyString;
|
||||
};
|
||||
export type PackagePropertyString = {
|
||||
type: 'string',
|
||||
description?: string,
|
||||
value: string,
|
||||
copyable?: boolean,
|
||||
qr?: boolean,
|
||||
masked?: boolean,
|
||||
}
|
||||
type: "string";
|
||||
description?: string;
|
||||
value: string;
|
||||
copyable?: boolean;
|
||||
qr?: boolean;
|
||||
masked?: boolean;
|
||||
};
|
||||
export type PackagePropertyObject = {
|
||||
value: PackagePropertiesV2;
|
||||
type: "object";
|
||||
description: string;
|
||||
}
|
||||
value: PackagePropertiesV2;
|
||||
type: "object";
|
||||
description: string;
|
||||
};
|
||||
|
||||
export type Properties = {
|
||||
version: 2,
|
||||
data: PackagePropertiesV2
|
||||
}
|
||||
version: 2;
|
||||
data: PackagePropertiesV2;
|
||||
};
|
||||
|
||||
|
||||
export type Dependencies = {
|
||||
[id: string]: {
|
||||
check(effects: Effects, input: Config): Promise<void | null>,
|
||||
autoConfigure(effects: Effects, input: Config): Promise<Config>,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
Out of volumes
|
||||
@@ -0,0 +1 @@
|
||||
../../
|
||||
BIN
deno_core.tar.gz
Normal file
BIN
deno_core.tar.gz
Normal file
Binary file not shown.
Reference in New Issue
Block a user