Skip to content

Commit db17cc8

Browse files
authored
Upload playground bundle to Cloudflare R2 (#7472)
* Configure Rclone remote * refactor generate_cmijs script * rewrite bundle uploading * fix workflow * add prefix * fix comments * update actions * Sync available version list * fix tar command * add a note * use semver explicitly
1 parent 59f4237 commit db17cc8

File tree

10 files changed

+234
-136
lines changed

10 files changed

+234
-136
lines changed

.github/workflows/ci.yml

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -396,11 +396,23 @@ jobs:
396396
if: matrix.build_playground
397397
run: yarn workspace playground test
398398

399+
- name: Setup Rclone
400+
if: ${{ matrix.build_playground && startsWith(github.ref, 'refs/tags/v') }}
401+
uses: cometkim/rclone-actions/setup-rclone@89de8311dd0ca0b43143329d3daeb1041852ad9f
402+
403+
- name: Configure Rclone remote
404+
if: ${{ matrix.build_playground && startsWith(github.ref, 'refs/tags/v') }}
405+
uses: cometkim/rclone-actions/configure-remote/s3-provider@89de8311dd0ca0b43143329d3daeb1041852ad9f
406+
with:
407+
name: rescript
408+
provider: Cloudflare
409+
endpoint: ${{ vars.CLOUDFLARE_ACCOUNT_ID }}.r2.cloudflarestorage.com
410+
access-key-id: ${{ secrets.CLOUDFLARE_R2_ACCESS_KEY_ID }}
411+
secret-access-key: ${{ secrets.CLOUDFLARE_R2_SECRET_ACCESS_KEY }}
412+
acl: private
413+
399414
- name: Upload playground compiler to CDN
400415
if: ${{ matrix.build_playground && startsWith(github.ref, 'refs/tags/v') }}
401-
env:
402-
KEYCDN_USER: ${{ secrets.KEYCDN_USER }}
403-
KEYCDN_PASSWORD: ${{ secrets.KEYCDN_PASSWORD }}
404416
run: yarn workspace playground upload-bundle
405417

406418
- name: "Upload artifacts: binaries"

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,14 +69,14 @@ artifacts: lib
6969
# Builds the core playground bundle (without the relevant cmijs files for the runtime)
7070
playground:
7171
dune build --profile browser
72-
cp ./_build/default/compiler/jsoo/jsoo_playground_main.bc.js packages/playground/compiler.js
72+
cp -f ./_build/default/compiler/jsoo/jsoo_playground_main.bc.js packages/playground/compiler.js
7373

7474
# Creates all the relevant core and third party cmij files to side-load together with the playground bundle
7575
playground-cmijs: artifacts
7676
yarn workspace playground build
7777

7878
# Builds the playground, runs some e2e tests and releases the playground to the
79-
# CDN (requires KEYCDN_USER and KEYCDN_PASSWORD set in the env variables)
79+
# Cloudflare R2 (requires Rclone `rescript:` remote)
8080
playground-release: playground playground-cmijs
8181
yarn workspace playground test
8282
yarn workspace playground upload-bundle

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,11 @@
8484
"devDependencies": {
8585
"@biomejs/biome": "1.9.4",
8686
"@types/node": "^20.14.9",
87+
"@types/semver": "^7.7.0",
8788
"@yarnpkg/types": "^4.0.1",
8889
"mocha": "10.8.2",
8990
"nyc": "15.0.0",
91+
"semver": "^7.7.2",
9092
"typescript": "5.8.2"
9193
},
9294
"workspaces": [

packages/playground/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,5 @@ yarn.lock
3232

3333
/packages/
3434
/compiler.js
35+
36+
.tmp/

packages/playground/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
"scripts": {
66
"clean": "rescript clean",
77
"test": "node ./playground_test.cjs",
8-
"build": "rescript clean && rescript build && node ./scripts/generate_cmijs.mjs && rollup -c",
9-
"upload-bundle": "./scripts/upload_bundle.sh",
8+
"build": "rescript clean && rescript build && node scripts/generate_cmijs.mjs && rollup -c",
9+
"upload-bundle": "node scripts/upload_bundle.mjs",
1010
"revalidate": "./scripts/website_update_playground.sh"
1111
},
1212
"dependencies": {
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// @ts-check
2+
3+
import * as child_process from "node:child_process";
4+
import * as path from "node:path";
5+
6+
export const compilerRootDir = path.join(
7+
import.meta.dirname,
8+
"..",
9+
"..",
10+
"..",
11+
);
12+
13+
// The playground-bundling root dir
14+
export const playgroundDir = path.join(import.meta.dirname, "..");
15+
16+
// Final target output directory where all the cmijs will be stored
17+
export const playgroundPackagesDir = path.join(playgroundDir, "packages");
18+
19+
/**
20+
* @param {string} cmd
21+
* @param {child_process.ExecSyncOptions} [opts={}]
22+
*/
23+
export function exec(cmd, opts = {}) {
24+
console.log(`>>>>>> running command: ${cmd}`);
25+
const result = child_process.execSync(cmd, {
26+
cwd: playgroundDir,
27+
stdio: "inherit",
28+
...opts,
29+
encoding: "utf8",
30+
});
31+
console.log("<<<<<<");
32+
return result || "";
33+
}

packages/playground/scripts/generate_cmijs.mjs

Lines changed: 31 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -13,92 +13,54 @@
1313
* playground bundle.
1414
*/
1515

16-
import * as child_process from "node:child_process";
1716
import * as fs from "node:fs";
1817
import * as path from "node:path";
1918

2019
import resConfig from "../rescript.json" with { type: "json" };
20+
import {
21+
exec,
22+
compilerRootDir,
23+
playgroundPackagesDir,
24+
} from "./common.mjs";
2125

22-
const RESCRIPT_COMPILER_ROOT_DIR = path.join(
23-
import.meta.dirname,
24-
"..",
25-
"..",
26-
"..",
27-
);
26+
exec("yarn rescript clean");
27+
exec("yarn rescript");
2828

29-
// The playground-bundling root dir
30-
const PLAYGROUND_DIR = path.join(import.meta.dirname, "..");
31-
32-
// Final target output directory where all the cmijs will be stored
33-
const PACKAGES_DIR = path.join(PLAYGROUND_DIR, "packages");
29+
// We need to build the compiler's builtin modules as a separate cmij.
30+
// Otherwise we can't use them for compilation within the playground.
31+
buildCmij(compilerRootDir, "compiler-builtins");
3432

35-
// Making sure this directory exists, since it's not checked in to git
36-
fs.mkdirSync(PACKAGES_DIR, { recursive: true });
33+
const packages = resConfig["bs-dependencies"];
34+
for (const pkgName of packages) {
35+
buildCmij(
36+
path.join(compilerRootDir, "node_modules", pkgName),
37+
pkgName,
38+
);
39+
}
3740

3841
/**
39-
* @param {string} cmd
42+
* @param {string} pkgDir
43+
* @param {string} pkgName
4044
*/
41-
function e(cmd) {
42-
console.log(`>>>>>> running command: ${cmd}`);
43-
child_process.execSync(cmd, {
44-
cwd: PLAYGROUND_DIR,
45-
encoding: "utf8",
46-
stdio: [0, 1, 2],
47-
});
48-
console.log("<<<<<<");
49-
}
50-
51-
e("yarn rescript clean");
52-
e("yarn rescript");
53-
54-
const packages = resConfig["bs-dependencies"];
55-
56-
// We need to build the compiler's builtin modules as a separate cmij.
57-
// Otherwise we can't use them for compilation within the playground.
58-
function buildCompilerCmij() {
59-
const rescriptLibOcamlFolder = path.join(
60-
RESCRIPT_COMPILER_ROOT_DIR,
45+
function buildCmij(pkgDir, pkgName) {
46+
const libOcamlFolder = path.join(
47+
pkgDir,
6148
"lib",
6249
"ocaml",
6350
);
6451

65-
const outputFolder = path.join(PACKAGES_DIR, "compiler-builtins");
52+
const outputFolder = path.join(playgroundPackagesDir, pkgName);
6653
fs.mkdirSync(outputFolder, { recursive: true });
6754

6855
const cmijFile = path.join(outputFolder, "cmij.js");
69-
70-
e(
71-
`find ${rescriptLibOcamlFolder} -name "*.cmi" -or -name "*.cmj" | xargs -n1 basename | xargs js_of_ocaml build-fs -o ${cmijFile} -I ${rescriptLibOcamlFolder}`,
72-
);
56+
const inputFiles = fs.readdirSync(libOcamlFolder).filter(isCmij).join(" ");
57+
exec(`js_of_ocaml build-fs -o ${cmijFile} -I ${libOcamlFolder} ${inputFiles}`);
7358
}
7459

75-
function buildThirdPartyCmijs() {
76-
for (const pkg of packages) {
77-
const libOcamlFolder = path.join(
78-
RESCRIPT_COMPILER_ROOT_DIR,
79-
"node_modules",
80-
pkg,
81-
"lib",
82-
"ocaml",
83-
);
84-
const libEs6Folder = path.join(
85-
RESCRIPT_COMPILER_ROOT_DIR,
86-
"node_modules",
87-
pkg,
88-
"lib",
89-
"es6",
90-
);
91-
const outputFolder = path.join(PACKAGES_DIR, pkg);
92-
fs.mkdirSync(outputFolder, { recursive: true });
93-
94-
const cmijFile = path.join(outputFolder, "cmij.js");
95-
96-
e(`find ${libEs6Folder} -name '*.js' -exec cp {} ${outputFolder} \\;`);
97-
e(
98-
`find ${libOcamlFolder} -name "*.cmi" -or -name "*.cmj" | xargs -n1 basename | xargs js_of_ocaml build-fs -o ${cmijFile} -I ${libOcamlFolder}`,
99-
);
100-
}
60+
/**
61+
* @param {string} basename
62+
* @return {boolean}
63+
*/
64+
function isCmij(basename) {
65+
return /\.cm(i|j)$/.test(basename);
10166
}
102-
103-
buildCompilerCmij();
104-
buildThirdPartyCmijs();
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
#!/usr/bin/env node
2+
3+
// @ts-check
4+
5+
// This script will publish the compiler.js bundle / packages cmij.js files to our Cloudclare R2.
6+
// The target folder on R2 bucket will be the compiler.js' version number.
7+
// This script requires `rclone` and `zstd` to be installed.
8+
9+
import * as fs from "node:fs";
10+
import * as path from "node:path";
11+
import * as readline from "node:readline/promises";
12+
import { createRequire } from "node:module";
13+
import semver from "semver";
14+
15+
import {
16+
exec,
17+
playgroundDir,
18+
playgroundPackagesDir,
19+
} from "./common.mjs";
20+
21+
const require = createRequire(import.meta.url);
22+
// @ts-ignore
23+
const { rescript_compiler } = require('../compiler.js');
24+
25+
/**
26+
* @type {number}
27+
*/
28+
const version = rescript_compiler.make().rescript.version;
29+
const tag = "v" + version;
30+
31+
console.log("Uploading playground artifacts for %s", tag);
32+
if (!process.env.CI) {
33+
const rl = readline.createInterface({
34+
input: process.stdin,
35+
output: process.stdout,
36+
});
37+
const answer = await rl.question("Do you want to proceed? [y/N]: ");
38+
rl.close();
39+
40+
if (answer.toLowerCase() !== "y") {
41+
console.log("Cancelled");
42+
process.exit(1);
43+
}
44+
}
45+
46+
const rcloneOpts = (process.env.CI
47+
? [
48+
"--stats 5",
49+
"--checkers 5000",
50+
"--transfers 8",
51+
"--buffer-size 128M",
52+
"--s3-chunk-size 128M",
53+
"--s3-upload-concurrency 8",
54+
]
55+
: [
56+
"--progress",
57+
"--checkers 5000",
58+
"--transfers 16",
59+
"--buffer-size 128M",
60+
"--s3-chunk-size 128M",
61+
"--s3-upload-concurrency 16",
62+
]).join(" ");
63+
64+
const remote = process.env.RCLONE_REMOTE || "rescript";
65+
const bucket = "cdn-assets";
66+
67+
// Create a temporary directory for bundling
68+
const tmpDir = path.join(playgroundDir, ".tmp");
69+
const artifactsDir = path.join(tmpDir, tag);
70+
const archivePath = path.join(tmpDir, `${tag}.tar.zst`);
71+
fs.rmSync(tmpDir, { recursive: true, force: true });
72+
fs.mkdirSync(artifactsDir, { recursive: true });
73+
74+
console.log("Copying compiler.js");
75+
fs.copyFileSync(
76+
path.join(playgroundDir, "compiler.js"),
77+
path.join(artifactsDir, "compiler.js"),
78+
);
79+
80+
console.log("Copying packages");
81+
fs.cpSync(playgroundPackagesDir, artifactsDir, { recursive: true });
82+
83+
// Create tar.zst archive
84+
console.log("Creating archive...");
85+
exec(`tar \\
86+
--use-compress-program="zstd -T0 --adapt --exclude-compressed" \\
87+
-cf "${archivePath}" \\
88+
-C "${artifactsDir}" .
89+
`);
90+
91+
// TODO: This will no longer be needed after the KeyCDN to Cloudflare transition is complete.
92+
console.log(`Uploading v${version} artifacts...`);
93+
exec(`rclone sync ${rcloneOpts} --fast-list \\
94+
"${artifactsDir}" \\
95+
"${remote}:${bucket}/${tag}"
96+
`);
97+
98+
console.log("Uploading archive...");
99+
exec(`rclone copyto ${rcloneOpts} \\
100+
"${archivePath}" \\
101+
"${remote}:${bucket}/playground-bundles/${tag}.tar.zst"
102+
`);
103+
104+
console.log("Update versions.json");
105+
{
106+
const bundles = exec(
107+
`rclone lsf --include="*.tar.zst" rescript:cdn-assets/playground-bundles`,
108+
{ stdio: ['ignore', 'pipe', 'pipe'] },
109+
).trimEnd().split("\n");
110+
111+
/** @type {string[]} */
112+
let playgroundVersions = [];
113+
for (const bundle of bundles) {
114+
playgroundVersions.push(bundle.replace(".tar.zst", ""));
115+
}
116+
playgroundVersions = semver.sort(playgroundVersions);
117+
118+
console.log('Versions: ', playgroundVersions);
119+
120+
const versionsPath = path.join(tmpDir, "versions.json");
121+
fs.writeFileSync(
122+
versionsPath,
123+
JSON.stringify(playgroundVersions),
124+
);
125+
exec(`rclone copyto ${rcloneOpts} \\
126+
"${versionsPath}" \\
127+
"${remote}:${bucket}/playground-bundles/versions.json"
128+
`);
129+
}

0 commit comments

Comments
 (0)