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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

## __WORK IN PROGRESS__
* IMPORTANT: The adapter creator requires Node.js 20.x or newer to run!
* (@Apollon77/@copilot) Replace custom_m.html with jsonCustom.json for Admin 5 compatibility (#723) · [Migration guide](docs/updates/20251014_jsoncustom_admin5_support.md)
* (@Apollon77/@copilot) Add `adminUI.config: "none"` for adapters without configuration UI to satisfy adapter-checker W164 (#1071) · [Migration guide](docs/updates/20251014_admin_ui_config_none.md)
* (@Apollon77/@copilot) Updated README template installation instructions to use GitHub Custom Install instead of direct npm commands
* (@Apollon77/@copilot) Switch to npm trusted publishing for automatic releases (#1202) · [Migration guide](docs/updates/20251013_trusted_deploy.md)
Expand Down
65 changes: 65 additions & 0 deletions docs/updates/20251014_jsoncustom_admin5_support.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Add jsonCustom.json for Admin 5 support

Admin 5 will no longer support `custom_m.html` files for custom state settings. Instead, a new JSON-based configuration format `jsonCustom.json` is required. This update ensures that adapters generated with create-adapter support both Admin 4 (with `custom_m.html`) and Admin 5 (with `jsonCustom.json`).

## What changed

When you select the "Custom options for states" feature during adapter creation, the tool now generates both:
- `admin/custom_m.html` - for backward compatibility with Admin 4
- `admin/jsonCustom.json` - new format required for Admin 5

The `jsonCustom.json` file follows the same JSON Config schema format used by `jsonConfig.json`, making it consistent with modern ioBroker admin interfaces.

## Example jsonCustom.json structure

```json
{
"i18n": true,
"type": "panel",
"items": {
"enabled": {
"type": "checkbox",
"label": "enabled",
"newLine": true
},
"interval": {
"type": "text",
"label": "period of time",
"newLine": true
},
"state": {
"type": "text",
"label": "new state",
"newLine": true
},
"setAck": {
"type": "checkbox",
"label": "ack",
"newLine": true
}
}
}
```

## For existing adapters

If you have an existing adapter that uses `custom_m.html`, you can create a `jsonCustom.json` file in your `admin` directory following the structure above. The VSCode schema validation is already configured to support this file format.

### Migration steps:

1. Create `admin/jsonCustom.json` with the structure shown above
2. Customize the `items` object to match your adapter's custom state settings
3. Keep your existing `custom_m.html` for backward compatibility with Admin 4
4. Set `"supportCustoms": true` in your `io-package.json` (should already be set if you have custom_m.html)

## Benefits

- **Admin 5 compatibility**: Your adapter will work with the latest Admin version
- **Backward compatibility**: Admin 4 users can still use your adapter
- **Better tooling**: JSON Config format provides IntelliSense and validation in VSCode
- **Consistency**: Uses the same configuration format as the main adapter settings

## References

- [JSON Config Schema Documentation](https://github.com/ioBroker/ioBroker.admin/tree/master/packages/jsonConfig/schemas)
- [Example from ioBroker.sql](https://github.com/ioBroker/ioBroker.sql/blob/master/admin/jsonCustom.json)
4 changes: 3 additions & 1 deletion src/lib/core/questions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,9 @@ export const questionGroups: QuestionGroup[] = [
(await ctx.fileExists("admin/tab.html")) || (await ctx.fileExists("admin/tab_m.html"))
? "tab"
: null,
(await ctx.fileExists("admin/custom.html")) || (await ctx.fileExists("admin/custom_m.html"))
(await ctx.fileExists("admin/custom.html")) ||
(await ctx.fileExists("admin/custom_m.html")) ||
(await ctx.fileExists("admin/jsonCustom.json"))
? "custom"
: null,
].filter(f => !!f) as string[],
Expand Down
58 changes: 5 additions & 53 deletions templates/admin/custom_m.html.ts
Original file line number Diff line number Diff line change
@@ -1,56 +1,8 @@
import type { TemplateFunction } from "../../src/lib/createAdapter";

export = (answers => {
const supportCustom = answers.adminFeatures && answers.adminFeatures.indexOf("custom") > -1;
if (!supportCustom) {
return;
}

const template = `
<script type="text/x-iobroker" data-template-name="${answers.adapterName}">
<div class="row">
<div class="col s2">
<input type="checkbox" data-field="enabled" data-default="false"/>
<!-- this field is mandatory, just to find out if to include this settings or not</span-->
<span class="translate">enabled</span>
</div>
<div class="col s4">
<input type="text" data-field="interval" size="30">
<span class="translate">period of time</span>
</div>
<div class="col s4">
<input type="text" data-field="state" size="30">
<span class="translate">new state</span>
</div>
<div class="col s2">
<input type="checkbox" data-field="setAck" data-default="false">
<span class="translate">ack</span>
</div>
</div>
</script>

<script type="text/javascript">
$.get("adapter/${answers.adapterName}/words.js", function(script) {
let translation = script.substring(script.indexOf('{'), script.length);
translation = translation.substring(0, translation.lastIndexOf(';'));
$.extend(systemDictionary, JSON.parse(translation));
});

// There are two ways how to predefine default settings:
// - with attribute "data-default" (content independent)
// - with function in global variable "defaults". Function name is equal with adapter name.
// as input function receives object with all information concerning it
if (typeof defaults !== 'undefined') {
defaults["${answers.adapterName}"] = function (obj, instanceObj) {
return {
enabled: false,
interval: '5m',
state: false,
setAck: false
};
}
}
</script>
`;
return template.trim();
// NOTE: Admin 5 no longer supports custom_m.html. Use jsonCustom.json instead.
// This file is kept for reference but will not generate any output.
export = (() => {
// Return undefined to prevent file generation
return;
}) as TemplateFunction;
36 changes: 36 additions & 0 deletions templates/admin/jsonCustom.json.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import type { TemplateFunction } from "../../src/lib/createAdapter";

export = (answers => {
const supportCustom = answers.adminFeatures && answers.adminFeatures.indexOf("custom") > -1;
if (!supportCustom) {
return;
}

const config = {
i18n: true,
type: "panel",
items: {
enabled: {
type: "checkbox",
label: "enabled",
newLine: true,
},
interval: {
type: "text",
label: "period of time",
newLine: true,
},
state: {
type: "text",
label: "new state",
newLine: true,
},
setAck: {
type: "checkbox",
label: "ack",
newLine: true,
},
},
};
return JSON.stringify(config, null, 4);
}) as TemplateFunction;
48 changes: 38 additions & 10 deletions templates/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,50 @@

import type { TemplateFunction } from "../src/lib/createAdapter";

const templates: { name: string, templateFunction: TemplateFunction }[] = [
const templates: { name: string; templateFunction: TemplateFunction }[] = [
{ name: "_create-adapter.json.ts", templateFunction: require("./_create-adapter.json") },
{ name: "_devcontainer/devcontainer.json.ts", templateFunction: require("./_devcontainer/devcontainer.json") },
{ name: "_devcontainer/docker-compose.yml.ts", templateFunction: require("./_devcontainer/docker-compose.yml") },
{ name: "_devcontainer/iobroker/_Dockerfile.ts", templateFunction: require("./_devcontainer/iobroker/_Dockerfile") },
{
name: "_devcontainer/iobroker/_Dockerfile.ts",
templateFunction: require("./_devcontainer/iobroker/_Dockerfile"),
},
{ name: "_devcontainer/iobroker/boot.sh.ts", templateFunction: require("./_devcontainer/iobroker/boot.sh") },
{ name: "_devcontainer/iobroker/node-wrapper.sh.ts", templateFunction: require("./_devcontainer/iobroker/node-wrapper.sh") },
{
name: "_devcontainer/iobroker/node-wrapper.sh.ts",
templateFunction: require("./_devcontainer/iobroker/node-wrapper.sh"),
},
{ name: "_devcontainer/nginx/nginx.conf.ts", templateFunction: require("./_devcontainer/nginx/nginx.conf") },
{ name: "_devcontainer/parcel/_Dockerfile.ts", templateFunction: require("./_devcontainer/parcel/_Dockerfile") },
{ name: "_devcontainer/parcel/run.sh.ts", templateFunction: require("./_devcontainer/parcel/run.sh") },
{ name: "_devcontainer/README.md.ts", templateFunction: require("./_devcontainer/README.md") },
{ name: "_devcontainer/scripts/postcreate.sh.ts", templateFunction: require("./_devcontainer/scripts/postcreate.sh") },
{ name: "_devcontainer/scripts/poststart.sh.ts", templateFunction: require("./_devcontainer/scripts/poststart.sh") },
{ name: "_devcontainer/scripts/wait_for_iobroker.sh.ts", templateFunction: require("./_devcontainer/scripts/wait_for_iobroker.sh") },
{
name: "_devcontainer/scripts/postcreate.sh.ts",
templateFunction: require("./_devcontainer/scripts/postcreate.sh"),
},
{
name: "_devcontainer/scripts/poststart.sh.ts",
templateFunction: require("./_devcontainer/scripts/poststart.sh"),
},
{
name: "_devcontainer/scripts/wait_for_iobroker.sh.ts",
templateFunction: require("./_devcontainer/scripts/wait_for_iobroker.sh"),
},
{ name: "_github/auto-merge.yml.ts", templateFunction: require("./_github/auto-merge.yml") },
{ name: "_github/dependabot.yml.ts", templateFunction: require("./_github/dependabot.yml") },
{ name: "_github/ISSUE_TEMPLATE/bug_report.md.ts", templateFunction: require("./_github/ISSUE_TEMPLATE/bug_report.md") },
{
name: "_github/ISSUE_TEMPLATE/bug_report.md.ts",
templateFunction: require("./_github/ISSUE_TEMPLATE/bug_report.md"),
},
{ name: "_github/ISSUE_TEMPLATE/config.yml.ts", templateFunction: require("./_github/ISSUE_TEMPLATE/config.yml") },
{ name: "_github/workflows/dependabot-auto-merge.yml.ts", templateFunction: require("./_github/workflows/dependabot-auto-merge.yml") },
{ name: "_github/workflows/test-and-release.yml.ts", templateFunction: require("./_github/workflows/test-and-release.yml") },
{
name: "_github/workflows/dependabot-auto-merge.yml.ts",
templateFunction: require("./_github/workflows/dependabot-auto-merge.yml"),
},
{
name: "_github/workflows/test-and-release.yml.ts",
templateFunction: require("./_github/workflows/test-and-release.yml"),
},
{ name: "_gitignore.ts", templateFunction: require("./_gitignore") },
{ name: "_prettierignore.ts", templateFunction: require("./_prettierignore") },
{ name: "_prettierrc.js.ts", templateFunction: require("./_prettierrc.js") },
Expand All @@ -48,8 +72,12 @@ const templates: { name: string, templateFunction: TemplateFunction }[] = [
{ name: "admin/icon.png.ts", templateFunction: require("./admin/icon.png") },
{ name: "admin/index_m.html.ts", templateFunction: require("./admin/index_m.html") },
{ name: "admin/jsonConfig.json.ts", templateFunction: require("./admin/jsonConfig.json") },
{ name: "admin/jsonCustom.json.ts", templateFunction: require("./admin/jsonCustom.json") },
{ name: "admin/src/app.tsx_jsx.ts", templateFunction: require("./admin/src/app.tsx_jsx") },
{ name: "admin/src/components/settings.tsx_jsx.ts", templateFunction: require("./admin/src/components/settings.tsx_jsx") },
{
name: "admin/src/components/settings.tsx_jsx.ts",
templateFunction: require("./admin/src/components/settings.tsx_jsx"),
},
{ name: "admin/src/i18n/de.json.ts", templateFunction: require("./admin/src/i18n/de.json") },
{ name: "admin/src/i18n/en.json.ts", templateFunction: require("./admin/src/i18n/en.json") },
{ name: "admin/src/i18n/es.json.ts", templateFunction: require("./admin/src/i18n/es.json") },
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
admin/custom_m.html
admin/index_m.html
admin/jsonCustom.json
admin/style.css
admin/tab_m.html
admin/test-adapter.png
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"i18n": true,
"type": "panel",
"items": {
"enabled": {
"type": "checkbox",
"label": "enabled",
"newLine": true
},
"interval": {
"type": "text",
"label": "period of time",
"newLine": true
},
"state": {
"type": "text",
"label": "new state",
"newLine": true
},
"setAck": {
"type": "checkbox",
"label": "ack",
"newLine": true
}
}
}