diff --git a/README.md b/README.md index b269aab..9a8ea00 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,22 @@ npm install @qiita/qiita-cli@latest ## Qiita CLI のセットアップ方法について +### init コマンドを実行する + +以下のコマンドを実行することで、 + +- .gitignore +- GitHub Actions のワークフローファイル + - 「GitHub で記事を管理する」の項目を参照 +- ユーザー設定ファイル(qiita.config.json) + - 「ユーザー設定ファイルについて」の項目を参照 + +が生成されます。 + +```console +npx qiita init +``` + ### Qiita のトークンを発行する 以下の流れでトークンを発行してください。 @@ -161,17 +177,6 @@ Qiita CLI、Qiita Preview から記事の削除はできません。 ## GitHub で記事を管理する -以下のコマンドを実行することで、 - -- .gitignore -- GitHub Actions のワークフローファイル - -が生成されます。 - -```console -npx qiita init -``` - ### GitHub の設定について 以下の流れで設定を行うことで、GitHub の特定のブランチにコミットしたタイミングで記事の投稿や更新を行うことが可能になります。 @@ -204,19 +209,37 @@ npx qiita pull ### version -qiita-cli のバージョンを確認できます。 +Qiita CLI のバージョンを確認できます。 ```console npx qiita version ``` +## ユーザー設定ファイルについて + +`npx qiita init`コマンドで生成される`qiita.config.json`について説明します。 +このファイルを用いて、Qiita CLI の設定を行うことができます。 +設定できるオプションは以下の通りです。 + +- includePrivate: 限定共有記事を含めるかどうかを選べます。デフォルトは`false`です。 + ## オプション +### --credential \ + +Qiita CLI の認証情報(`credentials.json`)を配置する・しているディレクトリを指定できます。 +デフォルトでは`$XDG_CONFIG_HOME/qiita-cli`もしくは`$HOME/.config/qiita-cli`になっています。 + +```console +npx qiita login ---credential ./my_conf/ +npx qiita preview --credential ./my_conf/ +``` + ### --config \ -qiita-cli の設定情報(`credentials.json`)を配置する・しているディレクトリを指定できます。 +Qiita CLI の設定情報(`qiita.config.json`)を配置する・しているディレクトリを指定できます。 -デフォルトでは`$XDG_CONFIG_HOME/qiita-cli`もしくは`$HOME/.config/qiita-cli`になっています。 +デフォルトでは、カレントディレクトリになります。 例) diff --git a/src/commands/help.ts b/src/commands/help.ts index 5c7ea12..13c5116 100644 --- a/src/commands/help.ts +++ b/src/commands/help.ts @@ -9,12 +9,12 @@ COMMAND: publish ... 記事を投稿、更新 publish --all 全ての記事を投稿、更新 pull 記事ファイルをQiitaと同期 - version qiita-cliのバージョンを表示 + version Qiita CLIのバージョンを表示 help ヘルプを表示 OPTIONS: - --config - qiita-cliの設定情報を配置するディレクトリを指定 + --credential + Qiita CLIの認証情報を配置するディレクトリを指定 --root 記事ファイルをダウンロードするディレクトリを指定 @@ -22,6 +22,9 @@ OPTIONS: --verbose 詳細表示オプションを有効 + --config + 設定ファイルを配置するディレクトリを指定 + 詳細についてはReadme(https://github.com/increments/qiita-cli)をご覧ください `; diff --git a/src/commands/init.ts b/src/commands/init.ts index 986133f..d52d187 100644 --- a/src/commands/init.ts +++ b/src/commands/init.ts @@ -1,5 +1,6 @@ import fs from "node:fs"; import path from "node:path"; +import { config } from "../lib/config"; export const init = async () => { const rootDir = process.cwd(); @@ -48,6 +49,18 @@ jobs: node_modules `; writeFile(gitignoreFilePath, gitignoreFileContent); + + const userConfigFilePath = config.getUserConfigFilePath(); + const userConfigDir = config.getUserConfigDir(); + if (!fs.existsSync(userConfigFilePath)) { + fs.mkdirSync(userConfigDir, { recursive: true }); + } + const userConfigFileContent = JSON.stringify( + await config.getUserConfig(), + null, + 2 + ); + writeFile(userConfigFilePath, userConfigFileContent); }; const writeFile = (path: string, content: string) => { diff --git a/src/lib/config.test.ts b/src/lib/config.test.ts index ea5e0d4..2d67841 100644 --- a/src/lib/config.test.ts +++ b/src/lib/config.test.ts @@ -55,15 +55,23 @@ jest.mock("node:fs/promises", () => { mkdir: jest.fn(() => {}), }; }); +jest.mock("node:fs", () => { + return { + existsSync: jest.fn((filePath: string): boolean => { + const text = getFile(filePath); + return !!text; + }), + }; +}); describe("config", () => { - describe("#getConfigDir", () => { + describe("#getCredentialDir", () => { beforeEach(() => { config.load({}); }); it("returns default path", () => { - expect(config.getConfigDir()).toEqual( + expect(config.getCredentialDir()).toEqual( "/home/test-user/.config/qiita-cli" ); }); @@ -71,13 +79,13 @@ describe("config", () => { describe("with options", () => { beforeEach(() => { config.load({ - configDir: "my-config", + credentialDir: "my-credential", }); }); it("returns customized path", () => { - expect(config.getConfigDir()).toEqual( - "/home/test-user/qiita-articles/my-config" + expect(config.getCredentialDir()).toEqual( + "/home/test-user/qiita-articles/my-credential" ); }); }); @@ -111,6 +119,101 @@ describe("config", () => { }); }); + describe("#getUserConfigDir", () => { + describe("paths", () => { + beforeEach(() => { + config.load({}); + }); + + it("returns default path", () => { + expect(config.getUserConfigDir()).toEqual( + "/home/test-user/qiita-articles" + ); + }); + + describe("with options", () => { + beforeEach(() => { + config.load({ + userConfigDir: "my-root", + }); + }); + + it("returns customized path", () => { + expect(config.getUserConfigDir()).toEqual( + "/home/test-user/qiita-articles/my-root" + ); + }); + }); + }); + }); + + describe("#getUserConfigFilePath", () => { + describe("paths", () => { + beforeEach(() => { + config.load({}); + }); + + it("returns default path", () => { + expect(config.getUserConfigFilePath()).toEqual( + "/home/test-user/qiita-articles/qiita.config.json" + ); + }); + + describe("with options", () => { + beforeEach(() => { + config.load({ + userConfigDir: "my-root", + }); + }); + + it("returns customized path", () => { + expect(config.getUserConfigFilePath()).toEqual( + "/home/test-user/qiita-articles/my-root/qiita.config.json" + ); + }); + }); + }); + }); + + describe("#getUserConfig", () => { + const userConfigFilePath = + "/home/test-user/qiita-articles/qiita.config.json"; + + beforeEach(() => { + config.load({}); + }); + + describe("when user config file already exists", () => { + beforeEach(() => { + const userConfigData = { + includePrivate: true, + }; + resetFiles(); + setFile(userConfigFilePath, JSON.stringify(userConfigData, null, 2)); + }); + + it("returns user config", async () => { + const userConfig = await config.getUserConfig(); + expect(userConfig).toStrictEqual({ + includePrivate: true, + }); + }); + }); + + describe("when user config file does not exist", () => { + beforeEach(() => { + resetFiles(); + }); + + it("returns default user config", async () => { + const userConfig = await config.getUserConfig(); + expect(userConfig).toStrictEqual({ + includePrivate: false, + }); + }); + }); + }); + describe("#getCredential", () => { const credentialFilePath = "/home/test-user/.config/qiita-cli/credentials.json"; diff --git a/src/lib/config.ts b/src/lib/config.ts index 1f1cbd3..f7a43b6 100644 --- a/src/lib/config.ts +++ b/src/lib/config.ts @@ -1,44 +1,57 @@ import fs from "node:fs/promises"; +import fsSync from "node:fs"; import os from "node:os"; import path from "node:path"; import process from "node:process"; import { configDebugger } from "./debugger"; interface Options { - configDir?: string; + credentialDir?: string; profile?: string; itemsRootDir?: string; + userConfigDir?: string; } +type UserConfig = { + includePrivate: boolean; +}; + class Config { - private configDir?: string; + private credentialDir?: string; private itemsRootDir?: string; + private userConfigFilePath?: string; + private userConfigDir?: string; private credential?: Credential; constructor() {} load(options: Options) { - this.configDir = this.resolveConfigDir(options.configDir); + this.credentialDir = this.resolveConfigDir(options.credentialDir); this.itemsRootDir = this.resolveItemsRootDir(options.itemsRootDir); + this.userConfigDir = this.resolveUserConfigDirPath(options.userConfigDir); + this.userConfigFilePath = this.resolveUserConfigFilePath( + options.userConfigDir + ); this.credential = new Credential({ - credentialDir: this.configDir, + credentialDir: this.credentialDir, profile: options.profile, }); configDebugger( "load", JSON.stringify({ - configDir: this.configDir, + credentialDir: this.credentialDir, itemsRootDir: this.itemsRootDir, + userConfigFilePath: this.userConfigFilePath, }) ); } - getConfigDir() { - if (!this.configDir) { - throw new Error("configDir is undefined"); + getCredentialDir() { + if (!this.credentialDir) { + throw new Error("credentialDir is undefined"); } - return this.configDir; + return this.credentialDir; } // TODO: filesystemrepo 側にあるべきか確認 @@ -49,6 +62,20 @@ class Config { return this.itemsRootDir; } + getUserConfigDir() { + if (!this.userConfigDir) { + throw new Error("userConfigDir is undefined"); + } + return this.userConfigDir; + } + + getUserConfigFilePath() { + if (!this.userConfigFilePath) { + throw new Error("userConfigFilePath is undefined"); + } + return this.userConfigFilePath; + } + getCredential() { if (!this.credential) { throw new Error("credential is undefined"); @@ -63,19 +90,37 @@ class Config { return this.credential.setCredential(credential); } - private resolveConfigDir(configDirPath?: string) { + async getUserConfig() { + const defaultConfig = { + includePrivate: false, + } as UserConfig; + + if (fsSync.existsSync(this.getUserConfigFilePath())) { + const userConfigFileData = await fs.readFile( + this.userConfigFilePath as string + ); + const userConfig = JSON.parse( + userConfigFileData.toString() + ) as UserConfig; + return { ...defaultConfig, ...userConfig }; + } + + return defaultConfig; + } + + private resolveConfigDir(credentialDirPath?: string) { const packageName = "qiita-cli"; if (process.env.XDG_CONFIG_HOME) { - const configDir = process.env.XDG_CONFIG_HOME; - return path.join(configDir, packageName); + const credentialDir = process.env.XDG_CONFIG_HOME; + return path.join(credentialDir, packageName); } - if (!configDirPath) { + if (!credentialDirPath) { const homeDir = os.homedir(); return path.join(homeDir, ".config", packageName); } - return this.resolveFullPath(configDirPath); + return this.resolveFullPath(credentialDirPath); } private resolveItemsRootDir(dirPath?: string) { @@ -89,6 +134,22 @@ class Config { return this.resolveFullPath(dirPath); } + private resolveUserConfigDirPath(dirPath?: string) { + if (process.env.QIITA_CLI_USER_CONFIG_DIR) { + return process.env.QIITA_CLI_USER_CONFIG_DIR; + } + if (!dirPath) { + return process.cwd(); + } + + return this.resolveFullPath(dirPath); + } + + private resolveUserConfigFilePath(dirPath?: string) { + const filename = "qiita.config.json"; + return path.join(this.resolveUserConfigDirPath(dirPath), filename); + } + private resolveFullPath(filePath: string) { if (path.isAbsolute(filePath)) { return filePath; diff --git a/src/lib/sync-articles-from-qiita.ts b/src/lib/sync-articles-from-qiita.ts index 7465047..3c99cce 100644 --- a/src/lib/sync-articles-from-qiita.ts +++ b/src/lib/sync-articles-from-qiita.ts @@ -1,5 +1,6 @@ import type { QiitaApi } from "../qiita-api"; import type { FileSystemRepo } from "./file-system-repo"; +import { config } from "./config"; export const syncArticlesFromQiita = async ({ fileSystemRepo, @@ -9,12 +10,16 @@ export const syncArticlesFromQiita = async ({ qiitaApi: QiitaApi; }) => { const per = 100; + const userConfig = await config.getUserConfig(); for (let page = 1; page <= 100; page += 1) { const items = await qiitaApi.authenticatedUserItems(page, per); if (items.length <= 0) { break; } - await fileSystemRepo.saveItems(items); + const result = userConfig.includePrivate + ? items + : items.filter((item) => !item.private); + await fileSystemRepo.saveItems(result); } }; diff --git a/src/main.ts b/src/main.ts index 4147355..c6424ce 100644 --- a/src/main.ts +++ b/src/main.ts @@ -11,8 +11,9 @@ dotenv.config(); const args = arg( { - "--config": String, + "--credential": String, "--profile": String, + "--config": String, "--root": String, "--verbose": Boolean, }, @@ -29,9 +30,10 @@ if (args["--verbose"]) { } config.load({ - configDir: args["--config"], + credentialDir: args["--credential"], profile: args["--profile"], itemsRootDir: args["--root"], + userConfigDir: args["--config"], }); exec(commandName, commandArgs);