generated from salesforcecli/plugin-template
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
7e61525
commit 61c4b00
Showing
13 changed files
with
401 additions
and
350 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,20 +1,17 @@ | ||
# [1.0.0-beta.3](https://github.com/mdonnalley/multiple-package-manager/compare/v1.0.0-beta.2...v1.0.0-beta.3) (2021-08-20) | ||
|
||
|
||
### Bug Fixes | ||
|
||
* use @octokit/core ([dbc52d7](https://github.com/mdonnalley/multiple-package-manager/commit/dbc52d733881e8758884c7ad4efea58bcff9a1b4)) | ||
- use @octokit/core ([dbc52d7](https://github.com/mdonnalley/multiple-package-manager/commit/dbc52d733881e8758884c7ad4efea58bcff9a1b4)) | ||
|
||
# [1.0.0-beta.2](https://github.com/mdonnalley/multiple-package-manager/compare/v1.0.0-beta.1...v1.0.0-beta.2) (2021-08-20) | ||
|
||
|
||
### Features | ||
|
||
* add initial set of commands ([69b6f54](https://github.com/mdonnalley/multiple-package-manager/commit/69b6f5496952bc71ce43def76ff2b425077d9c13)) | ||
- add initial set of commands ([69b6f54](https://github.com/mdonnalley/multiple-package-manager/commit/69b6f5496952bc71ce43def76ff2b425077d9c13)) | ||
|
||
# 1.0.0-beta.1 (2021-08-20) | ||
|
||
|
||
### Features | ||
|
||
* intial release ([0a31f71](https://github.com/mdonnalley/multiple-package-manager/commit/0a31f7156fd846f8cd5007591a3597fd724812ac)) | ||
- intial release ([0a31f71](https://github.com/mdonnalley/multiple-package-manager/commit/0a31f7156fd846f8cd5007591a3597fd724812ac)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,79 +1,72 @@ | ||
import * as path from 'path'; | ||
import { mkdir, access, writeFile, readFile } from 'fs/promises'; | ||
import { Octokit } from '@octokit/core'; | ||
import { mkdir } from 'fs/promises'; | ||
import { URL } from 'url'; | ||
import { exec } from 'shelljs'; | ||
import * as chalk from 'chalk'; | ||
import { Command, Flags } from '@oclif/core'; | ||
import { MPM_DIR } from '../util'; | ||
import { Config } from '../config'; | ||
import { Repos } from '../repos'; | ||
import { Directory } from '../directory'; | ||
|
||
async function initDir(directory: string): Promise<void> { | ||
try { | ||
await access(directory); | ||
} catch { | ||
await mkdir(directory, { recursive: true }); | ||
} | ||
} | ||
|
||
function getToken(): string { | ||
const token = process.env.GH_TOKEN ?? process.env.GITHUB_TOKEN; | ||
if (!token) { | ||
throw new Error('GH_TOKEN or GITHUB_TOKEN must be set in the environment'); | ||
} | ||
return token; | ||
} | ||
|
||
function clone(url: string, dir: string): void { | ||
async function clone(url: string, dir: string): Promise<void> { | ||
await mkdir(dir, { recursive: true }); | ||
exec(`git -C ${dir} clone ${url}`, { silent: true }); | ||
} | ||
|
||
async function addToCache(contents: Record<string, unknown>): Promise<void> { | ||
const filepath = path.join(MPM_DIR, 'repos.json'); | ||
let existing: Record<string, unknown> = {}; | ||
try { | ||
existing = JSON.parse(await readFile(filepath, 'utf-8')) as Record<string, unknown>; | ||
} catch { | ||
// do nothing | ||
function parseOrgAndRepo(entity: string): { org: string; repo: string | null } { | ||
if (entity.startsWith('https://')) { | ||
const url = new URL(entity); | ||
const pathParts = url.pathname.split('/').filter((p) => !!p); | ||
// ex: https://github.com/my-org | ||
if (pathParts.length === 1) return { org: pathParts[0], repo: null }; | ||
// ex: https://github.com/my-org/my-repo | ||
else return { org: pathParts[0], repo: pathParts[1] }; | ||
} else { | ||
const parts = entity.split('/').filter((e) => !!e); | ||
if (parts.length === 1) { | ||
// ex: my-org | ||
return { org: entity, repo: null }; | ||
} else { | ||
// ex: my-org/my-repo | ||
return { org: parts[0], repo: parts[1] }; | ||
} | ||
} | ||
const merged = Object.assign(existing, contents); | ||
await writeFile(filepath, JSON.stringify(merged, null, 2)); | ||
} | ||
|
||
export class Add extends Command { | ||
public static readonly description = 'Add a github org. Requires GH_TOKEN to be set in the environment'; | ||
public static readonly description = 'Add a github org. Requires GH_TOKEN to be set in the environment.'; | ||
public static readonly flags = { | ||
directory: Flags.string({ | ||
description: 'location to clone repo', | ||
char: 'd', | ||
required: true, | ||
}), | ||
'github-org': Flags.string({ | ||
description: 'github org to clone', | ||
char: 'g', | ||
required: true, | ||
}), | ||
method: Flags.string({ | ||
description: 'method to use for cloning', | ||
description: 'Method to use for cloning.', | ||
default: 'ssh', | ||
options: ['ssh', 'https'], | ||
}), | ||
}; | ||
|
||
public static readonly args = [ | ||
{ | ||
name: 'entity', | ||
description: 'Github org, repo, or url to add', | ||
required: true, | ||
}, | ||
]; | ||
|
||
public async run(): Promise<void> { | ||
const { flags } = await this.parse(Add); | ||
const directory = path.resolve(flags.directory); | ||
await initDir(directory); | ||
await initDir(MPM_DIR); | ||
const octokit = new Octokit({ auth: getToken() }); | ||
const repos = await octokit.request('GET /orgs/{org}/repos', { org: flags['github-org'] }); | ||
this.log(`Cloning all ${chalk.cyan.bold(flags['github-org'])} repositories into ${flags.directory}`); | ||
const cached = {} as Record<string, unknown>; | ||
for (const repo of repos.data) { | ||
const { flags, args } = await this.parse(Add); | ||
const config = await Config.create(); | ||
const directory = await Directory.create({ name: config.get('directory') }); | ||
const repos = await Repos.create(); | ||
|
||
const info = parseOrgAndRepo(args.entity); | ||
const repositories = await repos.fetch(info.org, info.repo); | ||
this.log(`Cloning repositories into ${path.join(directory.name, info.org)}`); | ||
for (const repo of repositories) { | ||
const url = flags.method === 'ssh' ? repo.ssh_url : repo.clone_url; | ||
this.log(` * ${chalk.bold(url)}`); | ||
clone(url, flags.directory); | ||
cached[repo.name] = repo; | ||
break; | ||
this.log(` * ${chalk.bold(repo.name)}`); | ||
const dir = path.join(directory.name, repo.owner.login); | ||
await clone(url, dir); | ||
repos.set(repo.name, repo); | ||
} | ||
await addToCache(cached); | ||
await repos.write(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import { Command } from '@oclif/core'; | ||
import * as chalk from 'chalk'; | ||
import { cli } from 'cli-ux'; | ||
import { groupBy, sortBy } from 'lodash'; | ||
import { Repos } from '../repos'; | ||
|
||
export class List extends Command { | ||
public static readonly description = 'List all added repositories.'; | ||
public static readonly flags = {}; | ||
|
||
public async run(): Promise<void> { | ||
const repositories = (await Repos.create()).getContents(); | ||
const grouped = groupBy(repositories, 'owner.login'); | ||
for (const [org, repos] of Object.entries(grouped)) { | ||
const columns = { | ||
name: { header: 'Name' }, | ||
html_url: { header: 'URL' }, | ||
}; | ||
const sorted = sortBy(Object.values(repos), 'name'); | ||
cli.table(sorted, columns, { title: chalk.cyan.bold(`${org} Respositories`) }); | ||
this.log(); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,21 +1,21 @@ | ||
import { Command } from '@oclif/core'; | ||
import * as open from 'open'; | ||
import { readRepo } from '../util'; | ||
import { Repos } from '../repos'; | ||
|
||
export class Open extends Command { | ||
public static readonly description = 'Open a github repository'; | ||
public static readonly description = 'Open a github repository.'; | ||
public static readonly flags = {}; | ||
public static readonly args = [ | ||
{ | ||
name: 'repo', | ||
description: 'Name of repository', | ||
description: 'Name of repository.', | ||
required: true, | ||
}, | ||
]; | ||
|
||
public async run(): Promise<void> { | ||
const { args } = await this.parse(Open); | ||
const repo = await readRepo(args.repo); | ||
const repo = (await Repos.create()).get(args.repo); | ||
await open(repo.html_url, { wait: false }); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import * as os from 'os'; | ||
import * as path from 'path'; | ||
import { Command, Flags } from '@oclif/core'; | ||
import { prompt } from 'inquirer'; | ||
import { Config } from '../config'; | ||
|
||
export class Setup extends Command { | ||
public static readonly description = 'Setup mpm'; | ||
public static readonly flags = { | ||
directory: Flags.string({ | ||
description: 'Location to setup repositories.', | ||
char: 'd', | ||
}), | ||
}; | ||
|
||
public async run(): Promise<void> { | ||
const { flags } = await this.parse(Setup); | ||
const config = await Config.create(); | ||
if (!flags.directory) { | ||
const answers = await prompt<{ directory: string }>({ | ||
name: 'directory', | ||
type: 'input', | ||
message: 'Where would you link to clone your repositories?', | ||
default: Config.DEFAULT_DIRECTORY, | ||
}); | ||
config.set('directory', path.resolve(answers.directory.replace('~', os.homedir()))); | ||
} else { | ||
config.set('directory', flags.directory); | ||
} | ||
await config.write(); | ||
|
||
this.log(`All repositories will be cloned into ${config.get('directory')}`); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import * as os from 'os'; | ||
import * as path from 'path'; | ||
import { ConfigFile, JsonMap } from './configFile'; | ||
|
||
export interface Configuration extends JsonMap { | ||
directory: string; | ||
} | ||
|
||
export class Config extends ConfigFile<Configuration> { | ||
public static DEFAULT_DIRECTORY = path.join(os.homedir(), 'repos'); | ||
|
||
private static DEFAULT_CONFIG: Configuration = { directory: Config.DEFAULT_DIRECTORY }; | ||
|
||
public constructor() { | ||
super('config.json'); | ||
} | ||
|
||
protected make(): Configuration { | ||
return Config.DEFAULT_CONFIG; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import { readFile, writeFile } from 'fs/promises'; | ||
import * as os from 'os'; | ||
import * as path from 'path'; | ||
import { AsyncOptionalCreatable } from '@salesforce/kit'; | ||
import { exists } from './util'; | ||
import { Directory } from './directory'; | ||
|
||
export interface JsonMap<T = unknown> { | ||
[key: string]: T; | ||
} | ||
|
||
export abstract class ConfigFile<T extends JsonMap> extends AsyncOptionalCreatable<string> { | ||
public static MPM_DIR_NAME = '.mpm'; | ||
public static MPM_DIR = path.join(os.homedir(), ConfigFile.MPM_DIR_NAME); | ||
|
||
private contents: T; | ||
private filepath: string; | ||
|
||
public constructor(filename: string) { | ||
super(filename); | ||
this.filepath = path.join(ConfigFile.MPM_DIR, filename); | ||
} | ||
|
||
public async read(): Promise<T> { | ||
if (await exists(this.filepath)) { | ||
const config = JSON.parse(await readFile(this.filepath, 'utf-8')) as T; | ||
this.contents = config; | ||
return this.contents; | ||
} else { | ||
this.contents = this.make(); | ||
await this.write(); | ||
return this.contents; | ||
} | ||
} | ||
|
||
public async write(newContents: T = this.contents): Promise<void> { | ||
await writeFile(this.filepath, JSON.stringify(newContents, null, 2)); | ||
} | ||
|
||
public get(key: keyof T): T[keyof T] { | ||
return this.contents[key]; | ||
} | ||
|
||
public getContents(): T { | ||
return this.contents; | ||
} | ||
|
||
public set(key: keyof T, value: T[keyof T]): void { | ||
this.contents[key] = value; | ||
} | ||
|
||
protected async init(): Promise<void> { | ||
await Directory.create({ name: ConfigFile.MPM_DIR }); | ||
this.contents = await this.read(); | ||
} | ||
|
||
protected make(): T { | ||
return {} as T; | ||
} | ||
} |
Oops, something went wrong.