···11+// For format details, see https://aka.ms/devcontainer.json. For config options, see the
22+// README at: https://github.com/devcontainers/templates/tree/main/src/debian
33+{
44+ "name": "Debian",
55+ // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
66+ "image": "mcr.microsoft.com/devcontainers/base:bullseye",
77+ "features": {
88+ "ghcr.io/devcontainers/features/github-cli:1": {},
99+ "ghcr.io/devcontainers/features/nix:1": {}
1010+ },
1111+1212+ // Features to add to the dev container. More info: https://containers.dev/features.
1313+ // "features": {},
1414+1515+ // Use 'forwardPorts' to make a list of ports inside the container available locally.
1616+ // "forwardPorts": [],
1717+1818+ // Use 'postCreateCommand' to run commands after the container is created.
1919+ "postCreateCommand": "nix develop --experimental-features \"nix-command flakes\""
2020+ // Configure tool-specific properties.
2121+ // "customizations": {},
2222+2323+ // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
2424+ // "remoteUser": "root"
2525+}
···11+# Contributor Covenant Code of Conduct
22+33+## Our Pledge
44+55+We as members, contributors, and leaders pledge to make participation in our
66+community a harassment-free experience for everyone, regardless of age, body
77+size, visible or invisible disability, ethnicity, sex characteristics, gender
88+identity and expression, level of experience, education, socio-economic status,
99+nationality, personal appearance, race, caste, color, religion, or sexual
1010+identity and orientation.
1111+1212+We pledge to act and interact in ways that contribute to an open, welcoming,
1313+diverse, inclusive, and healthy community.
1414+1515+## Our Standards
1616+1717+Examples of behavior that contributes to a positive environment for our
1818+community include:
1919+2020+- Demonstrating empathy and kindness toward other people
2121+- Being respectful of differing opinions, viewpoints, and experiences
2222+- Giving and gracefully accepting constructive feedback
2323+- Accepting responsibility and apologizing to those affected by our mistakes,
2424+ and learning from the experience
2525+- Focusing on what is best not just for us as individuals, but for the overall
2626+ community
2727+2828+Examples of unacceptable behavior include:
2929+3030+- The use of sexualized language or imagery, and sexual attention or advances of
3131+ any kind
3232+- Trolling, insulting or derogatory comments, and personal or political attacks
3333+- Public or private harassment
3434+- Publishing others' private information, such as a physical or email address,
3535+ without their explicit permission
3636+- Other conduct which could reasonably be considered inappropriate in a
3737+ professional setting
3838+3939+## Enforcement Responsibilities
4040+4141+Community leaders are responsible for clarifying and enforcing our standards of
4242+acceptable behavior and will take appropriate and fair corrective action in
4343+response to any behavior that they deem inappropriate, threatening, offensive,
4444+or harmful.
4545+4646+Community leaders have the right and responsibility to remove, edit, or reject
4747+comments, commits, code, wiki edits, issues, and other contributions that are
4848+not aligned to this Code of Conduct, and will communicate reasons for moderation
4949+decisions when appropriate.
5050+5151+## Scope
5252+5353+This Code of Conduct applies within all community spaces, and also applies when
5454+an individual is officially representing the community in public spaces.
5555+Examples of representing our community include using an official e-mail address,
5656+posting via an official social media account, or acting as an appointed
5757+representative at an online or offline event.
5858+5959+## Enforcement
6060+6161+Instances of abusive, harassing, or otherwise unacceptable behavior may be
6262+reported to the community leaders responsible for enforcement at
6363+[GitHub Issues](https://github.com/fluent-ci-templates/rust-pipeline/issues).
6464+All complaints will be reviewed and investigated promptly and fairly.
6565+6666+All community leaders are obligated to respect the privacy and security of the
6767+reporter of any incident.
6868+6969+## Enforcement Guidelines
7070+7171+Community leaders will follow these Community Impact Guidelines in determining
7272+the consequences for any action they deem in violation of this Code of Conduct:
7373+7474+### 1. Correction
7575+7676+**Community Impact**: Use of inappropriate language or other behavior deemed
7777+unprofessional or unwelcome in the community.
7878+7979+**Consequence**: A private, written warning from community leaders, providing
8080+clarity around the nature of the violation and an explanation of why the
8181+behavior was inappropriate. A public apology may be requested.
8282+8383+### 2. Warning
8484+8585+**Community Impact**: A violation through a single incident or series of
8686+actions.
8787+8888+**Consequence**: A warning with consequences for continued behavior. No
8989+interaction with the people involved, including unsolicited interaction with
9090+those enforcing the Code of Conduct, for a specified period of time. This
9191+includes avoiding interactions in community spaces as well as external channels
9292+like social media. Violating these terms may lead to a temporary or permanent
9393+ban.
9494+9595+### 3. Temporary Ban
9696+9797+**Community Impact**: A serious violation of community standards, including
9898+sustained inappropriate behavior.
9999+100100+**Consequence**: A temporary ban from any sort of interaction or public
101101+communication with the community for a specified period of time. No public or
102102+private interaction with the people involved, including unsolicited interaction
103103+with those enforcing the Code of Conduct, is allowed during this period.
104104+Violating these terms may lead to a permanent ban.
105105+106106+### 4. Permanent Ban
107107+108108+**Community Impact**: Demonstrating a pattern of violation of community
109109+standards, including sustained inappropriate behavior, harassment of an
110110+individual, or aggression toward or disparagement of classes of individuals.
111111+112112+**Consequence**: A permanent ban from any sort of public interaction within the
113113+community.
114114+115115+## Attribution
116116+117117+This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118118+version 2.1, available at
119119+[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
120120+121121+Community Impact Guidelines were inspired by
122122+[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
123123+124124+For answers to common questions about this code of conduct, see the FAQ at
125125+[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
126126+[https://www.contributor-covenant.org/translations][translations].
127127+128128+[homepage]: https://www.contributor-covenant.org
129129+[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
130130+[Mozilla CoC]: https://github.com/mozilla/diversity
131131+[FAQ]: https://www.contributor-covenant.org/faq
132132+[translations]: https://www.contributor-covenant.org/translations
+70
.fluentci/CONTRIBUTING.md
···11+# Contributing Guidelines
22+33+Thank you for your interest in contributing to our project. Whether it's a bug
44+report, new feature, correction, or additional documentation, we greatly value
55+feedback and contributions from our community.
66+77+Please read through this document before submitting any issues or pull requests
88+to ensure we have all the necessary information to effectively respond to your
99+bug report or contribution.
1010+1111+## Reporting Bugs/Feature Requests
1212+1313+We welcome you to use the GitHub issue tracker to report bugs or suggest
1414+features.
1515+1616+When filing an issue, please check existing open, or recently closed, issues to
1717+make sure somebody else hasn't already reported the issue. Please try to include
1818+as much information as you can. Details like these are incredibly useful:
1919+2020+- A reproducible test case or series of steps
2121+- The version of our code being used
2222+- Any modifications you've made relevant to the bug
2323+- Anything unusual about your environment or deployment
2424+2525+## Contributing via Pull Requests
2626+2727+Contributions via pull requests are much appreciated. Before sending us a pull
2828+request, please ensure that:
2929+3030+1. You are working against the latest source on the _master_ branch.
3131+2. You check existing open, and recently merged, pull requests to make sure
3232+ someone else hasn't addressed the problem already.
3333+3. You open an issue to discuss any significant work - we would hate for your
3434+ time to be wasted.
3535+3636+To send us a pull request, please:
3737+3838+1. Fork the repository.
3939+2. Modify the source; please focus on the specific change you are contributing.
4040+ If you also reformat all the code, it will be hard for us to focus on your
4141+ change.
4242+3. Ensure local tests pass.
4343+4. Commit to your fork using clear commit messages.
4444+5. Send us a pull request, answering any default questions in the pull request
4545+ interface.
4646+6. Pay attention to any automated CI failures reported in the pull request, and
4747+ stay involved in the conversation.
4848+4949+GitHub provides additional document on
5050+[forking a repository](https://help.github.com/articles/fork-a-repo/) and
5151+[creating a pull request](https://help.github.com/articles/creating-a-pull-request/).
5252+5353+## Finding contributions to work on
5454+5555+Looking at the existing issues is a great way to find something to contribute
5656+on. As our projects, by default, use the default GitHub issue labels
5757+(enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any
5858+'help wanted' issues is a great place to start.
5959+6060+## Code of Conduct
6161+6262+This project has adopted the
6363+[Contributor Covenant](https://www.contributor-covenant.org/), version 2.1,
6464+available at
6565+https://www.contributor-covenant.org/version/2/1/code_of_conduct.html.
6666+6767+## Licensing
6868+6969+See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to
7070+confirm the licensing of your contribution.
+19
.fluentci/LICENSE
···11+Copyright (c) 2023 Tsiry Sandratraina <tsiry.sndr@aol.com>
22+33+Permission is hereby granted, free of charge, to any person obtaining a copy
44+of this software and associated documentation files (the "Software"), to deal
55+in the Software without restriction, including without limitation the rights
66+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
77+copies of the Software, and to permit persons to whom the Software is
88+furnished to do so, subject to the following conditions:
99+1010+The above copyright notice and this permission notice shall be included in all
1111+copies or substantial portions of the Software.
1212+1313+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1414+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1515+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1616+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1717+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
1818+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
1919+SOFTWARE.
+54
.fluentci/README.md
···11+# Rust Pipeline
22+33+[](https://pkg.fluentci.io/rust_pipeline)
44+[](https://deno.land/x/rust_pipeline)
55+
66+[](https://codecov.io/gh/fluent-ci-templates/rust-pipeline)
77+88+A ready-to-use CI/CD Pipeline for your Rust projects.
99+## 🚀 Usage
1010+1111+Run the following command in your Rust Project:
1212+1313+```bash
1414+fluentci run rust_pipeline
1515+```
1616+1717+Or if you want to run specific jobs:
1818+1919+```bash
2020+fluentci run rust_pipeline test build
2121+```
2222+2323+2424+if you want to use it as a template:
2525+2626+```bash
2727+fluentci init -t rust
2828+```
2929+3030+This will create a `.fluentci` folder in your project.
3131+3232+Now you can run the pipeline with:
3333+3434+```bash
3535+fluentci run .
3636+```
3737+3838+## Jobs
3939+4040+| Job | Description |
4141+| ----- | ------------------ |
4242+| build | build your project |
4343+| test | Run your tests |
4444+4545+## Programmatic usage
4646+4747+You can also use this pipeline programmatically:
4848+4949+```ts
5050+import { build, test } from "https://pkg.fluentci.io/rust_pipeline@v0.6.1/mod.ts";
5151+5252+await test();
5353+await build();
5454+```
···11+import { Writable } from "node:stream";
22+import { Client } from "./client.gen.ts";
33+import { Context } from "./context.ts";
44+55+/**
66+ * ConnectOpts defines option used to connect to an engine.
77+ */
88+export interface ConnectOpts {
99+ /**
1010+ * Use to overwrite Dagger workdir
1111+ * @defaultValue process.cwd()
1212+ */
1313+ Workdir?: string;
1414+ /**
1515+ * Enable logs output
1616+ * @example
1717+ * LogOutput
1818+ * ```ts
1919+ * connect(async (client: Client) => {
2020+ const source = await client.host().workdir().id()
2121+ ...
2222+ }, {LogOutput: process.stdout})
2323+ ```
2424+ */
2525+ LogOutput?: Writable;
2626+}
2727+2828+export type CallbackFct = (client: Client) => Promise<void>;
2929+3030+export interface ConnectParams {
3131+ port: number;
3232+ session_token: string;
3333+}
3434+3535+/**
3636+ * connect runs GraphQL server and initializes a
3737+ * GraphQL client to execute query on it through its callback.
3838+ * This implementation is based on the existing Go SDK.
3939+ */
4040+export async function connect(
4141+ cb: CallbackFct,
4242+ _config: ConnectOpts = {}
4343+): Promise<void> {
4444+ const ctx = new Context();
4545+ const client = new Client({ ctx: ctx });
4646+4747+ // Initialize connection
4848+ await ctx.connection();
4949+5050+ await cb(client).finally(() => {
5151+ ctx.close();
5252+ });
5353+}
+53
.fluentci/sdk/context.ts
···11+import { GraphQLClient } from "../deps.ts";
22+33+import { initDefaultContext } from "./builder.ts";
44+55+interface ContextConfig {
66+ client?: GraphQLClient;
77+}
88+99+/**
1010+ * Context abstracts the connection to the engine.
1111+ *
1212+ * It's required to implement the default global SDK.
1313+ * Its purpose is to store and returns the connection to the graphQL API, if
1414+ * no connection is set, it can create its own.
1515+ *
1616+ * This is also useful for lazy evaluation with the default global client,
1717+ * this one should only run the engine if it actually executes something.
1818+ */
1919+export class Context {
2020+ private _client?: GraphQLClient;
2121+2222+ constructor(config?: ContextConfig) {
2323+ this._client = config?.client;
2424+ }
2525+2626+ /**
2727+ * Returns a GraphQL client connected to the engine.
2828+ *
2929+ * If no client is set, it will create one.
3030+ */
3131+ public async connection(): Promise<GraphQLClient> {
3232+ if (!this._client) {
3333+ const defaultCtx = await initDefaultContext();
3434+ this._client = defaultCtx._client as GraphQLClient;
3535+ }
3636+3737+ return this._client;
3838+ }
3939+4040+ /**
4141+ * Close the connection and the engine if this one was started by the node
4242+ * SDK.
4343+ */
4444+ public close(): void {
4545+ // Reset client, so it can restart a new connection if necessary
4646+ this._client = undefined;
4747+ }
4848+}
4949+5050+/**
5151+ * Expose a default context for the global client
5252+ */
5353+export const defaultContext = new Context();
+244
.fluentci/sdk/utils.ts
···11+// deno-lint-ignore-file no-explicit-any
22+import {
33+ ClientError,
44+ gql,
55+ GraphQLClient,
66+ GraphQLRequestError,
77+ TooManyNestedObjectsError,
88+ UnknownDaggerError,
99+ NotAwaitedRequestError,
1010+ ExecError,
1111+} from "../deps.ts";
1212+1313+import { Metadata, QueryTree } from "./client.gen.ts";
1414+1515+/**
1616+ * Format argument into GraphQL query format.
1717+ */
1818+function buildArgs(args: any): string {
1919+ const metadata: Metadata = args.__metadata || {};
2020+2121+ // Remove unwanted quotes
2222+ const formatValue = (key: string, value: string) => {
2323+ // Special treatment for enumeration, they must be inserted without quotes
2424+ if (metadata[key]?.is_enum) {
2525+ return JSON.stringify(value).replace(/['"]+/g, "");
2626+ }
2727+2828+ return JSON.stringify(value).replace(
2929+ /\{"[a-zA-Z]+":|,"[a-zA-Z]+":/gi,
3030+ (str) => {
3131+ return str.replace(/"/g, "");
3232+ }
3333+ );
3434+ };
3535+3636+ if (args === undefined || args === null) {
3737+ return "";
3838+ }
3939+4040+ const formattedArgs = Object.entries(args).reduce(
4141+ (acc: any, [key, value]) => {
4242+ // Ignore internal metadata key
4343+ if (key === "__metadata") {
4444+ return acc;
4545+ }
4646+4747+ if (value !== undefined && value !== null) {
4848+ acc.push(`${key}: ${formatValue(key, value as string)}`);
4949+ }
5050+5151+ return acc;
5252+ },
5353+ []
5454+ );
5555+5656+ if (formattedArgs.length === 0) {
5757+ return "";
5858+ }
5959+6060+ return `(${formattedArgs})`;
6161+}
6262+6363+/**
6464+ * Find QueryTree, convert them into GraphQl query
6565+ * then compute and return the result to the appropriate field
6666+ */
6767+async function computeNestedQuery(
6868+ query: QueryTree[],
6969+ client: GraphQLClient
7070+): Promise<void> {
7171+ // Check if there is a nested queryTree to be executed
7272+ const isQueryTree = (value: any) => value["_queryTree"] !== undefined;
7373+7474+ // Check if there is a nested array of queryTree to be executed
7575+ const isArrayQueryTree = (value: any[]) =>
7676+ value.every((v) => v instanceof Object && isQueryTree(v));
7777+7878+ // Prepare query tree for final query by computing nested queries
7979+ // and building it with their results.
8080+ const computeQueryTree = async (value: any): Promise<string> => {
8181+ // Resolve sub queries if operation's args is a subquery
8282+ for (const op of value["_queryTree"]) {
8383+ await computeNestedQuery([op], client);
8484+ }
8585+8686+ // push an id that will be used by the container
8787+ return buildQuery([
8888+ ...value["_queryTree"],
8989+ {
9090+ operation: "id",
9191+ },
9292+ ]);
9393+ };
9494+9595+ // Remove all undefined args and assert args type
9696+ const queryToExec = query.filter((q): q is Required<QueryTree> => !!q.args);
9797+9898+ for (const q of queryToExec) {
9999+ await Promise.all(
100100+ // Compute nested query for single object
101101+ Object.entries(q.args).map(async ([key, value]: any) => {
102102+ if (value instanceof Object && isQueryTree(value)) {
103103+ // push an id that will be used by the container
104104+ const getQueryTree = await computeQueryTree(value);
105105+106106+ q.args[key] = await compute(getQueryTree, client);
107107+ }
108108+109109+ // Compute nested query for array of object
110110+ if (Array.isArray(value) && isArrayQueryTree(value)) {
111111+ const tmp: any = q.args[key];
112112+113113+ for (let i = 0; i < value.length; i++) {
114114+ // push an id that will be used by the container
115115+ const getQueryTree = await computeQueryTree(value[i]);
116116+117117+ tmp[i] = await compute(getQueryTree, client);
118118+ }
119119+120120+ q.args[key] = tmp;
121121+ }
122122+ })
123123+ );
124124+ }
125125+}
126126+127127+/**
128128+ * Convert the queryTree into a GraphQL query
129129+ * @param q
130130+ * @returns
131131+ */
132132+export function buildQuery(q: QueryTree[]): string {
133133+ const query = q.reduce((acc, { operation, args }, i) => {
134134+ const qLen = q.length;
135135+136136+ acc += ` ${operation} ${args ? `${buildArgs(args)}` : ""} ${
137137+ qLen - 1 !== i ? "{" : "}".repeat(qLen - 1)
138138+ }`;
139139+140140+ return acc;
141141+ }, "");
142142+143143+ return `{${query} }`;
144144+}
145145+146146+/**
147147+ * Convert querytree into a Graphql query then compute it
148148+ * @param q | QueryTree[]
149149+ * @param client | GraphQLClient
150150+ * @returns
151151+ */
152152+export async function computeQuery<T>(
153153+ q: QueryTree[],
154154+ client: GraphQLClient
155155+): Promise<T> {
156156+ await computeNestedQuery(q, client);
157157+158158+ const query = buildQuery(q);
159159+160160+ return await compute(query, client);
161161+}
162162+163163+/**
164164+ * Return a Graphql query result flattened
165165+ * @param response any
166166+ * @returns
167167+ */
168168+export function queryFlatten<T>(response: any): T {
169169+ // Recursion break condition
170170+ // If our response is not an object or an array we assume we reached the value
171171+ if (!(response instanceof Object) || Array.isArray(response)) {
172172+ return response;
173173+ }
174174+175175+ const keys = Object.keys(response);
176176+177177+ if (keys.length != 1) {
178178+ // Dagger is currently expecting to only return one value
179179+ // If the response is nested in a way were more than one object is nested inside throw an error
180180+ throw new TooManyNestedObjectsError(
181181+ "Too many nested objects inside graphql response",
182182+ { response: response }
183183+ );
184184+ }
185185+186186+ const nestedKey = keys[0];
187187+188188+ return queryFlatten(response[nestedKey]);
189189+}
190190+191191+/**
192192+ * Send a GraphQL document to the server
193193+ * return a flatten result
194194+ * @hidden
195195+ */
196196+export async function compute<T>(
197197+ query: string,
198198+ client: GraphQLClient
199199+): Promise<T> {
200200+ let computeQuery: Awaited<T>;
201201+ try {
202202+ computeQuery = await client.request(
203203+ gql`
204204+ ${query}
205205+ `
206206+ );
207207+ } catch (e: any) {
208208+ if (e instanceof ClientError) {
209209+ const msg = e.response.errors?.[0]?.message ?? `API Error`;
210210+ const ext = e.response.errors?.[0]?.extensions;
211211+212212+ if (ext?._type === "EXEC_ERROR") {
213213+ throw new ExecError(msg, {
214214+ cmd: (ext.cmd as string[]) ?? [],
215215+ exitCode: (ext.exitCode as number) ?? -1,
216216+ stdout: (ext.stdout as string) ?? "",
217217+ stderr: (ext.stderr as string) ?? "",
218218+ });
219219+ }
220220+221221+ throw new GraphQLRequestError(msg, {
222222+ request: e.request,
223223+ response: e.response,
224224+ cause: e,
225225+ });
226226+ }
227227+228228+ // Looking for connection error in case the function has not been awaited.
229229+ if (e.errno === "ECONNREFUSED") {
230230+ throw new NotAwaitedRequestError(
231231+ "Encountered an error while requesting data via graphql through a synchronous call. Make sure the function called is awaited.",
232232+ { cause: e }
233233+ );
234234+ }
235235+236236+ // Just throw the unknown error
237237+ throw new UnknownDaggerError(
238238+ "Encountered an unknown error while requesting data via graphql",
239239+ { cause: e }
240240+ );
241241+ }
242242+243243+ return queryFlatten(computeQuery);
244244+}