diff --git a/features/record/common.feature b/features/record/common.feature index c5de1047690..c1e903eab16 100644 --- a/features/record/common.feature +++ b/features/record/common.feature @@ -66,7 +66,7 @@ Feature: record common Scenario: No authorization information When I run the command with args "record export --base-url $$TEST_KINTONE_BASE_URL --app 1" Then I should get the exit code is non-zero - And The output error message should match with the pattern: "\[401\] \[CB_AU01\] Please login." + And The output error message should match with the pattern: "Authentication required \(login or API token\)" Scenario: Incorrect API token When I run the command with args "record export --base-url $$TEST_KINTONE_BASE_URL --app 1 --api-token abc" @@ -82,7 +82,7 @@ Feature: record common Given Load username and password of user "user_for_common" as env vars: "USERNAME" and "PASSWORD" When I run the command with args "record export --base-url $$TEST_KINTONE_BASE_URL --app 1 --username $USERNAME" Then I should get the exit code is non-zero - And The output error message should match with the pattern: "\[401\] \[CB_WA01\] Password authentication failed." + And The output error message should match with the pattern: "Authentication required \(login or API token\)" Scenario: Incorrect password Given Load username and password of user "user_for_common" as env vars: "USERNAME" and "PASSWORD" diff --git a/features/record/delete.feature b/features/record/delete.feature index 6720fa50d1b..d051a0a85c2 100644 --- a/features/record/delete.feature +++ b/features/record/delete.feature @@ -39,7 +39,7 @@ Feature: record delete And Load app ID of the app "app_for_delete" as env var: "APP_ID" And Load app token of the app "app_for_delete" with exact permissions "view,delete" as env var: "API_TOKEN" And Load username and password of the app "app_for_delete" with exact permissions "add" as env vars: "USERNAME" and "PASSWORD" - When I run the command with args "record delete --app $APP_ID --base-url $$TEST_KINTONE_BASE_URL --api-token $API_TOKEN --username $USERNAME --password $PASSWORD --yes" + When I run the command with args "record delete --app $APP_ID --base-url $$TEST_KINTONE_BASE_URL --api-token $API_TOKEN --yes" Then I should get the exit code is zero And The app "app_for_delete" should have no records @@ -99,7 +99,7 @@ Feature: record delete And Load username and password of the app "app_for_delete" with exact permissions "view,delete" as env vars: "USERNAME" and "PASSWORD" When I run the command with args "record delete --app $APP_ID --base-url $$TEST_KINTONE_BASE_URL --username $USERNAME --password $PASSWORD --yes" Then I should get the exit code is non-zero - And The output error message should match with the pattern: "ERROR: The delete command only supports API token authentication." + And The output error message should match with the pattern: "Authentication required \(API token\)" @serial(app_for_delete) Scenario: Specify records with a file diff --git a/src/__tests__/api.test.ts b/src/__tests__/api.test.ts index 1a2b91c345a..ce469f7f4d6 100644 --- a/src/__tests__/api.test.ts +++ b/src/__tests__/api.test.ts @@ -124,27 +124,6 @@ describe("api", () => { }); }); - it("should prioritize username and password over apiToken", () => { - const apiClient = buildRestAPIClient({ - baseUrl: BASE_URL, - username: USERNAME, - password: PASSWORD, - apiToken: API_TOKEN, - }); - expect(apiClient).toBeInstanceOf(KintoneRestAPIClient); - expect(KintoneRestAPIClient).toHaveBeenCalledWith({ - baseUrl: BASE_URL, - auth: { - username: USERNAME, - password: PASSWORD, - }, - userAgent: expectedUa, - httpsAgent: new https.Agent(), - proxy: false, - socketTimeout: DEFAULT_SOCKET_TIMEOUT, - }); - }); - it("should pass basic auth params to the apiClient correctly", () => { const BASIC_AUTH_USERNAME = "basic_auth_username"; const BASIC_AUTH_PASSWORD = "basic_auth_password"; diff --git a/src/cli/__tests__/commands.test.ts b/src/cli/__tests__/commands.test.ts new file mode 100644 index 00000000000..a0429387a2f --- /dev/null +++ b/src/cli/__tests__/commands.test.ts @@ -0,0 +1,104 @@ +import { vi } from "vitest"; +import childProcess from "child_process"; +import { promisify } from "util"; +import path from "path"; + +const projectRoot = path.resolve(__dirname, "../../../"); +const exec = promisify(childProcess.exec); +const packageJson = require(path.resolve(projectRoot, "package.json")); +const mainFilePath = path.resolve(projectRoot, packageJson.bin["cli-kintone"]); + +vi.setConfig({ testTimeout: 30000 }); + +// Remove auth env vars to test auth validation +const envWithoutAuth = (() => { + const { + KINTONE_USERNAME: _u, + KINTONE_PASSWORD: _p, + KINTONE_API_TOKEN: _t, + ...rest + } = process.env; + return rest as NodeJS.ProcessEnv; +})(); + +const checkRejectArg = ({ + arg, + errorMessage, +}: { + arg: string; + errorMessage: string | RegExp; +}) => { + return expect( + exec(`cross-env LC_ALL='en_US' node ${mainFilePath} ${arg}`, { + env: envWithoutAuth, + }), + ).rejects.toThrow(errorMessage); +}; + +describe("record import", () => { + it("should reject when no auth is provided", () => { + return checkRejectArg({ + arg: "record import --base-url https://example.com --app 1 --file-path /tmp/test.csv", + errorMessage: /Authentication required \(login or API token\)/, + }); + }); +}); + +describe("record export", () => { + it("should reject when no auth is provided", () => { + return checkRejectArg({ + arg: "record export --base-url https://example.com --app 1", + errorMessage: /Authentication required \(login or API token\)/, + }); + }); +}); + +describe("record delete", () => { + it("should reject when no auth is provided", () => { + return checkRejectArg({ + arg: "record delete --base-url https://example.com --app 1 --yes", + errorMessage: /Authentication required \(API token\)/, + }); + }); + + it("should reject --username with auth required error", () => { + return checkRejectArg({ + arg: "record delete --base-url https://example.com --app 1 --yes --username user", + errorMessage: /Authentication required \(API token\)/, + }); + }); +}); + +describe("plugin upload", () => { + it("should reject when no auth is provided", () => { + return checkRejectArg({ + arg: "plugin upload --base-url https://example.com --input /tmp/plugin.zip", + errorMessage: /Authentication required \(login\)/, + }); + }); + + it("should reject --api-token with auth required error", () => { + return checkRejectArg({ + arg: "plugin upload --base-url https://example.com --input /tmp/plugin.zip --api-token xxx", + errorMessage: /Authentication required \(login\)/, + }); + }); +}); + +describe("customize apply", () => { + it("should reject when no auth is provided", () => { + return checkRejectArg({ + arg: "customize apply --base-url https://example.com --app 1 --input /tmp/manifest.json", + errorMessage: /Authentication required \(login\)/, + }); + }); +}); + +describe("customize export", () => { + it("should reject when no auth is provided", () => { + return checkRejectArg({ + arg: "customize export --base-url https://example.com --app 1", + errorMessage: /Authentication required \(login\)/, + }); + }); +}); diff --git a/src/cli/__tests__/connectionOptions.test.ts b/src/cli/__tests__/connectionOptions.test.ts new file mode 100644 index 00000000000..8aedba2b56d --- /dev/null +++ b/src/cli/__tests__/connectionOptions.test.ts @@ -0,0 +1,110 @@ +import yargsFactory from "yargs"; +import { buildConnectionOptions } from "../connectionOptions"; + +const buildParser = ( + config: Parameters[1], + args: string[], +) => { + return buildConnectionOptions(yargsFactory(args), config) + .option("base-url", { default: "https://example.com" }) + .exitProcess(false) + .fail((msg, err) => { + throw err || new Error(msg); + }); +}; + +describe("buildConnectionOptions with password auth", () => { + const config = { auth: ["password"] } as const; + + it("should accept when username and password are provided", () => { + expect(() => + buildParser(config, [ + "--username", + "user", + "--password", + "pw", + ]).parseSync(), + ).not.toThrow(); + }); + + it("should reject when only username is provided", () => { + expect(() => + buildParser(config, ["--username", "user"]).parseSync(), + ).toThrow("Authentication required (login)"); + }); + + it("should reject when only password is provided", () => { + expect(() => buildParser(config, ["--password", "pw"]).parseSync()).toThrow( + "Authentication required (login)", + ); + }); + + it("should reject when no auth is provided", () => { + expect(() => buildParser(config, []).parseSync()).toThrow( + "Authentication required (login)", + ); + }); +}); + +describe("buildConnectionOptions with apiToken auth", () => { + const config = { auth: ["apiToken"] } as const; + + it("should accept when api-token is provided", () => { + expect(() => + buildParser(config, ["--api-token", "token"]).parseSync(), + ).not.toThrow(); + }); + + it("should reject when no auth is provided", () => { + expect(() => buildParser(config, []).parseSync()).toThrow( + "Authentication required (API token)", + ); + }); +}); + +describe("buildConnectionOptions with either auth", () => { + const config = { auth: ["password", "apiToken"] } as const; + + it("should accept when username and password are provided", () => { + expect(() => + buildParser(config, [ + "--username", + "user", + "--password", + "pw", + ]).parseSync(), + ).not.toThrow(); + }); + + it("should accept when only api-token is provided", () => { + expect(() => + buildParser(config, ["--api-token", "token"]).parseSync(), + ).not.toThrow(); + }); + + it("should accept when both are provided and clear api-token", async () => { + const argv = await buildParser(config, [ + "--username", + "user", + "--password", + "pw", + "--api-token", + "token", + ]).parse(); + + expect(argv.username).toBe("user"); + expect(argv["api-token"]).toBeUndefined(); + }); + + it("should reject when only username is provided without password", () => { + expect(() => + buildParser(config, ["--username", "user"]).parseSync(), + ).toThrow("Authentication required (login or API token)"); + }); + + it("should reject when no auth is provided", () => { + expect(() => buildParser(config, []).parseSync()).toThrow( + "Authentication required (login or API token)", + ); + }); +}); diff --git a/src/cli/authOptions/apiTokenAuth.ts b/src/cli/authOptions/apiTokenAuth.ts new file mode 100644 index 00000000000..98b6f0ac163 --- /dev/null +++ b/src/cli/authOptions/apiTokenAuth.ts @@ -0,0 +1,37 @@ +import type { AuthModule } from "./types"; +import type yargs from "yargs"; + +type OptionsDefinition = Parameters[0]; + +const options = { + "api-token": { + describe: "App's API token", + default: process.env.KINTONE_API_TOKEN, + defaultDescription: "KINTONE_API_TOKEN", + type: "array", + string: true, + requiresArg: true, + }, +} satisfies OptionsDefinition; + +export const apiTokenAuth = { + label: "API token", + priority: 1, + options, + hiddenOptions: { + "api-token": { ...options["api-token"], hidden: true }, + }, + check: (argv) => { + const apiToken = argv["api-token"]; + if (!apiToken) { + return false; + } + if (Array.isArray(apiToken)) { + return apiToken.some(Boolean); + } + return !!apiToken; + }, + clear: (argv) => { + argv["api-token"] = undefined; + }, +} satisfies AuthModule; diff --git a/src/cli/authOptions/index.ts b/src/cli/authOptions/index.ts new file mode 100644 index 00000000000..ffec1f52276 --- /dev/null +++ b/src/cli/authOptions/index.ts @@ -0,0 +1,36 @@ +export type { AuthArgv, AuthModule, AuthResolvedArgv } from "./types"; + +import type { AuthArgv, AuthModule } from "./types"; +import { passwordAuth } from "./passwordAuth"; +import { apiTokenAuth } from "./apiTokenAuth"; + +export type AuthMethod = "password" | "apiToken"; + +export const authModules = { + password: passwordAuth, + apiToken: apiTokenAuth, +} satisfies Record; + +export const checkAuth = ( + methods: readonly AuthMethod[], + argv: AuthArgv, +): true => { + if (methods.some((m) => authModules[m].check(argv))) { + return true; + } + const description = methods.map((m) => authModules[m].label).join(" or "); + throw new Error(`Authentication required (${description})`); +}; + +export const resolveAuthPriority = ( + methods: readonly AuthMethod[], + argv: AuthArgv, +): void => { + const matched = methods + .filter((m) => authModules[m].check(argv)) + .sort((a, b) => authModules[a].priority - authModules[b].priority); + + for (const rest of matched.slice(1)) { + authModules[rest].clear(argv); + } +}; diff --git a/src/cli/authOptions/passwordAuth.ts b/src/cli/authOptions/passwordAuth.ts new file mode 100644 index 00000000000..5185b1d8788 --- /dev/null +++ b/src/cli/authOptions/passwordAuth.ts @@ -0,0 +1,38 @@ +import type { AuthModule } from "./types"; +import type yargs from "yargs"; + +type OptionsDefinition = Parameters[0]; + +const options = { + username: { + alias: "u", + describe: "Kintone Username", + default: process.env.KINTONE_USERNAME, + defaultDescription: "KINTONE_USERNAME", + type: "string", + requiresArg: true, + }, + password: { + alias: "p", + describe: "Kintone Password", + default: process.env.KINTONE_PASSWORD, + defaultDescription: "KINTONE_PASSWORD", + type: "string", + requiresArg: true, + }, +} satisfies OptionsDefinition; + +export const passwordAuth = { + label: "login", + priority: 0, + options, + hiddenOptions: { + username: { ...options.username, hidden: true }, + password: { ...options.password, hidden: true }, + }, + check: (argv) => !!argv.username && !!argv.password, + clear: (argv) => { + argv.username = undefined; + argv.password = undefined; + }, +} satisfies AuthModule; diff --git a/src/cli/authOptions/types.ts b/src/cli/authOptions/types.ts new file mode 100644 index 00000000000..03e047edfc3 --- /dev/null +++ b/src/cli/authOptions/types.ts @@ -0,0 +1,22 @@ +import type yargs from "yargs"; + +type OptionsDefinition = Parameters[0]; + +export type AuthArgv = Record; + +export type AuthModule = { + label: string; + /** Lower number = higher priority */ + priority: number; + options: OptionsDefinition; + hiddenOptions: OptionsDefinition; + check: (argv: AuthArgv) => boolean; + clear: (argv: AuthArgv) => void; +}; + +/** Resolved argv types for auth options after yargs parsing */ +export type AuthResolvedArgv = { + username: string | undefined; + password: string | undefined; + "api-token": string[] | undefined; +}; diff --git a/src/cli/commonOptions.ts b/src/cli/commonOptions.ts deleted file mode 100644 index 0d575e3e838..00000000000 --- a/src/cli/commonOptions.ts +++ /dev/null @@ -1,78 +0,0 @@ -import type yargs from "yargs"; - -/** - * Common options for all commands - * Usage: specify to yargs.options() - * ref. https://cli.kintone.dev/guide/options - */ -export const commonOptions = { - "base-url": { - describe: "Kintone Base Url", - default: process.env.KINTONE_BASE_URL, - defaultDescription: "KINTONE_BASE_URL", - type: "string", - demandOption: true, - requiresArg: true, - }, - username: { - alias: "u", - describe: "Kintone Username", - default: process.env.KINTONE_USERNAME, - defaultDescription: "KINTONE_USERNAME", - type: "string", - requiresArg: true, - }, - password: { - alias: "p", - describe: "Kintone Password", - default: process.env.KINTONE_PASSWORD, - defaultDescription: "KINTONE_PASSWORD", - type: "string", - requiresArg: true, - }, - "api-token": { - describe: "App's API token", - default: process.env.KINTONE_API_TOKEN, - defaultDescription: "KINTONE_API_TOKEN", - type: "array", - string: true, - requiresArg: true, - }, - "basic-auth-username": { - describe: "Kintone Basic Auth Username", - default: process.env.KINTONE_BASIC_AUTH_USERNAME, - defaultDescription: "KINTONE_BASIC_AUTH_USERNAME", - type: "string", - requiresArg: true, - }, - "basic-auth-password": { - describe: "Kintone Basic Auth Password", - default: process.env.KINTONE_BASIC_AUTH_PASSWORD, - defaultDescription: "KINTONE_BASIC_AUTH_PASSWORD", - type: "string", - requiresArg: true, - }, - "guest-space-id": { - describe: "The ID of guest space", - default: process.env.KINTONE_GUEST_SPACE_ID, - defaultDescription: "KINTONE_GUEST_SPACE_ID", - type: "string", - requiresArg: true, - }, - "pfx-file-path": { - describe: "The path to client certificate file", - type: "string", - requiresArg: true, - }, - "pfx-file-password": { - describe: "The password of client certificate file", - type: "string", - requiresArg: true, - }, - proxy: { - describe: "The URL of a proxy server", - default: process.env.HTTPS_PROXY ?? process.env.https_proxy, - defaultDescription: "HTTPS_PROXY", - type: "string", - }, -} satisfies Parameters[0]; diff --git a/src/cli/connectionOptions.ts b/src/cli/connectionOptions.ts new file mode 100644 index 00000000000..80d98dcd8a9 --- /dev/null +++ b/src/cli/connectionOptions.ts @@ -0,0 +1,104 @@ +import type yargs from "yargs"; + +import { + type AuthMethod, + type AuthArgv, + type AuthResolvedArgv, + authModules, + checkAuth, + resolveAuthPriority, +} from "./authOptions"; + +type OptionsDefinition = Parameters[0]; + +const connectionOptions = { + "base-url": { + describe: "Kintone Base Url", + default: process.env.KINTONE_BASE_URL, + defaultDescription: "KINTONE_BASE_URL", + type: "string", + demandOption: true, + requiresArg: true, + }, + "basic-auth-username": { + describe: "Kintone Basic Auth Username", + default: process.env.KINTONE_BASIC_AUTH_USERNAME, + defaultDescription: "KINTONE_BASIC_AUTH_USERNAME", + type: "string", + requiresArg: true, + }, + "basic-auth-password": { + describe: "Kintone Basic Auth Password", + default: process.env.KINTONE_BASIC_AUTH_PASSWORD, + defaultDescription: "KINTONE_BASIC_AUTH_PASSWORD", + type: "string", + requiresArg: true, + }, + "pfx-file-path": { + describe: "The path to client certificate file", + type: "string", + requiresArg: true, + }, + "pfx-file-password": { + describe: "The password of client certificate file", + type: "string", + requiresArg: true, + }, + proxy: { + describe: "The URL of a proxy server", + default: process.env.HTTPS_PROXY ?? process.env.https_proxy, + defaultDescription: "HTTPS_PROXY", + type: "string", + }, +} satisfies OptionsDefinition; + +const guestSpaceOptions = { + "guest-space-id": { + describe: "The ID of guest space", + default: process.env.KINTONE_GUEST_SPACE_ID, + defaultDescription: "KINTONE_GUEST_SPACE_ID", + type: "string", + requiresArg: true, + }, +} satisfies OptionsDefinition; + +export const buildConnectionOptions = ( + args: yargs.Argv, + config: { + auth: readonly [AuthMethod, ...AuthMethod[]]; + guestSpace?: boolean; + }, +) => { + // NOTE: Calling .options() in a loop breaks yargs type inference, so we use AuthResolvedArgv to supplement the auth argv types. + let builder = args.options(connectionOptions) as yargs.Argv< + yargs.InferredOptionTypes & AuthResolvedArgv + >; + + for (const [method, module] of Object.entries(authModules)) { + builder = builder.options( + config.auth.includes(method as AuthMethod) + ? module.options + : module.hiddenOptions, + ); + } + + const checked = builder + .options( + config.guestSpace + ? guestSpaceOptions + : { + "guest-space-id": { + ...guestSpaceOptions["guest-space-id"], + hidden: true, + }, + }, + ) + .check((a: AuthArgv): true => checkAuth(config.auth, a)); + + if (config.auth.length > 1) { + return checked.middleware((a: AuthArgv): void => + resolveAuthPriority(config.auth, a), + ); + } + return checked; +}; diff --git a/src/cli/customize/apply.ts b/src/cli/customize/apply.ts index be5ba8a19ad..aa03c6c5acb 100644 --- a/src/cli/customize/apply.ts +++ b/src/cli/customize/apply.ts @@ -2,7 +2,7 @@ import type yargs from "yargs"; import type { CommandModule } from "yargs"; import { logger } from "../../utils/log"; import { RunError } from "../../record/error"; -import { commonOptions } from "../commonOptions"; +import { buildConnectionOptions } from "../connectionOptions"; import { runApply } from "../../customize/apply"; const command = "apply"; @@ -10,10 +10,7 @@ const command = "apply"; const describe = "Apply JavaScript/CSS customization to a kintone app"; const builder = (args: yargs.Argv) => - args - .options(commonOptions) - // NOTE: This command only supports password authn. - .hide("api-token") + buildConnectionOptions(args, { auth: ["password"], guestSpace: true }) .option("input", { alias: "i", describe: "The path to customize-manifest.json", diff --git a/src/cli/customize/export.ts b/src/cli/customize/export.ts index 78b4e4f9a33..8d6d151c9e5 100644 --- a/src/cli/customize/export.ts +++ b/src/cli/customize/export.ts @@ -2,7 +2,7 @@ import type yargs from "yargs"; import type { CommandModule } from "yargs"; import { logger } from "../../utils/log"; import { RunError } from "../../record/error"; -import { commonOptions } from "../commonOptions"; +import { buildConnectionOptions } from "../connectionOptions"; import { runExport } from "../../customize/export"; const command = "export"; @@ -11,10 +11,7 @@ const describe = "Export JavaScript/CSS customization settings from a kintone app"; const builder = (args: yargs.Argv) => - args - .options(commonOptions) - // NOTE: This command only supports password authn. - .hide("api-token") + buildConnectionOptions(args, { auth: ["password"], guestSpace: true }) .option("app", { describe: "The kintone app ID", type: "string", diff --git a/src/cli/plugin/upload.ts b/src/cli/plugin/upload.ts index 31c7229c923..727b69416c4 100644 --- a/src/cli/plugin/upload.ts +++ b/src/cli/plugin/upload.ts @@ -2,7 +2,7 @@ import type yargs from "yargs"; import type { CommandModule } from "yargs"; import { logger } from "../../utils/log"; import { RunError } from "../../record/error"; -import { commonOptions } from "../commonOptions"; +import { buildConnectionOptions } from "../connectionOptions"; import type { Params } from "../../plugin/upload"; import { upload } from "../../plugin/upload"; import type { RestAPIClientOptions } from "../../kintone/client"; @@ -12,12 +12,7 @@ const command = "upload"; const describe = "Upload a plugin to kintone"; const builder = (args: yargs.Argv) => - args - .options(commonOptions) - // NOTE: This command only supports password authn. - .hide("api-token") - // NOTE: This command works regardless of guest space usage. - .hide("guest-space-id") + buildConnectionOptions(args, { auth: ["password"] }) .option("input", { alias: "i", describe: "The input plugin zip", @@ -50,13 +45,11 @@ const handler = async (args: Args) => { watch: args.watch, }; const apiClientOptions: RestAPIClientOptions = { - // TODO: Refactor API client params baseUrl: args["base-url"], username: args.username, password: args.password, basicAuthUsername: args["basic-auth-username"], basicAuthPassword: args["basic-auth-password"], - guestSpaceId: args["guest-space-id"], pfxFilePath: args["pfx-file-path"], pfxFilePassword: args["pfx-file-password"], httpsProxy: args.proxy, diff --git a/src/cli/record/delete.ts b/src/cli/record/delete.ts index d448f57a5cc..984ae1f9919 100644 --- a/src/cli/record/delete.ts +++ b/src/cli/record/delete.ts @@ -2,9 +2,8 @@ import type yargs from "yargs"; import type { CommandModule } from "yargs"; import { run } from "../../record/delete"; import type { SupportedImportEncoding } from "../../utils/file"; -import { logger } from "../../utils/log"; import { confirm } from "@inquirer/prompts"; -import { commonOptions } from "../commonOptions"; +import { buildConnectionOptions } from "../connectionOptions"; const command = "delete"; @@ -16,11 +15,7 @@ const FORCE_DELETE_KEY = "yes"; const FORCE_DELETE_ALIAS = "y"; const builder = (args: yargs.Argv) => - args - .options(commonOptions) - // NOTE: record delete command only accepts API token authn. - .hide("username") - .hide("password") + buildConnectionOptions(args, { auth: ["apiToken"], guestSpace: true }) .option("app", { describe: "The ID of the app", type: "string", @@ -65,12 +60,6 @@ const execute = (args: Args) => { }; const handler = async (args: Args) => { - if (!hasApiToken(args["api-token"]) && (args.username || args.password)) { - logger.error("The delete command only supports API token authentication."); - // eslint-disable-next-line n/no-process-exit - process.exit(1); - } - if (args.yes !== undefined && args.yes) { return execute(args); } @@ -88,18 +77,6 @@ const handler = async (args: Args) => { return undefined; }; -const hasApiToken = (apiTokenArg?: string | string[]): boolean => { - if (!apiTokenArg) { - return false; - } - - if (typeof apiTokenArg === "string") { - return !!apiTokenArg; - } - - return apiTokenArg.filter(Boolean).length > 0; -}; - export const deleteCommand: CommandModule<{}, Args> = { command, describe, diff --git a/src/cli/record/export.ts b/src/cli/record/export.ts index b0942e6f9f5..0ecbd0e952e 100644 --- a/src/cli/record/export.ts +++ b/src/cli/record/export.ts @@ -2,7 +2,7 @@ import type yargs from "yargs"; import type { CommandModule } from "yargs"; import type { ExportFileEncoding } from "../../record/export"; import { run } from "../../record/export"; -import { commonOptions } from "../commonOptions"; +import { buildConnectionOptions } from "../connectionOptions"; const encodings: ExportFileEncoding[] = ["utf8", "sjis"]; @@ -11,8 +11,10 @@ const command = "export"; const describe = "export the records of the specified app"; const builder = (args: yargs.Argv) => - args - .options(commonOptions) + buildConnectionOptions(args, { + auth: ["password", "apiToken"], + guestSpace: true, + }) .option("app", { describe: "The ID of the app", type: "string", diff --git a/src/cli/record/import.ts b/src/cli/record/import.ts index ec84c732d7b..553552c2e8b 100644 --- a/src/cli/record/import.ts +++ b/src/cli/record/import.ts @@ -2,7 +2,7 @@ import type yargs from "yargs"; import { run } from "../../record/import"; import type { SupportedImportEncoding } from "../../utils/file"; import type { CommandModule } from "yargs"; -import { commonOptions } from "../commonOptions"; +import { buildConnectionOptions } from "../connectionOptions"; const command = "import"; @@ -11,8 +11,10 @@ const describe = "import the records of the specified app"; const encoding: SupportedImportEncoding[] = ["utf8", "sjis"]; const builder = (args: yargs.Argv) => - args - .options(commonOptions) + buildConnectionOptions(args, { + auth: ["password", "apiToken"], + guestSpace: true, + }) .option("app", { describe: "The ID of the app", type: "string", diff --git a/website/docs/guide/commands/customize-apply.md b/website/docs/guide/commands/customize-apply.md index f8ddcfea13e..c728a58cc3a 100644 --- a/website/docs/guide/commands/customize-apply.md +++ b/website/docs/guide/commands/customize-apply.md @@ -6,6 +6,10 @@ sidebar_position: 600 The `customize apply` command allows you to apply JavaScript/CSS customization to a Kintone app from a manifest file. +**Notice** + +- This command only supports password authentication. + ## Example ```shell diff --git a/website/docs/guide/commands/customize-export.md b/website/docs/guide/commands/customize-export.md index 6228f5a7c61..7c6a41ef1d2 100644 --- a/website/docs/guide/commands/customize-export.md +++ b/website/docs/guide/commands/customize-export.md @@ -6,6 +6,10 @@ sidebar_position: 500 The `customize export` command allows you to export JavaScript/CSS customization settings from a Kintone app. +**Notice** + +- This command only supports password authentication. + ## Example ```shell diff --git a/website/i18n/ja/docusaurus-plugin-content-docs/current/guide/commands/customize-apply.md b/website/i18n/ja/docusaurus-plugin-content-docs/current/guide/commands/customize-apply.md index c56f3ef747f..ca021cdd39d 100644 --- a/website/i18n/ja/docusaurus-plugin-content-docs/current/guide/commands/customize-apply.md +++ b/website/i18n/ja/docusaurus-plugin-content-docs/current/guide/commands/customize-apply.md @@ -6,6 +6,10 @@ sidebar_position: 600 `customize apply`コマンドは、マニフェストファイルからKintoneアプリにJavaScript/CSSカスタマイズを適用します。 +**注意** + +- このコマンドはパスワード認証のみをサポートしています。 + ## 例 ```shell diff --git a/website/i18n/ja/docusaurus-plugin-content-docs/current/guide/commands/customize-export.md b/website/i18n/ja/docusaurus-plugin-content-docs/current/guide/commands/customize-export.md index d07e6089c59..4ae1ba4f5f0 100644 --- a/website/i18n/ja/docusaurus-plugin-content-docs/current/guide/commands/customize-export.md +++ b/website/i18n/ja/docusaurus-plugin-content-docs/current/guide/commands/customize-export.md @@ -6,6 +6,10 @@ sidebar_position: 500 `customize export`コマンドは、KintoneアプリからJavaScript/CSSカスタマイズ設定をエクスポートします。 +**注意** + +- このコマンドはパスワード認証のみをサポートしています。 + ## 例 ```shell