@@ -19,12 +19,118 @@ runs:
1919 using : " composite"
2020 steps :
2121 - run : |
22- EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64)
23- echo "markdown<<${EOF}" >> "${GITHUB_OUTPUT}"
24- echo "$(${GITHUB_ACTION_PATH}/container-size-diff.sh ${INPUT_FROM_CONTAINER} ${INPUT_TO_CONTAINER})" >> "${GITHUB_OUTPUT}"
25- echo "${EOF}" >> "${GITHUB_OUTPUT}"
26- id: size-diff
22+ cat > package.json << 'EOF'
23+ {
24+ "dependencies": {
25+ "execa": "^9.6.0",
26+ "filesize": "^11.0.2",
27+ "lodash": "^4.0.0",
28+ "markdown-table": "^3.0.4"
29+ }
30+ }
31+ EOF
32+ shell: bash
33+
34+ - uses : actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
35+ with :
36+ cache : npm
37+ cache-dependency-path : package.json
38+ node-version : 22.x
39+
40+ - run : npm install
2741 shell : bash
42+
43+ - uses : actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
44+ id : size-diff
2845 env :
29- INPUT_FROM_CONTAINER: ${{ inputs.from-container }}
30- INPUT_TO_CONTAINER: ${{ inputs.to-container }}
46+ FROM_CONTAINER : ${{ inputs.from-container }}
47+ TO_CONTAINER : ${{ inputs.to-container }}
48+ with :
49+ script : |
50+ const { execaSync } = require("execa");
51+ const { filesize } = require("filesize");
52+ const { markdownTable } = require("markdown-table");
53+ const _ = require("lodash");
54+
55+ const fromContainer = process.env.FROM_CONTAINER;
56+ const toContainer = process.env.TO_CONTAINER;
57+
58+ async function getSizes(container) {
59+ try {
60+ const { stdout } = execaSync("docker", ["manifest", "inspect", "-v", container]);
61+ const manifests = _.castArray(JSON.parse(stdout.trim()));
62+
63+ return _(manifests)
64+ .map(m => {
65+ const platform = _.get(m, 'Descriptor.platform', {});
66+
67+ const key = _.compact([
68+ platform.os,
69+ platform.architecture,
70+ platform.variant,
71+ platform["os.version"]
72+ ])
73+ .join("/");
74+
75+ const layers = _.get(m, 'OCIManifest.layers') || _.get(m, 'layers', []);
76+ const total = _.sumBy(layers, 'size');
77+
78+ return [key, total];
79+ })
80+ .fromPairs()
81+ .value();
82+ } catch (e) {
83+ core.error(`Failed to inspect ${container}: ${e.message}`);
84+ return {};
85+ }
86+ }
87+
88+ const fromSizes = await getSizes(fromContainer);
89+ const toSizes = await getSizes(toContainer);
90+
91+ const platforms = _(fromSizes)
92+ .keys()
93+ .union(_.keys(toSizes))
94+ .filter(platform => platform && !platform.includes("unknown"))
95+ .sort()
96+ .value();
97+
98+ const platformRow = (platform) => {
99+ const previous = _.get(fromSizes, platform, 0);
100+ const current = _.get(toSizes, platform, 0);
101+ const change = current - previous;
102+
103+ const percentage = previous
104+ ? `${change >= 0 ? "+" : ""}${_.round(change / previous * 100, 2)}%`
105+ : current ? "+∞" : "+0.00%";
106+
107+ const icon = _.cond([
108+ [change => change < 0, _.constant("🔽")],
109+ [change => change > 0, _.constant("🔼")],
110+ [_.stubTrue, _.constant("🔄")]
111+ ])(change);
112+
113+ return [
114+ platform,
115+ filesize(previous),
116+ filesize(current),
117+ `${change >= 0 ? "+" : ""}${filesize(Math.abs(change))} (${percentage})`,
118+ icon
119+ ];
120+ };
121+
122+ const rows = [
123+ ["OS/Platform", "Previous", "Current", "Change", "Trend"],
124+ ..._.map(platforms, platformRow)
125+ ];
126+
127+ const output = `## 📦 Container Size Analysis
128+
129+ > [!NOTE]
130+ > Comparing \`${fromContainer}\` ➔ \`${toContainer}\`
131+
132+ ### 📈 Size Comparison Table
133+
134+ ${markdownTable(rows, { align: ["l", "c", "c", "c", "c"] })}`;
135+
136+ core.setOutput("markdown", output);
0 commit comments