@bigconfig/bb 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +28 -4
  2. package/bin/bb.js +153 -3
  3. package/package.json +13 -22
package/README.md CHANGED
@@ -37,6 +37,27 @@ and may prompt for a sudo password; in non-interactive environments without
37
37
  passwordless sudo it will fail with an actionable message — pre-install git to
38
38
  avoid this entirely.
39
39
 
40
+ ## bb.edn bootstrap (optional)
41
+
42
+ If the current directory has **no `bb.edn`** and `BB_EDN_REPO=owner/project`
43
+ is set, that repo's `bb.edn` is downloaded (pinned to its default branch's
44
+ latest commit) and the repo itself is added to `:deps` as
45
+ `io.github.<owner>/<project> {:git/sha "<sha>"}`. The edit is done with
46
+ `borkdude/rewrite-edn`, so existing comments and formatting are preserved.
47
+
48
+ Any dependency using `:local/root` (in `:deps` or a task's `:extra-deps`) is
49
+ removed first, since those paths don't exist once the file is downloaded.
50
+ Valid Maven/git deps are kept.
51
+
52
+ - Skipped entirely if `BB_EDN_REPO` is unset or a `bb.edn` already exists.
53
+ - Fatal error if the repo is missing/inaccessible or has no `bb.edn`.
54
+ - Set `GITHUB_TOKEN` for private repos or to avoid GitHub's unauthenticated
55
+ API rate limit.
56
+
57
+ ```sh
58
+ BB_EDN_REPO=my-org/shared-tasks npx @bigconfig/bb tasks
59
+ ```
60
+
40
61
  ## Cache location
41
62
 
42
63
  A single shared directory, reused across all projects:
@@ -50,10 +71,13 @@ Delete that directory to force a clean reinstall.
50
71
 
51
72
  ## Configuration
52
73
 
53
- | Env var | Default | Effect |
54
- | ------------- | ---------- | --------------------------------------- |
55
- | `BB_VERSION` | `1.12.196` | babashka release version to install |
56
- | `JDK_VERSION` | `21` | Temurin feature version (e.g. `17`, `21`, `25`) |
74
+ | Env var | Default | Effect |
75
+ | --------------------- | ---------- | ----------------------------------------------- |
76
+ | `BB_VERSION` | `1.12.196` | babashka release version to install |
77
+ | `JDK_VERSION` | `21` | Temurin feature version (e.g. `17`, `21`, `25`) |
78
+ | `BB_EDN_REPO` | _(unset)_ | `owner/project` to bootstrap a `bb.edn` from (see below) |
79
+ | `GITHUB_TOKEN` | _(unset)_ | Used for `BB_EDN_REPO` (private repos / higher API rate limit) |
80
+ | `REWRITE_EDN_VERSION` | `0.5.9` | `borkdude/rewrite-edn` version used to edit the `bb.edn` |
57
81
 
58
82
  ## Supported platforms
59
83
 
package/bin/bb.js CHANGED
@@ -14,6 +14,7 @@ const { pipeline } = require('stream/promises');
14
14
  // Pinned, known-good versions. Overridable via env.
15
15
  const DEFAULT_BB_VERSION = process.env.BB_VERSION || '1.12.196';
16
16
  const DEFAULT_JDK_VERSION = process.env.JDK_VERSION || '21';
17
+ const REWRITE_EDN_VERSION = process.env.REWRITE_EDN_VERSION || '0.5.9';
17
18
 
18
19
  const TAG = '[@bigconfig/bb]';
19
20
 
@@ -349,9 +350,10 @@ function ensureGit() {
349
350
  }
350
351
  }
351
352
 
352
- // --- Run bb --------------------------------------------------------------
353
+ // --- bb.edn bootstrap ----------------------------------------------------
353
354
 
