* feat(#1928): remove sunset gemini cli runtime, redirect to antigravity Google sunset Gemini CLI on 2026-06-18; Antigravity CLI is its official successor (already a first-class GSD runtime). Remove the gemini runtime from the enum (16->15), aliases, labels, config-home fragment, install path, converters (convertClaudeToGemini{Markdown,Toml,Agent}, convertSlashCommandsToGeminiMentions), capability descriptor, gemini-extension.json, RULESET.GEMINI.*, and the interactive menu (renumbered, no gap). --gemini now prints an explicit deprecation notice citing the 2026-06-18 sunset and redirects to --antigravity (no silent alias, per the issue's Hyrum's-Law rejection). Antigravity is preserved throughout: its GEMINI.md contextFileName, .gemini/antigravity config home, the shared convertGeminiToolName/claudeToGeminiTools tool vocabulary, and the 'gemini' hookEvents dialect it declares. GEMINI.md retargeted as Antigravity's context file. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(#1928): backfill changeset PR number (#1996) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(#1928): drop Gemini CLI from issue templates (review nit) Removes the sunset Gemini CLI runtime from the two GitHub issue-template runtime lists that the removal PR missed, per @davesienkowski's review nit: - feature_request.yml: 'Applicable runtimes' checkbox (a user could otherwise request a feature for a runtime GSD no longer supports) - bug_report.yml: 'Runtime' dropdown + the stale ~/.gemini/settings.json retrieval-help line Leaves the post-removal templates fully consistent with the Antigravity redirect. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
544 lines
20 KiB
JavaScript
544 lines
20 KiB
JavaScript
'use strict';
|
|
|
|
// Tests for runtime-config-adapter-registry.cjs (issue #60).
|
|
// TDD: this file is written BEFORE the implementation to establish the red state.
|
|
|
|
const { describe, test } = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const path = require('node:path');
|
|
|
|
const ROOT = path.join(__dirname, '..');
|
|
const {
|
|
resolveRuntimeConfigIntent,
|
|
resolveInstallPlan,
|
|
resolveInstallPlanFromRuntimes,
|
|
ALLOWED_CONFIG_RUNTIMES,
|
|
INSTALL_SURFACES,
|
|
} = require(path.join(ROOT, 'gsd-core', 'bin', 'lib', 'runtime-config-adapter-registry.cjs'));
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Source-of-truth table (mirrors the intent table in the brief exactly)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
const EXPECTED_TABLE = [
|
|
{ runtime: 'claude', installSurface: 'settings-json', writesSharedSettings: true, finishPermissionWriter: null },
|
|
{ runtime: 'antigravity', installSurface: 'settings-json', writesSharedSettings: true, finishPermissionWriter: null },
|
|
{ runtime: 'augment', installSurface: 'settings-json', writesSharedSettings: true, finishPermissionWriter: null },
|
|
{ runtime: 'qwen', installSurface: 'settings-json', writesSharedSettings: true, finishPermissionWriter: null },
|
|
{ runtime: 'hermes', installSurface: 'settings-json', writesSharedSettings: true, finishPermissionWriter: null },
|
|
{ runtime: 'codebuddy', installSurface: 'settings-json', writesSharedSettings: true, finishPermissionWriter: null },
|
|
{ runtime: 'opencode', installSurface: 'settings-json', writesSharedSettings: true, finishPermissionWriter: 'opencode' },
|
|
{ runtime: 'kilo', installSurface: 'settings-json', writesSharedSettings: false, finishPermissionWriter: 'kilo' },
|
|
{ runtime: 'codex', installSurface: 'codex-toml', writesSharedSettings: false, finishPermissionWriter: null },
|
|
{ runtime: 'copilot', installSurface: 'copilot-instructions', writesSharedSettings: false, finishPermissionWriter: null },
|
|
{ runtime: 'cline', installSurface: 'cline-rules', writesSharedSettings: false, finishPermissionWriter: null },
|
|
{ runtime: 'cursor', installSurface: 'cursor-hooks-json', writesSharedSettings: false, finishPermissionWriter: null },
|
|
{ runtime: 'windsurf', installSurface: 'profile-marker-only', writesSharedSettings: false, finishPermissionWriter: null },
|
|
{ runtime: 'trae', installSurface: 'profile-marker-only', writesSharedSettings: false, finishPermissionWriter: null },
|
|
{ runtime: 'kimi', installSurface: 'profile-marker-only', writesSharedSettings: false, finishPermissionWriter: null },
|
|
];
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Test 1: Table-lock — every row in EXPECTED_TABLE must match exactly
|
|
// ---------------------------------------------------------------------------
|
|
|
|
describe('resolveRuntimeConfigIntent — table-lock', () => {
|
|
for (const row of EXPECTED_TABLE) {
|
|
test(`${row.runtime} resolves to expected intent`, () => {
|
|
const intent = resolveRuntimeConfigIntent(row.runtime);
|
|
assert.deepStrictEqual(intent, {
|
|
runtime: row.runtime,
|
|
installSurface: row.installSurface,
|
|
writesSharedSettings: row.writesSharedSettings,
|
|
finishPermissionWriter: row.finishPermissionWriter,
|
|
});
|
|
});
|
|
}
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Test 2: Unknown runtime fails loudly (AC#2)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
describe('resolveRuntimeConfigIntent — unknown runtime throws TypeError', () => {
|
|
test('throws TypeError for unknown string "grok"', () => {
|
|
assert.throws(() => resolveRuntimeConfigIntent('grok'), TypeError);
|
|
});
|
|
|
|
test('throws TypeError for unknown string "xyzunknown"', () => {
|
|
assert.throws(() => resolveRuntimeConfigIntent('xyzunknown'), TypeError);
|
|
});
|
|
|
|
test('throws TypeError for empty string ""', () => {
|
|
assert.throws(() => resolveRuntimeConfigIntent(''), TypeError);
|
|
});
|
|
|
|
test('throws TypeError for undefined', () => {
|
|
assert.throws(() => resolveRuntimeConfigIntent(undefined), TypeError);
|
|
});
|
|
|
|
test('throws TypeError for "__proto__" (prototype-chain key)', () => {
|
|
assert.throws(() => resolveRuntimeConfigIntent('__proto__'), TypeError);
|
|
});
|
|
|
|
test('throws TypeError for "constructor" (prototype-chain key)', () => {
|
|
assert.throws(() => resolveRuntimeConfigIntent('constructor'), TypeError);
|
|
});
|
|
|
|
test('throws TypeError for "hasOwnProperty" (prototype-chain key)', () => {
|
|
assert.throws(() => resolveRuntimeConfigIntent('hasOwnProperty'), TypeError);
|
|
});
|
|
|
|
test('throws TypeError for "toString" (prototype-chain key)', () => {
|
|
assert.throws(() => resolveRuntimeConfigIntent('toString'), TypeError);
|
|
});
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Test 3: writesSharedSettings exclusion equivalence
|
|
// ---------------------------------------------------------------------------
|
|
|
|
describe('writesSharedSettings exclusion equivalence', () => {
|
|
const EXPECTED_FALSE_SET = new Set(['codex', 'copilot', 'kilo', 'cursor', 'windsurf', 'trae', 'cline', 'kimi']);
|
|
|
|
test('runtimes with writesSharedSettings===false are exactly the exclusion set', () => {
|
|
const falseRuntimes = EXPECTED_TABLE
|
|
.filter(r => r.writesSharedSettings === false)
|
|
.map(r => r.runtime);
|
|
assert.deepStrictEqual(new Set(falseRuntimes), EXPECTED_FALSE_SET);
|
|
});
|
|
|
|
test('all other supported runtimes have writesSharedSettings===true', () => {
|
|
const trueRuntimes = EXPECTED_TABLE
|
|
.filter(r => r.writesSharedSettings === true)
|
|
.map(r => r.runtime);
|
|
for (const runtime of trueRuntimes) {
|
|
assert.ok(!EXPECTED_FALSE_SET.has(runtime), `${runtime} should have writesSharedSettings true`);
|
|
}
|
|
});
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Test 4: finishPermissionWriter correctness
|
|
// ---------------------------------------------------------------------------
|
|
|
|
describe('finishPermissionWriter', () => {
|
|
test('opencode -> "opencode"', () => {
|
|
assert.strictEqual(resolveRuntimeConfigIntent('opencode').finishPermissionWriter, 'opencode');
|
|
});
|
|
|
|
test('kilo -> "kilo"', () => {
|
|
assert.strictEqual(resolveRuntimeConfigIntent('kilo').finishPermissionWriter, 'kilo');
|
|
});
|
|
|
|
test('every other supported runtime -> null', () => {
|
|
const nullExpected = EXPECTED_TABLE
|
|
.filter(r => r.finishPermissionWriter === null)
|
|
.map(r => r.runtime);
|
|
for (const runtime of nullExpected) {
|
|
assert.strictEqual(
|
|
resolveRuntimeConfigIntent(runtime).finishPermissionWriter,
|
|
null,
|
|
`${runtime} should have finishPermissionWriter null`,
|
|
);
|
|
}
|
|
});
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Test 5: Distinct dedicated surfaces
|
|
// ---------------------------------------------------------------------------
|
|
|
|
describe('installSurface correctness', () => {
|
|
test('codex -> "codex-toml"', () => {
|
|
assert.strictEqual(resolveRuntimeConfigIntent('codex').installSurface, 'codex-toml');
|
|
});
|
|
|
|
test('copilot -> "copilot-instructions"', () => {
|
|
assert.strictEqual(resolveRuntimeConfigIntent('copilot').installSurface, 'copilot-instructions');
|
|
});
|
|
|
|
test('cline -> "cline-rules"', () => {
|
|
assert.strictEqual(resolveRuntimeConfigIntent('cline').installSurface, 'cline-rules');
|
|
});
|
|
|
|
test('cursor -> "cursor-hooks-json"', () => {
|
|
assert.strictEqual(resolveRuntimeConfigIntent('cursor').installSurface, 'cursor-hooks-json');
|
|
});
|
|
|
|
test('windsurf -> "profile-marker-only"', () => {
|
|
assert.strictEqual(resolveRuntimeConfigIntent('windsurf').installSurface, 'profile-marker-only');
|
|
});
|
|
|
|
test('trae -> "profile-marker-only"', () => {
|
|
assert.strictEqual(resolveRuntimeConfigIntent('trae').installSurface, 'profile-marker-only');
|
|
});
|
|
|
|
test('kimi -> "profile-marker-only"', () => {
|
|
assert.strictEqual(resolveRuntimeConfigIntent('kimi').installSurface, 'profile-marker-only');
|
|
});
|
|
|
|
test('the 6 passthroughs + opencode + kilo -> "settings-json"', () => {
|
|
const settingsJsonRuntimes = ['claude', 'antigravity', 'augment', 'qwen', 'hermes', 'codebuddy', 'opencode', 'kilo'];
|
|
for (const runtime of settingsJsonRuntimes) {
|
|
assert.strictEqual(
|
|
resolveRuntimeConfigIntent(runtime).installSurface,
|
|
'settings-json',
|
|
`${runtime} should have installSurface "settings-json"`,
|
|
);
|
|
}
|
|
});
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Test 6: Returned intent is a fresh object (no shared reference mutation)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
describe('resolveRuntimeConfigIntent — fresh object each call', () => {
|
|
test('mutating the returned object does not affect a subsequent resolve', () => {
|
|
const first = resolveRuntimeConfigIntent('claude');
|
|
first.installSurface = 'MUTATED';
|
|
first.writesSharedSettings = false;
|
|
|
|
const second = resolveRuntimeConfigIntent('claude');
|
|
assert.strictEqual(second.installSurface, 'settings-json');
|
|
assert.strictEqual(second.writesSharedSettings, true);
|
|
});
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Test 7: Completeness (AC#4 table-driven) — ALLOWED_CONFIG_RUNTIMES
|
|
// ---------------------------------------------------------------------------
|
|
|
|
describe('ALLOWED_CONFIG_RUNTIMES completeness', () => {
|
|
const EXPECTED_15 = new Set([
|
|
'claude', 'antigravity', 'augment', 'qwen', 'hermes', 'codebuddy',
|
|
'opencode', 'kilo', 'codex', 'copilot', 'cline', 'cursor', 'windsurf', 'trae',
|
|
'kimi',
|
|
]);
|
|
|
|
test('ALLOWED_CONFIG_RUNTIMES contains exactly the 15 expected runtimes', () => {
|
|
const runtimeSet = new Set(ALLOWED_CONFIG_RUNTIMES);
|
|
assert.deepStrictEqual(runtimeSet, EXPECTED_15);
|
|
});
|
|
|
|
test('every member of ALLOWED_CONFIG_RUNTIMES resolves without throwing', () => {
|
|
for (const runtime of ALLOWED_CONFIG_RUNTIMES) {
|
|
assert.doesNotThrow(() => resolveRuntimeConfigIntent(runtime), `${runtime} should resolve without throwing`);
|
|
}
|
|
});
|
|
|
|
test('ALLOWED_CONFIG_RUNTIMES has exactly 15 entries', () => {
|
|
assert.strictEqual([...ALLOWED_CONFIG_RUNTIMES].length, 15);
|
|
});
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Test 8: INSTALL_SURFACES export
|
|
// ---------------------------------------------------------------------------
|
|
|
|
describe('INSTALL_SURFACES export', () => {
|
|
const EXPECTED_SURFACES = new Set([
|
|
'settings-json',
|
|
'codex-toml',
|
|
'copilot-instructions',
|
|
'cline-rules',
|
|
'cursor-hooks-json',
|
|
'profile-marker-only',
|
|
]);
|
|
|
|
test('INSTALL_SURFACES contains exactly the 6 surface strings', () => {
|
|
assert.deepStrictEqual(new Set(INSTALL_SURFACES), EXPECTED_SURFACES);
|
|
});
|
|
});
|
|
|
|
describe('resolveInstallPlan — hooksSurface is descriptor-owned', () => {
|
|
test('real descriptor-owned none surface is preserved for opencode and kilo', () => {
|
|
assert.strictEqual(resolveInstallPlan('opencode').hooksSurface, 'none');
|
|
assert.strictEqual(resolveInstallPlan('kilo').hooksSurface, 'none');
|
|
});
|
|
|
|
test('synthetic descriptor resolves hooksSurface without runtime-name fallback', () => {
|
|
const runtimes = {
|
|
futurecli: {
|
|
runtime: {
|
|
installSurface: 'settings-json',
|
|
writesSharedSettings: true,
|
|
permissionWriter: null,
|
|
hookEvents: 'claude',
|
|
extendedHookEvents: ['Stop'],
|
|
hooksSurface: 'settings-json',
|
|
sandboxTier: 'none',
|
|
},
|
|
},
|
|
};
|
|
|
|
assert.deepStrictEqual(resolveInstallPlanFromRuntimes(runtimes, 'futurecli'), {
|
|
runtime: 'futurecli',
|
|
installSurface: 'settings-json',
|
|
writesSharedSettings: true,
|
|
finishPermissionWriter: null,
|
|
hookEvents: 'claude',
|
|
extendedHookEvents: ['Stop'],
|
|
hooksSurface: 'settings-json',
|
|
sandboxTier: 'none',
|
|
});
|
|
});
|
|
|
|
test('missing hooksSurface fails loudly instead of falling back from runtime name', () => {
|
|
const runtimes = {
|
|
opencode: {
|
|
runtime: {
|
|
installSurface: 'settings-json',
|
|
writesSharedSettings: true,
|
|
permissionWriter: 'opencode',
|
|
extendedHookEvents: [],
|
|
},
|
|
},
|
|
};
|
|
|
|
assert.throws(
|
|
() => resolveInstallPlanFromRuntimes(runtimes, 'opencode'),
|
|
/runtime\.hooksSurface/,
|
|
);
|
|
});
|
|
});
|
|
|
|
|
|
// ────────────────────────────────────────────────────────────────────────
|
|
// Folded from tests/enh-1082-install-plan-capstone.test.cjs — consolidation epic #1969 (B3 #1972)
|
|
// ────────────────────────────────────────────────────────────────────────
|
|
{
|
|
const { describe: __foldDescribe } = require('node:test');
|
|
__foldDescribe("folded:enh-1082-install-plan-capstone (consolidation epic #1969 B3 #1972)", () => {
|
|
'use strict';
|
|
|
|
/**
|
|
* Golden-master test for resolveInstallPlan — ADR-857 phase 5g capstone.
|
|
*
|
|
* Pins the exact InstallPlan shape for all 15 runtimes to guard against
|
|
* descriptor drift. Derived from actual resolveInstallPlan output at the time
|
|
* the seam was introduced (2026-06-11). Behavioral: calls the exported
|
|
* function and asserts on typed fields — no source-grep.
|
|
*/
|
|
|
|
const { describe, it } = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const { resolveInstallPlan } = require('../gsd-core/bin/lib/runtime-config-adapter-registry.cjs');
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Frozen golden-master table — derived from actual resolveInstallPlan output
|
|
// ---------------------------------------------------------------------------
|
|
|
|
const EXPECTED = {
|
|
claude: {
|
|
runtime: 'claude',
|
|
installSurface: 'settings-json',
|
|
writesSharedSettings: true,
|
|
finishPermissionWriter: null,
|
|
hookEvents: 'claude',
|
|
extendedHookEvents: ['SubagentStop', 'Stop', 'PreCompact', 'FileChanged'],
|
|
hooksSurface: 'settings-json',
|
|
sandboxTier: 'none',
|
|
},
|
|
codex: {
|
|
runtime: 'codex',
|
|
installSurface: 'codex-toml',
|
|
writesSharedSettings: false,
|
|
finishPermissionWriter: null,
|
|
hookEvents: 'claude',
|
|
extendedHookEvents: [],
|
|
hooksSurface: 'codex-hooks-json',
|
|
sandboxTier: 'codex-agent-sandbox',
|
|
},
|
|
antigravity: {
|
|
runtime: 'antigravity',
|
|
installSurface: 'settings-json',
|
|
writesSharedSettings: true,
|
|
finishPermissionWriter: null,
|
|
hookEvents: 'gemini',
|
|
extendedHookEvents: [],
|
|
hooksSurface: 'settings-json',
|
|
sandboxTier: 'none',
|
|
},
|
|
cursor: {
|
|
runtime: 'cursor',
|
|
installSurface: 'cursor-hooks-json',
|
|
writesSharedSettings: false,
|
|
finishPermissionWriter: null,
|
|
hookEvents: 'claude',
|
|
extendedHookEvents: [],
|
|
hooksSurface: 'cursor-hooks-json',
|
|
sandboxTier: 'none',
|
|
},
|
|
opencode: {
|
|
runtime: 'opencode',
|
|
installSurface: 'settings-json',
|
|
writesSharedSettings: true,
|
|
finishPermissionWriter: 'opencode',
|
|
hookEvents: undefined,
|
|
extendedHookEvents: [],
|
|
hooksSurface: 'none',
|
|
sandboxTier: 'none',
|
|
},
|
|
kilo: {
|
|
runtime: 'kilo',
|
|
installSurface: 'settings-json',
|
|
writesSharedSettings: false,
|
|
finishPermissionWriter: 'kilo',
|
|
hookEvents: undefined,
|
|
extendedHookEvents: [],
|
|
hooksSurface: 'none',
|
|
sandboxTier: 'none',
|
|
},
|
|
copilot: {
|
|
runtime: 'copilot',
|
|
installSurface: 'copilot-instructions',
|
|
writesSharedSettings: false,
|
|
finishPermissionWriter: null,
|
|
hookEvents: undefined,
|
|
extendedHookEvents: [],
|
|
hooksSurface: 'copilot-inline',
|
|
sandboxTier: 'none',
|
|
},
|
|
augment: {
|
|
runtime: 'augment',
|
|
installSurface: 'settings-json',
|
|
writesSharedSettings: true,
|
|
finishPermissionWriter: null,
|
|
hookEvents: 'claude',
|
|
extendedHookEvents: [],
|
|
hooksSurface: 'settings-json',
|
|
sandboxTier: 'none',
|
|
},
|
|
trae: {
|
|
runtime: 'trae',
|
|
installSurface: 'profile-marker-only',
|
|
writesSharedSettings: false,
|
|
finishPermissionWriter: null,
|
|
hookEvents: undefined,
|
|
extendedHookEvents: [],
|
|
hooksSurface: 'none',
|
|
sandboxTier: 'none',
|
|
},
|
|
qwen: {
|
|
runtime: 'qwen',
|
|
installSurface: 'settings-json',
|
|
writesSharedSettings: true,
|
|
finishPermissionWriter: null,
|
|
hookEvents: 'claude',
|
|
extendedHookEvents: ['SubagentStop', 'Stop', 'PreCompact'],
|
|
hooksSurface: 'settings-json',
|
|
sandboxTier: 'none',
|
|
},
|
|
hermes: {
|
|
runtime: 'hermes',
|
|
installSurface: 'settings-json',
|
|
writesSharedSettings: true,
|
|
finishPermissionWriter: null,
|
|
hookEvents: 'claude',
|
|
extendedHookEvents: [],
|
|
hooksSurface: 'settings-json',
|
|
sandboxTier: 'none',
|
|
},
|
|
codebuddy: {
|
|
runtime: 'codebuddy',
|
|
installSurface: 'settings-json',
|
|
writesSharedSettings: true,
|
|
finishPermissionWriter: null,
|
|
hookEvents: 'claude',
|
|
extendedHookEvents: [],
|
|
hooksSurface: 'settings-json',
|
|
sandboxTier: 'none',
|
|
},
|
|
cline: {
|
|
runtime: 'cline',
|
|
installSurface: 'cline-rules',
|
|
writesSharedSettings: false,
|
|
finishPermissionWriter: null,
|
|
hookEvents: undefined,
|
|
extendedHookEvents: [],
|
|
hooksSurface: 'cline-rules',
|
|
sandboxTier: 'none',
|
|
},
|
|
kimi: {
|
|
runtime: 'kimi',
|
|
installSurface: 'profile-marker-only',
|
|
writesSharedSettings: false,
|
|
finishPermissionWriter: null,
|
|
hookEvents: undefined,
|
|
extendedHookEvents: [],
|
|
hooksSurface: 'none',
|
|
sandboxTier: 'none',
|
|
},
|
|
windsurf: {
|
|
runtime: 'windsurf',
|
|
installSurface: 'profile-marker-only',
|
|
writesSharedSettings: false,
|
|
finishPermissionWriter: null,
|
|
hookEvents: undefined,
|
|
extendedHookEvents: [],
|
|
hooksSurface: 'none',
|
|
sandboxTier: 'none',
|
|
},
|
|
};
|
|
|
|
const ALL_RUNTIMES = Object.keys(EXPECTED);
|
|
|
|
describe('resolveInstallPlan — ADR-857 phase 5g golden master', () => {
|
|
it('covers exactly 15 runtimes', () => {
|
|
assert.strictEqual(ALL_RUNTIMES.length, 15);
|
|
});
|
|
|
|
for (const runtime of ALL_RUNTIMES) {
|
|
it(`resolveInstallPlan('${runtime}') matches frozen plan`, () => {
|
|
const actual = resolveInstallPlan(runtime);
|
|
assert.deepStrictEqual(actual, EXPECTED[runtime],
|
|
`InstallPlan for '${runtime}' drifted from golden master`);
|
|
});
|
|
}
|
|
|
|
it('resolveInstallPlan throws TypeError for unknown runtime', () => {
|
|
assert.throws(
|
|
() => resolveInstallPlan('bogus'),
|
|
(err) => err instanceof TypeError && /bogus/.test(err.message),
|
|
);
|
|
});
|
|
|
|
it('extendedHookEvents is always an array for every runtime', () => {
|
|
for (const runtime of ALL_RUNTIMES) {
|
|
const plan = resolveInstallPlan(runtime);
|
|
assert.ok(Array.isArray(plan.extendedHookEvents),
|
|
`${runtime}: extendedHookEvents should be an array`);
|
|
}
|
|
});
|
|
|
|
it('hooksSurface is always a non-empty string for every runtime', () => {
|
|
for (const runtime of ALL_RUNTIMES) {
|
|
const plan = resolveInstallPlan(runtime);
|
|
assert.strictEqual(typeof plan.hooksSurface, 'string',
|
|
`${runtime}: hooksSurface should be a string`);
|
|
assert.ok(plan.hooksSurface.length > 0,
|
|
`${runtime}: hooksSurface should not be empty`);
|
|
}
|
|
});
|
|
|
|
it('parity: resolveInstallPlan config-intent fields match resolveRuntimeConfigIntent', () => {
|
|
// Guard that resolveInstallPlan composes resolveRuntimeConfigIntent correctly —
|
|
// any drift between the two would silently break install().
|
|
const { resolveRuntimeConfigIntent } = require('../gsd-core/bin/lib/runtime-config-adapter-registry.cjs');
|
|
for (const runtime of ALL_RUNTIMES) {
|
|
const plan = resolveInstallPlan(runtime);
|
|
const intent = resolveRuntimeConfigIntent(runtime);
|
|
assert.strictEqual(plan.installSurface, intent.installSurface,
|
|
`${runtime}: installSurface mismatch between plan and intent`);
|
|
assert.strictEqual(plan.writesSharedSettings, intent.writesSharedSettings,
|
|
`${runtime}: writesSharedSettings mismatch`);
|
|
assert.strictEqual(plan.finishPermissionWriter, intent.finishPermissionWriter,
|
|
`${runtime}: finishPermissionWriter mismatch`);
|
|
}
|
|
});
|
|
});
|
|
});
|
|
}
|