Skip to content

Commit

Permalink
feat: run on more operating systems
Browse files Browse the repository at this point in the history
  • Loading branch information
jonahsnider committed Jan 18, 2021
1 parent 374d744 commit 412011a
Show file tree
Hide file tree
Showing 7 changed files with 712 additions and 23 deletions.
Binary file removed lib/glow-1.3.0
Binary file not shown.
19 changes: 15 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"url": "https://github.com/pizzafox/how/issues"
},
"devDependencies": {
"@types/decompress": "4.2.3",
"@types/node": "14.14.21",
"prettier": "2.2.1",
"prettier-config-xo": "1.0.3",
Expand All @@ -17,8 +18,7 @@
"typescript": "4.1.3"
},
"files": [
"tsc_output",
"lib"
"tsc_output"
],
"bin": "./tsc_output/index.js",
"license": "Apache-2.0",
Expand All @@ -38,13 +38,24 @@
"style": "prettier --check ."
},
"os": [
"linux"
"darwin",
"freebsd",
"linux",
"openbsd",
"win32"
],
"cpu": [
"arm64",
"x32",
"x64"
],
"engines": {
"node": ">=14.0.0"
},
"version": "0.0.0-development",
"dependencies": {
"execa": "5.0.0"
"decompress": "4.2.1",
"execa": "5.0.0",
"got": "11.8.1"
}
}
137 changes: 137 additions & 0 deletions src/fetch-glow.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import decompress from 'decompress';
import fs from 'fs';
import fsp from 'fs/promises';
import got from 'got';
import os from 'os';
import stream from 'stream';
import {promisify} from 'util';
import {desiredGlowVersion, flushOptions, options} from './options.js';
import {glowPath} from './paths.js';
import path from 'path';

const pipeline = promisify(stream.pipeline);

namespace Glow {
export type Os = 'Darwin' | 'freebsd' | 'linux' | 'openbsd' | 'Windows';
export type Arch = 'arm64' | 'armv6' | 'armv7' | 'i386' | 'x86_64';
}

/** Unix-ish OSs. */
type Unixes = 'freebsd' | 'linux' | 'openbsd';

/** A type representing the name of a Glow binary asset from GitHub. */
export type GlowBinaryArchive = `glow_${typeof desiredGlowVersion}_${
| `${'Darwin_x86_64' | `${Unixes}_${Glow.Arch}`}.tar.gz`
| `Windows_${'i386' | 'x86_64'}.zip`}`;

/** Stricter typings for the `os` module. */
export namespace Os {
/** The operating system CPU architecture for which the Node.js binary was compiled. */
export type Arch = 'arm' | 'arm64' | 'ia32' | 'mips' | 'mipsel' | 'ppc' | 'ppc64' | 's390' | 's390x' | 'x32' | 'x64';

/** String identifying the operating system platform. */
export type Platform = 'aix' | 'darwin' | 'freebsd' | 'linux' | 'openbsd' | 'sunos' | 'win32' | 'android';
}

export interface OsKind {
arch: Glow.Arch;
os: Glow.Os;
}

/**
* Determine which binary for Glow should be installed for the given operating system.
* @example
* ```ts
* resolveVersion({ os: 'Darwin', arch: 'x86_64' }); // 'glow_1.3.0_Darwin_x86_64.tar.gz'
* ```
*/
export function resolveVersion(host: OsKind): GlowBinaryArchive {
switch (host.os) {
case 'Darwin': {
if (host.arch === 'x86_64') {
return `glow_${desiredGlowVersion}_Darwin_x86_64.tar.gz` as const;
}
break;
}
case 'Windows': {
if (host.arch === 'i386' || host.arch === 'x86_64') {
return `glow_${desiredGlowVersion}_Windows_${host.arch}.zip` as const;
}
break;
}
case 'freebsd':
case 'linux':
case 'openbsd': {
switch (host.arch) {
case 'arm64':
case 'i386':
case 'x86_64': {
return `glow_${desiredGlowVersion}_${host.os}_${host.arch}.tar.gz` as const;
}
}
break;
}
}

throw new RangeError('Unexpected combination of architecture and os');
}

/** Mapping of Node.js `os.arch` to architectures targeted by Glow builds. */
enum OsArchToGlowArch {
'arm64' = 'arm64',
'x32' = 'i386',
'x64' = 'x86_64'
}

/** Mapping of Node.js `os.platform` to OSs targeteted by Glow builds. */
enum OsPlatformToGlowOs {
'darwin' = 'Darwin',
'freebsd' = 'freebsd',
'linux' = 'linux',
'openbsd' = 'openbsd',
'win32' = 'Windows'
}