354
- function runBb(bbPath, args, javaHome) {
355
+ // Env augmented so the spawned bb finds the cached JDK; nothing system-wide.
356
+ function bbEnv(javaHome) {
355
357
  const env = { ...process.env };
356
358
  if (javaHome) {
357
359
  env.JAVA_HOME = javaHome;
@@ -360,6 +362,147 @@ function runBb(bbPath, args, javaHome) {
360
362
  path.delimiter +
361
363
  (process.env.PATH || '');
362
364
  }
365
+ return env;
366
+ }
367
+
368
+ function ghFetch(url, accept) {
369
+ const headers = {
370
+ 'user-agent': '@bigconfig/bb',
371
+ accept: accept || 'application/vnd.github+json',
372
+ 'x-github-api-version': '2022-11-28',
373
+ };
374
+ if (process.env.GITHUB_TOKEN) {
375
+ headers.authorization = `Bearer ${process.env.GITHUB_TOKEN}`;
376
+ }
377
+ return fetch(url, { headers, redirect: 'follow' });
378
+ }
379
+
380
+ // Reads/edits the EDN with borkdude/rewrite-edn so comments & formatting of
381
+ // untouched nodes survive. Params are passed via env to avoid quoting issues.
382
+ const REWRITE_SCRIPT = `(require '[babashka.deps :as deps])
383
+ (deps/add-deps {:deps {'borkdude/rewrite-edn {:mvn/version (System/getenv "BBEDN_REWRITE_VERSION")}}})
384
+ (require '[borkdude.rewrite-edn :as r])
385
+
386
+ (defn local-root? [coord]
387
+ (and (map? coord) (contains? coord :local/root)))
388
+
389
+ ;; Drop every entry whose effective coord (sexpr respects #_ discard) is a
390
+ ;; map containing :local/root. Returns the (possibly unchanged) map node.
391
+ (defn strip-local-root [m-node]
392
+ (if (nil? m-node)
393
+ m-node
394
+ (let [m (r/sexpr m-node)]
395
+ (reduce (fn [acc k]
396
+ (if (local-root? (get m k)) (r/dissoc acc k) acc))
397
+ m-node
398
+ (keys m)))))
399
+
400
+ (let [in (System/getenv "BBEDN_IN")
401
+ out (System/getenv "BBEDN_OUT")
402
+ owner (System/getenv "BBEDN_OWNER")
403
+ proj (System/getenv "BBEDN_PROJECT")
404
+ sha (System/getenv "BBEDN_SHA")
405
+ dep (symbol (str "io.github." owner) proj)
406
+ nodes (r/parse-string (slurp in))
407
+ ;; 1. strip :local/root from top-level :deps
408
+ nodes (if (r/get nodes :deps)
409
+ (r/update nodes :deps strip-local-root)
410
+ nodes)
411
+ ;; 2. strip :local/root from each task's :extra-deps
412
+ tasks (some-> (r/get nodes :tasks) r/sexpr)
413
+ nodes (reduce (fn [acc tk]
414
+ (let [tv (get tasks tk)]
415
+ (if (and (map? tv) (map? (:extra-deps tv)))
416
+ (r/update-in acc [:tasks tk :extra-deps] strip-local-root)
417
+ acc)))
418
+ nodes
419
+ (keys tasks))
420
+ ;; 3. ensure :deps exists, then inject the repo as a git dep
421
+ nodes (if (nil? (r/get nodes :deps)) (r/assoc nodes :deps {}) nodes)
422
+ nodes (r/assoc-in nodes [:deps dep] {:git/sha sha})]
423
+ (spit out (str nodes)))
424
+ `;
425
+
426
+ // When the cwd has no bb.edn and BB_EDN_REPO=owner/project is set, fetch that
427
+ // repo's bb.edn (pinned to its default-branch HEAD) and add the repo itself
428
+ // as an io.github git dep. Disabled (skipped) if the env var is unset.
429
+ async function ensureBbEdn(bbPath, javaHome) {
430
+ const repo = process.env.BB_EDN_REPO;
431
+ if (!repo) return; // step disabled — proceed straight to bb
432
+ const target = path.join(process.cwd(), 'bb.edn');
433
+ if (fs.existsSync(target)) return; // already present — leave it alone
434
+
435
+ const m = repo.trim().match(/^([^/\s@]+)\/([^/\s@]+)$/);
436
+ if (!m) {
437
+ throw new Error(`BB_EDN_REPO must be "owner/project" (got "${repo}")`);
438
+ }
439
+ const [, owner, project] = m;
440
+ const slug = `${owner}/${project}`;
441
+ const api = `https://api.github.com/repos/${owner}/${project}`;
442
+
443
+ const cr = await ghFetch(`${api}/commits?per_page=1`);
444
+ if (cr.status === 404) {
445
+ throw new Error(
446
+ `BB_EDN_REPO: ${slug} not found or not accessible ` +
447
+ `(set GITHUB_TOKEN for private repos)`
448
+ );
449
+ }
450
+ if (!cr.ok) {
451
+ throw new Error(`GitHub API error ${cr.status} resolving ${slug}`);
452
+ }
453
+ const commits = await cr.json();
454
+ const sha = Array.isArray(commits) && commits[0] && commits[0].sha;
455
+ if (!sha) throw new Error(`${slug} has no commits`);
456
+
457
+ const fr = await ghFetch(
458
+ `${api}/contents/bb.edn?ref=${sha}`,
459
+ 'application/vnd.github.raw'
460
+ );
461
+ if (fr.status === 404) {
462
+ throw new Error(`${slug} (at ${sha.slice(0, 7)}) has no bb.edn`);
463
+ }
464
+ if (!fr.ok) {
465
+ throw new Error(`GitHub API error ${fr.status} fetching bb.edn from ${slug}`);
466
+ }
467
+ const ednText = await fr.text();
468
+
469
+ log(`Bootstrapping bb.edn from ${slug}@${sha.slice(0, 7)}...`);
470
+ const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'bb-edn-'));
471
+ try {
472
+ const inFile = path.join(tmp, 'in.edn');
473
+ const script = path.join(tmp, 'rewrite.clj');
474
+ fs.writeFileSync(inFile, ednText);
475
+ fs.writeFileSync(script, REWRITE_SCRIPT);
476
+ const env = bbEnv(javaHome);
477
+ Object.assign(env, {
478
+ BBEDN_IN: inFile,
479
+ BBEDN_OUT: target,
480
+ BBEDN_OWNER: owner,
481
+ BBEDN_PROJECT: project,
482
+ BBEDN_SHA: sha,
483
+ BBEDN_REWRITE_VERSION: REWRITE_EDN_VERSION,
484
+ });
485
+ const r = spawnSync(bbPath, [script], {
486
+ stdio: ['ignore', 'ignore', 'inherit'],
487
+ cwd: process.cwd(),
488
+ env,
489
+ });
490
+ if (r.error || r.status !== 0) {
491
+ const why = r.error ? r.error.message : `exit ${r.status}`;
492
+ throw new Error(`failed to write bb.edn via rewrite-edn (${why})`);
493
+ }
494
+ if (!fs.existsSync(target)) {
495
+ throw new Error('rewrite-edn step did not produce a bb.edn');
496
+ }
497
+ } finally {
498
+ rmrf(tmp);
499
+ }
500
+ }
501
+
502
+ // --- Run bb --------------------------------------------------------------
503
+
504
+ function runBb(bbPath, args, javaHome) {
505
+ const env = bbEnv(javaHome);
363
506
 
364
507
  const child = spawn(bbPath, args, {
365
508
  stdio: 'inherit',
@@ -393,6 +536,7 @@ async function main(args) {
393
536
  const bbPath = await ensureBabashka(p);
394
537
  const javaHome = await ensureJdk(p);
395
538
  ensureGit();
539
+ await ensureBbEdn(bbPath, javaHome);
396
540
  const code = await runBb(bbPath, args, javaHome);
397
541
  process.exit(code);
398
542
  }
@@ -405,4 +549,10 @@ if (require.main === module) {
405
549
  }
406
550
 
407
551
  // Exported for tests / inspection.
408
- module.exports = { resolvePlatform, cacheRoot, findJavaHome, ensureGit };
552
+ module.exports = {
553
+ resolvePlatform,
554
+ cacheRoot,
555
+ findJavaHome,
556
+ ensureGit,
557
+ ensureBbEdn,
558
+ };
package/package.json CHANGED
@@ -1,24 +1,15 @@
1
1
  {
2
- "name": "@bigconfig/bb",
3
- "version": "0.1.0",
4
- "description": "Bootstrap and run babashka (bb): installs babashka and a Temurin JDK on first use if missing, then forwards all arguments to bb.",
5
- "bin": {
6
- "bb": "bin/bb.js"
7
- },
8
- "files": [
9
- "bin/",
10
- "README.md"
11
- ],
12
- "engines": {
13
- "node": ">=18"
14
- },
15
- "keywords": [
16
- "babashka",
17
- "bb",
18
- "clojure",
19
- "jdk",
20
- "cli"
21
- ],
22
- "license": "MIT",
23
- "type": "commonjs"
2
+ "name": "@bigconfig/bb",
3
+ "version": "0.2.0",
4
+ "description": "Bootstrap and run babashka (bb): installs babashka and a Temurin JDK on first use if missing, then forwards all arguments to bb.",
5
+ "bin": {
6
+ "bb": "bin/bb.js"
7
+ },
8
+ "files": ["bin/", "README.md"],
9
+ "engines": {
10
+ "node": ">=18"
11
+ },
12
+ "keywords": ["babashka", "bb", "clojure", "jdk", "cli"],
13
+ "license": "MIT",
14
+ "type": "commonjs"
24
15
  }