feat: auto push store paths and add CI (#1)

* feat: add flake devShell

* chore: add prettier

* fix: respect cache input during configure

* feat: auto push paths

* feat: add test workflow

* chore: update index.js

* refactors

* pnpm lock

* more refactors

* remove copying

---------

Co-authored-by: Ryan Cao <70191398+ryanccn@users.noreply.github.com>
This commit is contained in:
seth 2023-07-19 01:53:49 +00:00 committed by GitHub
parent 6e24bce2f4
commit 5dc7b671af
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 231 additions and 36 deletions

32
.github/workflows/test.yml vendored Normal file
View file

@ -0,0 +1,32 @@
name: "Test"
on:
push:
branches: ["main"]
pull_request:
workflow_call:
workflow_dispatch:
jobs:
test-cache:
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Install Nix
uses: cachix/install-nix-action@v22
- name: Setup Attic Cache
uses: ./
with:
endpoint: ${{ secrets.ATTIC_ENDPOINT }}
cache: ${{ secrets.ATTIC_CACHE }}
token: ${{ secrets.ATTIC_TOKEN }}
- run: nix-build test.nix

4
.gitignore vendored
View file

@ -1,3 +1,7 @@
.DS_Store .DS_Store
node_modules/ node_modules/
# nix
.direnv/
.envrc

4
.prettierignore Normal file
View file

@ -0,0 +1,4 @@
.direnv/
dist/
pnpm-lock.yaml

View file

@ -8,9 +8,9 @@ inputs:
cache: cache:
description: "attic cache" description: "attic cache"
required: true required: true
paths: skip-push:
description: "paths to upload" description: "set to true to disable pushing to the cache"
required: true required: false
token: token:
description: "attic token" description: "attic token"
required: false required: false
@ -18,3 +18,4 @@ inputs:
runs: runs:
using: "node16" using: "node16"
main: "dist/index.js" main: "dist/index.js"
post: "dist/post.js"

11
dist/index.js vendored

File diff suppressed because one or more lines are too long

9
dist/post.js vendored Normal file

File diff suppressed because one or more lines are too long

26
flake.lock Normal file
View file

@ -0,0 +1,26 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1689534811,
"narHash": "sha256-jnSUdzD/414d94plCyNlvTJJtiTogTep6t7ZgIKIHiE=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "6cee3b5893090b0f5f0a06b4cf42ca4e60e5d222",
"type": "github"
},
"original": {
"id": "nixpkgs",
"ref": "nixos-unstable",
"type": "indirect"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}

36
flake.nix Normal file
View file

@ -0,0 +1,36 @@
{
description = "";
inputs = {
nixpkgs.url = "nixpkgs/nixos-unstable";
};
outputs =
{ nixpkgs
, ...
}:
let
mkSystems = sys: builtins.map (arch: "${arch}-${sys}") [ "x86_64" "aarch64" ];
systems =
mkSystems "linux"
++ mkSystems "darwin";
forAllSystems = nixpkgs.lib.genAttrs systems;
nixpkgsFor = forAllSystems (system: import nixpkgs { inherit system; });
forEachSystem = fn:
forAllSystems (s: fn nixpkgsFor.${s});
in
{
devShells = forEachSystem (pkgs: {
default = pkgs.mkShell {
packages = with pkgs; [
actionlint
nodePackages.pnpm
];
};
});
formatter = forEachSystem (p: p.nixpkgs-fmt);
};
}

View file

@ -3,7 +3,8 @@
"version": "0.1.0", "version": "0.1.0",
"description": "Cache Nix derivations with attic", "description": "Cache Nix derivations with attic",
"scripts": { "scripts": {
"build": "esbuild src/index.ts --outdir=dist --platform=node --format=cjs --bundle --minify-whitespace --minify-syntax" "build": "esbuild src/index.ts src/post.ts --outdir=dist --platform=node --format=cjs --bundle --minify-whitespace --minify-syntax",
"format": "prettier --write ."
}, },
"keywords": [], "keywords": [],
"author": "Ryan Cao <hello@ryanccn.dev>", "author": "Ryan Cao <hello@ryanccn.dev>",
@ -16,6 +17,7 @@
"devDependencies": { "devDependencies": {
"@types/node": "^16.18.38", "@types/node": "^16.18.38",
"esbuild": "^0.18.14", "esbuild": "^0.18.14",
"prettier": "3.0.0",
"typescript": "^5.1.6" "typescript": "^5.1.6"
}, },
"packageManager": "pnpm@8.6.9" "packageManager": "pnpm@8.6.9"

View file

@ -22,6 +22,9 @@ devDependencies:
esbuild: esbuild:
specifier: ^0.18.14 specifier: ^0.18.14
version: 0.18.14 version: 0.18.14
prettier:
specifier: 3.0.0
version: 3.0.0
typescript: typescript:
specifier: ^5.1.6 specifier: ^5.1.6
version: 5.1.6 version: 5.1.6
@ -299,6 +302,12 @@ packages:
ufo: 1.1.2 ufo: 1.1.2
dev: false dev: false
/prettier@3.0.0:
resolution: {integrity: sha512-zBf5eHpwHOGPC47h0zrPyNn+eAEIdEzfywMoYn2XPi0P44Zp0tSq64rq0xAREh4auw2cJZHo9QUob+NqCQky4g==}
engines: {node: '>=14'}
hasBin: true
dev: true
/tunnel@0.0.6: /tunnel@0.0.6:
resolution: {integrity: sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==} resolution: {integrity: sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==}
engines: {node: '>=0.6.11 <=0.7.0 || >=0.7.3'} engines: {node: '>=0.6.11 <=0.7.0 || >=0.7.3'}