/**
* Get the Glow compatible architecture and OS for this machine.
*/
export function getOsKind(): OsKind {
const arch = os.arch() as Os.Arch;

if (!(arch in OsArchToGlowArch)) {
throw new Error('Unsupported CPU architecture');
}

const platform = os.platform() as Os.Platform;

if (!(platform in OsPlatformToGlowOs)) {
throw new Error('Unsupported OS platform');
}

return {
arch: OsArchToGlowArch[arch as keyof typeof OsArchToGlowArch],
os: OsPlatformToGlowOs[platform as keyof typeof OsPlatformToGlowOs]
};
}

/**
* Update Glow to the preferred version.
* @param archiveName - The name of the Glow binary archive to download
*/
export async function updateGlow(archiveName: GlowBinaryArchive): Promise<void> {
const url = `https://github.com/charmbracelet/glow/releases/download/v${desiredGlowVersion}/${archiveName}`;

const archivePath = `${glowPath}-archive`;
await pipeline(got.stream(url), fs.createWriteStream(archivePath));

await decompress(archivePath, path.join(glowPath, '..'), {
// Ignore the documentation files in releases
filter: file => file.path.startsWith('glow')
});

await fsp.unlink(archivePath);

options.glowVersion = desiredGlowVersion;

await flushOptions(options);
}
34 changes: 24 additions & 10 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,43 @@
#!/usr/bin/env node
import execa from 'execa';
import fs from 'fs/promises';
import {homedir} from 'os';
import path, {dirname} from 'path';
import path from 'path';
import {stdout} from 'process';
import {fileURLToPath} from 'url';
import {getOsKind, resolveVersion, updateGlow} from './fetch-glow.js';
import {format} from './format.js';

const __dirname = dirname(fileURLToPath(import.meta.url));

const tldrPath = path.join(homedir(), '.cache', 'how', 'tldr');
import {desiredGlowVersion, options} from './options.js';
import {glowPath, tldrPath} from './paths.js';

try {
await fs.access(tldrPath);
} catch (error) {
await fs.mkdir(tldrPath, {recursive: true});
await execa('git', ['clone', 'https://github.com/tldr-pages/tldr.git', tldrPath]);
console.log('downloaded the database of Markdown examples');
}

if (options.glowVersion !== desiredGlowVersion) {
// Currently installed version is not the same as the desired version

const osKind = getOsKind();
const version = resolveVersion(osKind);

try {
await updateGlow(version);

console.log('downloaded a new binary for Glow (makes Markdown pretty)');
} catch (error) {
console.error('failed to download a new binary for Glow (makes Markdown pretty)');
process.exit(1);
}
}

if (Math.random() > 0.95) {
try {
await execa('git', ['pull'], {cwd: tldrPath});
console.error('you got unlucky and the cache was refreshed');
console.log('you got unlucky and the cache for the Markdown database was refreshed');
} catch (error) {
console.error('failed to refresh cache');
console.error('failed to refresh the cache for the Markdown database');
}
}

Expand All @@ -47,7 +61,7 @@ for (const potentialPath of potentialPaths) {
const contents = await fs.readFile(potentialPath, 'utf-8');
const formatted = format(contents);

const tldr = await execa(path.join(__dirname, '..', 'lib', 'glow-1.3.0'), ['-s', 'dark', '-'], {
const tldr = await execa(glowPath, ['-s', 'dark', '-'], {
input: formatted
});

Expand Down
38 changes: 38 additions & 0 deletions src/options.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import * as fs from 'fs/promises';
import os from 'os';
import path from 'path';
import {cacheDir, optionsPath} from './paths.js';

export interface Options {
/** Currently installed version for glow. */
glowVersion: string | null;
versionNumber: 1;
}

/**
* The desired version of Glow to download locally.
* @see https://github.com/charmbracelet/glow/releases
*/
export const desiredGlowVersion = '1.3.0';

export const defaultOptions: Options = {
glowVersion: null,
versionNumber: 1
};

/**
* Flush options object to filesystem.
* @param data - Options to flush
*/
export async function flushOptions(data: Options): Promise<void> {
await fs.writeFile(optionsPath, JSON.stringify(data, undefined, 2) + os.EOL, 'utf-8');
}

try {
await fs.access(optionsPath);
} catch (error) {
await fs.mkdir(cacheDir, {recursive: true});
await flushOptions(defaultOptions);
}

export const options: Options = JSON.parse(await fs.readFile(path.join(optionsPath), 'utf-8'));
10 changes: 10 additions & 0 deletions src/paths.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import {homedir} from 'os';
import path, {dirname} from 'path';
import {fileURLToPath} from 'url';

const __dirname = dirname(fileURLToPath(import.meta.url));

export const cacheDir = path.join(homedir(), '.cache', 'how');
export const optionsPath = path.join(cacheDir, 'options.json');
export const tldrPath = path.join(cacheDir, 'tldr');
export const glowPath = path.join(cacheDir, 'glow');
Loading

0 comments on commit 412011a

Please sign in to comment.