Skip to content

Commit

Permalink
feat: support v prefix in CLI GH release name
Browse files Browse the repository at this point in the history
From Arduino CLI `>=0.35.0-rc.1`, the `v` prefix is expected in the
GitHub release name. The asset filenames do not contain the prefix.

Ref: arduino/arduino-cli#2374

Signed-off-by: dankeboy36 <[email protected]>
  • Loading branch information
dankeboy36 committed Oct 22, 2023
1 parent 072d71c commit 979f92b
Show file tree
Hide file tree
Showing 4 changed files with 163 additions and 41 deletions.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ardunno-cli-gen",
"version": "0.1.5",
"version": "0.1.7",
"description": "Generates nice-grpc API for the Arduino CLI",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
Expand Down
127 changes: 112 additions & 15 deletions src/__tests__/generate.spec.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import * as assert from 'assert';
import * as assert from 'assert/strict';
import { describe } from 'mocha';
import { join } from 'node:path';
import { dir as tempDir } from 'tmp-promise';
import generate, { __test, parseGitHub, parseSemver } from '../generate';
import generate, { __test } from '../generate';
import { SemVer } from 'semver';

const { execa } = __test;
const { parseSemver, parseGitHub, protoLocation, execa } = __test;

describe('generate', () => {
it("should fail when 'src' is an accessible file", async function () {
Expand Down Expand Up @@ -141,9 +142,9 @@ describe('generate', () => {
'protobuf',
'empty'
));
assert.notEqual(Empty, undefined);
assert.equal(typeof Empty.fromJSON, 'function');
assert.deepEqual(Empty.fromJSON(), {});
assert.notStrictEqual(Empty, undefined);
assert.strictEqual(typeof Empty.fromJSON, 'function');
assert.deepStrictEqual(Empty.fromJSON(), {});
} finally {
await execa('npm', ['unlink', 'protobufjs'], {
cwd: path,
Expand All @@ -154,13 +155,13 @@ describe('generate', () => {

describe('parseGitHub', () => {
it('should parse valid', () => {
assert.deepEqual(parseGitHub('arduino/arduino-cli'), {
assert.deepStrictEqual(parseGitHub('arduino/arduino-cli'), {
owner: 'arduino',
repo: 'arduino-cli',
});
});
it('should parse valid with commit', () => {
assert.deepEqual(parseGitHub('arduino/arduino-cli#5a4ffe0'), {
assert.deepStrictEqual(parseGitHub('arduino/arduino-cli#5a4ffe0'), {
owner: 'arduino',
repo: 'arduino-cli',
commit: '5a4ffe0',
Expand All @@ -176,29 +177,125 @@ describe('generate', () => {
'owner/repo#one two',
].forEach((src) =>
it(`should not parse '${src}'`, () =>
assert.equal(parseGitHub(src), undefined))
assert.strictEqual(parseGitHub(src), undefined))
);
});
describe('parseSemver', () => {
it('should parse valid', () =>
assert.equal(parseSemver('0.30.0'), '0.30.0'));
assert.strictEqual(
(<SemVer>parseSemver('0.30.0')).version,
'0.30.0'
));
it('should parse valid with rc', () =>
assert.equal(parseSemver('0.30.0-rc1'), '0.30.0-rc1'));
assert.strictEqual(
(<SemVer>parseSemver('0.30.0-rc1')).version,
'0.30.0-rc1'
));
it("should parse valid with 'v' prefix", () =>
assert.equal(parseSemver('v0.29.1'), '0.29.1'));
assert.strictEqual(
(<SemVer>parseSemver('v0.29.1')).version,
'0.29.1'
));
it("should parse valid semver '>=0.29.0' as a semver [arduino/arduino-cli#1931]", () =>
assert.equal(parseSemver('0.29.0'), '0.29.0'));
assert.strictEqual(
(<SemVer>parseSemver('0.29.0')).version,
'0.29.0'
));
it("should parse to GitHub ref when version is not greater than '0.28.0'", () =>
assert.deepEqual(parseSemver('0.28.0'), {
assert.deepStrictEqual(parseSemver('0.28.0'), {
owner: 'arduino',
repo: 'arduino-cli',
commit: '0.28.0',
}));
['a', '0', '0.30', '0.30.', '0.30.0.'].forEach((src) =>
it(`should not parse '${src}'`, () =>
assert.equal(parseSemver(src), undefined))
assert.strictEqual(parseSemver(src), undefined))
);
});
describe('protoLocation', () => {
(
[
['0.28.0', false],
['0.29.0-rc.1', false],
[
'0.29.0',
{
endpoint:
'https://github.com/arduino/arduino-cli/releases/download/0.29.0/arduino-cli_0.29.0_proto.zip',
filename: 'arduino-cli_0.29.0_proto.zip',
},
],
[
'v0.29.0',
{
endpoint:
'https://github.com/arduino/arduino-cli/releases/download/0.29.0/arduino-cli_0.29.0_proto.zip',
filename: 'arduino-cli_0.29.0_proto.zip',
},
],
[
'v0.34.2',
{
endpoint:
'https://github.com/arduino/arduino-cli/releases/download/0.34.2/arduino-cli_0.34.2_proto.zip',
filename: 'arduino-cli_0.34.2_proto.zip',
},
],
[
'v0.35.0-rc.0',
{
endpoint:
'https://github.com/arduino/arduino-cli/releases/download/0.35.0-rc.0/arduino-cli_0.35.0-rc.0_proto.zip',
filename: 'arduino-cli_0.35.0-rc.0_proto.zip',
},
],
[
'v0.35.0-rc.1',
{
endpoint:
'https://github.com/arduino/arduino-cli/releases/download/v0.35.0-rc.1/arduino-cli_0.35.0-rc.1_proto.zip',
filename: 'arduino-cli_0.35.0-rc.1_proto.zip',
},
],
[
'v0.35.0',
{
endpoint:
'https://github.com/arduino/arduino-cli/releases/download/v0.35.0/arduino-cli_0.35.0_proto.zip',
filename: 'arduino-cli_0.35.0_proto.zip',
},
],
[
'v0.35.1',
{
endpoint:
'https://github.com/arduino/arduino-cli/releases/download/v0.35.1/arduino-cli_0.35.1_proto.zip',
filename: 'arduino-cli_0.35.1_proto.zip',
},
],
] as const
)
.map(
([raw, expected]) =>
[new SemVer(raw, { loose: true }), expected] as [
SemVer,
{ endpoint: string; filename: string } | false
]
)
.forEach(([semver, expected]) =>
it(`should${
!expected ? ' not' : ''
} get the GitHub release asset location for the protos (${
semver.raw
})`, () => {
if (!expected) {
assert.throws(() => protoLocation(semver));
} else {
assert.deepStrictEqual(protoLocation(semver), expected);
}
})
);
});
});

async function dir<T>(test: (path: string) => Promise<T>): Promise<T> {
Expand Down
71 changes: 48 additions & 23 deletions src/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export default async function (options: Options): Promise<void> {
if (!semverOrGitHub) {
throw new Error(`Invalid <src>: ${src}`);
}
const { protoPath, dispose } = await (typeof semverOrGitHub === 'string'
const { protoPath, dispose } = await (semverOrGitHub instanceof SemVer
? download(semverOrGitHub)
: clone(semverOrGitHub));
try {
Expand All @@ -59,7 +59,7 @@ interface Plugin {
readonly options: Record<string, string | string[] | boolean | boolean[]>;
readonly path: string;
}
export type PluginName = 'ts_proto';
type PluginName = 'ts_proto';
const plugins: Record<PluginName, Plugin> = {
// eslint-disable-next-line @typescript-eslint/naming-convention
ts_proto: {
Expand Down Expand Up @@ -161,10 +161,7 @@ interface GitHub {
readonly commit?: string | undefined;
}

/**
* (non-API)
*/
export function parseGitHub(src: string): GitHub | undefined {
function parseGitHub(src: string): GitHub | undefined {
const match: RegExpGroups<['owner', 'repo', 'commit']> =
src.match(ghPattern);
if (match && match.groups) {
Expand Down Expand Up @@ -217,9 +214,10 @@ async function clone(
};
}

async function download(
semver: string
): Promise<{ protoPath: string; dispose: () => Promise<void> }> {
const { owner, repo } = arduinoGitHub;
const releases = `https://github.com/${owner}/${repo}/releases`;

function protoLocation(semver: SemVer): { endpoint: string; filename: string } {
if (!valid(semver)) {
log('attempted to download with invalid semver %s', semver);
throw new Error(`invalid semver ${semver}`);
Expand All @@ -228,11 +226,26 @@ async function download(
log('attempted to download the asset file with semver %s', semver);
throw new Error(`semver must be '>=0.29.0' it was ${semver}`);
}
const filenameVersion = semver.version;
const ghReleaseVersion = hasSemverPrefix(semver)
? semver.raw
: semver.version;
const filename = `arduino-cli_${filenameVersion}_proto.zip`;
const endpoint = `${releases}/download/${ghReleaseVersion}/${filename}`;
log(
'semver: %s (raw: %s), filename: %s, endpoint: %s',
semver.version,
semver.raw,
filename,
endpoint
);
return { endpoint, filename };
}

const { owner, repo } = arduinoGitHub;
const filename = `arduino-cli_${semver}_proto.zip`;
const releases = `https://github.com/${owner}/${repo}/releases`;
const endpoint = `${releases}/download/${semver}/${filename}`;
async function download(
semver: SemVer
): Promise<{ protoPath: string; dispose: () => Promise<void> }> {
const { endpoint, filename } = protoLocation(semver);
log('accessing protos from public endpoint %s', endpoint);
// asset GET will result in a HTTP 302 (Redirect)
const getLocationResp = await get(endpoint);
Expand Down Expand Up @@ -304,41 +317,50 @@ async function get(endpoint: string): Promise<IncomingMessage> {
}

/**
* (non-API)
*
* If the `src` argument is `<0.29.0` semver, the function returns with a `GitHub` instance.
*/
export function parseSemver(src: string): string | GitHub | undefined {
function parseSemver(src: string): SemVer | GitHub | undefined {
log('parse semver %s', src);
if (!valid(src)) {
log('invalid semver %s', src);
return undefined;
}
const semver = new SemVer(src, true);
const version = semver.version;
const semver = new SemVer(src, { loose: true });
if (canDownloadProtos(semver)) {
log('parsed semver %s is >=0.29.0', version);
return version;
log(
'parsed semver %s is >=0.29.0 (raw: %s)',
semver.version,
semver.raw
);
return semver;
}
const github = {
...arduinoGitHub,
commit: semver.version,
};
log(
'parsed semver %s is <0.29.0. falling back to GitHub ref %j',
version,
'parsed semver %s is <0.29.0 (raw: %s). falling back to GitHub ref %j',
semver.version,
semver.raw,
github
);
return github;
}

/**
* The `.proto` files were not part of the Arduino CLI release before version 0.29.0 ([`arduino/arduino-cli#1931`](https://github.com/arduino/arduino-cli/pull/1931)).
* The `.proto` files were not part of the Arduino CLI release before version `0.29.0` ([`arduino/arduino-cli#1931`](https://github.com/arduino/arduino-cli/pull/1931)).
*/
function canDownloadProtos(semver: SemVer | string): boolean {
return gte(semver, new SemVer('0.29.0'));
}

/**
* The Arduino CLI GitHub release has the `'v'` prefix from version `>=v0.35.0-rc.1` ([`arduino/arduino-cli#2374`](https://github.com/arduino/arduino-cli/pull/2374)).
*/
function hasSemverPrefix(semver: SemVer | string): boolean {
return gte(semver, new SemVer('0.35.0-rc.1'));
}

// Taken from https://github.com/microsoft/TypeScript/issues/32098#issuecomment-1212501932
type RegExpGroups<T extends string[]> =
| (RegExpMatchArray & {
Expand All @@ -351,5 +373,8 @@ type RegExpGroups<T extends string[]> =
*/
// eslint-disable-next-line @typescript-eslint/naming-convention
export const __test = {
parseGitHub,
protoLocation,
parseSemver,
execa,
} as const;

0 comments on commit 979f92b

Please sign in to comment.