Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions .github/create_templates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ const adapterAnswers: Answers = {
adminFeatures: ["custom", "tab"],
adminUi: "json",
tabReact: "no",
eslintConfig: "official", // Use official ESLint config as default and main rules
};

const templates: Record<string, Answers> = {
Expand Down Expand Up @@ -168,7 +169,9 @@ const templates: Record<string, Answers> = {
try {
execSync(`npm run lint`, cmdOpts);
} catch (e) {
console.error(red(`ESLint failed for template ${tplName}:`));
console.error(
red(`ESLint failed for template ${tplName}:`),
);
console.error(e.message || e);
hadError = true;
}
Expand All @@ -178,7 +181,9 @@ const templates: Record<string, Answers> = {
try {
execSync(`npm run check`, cmdOpts);
} catch (e) {
console.error(red(`Type check failed for template ${tplName}:`));
console.error(
red(`Type check failed for template ${tplName}:`),
);
console.error(e.message || e);
hadError = true;
}
Expand Down
Empty file modified bin/create-adapter.js
100644 → 100755
Empty file.
22 changes: 22 additions & 0 deletions src/lib/core/questions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -704,6 +704,27 @@ export const questionGroups: QuestionGroup[] = [
: null,
].filter((f) => !!f) as string[],
}),
{
condition: { name: "tools", contains: "ESLint" },
type: "select",
name: "eslintConfig",
label: "ESLint Configuration",
message:
"Do you want to configure ESLint by yourself or use the official ioBroker ESLint config?",
initial: "official",
choices: [
{
message: "Use official ioBroker ESLint config (includes prettier)",
hint: "(recommended)",
value: "official",
},
{
message: "Configure ESLint by yourself",
value: "custom",
},
],
migrate: () => "custom", // Default to custom for existing projects
},
{
condition: [
{ name: "features", contains: "adapter" },
Expand Down Expand Up @@ -1004,6 +1025,7 @@ export interface Answers {
| "code coverage"
| "devcontainer"
)[];
eslintConfig?: "official" | "custom";
nodeVersion?: "20" | "22" | "24";
title?: string;
license?: string;
Expand Down
2 changes: 2 additions & 0 deletions templates/_eslintignore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ const templateFunction: TemplateFunction = answers => {

const useESLint = answers.tools?.includes("ESLint")
if (!useESLint) return;
// Don't generate .eslintignore anymore - ignores are in eslint.config.mjs
return;

const useTypeScript = answers.language === "TypeScript";
const useReact = answers.adminUi === "react" || answers.tabReact === "yes";
Expand Down
3 changes: 3 additions & 0 deletions templates/_eslintrc_javascript.json.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ const templateFunction: TemplateFunction = answers => {

const useESLint = answers.tools && answers.tools.indexOf("ESLint") > -1;
if (!useESLint) return;
// Don't generate legacy .eslintrc.json anymore - use flat config instead
return;

// Original custom ESLint configuration
const template = `
{
"root": true,
Expand Down
4 changes: 4 additions & 0 deletions templates/_eslintrc_typescript.js.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,14 @@ const templateFunction: TemplateFunction = answers => {

const useESLint = answers.tools && answers.tools.indexOf("ESLint") > -1;
if (!useESLint) return;
// Don't generate legacy .eslintrc.js anymore - use flat config instead
return;

const usePrettier = answers.tools && answers.tools.indexOf("Prettier") > -1;
const useReact =
answers.adminUi === "react" || answers.tabReact === "yes";

// Original custom ESLint configuration
const template = `
module.exports = {
root: true, // Don't look outside this project for inherited configs
Expand Down
3 changes: 3 additions & 0 deletions templates/_prettierrc.js.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ const templateFunction: TemplateFunction = answers => {

const usePrettier = answers.tools && answers.tools.indexOf("Prettier") > -1;
if (!usePrettier) return;
const useOfficialESLintConfig = answers.eslintConfig === "official";
// Don't generate .prettierrc.js when using official config (prettier config is in prettier.config.mjs)
if (useOfficialESLintConfig) return;

// endOfLine could be made dependent on the current OS's line ending
// I need to think about it.
Expand Down
9 changes: 7 additions & 2 deletions templates/_vscode/extensions.json.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,19 @@ const templateFunction: TemplateFunction = answers => {

const useESLint = answers.tools && answers.tools.indexOf("ESLint") > -1;
const usePrettier = answers.tools && answers.tools.indexOf("Prettier") > -1;
const useOfficialESLintConfig = useESLint && answers.eslintConfig === "official";
const useTypeScript = answers.language === "TypeScript";

// Include prettier-vscode for TypeScript when using official ESLint config
const includePrettierExtension = usePrettier || (useOfficialESLintConfig && useTypeScript);

if (!useESLint && !usePrettier) return;
if (!useESLint && !includePrettierExtension) return;

const template = `
{
"recommendations": [
${useESLint ? `"dbaeumer.vscode-eslint",` : ""}
${usePrettier ? `"esbenp.prettier-vscode",` : ""}
${includePrettierExtension ? `"esbenp.prettier-vscode",` : ""}
]
}
`;
Expand Down
152 changes: 152 additions & 0 deletions templates/eslint.config.custom.js.mjs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import type { TemplateFunction } from "../src/lib/createAdapter";

const templateFunction: TemplateFunction = answers => {

// This version is intended for use in JS projects
if (answers.language !== "JavaScript") return;

const useESLint = answers.tools && answers.tools.indexOf("ESLint") > -1;
if (!useESLint) return;
const useOfficialESLintConfig = answers.eslintConfig === "official";
// Only generate ESLint 9 config for custom config
if (useOfficialESLintConfig) return;

const usePrettier = answers.tools && answers.tools.indexOf("Prettier") > -1;
const useReact = answers.adminUi === "react" || answers.tabReact === "yes";
const isWidget = answers.features && answers.features.indexOf("vis") > -1;

// Build base config
let config = `import js from '@eslint/js';`;

if (useReact) {
config += `
import react from 'eslint-plugin-react';`;
}

config += `

export default [
js.configs.recommended,
{
files: ['**/*.js', '**/*.jsx'],
languageOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
globals: {
'process': 'readonly',
'Buffer': 'readonly',
'__dirname': 'readonly',
'__filename': 'readonly',
'module': 'readonly',
'require': 'readonly',
'exports': 'readonly',
'global': 'readonly',
'console': 'readonly',
'setTimeout': 'readonly',
'setInterval': 'readonly',
'clearTimeout': 'readonly',
'clearInterval': 'readonly',
'document': 'readonly',
'window': 'readonly',
'describe': 'readonly',
'it': 'readonly',
'ioBroker': 'readonly',
},${useReact ? `
parserOptions: {
ecmaFeatures: {
jsx: true,
},
},` : ""}
},
plugins: {${useReact ? `
react,` : ""}
},
rules: {
...js.configs.recommended.rules,${useReact ? `
...react.configs.recommended.rules,
'react/react-in-jsx-scope': 'off',` : ""}${!usePrettier ? `
'indent': [
'error',
${answers.indentation === "Space (4)" ? "4" : "'tab'"},
{
'SwitchCase': 1
}
],
'quotes': [
'error',
${answers.quotes === "single" ? `'single'` : `'double'`}
],` : ""}
// Strict rules to match official @iobroker/eslint-config
'prefer-template': 'error',
'no-unused-vars': [
'warn',
{
'ignoreRestSiblings': true,
'argsIgnorePattern': '^_',
'varsIgnorePattern': '^_'
}
],
},${useReact ? `
settings: {
react: {
version: 'detect',
},
},` : ""}
},`;

// Add widgets configuration if VIS is used
if (isWidget) {
config += `
{
files: ['widgets/**/*.js'],
languageOptions: {
ecmaVersion: 5,
sourceType: 'script',
globals: {
'console': 'readonly',
'setTimeout': 'readonly',
},
},
rules: {
// Visualizations may run in very old browsers
'no-var': 'off',
'prefer-const': 'off',
'prefer-arrow-callback': 'off',
// The example code does not use some parameters
'no-unused-vars': [
'warn',
{
'ignoreRestSiblings': true,
'argsIgnorePattern': '^_',
'varsIgnorePattern': '^_'
}
]
}
},`;
}

config += `
{
ignores: [
'.dev-server/',
'.vscode/',
'*.test.js',
'test/**/*.js',
'*.config.mjs',
'build/',
'dist/',
'admin/build/',
'admin/words.js',
'admin/admin.d.ts',
'admin/blockly.js',
'**/adapter-config.d.ts',
],
},
];
`;

return config.trim();
};

templateFunction.customPath = "eslint.config.mjs";
export = templateFunction;
Loading