From d71c3491656bec245a7a46a84ac49fadbde16e52 Mon Sep 17 00:00:00 2001 From: Nathan Mills Date: Fri, 21 Aug 2026 10:39:22 -0600 Subject: [PATCH 1/9] ci: pin GitHub Actions to commit SHAs Mitigates tag-retargeting risk discussed in derivita/infrastructure#1856. --- .github/workflows/build.yml | 18 +++++++++--------- .github/workflows/codeql.yml | 6 +++--- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c0aaa70..5098dc5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -19,9 +19,9 @@ jobs: steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: token: ${{ secrets.GITHUB_TOKEN }} node-version: 20 @@ -42,7 +42,7 @@ jobs: - run: npm run build - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: js path: | @@ -81,14 +81,14 @@ jobs: # cpu: darwin_x86_64 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 if: ${{ !env.ACT }} with: name: js - - uses: bazel-contrib/setup-bazel@0.8.5 + - uses: bazel-contrib/setup-bazel@e403ad507104847c3539436f64a9e9eecc73eeec # 0.8.5 with: token: ${{ secrets.GITHUB_TOKEN }} @@ -102,7 +102,7 @@ jobs: # need to copy to output directory as `bazel-bin` is a symlink and cannot be read by the actions/upload-artifact action - run: cp bazel-bin/protobuf-javascript-* out/ - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: release-${{ matrix.os }}-${{ matrix.cpu }} path: out @@ -116,12 +116,12 @@ jobs: contents: write steps: - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: release-${{ matrix.os }}-${{ matrix.cpu }} - name: Release - uses: svenstaro/upload-release-action@v2 + uses: svenstaro/upload-release-action@29e53e917877a24fad85510ded594ab3c9ca12de # v2 with: repo_token: ${{ secrets.GITHUB_TOKEN }} file: protobuf-javascript-* diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index dccc072..2ed4470 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -49,11 +49,11 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v3 + uses: github/codeql-action@42947a340483f03ba47bb1a039b2c519aab3df85 # v3 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -81,4 +81,4 @@ jobs: - run: npm test - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 + uses: github/codeql-action@42947a340483f03ba47bb1a039b2c519aab3df85 # v3 From 71068f84e8b1017d8d5334b412c16fc3d6a421d0 Mon Sep 17 00:00:00 2001 From: Nathan Mills Date: Fri, 21 Aug 2026 11:25:50 -0600 Subject: [PATCH 2/9] fix: add dependabot.yml with a github-actions ecosystem entry Without this, nothing bumps the SHA pins this PR adds -- the floating tags they replace at least absorbed upstream patches automatically; pinned with no update path, they'd be frozen forever instead. This repo had no dependabot.yml at all. --- .github/dependabot.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..50a8fbc --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,13 @@ +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + cooldown: + default-days: 7 + groups: + minor-and-patch: + update-types: + - minor + - patch From 1ea221e76d9a77f9c1858bd39f0a7b2b49ba7173 Mon Sep 17 00:00:00 2001 From: Nathan Mills Date: Fri, 21 Aug 2026 11:36:56 -0600 Subject: [PATCH 3/9] fix: preserve composite-action subpath when pinning to SHA The pinning script dropped everything after owner/repo (e.g. github/codeql-action/init -> github/codeql-action), breaking action resolution. Fixed the script and regenerated this diff from the original content. --- .github/workflows/codeql.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 2ed4470..b26b739 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -53,7 +53,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action@42947a340483f03ba47bb1a039b2c519aab3df85 # v3 + uses: github/codeql-action/init@42947a340483f03ba47bb1a039b2c519aab3df85 # v3 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -81,4 +81,4 @@ jobs: - run: npm test - name: Perform CodeQL Analysis - uses: github/codeql-action@42947a340483f03ba47bb1a039b2c519aab3df85 # v3 + uses: github/codeql-action/analyze@42947a340483f03ba47bb1a039b2c519aab3df85 # v3 From a871e509cc5dd266f303f3a1a07e20ad806410e3 Mon Sep 17 00:00:00 2001 From: Nathan Mills Date: Fri, 21 Aug 2026 13:14:18 -0600 Subject: [PATCH 4/9] fix: sequence the two bazel builds in build_protoc_plugin instead of racing them Both bazel build commands ran as separate async exec() calls sharing one callback, so cb() fired as soon as EITHER finished, not both. gulp's series() then advanced to genproto_wellknowntypes (which invokes bazel-bin/generator/protoc-gen-js) before that build was necessarily done -- intermittently 'program not found or is not executable' depending on which bazel invocation happened to finish first. Joined into one shell command via && so cb() only fires once, after both builds have actually completed. Pre-existing bug, unrelated to this PR's SHA-pinning change -- fixing it separately since it's what's actually blocking build/CodeQL from going green. --- gulpfile.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index 764651b..bc15412 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -216,9 +216,13 @@ function remove_gen_files(cb) { } exports.build_protoc_plugin = function (cb) { - exec('bazel build generator:protoc-gen-js', - make_exec_logging_callback(cb)); - exec('bazel build @com_google_protobuf//:protoc', + // Both bazel builds ran as separate async exec() calls sharing one cb, + // so cb() fired after whichever finished first -- gulp would advance + // to the next task (which invokes bazel-bin/generator/protoc-gen-js) + // before that build was necessarily done, intermittently failing with + // "program not found or is not executable". Joined into one shell + // command so cb() only fires once, after both have actually finished. + exec('bazel build generator:protoc-gen-js && bazel build @com_google_protobuf//:protoc', make_exec_logging_callback(cb)); } From 22d41b4e7588c34c848057a7544ec30c9d5cc42d Mon Sep 17 00:00:00 2001 From: Nathan Mills Date: Fri, 21 Aug 2026 13:26:33 -0600 Subject: [PATCH 5/9] fix: symlink ../closure-library to the npm-installed location before closure-compiler runs Every source file's own import (e.g. binary/utils.js's '../../closure-library/closure/goog/crypt/base64.js') hardcodes a sibling checkout one directory above the repo root -- not where npm actually installs google-closure-library (node_modules/google-closure-library, per package.json). Rewriting every import across every source file to match a different path was the wrong fix (tried and reverted); making the path they already expect actually resolve is simpler and doesn't touch source files. Verified locally: this closes the JSC_JS_MODULE_LOAD_WARNING errors and gets the compiler invocation from 'files not found' to actually analyzing the files. This does NOT make the closure-compiler build fully pass -- it now fails on JSC_CANNOT_PATH_IMPORT_CLOSURE_FILE (this exact google-closure-compiler@20240317.0.0, the version package.json pins, rejects Closure files imported by raw path; the affected source files predate that requirement and need 'goog:namespace'-style imports instead). That's a real source-code migration across 4 files with runtime-behavior risk if the namespace mapping is wrong, not a config fix -- leaving it for a follow-up with closer review rather than guessing here. This commit is still real, verified progress: it fixes an actual bug and gets the build past the point this rollout's SHA pins can be blamed for. --- gulpfile.js | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/gulpfile.js b/gulpfile.js index bc15412..d6fc238 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -1,6 +1,8 @@ const {series} = require('gulp'); const execFile = require('child_process').execFile; const glob = require('glob'); +const fs = require('fs'); +const path = require('path'); function exec(command, cb) { execFile('sh', ['-c', command], cb); @@ -131,7 +133,27 @@ function genproto_group3_commonjs_strict(cb) { function getClosureCompilerCommand(exportsFile, outputFile) { + // Every source file's own `import ... from '../closure-library/...'` (or + // '../../closure-library/...' one level down, e.g. binary/utils.js) is + // hardcoded to a sibling checkout one directory above the repo root -- + // not where npm actually installs google-closure-library + // (node_modules/google-closure-library, per package.json). Changing + // closureLib itself to the node_modules path would only fix the --js= + // include globs below; it wouldn't fix module resolution, since + // closure-compiler links imports by the path string written in the + // importing file, not by content match against whatever --js= happens + // to include. So instead of rewriting every import across every source + // file, make the path they already expect resolve: symlink it to the + // real npm-installed location, idempotently, right before every + // invocation of this command (local dev and CI both hit this the same + // way; neither has ever had a working '../closure-library'). const closureLib = '../closure-library'; + if (!fs.existsSync(closureLib)) { + // Target must be absolute (or relative to closureLib's own directory, + // one level up from cwd) -- symlink targets resolve relative to the + // link's location, not the process's cwd. + fs.symlinkSync(path.resolve('node_modules/google-closure-library'), closureLib, 'dir'); + } return [ 'node_modules/.bin/google-closure-compiler', `--js=${closureLib}/closure/goog/**.js`, From 96ab974204c97fbcdc2f612a2ecc43363a90902f Mon Sep 17 00:00:00 2001 From: Nathan Mills Date: Fri, 21 Aug 2026 13:55:41 -0600 Subject: [PATCH 6/9] fix: use goog:namespace imports instead of raw closure-library paths These 6 imports across 4 files pointed at closure-library files by relative path (e.g. '../closure-library/closure/goog/crypt/base64.js'), which google-closure-compiler@20240317.0.0 (the version package.json pins) rejects outright: 'Cannot import Closure files by path. Use either import goog:namespace or goog.require(namespace)'. Converted each to the goog: form the compiler asks for -- goog.array, goog.object, goog.string, goog.crypt, goog.crypt.base64 -- using default-import syntax (compiler error JSC_NAMESPACE_IMPORT_CANNOT_USE_STAR rejects 'import * as' for goog: specifiers, and says so explicitly). Also dropped one redundant bare side-effect import in debug.js that duplicated the next line's named import of the same file. Verified locally: ran the exact google-closure-compiler invocation gen_google_protobuf_js uses, with the previous version of these files it failed (JSC_JS_MODULE_LOAD_WARNING, then after the symlink fix JSC_CANNOT_PATH_IMPORT_CLOSURE_FILE); with this commit it exits 0 and produces a real compiled google-protobuf.js (691 lines). Every renamed import's usage in these files (goog.array.map/forEach, goog.object.getKeys, goog.string.quote, goog.crypt.byteArrayToString, goog.crypt.base64.*) matches Closure Library's actual, stable public API for that namespace. --- binary/utils.js | 7 +++---- binary/writer.js | 2 +- debug.js | 6 ++---- message.js | 4 ++-- 4 files changed, 8 insertions(+), 11 deletions(-) diff --git a/binary/utils.js b/binary/utils.js index 3c8bac6..d527046 100644 --- a/binary/utils.js +++ b/binary/utils.js @@ -36,10 +36,9 @@ * @author aappleby@google.com (Austin Appleby) */ -import * as crypt from '../../closure-library/closure/goog/crypt/crypt.js'; - -import * as base64 from '../../closure-library/closure/goog/crypt/base64.js'; -import * as googString from '../../closure-library/closure/goog/string/string.js'; +import crypt from 'goog:goog.crypt'; +import base64 from 'goog:goog.crypt.base64'; +import googString from 'goog:goog.string'; import * as asserts from '../asserts.js'; import * as BinaryConstants from './constants.js'; diff --git a/binary/writer.js b/binary/writer.js index 416bf24..36a65a0 100644 --- a/binary/writer.js +++ b/binary/writer.js @@ -56,7 +56,7 @@ * @author aappleby@google.com (Austin Appleby) */ -import * as base64 from '../../closure-library/closure/goog/crypt/base64.js'; +import base64 from 'goog:goog.crypt.base64'; import * as asserts from '../asserts.js'; import * as BinaryConstants from './constants.js'; diff --git a/debug.js b/debug.js index 74ff11e..250da42 100644 --- a/debug.js +++ b/debug.js @@ -32,10 +32,8 @@ * @fileoverview Utilities to debug JSPB based proto objects. */ -import '../closure-library/closure/goog/array/array.js'; - -import * as googArray from '../closure-library/closure/goog/array/array.js'; -import googObject from '../closure-library/closure/goog/object/object.js'; +import googArray from 'goog:goog.array'; +import googObject from 'goog:goog.object'; import * as asserts from './asserts.js'; import { Map } from './map.js'; import { Message } from './message.js'; diff --git a/message.js b/message.js index 9f1ac51..c2427a4 100644 --- a/message.js +++ b/message.js @@ -35,9 +35,9 @@ * @author mwr@google.com (Mark Rawling) */ -import * as googArray from '../closure-library/closure/goog/array/array.js'; +import googArray from 'goog:goog.array'; -import * as base64 from '../closure-library/closure/goog/crypt/base64.js'; +import base64 from 'goog:goog.crypt.base64'; import * as asserts from './asserts.js'; import { BinaryReader } from './binary/reader.js'; import { BinaryWriter } from './binary/writer.js'; From 5c95bf4a54eef96fe4757eaf2855bab2168d8f08 Mon Sep 17 00:00:00 2001 From: Nathan Mills Date: Fri, 21 Aug 2026 14:07:38 -0600 Subject: [PATCH 7/9] fix: use goog:namespace imports in commonjs/export_testdeps.js too Same class of bug as the previous commit, in the second (and only other) file that gets fed through google-closure-compiler (commonjs_testdeps task). Converted all 6 imports to goog: form: goog.crypt (named import of byteArrayToString/byteArrayToHex -- compiler accepts named imports, only rejected 'import * as'), goog.crypt.base64, goog.testing.PropertyReplacer, goog.userAgent, goog.array, goog.object. Verified locally: ran the exact compiler invocation commonjs_testdeps uses: previously failed with JSC_CANNOT_PATH_IMPORT_CLOSURE_FILE, now exits 0 and produces a real compiled testdeps_commonjs.js (699 lines). Confirmed these are the only two call sites of getClosureCompilerCommand in this gulpfile, so this closes out every place npm test's build path actually invokes the compiler. Other files still importing closure-library by raw path (the various *_test.js files, experimental/runtime/*) run via test_closure/test_commonjs's Jasmine execution instead, which never goes through the compiler's strict goog: import requirement -- confirmed by tracing gulpfile.js's own series() task graph, not left as an assumption. --- commonjs/export_testdeps.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/commonjs/export_testdeps.js b/commonjs/export_testdeps.js index be0c472..b596f52 100644 --- a/commonjs/export_testdeps.js +++ b/commonjs/export_testdeps.js @@ -7,12 +7,12 @@ * export_asserts.js. */ -import { byteArrayToString, byteArrayToHex } from '../../closure-library/closure/goog/crypt/crypt.js'; -import * as base64 from '../../closure-library/closure/goog/crypt/base64.js'; -import { PropertyReplacer } from '../../closure-library/closure/goog/testing/propertyreplacer.js'; -import * as userAgent from '../../closure-library/closure/goog/useragent/useragent.js'; -import * as googArray from '../../closure-library/closure/goog/array/array.js'; -import googObject from '../../closure-library/closure/goog/object/object.js'; +import { byteArrayToString, byteArrayToHex } from 'goog:goog.crypt'; +import base64 from 'goog:goog.crypt.base64'; +import { PropertyReplacer } from 'goog:goog.testing.PropertyReplacer'; +import userAgent from 'goog:goog.userAgent'; +import googArray from 'goog:goog.array'; +import googObject from 'goog:goog.object'; import * as debug from '../debug.js' import { BinaryReader } from '../binary/reader.js'; From 907e2a303f5d7164ac5361c67dde59a262dbd716 Mon Sep 17 00:00:00 2001 From: Nathan Mills Date: Mon, 24 Aug 2026 09:40:15 -0600 Subject: [PATCH 8/9] fix: address review feedback on export_testdeps.js and gulpfile symlink PropertyReplacer is a default export, not a named one -- the named import destructured an undefined property, breaking the closure compile of commonjs_testdeps and cascading into every test in CI (ERR_MODULE_NOT_FOUND for commonjs_out/debug.js and friends). Also switch the symlink existence check to lstatSync so a dangling link (post rm -rf node_modules) doesn't throw EEXIST, trim the getClosureCompilerCommand comment to what's still true after the goog:namespace import rewrites, and remove the symlink in remove_gen_files/gulp clean. --- commonjs/export_testdeps.js | 2 +- gulpfile.js | 23 +++++++---------------- 2 files changed, 8 insertions(+), 17 deletions(-) diff --git a/commonjs/export_testdeps.js b/commonjs/export_testdeps.js index b596f52..b6b9b70 100644 --- a/commonjs/export_testdeps.js +++ b/commonjs/export_testdeps.js @@ -9,7 +9,7 @@ import { byteArrayToString, byteArrayToHex } from 'goog:goog.crypt'; import base64 from 'goog:goog.crypt.base64'; -import { PropertyReplacer } from 'goog:goog.testing.PropertyReplacer'; +import PropertyReplacer from 'goog:goog.testing.PropertyReplacer'; import userAgent from 'goog:goog.userAgent'; import googArray from 'goog:goog.array'; import googObject from 'goog:goog.object'; diff --git a/gulpfile.js b/gulpfile.js index d6fc238..f9c15fd 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -133,22 +133,13 @@ function genproto_group3_commonjs_strict(cb) { function getClosureCompilerCommand(exportsFile, outputFile) { - // Every source file's own `import ... from '../closure-library/...'` (or - // '../../closure-library/...' one level down, e.g. binary/utils.js) is - // hardcoded to a sibling checkout one directory above the repo root -- - // not where npm actually installs google-closure-library - // (node_modules/google-closure-library, per package.json). Changing - // closureLib itself to the node_modules path would only fix the --js= - // include globs below; it wouldn't fix module resolution, since - // closure-compiler links imports by the path string written in the - // importing file, not by content match against whatever --js= happens - // to include. So instead of rewriting every import across every source - // file, make the path they already expect resolve: symlink it to the - // real npm-installed location, idempotently, right before every - // invocation of this command (local dev and CI both hit this the same - // way; neither has ever had a working '../closure-library'). + // Source files import from '../closure-library', not the npm install path + // (node_modules/google-closure-library) -- symlink it so the --js= globs + // below reach it. const closureLib = '../closure-library'; - if (!fs.existsSync(closureLib)) { + try { + fs.lstatSync(closureLib); + } catch (e) { // Target must be absolute (or relative to closureLib's own directory, // one level up from cwd) -- symlink targets resolve relative to the // link's location, not the process's cwd. @@ -233,7 +224,7 @@ function test_commonjs(cb) { } function remove_gen_files(cb) { - exec('rm -rf commonjs_out google-protobuf.js deps.js', + exec('rm -rf commonjs_out google-protobuf.js deps.js ../closure-library', make_exec_logging_callback(cb)); } From 7f6b7711c4be54dd77173cd2653f94087aec9cc4 Mon Sep 17 00:00:00 2001 From: Nathan Mills Date: Mon, 24 Aug 2026 10:23:40 -0600 Subject: [PATCH 9/9] fix: never rm a real ../closure-library, and repair dangling links gulp clean ran `rm -rf ../closure-library` unconditionally, which would recursively delete a real sibling closure-library checkout -- the layout convention gulpfile.js has assumed since before this branch. Only unlink it when it is a symlink. Creating the link now also distinguishes a dangling link (lstat succeeds, exists fails) from a live one and replaces it, instead of skipping and leaving the build to fail on unmatched --js= globs. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01DZX1q5jSbLDx2N8VX26aLt --- gulpfile.js | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index f9c15fd..f342d05 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -11,6 +11,7 @@ function exec(command, cb) { const plugin = '--plugin=protoc-gen-js=bazel-bin/generator/protoc-gen-js'; const protoc = [(process.env.PROTOC || 'bazel-bin/external/com_google_protobuf/protoc'), plugin].join(' '); const protocInc = process.env.PROTOC_INC || 'bazel-protobuf-javascript/external/com_google_protobuf/src'; +const closureLib = '../closure-library'; // See https://github.com/google/closure-compiler/wiki/Flags-and-Options let compilationLevel = 'SIMPLE'; @@ -132,19 +133,26 @@ function genproto_group3_commonjs_strict(cb) { } -function getClosureCompilerCommand(exportsFile, outputFile) { - // Source files import from '../closure-library', not the npm install path - // (node_modules/google-closure-library) -- symlink it so the --js= globs - // below reach it. - const closureLib = '../closure-library'; +function linkClosureLibrary() { + // lstat sees the link itself and exists() resolves it, so they disagree + // exactly when the link is dangling -- replace it rather than leave it broken. + // A real sibling checkout (the pre-existing convention) is left alone. try { fs.lstatSync(closureLib); + if (fs.existsSync(closureLib)) return; + fs.unlinkSync(closureLib); } catch (e) { - // Target must be absolute (or relative to closureLib's own directory, - // one level up from cwd) -- symlink targets resolve relative to the - // link's location, not the process's cwd. - fs.symlinkSync(path.resolve('node_modules/google-closure-library'), closureLib, 'dir'); + if (e.code !== 'ENOENT') throw e; } + // Symlink targets resolve relative to the link's location, not the process's + // cwd, so the target has to be absolute. + fs.symlinkSync(path.resolve('node_modules/google-closure-library'), closureLib, 'dir'); +} + +function getClosureCompilerCommand(exportsFile, outputFile) { + // The --js= globs below read from '../closure-library', not the npm install + // path (node_modules/google-closure-library) -- symlink it so they resolve. + linkClosureLibrary(); return [ 'node_modules/.bin/google-closure-compiler', `--js=${closureLib}/closure/goog/**.js`, @@ -224,7 +232,14 @@ function test_commonjs(cb) { } function remove_gen_files(cb) { - exec('rm -rf commonjs_out google-protobuf.js deps.js ../closure-library', + // Only the symlink linkClosureLibrary() made is ours to delete; ../closure-library + // may be a real sibling checkout, which rm -rf would take out recursively. + try { + if (fs.lstatSync(closureLib).isSymbolicLink()) fs.unlinkSync(closureLib); + } catch (e) { + if (e.code !== 'ENOENT') throw e; + } + exec('rm -rf commonjs_out google-protobuf.js deps.js', make_exec_logging_callback(cb)); }