Compare commits
33 Commits
2eb027754b
...
44e125e00c
Author | SHA1 | Date | |
---|---|---|---|
44e125e00c | |||
83699de4ad | |||
459014b233 | |||
d365a9f8e2 | |||
f61afa73df | |||
e4f614fdbf | |||
fb6768a6fc | |||
49d3fc6b22 | |||
7f3399f1d8 | |||
0eaf0dd3be | |||
91e39f4e65 | |||
a71f60784f | |||
5a5b0e40f7 | |||
f03452840c | |||
7153c24f01 | |||
989d5bc260 | |||
c625ae49ba | |||
31eb44517d | |||
e5383b51f3 | |||
1e26b383d4 | |||
533b9dfef0 | |||
bec62de62f | |||
571963163f | |||
36b0ecdabf | |||
578c2a6883 | |||
6be8482b46 | |||
0cbd1790b0 | |||
728840ca8e | |||
59e2572173 | |||
d894def70c | |||
cb9a4dd84f | |||
d1726a052a | |||
4a4c03f3c2 |
21
.github/workflows/create-release.yml
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
name: Release
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- v[0-9]+.*
|
||||||
|
jobs:
|
||||||
|
create-release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: taiki-e/create-gh-release-action@v1
|
||||||
|
with:
|
||||||
|
# (Optional) Path to changelog.
|
||||||
|
changelog: CHANGELOG.md
|
||||||
|
# (Optional) Create a draft release.
|
||||||
|
# [default value: false]
|
||||||
|
draft: true
|
||||||
|
# (Required) GitHub token for creating GitHub Releases.
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
23
.github/workflows/create-tag.yml
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
name: Deploy
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- "main"
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout Code
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: Get Next Version
|
||||||
|
id: semver
|
||||||
|
uses: ietf-tools/semver-action@v1
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.PERSONAL_GITHUB_TOKEN }}
|
||||||
|
branch: main
|
||||||
|
- uses: rickstaa/action-create-tag@v1
|
||||||
|
with:
|
||||||
|
tag: ${{ steps.semver.outputs.next }}
|
||||||
|
message: ${{ steps.semver.outputs.next }}
|
||||||
|
github_token: ${{ secrets.PERSONAL_GITHUB_TOKEN }}
|
4
CHANGELOG.md
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
## 0.2.0 - 2023-02-17
|
||||||
|
|
||||||
|
First actual useful version of dagger-sdk. contains code for codegen, cli, and
|
||||||
|
core.
|
45
Cargo.lock
generated
@ -184,7 +184,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dagger-codegen"
|
name = "dagger-codegen"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"convert_case",
|
"convert_case",
|
||||||
"dagger-core",
|
"dagger-core",
|
||||||
@ -198,7 +198,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dagger-core"
|
name = "dagger-core"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"dirs",
|
"dirs",
|
||||||
@ -220,7 +220,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dagger-rs"
|
name = "dagger-rs"
|
||||||
version = "0.1.2"
|
version = "0.2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"dagger-codegen",
|
"dagger-codegen",
|
||||||
@ -244,7 +244,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dagger-sdk"
|
name = "dagger-sdk"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64",
|
||||||
"dagger-core",
|
"dagger-core",
|
||||||
@ -253,6 +253,7 @@ dependencies = [
|
|||||||
"genco",
|
"genco",
|
||||||
"gql_client",
|
"gql_client",
|
||||||
"pretty_assertions",
|
"pretty_assertions",
|
||||||
|
"rand",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tokio",
|
"tokio",
|
||||||
@ -1047,6 +1048,12 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ppv-lite86"
|
||||||
|
version = "0.2.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pretty_assertions"
|
name = "pretty_assertions"
|
||||||
version = "1.3.0"
|
version = "1.3.0"
|
||||||
@ -1077,6 +1084,36 @@ dependencies = [
|
|||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand"
|
||||||
|
version = "0.8.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"rand_chacha",
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_chacha"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||||
|
dependencies = [
|
||||||
|
"ppv-lite86",
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_core"
|
||||||
|
version = "0.6.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redox_syscall"
|
name = "redox_syscall"
|
||||||
version = "0.2.16"
|
version = "0.2.16"
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "dagger-rs"
|
name = "dagger-rs"
|
||||||
version = "0.1.2"
|
version = "0.2.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
license-file = "LICENSE.MIT"
|
license-file = "LICENSE.MIT"
|
||||||
description = "A dagger sdk for rust, written in rust"
|
description = "A dagger sdk for rust, written in rust"
|
||||||
|
repository = "https://github.com/kjuulh/dagger-rs"
|
||||||
|
publish = true
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
@ -12,8 +14,8 @@ description = "A dagger sdk for rust, written in rust"
|
|||||||
members = ["crates/dagger-codegen", "crates/dagger-sdk", "crates/dagger-core"]
|
members = ["crates/dagger-codegen", "crates/dagger-sdk", "crates/dagger-core"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
dagger-codegen = { path = "crates/dagger-codegen" }
|
dagger-codegen = { path = "crates/dagger-codegen", version = "0.2.0" }
|
||||||
dagger-core = { path = "crates/dagger-core" }
|
dagger-core = { path = "crates/dagger-core", version = "0.2.0" }
|
||||||
|
|
||||||
clap = "4.1.4"
|
clap = "4.1.4"
|
||||||
dirs = "4.0.0"
|
dirs = "4.0.0"
|
||||||
|
@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
A dagger sdk written in rust for rust.
|
A dagger sdk written in rust for rust.
|
||||||
|
|
||||||
## Disclaimer
|
# Usage
|
||||||
|
|
||||||
Work in progress. This is not ready for usage yet
|
See [dagger-sdk](./crates/dagger-sdk/README.md)
|
||||||
|
|
||||||
### Status
|
### Status
|
||||||
|
|
||||||
@ -20,10 +20,10 @@ Work in progress. This is not ready for usage yet
|
|||||||
- [x] Querier
|
- [x] Querier
|
||||||
- [x] Context
|
- [x] Context
|
||||||
- [x] Deserializer for nested response (bind)
|
- [x] Deserializer for nested response (bind)
|
||||||
- [ ] Add codegen to hook into querier
|
- [x] Add codegen to hook into querier
|
||||||
- [ ] fix build / release cycle
|
- [ ] fix build / release cycle
|
||||||
- [ ] general api stabilisation
|
- [ ] general api stabilisation
|
||||||
- [ ] document usage
|
- [x] document usage
|
||||||
- [ ] make async variant
|
- [ ] make async variant
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
@ -1,13 +1,17 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "dagger-codegen"
|
name = "dagger-codegen"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
readme = "README.md"
|
||||||
|
license-file = "LICENSE.MIT"
|
||||||
|
description = "dagger sdk codegen library"
|
||||||
|
repository = "https://github.com/kjuulh/dagger-rs"
|
||||||
|
publish = true
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
convert_case = "0.6.0"
|
convert_case = "0.6.0"
|
||||||
dagger-core = { path = "../dagger-core" }
|
dagger-core = { path = "../dagger-core", version = "0.2.0" }
|
||||||
|
|
||||||
eyre = "0.6.8"
|
eyre = "0.6.8"
|
||||||
genco = "0.17.3"
|
genco = "0.17.3"
|
||||||
|
7
crates/dagger-codegen/LICENSE.MIT
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
Copyright 2023 Kasper J. Hermansen
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
0
crates/dagger-codegen/README.md
Normal file
@ -1 +0,0 @@
|
|||||||
nightly
|
|
@ -1,155 +0,0 @@
|
|||||||
use std::{
|
|
||||||
io::{BufWriter, Write},
|
|
||||||
sync::Arc,
|
|
||||||
};
|
|
||||||
|
|
||||||
use dagger_core::introspection::{FullType, IntrospectionResponse, Schema};
|
|
||||||
use genco::{fmt, prelude::rust, prelude::*, quote};
|
|
||||||
|
|
||||||
use crate::handlers::{
|
|
||||||
enumeration::Enumeration, input::Input, object::Object, scalar::Scalar, DynHandler, Handlers,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub struct CodeGeneration {
|
|
||||||
handlers: Handlers,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CodeGeneration {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
handlers: vec![
|
|
||||||
Arc::new(Scalar {}),
|
|
||||||
Arc::new(Enumeration {}),
|
|
||||||
Arc::new(Input {}),
|
|
||||||
Arc::new(Object {}),
|
|
||||||
],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn generate(&self, schema: &IntrospectionResponse) -> eyre::Result<String> {
|
|
||||||
let code = self.generate_from_schema(
|
|
||||||
schema
|
|
||||||
.as_schema()
|
|
||||||
.schema
|
|
||||||
.as_ref()
|
|
||||||
.ok_or(eyre::anyhow!("could not get schema to generate code from"))?,
|
|
||||||
)?;
|
|
||||||
Ok(code)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generate_from_schema(&self, schema: &Schema) -> eyre::Result<String> {
|
|
||||||
let mut output = rust::Tokens::new();
|
|
||||||
output.push();
|
|
||||||
output.append(quote! {
|
|
||||||
$(format!("// code generated by dagger. DO NOT EDIT."))
|
|
||||||
});
|
|
||||||
|
|
||||||
output.push();
|
|
||||||
output.append(render_base_types());
|
|
||||||
output.push();
|
|
||||||
|
|
||||||
let mut types = get_types(schema)?;
|
|
||||||
//let remaining: Vec<Option<String>> = types.into_iter().map(type_name).collect();
|
|
||||||
|
|
||||||
types.sort_by_key(|a| a.name.as_ref());
|
|
||||||
|
|
||||||
for (handler, types) in self.group_by_handlers(&types) {
|
|
||||||
for t in types {
|
|
||||||
if let Some(_) = self.type_name(&t) {
|
|
||||||
let rendered = handler.render(&t)?;
|
|
||||||
output.push();
|
|
||||||
output.append(rendered);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut buffer = BufWriter::new(Vec::new());
|
|
||||||
let mut w = fmt::IoWriter::new(buffer.by_ref());
|
|
||||||
let fmt = fmt::Config::from_lang::<Rust>().with_indentation(fmt::Indentation::Space(4));
|
|
||||||
let config = rust::Config::default();
|
|
||||||
// Prettier imports and use.
|
|
||||||
//.with_default_import(rust::ImportMode::Qualified);
|
|
||||||
|
|
||||||
output.format_file(&mut w.as_formatter(&fmt), &config)?;
|
|
||||||
|
|
||||||
let out = String::from_utf8(buffer.into_inner()?)?;
|
|
||||||
Ok(out)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn group_by_handlers(&self, types: &Vec<&FullType>) -> Vec<(DynHandler, Vec<FullType>)> {
|
|
||||||
let mut group = vec![];
|
|
||||||
|
|
||||||
for handler in self.handlers.iter() {
|
|
||||||
let mut group_types: Vec<FullType> = vec![];
|
|
||||||
for t in types.iter() {
|
|
||||||
if handler.predicate(*t) {
|
|
||||||
group_types.push(t.clone().clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
group.push((handler.clone(), group_types))
|
|
||||||
}
|
|
||||||
|
|
||||||
group
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn type_name(&self, t: &FullType) -> Option<String> {
|
|
||||||
let name = t.name.as_ref();
|
|
||||||
if let Some(name) = name {
|
|
||||||
if name.starts_with("_") {
|
|
||||||
//|| !is_custom_scalar_type(t) {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Some(name.replace("Query", "Client"));
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn group_key(&self, t: &FullType) -> Option<DynHandler> {
|
|
||||||
for handler in self.handlers.iter() {
|
|
||||||
if handler.predicate(&t) {
|
|
||||||
return Some(handler.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn sort_key(&self, t: &FullType) -> (isize, String) {
|
|
||||||
for (i, handler) in self.handlers.iter().enumerate() {
|
|
||||||
if handler.predicate(t) {
|
|
||||||
return (i as isize, t.name.as_ref().unwrap().clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (-1, t.name.as_ref().unwrap().clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_base_types() -> rust::Tokens {
|
|
||||||
let i = rust::import("dagger_core", "Int");
|
|
||||||
let b = rust::import("dagger_core", "Boolean");
|
|
||||||
|
|
||||||
quote! {
|
|
||||||
$(register(i))
|
|
||||||
$(register(b))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_types(schema: &Schema) -> eyre::Result<Vec<&FullType>> {
|
|
||||||
let types = schema
|
|
||||||
.types
|
|
||||||
.as_ref()
|
|
||||||
.ok_or(eyre::anyhow!("types not found on schema"))?;
|
|
||||||
|
|
||||||
let types: Vec<&FullType> = types
|
|
||||||
.iter()
|
|
||||||
.map(|t| t.as_ref().map(|t| &t.full_type))
|
|
||||||
.flatten()
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
Ok(types)
|
|
||||||
}
|
|
422
crates/dagger-codegen/src/functions.rs
Normal file
@ -0,0 +1,422 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use dagger_core::introspection::{FullType, FullTypeFields, InputValue, TypeRef, __TypeKind};
|
||||||
|
use eyre::ContextCompat;
|
||||||
|
|
||||||
|
use crate::utility::OptionExt;
|
||||||
|
|
||||||
|
pub trait FormatTypeFuncs {
|
||||||
|
fn format_kind_list(&self, representation: &str) -> String;
|
||||||
|
fn format_kind_scalar_string(&self, representation: &str) -> String;
|
||||||
|
fn format_kind_scalar_int(&self, representation: &str) -> String;
|
||||||
|
fn format_kind_scalar_float(&self, representation: &str) -> String;
|
||||||
|
fn format_kind_scalar_boolean(&self, representation: &str) -> String;
|
||||||
|
fn format_kind_scalar_default(
|
||||||
|
&self,
|
||||||
|
representation: &str,
|
||||||
|
ref_name: &str,
|
||||||
|
input: bool,
|
||||||
|
) -> String;
|
||||||
|
fn format_kind_object(&self, representation: &str, ref_name: &str) -> String;
|
||||||
|
fn format_kind_input_object(&self, representation: &str, ref_name: &str) -> String;
|
||||||
|
fn format_kind_enum(&self, representation: &str, ref_name: &str) -> String;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type DynFormatTypeFuncs = Arc<dyn FormatTypeFuncs + Send + Sync>;
|
||||||
|
|
||||||
|
pub struct CommonFunctions {
|
||||||
|
format_type_funcs: DynFormatTypeFuncs,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CommonFunctions {
|
||||||
|
pub fn new(funcs: DynFormatTypeFuncs) -> Self {
|
||||||
|
Self {
|
||||||
|
format_type_funcs: funcs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn format_input_type(&self, t: &TypeRef) -> String {
|
||||||
|
self.format_type(t, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn format_output_type(&self, t: &TypeRef) -> String {
|
||||||
|
self.format_type(t, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn format_type(&self, t: &TypeRef, input: bool) -> String {
|
||||||
|
let mut representation = String::new();
|
||||||
|
let mut r = Some(t.clone());
|
||||||
|
while r.is_some() {
|
||||||
|
return match r.as_ref() {
|
||||||
|
Some(rf) => match rf.kind.as_ref() {
|
||||||
|
Some(k) => match k {
|
||||||
|
__TypeKind::SCALAR => match Scalar::from(rf) {
|
||||||
|
Scalar::Int => self
|
||||||
|
.format_type_funcs
|
||||||
|
.format_kind_scalar_int(&mut representation),
|
||||||
|
Scalar::Float => self
|
||||||
|
.format_type_funcs
|
||||||
|
.format_kind_scalar_float(&mut representation),
|
||||||
|
Scalar::String => self
|
||||||
|
.format_type_funcs
|
||||||
|
.format_kind_scalar_string(&mut representation),
|
||||||
|
Scalar::Boolean => self
|
||||||
|
.format_type_funcs
|
||||||
|
.format_kind_scalar_boolean(&mut representation),
|
||||||
|
Scalar::Default => self.format_type_funcs.format_kind_scalar_default(
|
||||||
|
&mut representation,
|
||||||
|
rf.name.as_ref().unwrap(),
|
||||||
|
input,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
__TypeKind::OBJECT => self
|
||||||
|
.format_type_funcs
|
||||||
|
.format_kind_object(&mut representation, rf.name.as_ref().unwrap()),
|
||||||
|
__TypeKind::ENUM => self
|
||||||
|
.format_type_funcs
|
||||||
|
.format_kind_enum(&mut representation, rf.name.as_ref().unwrap()),
|
||||||
|
__TypeKind::INPUT_OBJECT => {
|
||||||
|
self.format_type_funcs.format_kind_input_object(
|
||||||
|
&mut representation,
|
||||||
|
&rf.name.as_ref().unwrap(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
__TypeKind::LIST => {
|
||||||
|
let mut inner_type = rf
|
||||||
|
.of_type
|
||||||
|
.as_ref()
|
||||||
|
.map(|t| t.clone())
|
||||||
|
.map(|t| *t)
|
||||||
|
.map(|t| self.format_type(&t, input))
|
||||||
|
.context("could not get inner type of list")
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
representation =
|
||||||
|
self.format_type_funcs.format_kind_list(&mut inner_type);
|
||||||
|
|
||||||
|
return representation;
|
||||||
|
}
|
||||||
|
__TypeKind::NON_NULL => {
|
||||||
|
r = rf.of_type.as_ref().map(|t| t.clone()).map(|t| *t);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
__TypeKind::Other(_) => {
|
||||||
|
r = rf.of_type.as_ref().map(|t| t.clone()).map(|t| *t);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
__TypeKind::INTERFACE => break,
|
||||||
|
__TypeKind::UNION => break,
|
||||||
|
},
|
||||||
|
None => break,
|
||||||
|
},
|
||||||
|
None => break,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("rep: {}", representation);
|
||||||
|
println!("{:?}", t);
|
||||||
|
|
||||||
|
representation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Scalar {
|
||||||
|
Int,
|
||||||
|
Float,
|
||||||
|
String,
|
||||||
|
Boolean,
|
||||||
|
Default,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&TypeRef> for Scalar {
|
||||||
|
fn from(value: &TypeRef) -> Self {
|
||||||
|
match value.name.as_ref().map(|n| n.as_str()) {
|
||||||
|
Some("Int") => Scalar::Int,
|
||||||
|
Some("Float") => Scalar::Float,
|
||||||
|
Some("String") => Scalar::String,
|
||||||
|
Some("Boolean") => Scalar::Boolean,
|
||||||
|
Some(_) => Scalar::Default,
|
||||||
|
None => Scalar::Default,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_type_from_name<'t>(types: &'t [FullType], name: &'t str) -> Option<&'t FullType> {
|
||||||
|
types
|
||||||
|
.into_iter()
|
||||||
|
.find(|t| t.name.as_ref().map(|s| s.as_str()) == Some(name))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn type_ref_is_optional(type_ref: &TypeRef) -> bool {
|
||||||
|
type_ref
|
||||||
|
.kind
|
||||||
|
.pipe(|k| *k != __TypeKind::NON_NULL)
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn type_field_has_optional(field: &FullTypeFields) -> bool {
|
||||||
|
field
|
||||||
|
.args
|
||||||
|
.pipe(|a| {
|
||||||
|
a.iter()
|
||||||
|
.map(|a| a.pipe(|a| &a.input_value))
|
||||||
|
.flatten()
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
})
|
||||||
|
.pipe(|s| input_values_has_optionals(s.as_slice()))
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn type_ref_is_scalar(type_ref: &TypeRef) -> bool {
|
||||||
|
let mut type_ref = type_ref.clone();
|
||||||
|
if type_ref
|
||||||
|
.kind
|
||||||
|
.pipe(|k| *k == __TypeKind::NON_NULL)
|
||||||
|
.unwrap_or(false)
|
||||||
|
{
|
||||||
|
type_ref = *type_ref.of_type.unwrap().clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
type_ref
|
||||||
|
.kind
|
||||||
|
.pipe(|k| *k == __TypeKind::SCALAR)
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn type_ref_is_object(type_ref: &TypeRef) -> bool {
|
||||||
|
let mut type_ref = type_ref.clone();
|
||||||
|
if type_ref
|
||||||
|
.kind
|
||||||
|
.pipe(|k| *k == __TypeKind::NON_NULL)
|
||||||
|
.unwrap_or(false)
|
||||||
|
{
|
||||||
|
type_ref = *type_ref.of_type.unwrap().clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
type_ref
|
||||||
|
.kind
|
||||||
|
.pipe(|k| *k == __TypeKind::OBJECT)
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn type_ref_is_list(type_ref: &TypeRef) -> bool {
|
||||||
|
let mut type_ref = type_ref.clone();
|
||||||
|
if type_ref
|
||||||
|
.kind
|
||||||
|
.pipe(|k| *k == __TypeKind::NON_NULL)
|
||||||
|
.unwrap_or(false)
|
||||||
|
{
|
||||||
|
type_ref = *type_ref.of_type.unwrap().clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
type_ref
|
||||||
|
.kind
|
||||||
|
.pipe(|k| *k == __TypeKind::LIST)
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn type_ref_is_list_of_objects(type_ref: &TypeRef) -> bool {
|
||||||
|
let mut type_ref = type_ref.clone();
|
||||||
|
if type_ref
|
||||||
|
.kind
|
||||||
|
.pipe(|k| *k == __TypeKind::NON_NULL)
|
||||||
|
.unwrap_or(false)
|
||||||
|
{
|
||||||
|
type_ref = *type_ref.of_type.unwrap().clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
if type_ref
|
||||||
|
.kind
|
||||||
|
.pipe(|k| *k == __TypeKind::LIST)
|
||||||
|
.unwrap_or(false)
|
||||||
|
{
|
||||||
|
type_ref = *type_ref.of_type.unwrap().clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
type_ref_is_object(&type_ref)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn input_values_has_optionals(input_values: &[&InputValue]) -> bool {
|
||||||
|
input_values
|
||||||
|
.into_iter()
|
||||||
|
.map(|k| type_ref_is_optional(&k.type_))
|
||||||
|
.filter(|k| *k)
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.len()
|
||||||
|
> 0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn input_values_is_empty(input_values: &[InputValue]) -> bool {
|
||||||
|
input_values.len() > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use dagger_core::introspection::{FullType, InputValue, TypeRef, __TypeKind};
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
|
use crate::functions::{input_values_has_optionals, type_ref_is_optional};
|
||||||
|
|
||||||
|
use super::get_type_from_name;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn get_type_from_name_has_no_item() {
|
||||||
|
let input = vec![];
|
||||||
|
let output = get_type_from_name(&input, "some-name");
|
||||||
|
|
||||||
|
assert_eq!(output.is_none(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn get_type_from_name_has_item() {
|
||||||
|
let name = "some-name";
|
||||||
|
let input = vec![FullType {
|
||||||
|
kind: None,
|
||||||
|
name: Some(name.to_string()),
|
||||||
|
description: None,
|
||||||
|
fields: None,
|
||||||
|
input_fields: None,
|
||||||
|
interfaces: None,
|
||||||
|
enum_values: None,
|
||||||
|
possible_types: None,
|
||||||
|
}];
|
||||||
|
let output = get_type_from_name(&input, name);
|
||||||
|
|
||||||
|
assert_eq!(output.is_some(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn get_type_from_name_has_item_multiple_entries() {
|
||||||
|
let name = "some-name";
|
||||||
|
let input = vec![
|
||||||
|
FullType {
|
||||||
|
kind: None,
|
||||||
|
name: Some(name.to_string()),
|
||||||
|
description: None,
|
||||||
|
fields: None,
|
||||||
|
input_fields: None,
|
||||||
|
interfaces: None,
|
||||||
|
enum_values: None,
|
||||||
|
possible_types: None,
|
||||||
|
},
|
||||||
|
FullType {
|
||||||
|
kind: None,
|
||||||
|
name: Some(name.to_string()),
|
||||||
|
description: None,
|
||||||
|
fields: None,
|
||||||
|
input_fields: None,
|
||||||
|
interfaces: None,
|
||||||
|
enum_values: None,
|
||||||
|
possible_types: None,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
let output = get_type_from_name(&input, name);
|
||||||
|
|
||||||
|
assert_eq!(output.is_some(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn type_ref_is_optional_has_none() {
|
||||||
|
let input = TypeRef {
|
||||||
|
kind: None,
|
||||||
|
name: None,
|
||||||
|
of_type: None,
|
||||||
|
};
|
||||||
|
let output = type_ref_is_optional(&input);
|
||||||
|
|
||||||
|
assert_eq!(output, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn type_ref_is_optional_is_required() {
|
||||||
|
let input = TypeRef {
|
||||||
|
kind: Some(__TypeKind::NON_NULL),
|
||||||
|
name: None,
|
||||||
|
of_type: None,
|
||||||
|
};
|
||||||
|
let output = type_ref_is_optional(&input);
|
||||||
|
|
||||||
|
assert_eq!(output, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn type_ref_is_optional_is_optional() {
|
||||||
|
let input = TypeRef {
|
||||||
|
kind: Some(__TypeKind::OBJECT),
|
||||||
|
name: None,
|
||||||
|
of_type: None,
|
||||||
|
};
|
||||||
|
let output = type_ref_is_optional(&input);
|
||||||
|
|
||||||
|
assert_eq!(output, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn input_values_has_optionals_none() {
|
||||||
|
let input = vec![];
|
||||||
|
|
||||||
|
let output = input_values_has_optionals(&input);
|
||||||
|
|
||||||
|
assert_eq!(output, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn input_values_has_optionals_has_optional() {
|
||||||
|
let input = vec![
|
||||||
|
InputValue {
|
||||||
|
name: "some-name".to_string(),
|
||||||
|
description: None,
|
||||||
|
type_: TypeRef {
|
||||||
|
kind: Some(__TypeKind::NON_NULL),
|
||||||
|
name: None,
|
||||||
|
of_type: None,
|
||||||
|
},
|
||||||
|
default_value: None,
|
||||||
|
},
|
||||||
|
InputValue {
|
||||||
|
name: "some-other-name".to_string(),
|
||||||
|
description: None,
|
||||||
|
type_: TypeRef {
|
||||||
|
kind: Some(__TypeKind::OBJECT),
|
||||||
|
name: None,
|
||||||
|
of_type: None,
|
||||||
|
},
|
||||||
|
default_value: None,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
let output = input_values_has_optionals(input.iter().collect::<Vec<_>>().as_slice());
|
||||||
|
|
||||||
|
assert_eq!(output, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn input_values_has_optionals_is_required() {
|
||||||
|
let input = vec![
|
||||||
|
InputValue {
|
||||||
|
name: "some-name".to_string(),
|
||||||
|
description: None,
|
||||||
|
type_: TypeRef {
|
||||||
|
kind: Some(__TypeKind::NON_NULL),
|
||||||
|
name: None,
|
||||||
|
of_type: None,
|
||||||
|
},
|
||||||
|
default_value: None,
|
||||||
|
},
|
||||||
|
InputValue {
|
||||||
|
name: "some-other-name".to_string(),
|
||||||
|
description: None,
|
||||||
|
type_: TypeRef {
|
||||||
|
kind: Some(__TypeKind::NON_NULL),
|
||||||
|
name: None,
|
||||||
|
of_type: None,
|
||||||
|
},
|
||||||
|
default_value: None,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
let output = input_values_has_optionals(input.iter().collect::<Vec<_>>().as_slice());
|
||||||
|
|
||||||
|
assert_eq!(output, false);
|
||||||
|
}
|
||||||
|
}
|
21
crates/dagger-codegen/src/generator.rs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use dagger_core::introspection::Schema;
|
||||||
|
|
||||||
|
pub trait Generator {
|
||||||
|
fn generate(&self, schema: Schema) -> eyre::Result<String>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type DynGenerator = Arc<dyn Generator + Send + Sync>;
|
||||||
|
|
||||||
|
pub trait FormatTypeRefs {
|
||||||
|
fn format_kind_list(representation: &str) -> String;
|
||||||
|
fn format_kind_scalar_string(representation: &str) -> String;
|
||||||
|
fn format_kind_scalar_int(representation: &str) -> String;
|
||||||
|
fn format_kind_scalar_float(representation: &str) -> String;
|
||||||
|
fn format_kind_scalar_boolean(representation: &str) -> String;
|
||||||
|
fn format_kind_scalar_default(representation: &str, ref_name: &str, input: bool) -> String;
|
||||||
|
fn format_kind_object(representation: &str, ref_name: &str) -> String;
|
||||||
|
fn format_kind_input_object(representation: &str, ref_name: &str) -> String;
|
||||||
|
fn format_kind_enum(representation: &str, ref_name: &str) -> String;
|
||||||
|
}
|
@ -1,33 +0,0 @@
|
|||||||
use dagger_core::introspection::FullType;
|
|
||||||
use genco::{prelude::rust, quote};
|
|
||||||
|
|
||||||
use crate::predicates::is_enum_type;
|
|
||||||
|
|
||||||
use super::{utility::render_description, Handler};
|
|
||||||
|
|
||||||
pub struct Enumeration;
|
|
||||||
|
|
||||||
impl Handler for Enumeration {
|
|
||||||
fn predicate(&self, t: &FullType) -> bool {
|
|
||||||
is_enum_type(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render(&self, t: &FullType) -> eyre::Result<rust::Tokens> {
|
|
||||||
let name = t
|
|
||||||
.name
|
|
||||||
.as_ref()
|
|
||||||
.ok_or(eyre::anyhow!("could not get name from type"))?;
|
|
||||||
|
|
||||||
let description =
|
|
||||||
render_description(t).ok_or(eyre::anyhow!("could not find description"))?;
|
|
||||||
|
|
||||||
let out = quote! {
|
|
||||||
$description
|
|
||||||
pub enum $name {
|
|
||||||
// TODO: Add individual items
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(out)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,122 +0,0 @@
|
|||||||
use convert_case::{Case, Casing};
|
|
||||||
use dagger_core::introspection::{FullTypeFields, FullTypeFieldsArgs};
|
|
||||||
use genco::{prelude::rust, quote};
|
|
||||||
|
|
||||||
use super::{
|
|
||||||
type_ref::{self, render_type_ref},
|
|
||||||
utility::{render_description_from_field, render_description_from_input_value},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn render_fields(fields: &Vec<FullTypeFields>) -> eyre::Result<Option<rust::Tokens>> {
|
|
||||||
let mut collected_fields: Vec<rust::Tokens> = vec![];
|
|
||||||
for field in fields.iter() {
|
|
||||||
let name = field.name.as_ref().map(|n| n.to_case(Case::Snake)).unwrap();
|
|
||||||
let output = render_field_output(field)?;
|
|
||||||
let description = render_description_from_field(field);
|
|
||||||
let args = match field.args.as_ref() {
|
|
||||||
Some(a) => render_args(a),
|
|
||||||
None => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut tkns = rust::Tokens::new();
|
|
||||||
|
|
||||||
if let Some(args) = args.as_ref() {
|
|
||||||
tkns.append(quote! {
|
|
||||||
$description
|
|
||||||
pub struct $(&name)Args {
|
|
||||||
$(&args.args)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
tkns.push();
|
|
||||||
}
|
|
||||||
|
|
||||||
tkns.append(quote! {
|
|
||||||
pub fn $(&name)(
|
|
||||||
&self,
|
|
||||||
$(if let Some(_) = args.as_ref() => args: $(&name)Args)
|
|
||||||
) -> $(&output) {
|
|
||||||
let query = self.selection.select($(field.name.as_ref().map(|n| format!("\"{}\"", n))));
|
|
||||||
$(if let Some(_) = args.as_ref() => query.args(args);)
|
|
||||||
|
|
||||||
$output {
|
|
||||||
conn: self.conn.clone(),
|
|
||||||
proc: self.proc.clone(),
|
|
||||||
selection: query,
|
|
||||||
}
|
|
||||||
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
collected_fields.push(tkns);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Some(quote! {
|
|
||||||
$(for field in collected_fields => $field $['\n'] )
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Arg {
|
|
||||||
name: String,
|
|
||||||
description: Option<rust::Tokens>,
|
|
||||||
type_: rust::Tokens,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct CollectedArgs {
|
|
||||||
description: Option<rust::Tokens>,
|
|
||||||
args: rust::Tokens,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_args(args: &[Option<FullTypeFieldsArgs>]) -> Option<CollectedArgs> {
|
|
||||||
let mut collected_args: Vec<Arg> = vec![];
|
|
||||||
|
|
||||||
for arg in args {
|
|
||||||
if let Some(arg) = arg.as_ref().map(|a| &a.input_value) {
|
|
||||||
let name = arg.name.clone();
|
|
||||||
let description = render_description_from_input_value(&arg, &name);
|
|
||||||
let t = render_type_ref(&arg.type_).unwrap();
|
|
||||||
|
|
||||||
collected_args.push(Arg {
|
|
||||||
name,
|
|
||||||
description,
|
|
||||||
type_: t,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if collected_args.len() > 0 {
|
|
||||||
let mut collected_arg = CollectedArgs {
|
|
||||||
description: Some(rust::Tokens::new()),
|
|
||||||
args: rust::Tokens::new(),
|
|
||||||
};
|
|
||||||
|
|
||||||
for arg in collected_args {
|
|
||||||
if let Some(desc) = arg.description {
|
|
||||||
if let Some(inner_desc) = collected_arg.description.as_mut() {
|
|
||||||
inner_desc.append(desc);
|
|
||||||
inner_desc.push();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
collected_arg.args.append(quote! {
|
|
||||||
$(arg.name.to_case(Case::Snake)): $(arg.type_),
|
|
||||||
});
|
|
||||||
collected_arg.args.push();
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(desc) = collected_arg.description.as_ref() {
|
|
||||||
if desc.is_empty() {
|
|
||||||
collected_arg.description = None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(collected_arg)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn render_field_output(field: &FullTypeFields) -> eyre::Result<rust::Tokens> {
|
|
||||||
let inner = &field.type_.as_ref().unwrap();
|
|
||||||
type_ref::render_type_ref(&inner.type_ref)
|
|
||||||
}
|
|
@ -1,110 +0,0 @@
|
|||||||
use dagger_core::introspection::FullType;
|
|
||||||
use genco::prelude::rust;
|
|
||||||
use genco::prelude::*;
|
|
||||||
|
|
||||||
use crate::predicates::is_input_object_type;
|
|
||||||
|
|
||||||
use super::{input_field::render_input_fields, utility::render_description, Handler};
|
|
||||||
|
|
||||||
pub struct Input;
|
|
||||||
|
|
||||||
impl Handler for Input {
|
|
||||||
fn predicate(&self, t: &FullType) -> bool {
|
|
||||||
is_input_object_type(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render(&self, t: &FullType) -> eyre::Result<rust::Tokens> {
|
|
||||||
let name = t
|
|
||||||
.name
|
|
||||||
.as_ref()
|
|
||||||
.ok_or(eyre::anyhow!("could not find name"))?;
|
|
||||||
let description = render_description(t);
|
|
||||||
|
|
||||||
//let input = rust::import("dagger_core", "Input");
|
|
||||||
|
|
||||||
let fields = match t.input_fields.as_ref() {
|
|
||||||
Some(i) => render_input_fields(i)?,
|
|
||||||
None => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let out = quote! {
|
|
||||||
$(if description.is_some() => $description)
|
|
||||||
pub struct $name {
|
|
||||||
$(if fields.is_some() => $fields)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(out)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use dagger_core::introspection::{
|
|
||||||
FullType, FullTypeInputFields, InputValue, TypeRef, __TypeKind,
|
|
||||||
};
|
|
||||||
use pretty_assertions::assert_eq;
|
|
||||||
|
|
||||||
use crate::handlers::Handler;
|
|
||||||
|
|
||||||
use super::Input;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn can_gen_input() {
|
|
||||||
let input = Input {};
|
|
||||||
let t = FullType {
|
|
||||||
kind: Some(__TypeKind::INPUT_OBJECT),
|
|
||||||
name: Some("BuildArg".into()),
|
|
||||||
description: None,
|
|
||||||
input_fields: Some(vec![
|
|
||||||
FullTypeInputFields {
|
|
||||||
input_value: InputValue {
|
|
||||||
name: "name".into(),
|
|
||||||
description: None,
|
|
||||||
type_: TypeRef {
|
|
||||||
name: None,
|
|
||||||
kind: Some(__TypeKind::NON_NULL),
|
|
||||||
of_type: Some(Box::new(TypeRef {
|
|
||||||
kind: Some(__TypeKind::SCALAR),
|
|
||||||
name: Some("String".into()),
|
|
||||||
of_type: None,
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
default_value: None,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
FullTypeInputFields {
|
|
||||||
input_value: InputValue {
|
|
||||||
name: "value".into(),
|
|
||||||
description: None,
|
|
||||||
type_: TypeRef {
|
|
||||||
name: None,
|
|
||||||
kind: Some(__TypeKind::NON_NULL),
|
|
||||||
of_type: Some(Box::new(TypeRef {
|
|
||||||
kind: Some(__TypeKind::SCALAR),
|
|
||||||
name: Some("String".into()),
|
|
||||||
of_type: None,
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
default_value: None,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]),
|
|
||||||
interfaces: None,
|
|
||||||
enum_values: None,
|
|
||||||
possible_types: None,
|
|
||||||
fields: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let expected = r#"pub struct BuildArg {
|
|
||||||
pub name: String,
|
|
||||||
|
|
||||||
pub value: String,
|
|
||||||
}
|
|
||||||
"#;
|
|
||||||
|
|
||||||
let output = input.render(&t).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(output.to_file_string().unwrap(), expected);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,22 +0,0 @@
|
|||||||
use dagger_core::introspection::FullTypeInputFields;
|
|
||||||
use genco::{prelude::rust, quote};
|
|
||||||
|
|
||||||
use super::type_ref;
|
|
||||||
|
|
||||||
pub fn render_input_fields(
|
|
||||||
input_fields: &Vec<FullTypeInputFields>,
|
|
||||||
) -> eyre::Result<Option<rust::Tokens>> {
|
|
||||||
let mut fields: Vec<(String, rust::Tokens)> = vec![];
|
|
||||||
for field in input_fields.iter() {
|
|
||||||
fields.push((field.input_value.name.clone(), render_input_field(field)?));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Some(quote! {
|
|
||||||
$(for (name, field) in fields => pub $name: $field, $['\n'] )
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn render_input_field(field: &FullTypeInputFields) -> eyre::Result<rust::Tokens> {
|
|
||||||
let inner = &field.input_value.type_;
|
|
||||||
type_ref::render_type_ref(inner)
|
|
||||||
}
|
|
@ -1,93 +0,0 @@
|
|||||||
pub mod enumeration;
|
|
||||||
mod fields;
|
|
||||||
pub mod input;
|
|
||||||
mod input_field;
|
|
||||||
pub mod object;
|
|
||||||
pub mod scalar;
|
|
||||||
mod type_ref;
|
|
||||||
mod utility;
|
|
||||||
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use dagger_core::introspection::FullType;
|
|
||||||
use genco::prelude::rust::Tokens;
|
|
||||||
use genco::prelude::*;
|
|
||||||
|
|
||||||
pub trait Handler {
|
|
||||||
fn predicate(&self, _t: &FullType) -> bool {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render(&self, t: &FullType) -> eyre::Result<rust::Tokens> {
|
|
||||||
let tstruct = self.render_struct(t)?;
|
|
||||||
let timpl = self.render_impl(t)?;
|
|
||||||
let mut out = rust::Tokens::new();
|
|
||||||
out.append(tstruct);
|
|
||||||
out.push();
|
|
||||||
out.append(timpl);
|
|
||||||
out.push();
|
|
||||||
Ok(out)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_struct(&self, t: &FullType) -> eyre::Result<Tokens> {
|
|
||||||
let name = t.name.as_ref().ok_or(eyre::anyhow!("name not found"))?;
|
|
||||||
|
|
||||||
Ok(quote! {
|
|
||||||
pub struct $name {} {
|
|
||||||
// TODO: Add fields
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_impl(&self, t: &FullType) -> eyre::Result<Tokens> {
|
|
||||||
let name = t.name.as_ref().ok_or(eyre::anyhow!("name not found"))?;
|
|
||||||
|
|
||||||
Ok(quote! {
|
|
||||||
impl $name {} {
|
|
||||||
// TODO: Add fields
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type DynHandler = Arc<dyn Handler + Send + Sync>;
|
|
||||||
pub type Handlers = Vec<DynHandler>;
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use dagger_core::introspection::FullType;
|
|
||||||
use pretty_assertions::assert_eq;
|
|
||||||
|
|
||||||
use super::Handler;
|
|
||||||
|
|
||||||
struct DefaultHandler;
|
|
||||||
impl Handler for DefaultHandler {}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn render_returns_expected() {
|
|
||||||
let handler = DefaultHandler {};
|
|
||||||
let t = FullType {
|
|
||||||
kind: None,
|
|
||||||
name: Some("SomeName".into()),
|
|
||||||
description: None,
|
|
||||||
fields: None,
|
|
||||||
input_fields: None,
|
|
||||||
interfaces: None,
|
|
||||||
enum_values: None,
|
|
||||||
possible_types: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let res = handler.render(&t).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
res.to_string().unwrap(),
|
|
||||||
"pub struct SomeName {} {
|
|
||||||
|
|
||||||
}
|
|
||||||
impl SomeName {} {
|
|
||||||
|
|
||||||
}"
|
|
||||||
.to_string()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,167 +0,0 @@
|
|||||||
use dagger_core::introspection::FullType;
|
|
||||||
use genco::{prelude::rust, quote};
|
|
||||||
|
|
||||||
use crate::predicates::is_object_type;
|
|
||||||
|
|
||||||
use super::{fields, input_field, utility::render_description, Handler};
|
|
||||||
|
|
||||||
pub struct Object;
|
|
||||||
|
|
||||||
impl Handler for Object {
|
|
||||||
fn predicate(&self, t: &FullType) -> bool {
|
|
||||||
is_object_type(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render(&self, t: &FullType) -> eyre::Result<rust::Tokens> {
|
|
||||||
let name = t
|
|
||||||
.name
|
|
||||||
.as_ref()
|
|
||||||
.ok_or(eyre::anyhow!("could not find name"))?;
|
|
||||||
let description = render_description(t);
|
|
||||||
|
|
||||||
let fields = match t.fields.as_ref() {
|
|
||||||
Some(i) => fields::render_fields(i)?,
|
|
||||||
None => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let input_fields = match t.input_fields.as_ref() {
|
|
||||||
Some(i) => input_field::render_input_fields(i)?,
|
|
||||||
None => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let out = quote! {
|
|
||||||
$(if description.is_some() => $description)
|
|
||||||
pub struct $name {
|
|
||||||
$(if input_fields.is_some() => $input_fields)
|
|
||||||
}
|
|
||||||
|
|
||||||
impl $name {
|
|
||||||
$(if fields.is_some() => $fields)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(out)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use dagger_core::introspection::{
|
|
||||||
FullType, FullTypeFields, FullTypeFieldsArgs, FullTypeFieldsType, InputValue, TypeRef,
|
|
||||||
__TypeKind,
|
|
||||||
};
|
|
||||||
use pretty_assertions::assert_eq;
|
|
||||||
|
|
||||||
use crate::handlers::Handler;
|
|
||||||
|
|
||||||
use super::Object;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn can_render_object() {
|
|
||||||
let t: FullType = FullType {
|
|
||||||
kind: Some(__TypeKind::OBJECT),
|
|
||||||
name: Some("CacheVolume".into()),
|
|
||||||
description: Some("A directory whose contents persists across sessions".into()),
|
|
||||||
fields: Some(vec![FullTypeFields {
|
|
||||||
name: Some("id".into()),
|
|
||||||
description: None,
|
|
||||||
args: None,
|
|
||||||
type_: Some(FullTypeFieldsType {
|
|
||||||
type_ref: TypeRef {
|
|
||||||
kind: Some(__TypeKind::NON_NULL),
|
|
||||||
name: None,
|
|
||||||
of_type: Some(Box::new(TypeRef {
|
|
||||||
kind: Some(__TypeKind::SCALAR),
|
|
||||||
name: Some("CacheID".into()),
|
|
||||||
of_type: None,
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
is_deprecated: Some(false),
|
|
||||||
deprecation_reason: None,
|
|
||||||
}]),
|
|
||||||
input_fields: None,
|
|
||||||
interfaces: None,
|
|
||||||
enum_values: None,
|
|
||||||
possible_types: None,
|
|
||||||
};
|
|
||||||
let expected = r#"
|
|
||||||
/// A directory whose contents persists across sessions
|
|
||||||
pub struct CacheVolume {}
|
|
||||||
|
|
||||||
impl CacheVolume {
|
|
||||||
pub fn id(
|
|
||||||
&self,
|
|
||||||
) -> CacheID {
|
|
||||||
let query = self.selection.select("id");
|
|
||||||
|
|
||||||
CacheID {
|
|
||||||
conn: self.conn.clone(),
|
|
||||||
proc: self.proc.clone(),
|
|
||||||
selection: query,
|
|
||||||
}
|
|
||||||
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"#;
|
|
||||||
let handler = Object {};
|
|
||||||
let obj = handler.render(&t).unwrap();
|
|
||||||
let actual = obj.to_file_string().unwrap();
|
|
||||||
|
|
||||||
assert_eq!(actual, expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn can_render_query_container() {
|
|
||||||
let t: FullType = FullType {
|
|
||||||
kind: Some(__TypeKind::OBJECT),
|
|
||||||
name: Some("Query".into()),
|
|
||||||
description: None,
|
|
||||||
fields: Some(vec![FullTypeFields {
|
|
||||||
name: Some("container".into()),
|
|
||||||
description: Some("Loads a container from ID.\nNull ID returns an empty container (scratch).\nOptional platform argument initializes new containers to execute and publish as that platform. Platform defaults to that of the builder's host.".into()),
|
|
||||||
args: Some(
|
|
||||||
vec![
|
|
||||||
Some(
|
|
||||||
FullTypeFieldsArgs
|
|
||||||
{
|
|
||||||
input_value: InputValue { name: "id".into(), description: None, type_: TypeRef { kind: Some(__TypeKind::SCALAR), name: Some("ContainerID".into()), of_type: None }, default_value: None }
|
|
||||||
}
|
|
||||||
),
|
|
||||||
Some(
|
|
||||||
FullTypeFieldsArgs {
|
|
||||||
input_value: InputValue {
|
|
||||||
name: "platform".into(), description: None, type_: TypeRef { kind: Some(__TypeKind::SCALAR), name: Some("Platform".into()), of_type: None },
|
|
||||||
default_value: None }
|
|
||||||
})
|
|
||||||
]),
|
|
||||||
type_: Some(FullTypeFieldsType {
|
|
||||||
type_ref: TypeRef {
|
|
||||||
kind: Some(__TypeKind::NON_NULL),
|
|
||||||
name: None,
|
|
||||||
of_type: Some(Box::new(TypeRef {
|
|
||||||
kind: Some(__TypeKind::SCALAR),
|
|
||||||
name: Some("CacheID".into()),
|
|
||||||
of_type: None,
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
is_deprecated: Some(false),
|
|
||||||
deprecation_reason: None,
|
|
||||||
}
|
|
||||||
]),
|
|
||||||
input_fields: None,
|
|
||||||
interfaces: None,
|
|
||||||
enum_values: None,
|
|
||||||
possible_types: None,
|
|
||||||
};
|
|
||||||
let expected = r#"
|
|
||||||
"#;
|
|
||||||
let handler = Object {};
|
|
||||||
let obj = handler.render(&t).unwrap();
|
|
||||||
let actual = obj.to_file_string().unwrap();
|
|
||||||
|
|
||||||
assert_eq!(actual, expected);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,42 +0,0 @@
|
|||||||
use dagger_core::introspection::FullType;
|
|
||||||
use genco::{prelude::rust, quote};
|
|
||||||
|
|
||||||
use crate::predicates::is_custom_scalar_type;
|
|
||||||
|
|
||||||
use super::{utility::render_description, Handler};
|
|
||||||
|
|
||||||
pub struct Scalar;
|
|
||||||
|
|
||||||
impl Handler for Scalar {
|
|
||||||
fn predicate(&self, t: &FullType) -> bool {
|
|
||||||
is_custom_scalar_type(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render(&self, t: &FullType) -> eyre::Result<rust::Tokens> {
|
|
||||||
let mut out = rust::Tokens::new();
|
|
||||||
|
|
||||||
let description =
|
|
||||||
render_description(t).ok_or(eyre::anyhow!("could not find description"))?;
|
|
||||||
let tstruct = self.render_struct(t)?;
|
|
||||||
|
|
||||||
out.append(description);
|
|
||||||
out.push();
|
|
||||||
out.append(tstruct);
|
|
||||||
|
|
||||||
Ok(out)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_struct(&self, t: &FullType) -> eyre::Result<genco::prelude::rust::Tokens> {
|
|
||||||
let name = t.name.as_ref().ok_or(eyre::anyhow!("name not found"))?;
|
|
||||||
|
|
||||||
let scalar = rust::import("dagger_core", "Scalar");
|
|
||||||
|
|
||||||
Ok(quote! {
|
|
||||||
pub struct $name($scalar);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_impl(&self, _t: &FullType) -> eyre::Result<genco::prelude::rust::Tokens> {
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,97 +0,0 @@
|
|||||||
use dagger_core::introspection::TypeRef;
|
|
||||||
use genco::prelude::rust;
|
|
||||||
use genco::prelude::*;
|
|
||||||
|
|
||||||
use crate::predicates::{
|
|
||||||
is_custom_scalar_type_ref, is_list_type, is_required_type_ref, is_scalar_type_ref,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn render_type_ref(inner: &TypeRef) -> eyre::Result<rust::Tokens> {
|
|
||||||
let extract_of_type = |t: &TypeRef| -> Option<TypeRef> {
|
|
||||||
return t.clone().of_type.map(|t| *t);
|
|
||||||
};
|
|
||||||
|
|
||||||
let (optional, inner) = if !is_required_type_ref(inner) {
|
|
||||||
(true, inner.clone())
|
|
||||||
} else {
|
|
||||||
(false, extract_of_type(inner).unwrap())
|
|
||||||
};
|
|
||||||
|
|
||||||
if is_list_type(&inner) {
|
|
||||||
if let Some(inner_of_type) = extract_of_type(&inner) {
|
|
||||||
let inner_field = render_type_ref(&inner_of_type)?;
|
|
||||||
if optional {
|
|
||||||
return Ok(quote! {
|
|
||||||
Option<Vec<$inner_field>>
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return Ok(quote! {
|
|
||||||
Vec<$inner_field>
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if is_custom_scalar_type_ref(&inner) {
|
|
||||||
if let Some(inner_of_type) = extract_of_type(&inner) {
|
|
||||||
let inner_field = render_type_ref(&inner_of_type)?;
|
|
||||||
if optional {
|
|
||||||
return Ok(quote! {
|
|
||||||
Option<$inner_field>
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return Ok(quote! {
|
|
||||||
$inner_field
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if is_scalar_type_ref(&inner) {
|
|
||||||
let name = match inner.name.as_ref().map(|s| s.as_str()) {
|
|
||||||
Some("ID") => "ID",
|
|
||||||
Some("Int") => "Int",
|
|
||||||
Some("String") => "String",
|
|
||||||
Some("Float") => "Float",
|
|
||||||
Some("Boolean") => "Boolean",
|
|
||||||
Some("Date") => "Date",
|
|
||||||
Some("DateTime") => "DateTime",
|
|
||||||
Some("Time") => "Time",
|
|
||||||
Some("Decimal") => "Decimal",
|
|
||||||
Some(n) => n,
|
|
||||||
_ => eyre::bail!("missing type in the end of chain"),
|
|
||||||
};
|
|
||||||
|
|
||||||
if optional {
|
|
||||||
return Ok(quote! {
|
|
||||||
Option<$name>
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return Ok(quote! {
|
|
||||||
$name
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(inner_type) = inner.of_type.as_ref() {
|
|
||||||
let inner_field = render_type_ref(&inner_type)?;
|
|
||||||
if optional {
|
|
||||||
return Ok(quote! {
|
|
||||||
Option<$inner_field>
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return Ok(inner_field);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(name) = inner.name.as_ref() {
|
|
||||||
if optional {
|
|
||||||
return Ok(quote! {
|
|
||||||
Option<$name>
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return Ok(quote! {
|
|
||||||
$name
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
eyre::bail!("could not determine type")
|
|
||||||
}
|
|
@ -1,56 +0,0 @@
|
|||||||
use dagger_core::introspection::{FullType, FullTypeFields, InputValue};
|
|
||||||
use genco::{prelude::*, quote};
|
|
||||||
|
|
||||||
pub fn render_description(t: &FullType) -> Option<rust::Tokens> {
|
|
||||||
if let Some(description) = t.description.as_ref() {
|
|
||||||
let lines = description.split('\n');
|
|
||||||
let output: rust::Tokens = quote! {
|
|
||||||
$(for line in lines => $(format!("\n/// {line}")))
|
|
||||||
};
|
|
||||||
|
|
||||||
return Some(output);
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn render_description_from_field(t: &FullTypeFields) -> Option<rust::Tokens> {
|
|
||||||
if let Some(description) = t.description.as_ref() {
|
|
||||||
let lines = description.split('\n');
|
|
||||||
let output: rust::Tokens = quote! {
|
|
||||||
$(for line in lines => $(format!("\n/// {line}")))
|
|
||||||
};
|
|
||||||
|
|
||||||
return Some(output);
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn render_description_from_input_value(t: &InputValue, name: &String) -> Option<rust::Tokens> {
|
|
||||||
if let Some(description) = t.description.as_ref() {
|
|
||||||
if description == "" {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let lines = description.split('\n').collect::<Vec<&str>>();
|
|
||||||
let mut output = rust::Tokens::new();
|
|
||||||
|
|
||||||
if let Some(line) = lines.first() {
|
|
||||||
output.append(quote! {
|
|
||||||
$(format!("/// * `{name}` - {line}"))
|
|
||||||
});
|
|
||||||
output.push();
|
|
||||||
}
|
|
||||||
|
|
||||||
for line in lines.iter().skip(1) {
|
|
||||||
output.append(quote! {
|
|
||||||
$(format!("/// {line}"))
|
|
||||||
});
|
|
||||||
output.push();
|
|
||||||
}
|
|
||||||
|
|
||||||
return Some(output);
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
@ -1,4 +1,25 @@
|
|||||||
pub mod codegen;
|
mod functions;
|
||||||
mod handlers;
|
mod generator;
|
||||||
mod models;
|
pub mod rust;
|
||||||
mod predicates;
|
pub mod utility;
|
||||||
|
mod visitor;
|
||||||
|
|
||||||
|
use dagger_core::introspection::Schema;
|
||||||
|
|
||||||
|
use self::generator::DynGenerator;
|
||||||
|
|
||||||
|
fn set_schema_parents(mut schema: Schema) -> Schema {
|
||||||
|
for t in schema.types.as_mut().into_iter().flatten().flatten() {
|
||||||
|
let t_parent = t.full_type.clone();
|
||||||
|
for mut field in t.full_type.fields.as_mut().into_iter().flatten() {
|
||||||
|
field.parent_type = Some(t_parent.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return schema;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate(schema: Schema, generator: DynGenerator) -> eyre::Result<String> {
|
||||||
|
let schema = set_schema_parents(schema);
|
||||||
|
generator.generate(schema)
|
||||||
|
}
|
||||||
|
@ -1,11 +0,0 @@
|
|||||||
pub enum Scalars {
|
|
||||||
ID(String),
|
|
||||||
Int(usize),
|
|
||||||
String(String),
|
|
||||||
Float(f64),
|
|
||||||
Boolean(bool),
|
|
||||||
Date(String),
|
|
||||||
DateTime(String),
|
|
||||||
Time(String),
|
|
||||||
Decimal(f64),
|
|
||||||
}
|
|
@ -1,101 +0,0 @@
|
|||||||
use dagger_core::introspection::{FullType, FullTypeInputFields, TypeRef, __TypeKind};
|
|
||||||
|
|
||||||
use crate::models::Scalars;
|
|
||||||
|
|
||||||
pub fn is_scalar_type(t: &FullType) -> bool {
|
|
||||||
if let Some(__TypeKind::SCALAR) = t.kind {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_scalar_type_ref(t: &TypeRef) -> bool {
|
|
||||||
if let Some(__TypeKind::SCALAR) = t.kind {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_enum_type(t: &FullType) -> bool {
|
|
||||||
if let Some(__TypeKind::ENUM) = t.kind {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_input_object_type(t: &FullType) -> bool {
|
|
||||||
if let Some(__TypeKind::INPUT_OBJECT) = t.kind {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_required_type(t: &FullTypeInputFields) -> bool {
|
|
||||||
match t.input_value.type_.kind {
|
|
||||||
Some(__TypeKind::NON_NULL) => return true,
|
|
||||||
Some(_) => return false,
|
|
||||||
_ => return false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_required_type_ref(t: &TypeRef) -> bool {
|
|
||||||
match t.kind {
|
|
||||||
Some(__TypeKind::NON_NULL) => return true,
|
|
||||||
Some(_) => return false,
|
|
||||||
_ => return false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_list_type(t: &TypeRef) -> bool {
|
|
||||||
if let Some(__TypeKind::LIST) = t.kind {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_object_type(t: &FullType) -> bool {
|
|
||||||
if let Some(__TypeKind::OBJECT) = t.kind {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_custom_scalar_type(t: &FullType) -> bool {
|
|
||||||
if is_scalar_type(t) {
|
|
||||||
// TODO: Insert scalar
|
|
||||||
let _ = match t.name.as_ref().map(|s| s.as_str()) {
|
|
||||||
Some("ID") => Scalars::ID("ID".into()),
|
|
||||||
Some("Int") => Scalars::Int(0),
|
|
||||||
Some("String") => Scalars::String("ID".into()),
|
|
||||||
Some("Float") => Scalars::Float(0.0),
|
|
||||||
Some("Boolean") => Scalars::Boolean(false),
|
|
||||||
Some("Date") => Scalars::Date("ID".into()),
|
|
||||||
Some("DateTime") => Scalars::DateTime("ID".into()),
|
|
||||||
Some("Time") => Scalars::Time("ID".into()),
|
|
||||||
Some("Decimal") => Scalars::Decimal(0.0),
|
|
||||||
Some(_) => return true,
|
|
||||||
None => return false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_custom_scalar_type_ref(t: &TypeRef) -> bool {
|
|
||||||
if is_scalar_type_ref(t) {
|
|
||||||
// TODO: Insert scalar
|
|
||||||
let _ = match t.name.as_ref().map(|s| s.as_str()) {
|
|
||||||
Some("ID") => Scalars::ID("ID".into()),
|
|
||||||
Some("Int") => Scalars::Int(0),
|
|
||||||
Some("String") => Scalars::String("ID".into()),
|
|
||||||
Some("Float") => Scalars::Float(0.0),
|
|
||||||
Some("Boolean") => Scalars::Boolean(false),
|
|
||||||
Some("Date") => Scalars::Date("ID".into()),
|
|
||||||
Some("DateTime") => Scalars::DateTime("ID".into()),
|
|
||||||
Some("Time") => Scalars::Time("ID".into()),
|
|
||||||
Some("Decimal") => Scalars::Decimal(0.0),
|
|
||||||
Some(_) => return true,
|
|
||||||
None => return false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
|
64
crates/dagger-codegen/src/rust/format.rs
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
use crate::functions::FormatTypeFuncs;
|
||||||
|
|
||||||
|
use super::functions::format_name;
|
||||||
|
|
||||||
|
pub struct FormatTypeFunc;
|
||||||
|
|
||||||
|
impl FormatTypeFuncs for FormatTypeFunc {
|
||||||
|
fn format_kind_list(&self, representation: &str) -> String {
|
||||||
|
format!("Vec<{}>", representation)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn format_kind_scalar_string(&self, representation: &str) -> String {
|
||||||
|
let mut rep = representation.to_string();
|
||||||
|
rep.push_str("String");
|
||||||
|
rep
|
||||||
|
}
|
||||||
|
|
||||||
|
fn format_kind_scalar_int(&self, representation: &str) -> String {
|
||||||
|
let mut rep = representation.to_string();
|
||||||
|
rep.push_str("isize");
|
||||||
|
rep
|
||||||
|
}
|
||||||
|
|
||||||
|
fn format_kind_scalar_float(&self, representation: &str) -> String {
|
||||||
|
let mut rep = representation.to_string();
|
||||||
|
rep.push_str("float");
|
||||||
|
rep
|
||||||
|
}
|
||||||
|
|
||||||
|
fn format_kind_scalar_boolean(&self, representation: &str) -> String {
|
||||||
|
let mut rep = representation.to_string();
|
||||||
|
rep.push_str("bool");
|
||||||
|
rep
|
||||||
|
}
|
||||||
|
|
||||||
|
fn format_kind_scalar_default(
|
||||||
|
&self,
|
||||||
|
representation: &str,
|
||||||
|
ref_name: &str,
|
||||||
|
_input: bool,
|
||||||
|
) -> String {
|
||||||
|
let mut rep = representation.to_string();
|
||||||
|
rep.push_str(&format_name(ref_name));
|
||||||
|
rep
|
||||||
|
}
|
||||||
|
|
||||||
|
fn format_kind_object(&self, representation: &str, ref_name: &str) -> String {
|
||||||
|
let mut rep = representation.to_string();
|
||||||
|
rep.push_str(&format_name(ref_name));
|
||||||
|
rep
|
||||||
|
}
|
||||||
|
|
||||||
|
fn format_kind_input_object(&self, representation: &str, ref_name: &str) -> String {
|
||||||
|
let mut rep = representation.to_string();
|
||||||
|
rep.push_str(&format_name(ref_name));
|
||||||
|
rep
|
||||||
|
}
|
||||||
|
|
||||||
|
fn format_kind_enum(&self, representation: &str, ref_name: &str) -> String {
|
||||||
|
let mut rep = representation.to_string();
|
||||||
|
rep.push_str(&format_name(ref_name));
|
||||||
|
rep
|
||||||
|
}
|
||||||
|
}
|
206
crates/dagger-codegen/src/rust/functions.rs
Normal file
@ -0,0 +1,206 @@
|
|||||||
|
use convert_case::{Case, Casing};
|
||||||
|
use dagger_core::introspection::FullTypeFields;
|
||||||
|
use genco::prelude::rust;
|
||||||
|
use genco::quote;
|
||||||
|
use genco::tokens::quoted;
|
||||||
|
|
||||||
|
use crate::functions::{
|
||||||
|
type_field_has_optional, type_ref_is_list_of_objects, type_ref_is_object,
|
||||||
|
type_ref_is_optional, CommonFunctions,
|
||||||
|
};
|
||||||
|
use crate::utility::OptionExt;
|
||||||
|
|
||||||
|
pub fn format_name(s: &str) -> String {
|
||||||
|
s.to_case(Case::Pascal)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn format_struct_name(s: &str) -> String {
|
||||||
|
s.to_case(Case::Snake)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn field_options_struct_name(field: &FullTypeFields) -> Option<String> {
|
||||||
|
field
|
||||||
|
.parent_type
|
||||||
|
.as_ref()
|
||||||
|
.map(|p| p.name.as_ref().map(|n| format_name(n)))
|
||||||
|
.flatten()
|
||||||
|
.zip(field.name.as_ref().map(|n| format_name(n)))
|
||||||
|
.map(|(parent_name, field_name)| format!("{parent_name}{field_name}Opts"))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn format_function(funcs: &CommonFunctions, field: &FullTypeFields) -> Option<rust::Tokens> {
|
||||||
|
let signature = quote! {
|
||||||
|
pub fn $(field.name.pipe(|n | format_struct_name(n)))
|
||||||
|
};
|
||||||
|
let args = format_function_args(funcs, field);
|
||||||
|
|
||||||
|
let output_type = field
|
||||||
|
.type_
|
||||||
|
.pipe(|t| &t.type_ref)
|
||||||
|
.pipe(|t| funcs.format_output_type(t));
|
||||||
|
|
||||||
|
Some(quote! {
|
||||||
|
$(signature)(
|
||||||
|
$(args)
|
||||||
|
) -> $(output_type) {
|
||||||
|
let mut query = self.selection.select($(quoted(field.name.as_ref())));
|
||||||
|
|
||||||
|
$(render_required_args(funcs, field))
|
||||||
|
$(render_optional_args(funcs, field))
|
||||||
|
|
||||||
|
$(render_execution(funcs, field))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_required_args(_funcs: &CommonFunctions, field: &FullTypeFields) -> Option<rust::Tokens> {
|
||||||
|
if let Some(args) = field.args.as_ref() {
|
||||||
|
let args = args
|
||||||
|
.into_iter()
|
||||||
|
.map(|a| {
|
||||||
|
a.as_ref().and_then(|s| {
|
||||||
|
if type_ref_is_optional(&s.input_value.type_) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let n = format_struct_name(&s.input_value.name);
|
||||||
|
let name = &s.input_value.name;
|
||||||
|
|
||||||
|
Some(quote! {
|
||||||
|
query = query.arg($(quoted(name)), $(n)).unwrap();
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.flatten()
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let required_args = quote! {
|
||||||
|
$(for arg in args join ($['\r']) => $arg)
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(required_args)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_optional_args(_funcs: &CommonFunctions, field: &FullTypeFields) -> Option<rust::Tokens> {
|
||||||
|
if let Some(args) = field.args.as_ref() {
|
||||||
|
let args = args
|
||||||
|
.into_iter()
|
||||||
|
.map(|a| {
|
||||||
|
a.as_ref().and_then(|s| {
|
||||||
|
if !type_ref_is_optional(&s.input_value.type_) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let n = format_struct_name(&s.input_value.name);
|
||||||
|
let name = &s.input_value.name;
|
||||||
|
|
||||||
|
Some(quote! {
|
||||||
|
if let Some($(&n)) = opts.$(&n) {
|
||||||
|
query = query.arg($(quoted(name)), $(&n)).unwrap();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.flatten()
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
if args.len() == 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let required_args = quote! {
|
||||||
|
if let Some(opts) = opts {
|
||||||
|
$(for arg in args join ($['\r']) => $arg)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(required_args)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_execution(funcs: &CommonFunctions, field: &FullTypeFields) -> rust::Tokens {
|
||||||
|
if let Some(true) = field.type_.pipe(|t| type_ref_is_object(&t.type_ref)) {
|
||||||
|
let output_type = funcs.format_output_type(&field.type_.as_ref().unwrap().type_ref);
|
||||||
|
return quote! {
|
||||||
|
return $(output_type) {
|
||||||
|
proc: self.proc.clone(),
|
||||||
|
selection: query,
|
||||||
|
conn: self.conn.clone(),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(true) = field
|
||||||
|
.type_
|
||||||
|
.pipe(|t| type_ref_is_list_of_objects(&t.type_ref))
|
||||||
|
{
|
||||||
|
let output_type = funcs.format_output_type(
|
||||||
|
&field
|
||||||
|
.type_
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.type_ref
|
||||||
|
.of_type
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.of_type
|
||||||
|
.as_ref()
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
return quote! {
|
||||||
|
return vec![$(output_type) {
|
||||||
|
proc: self.proc.clone(),
|
||||||
|
selection: query,
|
||||||
|
conn: self.conn.clone(),
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let graphql_client = rust::import("crate::client", "graphql_client");
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
query.execute(&$graphql_client(&self.conn)).unwrap().unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn format_function_args(funcs: &CommonFunctions, field: &FullTypeFields) -> Option<rust::Tokens> {
|
||||||
|
if let Some(args) = field.args.as_ref() {
|
||||||
|
let args = args
|
||||||
|
.into_iter()
|
||||||
|
.map(|a| {
|
||||||
|
a.as_ref().and_then(|s| {
|
||||||
|
if type_ref_is_optional(&s.input_value.type_) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let t = funcs.format_input_type(&s.input_value.type_);
|
||||||
|
let n = format_struct_name(&s.input_value.name);
|
||||||
|
|
||||||
|
Some(quote! {
|
||||||
|
$(n): $(t),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.flatten()
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let required_args = quote! {
|
||||||
|
&self,
|
||||||
|
$(for arg in args join ($['\r']) => $arg)
|
||||||
|
};
|
||||||
|
|
||||||
|
if type_field_has_optional(field) {
|
||||||
|
Some(quote! {
|
||||||
|
$(required_args)
|
||||||
|
opts: Option<$(field_options_struct_name(field))>
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Some(required_args)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
114
crates/dagger-codegen/src/rust/mod.rs
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
pub mod format;
|
||||||
|
mod functions;
|
||||||
|
pub mod templates;
|
||||||
|
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
use dagger_core::introspection::Schema;
|
||||||
|
use eyre::Context;
|
||||||
|
use genco::prelude::rust;
|
||||||
|
|
||||||
|
use crate::functions::CommonFunctions;
|
||||||
|
use crate::generator::Generator;
|
||||||
|
use crate::visitor::{VisitHandlers, Visitor};
|
||||||
|
|
||||||
|
use self::format::FormatTypeFunc;
|
||||||
|
use self::templates::enum_tmpl::render_enum;
|
||||||
|
use self::templates::input_tmpl::render_input;
|
||||||
|
use self::templates::object_tmpl::render_object;
|
||||||
|
use self::templates::scalar_tmpl::render_scalar;
|
||||||
|
|
||||||
|
pub struct RustGenerator {}
|
||||||
|
|
||||||
|
impl Generator for RustGenerator {
|
||||||
|
fn generate(&self, schema: Schema) -> eyre::Result<String> {
|
||||||
|
let render = Arc::new(Mutex::new(rust::Tokens::new()));
|
||||||
|
let common_funcs = Arc::new(CommonFunctions::new(Arc::new(FormatTypeFunc {})));
|
||||||
|
println!("generating dagger for rust");
|
||||||
|
|
||||||
|
let visitor = Visitor {
|
||||||
|
schema,
|
||||||
|
handlers: VisitHandlers {
|
||||||
|
visit_scalar: Arc::new({
|
||||||
|
let render = render.clone();
|
||||||
|
let _common_funcs = common_funcs.clone();
|
||||||
|
|
||||||
|
move |t| {
|
||||||
|
println!("generating scalar");
|
||||||
|
let rendered_scalar = render_scalar(t)?;
|
||||||
|
|
||||||
|
let mut render = render.lock().unwrap();
|
||||||
|
|
||||||
|
render.append(rendered_scalar);
|
||||||
|
render.push();
|
||||||
|
|
||||||
|
println!("generated scalar");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
visit_object: Arc::new({
|
||||||
|
let render = render.clone();
|
||||||
|
let common_funcs = common_funcs.clone();
|
||||||
|
|
||||||
|
move |t| {
|
||||||
|
println!("generating object");
|
||||||
|
let rendered_scalar = render_object(&common_funcs, t)?;
|
||||||
|
|
||||||
|
let mut render = render.lock().unwrap();
|
||||||
|
|
||||||
|
render.append(rendered_scalar);
|
||||||
|
render.push();
|
||||||
|
println!("generated object");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
visit_input: Arc::new({
|
||||||
|
let render = render.clone();
|
||||||
|
let common_funcs = common_funcs.clone();
|
||||||
|
|
||||||
|
move |t| {
|
||||||
|
println!("generating input");
|
||||||
|
let rendered_scalar = render_input(&common_funcs, t)?;
|
||||||
|
|
||||||
|
let mut render = render.lock().unwrap();
|
||||||
|
|
||||||
|
render.append(rendered_scalar);
|
||||||
|
render.push();
|
||||||
|
println!("generated input");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
visit_enum: Arc::new({
|
||||||
|
let render = render.clone();
|
||||||
|
let _common_funcs = common_funcs.clone();
|
||||||
|
|
||||||
|
move |t| {
|
||||||
|
println!("generating enum");
|
||||||
|
let rendered_scalar = render_enum(t)?;
|
||||||
|
|
||||||
|
let mut render = render.lock().unwrap();
|
||||||
|
|
||||||
|
render.append(rendered_scalar);
|
||||||
|
render.push();
|
||||||
|
println!("generated enum");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
visitor.run()?;
|
||||||
|
|
||||||
|
println!("done generating objects");
|
||||||
|
|
||||||
|
let rendered = render.lock().unwrap();
|
||||||
|
|
||||||
|
rendered
|
||||||
|
.to_file_string()
|
||||||
|
.context("could not render to file string")
|
||||||
|
}
|
||||||
|
}
|
33
crates/dagger-codegen/src/rust/templates/enum_tmpl.rs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
use dagger_core::introspection::FullType;
|
||||||
|
use genco::prelude::rust;
|
||||||
|
use genco::quote;
|
||||||
|
|
||||||
|
fn render_enum_values(values: &FullType) -> Option<rust::Tokens> {
|
||||||
|
let values = values
|
||||||
|
.enum_values
|
||||||
|
.as_ref()
|
||||||
|
.into_iter()
|
||||||
|
.map(|values| {
|
||||||
|
values
|
||||||
|
.into_iter()
|
||||||
|
.map(|val| quote! { $(val.name.as_ref()) })
|
||||||
|
})
|
||||||
|
.flatten()
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let mut tokens = rust::Tokens::new();
|
||||||
|
for val in values {
|
||||||
|
tokens.append(val);
|
||||||
|
tokens.push();
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(tokens)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render_enum(t: &FullType) -> eyre::Result<rust::Tokens> {
|
||||||
|
Ok(quote! {
|
||||||
|
pub enum $(t.name.as_ref()) {
|
||||||
|
$(render_enum_values(t))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
38
crates/dagger-codegen/src/rust/templates/input_tmpl.rs
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
use dagger_core::introspection::{FullType, FullTypeInputFields};
|
||||||
|
use genco::prelude::rust;
|
||||||
|
use genco::quote;
|
||||||
|
|
||||||
|
use crate::functions::CommonFunctions;
|
||||||
|
use crate::rust::functions::{format_name, format_struct_name};
|
||||||
|
|
||||||
|
pub fn render_input(funcs: &CommonFunctions, t: &FullType) -> eyre::Result<rust::Tokens> {
|
||||||
|
let deserialize = rust::import("serde", "Deserialize");
|
||||||
|
let serialize = rust::import("serde", "Serialize");
|
||||||
|
Ok(quote! {
|
||||||
|
#[derive($serialize, $deserialize)]
|
||||||
|
pub struct $(format_name(t.name.as_ref().unwrap())) {
|
||||||
|
$(render_input_fields(funcs, t.input_fields.as_ref().unwrap_or(&Vec::new()) ))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render_input_fields(
|
||||||
|
funcs: &CommonFunctions,
|
||||||
|
fields: &[FullTypeInputFields],
|
||||||
|
) -> Option<rust::Tokens> {
|
||||||
|
let rendered_fields = fields.iter().map(|f| render_input_field(funcs, f));
|
||||||
|
|
||||||
|
if rendered_fields.len() == 0 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(quote! {
|
||||||
|
$(for field in rendered_fields join ($['\r']) => $field)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render_input_field(funcs: &CommonFunctions, field: &FullTypeInputFields) -> rust::Tokens {
|
||||||
|
quote! {
|
||||||
|
pub $(format_struct_name(&field.input_value.name)): $(funcs.format_input_type(&field.input_value.type_)),
|
||||||
|
}
|
||||||
|
}
|
4
crates/dagger-codegen/src/rust/templates/mod.rs
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
pub mod enum_tmpl;
|
||||||
|
pub mod input_tmpl;
|
||||||
|
pub mod object_tmpl;
|
||||||
|
pub mod scalar_tmpl;
|
112
crates/dagger-codegen/src/rust/templates/object_tmpl.rs
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
use dagger_core::introspection::{FullType, FullTypeFields, FullTypeFieldsArgs};
|
||||||
|
use genco::prelude::rust;
|
||||||
|
use genco::quote;
|
||||||
|
|
||||||
|
use crate::functions::{type_ref_is_optional, CommonFunctions};
|
||||||
|
use crate::rust::functions::{
|
||||||
|
field_options_struct_name, format_function, format_name, format_struct_name,
|
||||||
|
};
|
||||||
|
use crate::utility::OptionExt;
|
||||||
|
|
||||||
|
pub fn render_object(funcs: &CommonFunctions, t: &FullType) -> eyre::Result<rust::Tokens> {
|
||||||
|
let selection = rust::import("crate::querybuilder", "Selection");
|
||||||
|
let child = rust::import("std::process", "Child");
|
||||||
|
let conn = rust::import("dagger_core::connect_params", "ConnectParams");
|
||||||
|
let arc = rust::import("std::sync", "Arc");
|
||||||
|
|
||||||
|
Ok(quote! {
|
||||||
|
pub struct $(t.name.pipe(|s| format_name(s))) {
|
||||||
|
pub proc: $arc<$child>,
|
||||||
|
pub selection: $selection,
|
||||||
|
pub conn: $conn,
|
||||||
|
}
|
||||||
|
|
||||||
|
$(t.fields.pipe(|f| render_optional_args(funcs, f)))
|
||||||
|
|
||||||
|
impl $(t.name.pipe(|s| format_name(s))) {
|
||||||
|
$(t.fields.pipe(|f| render_functions(funcs, f)))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_optional_args(
|
||||||
|
funcs: &CommonFunctions,
|
||||||
|
fields: &Vec<FullTypeFields>,
|
||||||
|
) -> Option<rust::Tokens> {
|
||||||
|
let rendered_fields = fields
|
||||||
|
.iter()
|
||||||
|
.map(|f| render_optional_arg(funcs, f))
|
||||||
|
.flatten()
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
if rendered_fields.len() == 0 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(quote! {
|
||||||
|
$(for field in rendered_fields join ($['\r']) => $field)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_optional_arg(funcs: &CommonFunctions, field: &FullTypeFields) -> Option<rust::Tokens> {
|
||||||
|
let output_type = field_options_struct_name(field);
|
||||||
|
let fields = field
|
||||||
|
.args
|
||||||
|
.pipe(|t| t.into_iter().flatten().collect::<Vec<_>>())
|
||||||
|
.map(|t| {
|
||||||
|
t.into_iter()
|
||||||
|
.filter(|t| type_ref_is_optional(&t.input_value.type_))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
})
|
||||||
|
.pipe(|t| render_optional_field_args(funcs, t))
|
||||||
|
.flatten();
|
||||||
|
|
||||||
|
if let Some(fields) = fields {
|
||||||
|
Some(quote! {
|
||||||
|
pub struct $output_type {
|
||||||
|
$fields
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_optional_field_args(
|
||||||
|
funcs: &CommonFunctions,
|
||||||
|
args: &Vec<&FullTypeFieldsArgs>,
|
||||||
|
) -> Option<rust::Tokens> {
|
||||||
|
if args.len() == 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let rendered_args = args.into_iter().map(|a| &a.input_value).map(|a| {
|
||||||
|
quote! {
|
||||||
|
pub $(format_struct_name(&a.name)): Option<$(funcs.format_output_type(&a.type_))>,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Some(quote! {
|
||||||
|
$(for arg in rendered_args join ($['\r']) => $arg)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_functions(funcs: &CommonFunctions, fields: &Vec<FullTypeFields>) -> Option<rust::Tokens> {
|
||||||
|
let rendered_functions = fields
|
||||||
|
.iter()
|
||||||
|
.map(|f| render_function(funcs, f))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
if rendered_functions.len() > 0 {
|
||||||
|
Some(quote! {
|
||||||
|
$(for func in rendered_functions join ($['\r']) => $func)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_function(funcs: &CommonFunctions, field: &FullTypeFields) -> Option<rust::Tokens> {
|
||||||
|
Some(quote! {
|
||||||
|
$(format_function(funcs, field))
|
||||||
|
})
|
||||||
|
}
|
16
crates/dagger-codegen/src/rust/templates/scalar_tmpl.rs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
use dagger_core::introspection::FullType;
|
||||||
|
use genco::prelude::rust;
|
||||||
|
use genco::quote;
|
||||||
|
|
||||||
|
use crate::rust::functions::format_name;
|
||||||
|
use crate::utility::OptionExt;
|
||||||
|
|
||||||
|
pub fn render_scalar(t: &FullType) -> eyre::Result<rust::Tokens> {
|
||||||
|
let deserialize = rust::import("serde", "Deserialize");
|
||||||
|
let serialize = rust::import("serde", "Serialize");
|
||||||
|
|
||||||
|
Ok(quote! {
|
||||||
|
#[derive($serialize, $deserialize)]
|
||||||
|
pub struct $(t.name.pipe(|n|format_name(n)))(String);
|
||||||
|
})
|
||||||
|
}
|
18
crates/dagger-codegen/src/utility.rs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
pub trait OptionExt<'t, T: 't> {
|
||||||
|
fn pipe<U, F>(&'t self, f: F) -> Option<U>
|
||||||
|
where
|
||||||
|
F: FnOnce(&'t T) -> U;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'t, T: 't> OptionExt<'t, T> for Option<T> {
|
||||||
|
#[inline]
|
||||||
|
fn pipe<U, F>(&'t self, f: F) -> Option<U>
|
||||||
|
where
|
||||||
|
F: FnOnce(&'t T) -> U,
|
||||||
|
{
|
||||||
|
match *self {
|
||||||
|
Some(ref x) => Some(f(x)),
|
||||||
|
None => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
106
crates/dagger-codegen/src/visitor.rs
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use dagger_core::introspection::{FullType, Schema, __TypeKind};
|
||||||
|
use itertools::Itertools;
|
||||||
|
|
||||||
|
pub struct Visitor {
|
||||||
|
pub schema: Schema,
|
||||||
|
pub handlers: VisitHandlers,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type VisitFunc = Arc<dyn Fn(&FullType) -> eyre::Result<()>>;
|
||||||
|
|
||||||
|
pub struct VisitHandlers {
|
||||||
|
pub visit_scalar: VisitFunc,
|
||||||
|
pub visit_object: VisitFunc,
|
||||||
|
pub visit_input: VisitFunc,
|
||||||
|
pub visit_enum: VisitFunc,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SequenceItem {
|
||||||
|
kind: __TypeKind,
|
||||||
|
handler: VisitFunc,
|
||||||
|
ignore: Option<Vec<String>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Visitor {
|
||||||
|
pub fn run(&self) -> eyre::Result<()> {
|
||||||
|
let sequence = vec![
|
||||||
|
SequenceItem {
|
||||||
|
kind: __TypeKind::SCALAR,
|
||||||
|
handler: self.handlers.visit_scalar.clone(),
|
||||||
|
ignore: Some(vec![
|
||||||
|
"String".into(),
|
||||||
|
"Float".into(),
|
||||||
|
"Int".into(),
|
||||||
|
"Boolean".into(),
|
||||||
|
"DateTime".into(),
|
||||||
|
"ID".into(),
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
SequenceItem {
|
||||||
|
kind: __TypeKind::INPUT_OBJECT,
|
||||||
|
handler: self.handlers.visit_input.clone(),
|
||||||
|
ignore: None,
|
||||||
|
},
|
||||||
|
SequenceItem {
|
||||||
|
kind: __TypeKind::OBJECT,
|
||||||
|
handler: self.handlers.visit_object.clone(),
|
||||||
|
ignore: None,
|
||||||
|
},
|
||||||
|
SequenceItem {
|
||||||
|
kind: __TypeKind::ENUM,
|
||||||
|
handler: self.handlers.visit_enum.clone(),
|
||||||
|
ignore: None,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
for item in sequence {
|
||||||
|
self.visit(&item)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit(&self, item: &SequenceItem) -> eyre::Result<()> {
|
||||||
|
self.schema
|
||||||
|
.types
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.into_iter()
|
||||||
|
.map(|t| t.as_ref().unwrap())
|
||||||
|
.filter(|t| match t.full_type.kind.as_ref().unwrap() == &item.kind {
|
||||||
|
true => match (item.ignore.as_ref(), t.full_type.name.as_ref()) {
|
||||||
|
(Some(ignore), Some(name)) => {
|
||||||
|
if name.starts_with("__") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ignore.contains(name) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
(None, Some(name)) => {
|
||||||
|
if name.starts_with("__") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
},
|
||||||
|
false => false,
|
||||||
|
})
|
||||||
|
.sorted_by(|a, b| {
|
||||||
|
a.full_type
|
||||||
|
.name
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.cmp(&b.full_type.name.as_ref().unwrap())
|
||||||
|
})
|
||||||
|
.map(|t| (*item.handler)(&t.full_type))
|
||||||
|
.collect::<eyre::Result<Vec<_>>>()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
@ -1,8 +1,12 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "dagger-core"
|
name = "dagger-core"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
readme = "README.md"
|
||||||
|
license-file = "LICENSE.MIT"
|
||||||
|
description = "dagger sdk core library"
|
||||||
|
repository = "https://github.com/kjuulh/dagger-rs"
|
||||||
|
publish = true
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
7
crates/dagger-core/LICENSE.MIT
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
Copyright 2023 Kasper J. Hermansen
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
0
crates/dagger-core/README.md
Normal file
@ -89,17 +89,22 @@ impl InnerCliSession {
|
|||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
let stdout_bufr = BufReader::new(stdout);
|
let stdout_bufr = BufReader::new(stdout);
|
||||||
for line in stdout_bufr.lines() {
|
for line in stdout_bufr.lines() {
|
||||||
let out = line.unwrap();
|
let out = line.as_ref().unwrap();
|
||||||
if let Ok(conn) = serde_json::from_str::<ConnectParams>(&out) {
|
if let Ok(conn) = serde_json::from_str::<ConnectParams>(&out) {
|
||||||
sender.send(conn).unwrap();
|
sender.send(conn).unwrap();
|
||||||
}
|
}
|
||||||
|
if let Ok(line) = line {
|
||||||
|
println!("dagger: {}", line);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
std::thread::spawn(|| {
|
std::thread::spawn(|| {
|
||||||
let stderr_bufr = BufReader::new(stderr);
|
let stderr_bufr = BufReader::new(stderr);
|
||||||
for line in stderr_bufr.lines() {
|
for line in stderr_bufr.lines() {
|
||||||
let out = line.unwrap();
|
if let Ok(line) = line {
|
||||||
|
println!("dagger: {}", line);
|
||||||
|
}
|
||||||
//panic!("could not start dagger session: {}", out)
|
//panic!("could not start dagger session: {}", out)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -161,6 +161,9 @@ pub struct FullTypeFields {
|
|||||||
pub type_: Option<FullTypeFieldsType>,
|
pub type_: Option<FullTypeFieldsType>,
|
||||||
pub is_deprecated: Option<bool>,
|
pub is_deprecated: Option<bool>,
|
||||||
pub deprecation_reason: Option<String>,
|
pub deprecation_reason: Option<String>,
|
||||||
|
|
||||||
|
#[serde(skip)]
|
||||||
|
pub parent_type: Option<FullType>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize)]
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
|
@ -1,16 +1,18 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "dagger-sdk"
|
name = "dagger-sdk"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
license-file = "LICENSE.MIT"
|
license-file = "LICENSE.MIT"
|
||||||
description = "A dagger sdk for rust, written in rust"
|
description = "A dagger sdk for rust, written in rust"
|
||||||
|
repository = "https://github.com/kjuulh/dagger-rs"
|
||||||
|
publish = true
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
base64 = "0.21.0"
|
base64 = "0.21.0"
|
||||||
dagger-core = { path = "../dagger-core" }
|
dagger-core = { path = "../dagger-core", version = "0.2.0" }
|
||||||
eyre = "0.6.8"
|
eyre = "0.6.8"
|
||||||
futures = "0.3.26"
|
futures = "0.3.26"
|
||||||
|
|
||||||
@ -20,3 +22,6 @@ pretty_assertions = "1.3.0"
|
|||||||
serde = { version = "1.0.152", features = ["derive"] }
|
serde = { version = "1.0.152", features = ["derive"] }
|
||||||
serde_json = "1.0.92"
|
serde_json = "1.0.92"
|
||||||
tokio = { version = "1.25.0", features = ["full"] }
|
tokio = { version = "1.25.0", features = ["full"] }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
rand = "0.8.5"
|
||||||
|
@ -2,14 +2,46 @@
|
|||||||
|
|
||||||
A dagger sdk written in rust for rust.
|
A dagger sdk written in rust for rust.
|
||||||
|
|
||||||
## Disclaimer
|
## Examples
|
||||||
|
|
||||||
Work in progress. This is not ready for usage yet
|
See [examples](./examples/)
|
||||||
|
|
||||||
### Status
|
Run them like so
|
||||||
|
|
||||||
- [x] dagger cli downloader
|
```bash
|
||||||
- [x] dagger network session
|
cargo run --example first-pipeline
|
||||||
- [ ] graphql rust codegen (User API)
|
```
|
||||||
- [ ] fix build / release cycle
|
|
||||||
- [ ] general api stabilisation
|
The examples match the folder name in each directory in examples
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
Simply install like:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo install dagger-sdk
|
||||||
|
```
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn main() -> eyre::Result<()> {
|
||||||
|
let client = dagger_sdk::client::connect()?;
|
||||||
|
|
||||||
|
let version = client
|
||||||
|
.container(None)
|
||||||
|
.from("golang:1.19".into())
|
||||||
|
.with_exec(vec!["go".into(), "version".into()], None)
|
||||||
|
.stdout();
|
||||||
|
|
||||||
|
println!("Hello from Dagger and {}", version.trim());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
And run it like a normal application:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo run
|
||||||
|
```
|
||||||
|
25
crates/dagger-sdk/examples/build-the-application/app/.gitignore
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
/.pnp
|
||||||
|
.pnp.js
|
||||||
|
|
||||||
|
# testing
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
# production
|
||||||
|
/build
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
build-*
|
@ -0,0 +1,17 @@
|
|||||||
|
# React Build
|
||||||
|
|
||||||
|
This is based on the [Getting Started guide for Nodejs](https://docs.dagger.io/sdk/nodejs/783645/get-started#step-5-test-against-multiple-nodejs-versions)
|
||||||
|
|
||||||
|
A simple react app is created with `create-react-app` which is built and tested by `build.js` or `build.ts`.
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
`npm install`
|
||||||
|
|
||||||
|
and then:
|
||||||
|
|
||||||
|
`node --loader ts-node/esm ./build.ts`
|
||||||
|
|
||||||
|
or
|
||||||
|
|
||||||
|
`node ./build.js`
|
29700
crates/dagger-sdk/examples/build-the-application/app/package-lock.json
generated
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
{
|
||||||
|
"name": "react-build",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@testing-library/jest-dom": "^5.16.5",
|
||||||
|
"@testing-library/react": "^13.4.0",
|
||||||
|
"@testing-library/user-event": "^13.5.0",
|
||||||
|
"@types/jest": "^27.5.2",
|
||||||
|
"@types/node": "^16.18.6",
|
||||||
|
"@types/react": "^18.0.26",
|
||||||
|
"@types/react-dom": "^18.0.9",
|
||||||
|
"react": "^18.2.0",
|
||||||
|
"react-dom": "^18.2.0",
|
||||||
|
"react-scripts": "5.0.1",
|
||||||
|
"ts-node": "^10.9.1",
|
||||||
|
"typescript": "^4.9.3",
|
||||||
|
"web-vitals": "^2.1.4"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"start": "react-scripts start",
|
||||||
|
"build": "react-scripts build",
|
||||||
|
"test": "react-scripts test",
|
||||||
|
"eject": "react-scripts eject"
|
||||||
|
},
|
||||||
|
"eslintConfig": {
|
||||||
|
"extends": [
|
||||||
|
"react-app",
|
||||||
|
"react-app/jest"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"browserslist": {
|
||||||
|
"production": [
|
||||||
|
">0.2%",
|
||||||
|
"not dead",
|
||||||
|
"not op_mini all"
|
||||||
|
],
|
||||||
|
"development": [
|
||||||
|
"last 1 chrome version",
|
||||||
|
"last 1 firefox version",
|
||||||
|
"last 1 safari version"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"type": "module",
|
||||||
|
"devDependencies": {
|
||||||
|
"@dagger.io/dagger": "^0.3.2"
|
||||||
|
}
|
||||||
|
}
|
After Width: | Height: | Size: 3.8 KiB |
@ -0,0 +1,43 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<meta name="theme-color" content="#000000" />
|
||||||
|
<meta
|
||||||
|
name="description"
|
||||||
|
content="Web site created using create-react-app"
|
||||||
|
/>
|
||||||
|
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||||
|
<!--
|
||||||
|
manifest.json provides metadata used when your web app is installed on a
|
||||||
|
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||||
|
-->
|
||||||
|
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||||
|
<!--
|
||||||
|
Notice the use of %PUBLIC_URL% in the tags above.
|
||||||
|
It will be replaced with the URL of the `public` folder during the build.
|
||||||
|
Only files inside the `public` folder can be referenced from the HTML.
|
||||||
|
|
||||||
|
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||||
|
work correctly both with client-side routing and a non-root public URL.
|
||||||
|
Learn how to configure a non-root public URL by running `npm run build`.
|
||||||
|
-->
|
||||||
|
<title>React App</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
|
<div id="root"></div>
|
||||||
|
<!--
|
||||||
|
This HTML file is a template.
|
||||||
|
If you open it directly in the browser, you will see an empty page.
|
||||||
|
|
||||||
|
You can add webfonts, meta tags, or analytics to this file.
|
||||||
|
The build step will place the bundled scripts into the <body> tag.
|
||||||
|
|
||||||
|
To begin the development, run `npm start` or `yarn start`.
|
||||||
|
To create a production bundle, use `npm run build` or `yarn build`.
|
||||||
|
-->
|
||||||
|
</body>
|
||||||
|
</html>
|
After Width: | Height: | Size: 5.2 KiB |
After Width: | Height: | Size: 9.4 KiB |
@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"short_name": "React App",
|
||||||
|
"name": "Create React App Sample",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "favicon.ico",
|
||||||
|
"sizes": "64x64 32x32 24x24 16x16",
|
||||||
|
"type": "image/x-icon"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "logo192.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "192x192"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "logo512.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "512x512"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"start_url": ".",
|
||||||
|
"display": "standalone",
|
||||||
|
"theme_color": "#000000",
|
||||||
|
"background_color": "#ffffff"
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
# https://www.robotstxt.org/robotstxt.html
|
||||||
|
User-agent: *
|
||||||
|
Disallow:
|
@ -0,0 +1,38 @@
|
|||||||
|
.App {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.App-logo {
|
||||||
|
height: 40vmin;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: no-preference) {
|
||||||
|
.App-logo {
|
||||||
|
animation: App-logo-spin infinite 20s linear;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.App-header {
|
||||||
|
background-color: #282c34;
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: calc(10px + 2vmin);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.App-link {
|
||||||
|
color: #61dafb;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes App-logo-spin {
|
||||||
|
from {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import App from './App';
|
||||||
|
|
||||||
|
test('renders learn react link', () => {
|
||||||
|
render(<App />);
|
||||||
|
const linkElement = screen.getByText(/learn react/i);
|
||||||
|
expect(linkElement).toBeInTheDocument();
|
||||||
|
});
|
@ -0,0 +1,26 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import logo from './logo.svg';
|
||||||
|
import './App.css';
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
return (
|
||||||
|
<div className="App">
|
||||||
|
<header className="App-header">
|
||||||
|
<img src={logo} className="App-logo" alt="logo" />
|
||||||
|
<p>
|
||||||
|
Edit <code>src/App.tsx</code> and save to reload.
|
||||||
|
</p>
|
||||||
|
<a
|
||||||
|
className="App-link"
|
||||||
|
href="https://reactjs.org"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
Learn React
|
||||||
|
</a>
|
||||||
|
</header>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App;
|
@ -0,0 +1,13 @@
|
|||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||||
|
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||||
|
sans-serif;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||||
|
monospace;
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom/client';
|
||||||
|
import './index.css';
|
||||||
|
import App from './App';
|
||||||
|
import reportWebVitals from './reportWebVitals';
|
||||||
|
|
||||||
|
const root = ReactDOM.createRoot(
|
||||||
|
document.getElementById('root') as HTMLElement
|
||||||
|
);
|
||||||
|
root.render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<App />
|
||||||
|
</React.StrictMode>
|
||||||
|
);
|
||||||
|
|
||||||
|
// If you want to start measuring performance in your app, pass a function
|
||||||
|
// to log results (for example: reportWebVitals(console.log))
|
||||||
|
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
|
||||||
|
reportWebVitals();
|
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>
|
After Width: | Height: | Size: 2.6 KiB |
1
crates/dagger-sdk/examples/build-the-application/app/src/react-app-env.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/// <reference types="react-scripts" />
|
@ -0,0 +1,15 @@
|
|||||||
|
import { ReportHandler } from 'web-vitals';
|
||||||
|
|
||||||
|
const reportWebVitals = (onPerfEntry?: ReportHandler) => {
|
||||||
|
if (onPerfEntry && onPerfEntry instanceof Function) {
|
||||||
|
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
|
||||||
|
getCLS(onPerfEntry);
|
||||||
|
getFID(onPerfEntry);
|
||||||
|
getFCP(onPerfEntry);
|
||||||
|
getLCP(onPerfEntry);
|
||||||
|
getTTFB(onPerfEntry);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default reportWebVitals;
|
@ -0,0 +1,5 @@
|
|||||||
|
// jest-dom adds custom jest matchers for asserting on DOM nodes.
|
||||||
|
// allows you to do things like:
|
||||||
|
// expect(element).toHaveTextContent(/react/i)
|
||||||
|
// learn more: https://github.com/testing-library/jest-dom
|
||||||
|
import '@testing-library/jest-dom';
|
@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es5",
|
||||||
|
"lib": [
|
||||||
|
"dom",
|
||||||
|
"dom.iterable",
|
||||||
|
"esnext"
|
||||||
|
],
|
||||||
|
"allowJs": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"strict": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "react-jsx"
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src"
|
||||||
|
]
|
||||||
|
}
|
44
crates/dagger-sdk/examples/build-the-application/main.rs
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
use dagger_sdk::gen::HostDirectoryOpts;
|
||||||
|
|
||||||
|
fn main() -> eyre::Result<()> {
|
||||||
|
let client = dagger_sdk::client::connect()?;
|
||||||
|
|
||||||
|
let host_source_dir = client.host().directory(
|
||||||
|
"examples/build-the-application/app".into(),
|
||||||
|
Some(HostDirectoryOpts {
|
||||||
|
exclude: Some(vec!["node_modules".into(), "ci/".into()]),
|
||||||
|
include: None,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
let source = client
|
||||||
|
.container(None)
|
||||||
|
.from("node:16".into())
|
||||||
|
.with_mounted_directory("/src".into(), host_source_dir.id());
|
||||||
|
|
||||||
|
let runner = source
|
||||||
|
.with_workdir("/src".into())
|
||||||
|
.with_exec(vec!["npm".into(), "install".into()], None);
|
||||||
|
|
||||||
|
let test = runner.with_exec(
|
||||||
|
vec![
|
||||||
|
"npm".into(),
|
||||||
|
"test".into(),
|
||||||
|
"--".into(),
|
||||||
|
"--watchAll=false".into(),
|
||||||
|
],
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
let build_dir = test
|
||||||
|
.with_exec(vec!["npm".into(), "run".into(), "build".into()], None)
|
||||||
|
.directory("./build".into());
|
||||||
|
|
||||||
|
let _ = build_dir.export("./build".into());
|
||||||
|
|
||||||
|
let entries = build_dir.entries(None);
|
||||||
|
|
||||||
|
println!("build dir contents: \n {:?}", entries);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
25
crates/dagger-sdk/examples/caching/app/.gitignore
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
/.pnp
|
||||||
|
.pnp.js
|
||||||
|
|
||||||
|
# testing
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
# production
|
||||||
|
/build
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
build-*
|
17
crates/dagger-sdk/examples/caching/app/README.md
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# React Build
|
||||||
|
|
||||||
|
This is based on the [Getting Started guide for Nodejs](https://docs.dagger.io/sdk/nodejs/783645/get-started#step-5-test-against-multiple-nodejs-versions)
|
||||||
|
|
||||||
|
A simple react app is created with `create-react-app` which is built and tested by `build.js` or `build.ts`.
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
`npm install`
|
||||||
|
|
||||||
|
and then:
|
||||||
|
|
||||||
|
`node --loader ts-node/esm ./build.ts`
|
||||||
|
|
||||||
|
or
|
||||||
|
|
||||||
|
`node ./build.js`
|
29700
crates/dagger-sdk/examples/caching/app/package-lock.json
generated
Normal file
48
crates/dagger-sdk/examples/caching/app/package.json
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
{
|
||||||
|
"name": "react-build",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@testing-library/jest-dom": "^5.16.5",
|
||||||
|
"@testing-library/react": "^13.4.0",
|
||||||
|
"@testing-library/user-event": "^13.5.0",
|
||||||
|
"@types/jest": "^27.5.2",
|
||||||
|
"@types/node": "^16.18.6",
|
||||||
|
"@types/react": "^18.0.26",
|
||||||
|
"@types/react-dom": "^18.0.9",
|
||||||
|
"react": "^18.2.0",
|
||||||
|
"react-dom": "^18.2.0",
|
||||||
|
"react-scripts": "5.0.1",
|
||||||
|
"ts-node": "^10.9.1",
|
||||||
|
"typescript": "^4.9.3",
|
||||||
|
"web-vitals": "^2.1.4"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"start": "react-scripts start",
|
||||||
|
"build": "react-scripts build",
|
||||||
|
"test": "react-scripts test",
|
||||||
|
"eject": "react-scripts eject"
|
||||||
|
},
|
||||||
|
"eslintConfig": {
|
||||||
|
"extends": [
|
||||||
|
"react-app",
|
||||||
|
"react-app/jest"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"browserslist": {
|
||||||
|
"production": [
|
||||||
|
">0.2%",
|
||||||
|
"not dead",
|
||||||
|
"not op_mini all"
|
||||||
|
],
|
||||||
|
"development": [
|
||||||
|
"last 1 chrome version",
|
||||||
|
"last 1 firefox version",
|
||||||
|
"last 1 safari version"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"type": "module",
|
||||||
|
"devDependencies": {
|
||||||
|
"@dagger.io/dagger": "^0.3.2"
|
||||||
|
}
|
||||||
|
}
|
BIN
crates/dagger-sdk/examples/caching/app/public/favicon.ico
Normal file
After Width: | Height: | Size: 3.8 KiB |
43
crates/dagger-sdk/examples/caching/app/public/index.html
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<meta name="theme-color" content="#000000" />
|
||||||
|
<meta
|
||||||
|
name="description"
|
||||||
|
content="Web site created using create-react-app"
|
||||||
|
/>
|
||||||
|
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||||
|
<!--
|
||||||
|
manifest.json provides metadata used when your web app is installed on a
|
||||||
|
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||||
|
-->
|
||||||
|
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||||
|
<!--
|
||||||
|
Notice the use of %PUBLIC_URL% in the tags above.
|
||||||
|
It will be replaced with the URL of the `public` folder during the build.
|
||||||
|
Only files inside the `public` folder can be referenced from the HTML.
|
||||||
|
|
||||||
|
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||||
|
work correctly both with client-side routing and a non-root public URL.
|
||||||
|
Learn how to configure a non-root public URL by running `npm run build`.
|
||||||
|
-->
|
||||||
|
<title>React App</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
|
<div id="root"></div>
|
||||||
|
<!--
|
||||||
|
This HTML file is a template.
|
||||||
|
If you open it directly in the browser, you will see an empty page.
|
||||||
|
|
||||||
|
You can add webfonts, meta tags, or analytics to this file.
|
||||||
|
The build step will place the bundled scripts into the <body> tag.
|
||||||
|
|
||||||
|
To begin the development, run `npm start` or `yarn start`.
|
||||||
|
To create a production bundle, use `npm run build` or `yarn build`.
|
||||||
|
-->
|
||||||
|
</body>
|
||||||
|
</html>
|
BIN
crates/dagger-sdk/examples/caching/app/public/logo192.png
Normal file
After Width: | Height: | Size: 5.2 KiB |
BIN
crates/dagger-sdk/examples/caching/app/public/logo512.png
Normal file
After Width: | Height: | Size: 9.4 KiB |
25
crates/dagger-sdk/examples/caching/app/public/manifest.json
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"short_name": "React App",
|
||||||
|
"name": "Create React App Sample",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "favicon.ico",
|
||||||
|
"sizes": "64x64 32x32 24x24 16x16",
|
||||||
|
"type": "image/x-icon"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "logo192.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "192x192"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "logo512.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "512x512"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"start_url": ".",
|
||||||
|
"display": "standalone",
|
||||||
|
"theme_color": "#000000",
|
||||||
|
"background_color": "#ffffff"
|
||||||
|
}
|
3
crates/dagger-sdk/examples/caching/app/public/robots.txt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# https://www.robotstxt.org/robotstxt.html
|
||||||
|
User-agent: *
|
||||||
|
Disallow:
|
38
crates/dagger-sdk/examples/caching/app/src/App.css
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
.App {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.App-logo {
|
||||||
|
height: 40vmin;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: no-preference) {
|
||||||
|
.App-logo {
|
||||||
|
animation: App-logo-spin infinite 20s linear;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.App-header {
|
||||||
|
background-color: #282c34;
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: calc(10px + 2vmin);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.App-link {
|
||||||
|
color: #61dafb;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes App-logo-spin {
|
||||||
|
from {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
9
crates/dagger-sdk/examples/caching/app/src/App.test.tsx
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import App from './App';
|
||||||
|
|
||||||
|
test('renders learn react link', () => {
|
||||||
|
render(<App />);
|
||||||
|
const linkElement = screen.getByText(/learn react/i);
|
||||||
|
expect(linkElement).toBeInTheDocument();
|
||||||
|
});
|
26
crates/dagger-sdk/examples/caching/app/src/App.tsx
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import logo from './logo.svg';
|
||||||
|
import './App.css';
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
return (
|
||||||
|
<div className="App">
|
||||||
|
<header className="App-header">
|
||||||
|
<img src={logo} className="App-logo" alt="logo" />
|
||||||
|
<p>
|
||||||
|
Edit <code>src/App.tsx</code> and save to reload.
|
||||||
|
</p>
|
||||||
|
<a
|
||||||
|
className="App-link"
|
||||||
|
href="https://reactjs.org"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
Learn React
|
||||||
|
</a>
|
||||||
|
</header>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App;
|
13
crates/dagger-sdk/examples/caching/app/src/index.css
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||||
|
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||||
|
sans-serif;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||||
|
monospace;
|
||||||
|
}
|
19
crates/dagger-sdk/examples/caching/app/src/index.tsx
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom/client';
|
||||||
|
import './index.css';
|
||||||
|
import App from './App';
|
||||||
|
import reportWebVitals from './reportWebVitals';
|
||||||
|
|
||||||
|
const root = ReactDOM.createRoot(
|
||||||
|
document.getElementById('root') as HTMLElement
|
||||||
|
);
|
||||||
|
root.render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<App />
|
||||||
|
</React.StrictMode>
|
||||||
|
);
|
||||||
|
|
||||||
|
// If you want to start measuring performance in your app, pass a function
|
||||||
|
// to log results (for example: reportWebVitals(console.log))
|
||||||
|
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
|
||||||
|
reportWebVitals();
|
1
crates/dagger-sdk/examples/caching/app/src/logo.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>
|
After Width: | Height: | Size: 2.6 KiB |
1
crates/dagger-sdk/examples/caching/app/src/react-app-env.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/// <reference types="react-scripts" />
|
@ -0,0 +1,15 @@
|
|||||||
|
import { ReportHandler } from 'web-vitals';
|
||||||
|
|
||||||
|
const reportWebVitals = (onPerfEntry?: ReportHandler) => {
|
||||||
|
if (onPerfEntry && onPerfEntry instanceof Function) {
|
||||||
|
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
|
||||||
|
getCLS(onPerfEntry);
|
||||||
|
getFID(onPerfEntry);
|
||||||
|
getFCP(onPerfEntry);
|
||||||
|
getLCP(onPerfEntry);
|
||||||
|
getTTFB(onPerfEntry);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default reportWebVitals;
|
5
crates/dagger-sdk/examples/caching/app/src/setupTests.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
// jest-dom adds custom jest matchers for asserting on DOM nodes.
|
||||||
|
// allows you to do things like:
|
||||||
|
// expect(element).toHaveTextContent(/react/i)
|
||||||
|
// learn more: https://github.com/testing-library/jest-dom
|
||||||
|
import '@testing-library/jest-dom';
|
26
crates/dagger-sdk/examples/caching/app/tsconfig.json
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es5",
|
||||||
|
"lib": [
|
||||||
|
"dom",
|
||||||
|
"dom.iterable",
|
||||||
|
"esnext"
|
||||||
|
],
|
||||||
|
"allowJs": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"strict": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "react-jsx"
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src"
|
||||||
|
]
|
||||||
|
}
|
55
crates/dagger-sdk/examples/caching/main.rs
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
use dagger_sdk::gen::HostDirectoryOpts;
|
||||||
|
use rand::Rng;
|
||||||
|
|
||||||
|
fn main() -> eyre::Result<()> {
|
||||||
|
let client = dagger_sdk::client::connect()?;
|
||||||
|
|
||||||
|
let host_source_dir = client.host().directory(
|
||||||
|
"./examples/caching/app".into(),
|
||||||
|
Some(HostDirectoryOpts {
|
||||||
|
exclude: Some(vec!["node_modules".into(), "ci/".into()]),
|
||||||
|
include: None,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
let node_cache = client.cache_volume("node".into()).id();
|
||||||
|
|
||||||
|
let source = client
|
||||||
|
.container(None)
|
||||||
|
.from("node:16".into())
|
||||||
|
.with_mounted_directory("/src".into(), host_source_dir.id())
|
||||||
|
.with_mounted_cache("/src/node_modules".into(), node_cache, None);
|
||||||
|
|
||||||
|
let runner = source
|
||||||
|
.with_workdir("/src".into())
|
||||||
|
.with_exec(vec!["npm".into(), "install".into()], None);
|
||||||
|
|
||||||
|
let test = runner.with_exec(
|
||||||
|
vec![
|
||||||
|
"npm".into(),
|
||||||
|
"test".into(),
|
||||||
|
"--".into(),
|
||||||
|
"--watchAll=false".into(),
|
||||||
|
],
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
let build_dir = test
|
||||||
|
.with_exec(vec!["npm".into(), "run".into(), "build".into()], None)
|
||||||
|
.directory("./build".into());
|
||||||
|
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
|
||||||
|
let ref_ = client
|
||||||
|
.container(None)
|
||||||
|
.from("nginx".into())
|
||||||
|
.with_directory("/usr/share/nginx/html".into(), build_dir.id(), None)
|
||||||
|
.publish(
|
||||||
|
format!("ttl.sh/hello-dagger-rs-{}:1h", rng.gen::<u64>()),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
|
println!("published image to: {}", ref_);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
25
crates/dagger-sdk/examples/existing-dockerfile/app/.gitignore
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
/.pnp
|
||||||
|
.pnp.js
|
||||||
|
|
||||||
|
# testing
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
# production
|
||||||
|
/build
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
build-*
|
@ -0,0 +1,15 @@
|
|||||||
|
FROM node:16 as builder
|
||||||
|
|
||||||
|
COPY . /src
|
||||||
|
|
||||||
|
WORKDIR /src
|
||||||
|
|
||||||
|
RUN npm install
|
||||||
|
|
||||||
|
RUN npm test -- --watchAll=false
|
||||||
|
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
FROM nginx
|
||||||
|
|
||||||
|
COPY --from=builder /src/build /usr/share/nginx/html
|
17
crates/dagger-sdk/examples/existing-dockerfile/app/README.md
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# React Build
|
||||||
|
|
||||||
|
This is based on the [Getting Started guide for Nodejs](https://docs.dagger.io/sdk/nodejs/783645/get-started#step-5-test-against-multiple-nodejs-versions)
|
||||||
|
|
||||||
|
A simple react app is created with `create-react-app` which is built and tested by `build.js` or `build.ts`.
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
`npm install`
|
||||||
|
|
||||||
|
and then:
|
||||||
|
|
||||||
|
`node --loader ts-node/esm ./build.ts`
|
||||||
|
|
||||||
|
or
|
||||||
|
|
||||||
|
`node ./build.js`
|
@ -0,0 +1,2 @@
|
|||||||
|
node_modules/
|
||||||
|
build/
|
29700
crates/dagger-sdk/examples/existing-dockerfile/app/package-lock.json
generated
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
{
|
||||||
|
"name": "react-build",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@testing-library/jest-dom": "^5.16.5",
|
||||||
|
"@testing-library/react": "^13.4.0",
|
||||||
|
"@testing-library/user-event": "^13.5.0",
|
||||||
|
"@types/jest": "^27.5.2",
|
||||||
|
"@types/node": "^16.18.6",
|
||||||
|
"@types/react": "^18.0.26",
|
||||||
|
"@types/react-dom": "^18.0.9",
|
||||||
|
"react": "^18.2.0",
|
||||||
|
"react-dom": "^18.2.0",
|
||||||
|
"react-scripts": "5.0.1",
|
||||||
|
"ts-node": "^10.9.1",
|
||||||
|
"typescript": "^4.9.3",
|
||||||
|
"web-vitals": "^2.1.4"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"start": "react-scripts start",
|
||||||
|
"build": "react-scripts build",
|
||||||
|
"test": "react-scripts test",
|
||||||
|
"eject": "react-scripts eject"
|
||||||
|
},
|
||||||
|
"eslintConfig": {
|
||||||
|
"extends": [
|
||||||
|
"react-app",
|
||||||
|
"react-app/jest"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"browserslist": {
|
||||||
|
"production": [
|
||||||
|
">0.2%",
|
||||||
|
"not dead",
|
||||||
|
"not op_mini all"
|
||||||
|
],
|
||||||
|
"development": [
|
||||||
|
"last 1 chrome version",
|
||||||
|
"last 1 firefox version",
|
||||||
|
"last 1 safari version"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"type": "module",
|
||||||
|
"devDependencies": {
|
||||||
|
"@dagger.io/dagger": "^0.3.2"
|
||||||
|
}
|
||||||
|
}
|
After Width: | Height: | Size: 3.8 KiB |
@ -0,0 +1,43 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<meta name="theme-color" content="#000000" />
|
||||||
|
<meta
|
||||||
|
name="description"
|
||||||
|
content="Web site created using create-react-app"
|
||||||
|
/>
|
||||||
|
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||||
|
<!--
|
||||||
|
manifest.json provides metadata used when your web app is installed on a
|
||||||
|
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||||
|
-->
|
||||||
|
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||||
|
<!--
|
||||||
|
Notice the use of %PUBLIC_URL% in the tags above.
|
||||||
|
It will be replaced with the URL of the `public` folder during the build.
|
||||||
|
Only files inside the `public` folder can be referenced from the HTML.
|
||||||
|
|
||||||
|
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||||
|
work correctly both with client-side routing and a non-root public URL.
|
||||||
|
Learn how to configure a non-root public URL by running `npm run build`.
|
||||||
|
-->
|
||||||
|
<title>React App</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
|
<div id="root"></div>
|
||||||
|
<!--
|
||||||
|
This HTML file is a template.
|
||||||
|
If you open it directly in the browser, you will see an empty page.
|
||||||
|
|
||||||
|
You can add webfonts, meta tags, or analytics to this file.
|
||||||
|
The build step will place the bundled scripts into the <body> tag.
|
||||||
|
|
||||||
|
To begin the development, run `npm start` or `yarn start`.
|
||||||
|
To create a production bundle, use `npm run build` or `yarn build`.
|
||||||
|
-->
|
||||||
|
</body>
|
||||||
|
</html>
|
After Width: | Height: | Size: 5.2 KiB |
After Width: | Height: | Size: 9.4 KiB |
@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"short_name": "React App",
|
||||||
|
"name": "Create React App Sample",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "favicon.ico",
|
||||||
|
"sizes": "64x64 32x32 24x24 16x16",
|
||||||
|
"type": "image/x-icon"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "logo192.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "192x192"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "logo512.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "512x512"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"start_url": ".",
|
||||||
|
"display": "standalone",
|
||||||
|
"theme_color": "#000000",
|
||||||
|
"background_color": "#ffffff"
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
# https://www.robotstxt.org/robotstxt.html
|
||||||
|
User-agent: *
|
||||||
|
Disallow:
|
@ -0,0 +1,38 @@
|
|||||||
|
.App {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.App-logo {
|
||||||
|
height: 40vmin;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: no-preference) {
|
||||||
|
.App-logo {
|
||||||
|
animation: App-logo-spin infinite 20s linear;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.App-header {
|
||||||
|
background-color: #282c34;
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: calc(10px + 2vmin);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.App-link {
|
||||||
|
color: #61dafb;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes App-logo-spin {
|
||||||
|
from {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { render, screen } from '@testing-library/react';
|
||||||
|
import App from './App';
|
||||||
|
|
||||||
|
test('renders learn react link', () => {
|
||||||
|
render(<App />);
|
||||||
|
const linkElement = screen.getByText(/learn react/i);
|
||||||
|
expect(linkElement).toBeInTheDocument();
|
||||||
|
});
|
@ -0,0 +1,26 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import logo from './logo.svg';
|
||||||
|
import './App.css';
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
return (
|
||||||
|
<div className="App">
|
||||||
|
<header className="App-header">
|
||||||
|
<img src={logo} className="App-logo" alt="logo" />
|
||||||
|
<p>
|
||||||
|
Edit <code>src/App.tsx</code> and save to reload.
|
||||||
|
</p>
|
||||||
|
<a
|
||||||
|
className="App-link"
|
||||||
|
href="https://reactjs.org"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
Learn React
|
||||||
|
</a>
|
||||||
|
</header>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App;
|
@ -0,0 +1,13 @@
|
|||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||||
|
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||||
|
sans-serif;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||||
|
monospace;
|
||||||
|
}
|