mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-29 03:09:36 +00:00
fix(hacs): keep exactly one *manifest.json in the tree
Validate / golden (push) Failing after 6m50s
Validate / hacs (push) Failing after 11s
Validate / hassfest (push) Failing after 14s
Validate / frontend (push) Successful in 8m18s
Validate / backend (push) Failing after 10m34s
Validate / smoke (push) Failing after 24m45s
Validate / performance_smoke (push) Failing after 9m42s
Full Performance / performance (push) Failing after 54m31s
Validate / golden (push) Failing after 6m50s
Validate / hacs (push) Failing after 11s
Validate / hassfest (push) Failing after 14s
Validate / frontend (push) Successful in 8m18s
Validate / backend (push) Failing after 10m34s
Validate / smoke (push) Failing after 24m45s
Validate / performance_smoke (push) Failing after 9m42s
Full Performance / performance (push) Failing after 54m31s
The HACS submission check does not read hacs.json to find the integration: it globs `*manifest.json` over the whole clone of the default branch and exits 1 unless there is exactly one (hacs/default, scripts/helpers/integration_path.py). Three files matched — the two stand-only integrations added on 2026-07-31 and the golden baseline index added on 2026-08-11 — so the Hassfest job of PR #9004 went red five weeks into the review queue, with a log that named no file. The stand manifests ship as manifest.template.json and demo/stand/install.sh renames them at install time; the golden index becomes baselines-index.json (the exported constant keeps its name, so no consumer changes). test/repo-hygiene.test.mjs fails if a second manifest ever appears, and the existing golden-policy assertion — which compared against 'manifest.json' and happily passed on 'baseline-manifest.json' — now checks the suffix.
This commit is contained in:
@@ -1,4 +1,7 @@
|
||||
export const GOLDEN_BASELINE_MANIFEST = 'baseline-manifest.json';
|
||||
// The name must NOT end with `manifest.json`: the HACS submission check globs
|
||||
// `*manifest.json` over the whole clone of the default branch and refuses a
|
||||
// repository with more than one match (test/repo-hygiene.test.mjs).
|
||||
export const GOLDEN_BASELINE_MANIFEST = 'baselines-index.json';
|
||||
|
||||
export const assertGoldenInvocation = (mode, scenarioFilter = '') => {
|
||||
if (!['capture', 'verify'].includes(mode)) throw new Error(`unknown golden mode: ${mode}`);
|
||||
|
||||
+19
-6
@@ -4,10 +4,10 @@ Things that live on the public stand (demo.houseplan.tech) but are not part
|
||||
of the shipped integration.
|
||||
|
||||
- `demo_robot/` — the scripted robot vacuum (docs/VACUUM.md, "demo stand gets
|
||||
a scripted synthetic robot"). Deployed by copying the folder to
|
||||
`custom_components/demo_robot` in both stand seeds and adding `demo_robot:`
|
||||
to configuration.yaml; the dev stand picks it up automatically from this
|
||||
path (`/opt/hp/bin/hp-update-dev.sh`). The rest of the stand-only config
|
||||
a scripted synthetic robot"). Deployed with `./install.sh <ha-config-dir>`
|
||||
into both stand seeds, plus `demo_robot:` in configuration.yaml; the dev
|
||||
stand picks it up automatically from this path
|
||||
(`/opt/hp/bin/hp-update-dev.sh`, which calls the same script). The rest of the stand-only config
|
||||
(template LQI sensors, alarm helpers, the smoke automation) lives in the
|
||||
seeds on the stand host — see `docs/TESTING-DEMO.md` and the memory note
|
||||
`houseplan-demo-stand`.
|
||||
@@ -16,8 +16,8 @@ of the shipped integration.
|
||||
administrator (the card editor is gated on `is_admin`) and kept restarting
|
||||
HA from the UI, which looked like the stand crashing between hourly resets.
|
||||
The component re-registers `homeassistant.restart`/`homeassistant.stop` as
|
||||
no-ops after startup. Deployed to `custom_components/demo_guard` in
|
||||
seed-demo only (the dev stand sits behind basic auth) + `demo_guard:` in
|
||||
no-ops after startup. Deployed by the same `install.sh` into seed-demo only
|
||||
(the dev stand sits behind basic auth) + `demo_guard:` in
|
||||
configuration.yaml.
|
||||
|
||||
- `www/stand-reset-timer.js` — console-only countdown to the next hourly
|
||||
@@ -27,3 +27,16 @@ of the shipped integration.
|
||||
switching to `console.warn` for the last 5 minutes. `www/stand-dev-info.js`
|
||||
is the dev-stand counterpart: a single `console.info` saying the dev stand
|
||||
only resets on deploy.
|
||||
|
||||
## Why the manifests are templates
|
||||
|
||||
Both components ship their manifest as `manifest.template.json`, and
|
||||
`install.sh` renames it to `manifest.json` on the stand.
|
||||
|
||||
The reason is the HACS submission check: `hacs/default` validates a repository
|
||||
by globbing `*manifest.json` over the whole clone of the DEFAULT branch and
|
||||
refuses anything that does not have exactly one
|
||||
(`scripts/helpers/integration_path.py` — "No manifest", exit 1). Two stand-only
|
||||
manifests turned the Hassfest job of PR #9004 red on 2026-08-11, five weeks into
|
||||
the review queue. `test/repo-hygiene.test.mjs` now fails if a second one
|
||||
appears, so this cannot be rediscovered by a reviewer again.
|
||||
|
||||
Executable
+23
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env sh
|
||||
# Install the stand-only integrations into a Home Assistant config directory.
|
||||
#
|
||||
# ./install.sh /mnt/data/supervisor/homeassistant
|
||||
#
|
||||
# The manifests are stored as `manifest.template.json` on purpose: the HACS
|
||||
# submission check globs `*manifest.json` over the whole clone of the default
|
||||
# branch and refuses a repository with more than one of them
|
||||
# (hacs/default, scripts/helpers/integration_path.py). See README.md.
|
||||
set -eu
|
||||
|
||||
target="${1:?usage: install.sh <path to the Home Assistant config directory>}"
|
||||
here="$(cd "$(dirname "$0")" && pwd)"
|
||||
|
||||
for comp in demo_robot demo_guard; do
|
||||
src="$here/$comp"
|
||||
[ -d "$src" ] || { echo "missing $src" >&2; exit 1; }
|
||||
dst="$target/custom_components/$comp"
|
||||
mkdir -p "$dst"
|
||||
cp -a "$src/." "$dst/"
|
||||
mv -f "$dst/manifest.template.json" "$dst/manifest.json"
|
||||
echo "installed $comp -> $dst"
|
||||
done
|
||||
@@ -7,8 +7,12 @@ import {
|
||||
} from '../demo/golden/policy.mjs';
|
||||
|
||||
test('golden metadata cannot be mistaken for a Home Assistant integration manifest', () => {
|
||||
assert.equal(GOLDEN_BASELINE_MANIFEST, 'baseline-manifest.json');
|
||||
assert.notEqual(GOLDEN_BASELINE_MANIFEST, 'manifest.json');
|
||||
assert.equal(GOLDEN_BASELINE_MANIFEST, 'baselines-index.json');
|
||||
// The old check compared against 'manifest.json' and passed while the file
|
||||
// was called 'baseline-manifest.json' — which is exactly what the HACS glob
|
||||
// `*manifest.json` matches, and what turned Hassfest red on PR #9004
|
||||
// (2026-08-11). Ending with the word is enough to break the submission.
|
||||
assert.equal(GOLDEN_BASELINE_MANIFEST.endsWith('manifest.json'), false);
|
||||
});
|
||||
|
||||
test('golden capture fails on runtime errors but permits missing baselines', () => {
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { readdirSync, statSync } from 'node:fs';
|
||||
import { join, relative, sep } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import test from 'node:test';
|
||||
|
||||
const ROOT = fileURLToPath(new URL('..', import.meta.url));
|
||||
const SKIP = new Set(['.git', 'node_modules', 'dist', 'coverage', '.venv', '__pycache__', '.pytest_cache']);
|
||||
|
||||
const walk = (dir, out = []) => {
|
||||
for (const name of readdirSync(dir)) {
|
||||
if (SKIP.has(name)) continue;
|
||||
const full = join(dir, name);
|
||||
if (statSync(full).isDirectory()) walk(full, out);
|
||||
else out.push(relative(ROOT, full).split(sep).join('/'));
|
||||
}
|
||||
return out;
|
||||
};
|
||||
|
||||
// The HACS submission check does not read hacs.json to find the integration: it
|
||||
// globs `*manifest.json` over the whole clone of the DEFAULT branch and exits 1
|
||||
// unless there is exactly one (hacs/default, scripts/helpers/integration_path.py
|
||||
// -> "No manifest"). Two stand-only manifests under demo/stand turned the
|
||||
// Hassfest job of PR #9004 red on 2026-08-11, five weeks into the review queue,
|
||||
// and the failure said nothing about which file was to blame. Anything that
|
||||
// needs a second manifest ships it as `manifest.template.json` and renames it at
|
||||
// install time — see demo/stand/install.sh.
|
||||
test('the tree carries exactly one *manifest.json: HACS rejects a repository with two', () => {
|
||||
const found = walk(ROOT).filter((path) => path.split('/').pop().endsWith('manifest.json'));
|
||||
assert.deepEqual(found, ['custom_components/houseplan/manifest.json']);
|
||||
});
|
||||
Reference in New Issue
Block a user