···11+# `autopack`
22+33+## About
44+`autopack` creates Docker/OCI images of a React web application built using Create React App (CRA) tool. All that without any Dockerfile or any Docker mastery. It is available as a command line application (CLI) that run across Windows, Linux, and macOS.
55+66+For motivation and other details, check out the [autopack RFC](doc/autopack-rfc.md)
77+88+## Installation
99+1010+Autopack is distributed via the pre-built binaries that one could install from the [Releases page](https://github.com/kaychaks/autopack/releases). Download, unzip, move the unzipped executable to have it available in your terminal's path, and then use the `auto-pack` command from your React project root folder.
1111+1212+## Pre-requisties
1313+1414+`autopack` relies on Docker to run the built OCI images. So a relevant Docker runtime is required to be installed.
1515+1616+Currently, `autopack` only works for React projects built using CRA. It requires at least the `build` command to be present in the `package.json`.
1717+1818+## Usage
1919+2020+```bash
2121+> auto-pack --help
2222+autopack 0.1.0
2323+Auto Pack CLI
2424+2525+USAGE:
2626+ auto-pack <SUBCOMMAND>
2727+2828+OPTIONS:
2929+ -h, --help Print help information
3030+ -V, --version Print version information
3131+3232+SUBCOMMANDS:
3333+ build Build auto-pack
3434+ help Print this message or the help of the given subcommand(s)
3535+ init Initializes auto-pack
3636+ run Runs auto-pack
3737+3838+```
3939+### Initialization
4040+4141+`autopack` initializes itself on the first run of this command. It will also download and install the required underlying tools.
4242+4343+
4444+4545+### Build
4646+4747+The build command generates the docker image.
4848+4949+
5050+5151+### Run
5252+5353+Run the container
5454+5555+
5656+5757+## Build from source
5858+5959+`autopack` is a Rust application. Follow the process of quickly setup the Rust development environment [as mentioned here](https://www.rust-lang.org/learn/get-started). And then
6060+6161+```bash
6262+# build
6363+$ cargo build
6464+# run
6565+$ cargo run init
6666+```
6767+
doc/assets/build.gif
This is a binary file and will not be displayed.
doc/assets/init.gif
This is a binary file and will not be displayed.
doc/assets/run.gif
This is a binary file and will not be displayed.
+187
doc/autopack-rfc.md
···11+- Feature Name: auto-pack
22+- Start Date: 2022-04-21
33+44+# Summary
55+66+A tool enabling web developers to use their favorite boilerplate-creating zero-config framework like CRA and but run the app in a setup similar as prod without - sacrificing the charm of the expected local development workflow or/and expecting them to get a DevOps certification. A tool to end the issue of *it runs fine in local but fails in the deployment pipeline*.
77+88+# Motivation
99+1010+Modern enterprise web developers use boilerplate-creating tools like CRA that enhances their productivity when it comes to local development and also to produce a production-ready build artifact(s). However, the same developer is on their own when it comes to taking the built artifact(s) and packaging it in a way that it runs everywhere.
1111+1212+There is a gap both from technical knowledge as well tooling perspective when it comes for the web developers to have the necessary softwares installed and configurations set to make their application deployable in foreign environments with the same level of sophistication / automation that they get for their local development.
1313+1414+Most of the time web developers delegate these tasks to a different team (DevOps). And only would realize the issues with their package if/when some form of a continuous deployment pipeline fails to run the same package. This often becomes a contentious issue between developers and DevOps that impacts normal release workflow leading to impacting the overall project delivery.
1515+1616+Containers today solves the issues of the past when it comes to have reproducible software deployment across various host platforms. However, to create container images from application's source code require decent amount of domain knowledge of unix shells, unix networking / disk I/O, and some general virtualization concepts. Containerizing web applications has its own challenges thanks to the complexity of writing a modern web application itself. So someone has to always create the container images post-facto looking/understanding the semantics of overall application design.
1717+1818+What if we automate this whole process where developers don't have to change much in their local development workflow and not only get a production-ready build artifact but also a distributable artifact (container image) which takes care of cross-platform deployment issues.
1919+2020+# Guide-level explanation
2121+2222+As a web developer today working with modern front-end frameworks like React I have a standard development workflow -
2323+- I use my favorite boilerplate-creating tool like CRA to build a fresh application scaffolding for my new awesome project
2424+- open the project folder in my favorite IDE like VSCode
2525+- run the local development server using `npm run start`,
2626+- start making changes to the source code.
2727+2828+In the above workflow one of the important gain that I get is the instant feedback in the form of auto-reloading browser. I as a web developer only concern myself to write application specific code and the boilerplate-creating tool takes care of the rest to finally show it in the browser either some error in my code or the actual web application.
2929+3030+I want the tool here to behave in such a manner. I want to just keep on writing the same application code and I expect the tool to create the relevant deployment artifact. Better use the deployment artifact to deploy in some locally installed runtime. And much cooler would be if the application that today runs locally in the browser from some adhoc web server located within the node_modules folder (this is something [Webpack Dev server](https://webpack.js.org/configuration/dev-server/) provides) would actually gets served from the runtime after loading the deployment artifact !
3131+3232+## Pre-Requisite
3333+3434+Developers today need to have Node and VSCode installed for them to start creating any web application. For the time being, they would need one more software for this tool to work - Docker.
3535+3636+## Installation
3737+3838+This tool will be a cross-platform CLI executable. Usually it means that for this tool to work it need to be in the system path. For the rest of discussion we will use the name of the tool to be `auto-pack`
3939+4040+## Usage
4141+4242+`auto-pack` is a command-line utility. It generally performs tasks in the background but also have some minimum set of commands
4343+4444+```bash
4545+$ auto-pack init
4646+```
4747+4848+`init` command initializes the project by doing some cursory checks to see if the relevant software is already installed. Right now it will check for Docker's availability in the system and whether it's already running. It will also take necessary steps to start any pre-requisites if not started already.
4949+5050+Along with that it will also take care to install and configure any external dependencies required. Right now it might need to install Buildpack's CLI.
5151+5252+Once the dependencies are installed and configured, this command will try to prime the project thereby creating and caching some default layers that might future processing faster.
5353+5454+`init` might also change relevant `npm` scripts in project's `package.json` so that `auto-pack` could channel the usual project build and run commands.
5555+5656+Finally, `init` would ensure that `auto-pack` runs as a daemon in the background.
5757+5858+```bash
5959+$ auto-pack run
6060+```
6161+6262+`run` command would try to launch the packed image as a container and also do the necessary steps to wire the host system hardware ports & launch the relevant browser to render the app. Its job is to kind of mimic the similar experience that `npm run start` provides for a CRA built app.
6363+6464+```bash
6565+$ auto-pack export
6666+```
6767+6868+`export` command would try to export the image & other relevant artifacts either directly to some registry or to some distributable format. Right now it might try to publish the image to some docker registry
6969+7070+```bash
7171+$ auto-pack show
7272+```
7373+7474+`show` command would try to provide information about the images and running containers (if any). It would also provide information about the tool itself.
7575+7676+```bash
7777+$ auto-pack stop
7878+```
7979+8080+`stop` command would try to stop the background process
8181+8282+```bash
8383+$ auto-pack clean
8484+```
8585+8686+`clean` command would clean up any stalled processes, intermediate logs, and other temporary files. It will also try to clean up images & running containers.
8787+8888+## Expected workflow
8989+9090+The intent of the tool is not to change developer's current workflow drastically. Once the developer has setup their project using a boilerplate-creating tool they install `auto-pack` executable and run `auto-pack init`. From that point - `auto-pack` should run in the background and developers should have no change in the way they interact with their application during their usual development process.
9191+9292+Developers should have option to run their application using the standard `npm` scripts provided as part of their boilerplate creating tool or use the new scripts that will leverage `auto-pack` to run the same application but this time by serving the distributable artifact from within the container which `auto-pack` should have generated & launched while it was running in the background.
9393+9494+## CI/CD Pipeline Usage
9595+9696+# Reference-level explanation
9797+9898+The idea of `auto-pack` is to leverage the concept of creating container images without a Dockerfile. Tools like [Buildpack](https://buildpacks.io/) & [source-to-image (s2i)](https://github.com/openshift/source-to-image) takes this concept to the point where they could automatically generate container images from source code. Such tools work as per this sequence of tasks
9999+100100+```
101101+ Detect --> Build --> Export
102102+```
103103+104104+- **Detect**: detects from source code to pick the right base image
105105+- **Build**: install dependencies and run the build command
106106+- **Export**: create OCI image
107107+108108+`auto-pack` apart from the above tasks will also be doing
109109+110110+- **SPA Server**: `auto-pack` will create an automatic Node server to serve the SPA which will provide few functionality by default like dynamic configuration management & efficient static file caching. In case a project already has some server `auto-pack` will have a way to leverage the same in place of it's own server.
111111+- **Launch**: once exported the image will be used to launch within a container runtime and also render the web application by (re)launching a relevant browser
112112+- **Watch**: watch for file changes &initiate Build, Export, and Launch
113113+114114+```
115115+ Detect --> SPA Server --> Build --> Export --> Launch
116116+ ^ |
117117+ | v
118118+ +----------Watch------+
119119+```
120120+121121+## Extendable Design
122122+123123+`auto-pack` will use Buildpack internally to create OCI images from source code. However, the implementation of the same will be made following a [Bridge](https://en.wikipedia.org/wiki/Bridge_pattern) design pattern so that the relevant tasks are abstracted from the actual implementation using some tool. That tool might be Buildpack right now but that also could change in future.
124124+125125+## Rust as the implementation language
126126+127127+Implementing a tool like `auto-pack` would require system level tasks such as running processes in background mostly as a daemon, doing efficient disk I/O, working with OS level concurrency when it comes to file watching, and finally working efficiently as a CLI across platforms. It is necessary for implementation of `auto-pack` to happen in a system language and not in a high-level programming language so that we don't have to deal with system-level optimizations later. Moreover, we also want the implementation of `auto-pack` to be following a typed functional programming design so that we have solid guarantees from the type system during construction and have other efficiencies that a functional programming thinking provides.
128128+129129+Rust is the only systems-programing language that matches our desired criteria. It has a sound and highly sophisticated type system that will not only guide us during implementation but also keep our implementation safe. It's system-level support via language primitives & supporting libraries will help having an efficient `auto-pack`' implementation. Moreover, today [Rust is considered to be the future of JS infrastructure](https://leerob.io/blog/rust) given the kind of adoption Rust is having across JS community.
130130+131131+132132+## Custom Buildpack
133133+134134+`auto-pack` will be leveraging Buildpack to create OCI images without Dockerfiles. Buildpack today provides a nice design via which source code of any language are converted into OCI images. There are already efficient base build packs available for JS projects especially from [Paketo](https://paketo.io/) which `auto-pack` will leverage.
135135+136136+However, for `auto-pack` we will create a custom buildpack which will have its own detection & building routine. The initial design will focus on some common patterns that today's boilerplate-creating frameworks like CRA, Vite are adopting when they are building the dev server for local development. `auto-pack` will hook into those places to have an efficient build routine.
137137+138138+Moreover, `auto-pack` will be having its own custom process of layer caching based on our understanding of different types of files that are generated for a modern web application. Every source code change won't generate all new files especially the static assets and hence such could be well cached. A custom buildpack would contain such instructions & more.
139139+140140+## Background Process
141141+142142+`auto-pack` will be working as a directory specific daemon. Core reason for this design decision is because of the nature of tasks that `auto-pack` will do. The tasks of generating images from source code might take a long time (initially) which will surely impact developer productivity. We don't want developers to change their normal workflow but still get the advantage of a local containerized deployment and execution of their web application. And hence doing the resource consuming tasks in the background without impacting developer's main workflow will be beneficial.
143143+144144+`auto-pack` will be doing following tasks in the background
145145+- **Build**: generating the production build from the current source code of the project
146146+- **Creating Image**: using the build to create the OCI image
147147+- **Creating Container**: using the image to create a container in the available runtime (for now it would be docker)
148148+- **File Watching**: watching for file changes to do incremental building and re-creating images & containers
149149+150150+## Incremental builds
151151+152152+`auto-pack` will be optimizing the build routine on top of the build artifacts produced by the boilerplate-creating tools like CRA. it is going to help `auto-pack` provide a faster image creation & execution feedback. The build produced by most boilerplate-creating tools are in the form of JS bundles (this might change in future once native ES modules are widely used for production build). `auto-pack` will try to use a content-hashing based approach to identify and replace built artifacts in running containers leveraging Buildpack's layer rebasing strategies.
153153+154154+# Drawbacks
155155+156156+Generating images without Dockerfile is not a mainstream approach. And when things happen in the background it becomes difficult to diagnose any issues. `auto-pack` will try to smoothen all this with efficient logging, helpful info messages, and proper CLI command options.
157157+158158+Tools that try to leverage OS processes to do background file monitoring and disk I/O at the same time might suffer from standard issues like stalled processes, zombie daemons, and resource draining threads. `auto-task` will be leveraging Rust's powerful [Tokio](https://tokio.rs/) suite of APIs to safely manage concurrent I/O processes and will use a Supervision Trees based approach to monitor processes.
159159+160160+Generating & executing images without Dockerfile would surely help developers achieve productivity when it comes to running & testing their application in a production environment locally. However, it might be an issue for DevOps teams who are generally tasked to containerize the application via CI/CD pipelines. And there Dockerfiles are still the preferred way to create images. Even though there are [CI/CD platforms](https://buildpacks.io/docs/tools/) that support Cloud Native Buildpacks (CNB) to create images from source code but in enterprises that process is not as prevalent as it should be. `auto-pack` being a cross platform CLI executable would ensure that it works in CI/CD platforms (which are mostly linux based). Once the CI/CD pipeline environment has docker engine available then `auto-pack` would gather all its relevant dependencies during initialization hence for DevOps it would as good as replacing some variation of `docker build` command with `auto-pack init && auto-pack export` - these series of command would first initialize and then do the necessary steps to export an image out of the source code.
161161+162162+# Rationale and alternatives
163163+164164+There are tools today which either provide some way to build CNI images without the requirement of Docker to be available as a deamon or Dockerfile to be available. `auto-pack` is mostly concerned with the later use case.
165165+166166+Cloud Native Buildpacks are the most preferred way to generate images from source code today. However, it comes with its own learning curve which might not be as daunting as Docker itself but still it requires a standard web developer proficient in React to learn decent amount of lingo from world of containers. And similarly there are other tools to generate images directly without Dockerfile like [creating Docker images with Nix package manager](https://nix.dev/tutorials/building-and-running-docker-images) and [Jib](https://github.com/GoogleContainerTools/jib). Jib comes close to something what CNBs are doing - i.e. directly producing CNI images from source code. However, where CNB is a generic specification that can be operationalized with applications built in any language / platform, Jib is only specific to Java based applications. Creating docker images with Nix is as generic as writing Dockerfiles but in a more sophisticated & expressive programming language (unlike the Dockerfile syntax which is an adhoc configuration language syntax lacking expressiveness and usual developer experience). However, it comes with additional requirements of a presence of a system level tool (i.e. Nix itself) and/or managing tool specific configurations along with application source code.
167167+168168+Intent of `auto-pack` is to make it easier for web developers (the target users) to venture the world of containers and hence it does not have to bother about requirements of other set of users. That constraint also enables `auto-pack` to have specific customizations that will be relevant and beneficial for modern web developers using tools like CRA. And again it can be safely used as a local only tool while CI/CD platforms could use known tools like Dockerfiles.
169169+170170+Apropos the above current state of tools to build CNI images without Dockerfiles and the unique mission of `auto-pack`, Cloud Native Buildpacks based backend to create CNI images has been considered to be the choice of underlying technology to create CNI images directly from code. Since the focus is to balance between providing similar developer experience that modern web-developers expect from CLI tools and also to not re-inventing the wheel of creating something from scratch, `auto-pack` is an attempt to create a simple automaton over Cloud Native Buildpacks for a very specific set of users i.e. modern web developers using React specific tools like CRA to develop their applications.
171171+# Prior art
172172+173173+`auto-pack` will leverage ideas from various tools. The basic idea of generating container images from source code was first seen in Nix and then popularized as CNB. The idea of running processes in the background to improve the efficiency of the activities happening in the foreground is something that Unix daemons & Kernel level processes generally do. There are many designs captured in [The Architecture of Open Source Applications](http://aosabook.org/en/index.html) which kind of elaborates how to do the same efficiently for a reduced scope tool like `auto-pack`. Idea of doing incremental builds via content hashing is something that build tools like [Bazel](https://bazel.build/docs/build#correct-incremental-rebuilds does pretty efficiently. `auto-pack` will leverage some patterns from those implementations.
174174+175175+# Unresolved questions
176176+177177+- How can `auto-pack` hook its logs into the UX of `npm run start` provided by CRA ? Or will it be fine if it works via a different npm script like `npm run start:pack` ? The later would reduce its usage though.
178178+- Should `auto-pack` start the background process as soon as users goes inside project's directory like how tools like [direnv](https://direnv.net/) works ? Or it should only start on users action ? We can also have a configuration to decide.
179179+- How to manage the proliferation of docker images which would get created for every change the developer would make ?
180180+181181+# Future possibilities
182182+183183+`auto-pack` will start with limited scope to support specific types of projects (React projects built using CRA) with some fixed conventions. Reason for the same is to have the initial version properly tested. However, future versions should have ways to support more type of projects and should have a way to support configurations.
184184+185185+The requirement of the presence of Docker as engine / daemon with root level privileges for `auto-pack` to work would change once native OCI builders & runtimes like [Buildah](https://github.com/containers/buildah/blob/main/README.md) and [Podman](https://github.com/containers/podman) respectively are widely available across platforms especially in Windows. For `auto-pack` to work in Windows which is the dominant platform of web developers in enterprises Docker is an unfortunate dependency. The future of containerized distribution of applications should be to create OCI images directly from code and running it in some rootless OCI runtime.
186186+187187+Although `auto-pack` would always be a CLI tool it would be beneficial if we could also have it as an editor plugin. That way we can provide much better UX for developers. Modern IDEs like VSCode provide tools like [Language Server Protocol](https://microsoft.github.io/language-server-protocol/) which can also eliminate `auto-pack` to run as daemon and rather have a nice interface similar to how other Language Server Protocol servers work.
···11+use super::{
22+ crypto::{APCrypto, APEncryptVal},
33+ AutoPack,
44+};
55+use crate::{log::error, runtime::Runtime};
66+use rmp_serde::{decode, encode};
77+use std::{
88+ ffi::OsStr,
99+ fs::{self, File},
1010+ path::{Path, PathBuf},
1111+ time,
1212+};
1313+1414+pub(crate) struct StateFiles {
1515+ pub(crate) state_content: PathBuf,
1616+ pub(crate) checksum: PathBuf,
1717+ // pub(crate) state_dir: PathBuf,
1818+}
1919+2020+impl StateFiles {
2121+ pub(super) fn new(runtime_dir: Option<&Path>) -> anyhow::Result<StateFiles> {
2222+ let runtime_dir = runtime_dir
2323+ .map(|e| e.to_path_buf())
2424+ .unwrap_or_else(|| Runtime::default().dir());
2525+2626+ let state_dir = runtime_dir.join(".state");
2727+2828+ let fs = state_dir
2929+ .read_dir()
3030+ .map_err(|e| {
3131+ error("Failed loading autopack state");
3232+ anyhow::anyhow!(
3333+ "State path {} does not exist :: {:?}",
3434+ state_dir.display(),
3535+ e.to_string()
3636+ )
3737+ })?
3838+ .fold((None, None), |mut acc, f| match f {
3939+ Ok(x) => {
4040+ let ext = x.path();
4141+ let ext = ext.extension().and_then(OsStr::to_str).unwrap_or_default();
4242+ if ext == "checksum" {
4343+ acc.1 = Some(x.path());
4444+ } else {
4545+ acc.0 = Some(x.path());
4646+ }
4747+ acc
4848+ }
4949+ _ => acc,
5050+ });
5151+5252+ let state_content =
5353+ fs.0.ok_or_else(|| anyhow::anyhow!("State content path not found"))?;
5454+ let checksum =
5555+ fs.1.ok_or_else(|| anyhow::anyhow!("State content checksum path not found"))?;
5656+5757+ Ok(StateFiles {
5858+ state_content,
5959+ checksum,
6060+ // state_dir,
6161+ })
6262+ }
6363+ pub(super) fn save(
6464+ autopack: &AutoPack,
6565+ save_path: Option<&Path>,
6666+ ) -> anyhow::Result<StateFiles> {
6767+ let default_path = autopack.runtime.dir().join(".state");
6868+ let save_path = save_path.unwrap_or(&default_path);
6969+7070+ if !save_path.exists() {
7171+ fs::create_dir(save_path)?;
7272+ } else {
7373+ fs::remove_dir_all(save_path)?;
7474+ fs::create_dir(save_path)?;
7575+ }
7676+7777+ let version = autopack.client_project.package_json.version.clone();
7878+7979+ let state_file_name = format!(
8080+ "{}_{}_{}",
8181+ autopack.client_project.package_json.name,
8282+ version,
8383+ time::SystemTime::now()
8484+ .duration_since(time::UNIX_EPOCH)
8585+ .map_err(|e| anyhow::anyhow!("could not get current time :: {:?}", e))?
8686+ .as_millis()
8787+ );
8888+ let state_file = save_path.join(state_file_name.clone());
8989+9090+ let save_file = save_path.join(state_file);
9191+ let checksum_file = save_path.join(format!("{}.checksum", state_file_name));
9292+ let contents = encode::to_vec(&autopack)?;
9393+9494+ let a = APCrypto::builder().content_hash(contents.clone()).build();
9595+9696+ let APEncryptVal { cipher_text, nonce } = a.encrypt()?;
9797+9898+ fs::write(save_file.clone(), contents)
9999+ .map_err(|e| anyhow::anyhow!("error writing state file :: {:?}", e))?;
100100+101101+ fs::write(
102102+ checksum_file.clone(),
103103+ format!(
104104+ r"{}
105105+{}",
106106+ hex::encode(cipher_text),
107107+ hex::encode(nonce)
108108+ ),
109109+ )
110110+ .map_err(|e| anyhow::anyhow!("error writing checksum :: {:?}", e))?;
111111+112112+ Ok(StateFiles {
113113+ checksum: checksum_file,
114114+ state_content: save_file,
115115+ // state_dir: save_path.to_path_buf(),
116116+ })
117117+ }
118118+ pub(super) fn load_autopack(&self) -> anyhow::Result<AutoPack> {
119119+ APCrypto::validate_hashes(self)?;
120120+121121+ let autpack = decode::from_read(File::open(self.state_content.clone()).map_err(|e| {
122122+ anyhow::anyhow!("Failed to open file at {:?} :: {:?}", self.state_content, e)
123123+ })?)
124124+ .map_err(|e| {
125125+ anyhow::anyhow!(
126126+ "Fail to deserialize application state from file at {:?} :: {:?}",
127127+ self.state_content,
128128+ e
129129+ )
130130+ })?;
131131+ Ok(autpack)
132132+ }
133133+}
+103
src/autopack/tests.rs
···11+use crate::{autopack::AutoPack, runtime::Runtime};
22+use std::fs::{self, OpenOptions};
33+use tempfile::Builder;
44+55+#[test]
66+fn app_state_save_load() {
77+ let runtime_dir = Builder::new()
88+ .tempdir()
99+ .expect("expecting a temp file to be created");
1010+ let runtime = Runtime::builder(runtime_dir.path())
1111+ .dir(false)
1212+ .expect("")
1313+ .build();
1414+ let app = AutoPack::new(Some(runtime.clone()));
1515+1616+ app.save(None).expect("failed save");
1717+ let saved_app = AutoPack::load(Some(&runtime.dir())).expect("failed load");
1818+1919+ assert_eq!(app, saved_app);
2020+}
2121+2222+#[test]
2323+#[should_panic]
2424+fn app_state_bad_content() {
2525+ use std::io::Write;
2626+2727+ let app = AutoPack::default();
2828+ let save_path = Builder::new()
2929+ .tempdir()
3030+ .expect("expecting a temp file to be created");
3131+3232+ let s = app.save(Some(save_path.path())).expect("failed save");
3333+ // let st = StateFiles::new(&s.state_content).expect("failed to parse the state files");
3434+ let mut f = OpenOptions::new()
3535+ .append(true)
3636+ .open(s.state_content.clone())
3737+ .expect("failed opening state file");
3838+ writeln!(f, "A new line").expect("failed to write to state file");
3939+4040+ AutoPack::load(Some(s.state_content.as_path())).expect("failed load");
4141+}
4242+4343+#[test]
4444+#[should_panic]
4545+fn app_state_bad_hash() {
4646+ let app = AutoPack::default();
4747+ let save_path = Builder::new()
4848+ .tempdir()
4949+ .expect("expecting a temp file to be created");
5050+5151+ let s = app.save(Some(save_path.path())).expect("failed save");
5252+ // let st = StateFiles::new(&s).expect("failed to parse the state files");
5353+5454+ let lines = fs::read_to_string(s.checksum.clone()).expect("failed reading checksum");
5555+ let ls = lines.lines().collect::<Vec<_>>();
5656+ let ls1 = ls[0];
5757+ let mut ls11: Vec<_> = ls1.chars().take(ls1.len() - 2).collect();
5858+ ls11.push('0');
5959+ ls11.push('1');
6060+6161+ let x = ls11.into_iter().map(|x| x.to_string()).collect::<Vec<_>>();
6262+6363+ let lss = format!(
6464+ r"{}
6565+ {}",
6666+ &x.join(""),
6767+ ls[1]
6868+ );
6969+ fs::write(s.checksum, lss).expect("failed to write checksum");
7070+7171+ AutoPack::load(Some(s.state_content.as_path())).expect("failed load");
7272+}
7373+7474+#[test]
7575+#[should_panic]
7676+fn app_state_bad_nonce() {
7777+ let app = AutoPack::default();
7878+ let save_path = Builder::new()
7979+ .tempdir()
8080+ .expect("expecting a temp file to be created");
8181+8282+ let s = app.save(Some(save_path.path())).expect("failed save");
8383+ // let st = StateFiles::new(&s).expect("failed to parse the state files");
8484+8585+ let lines = fs::read_to_string(s.checksum.clone()).expect("failed reading checksum");
8686+ let ls = lines.lines().collect::<Vec<_>>();
8787+ let ls1 = ls[1];
8888+ let mut ls11: Vec<_> = ls1.chars().take(ls1.len() - 2).collect();
8989+ ls11.push('0');
9090+ ls11.push('0');
9191+9292+ let x = ls11.into_iter().map(|x| x.to_string()).collect::<Vec<_>>();
9393+9494+ let lss = format!(
9595+ r"{}
9696+ {}",
9797+ ls[0],
9898+ &x.join("")
9999+ );
100100+ fs::write(s.checksum, lss).expect("failed to write checksum");
101101+102102+ AutoPack::load(Some(s.state_content.as_path())).expect("failed load");
103103+}
···11+/// Macro to create a module to custom (de)serialization of a field of type `Option<String>`
22+/// where a `None` is serialized using the function `def_fn`
33+/// which must have the type signature `() -> String`.
44+///
55+/// During deserialization `def_fn` will be used to convert a value
66+/// mathching the return of `def_fn` to a `None`.
77+///
88+/// All this to ensure that reflexivity propery
99+/// of encode and decode holds.
1010+///
1111+/// i.e. `deserialize . serialize = id`
1212+#[macro_export]
1313+macro_rules! ser_deser_str_with_def {
1414+ ($module: ident, $def_fn: ident) => {
1515+ struct $module {}
1616+1717+ impl $module {
1818+ fn serialize<S>(x: &Option<String>, s: S) -> Result<S::Ok, S::Error>
1919+ where
2020+ S: serde::Serializer,
2121+ {
2222+ match x {
2323+ Some(str) => s.serialize_str(str),
2424+ None => {
2525+ let def_str = $def_fn();
2626+ s.serialize_str(&def_str)
2727+ }
2828+ }
2929+ }
3030+3131+ fn deserialize<'de, D>(d: D) -> Result<Option<String>, D::Error>
3232+ where
3333+ D: serde::Deserializer<'de>,
3434+ {
3535+ Ok(Option::<String>::deserialize(d)?.and_then(|f| {
3636+ if f == $def_fn() {
3737+ None
3838+ } else {
3939+ Some(f)
4040+ }
4141+ }))
4242+ }
4343+ }
4444+ };
4545+}
···11+pub(crate) mod autopack;
22+pub(crate) mod buildpack;
33+pub mod cli;
44+mod docker;
55+mod error;
66+pub(crate) mod log;
77+pub(crate) mod pack;
88+mod package_json;
99+pub(crate) mod runtime;