Skip to content

Commit

Permalink
init
Browse files Browse the repository at this point in the history
  • Loading branch information
vallyian committed Mar 21, 2023
1 parent 55da83e commit ea39780
Show file tree
Hide file tree
Showing 12 changed files with 442 additions and 0 deletions.
16 changes: 16 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Editor configuration, see http://editorconfig.org
root = true

[*]
charset = utf-8
indent_style = space
indent_size = 4
insert_final_newline = true
trim_trailing_whitespace = true

[*.js]
quote_type = double

[*.md]
max_line_length = off
trim_trailing_whitespace = false
3 changes: 3 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
*.sh text eol=lf
*.bash text eol=lf
*.js text eol=lf
36 changes: 36 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: publish

on:
release:
types: [created]

jobs:
test:
strategy:
matrix:
os: [macos-latest, ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2

- uses: actions/setup-node@v2
with:
node-version: 16
registry-url: https://registry.npmjs.org/

- run: node test

publish:
needs: [test]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2

- uses: actions/setup-node@v2
with:
node-version: 16
registry-url: https://registry.npmjs.org/

- env:
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
run: npm publish
26 changes: 26 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: test

on:
push:
branches: [main]

pull_request:
branches: [main]

workflow_dispatch:

jobs:
test:
strategy:
matrix:
os: [macos-latest, ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2

- uses: actions/setup-node@v2
with:
node-version: 16
registry-url: https://registry.npmjs.org/

- run: node test
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
node_modules
protoc-gen-js
test
tmp
package-lock.json
*.tgz
59 changes: 59 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# protoc-gen-js-binary

Downloads Google Protocol Buffers Javascript generator binary wrapped as npm package.
By default, it will download the latest released version.
If a specific version is required, add `"protoc-gen-js-binary": "x.x.x"` at the root of your package.json.

## Install

`npm i -D protoc-gen-js-binary`

To force re-check for latest protoc-gen-js binary releases, simply run `npm ci`.
Alternatively, you can manually invoke the install script `node node_modules/protoc-gen-js-binary/install`.
If working in both Windows and WSL, you can invoke the install script to download binaries for both,
however when switching OS you should run `npm ci`.

## Usage

Add `--plugin=protoc-gen-js=node_modules/protoc-gen-js-binary/protoc-gen-js` arg to `protoc` command

```sh
# download protoc and protoc-gen-js binaries
npm install protoc-gen-js-binary protoc-binary

# replace `input_dir`, `output_dir` and `my.proto` with actual values
node_modules/.bin/protoc \
--plugin=protoc-gen-js=node_modules/protoc-gen-js-binary/protoc-gen-js \
--js_out=import_style=commonjs:input_dir \
-I=output_dir \
my.proto
```

Alternatively, add the full path of `protoc-gen-js-binary` to PATH
e.g. `/home/user/node_modules/protoc-gen-js-binary`

## API

### `binary`

```js
/* Returns the absolute path to local protoc-gen-js binary */
require("protoc-gen-js-binary").binary;
```

### `version`

```js
/* Returns version of local protoc-gen-js binary */
require("protoc-gen-js-binary").version;
```

## Supported versions

See official [protocolbuffers/protobuf-javascript](https://github.com/protocolbuffers/protobuf-javascript/releases) download page.

* osx-x86_64.zip
* linux-x86_32.zip
* linux-x86_64.zip
* win32.zip
* win64.zip
21 changes: 21 additions & 0 deletions env.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
const path = require("path");

const binaryZip = {
"darwin-x64": "osx-x86_64.zip",
"linux-x32": "linux-x86_32.zip",
"linux-x64": "linux-x86_64.zip",
"win32-x32": "win32.zip",
"win32-x64": "win64.zip"
}[process.platform + "-" + process.arch];

module.exports = Object.freeze({
binary: path.join(__dirname, "protoc-gen-js"),
binaryZip,
downloadUrlTemplate: `https://github.com/protocolbuffers/protobuf-javascript/releases/download/v{version}/protobuf-javascript-{version}-${binaryZip}`,
latestReleaseUrl: "https://api.github.com/repos/protocolbuffers/protobuf-javascript/releases/latest",
safeUnzip: Object.freeze({
MAX_FILES: 1_000,
MAX_SIZE: 10_000_000, // 10 MB
MAX_RATIO: 20
})
});
7 changes: 7 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
declare module "protoc-gen-js-binary" {
/** Absolute path to local protoc-gen-js binary */
export const binary: string;

/** Version of local protoc-gen-js binary */
export const version: string | "";
}
22 changes: 22 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/usr/bin/env node
const fs = require("node:fs");
const evn = require("./env");

module.exports = Object.freeze({
/** Absolute path to local protoc-gen-js binary */
binary: evn.binary,

/** Version of local protoc-gen-js binary, or empty string */
get version() { return getBinaryVersion(); },
});

function getBinaryVersion() {
if (!fs.existsSync(evn.binary)) return "";
let ret;
try {
ret = require("./package.json")["protoc-gen-js-version"];
} catch (ex) {
ret = "";
}
return /[0-9.]/.test(ret) ? ret : "";
}
149 changes: 149 additions & 0 deletions install.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
const fs = require("node:fs");
const path = require("node:path");
const https = require("node:https");
const extract = require("extract-zip");
const env = require("./env");
const index = require("./index");

const binaryZipRx = new RegExp(`\/protobuf-javascript-(.*)-${env.binaryZip}`);

install();

function install() {
let version = "";
let downloadUrl = "";
const tmpDir = path.join(__dirname, "tmp");
const tmpZipFile = path.join(tmpDir, "protoc-gen-js.zip");
const tmpBinFile = path.join(tmpDir, "bin", `protoc-gen-js${process.platform === "win32" ? ".exe" : ""}`);

const validatePlatform = () => env.binaryZip
|| Promise.reject(`${process.platform}-${process.arch} unsupported`);
const checkBinaryVersion = () => getVersionInfo().then(bin => {
version = bin.version;
downloadUrl = bin.downloadUrl;
if (bin.version === index.version) return Promise.reject("ERR_BIN_EEXIST");
});
const removeBinary = () => fs.promises.rm(env.binary, { recursive: true, force: true });
const removeTmpDir = () => fs.promises.rm(tmpDir, { recursive: true, force: true });
const cleanDir = () => Promise.all([removeBinary(), removeTmpDir()]);
const createTmpDir = () => fs.promises.mkdir(tmpDir, { recursive: true });
const downloadZip = () => download(downloadUrl, tmpZipFile).then(() => fs.existsSync(tmpZipFile)
|| Promise.reject(`binary "${env.binaryZip} not downloaded"`));
const deflateZip = () => unzip(tmpZipFile, tmpDir).then(() => fs.existsSync(tmpBinFile)
|| Promise.reject(`binary "${tmpBinFile} not unzipped"`));
const moveBinary = () => fs.promises.rename(tmpBinFile, env.binary).then(() => fs.existsSync(env.binary)
|| Promise.reject(`binary "${env.binary} not available"`));
const makeBinaryExecutable = () => fs.promises.access(env.binary, fs.constants.X_OK).catch(({ code }) => code === "EACCES"
? fs.promises.chmod(env.binary, 0o775)
: Promise.reject(`binary "${env.binary} not executable"`));
const storeBinaryVersion = () => fs.promises.writeFile(
"package.json",
JSON.stringify({ ...require("./package.json"), "protoc-gen-js-version": version }, null, 4),
"utf8");

return Promise.resolve()
.then(validatePlatform)
.then(checkBinaryVersion)
.then(cleanDir)
.then(createTmpDir)
.then(() => console.log(`downloading protoc-gen-js v${version} from ${downloadUrl}`))
.then(downloadZip)
.then(deflateZip)
.then(moveBinary)
.then(makeBinaryExecutable)
.then(storeBinaryVersion)
.then(() => console.log(`downloaded protoc-gen-js v${version}`))
.catch(err => err === "ERR_BIN_EEXIST"
? console.log(`latest protoc-gen-js v${version} already exists, skipping download`)
: Promise.reject(err))
.finally(removeTmpDir);
}

async function unzip(zip, dir) {
let fileCount = 0;
let totalSize = 0;

await extract(zip, {
dir,
onEntry: entry => {
fileCount++;
if (fileCount > env.safeUnzip.MAX_FILES)
throw Error('Reached max. number of files');

let entrySize = entry.uncompressedSize;
totalSize += entrySize;
if (totalSize > env.safeUnzip.MAX_SIZE)
throw Error('Reached max. size');

if (entry.compressedSize > 0) {
let compressionRatio = entrySize / entry.compressedSize;
if (compressionRatio > env.safeUnzip.MAX_RATIO)
throw Error('Reached max. compression ratio');
}
}
});
}

async function getVersionInfo() {
let version = getRequestedVersion();
let downloadUrl = "";

if (version) {
downloadUrl = env.downloadUrlTemplate.replace(/\{version\}/g, version);
} else {
process.stdout.write(`querying latest protoc-gen-js version...`);
downloadUrl = await getLatestReleaseLink().catch(e => {
process.stdout.write('\n');
throw e;
});
version = downloadUrl.match(binaryZipRx)[1];
process.stdout.write(`v${version} found\n`);
}

return { version, downloadUrl };
}

function getRequestedVersion() {
let dir = process.cwd();
let requestedVersion = undefined;
while (!requestedVersion) {
const packageJsonPath = path.join(dir, "package.json");
if (fs.existsSync(packageJsonPath))
requestedVersion = require(packageJsonPath)["protoc-gen-js-binary"];
if (requestedVersion || !dir.includes("node_modules"))
break;
dir = path.normalize(path.join(dir, ".."));
}
return requestedVersion || "";
}

function getLatestReleaseLink() {
return new Promise((resolve, reject) => https.get(env.latestReleaseUrl, { headers: { "User-Agent": `Nodejs/${process.version}` } }, response => {
let data = "";
response.on("data", chunk => data += chunk);
response.on("end", () => {
const link = JSON.parse(data).assets.find(a => binaryZipRx.test(a.browser_download_url));
link
? resolve(link.browser_download_url)
: reject(`binary ${env.binaryZip} not available`);
});
}).on("error", reject));
}

function download(uri, filename) {
return new Promise((resolve, reject) => https.get(uri, response => {
if (response.statusCode === 200)
response.pipe(
fs.createWriteStream(filename)
.on("error", reject)
.on("close", resolve)
);

else if (response.headers.location)
resolve(download(response.headers.location, filename));

else
reject(Error(`${response.statusCode} ${response.statusMessage}`));

}).on("error", reject));
}
44 changes: 44 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
{
"name": "protoc-gen-js-binary",
"homepage": "https://github.com/vallyian/protoc-gen-js-binary#readme",
"description": "Downloads latest Google Protocol Buffers Javascript generator binary wrapped as npm package",
"keywords": [
"download",
"latest",
"google",
"protocol",
"buffers",
"javascript",
"generator",
"protoc-gen-js",
"binary"
],
"version": "1.0.0",
"author": "[email protected]",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/vallyian/protoc-gen-js-binary.git"
},
"bugs": {
"url": "https://github.com/vallyian/protoc-gen-js-binary/issues"
},
"engines": {
"node": ">=14",
"npm": ">=6"
},
"main": "index.js",
"types": "./index.d.ts",
"scripts": {
"postinstall": "node install"
},
"dependencies": {
"extract-zip": "latest"
},
"files": [
"env.js",
"index.d.ts",
"index.js",
"install.js"
]
}
Loading

0 comments on commit ea39780

Please sign in to comment.