From d35195f2f71a0e05a3978e665707e2a9804f1a79 Mon Sep 17 00:00:00 2001 From: Yukai Huang Date: Fri, 28 Apr 2023 19:37:56 +0800 Subject: [PATCH 1/6] feat: use $EDITOR for creating notes with content --- src/commands/notes/create.ts | 94 ++++++++++++++++++++++++------------ src/openEditor.ts | 45 +++++++++++++++++ src/utils.ts | 11 ++++- 3 files changed, 117 insertions(+), 33 deletions(-) create mode 100644 src/openEditor.ts diff --git a/src/commands/notes/create.ts b/src/commands/notes/create.ts index ed669ad..82e6da4 100644 --- a/src/commands/notes/create.ts +++ b/src/commands/notes/create.ts @@ -1,12 +1,23 @@ -import {CommentPermissionType, CreateNoteOptions, NotePermissionRole} from '@hackmd/api/dist/type' -import {CliUx, Flags} from '@oclif/core' +import { + CommentPermissionType, + CreateNoteOptions, + NotePermissionRole, +} from "@hackmd/api/dist/type"; +import { CliUx, Flags } from "@oclif/core"; +import * as fs from "fs"; -import HackMDCommand from '../../command' -import {commentPermission, noteContent, notePermission, noteTitle} from '../../flags' -import {safeStdinRead} from '../../utils' +import HackMDCommand from "../../command"; +import { + commentPermission, + noteContent, + notePermission, + noteTitle, +} from "../../flags"; +import { safeStdinRead, temporaryMD } from "../../utils"; +import openEditor from "../../openEditor"; -export default class Create extends HackMDCommand { - static description = 'Create a note' +export default class CreateCommand extends HackMDCommand { + static description = "Create a note"; static examples = [ "notes create --content='# A new note' --readPermission=owner --writePermission=owner --commentPermission=disabled", @@ -15,54 +26,73 @@ export default class Create extends HackMDCommand { ────────────────────── ──────────────────────────────── ────────────────────── ──────── raUuSTetT5uQbqQfLnz9lA A new note gvfz2UB5THiKABQJQnLs6Q null`, - 'Or you can pipe content via Unix pipeline:', - 'cat README.md | hackmd-cli notes create' - ] + "Or you can pipe content via Unix pipeline:", + "cat README.md | hackmd-cli notes create", + ]; static flags = { - help: Flags.help({char: 'h'}), + help: Flags.help({ char: "h" }), title: noteTitle(), content: noteContent(), readPermission: notePermission(), writePermission: notePermission(), commentPermission: commentPermission(), + editor: Flags.boolean({ + char: "e", + description: "create note with $EDITOR", + }), ...CliUx.ux.table.flags(), - } + }; async run() { - const {flags} = await this.parse(Create) - const pipeString = safeStdinRead() + const { flags } = await this.parse(CreateCommand); + const pipeString = safeStdinRead(); const options: CreateNoteOptions = { title: flags.title, content: pipeString || flags.content, readPermission: flags.readPermission as NotePermissionRole, writePermission: flags.writePermission as NotePermissionRole, - commentPermission: flags.commentPermission as CommentPermissionType + commentPermission: flags.commentPermission as CommentPermissionType, + }; + + if (flags.editor) { + try { + const mdFile = temporaryMD(); + await openEditor(mdFile); + + options.content = fs.readFileSync(mdFile).toString(); + } catch (e) { + this.error(e as Error); + } } try { - const APIClient = await this.getAPIClient() - const note = await APIClient.createNote(options) + const APIClient = await this.getAPIClient(); + const note = await APIClient.createNote(options); - CliUx.ux.table([note], { - id: { - header: 'ID', - }, - title: {}, - userPath: { - header: 'User path' + CliUx.ux.table( + [note], + { + id: { + header: "ID", + }, + title: {}, + userPath: { + header: "User path", + }, + teamPath: { + header: "Team path", + }, }, - teamPath: { - header: 'Team path' + { + printLine: this.log.bind(this), + ...flags, } - }, { - printLine: this.log.bind(this), - ...flags - }) + ); } catch (e) { - this.log('Create note failed') - this.error(e as Error) + this.log("Create note failed"); + this.error(e as Error); } } } diff --git a/src/openEditor.ts b/src/openEditor.ts new file mode 100644 index 0000000..9e232bf --- /dev/null +++ b/src/openEditor.ts @@ -0,0 +1,45 @@ +import { spawn, ChildProcess } from "child_process"; + +interface EditorOptions { + editor?: string; +} + +export function openEditor( + file: string, + opts: EditorOptions = {} +): Promise { + return new Promise((resolve, reject) => { + const editor = getEditor(opts.editor); + const args = editor.split(/\s+/); + const bin = args.shift(); + + if (!bin) { + reject(new Error("Editor binary not found")); + return; + } + + console.debug(`Opening ${file} with ${bin} ${args.join(" ")}`); + + const ps: ChildProcess = spawn(bin, [...args, file], { stdio: "inherit" }); + + ps.on("exit", (code: number, sig: NodeJS.Signals) => { + resolve(); + }); + + ps.on("error", (err: Error) => { + reject(err); + }); + }); +} + +function getEditor(editor?: string): string { + return ( + editor || process.env.VISUAL || process.env.EDITOR || getDefaultEditor() + ); +} + +function getDefaultEditor(): string { + return /^win/.test(process.platform) ? "notepad" : "vim"; +} + +export default openEditor; diff --git a/src/utils.ts b/src/utils.ts index 5ece3b3..e47cbd9 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,5 +1,5 @@ import fs from 'fs' -import {homedir} from 'os' +import {homedir, tmpdir} from 'os' import * as path from 'path' export function getConfigFilePath() { @@ -32,3 +32,12 @@ export function safeStdinRead() { } catch {} return result } + +// generate temporary markdown file in /tmp directory +export function temporaryMD() { + const tmpDir = tmpdir(); + const filename = `temp_${Math.random().toString(36).substring(2)}.md`; + const filePath = path.join(tmpDir, filename); + + return filePath; +} From adb744aa0ae31252dc2b9cfcb541623b648c2fd3 Mon Sep 17 00:00:00 2001 From: Yukai Huang Date: Fri, 28 Apr 2023 20:36:13 +0800 Subject: [PATCH 2/6] fix: ensure config file is present --- src/utils.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/utils.ts b/src/utils.ts index e47cbd9..fe9f7fc 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,4 +1,4 @@ -import fs from 'fs' +import fs from 'fs-extra' import {homedir, tmpdir} from 'os' import * as path from 'path' @@ -10,7 +10,11 @@ export function getConfigFilePath() { configDir = path.join(homedir(), '.hackmd') } - return path.join(configDir, 'config.json') + const configPath = path.join(configDir, 'config.json') + + fs.ensureFileSync(configPath) + + return configPath } export function setAccessTokenConfig(token: string) { From 42c6484a0802097850f4838ebec8a4282fc8e479 Mon Sep 17 00:00:00 2001 From: Yukai Huang Date: Fri, 28 Apr 2023 20:49:06 +0800 Subject: [PATCH 3/6] fix: create config file with empty object --- src/utils.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/utils.ts b/src/utils.ts index fe9f7fc..e2b80c8 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -12,7 +12,10 @@ export function getConfigFilePath() { const configPath = path.join(configDir, 'config.json') - fs.ensureFileSync(configPath) + if (!fs.existsSync(configDir)) { + fs.ensureFileSync(configPath) + fs.writeFileSync(configPath, JSON.stringify({})) + } return configPath } @@ -30,9 +33,8 @@ export function setAccessTokenConfig(token: string) { export function safeStdinRead() { let result - const STDIN_FD = 0 try { - result = fs.readFileSync(STDIN_FD).toString() + result = fs.readFileSync(process.stdin.fd).toString() } catch {} return result } From e9ae15a5f6659bb3f94ee756d35781b3e6dbefe4 Mon Sep 17 00:00:00 2001 From: Yukai Huang Date: Fri, 28 Apr 2023 20:49:18 +0800 Subject: [PATCH 4/6] chore: bump oclif to 18 --- .nvmrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.nvmrc b/.nvmrc index 98d9bcb..3c03207 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -17 +18 From b21fcbcb897b295c10d90fc1c651df85dcfb504f Mon Sep 17 00:00:00 2001 From: Yukai Huang Date: Fri, 28 Apr 2023 20:56:54 +0800 Subject: [PATCH 5/6] chore: use node 18 in workflow --- .github/workflows/config-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/config-test.yml b/.github/workflows/config-test.yml index 7fe22de..a5d054b 100644 --- a/.github/workflows/config-test.yml +++ b/.github/workflows/config-test.yml @@ -9,7 +9,7 @@ jobs: strategy: matrix: - node-version: ['12', '14', '16'] + node-version: ['18'] steps: - name: Checkout repository From 5959ddc054c94521172492f09095a96c9753762a Mon Sep 17 00:00:00 2001 From: Yukai Huang Date: Fri, 28 Apr 2023 21:00:47 +0800 Subject: [PATCH 6/6] fix: tslint --- src/commands/notes/create.ts | 60 ++++++++++++++++++------------------ src/open-editor.ts | 43 ++++++++++++++++++++++++++ src/openEditor.ts | 45 --------------------------- src/utils.ts | 8 ++--- 4 files changed, 77 insertions(+), 79 deletions(-) create mode 100644 src/open-editor.ts delete mode 100644 src/openEditor.ts diff --git a/src/commands/notes/create.ts b/src/commands/notes/create.ts index 82e6da4..635697e 100644 --- a/src/commands/notes/create.ts +++ b/src/commands/notes/create.ts @@ -2,22 +2,22 @@ import { CommentPermissionType, CreateNoteOptions, NotePermissionRole, -} from "@hackmd/api/dist/type"; -import { CliUx, Flags } from "@oclif/core"; -import * as fs from "fs"; +} from '@hackmd/api/dist/type' +import {CliUx, Flags} from '@oclif/core' +import * as fs from 'fs' -import HackMDCommand from "../../command"; +import HackMDCommand from '../../command' import { commentPermission, noteContent, notePermission, noteTitle, -} from "../../flags"; -import { safeStdinRead, temporaryMD } from "../../utils"; -import openEditor from "../../openEditor"; +} from '../../flags' +import openEditor from '../../open-editor' +import {safeStdinRead, temporaryMD} from '../../utils' export default class CreateCommand extends HackMDCommand { - static description = "Create a note"; + static description = 'Create a note' static examples = [ "notes create --content='# A new note' --readPermission=owner --writePermission=owner --commentPermission=disabled", @@ -26,27 +26,27 @@ export default class CreateCommand extends HackMDCommand { ────────────────────── ──────────────────────────────── ────────────────────── ──────── raUuSTetT5uQbqQfLnz9lA A new note gvfz2UB5THiKABQJQnLs6Q null`, - "Or you can pipe content via Unix pipeline:", - "cat README.md | hackmd-cli notes create", - ]; + 'Or you can pipe content via Unix pipeline:', + 'cat README.md | hackmd-cli notes create', + ] static flags = { - help: Flags.help({ char: "h" }), + help: Flags.help({char: 'h'}), title: noteTitle(), content: noteContent(), readPermission: notePermission(), writePermission: notePermission(), commentPermission: commentPermission(), editor: Flags.boolean({ - char: "e", - description: "create note with $EDITOR", + char: 'e', + description: 'create note with $EDITOR', }), ...CliUx.ux.table.flags(), - }; + } async run() { - const { flags } = await this.parse(CreateCommand); - const pipeString = safeStdinRead(); + const {flags} = await this.parse(CreateCommand) + const pipeString = safeStdinRead() const options: CreateNoteOptions = { title: flags.title, @@ -54,45 +54,45 @@ raUuSTetT5uQbqQfLnz9lA A new note gvfz2UB5THiKABQJQnLs6Q readPermission: flags.readPermission as NotePermissionRole, writePermission: flags.writePermission as NotePermissionRole, commentPermission: flags.commentPermission as CommentPermissionType, - }; + } if (flags.editor) { try { - const mdFile = temporaryMD(); - await openEditor(mdFile); + const mdFile = temporaryMD() + await openEditor(mdFile) - options.content = fs.readFileSync(mdFile).toString(); + options.content = fs.readFileSync(mdFile).toString() } catch (e) { - this.error(e as Error); + this.error(e as Error) } } try { - const APIClient = await this.getAPIClient(); - const note = await APIClient.createNote(options); + const APIClient = await this.getAPIClient() + const note = await APIClient.createNote(options) CliUx.ux.table( [note], { id: { - header: "ID", + header: 'ID', }, title: {}, userPath: { - header: "User path", + header: 'User path', }, teamPath: { - header: "Team path", + header: 'Team path', }, }, { printLine: this.log.bind(this), ...flags, } - ); + ) } catch (e) { - this.log("Create note failed"); - this.error(e as Error); + this.log('Create note failed') + this.error(e as Error) } } } diff --git a/src/open-editor.ts b/src/open-editor.ts new file mode 100644 index 0000000..2b255f7 --- /dev/null +++ b/src/open-editor.ts @@ -0,0 +1,43 @@ +import {ChildProcess, spawn} from 'child_process' + +interface EditorOptions { + editor?: string +} + +export function openEditor( + file: string, + opts: EditorOptions = {} +): Promise { + return new Promise((resolve, reject) => { + const editor = getEditor(opts.editor) + const args = editor.split(/\s+/) + const bin = args.shift() + + if (!bin) { + reject(new Error('Editor binary not found')) + return + } + + const ps: ChildProcess = spawn(bin, [...args, file], {stdio: 'inherit'}) + + ps.on('exit', () => { + resolve() + }) + + ps.on('error', (err: Error) => { + reject(err) + }) + }) +} + +function getEditor(editor?: string): string { + return ( + editor || process.env.VISUAL || process.env.EDITOR || getDefaultEditor() + ) +} + +function getDefaultEditor(): string { + return /^win/.test(process.platform) ? 'notepad' : 'vim' +} + +export default openEditor diff --git a/src/openEditor.ts b/src/openEditor.ts deleted file mode 100644 index 9e232bf..0000000 --- a/src/openEditor.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { spawn, ChildProcess } from "child_process"; - -interface EditorOptions { - editor?: string; -} - -export function openEditor( - file: string, - opts: EditorOptions = {} -): Promise { - return new Promise((resolve, reject) => { - const editor = getEditor(opts.editor); - const args = editor.split(/\s+/); - const bin = args.shift(); - - if (!bin) { - reject(new Error("Editor binary not found")); - return; - } - - console.debug(`Opening ${file} with ${bin} ${args.join(" ")}`); - - const ps: ChildProcess = spawn(bin, [...args, file], { stdio: "inherit" }); - - ps.on("exit", (code: number, sig: NodeJS.Signals) => { - resolve(); - }); - - ps.on("error", (err: Error) => { - reject(err); - }); - }); -} - -function getEditor(editor?: string): string { - return ( - editor || process.env.VISUAL || process.env.EDITOR || getDefaultEditor() - ); -} - -function getDefaultEditor(): string { - return /^win/.test(process.platform) ? "notepad" : "vim"; -} - -export default openEditor; diff --git a/src/utils.ts b/src/utils.ts index e2b80c8..b3fe381 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -41,9 +41,9 @@ export function safeStdinRead() { // generate temporary markdown file in /tmp directory export function temporaryMD() { - const tmpDir = tmpdir(); - const filename = `temp_${Math.random().toString(36).substring(2)}.md`; - const filePath = path.join(tmpDir, filename); + const tmpDir = tmpdir() + const filename = `temp_${Math.random().toString(36).substring(2)}.md` + const filePath = path.join(tmpDir, filename) - return filePath; + return filePath }