Compare commits

...

33 Commits

Author SHA1 Message Date
44e125e00c
fix: wip action 2023-02-17 19:14:40 +01:00
83699de4ad
fix: wip 2023-02-17 19:11:58 +01:00
459014b233
with personal github token 2023-02-17 19:11:23 +01:00
d365a9f8e2
Revert "merge releases"
This reverts commit e4f614fdbf2608d7bc174b912de6d74d8ca00235.
2023-02-17 19:07:55 +01:00
f61afa73df
fix: add merge 2023-02-17 19:03:58 +01:00
e4f614fdbf
merge releases 2023-02-17 19:03:07 +01:00
fb6768a6fc
bump version 2023-02-17 18:59:49 +01:00
49d3fc6b22
with github token 2023-02-17 18:54:36 +01:00
7f3399f1d8
only allow main 2023-02-17 18:51:47 +01:00
0eaf0dd3be
fix: test creator of tags 2023-02-17 18:50:39 +01:00
91e39f4e65
add creator of tags 2023-02-17 18:50:18 +01:00
a71f60784f
add create release 2023-02-17 18:44:59 +01:00
5a5b0e40f7
with changelog 2023-02-17 18:44:15 +01:00
f03452840c
remove toolchain 2023-02-17 18:26:38 +01:00
7153c24f01
with actual versions 2023-02-17 18:24:45 +01:00
989d5bc260
with publish 2023-02-17 18:19:31 +01:00
c625ae49ba
codegen also with repository 2023-02-17 18:14:51 +01:00
31eb44517d
with repo 2023-02-17 18:13:31 +01:00
e5383b51f3
with repo 2023-02-17 18:13:17 +01:00
1e26b383d4
with readme and license 2023-02-17 18:12:02 +01:00
533b9dfef0
with wildcard version 2023-02-17 18:09:44 +01:00
bec62de62f
cargo version 0.2.0 2023-02-17 18:05:33 +01:00
571963163f
add version to main 2023-02-17 18:02:55 +01:00
36b0ecdabf
bump version 2023-02-17 18:01:46 +01:00
578c2a6883
document usage 2023-02-17 18:00:20 +01:00
6be8482b46
fix all clippy 2023-02-17 17:51:39 +01:00
0cbd1790b0
add with dockerfile 2023-02-17 17:50:45 +01:00
728840ca8e
with caching 2023-02-17 17:40:46 +01:00
59e2572173
add more quickstart 2023-02-17 17:29:23 +01:00
d894def70c
build the application 2023-02-17 15:47:12 +01:00
cb9a4dd84f
add test-the-application 2023-02-17 15:34:18 +01:00
d1726a052a
with println 2023-02-17 15:17:28 +01:00
4a4c03f3c2
feature/add impl (#6)
* format code

* with object gen and args

* add implementation

* add rust generator

* reset generated code

* add basic output

* reset output

* add object

* add format function

* with opts

* fix vec

* add context to unwrap

* fix arguments

* with function body

* first complete generation: Still missing Vec<Obj>

* run full alpine

* add roadmap item
2023-02-17 12:33:16 +01:00
177 changed files with 183041 additions and 1755 deletions

21
.github/workflows/create-release.yml vendored Normal file
View 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
View 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
View 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
View File

@ -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"

View File

@ -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"

View File

@ -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

View File

@ -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"

View 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.

View File

View File

@ -1 +0,0 @@
nightly

View File

@ -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)
}

View 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);
}
}

View 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;
}

View File

@ -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)
}
}

View File

@ -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)
}

View File

@ -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);
}
}

View File

@ -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)
}

View File

@ -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()
);
}
}

View File

@ -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);
}
}

View File

@ -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!()
}
}

View File

@ -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")
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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),
}

View File

@ -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
}

View 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
}
}

View 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
}
}

View 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")
}
}

View 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))
}
})
}

View 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_)),
}
}

View File

@ -0,0 +1,4 @@
pub mod enum_tmpl;
pub mod input_tmpl;
pub mod object_tmpl;
pub mod scalar_tmpl;

View 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))
})
}

View 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);
})
}

View 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,
}
}
}

View 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(())
}
}

View File

@ -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]

View 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.

View File

View 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)
} }
}); });

View File

@ -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)]

View File

@ -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"

View File

@ -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
```

View 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-*

View 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`

File diff suppressed because it is too large Load Diff

View 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"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View 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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

View 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"
}

View File

@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

View 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);
}
}

View 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();
});

View 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;

View 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;
}

View 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();

View 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

View File

@ -0,0 +1 @@
/// <reference types="react-scripts" />

View File

@ -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;

View 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';

View 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"
]
}

View 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(())
}

View 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-*

View 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`

File diff suppressed because it is too large Load Diff

View 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"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View 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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

View 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"
}

View File

@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

View 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);
}
}

View 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();
});

View 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;

View 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;
}

View 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();

View 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

View File

@ -0,0 +1 @@
/// <reference types="react-scripts" />

View File

@ -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;

View 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';

View 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"
]
}

View 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(())
}

View 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-*

View File

@ -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

View 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`

View File

@ -0,0 +1,2 @@
node_modules/
build/

File diff suppressed because it is too large Load Diff

View 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"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View 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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

View 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"
}

View File

@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

View 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);
}
}

View 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();
});

View 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;

View 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;
}

Some files were not shown because too many files have changed in this diff Show More