View file

@ -1,12 +1,9 @@
import { install } from "./stages/install"; import { install } from "./stages/install";
import { configure } from "./stages/configure"; import { configure } from "./stages/configure";
import { push } from "./stages/push";
(async () => { const main = async () => {
await install(); await install();
await configure(); await configure();
await push(); };
})().catch((e) => {
console.error(e); main();
process.exit(1);
});

7
src/post.ts Normal file
View file

@ -0,0 +1,7 @@
import { push } from "./stages/push";
const main = async () => {
await push();
};
main();

View file

@ -1,11 +1,24 @@
import { getInput, startGroup, endGroup } from "@actions/core"; import * as core from "@actions/core";
import { exec } from "@actions/exec"; import { exec } from "@actions/exec";
import { getStorePaths } from "../utils";
export const configure = async () => { export const configure = async () => {
startGroup("Configure attic"); core.startGroup("Configure attic");
const endpoint = getInput("endpoint");
const token = getInput("token");
await exec("attic", ["login", "--set-default", "ci", endpoint, token]); try {
endGroup(); const endpoint = core.getInput("endpoint");
const cache = core.getInput("cache");
const token = core.getInput("token");
core.info("Logging in to attic cache");
await exec("attic", ["login", "--set-default", cache, endpoint, token]);
core.info("Collecting store paths before build");
const paths = await getStorePaths();
core.saveState("initial-paths", JSON.stringify(paths));
} catch (e) {
core.setFailed(`Action failed with error: ${e}`);
}
core.endGroup();
}; };

View file

@ -1,4 +1,4 @@
import { startGroup, endGroup } from "@actions/core"; import * as core from "@actions/core";
import { exec } from "@actions/exec"; import { exec } from "@actions/exec";
import { fetch } from "ofetch"; import { fetch } from "ofetch";
@ -7,23 +7,31 @@ import { tmpdir } from "node:os";
import { join } from "node:path"; import { join } from "node:path";
export const install = async () => { export const install = async () => {
startGroup("Install attic"); core.startGroup("Install attic");
core.info("Installing attic");
const installScript = await fetch( const installScript = await fetch(
"https://raw.githubusercontent.com/zhaofengli/attic/main/.github/install-attic-ci.sh" "https://raw.githubusercontent.com/zhaofengli/attic/main/.github/install-attic-ci.sh"
).then((r) => { ).then((r) => {
if (!r.ok) if (!r.ok) {
throw new Error( core.setFailed(`Action failed with error: ${r.statusText}`);
`Failed to fetch install script: ${r.status} ${r.statusText}` core.endGroup();
);
process.exit(1);
}
return r.text(); return r.text();
}); });
const installScriptPath = join(tmpdir(), "install-attic-ci.sh"); try {
const installScriptPath = join(tmpdir(), "install-attic-ci.sh");
await writeFile(installScriptPath, installScript); await writeFile(installScriptPath, installScript);
await exec("bash", [installScriptPath]); core.info("Running install script");
await exec("bash", [installScriptPath]);
} catch (e) {
core.setFailed(`Action failed with error: ${e}`);
}
endGroup(); core.endGroup();
}; };

View file

@ -1,9 +1,27 @@
import { getInput, getMultilineInput } from "@actions/core"; import * as core from "@actions/core";
import { exec } from "@actions/exec"; import { exec } from "@actions/exec";
import { getStorePaths } from "../utils";
export const push = async () => { export const push = async () => {
const cache = getInput("cache"); core.startGroup("Push to Attic");
const paths = getMultilineInput("paths");
await exec("attic", ["push", cache, ...paths]); try {
const skipPush = core.getInput("skip-push");
if (skipPush === "true") {
core.info("Pushing to cache is disabled by skip-push");
} else {
const cache = core.getInput("cache");
core.info("Pushing to cache");
const oldPaths = JSON.parse(core.getState("initial-paths")) as string[];
const newPaths = await getStorePaths();
const addedPaths = newPaths.filter((p) => !oldPaths.includes(p));
await exec("attic", ["push", cache, ...addedPaths]);
}
} catch (e) {
core.setFailed(`Action failed with error: ${e}`);
}
core.endGroup();
}; };

21
src/utils.ts Normal file
View file

@ -0,0 +1,21 @@
import { exec } from "@actions/exec";
import { Writable } from "node:stream";
const streamToString = (stream: Writable): Promise<string> => {
const chunks: Buffer[] = [];
return new Promise((resolve, reject) => {
stream.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
stream.on("error", (err) => reject(err));
stream.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
});
};
export const getStorePaths = async () => {
const outStream = new Writable();
await exec("nix", ["path-info", "--all"], { outStream });
const paths = await streamToString(outStream)
.then((res) => res.split("\n"))
.then((paths) => paths.filter(Boolean));
return paths;
};

7
test.nix Normal file
View file

@ -0,0 +1,7 @@
let
pkgs = import <nixpkgs> { };
time = with builtins; toString currentTime;
in
pkgs.runCommand "${time}-test" { } ''
echo "${time}" > $out
''