feat(shell-projection): centralize managed hook command policy (#3450)
* feat(shell-projection): route persistent PATH hints through projection seam * refactor(shell-projection): unify managed hook command policy * fix(install): guard malformed settings hooks during uninstall * chore(changeset): add fragment for pr 3450 * fix(install): guard malformed settings hook entries
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
const path = require('path');
|
||||
const { isManagedHookCommand } = require('../shell-command-projection.cjs');
|
||||
|
||||
function isStructurallyEmpty(value) {
|
||||
if (value === null || value === undefined) return true;
|
||||
@@ -11,12 +11,11 @@ function isStructurallyEmpty(value) {
|
||||
}
|
||||
|
||||
function isManagedCodexHookCommand(command, configDir) {
|
||||
if (typeof command !== 'string') return false;
|
||||
if (typeof configDir !== 'string' || configDir.length === 0) return false;
|
||||
const normalizedCommand = command.replace(/\\/g, '/');
|
||||
const managedHooksDir = `${path.join(configDir, 'hooks').replace(/\\/g, '/')}/`;
|
||||
if (!normalizedCommand.includes(managedHooksDir)) return false;
|
||||
return /(^|[\\/\s"'])(gsd-check-update\.js|gsd-update-check\.js)(?=$|[\s"'])/.test(normalizedCommand);
|
||||
return isManagedHookCommand(command, {
|
||||
surface: 'codex-hooks-json',
|
||||
includeLegacyAliases: true,
|
||||
configDir,
|
||||
});
|
||||
}
|
||||
|
||||
function pruneLegacyCodexHooksJsonValue(value, configDir) {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
const path = require('path');
|
||||
|
||||
/**
|
||||
* Shell Command Projection Module
|
||||
*
|
||||
@@ -99,6 +101,37 @@ const MANAGED_HOOK_BASENAMES_BY_SURFACE = {
|
||||
]),
|
||||
};
|
||||
|
||||
const MANAGED_HOOK_COMMAND_BASENAMES_BY_SURFACE = {
|
||||
'settings-json': new Set([
|
||||
'gsd-check-update.js',
|
||||
'gsd-statusline.js',
|
||||
'gsd-context-monitor.js',
|
||||
'gsd-prompt-guard.js',
|
||||
'gsd-read-guard.js',
|
||||
'gsd-read-injection-scanner.js',
|
||||
'gsd-update-banner.js',
|
||||
'gsd-workflow-guard.js',
|
||||
'gsd-session-state.sh',
|
||||
'gsd-validate-commit.sh',
|
||||
'gsd-phase-boundary.sh',
|
||||
]),
|
||||
'codex-toml': new Set([
|
||||
'gsd-check-update.js',
|
||||
]),
|
||||
'codex-hooks-json': new Set([
|
||||
'gsd-check-update.js',
|
||||
]),
|
||||
};
|
||||
|
||||
const LEGACY_MANAGED_HOOK_ALIASES_BY_SURFACE = {
|
||||
'codex-toml': new Set([
|
||||
'gsd-update-check.js',
|
||||
]),
|
||||
'codex-hooks-json': new Set([
|
||||
'gsd-update-check.js',
|
||||
]),
|
||||
};
|
||||
|
||||
function managedHookSurfaceSet(surface = 'settings-json') {
|
||||
return MANAGED_HOOK_BASENAMES_BY_SURFACE[surface] || MANAGED_HOOK_BASENAMES_BY_SURFACE['settings-json'];
|
||||
}
|
||||
@@ -110,6 +143,36 @@ function isManagedHookBasename(scriptPathOrBasename, opts = {}) {
|
||||
return managedHookSurfaceSet(surface).has(basename);
|
||||
}
|
||||
|
||||
function managedHookCommandSurfaceSet(surface = 'settings-json', includeLegacyAliases = false) {
|
||||
const base = MANAGED_HOOK_COMMAND_BASENAMES_BY_SURFACE[surface]
|
||||
|| MANAGED_HOOK_COMMAND_BASENAMES_BY_SURFACE['settings-json'];
|
||||
if (!includeLegacyAliases) return base;
|
||||
const aliases = LEGACY_MANAGED_HOOK_ALIASES_BY_SURFACE[surface];
|
||||
if (!aliases || aliases.size === 0) return base;
|
||||
return new Set([...base, ...aliases]);
|
||||
}
|
||||
|
||||
function isManagedHookCommand(commandText, opts = {}) {
|
||||
if (typeof commandText !== 'string') return false;
|
||||
const surface = opts.surface || 'settings-json';
|
||||
const includeLegacyAliases = opts.includeLegacyAliases === true;
|
||||
const managedBasenames = managedHookCommandSurfaceSet(surface, includeLegacyAliases);
|
||||
if (!managedBasenames || managedBasenames.size === 0) return false;
|
||||
const normalizedCommand = commandText.replace(/\\/g, '/');
|
||||
|
||||
if (typeof opts.configDir === 'string' && opts.configDir.length > 0) {
|
||||
const normalizedHooksDir = `${path.join(opts.configDir, 'hooks').replace(/\\/g, '/')}/`;
|
||||
if (!normalizedCommand.includes(normalizedHooksDir)) return false;
|
||||
}
|
||||
|
||||
for (const basename of managedBasenames) {
|
||||
const escapedBasename = basename.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
const pattern = new RegExp(`(^|[\\\\/\\s"'` + '`' + `])${escapedBasename}(?=$|[\\s"'` + '`' + `])`);
|
||||
if (pattern.test(normalizedCommand)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Projection helper for legacy settings.json hook rewrites.
|
||||
*
|
||||
@@ -158,11 +221,9 @@ function escapePowerShellSingleQuoted(value) {
|
||||
function escapePosixDoubleQuoted(value) {
|
||||
return String(value).replace(/[\\$"`]/g, '\\$&');
|
||||
}
|
||||
|
||||
function escapeSingleQuotedShellLiteral(value) {
|
||||
return String(value).replace(/'/g, "'\\''");
|
||||
}
|
||||
|
||||
function renderShellActionLines(shellActions = []) {
|
||||
return shellActions.map((action) => {
|
||||
if (!action || !action.command) return '';
|
||||
@@ -231,8 +292,16 @@ function projectPathActionProjection({
|
||||
};
|
||||
}
|
||||
|
||||
function projectPersistentPathExportActions({ targetDir, platform = process.platform }) {
|
||||
const projected = projectPathActionProjection({
|
||||
mode: 'persist',
|
||||
targetDir,
|
||||
platform,
|
||||
});
|
||||
return { shellActions: projected.shellActions };
|
||||
}
|
||||
|
||||
function buildWindowsShimTriple(shimSrc) {
|
||||
const path = require('path');
|
||||
const shimAbs = path.resolve(shimSrc);
|
||||
const shimQuoted = JSON.stringify(shimAbs);
|
||||
const invocation = {
|
||||
@@ -292,11 +361,13 @@ module.exports = {
|
||||
projectShellCommandText,
|
||||
projectManagedHookCommand,
|
||||
isManagedHookBasename,
|
||||
isManagedHookCommand,
|
||||
projectLegacySettingsHookCommand,
|
||||
escapeTomlDoubleQuotedString,
|
||||
projectCodexHookTomlCommand,
|
||||
projectPathActionProjection,
|
||||
renderShellActionLines,
|
||||
projectPersistentPathExportActions,
|
||||
buildWindowsShimTriple,
|
||||
formatSdkPathDiagnostic,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user