Skip to content

feat: add plugin check command (Experimental)#1228

Draft
tasshi-me wants to merge 20 commits intomainfrom
feat/plugin-check
Draft

feat: add plugin check command (Experimental)#1228
tasshi-me wants to merge 20 commits intomainfrom
feat/plugin-check

Conversation

@tasshi-me
Copy link
Member

@tasshi-me tasshi-me commented Nov 14, 2025

Why

When developing kintone plugins, using internal APIs or internal CSS class names creates a risk that the plugin will break in future kintone updates. However, there was no mechanism to detect these usages beforehand.

We need a tool that can automatically detect potential issues so plugin developers can write safer code.

What

Added a plugin check command to check kintone plugin code for potential issues.

⚠️ Note: This is an experimental feature under early development. The command behavior, options, and check rules may change in future releases.

This command inspects JavaScript files in plugins using the following custom ESLint rules:

Check Rules

  1. no-cybozu-data (Doc) :

    • Detects access to the internal API cybozu.data
    • Covers patterns like cybozu.data, cybozu["data"], cybozu?.data, etc.
  2. no-kintone-internal-selector (Doc) :

    • Detects usage of internal CSS class names
    • Detects the following patterns:
      • gaia-argoui-* (e.g., gaia-argoui-button)
      • *-gaia (e.g., button-gaia)
      • ocean-* (e.g., ocean-ui-button)
      • kintone-* (e.g., kintone-dialog)
    • Detects not only DOM API calls (querySelector, querySelectorAll, closest, getElementsByClassName, matches) but also class names in string literals (e.g., jQuery)

Features

  • Accepts plugin zip files or manifest.json files as input
  • Supports plain (default) and json output formats

How to test

Basic functionality

# Build the project
pnpm build

# Check a plugin zip file
./bin/cli-kintone.js plugin check --input ./path/to/plugin.zip

# Check by specifying manifest.json
./bin/cli-kintone.js plugin check --input ./path/to/manifest.json

# Output in JSON format
./bin/cli-kintone.js plugin check --input ./plugin.zip --format json

Run test cases

# Run unit tests
pnpm test

# Check ESLint rule tests
pnpm test src/plugin/check/rules/__tests__/

Sample code for detection verification

You can test with a plugin containing code like:

// cybozu.data usage (should be detected)
console.log(cybozu.data);
const data = cybozu.data;

// Internal class name usage (should be detected)
document.querySelector(".gaia-argoui-button");
document.querySelectorAll(".ocean-ui-dialog");
element.closest(".kintone-dialog");
$(".button-gaia").click();

Checklist

  • Read CONTRIBUTING.md
  • Updated documentation if it is required.
  • Added/updated tests if it is required. (or tested manually)
  • Passed pnpm lint and pnpm test on the root directory.

Comment on lines +14 to +27
import noCybozuData from "./rules/no-cybozu-data";
import noKintoneInternalSelector from "./rules/no-kintone-internal-selector";

