Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Scaffolding for TypeScript CLI #198

Merged
merged 17 commits into from
May 20, 2022
13 changes: 10 additions & 3 deletions .github/workflows/build-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,10 @@ jobs:
[Pull request has been updated, please wait for latest changes to be pushed to npm...]

- name: Install dependencies
run: npm install
run: |
npm install
cd cli
npm install
gimenete marked this conversation as resolved.
Show resolved Hide resolved

- name: Compile
run: npm run build
Expand Down Expand Up @@ -133,7 +136,10 @@ jobs:
cache: 'npm'

- name: Install dependencies
run: npm install
run: |
npm install
cd cli
npm install

- name: Compile
run: npm run build
Expand All @@ -142,7 +148,8 @@ jobs:
run: npm run lint

- name: Test
run: npm test
run: |
npm test
env:
XATA_DATABASE_URL: ${{ secrets.INTEGRATION_TEST_DATABASE_URL }}
XATA_API_KEY: ${{ secrets.INTEGRATION_TEST_API_KEY }}
Expand Down
1 change: 1 addition & 0 deletions cli/.eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/dist
Empty file added cli/.eslintrc
Empty file.
10 changes: 10 additions & 0 deletions cli/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
*-debug.log
*-error.log
/.nyc_output
/dist
/lib
/package-lock.json
/tmp
/yarn.lock
node_modules
oclif.manifest.json
1 change: 1 addition & 0 deletions cli/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Xata CLI
17 changes: 17 additions & 0 deletions cli/bin/dev
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/usr/bin/env node
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gimenete do we want to try clipanion? It's ES ready and a bit more typesafe for parsing the options and should have the same benefit as oclif for autogenerated help and testability

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

The overall syntax is almost the same

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I took a look to it but oclif supports many other things such as plugins, already built-in behaviours such the --json flag, generating binaries ready to be uploaded to homebrew, etc.


const oclif = require('@oclif/core')

const path = require('path')
const project = path.join(__dirname, '..', 'tsconfig.json')

// In dev mode -> use ts-node and dev plugins
process.env.NODE_ENV = 'development'

require('ts-node').register({project})

// In dev mode, always show stack traces
oclif.settings.debug = true;

// Start the CLI
oclif.run().then(oclif.flush).catch(oclif.Errors.handle)
3 changes: 3 additions & 0 deletions cli/bin/dev.cmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
@echo off

node "%~dp0\dev" %*
gimenete marked this conversation as resolved.
Show resolved Hide resolved
68 changes: 68 additions & 0 deletions cli/bin/mokey-patch-oclif.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/**
* Oclif mostly supports ESM, but it doesn't properly support commands
* in "index.js" files. It always thinks they are not ESM files.
*
* This file monkey-patches Oclif with the code
* from this PR https://github.com/oclif/core/pull/417 to support them.
*/
import { tsPath } from '@oclif/core';
gimenete marked this conversation as resolved.
Show resolved Hide resolved
import ModuleLoader from '@oclif/core/lib/module-loader.js';
import fs from 'fs';
import path from 'path';

ModuleLoader.default.resolvePath = (config, modulePath) => {
let isESM;
let filePath;

try {
// eslint-disable-next-line no-undef
filePath = require.resolve(modulePath);
isESM = ModuleLoader.default.isPathModule(filePath);
} catch {
filePath = tsPath(config.root, modulePath);

let fileExists = false;
let isDirectory = false;
if (fs.existsSync(filePath)) {
fileExists = true;
try {
if (fs.lstatSync(filePath)?.isDirectory?.()) {
fileExists = false;
isDirectory = true;
}
} catch {
// ignore
}
}

if (!fileExists) {
// Try all supported extensions.
let foundPath = findFile(filePath);
if (!foundPath && isDirectory) {
// Since filePath is a directory, try looking for index.js file.
foundPath = findFile(path.join(filePath, 'index'));
}

if (foundPath) {
filePath = foundPath;
}
}

isESM = ModuleLoader.default.isPathModule(filePath);
}

return { isESM, filePath };
};

function findFile(filePath) {
// eslint-disable-next-line camelcase
for (const extension of ['.js', '.cjs']) {
const testPath = `${filePath}${extension}`;

if (fs.existsSync(testPath)) {
return testPath;
}
}

return null;
}
3 changes: 3 additions & 0 deletions cli/bin/run.cmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
@echo off

node "%~dp0\run" %*
gimenete marked this conversation as resolved.
Show resolved Hide resolved
7 changes: 7 additions & 0 deletions cli/bin/run.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/usr/bin/env node
import { Errors, flush, run } from '@oclif/core';
import './mokey-patch-oclif.js';

run(void 0, import.meta.url)
.then(flush)
.catch(Errors.handle);
68 changes: 68 additions & 0 deletions cli/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
{
"name": "@xata.io/xata",
"version": "0.0.0",
"description": "Xata.io CLI",
"author": "Xata Inc.",
"bin": {
"xata": "./bin/run"
},
"homepage": "https://github.com/xataio/client-ts",
"license": "MIT",
"main": "dist/index.js",
"module": "dist/index.js",
"type": "module",
"repository": "xataio/client-ts",
"files": [
"/bin",
"/dist",
"/npm-shrinkwrap.json",
"/oclif.manifest.json"
],
"dependencies": {
"@oclif/core": "^1.8.1",
"@oclif/plugin-help": "^5",
"@oclif/plugin-plugins": "^2.0.1"
},
"devDependencies": {
"eslint": "^7.32.0",
"eslint-config-oclif": "^4",
"eslint-config-oclif-typescript": "^1.0.2",
"globby": "^11",
"oclif": "^3",
"shx": "^0.3.3",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TIL shx is a thing

"ts-node": "^10.2.1",
"tslib": "^2.3.1",
"typescript": "^4.4.3"
},
"oclif": {
"bin": "xata",
"dirname": "xata",
"commands": "./dist/commands",
"plugins": [
"@oclif/plugin-help",
"@oclif/plugin-plugins"
],
"topicSeparator": " ",
"topics": {
"hello": {
"description": "Say hello to the world and others"
}
}
},
"scripts": {
Copy link
Member

@SferaDev SferaDev May 20, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The files in bin probably make sense as scripts instead. I couldn't get to work dev one, but run works fine

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool. I've done that but I've removed the dev one because it doesn't read correclty the ESM commands and I think we don't need it.

"build": "shx rm -rf dist && tsc -b",
"lint": "eslint . --ext .ts --config .eslintrc",
"postpack": "shx rm -f oclif.manifest.json",
"posttest": "yarn lint",
"prepack": "yarn build && oclif manifest && oclif readme",
"version": "oclif readme && git add README.md"
},
"engines": {
"node": ">=12.0.0"
},
"bugs": "https://github.com/xataio/client-ts/issues",
"keywords": [
"oclif"
],
"types": "dist/index.d.ts"
}
15 changes: 15 additions & 0 deletions cli/src/commands/branches/create.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Command } from '@oclif/core';

export default class BranchesCreate extends Command {
static description = 'Create a branch';

static examples = [];

static flags = {};

static args = [];

async run(): Promise<void> {
this.error('To be done');
}
}
15 changes: 15 additions & 0 deletions cli/src/commands/branches/delete.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Command } from '@oclif/core';

export default class BranchesDelete extends Command {
static description = 'Delete a branch';

static examples = [];

static flags = {};

static args = [];

async run(): Promise<void> {
this.error('To be done');
}
}
17 changes: 17 additions & 0 deletions cli/src/commands/branches/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Command } from '@oclif/core';

export default class Branches extends Command {
static description = 'List, create and delete branches';

static examples = [];

static flags = {};

static args = [];

async run(): Promise<void> {
// const { args, flags } = await this.parse(Branches);

this.error('To be done');
}
}
10 changes: 10 additions & 0 deletions cli/src/commands/branches/list.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Config } from '@oclif/core';
import { expect, test } from 'vitest';
import BranchesList from './list.js';

test('branches list', async () => {
const config = await Config.load();
const list = new BranchesList([], config as Config);

await expect(list.run()).rejects.toThrow('To be done');
});
15 changes: 15 additions & 0 deletions cli/src/commands/branches/list.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Command } from '@oclif/core';

export default class BranchesList extends Command {
static description = 'List branches';

static examples = [];

static flags = {};

static args = [];

async run(): Promise<void> {
this.error('To be done');
}
}
17 changes: 17 additions & 0 deletions cli/src/commands/browse/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Command } from '@oclif/core';

export default class Browse extends Command {
static description = 'Open the current database in the browser';

static examples = [];

static flags = {};

static args = [];

async run(): Promise<void> {
// const { args, flags } = await this.parse(Browse);

this.error('To be done');
}
}
15 changes: 15 additions & 0 deletions cli/src/commands/dbs/create.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Command } from '@oclif/core';

export default class DatabasesCreate extends Command {
static description = 'Create a database';

static examples = [];

static flags = {};

static args = [];

async run(): Promise<void> {
this.error('To be done');
}
}
15 changes: 15 additions & 0 deletions cli/src/commands/dbs/delete.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Command } from '@oclif/core';

export default class DatabasesDelete extends Command {
static description = 'Delete a database';

static examples = [];

static flags = {};

static args = [];

async run(): Promise<void> {
this.error('To be done');
}
}
17 changes: 17 additions & 0 deletions cli/src/commands/dbs/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Command } from '@oclif/core';

export default class Databases extends Command {
static description = 'List, create and delete databases';

static examples = [];

static flags = {};

static args = [];

async run(): Promise<void> {
// const { args, flags } = await this.parse(Databases);

this.error('To be done');
}
}
15 changes: 15 additions & 0 deletions cli/src/commands/dbs/list.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Command } from '@oclif/core';

export default class DatabasesList extends Command {
static description = 'List your databases';

static examples = [];

static flags = {};

static args = [];

async run(): Promise<void> {
this.error('To be done');
}
}