const eslintConfig: Linter.Config[] = [
{
// https://eslint.org/docs/latest/use/configure/plugins#configure-a-virtual-plugin
plugins: {
local: {
rules: {
"no-cybozu-data": noCybozuData,
"no-kintone-internal-selector": noKintoneInternalSelector,
},
},
},
Copy link
Member Author

Choose a reason for hiding this comment

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

cli-kintoneのローカルルールとして作っていたルールをjs-sdkのeslint-pluginに移動した。
kintone/js-sdk#3616

そのため、このローカルルールを削除して、@kintone/eslint-pluginを使うようにする。

Copy link
Member Author

Choose a reason for hiding this comment

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

eslint-pluginのrecommendedを設定すればOK。
(eslint-plugin側がリリースされるまでCIが落ちるかも)


## Checked Rules

The `plugin check` command runs the following checks on JavaScript files in the plugin:
Copy link
Member Author

Choose a reason for hiding this comment

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

ここのルール説明はeslint-pluginへのリンクにする

@tasshi-me tasshi-me marked this pull request as ready for review February 24, 2026 12:30
@tasshi-me tasshi-me requested a review from a team as a code owner February 24, 2026 12:30
@tasshi-me tasshi-me requested review from chihiro-adachi and Copilot and removed request for a team February 24, 2026 12:30
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds an experimental plugin check subcommand to help plugin developers detect usage of kintone internal/unsupported APIs and internal CSS selectors before release.

Changes:

  • Introduces plugin check command wiring in the CLI and a new implementation that runs custom ESLint rules against plugin JS sources.
  • Updates packaging/dependencies so ESLint and @kintone/eslint-plugin are available for the npm-distributed CLI while excluded from the SEA binary.
  • Adds (unlisted) documentation and E2E scaffolding/assets for the new command.

Reviewed changes

Copilot reviewed 14 out of 16 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
website/docs/guide/commands/plugin-check.md New English docs page for plugin check usage/options/output.
website/i18n/ja/docusaurus-plugin-content-docs/current/guide/commands/plugin-check.md New Japanese docs page for plugin check.
src/plugin/core/plugin/index.ts Exposes contentsZip() so other features can read plugin contents from a zip.
src/plugin/check/index.ts New implementation that dynamically imports ESLint + runs checks, supports plain/json output.
src/cli/plugin/index.ts Registers the new plugin check subcommand under plugin.
src/cli/plugin/check.ts New yargs command module for plugin check (experimental).
package.json Excludes ESLint from ncc bundle; adds runtime deps for plugin check.
pnpm-workspace.yaml Excludes @kintone/eslint-plugin from minimum release age.
pnpm-lock.yaml Lockfile updates for added dependencies.
license-manager.config.js Adds license overrides for newly introduced transitive deps.
cucumber.js Skips @skip scenarios by default.
features/plugin/check.feature Adds E2E scenarios for plugin check (real linting scenarios are @skip).
features/step_definitions/file.ts Adds fixture mapping for plugin_with_issues.
features/assets/plugin_with_issues/** Adds fixture plugin project used by E2E tests.
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +58 to +84
```
js/script.js
42:7 error Accessing `cybozu.data` is not allowed. (local/no-cybozu-data)

Files checked: 3
Problems: 1 (Errors: 1, Warnings: 0)
```

### JSON Format

```json
{
"errorCount": 1,
"warningCount": 0,
"filesChecked": 3,
"filesSkipped": 1,
"files": [
{
"filePath": "js/script.js",
"messages": [
{
"ruleId": "local/no-cybozu-data",
"severity": 2,
"message": "Accessing `cybozu.data` is not allowed.",
"line": 42,
"column": 7
}
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

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

The docs’ sample output uses the rule id local/no-cybozu-data, but the implementation enables @kintone/eslint-plugin/no-cybozu-data / @kintone/eslint-plugin/no-kintone-internal-selector, so the reported ruleId will include the plugin name (e.g. @kintone/eslint-plugin/no-cybozu-data). Please update the sample outputs to match the actual ruleId values (both in the Plain and JSON examples).

Copilot uses AI. Check for mistakes.
Comment on lines +66 to +90
### JSON Format

```json
{
"errorCount": 1,
"warningCount": 0,
"filesChecked": 3,
"filesSkipped": 1,
"files": [
{
"filePath": "js/script.js",
"messages": [
{
"ruleId": "local/no-cybozu-data",
"severity": 2,
"message": "Accessing `cybozu.data` is not allowed.",
"line": 42,
"column": 7
}
],
"errorCount": 1,
"warningCount": 0
}
]
}
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

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

The JSON output example documents messages entries as a reduced shape (ruleId/severity/message/line/column), but the code currently serializes ESLint’s full message objects. Either normalize the JSON output to the documented shape, or adjust the docs to clarify that the output is raw ESLint messages (and may include additional fields).

Copilot uses AI. Check for mistakes.
| Option | Required | Description |
| --------------- | -------- | --------------------------------------------------------------------- |
| `--input`, `-i` | Yes | The input plugin zip file or manifest.json file path. |
| `--format` | | Output format.<br/>Format: `plain` or `json`.<br/>Default to `plain`. |
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

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

Minor grammar: “Default to plain.” should be “Defaults to plain.” (or “Default: plain.”) to match the style used elsewhere in the docs.

Suggested change
| `--format` | | Output format.<br/>Format: `plain` or `json`.<br/>Default to `plain`. |
| `--format` | | Output format.<br/>Format: `plain` or `json`.<br/>Defaults to `plain`.|

Copilot uses AI. Check for mistakes.
Comment on lines +56 to +84
### Plainフォーマット(デフォルト)

```
js/script.js
42:7 error Accessing `cybozu.data` is not allowed. (local/no-cybozu-data)

Files checked: 3
Problems: 1 (Errors: 1, Warnings: 0)
```

### JSONフォーマット

```json
{
"errorCount": 1,
"warningCount": 0,
"filesChecked": 3,
"filesSkipped": 1,
"files": [
{
"filePath": "js/script.js",
"messages": [
{
"ruleId": "local/no-cybozu-data",
"severity": 2,
"message": "Accessing `cybozu.data` is not allowed.",
"line": 42,
"column": 7
}
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

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

サンプル出力のルールIDが local/no-cybozu-data になっていますが、実装では @kintone/eslint-plugin/no-cybozu-data / @kintone/eslint-plugin/no-kintone-internal-selector を有効化しているため、出力される ruleId もそれに合わせた値になります。Plain/JSON両方の例を実際の ruleId に合わせて更新してください。

Copilot uses AI. Check for mistakes.
Comment on lines +6 to +13
import { check } from "../../plugin/check";
import type { OutputFormat } from "../../plugin/info";

const command = "check";

const describe = "Check plugin";

const outputFormats: OutputFormat[] = ["plain", "json"];
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

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

OutputFormat is imported from ../../plugin/info, but this command calls ../../plugin/check and that module already defines its own OutputFormat. Importing the type from plugin/info introduces unnecessary coupling and could become incorrect if the formats diverge; please import OutputFormat from ../../plugin/check (or define a local union here).

Copilot uses AI. Check for mistakes.
Comment on lines +66 to +90
### JSONフォーマット

```json
{
"errorCount": 1,
"warningCount": 0,
"filesChecked": 3,
"filesSkipped": 1,
"files": [
{
"filePath": "js/script.js",
"messages": [
{
"ruleId": "local/no-cybozu-data",
"severity": 2,
"message": "Accessing `cybozu.data` is not allowed.",
"line": 42,
"column": 7
}
],
"errorCount": 1,
"warningCount": 0
}
]
}
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

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

JSONフォーマット例では messages を最小限のフィールドだけに絞った形で記載していますが、現状の実装は ESLint の message オブジェクトをそのまま JSON 化するため、追加フィールド(endLine 等)が含まれます。出力を例の形に正規化するか、ドキュメント側で「ESLintの生メッセージを出力する」旨を明記してください。

Copilot uses AI. Check for mistakes.
Comment on lines 103 to 113
"dependencies": {
"@inquirer/prompts": "^7.10.1",
"@kintone/eslint-plugin": "^0.1.1",
"@kintone/plugin-manifest-validator": "^11.1.0",
"@kintone/rest-api-client": "^6.1.2",
"chalk": "4.1.2",
"chokidar": "^4.0.3",
"csv-parse": "^5.6.0",
"csv-stringify": "6.5.2",
"eslint": "^9.38.0",
"https-proxy-agent": "^7.0.6",
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

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

eslint is now listed in both devDependencies and dependencies (with different ranges). This can lead to duplicate installs and version skew between development and runtime. Since plugin check needs ESLint at runtime, consider keeping a single entry (typically in dependencies) and aligning/removing the devDependency version.

Copilot uses AI. Check for mistakes.
Comment on lines +147 to +149
if (m.message.match(/Definition for rule '.+' was not found./)) {
continue;
}
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

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

normalizeResults silently drops ESLint messages that indicate a rule is missing (“Definition for rule ... was not found.”). This can hide misconfiguration (e.g., the recommended config enabling rules whose plugins aren’t installed) and give users a false sense that checks are complete. Consider surfacing these as errors/warnings in the output (or fail fast) so users know the check setup is incomplete.

Suggested change
if (m.message.match(/Definition for rule '.+' was not found./)) {
continue;
}

Copilot uses AI. Check for mistakes.
Comment on lines +62 to +134
export const check = async (inputFilePath: string, format: OutputFormat) => {
const { ESLint, eslintConfig } = await loadESLintDependencies();

let manifest: ManifestInterface;
let driver: DriverInterface;

// TODO: Better file type detection
switch (path.extname(inputFilePath)) {
case ".json": {
manifest = await ManifestFactory.loadJsonFile(inputFilePath);
driver = new LocalFSDriver(path.dirname(inputFilePath));
break;
}
case ".zip": {
const buffer = await fs.readFile(inputFilePath);
const pluginZip = await PluginZip.fromBuffer(buffer);
manifest = await pluginZip.manifest();
driver = await pluginZip.contentsZip();
break;
}
default: {
throw new Error(`Unsupported file format: ${inputFilePath}`);
}
}

const eslint = new ESLint({
overrideConfigFile: true,
overrideConfig: eslintConfig,
});

const allResults: AllResult = {
errorCount: 0,
warningCount: 0,
filesChecked: 0,
filesSkipped: 0,
files: [],
};

for (const source of manifest.sourceList()) {
if (path.extname(source) !== ".js") {
allResults.filesSkipped++;
logger.debug(`Skip ${source}`);
continue;
}

logger.debug(`Checking ${source}`);
allResults.filesChecked++;
const sourceFile = await driver.readFile(source);
const results = normalizeResults(
await eslint.lintText(sourceFile.toString(), {
filePath: source,
}),
);

const fileResult: FileResult = {
filePath: source,
messages: [],
errorCount: 0,
warningCount: 0,
};
for (const result of results) {
fileResult.errorCount += result.errorCount;
fileResult.warningCount += result.warningCount;

fileResult.messages.push(...result.messages);
}
allResults.errorCount += fileResult.errorCount;
allResults.warningCount += fileResult.warningCount;
allResults.files.push(fileResult);
}

console.log(formatResult(allResults, format));
};
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

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

There’s no non-skipped automated coverage for the new plugin check behavior (the E2E scenarios that validate real linting are tagged @skip). Please add a Vitest test that runs the command/module in “npm mode” against a small fixture plugin and asserts the plain/json outputs (and counts) so regressions in rule wiring/output formatting are caught in CI.

Copilot uses AI. Check for mistakes.
@shabaraba shabaraba marked this pull request as draft February 25, 2026 02:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants