easy-coding-harness 0.5.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/dist/cli.js +312 -9
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
- package/templates/claude/settings.json +4 -4
- package/templates/codex/hooks.json +2 -2
- package/templates/common/bundled-skills/ec-init/SKILL.md +10 -0
- package/templates/qoder/settings.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,12 @@
|
|
|
6
6
|
- `y`:常规功能升级
|
|
7
7
|
- `z`:日常 bug 修复
|
|
8
8
|
|
|
9
|
+
## 0.5.1
|
|
10
|
+
|
|
11
|
+
- 修复 Claude Code hook 命令使用相对路径导致在子目录 cwd 下找不到 `.claude/hooks/*.py` 的问题;Claude、Codex、Qoder 的 hook 配置现在安装时渲染为带引号的绝对脚本路径。
|
|
12
|
+
- `easy-coding upgrade` 可直接刷新存量 0.5.0 项目的 hook 配置并解决该问题;升级后 `ec-init` 合规扫描会校验 hook 配置是否已使用当前版本要求的绝对路径。
|
|
13
|
+
- `easy-coding clear` 和 `install-manifest.json` 兼容新绝对 hook 命令,清理时仍按项目相对 hook 路径识别托管注册项。
|
|
14
|
+
|
|
9
15
|
## 0.5.0
|
|
10
16
|
|
|
11
17
|
- 新增 supermodule 支持:`easy-coding init` 会识别 `.gitmodules`,父仓必装,已检出的一级子仓可按交互或参数选择分层安装。
|
package/dist/cli.js
CHANGED
|
@@ -350,7 +350,7 @@ async function writeInstallManifest(cwd, params) {
|
|
|
350
350
|
const registration = {
|
|
351
351
|
config_path: configPath2,
|
|
352
352
|
command: artifact.command,
|
|
353
|
-
hook_path: artifact.hookPath,
|
|
353
|
+
hook_path: artifact.hookPath ? normalizeHookPath(cwd, artifact.hookPath) : null,
|
|
354
354
|
platform: artifact.platform
|
|
355
355
|
};
|
|
356
356
|
hookRegistrations.set(`${configPath2}\0${normalizeCommand(artifact.command)}`, registration);
|
|
@@ -465,9 +465,75 @@ function collectHookCommands(hooks) {
|
|
|
465
465
|
return commands;
|
|
466
466
|
}
|
|
467
467
|
function extractHookPathFromCommand(command) {
|
|
468
|
-
|
|
468
|
+
return extractQuotedHookPath(command) ?? extractUnquotedHookPath(command);
|
|
469
|
+
}
|
|
470
|
+
function extractQuotedHookPath(command) {
|
|
471
|
+
for (let index = 0; index < command.length; index += 1) {
|
|
472
|
+
const quote = command[index];
|
|
473
|
+
if (quote !== '"' && quote !== "'") {
|
|
474
|
+
continue;
|
|
475
|
+
}
|
|
476
|
+
if (index > 0 && !/\s/.test(command[index - 1])) {
|
|
477
|
+
continue;
|
|
478
|
+
}
|
|
479
|
+
const parsed = parseQuotedToken(command, index, quote);
|
|
480
|
+
if (!parsed) {
|
|
481
|
+
continue;
|
|
482
|
+
}
|
|
483
|
+
const suffix = readPathSuffix(command, parsed.endIndex);
|
|
484
|
+
for (const candidate of [parsed.value + suffix, parsed.value]) {
|
|
485
|
+
if (isHookPythonPath(candidate)) {
|
|
486
|
+
return candidate;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
index = parsed.endIndex;
|
|
490
|
+
}
|
|
491
|
+
return null;
|
|
492
|
+
}
|
|
493
|
+
function parseQuotedToken(command, startIndex, quote) {
|
|
494
|
+
let value = "";
|
|
495
|
+
for (let index = startIndex + 1; index < command.length; index += 1) {
|
|
496
|
+
const char = command[index];
|
|
497
|
+
if (quote === '"' && char === "\\" && index + 1 < command.length) {
|
|
498
|
+
value += command[index + 1];
|
|
499
|
+
index += 1;
|
|
500
|
+
continue;
|
|
501
|
+
}
|
|
502
|
+
if (char === quote) {
|
|
503
|
+
return { value, endIndex: index + 1 };
|
|
504
|
+
}
|
|
505
|
+
value += char;
|
|
506
|
+
}
|
|
507
|
+
return null;
|
|
508
|
+
}
|
|
509
|
+
function readPathSuffix(command, startIndex) {
|
|
510
|
+
if (command[startIndex] !== "/") {
|
|
511
|
+
return "";
|
|
512
|
+
}
|
|
513
|
+
let suffix = "";
|
|
514
|
+
for (let index = startIndex; index < command.length; index += 1) {
|
|
515
|
+
const char = command[index];
|
|
516
|
+
if (/\s/.test(char)) {
|
|
517
|
+
break;
|
|
518
|
+
}
|
|
519
|
+
suffix += char;
|
|
520
|
+
}
|
|
521
|
+
return suffix;
|
|
522
|
+
}
|
|
523
|
+
function extractUnquotedHookPath(command) {
|
|
524
|
+
const match = command.match(/(?:^|\s)(\S+\/hooks\/\S+\.py)(?:\s|$)/);
|
|
469
525
|
return match?.[1] ?? null;
|
|
470
526
|
}
|
|
527
|
+
function isHookPythonPath(candidate) {
|
|
528
|
+
return /\/hooks\/\S+\.py$/.test(candidate);
|
|
529
|
+
}
|
|
530
|
+
function normalizeHookPath(cwd, hookPath) {
|
|
531
|
+
const normalized = hookPath.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
532
|
+
if (path4.isAbsolute(hookPath) || path4.posix.isAbsolute(normalized) || path4.win32.isAbsolute(hookPath)) {
|
|
533
|
+
return toProjectPath(cwd, hookPath);
|
|
534
|
+
}
|
|
535
|
+
return normalized;
|
|
536
|
+
}
|
|
471
537
|
function byPath(a, b) {
|
|
472
538
|
return a.path.localeCompare(b.path);
|
|
473
539
|
}
|
|
@@ -696,6 +762,33 @@ function resolvePlaceholders(content, ctx, contentName = "template") {
|
|
|
696
762
|
}
|
|
697
763
|
return resolved;
|
|
698
764
|
}
|
|
765
|
+
function withInstallPaths(cwd, ctx) {
|
|
766
|
+
const hooksDir = toPosixPath(path8.resolve(cwd, ctx.platform_config_dir, "hooks"));
|
|
767
|
+
return {
|
|
768
|
+
...ctx,
|
|
769
|
+
platform_hooks_dir_abs: hooksDir,
|
|
770
|
+
platform_hooks_dir_shell: jsonStringContent(shellDoubleQuoteArg(hooksDir))
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
function renderHookCommand(cwd, ctx, scriptName, platform = process.platform) {
|
|
774
|
+
const hooksDir = toPosixPath(path8.resolve(cwd, ctx.platform_config_dir, "hooks"));
|
|
775
|
+
return `${ctx.python_cmd} ${shellDoubleQuoteArg(hooksDir, platform)}/${scriptName}`;
|
|
776
|
+
}
|
|
777
|
+
function shellDoubleQuoteArg(value, platform = process.platform) {
|
|
778
|
+
if (platform === "win32") {
|
|
779
|
+
if (value.includes('"')) {
|
|
780
|
+
throw new Error("Windows hook paths cannot contain double quotes.");
|
|
781
|
+
}
|
|
782
|
+
return `"${value}"`;
|
|
783
|
+
}
|
|
784
|
+
return `"${value.replace(/(["\\$`])/g, "\\$1")}"`;
|
|
785
|
+
}
|
|
786
|
+
function toPosixPath(filePath) {
|
|
787
|
+
return filePath.split(path8.sep).join("/");
|
|
788
|
+
}
|
|
789
|
+
function jsonStringContent(value) {
|
|
790
|
+
return JSON.stringify(value).slice(1, -1);
|
|
791
|
+
}
|
|
699
792
|
async function resolveSkills(ctx) {
|
|
700
793
|
const skillsRoot = getTemplatePath("common", "skills");
|
|
701
794
|
const entries = await readdir2(skillsRoot);
|
|
@@ -851,7 +944,7 @@ function stripTemplateExtension(fileName) {
|
|
|
851
944
|
async function configureClaude(cwd, opts = {}) {
|
|
852
945
|
const platform = "claude-code";
|
|
853
946
|
const meta = PLATFORM_META[platform];
|
|
854
|
-
const ctx = meta.templateContext;
|
|
947
|
+
const ctx = withInstallPaths(cwd, meta.templateContext);
|
|
855
948
|
const dest = path9.join(cwd, ".claude");
|
|
856
949
|
const hookConfigPath = path9.join(cwd, meta.hookConfigFile);
|
|
857
950
|
const artifacts = [];
|
|
@@ -889,7 +982,7 @@ import path10 from "path";
|
|
|
889
982
|
async function configureCodex(cwd, opts = {}) {
|
|
890
983
|
const platform = "codex";
|
|
891
984
|
const meta = PLATFORM_META[platform];
|
|
892
|
-
const ctx = meta.templateContext;
|
|
985
|
+
const ctx = withInstallPaths(cwd, meta.templateContext);
|
|
893
986
|
const hookConfigPath = path10.join(cwd, meta.hookConfigFile);
|
|
894
987
|
const artifacts = [];
|
|
895
988
|
artifacts.push(
|
|
@@ -1005,7 +1098,7 @@ async function existingManagedSkillArtifacts(skillsDir, skillNames, platform) {
|
|
|
1005
1098
|
async function configureQoder(cwd, opts = {}) {
|
|
1006
1099
|
const platform = "qoder";
|
|
1007
1100
|
const meta = resolvePlatformMeta(cwd, platform);
|
|
1008
|
-
const ctx = meta.templateContext;
|
|
1101
|
+
const ctx = withInstallPaths(cwd, meta.templateContext);
|
|
1009
1102
|
const dest = path12.join(cwd, ctx.platform_config_dir);
|
|
1010
1103
|
const hookConfigPath = path12.join(cwd, meta.hookConfigFile);
|
|
1011
1104
|
const artifacts = [];
|
|
@@ -1770,11 +1863,12 @@ async function buildManifestClearPlan(cwd, agents, manifest) {
|
|
|
1770
1863
|
if (!agentSet.has(registration.platform)) {
|
|
1771
1864
|
continue;
|
|
1772
1865
|
}
|
|
1866
|
+
const managedHookPaths = registration.hook_path ? managedHookPathsForRegistration(cwd, registration.hook_path) : [];
|
|
1773
1867
|
addHookConfigPrune(
|
|
1774
1868
|
plan.pruneHookConfigs,
|
|
1775
1869
|
plan.pruneHookCommands,
|
|
1776
1870
|
manifestPath(cwd, registration.config_path),
|
|
1777
|
-
|
|
1871
|
+
managedHookPaths,
|
|
1778
1872
|
[registration.command]
|
|
1779
1873
|
);
|
|
1780
1874
|
}
|
|
@@ -1822,7 +1916,7 @@ async function addTemplateClearEntries(plan, cwd, platform, metas, managedSkills
|
|
|
1822
1916
|
plan.pruneHookConfigs,
|
|
1823
1917
|
plan.pruneHookCommands,
|
|
1824
1918
|
path16.join(cwd, meta.hookConfigFile),
|
|
1825
|
-
|
|
1919
|
+
managedHookPathsForTemplate(cwd, meta, hookFileNames),
|
|
1826
1920
|
[]
|
|
1827
1921
|
);
|
|
1828
1922
|
plan.constraints.add(path16.join(cwd, meta.mainConstraint));
|
|
@@ -1873,6 +1967,51 @@ function addHookConfigPrune(pruneHookConfigs, pruneHookCommands, filePath, manag
|
|
|
1873
1967
|
}
|
|
1874
1968
|
pruneHookCommands.set(filePath, existingCommands);
|
|
1875
1969
|
}
|
|
1970
|
+
function managedHookPathsForRegistration(cwd, hookPath) {
|
|
1971
|
+
const paths = [hookPath];
|
|
1972
|
+
try {
|
|
1973
|
+
paths.push(...managedHookPathTokens(manifestPath(cwd, hookPath)));
|
|
1974
|
+
} catch {
|
|
1975
|
+
if (path16.isAbsolute(hookPath)) {
|
|
1976
|
+
paths.push(...managedHookPathTokens(hookPath));
|
|
1977
|
+
}
|
|
1978
|
+
}
|
|
1979
|
+
return paths;
|
|
1980
|
+
}
|
|
1981
|
+
function managedHookPathsForTemplate(cwd, meta, hookFileNames) {
|
|
1982
|
+
const relativePaths = hookFileNames.map(
|
|
1983
|
+
(name) => `${meta.templateContext.platform_config_dir}/hooks/${name}`
|
|
1984
|
+
);
|
|
1985
|
+
const absolutePaths = hookFileNames.flatMap(
|
|
1986
|
+
(name) => managedHookPathTokens(path16.join(cwd, meta.hooksDir, name))
|
|
1987
|
+
);
|
|
1988
|
+
return [...relativePaths, ...absolutePaths];
|
|
1989
|
+
}
|
|
1990
|
+
function managedHookPathTokens(filePath) {
|
|
1991
|
+
const tokens = /* @__PURE__ */ new Set();
|
|
1992
|
+
for (const equivalentPath of equivalentAbsolutePaths(filePath)) {
|
|
1993
|
+
const normalized = equivalentPath.replace(/\\/g, "/");
|
|
1994
|
+
const dir = path16.posix.dirname(normalized);
|
|
1995
|
+
const basename = path16.posix.basename(normalized);
|
|
1996
|
+
tokens.add(normalized);
|
|
1997
|
+
tokens.add(shellDoubleQuoteArg2(normalized));
|
|
1998
|
+
tokens.add(`${shellDoubleQuoteArg2(dir)}/${basename}`);
|
|
1999
|
+
}
|
|
2000
|
+
return [...tokens];
|
|
2001
|
+
}
|
|
2002
|
+
function equivalentAbsolutePaths(filePath) {
|
|
2003
|
+
const normalized = path16.resolve(filePath).replace(/\\/g, "/");
|
|
2004
|
+
const equivalents = [normalized];
|
|
2005
|
+
if (normalized.startsWith("/private/var/")) {
|
|
2006
|
+
equivalents.push(normalized.replace(/^\/private\/var\//, "/var/"));
|
|
2007
|
+
} else if (normalized.startsWith("/var/")) {
|
|
2008
|
+
equivalents.push(normalized.replace(/^\/var\//, "/private/var/"));
|
|
2009
|
+
}
|
|
2010
|
+
return equivalents;
|
|
2011
|
+
}
|
|
2012
|
+
function shellDoubleQuoteArg2(value) {
|
|
2013
|
+
return `"${value.replace(/(["\\$`])/g, "\\$1")}"`;
|
|
2014
|
+
}
|
|
1876
2015
|
function createClearPlan() {
|
|
1877
2016
|
return {
|
|
1878
2017
|
remove: /* @__PURE__ */ new Set(),
|
|
@@ -2057,7 +2196,7 @@ function isManagedHook(hook, managedHookPaths, managedHookCommands) {
|
|
|
2057
2196
|
if (normalizedManagedCommands.includes(normalizeCommand(command))) {
|
|
2058
2197
|
return true;
|
|
2059
2198
|
}
|
|
2060
|
-
const normalizedCommand = command
|
|
2199
|
+
const normalizedCommand = command;
|
|
2061
2200
|
return managedHookPaths.some(
|
|
2062
2201
|
(hookPath) => commandContainsPathToken(normalizedCommand, hookPath.replace(/\\/g, "/"))
|
|
2063
2202
|
);
|
|
@@ -2543,8 +2682,35 @@ async function update(opts) {
|
|
|
2543
2682
|
}
|
|
2544
2683
|
|
|
2545
2684
|
// src/commands/upgrade.ts
|
|
2685
|
+
import path22 from "path";
|
|
2546
2686
|
import { cancel as cancel5, confirm as confirm4, outro as outro5 } from "@clack/prompts";
|
|
2547
2687
|
import chalk7 from "chalk";
|
|
2688
|
+
var EXPECTED_HOOK_REGISTRATION_SCRIPTS = {
|
|
2689
|
+
"claude-code": [
|
|
2690
|
+
{ event: "SessionStart", scriptName: "session-start.py" },
|
|
2691
|
+
{ event: "UserPromptSubmit", scriptName: "session-start.py" },
|
|
2692
|
+
{ event: "UserPromptSubmit", scriptName: "inject-workflow-state.py" },
|
|
2693
|
+
{ event: "PreToolUse", scriptName: "inject-subagent-context.py" }
|
|
2694
|
+
],
|
|
2695
|
+
codex: [
|
|
2696
|
+
{ event: "UserPromptSubmit", scriptName: "session-start.py" },
|
|
2697
|
+
{ event: "UserPromptSubmit", scriptName: "inject-workflow-state.py" }
|
|
2698
|
+
],
|
|
2699
|
+
qoder: [
|
|
2700
|
+
{ event: "UserPromptSubmit", scriptName: "session-start.py" },
|
|
2701
|
+
{ event: "UserPromptSubmit", scriptName: "inject-workflow-state.py" },
|
|
2702
|
+
{ event: "PreToolUse", scriptName: "inject-subagent-context.py" }
|
|
2703
|
+
]
|
|
2704
|
+
};
|
|
2705
|
+
var MANAGED_HOOK_SCRIPT_NAMES = {
|
|
2706
|
+
"claude-code": [
|
|
2707
|
+
...new Set(
|
|
2708
|
+
EXPECTED_HOOK_REGISTRATION_SCRIPTS["claude-code"].map(({ scriptName }) => scriptName)
|
|
2709
|
+
)
|
|
2710
|
+
],
|
|
2711
|
+
codex: [...new Set(EXPECTED_HOOK_REGISTRATION_SCRIPTS.codex.map(({ scriptName }) => scriptName))],
|
|
2712
|
+
qoder: [...new Set(EXPECTED_HOOK_REGISTRATION_SCRIPTS.qoder.map(({ scriptName }) => scriptName))]
|
|
2713
|
+
};
|
|
2548
2714
|
async function upgrade(opts) {
|
|
2549
2715
|
renderBanner();
|
|
2550
2716
|
const cwd = process.cwd();
|
|
@@ -2645,12 +2811,149 @@ async function resolvePendingUpgradeTargets(targets) {
|
|
|
2645
2811
|
`${target.label} harness version ${installedVersion} is newer than CLI ${VERSION}. Update the CLI first.`
|
|
2646
2812
|
);
|
|
2647
2813
|
}
|
|
2648
|
-
if (relation === -1) {
|
|
2814
|
+
if (relation === -1 || relation === 0 && await needsHookConfigRefresh(target, config.agents)) {
|
|
2649
2815
|
pending.push({ target, config });
|
|
2650
2816
|
}
|
|
2651
2817
|
}
|
|
2652
2818
|
return pending;
|
|
2653
2819
|
}
|
|
2820
|
+
async function needsHookConfigRefresh(target, agents) {
|
|
2821
|
+
const manifest = await readInstallManifest(target.dir);
|
|
2822
|
+
for (const agent of agents) {
|
|
2823
|
+
const meta = resolvePlatformMeta(target.dir, agent);
|
|
2824
|
+
const configPath2 = path22.join(target.dir, meta.hookConfigFile);
|
|
2825
|
+
const content = await readTextIfExists(configPath2);
|
|
2826
|
+
if (content === null) {
|
|
2827
|
+
return true;
|
|
2828
|
+
}
|
|
2829
|
+
let parsed;
|
|
2830
|
+
try {
|
|
2831
|
+
parsed = JSON.parse(content);
|
|
2832
|
+
} catch {
|
|
2833
|
+
return true;
|
|
2834
|
+
}
|
|
2835
|
+
const commandsByEvent = collectHookCommandsByEvent(parsed.hooks);
|
|
2836
|
+
const commands = [...commandsByEvent.values()].flat();
|
|
2837
|
+
const expectedRegistrations = expectedHookRegistrations(target.dir, meta, agent);
|
|
2838
|
+
const expectedCommandSet = new Set(
|
|
2839
|
+
expectedRegistrations.map((registration) => registration.command)
|
|
2840
|
+
);
|
|
2841
|
+
if (!hasExpectedHookRegistrations(commandsByEvent, expectedRegistrations)) {
|
|
2842
|
+
return true;
|
|
2843
|
+
}
|
|
2844
|
+
const manifestManagedCommands = new Set(
|
|
2845
|
+
manifest?.hook_registrations.filter((registration) => registration.platform === agent).map((registration) => registration.command) ?? []
|
|
2846
|
+
);
|
|
2847
|
+
if (commands.some(
|
|
2848
|
+
(command) => isUnexpectedManagedHookCommand(
|
|
2849
|
+
command,
|
|
2850
|
+
expectedCommandSet,
|
|
2851
|
+
target.dir,
|
|
2852
|
+
meta,
|
|
2853
|
+
agent,
|
|
2854
|
+
manifestManagedCommands
|
|
2855
|
+
)
|
|
2856
|
+
)) {
|
|
2857
|
+
return true;
|
|
2858
|
+
}
|
|
2859
|
+
}
|
|
2860
|
+
return false;
|
|
2861
|
+
}
|
|
2862
|
+
function expectedHookRegistrations(cwd, meta, platform) {
|
|
2863
|
+
return EXPECTED_HOOK_REGISTRATION_SCRIPTS[platform].map(({ event, scriptName }) => ({
|
|
2864
|
+
event,
|
|
2865
|
+
command: renderHookCommand(cwd, meta.templateContext, scriptName)
|
|
2866
|
+
}));
|
|
2867
|
+
}
|
|
2868
|
+
function hasExpectedHookRegistrations(commandsByEvent, expectedRegistrations) {
|
|
2869
|
+
const actualCounts = countRegistrations(
|
|
2870
|
+
[...commandsByEvent.entries()].flatMap(
|
|
2871
|
+
([event, commands]) => commands.map((command) => ({ event, command }))
|
|
2872
|
+
)
|
|
2873
|
+
);
|
|
2874
|
+
const expectedCounts = countRegistrations(expectedRegistrations);
|
|
2875
|
+
return [...expectedCounts.entries()].every(
|
|
2876
|
+
([registrationKey, expectedCount]) => (actualCounts.get(registrationKey) ?? 0) >= expectedCount
|
|
2877
|
+
);
|
|
2878
|
+
}
|
|
2879
|
+
function countRegistrations(registrations) {
|
|
2880
|
+
const counts = /* @__PURE__ */ new Map();
|
|
2881
|
+
for (const registration of registrations) {
|
|
2882
|
+
const key = hookRegistrationKey(registration);
|
|
2883
|
+
counts.set(key, (counts.get(key) ?? 0) + 1);
|
|
2884
|
+
}
|
|
2885
|
+
return counts;
|
|
2886
|
+
}
|
|
2887
|
+
function hookRegistrationKey(registration) {
|
|
2888
|
+
return `${registration.event}\0${registration.command}`;
|
|
2889
|
+
}
|
|
2890
|
+
function collectHookCommandsByEvent(hooks) {
|
|
2891
|
+
if (!hooks || typeof hooks !== "object" || Array.isArray(hooks)) {
|
|
2892
|
+
return /* @__PURE__ */ new Map();
|
|
2893
|
+
}
|
|
2894
|
+
const commandsByEvent = /* @__PURE__ */ new Map();
|
|
2895
|
+
for (const [event, value] of Object.entries(hooks)) {
|
|
2896
|
+
if (!Array.isArray(value)) {
|
|
2897
|
+
continue;
|
|
2898
|
+
}
|
|
2899
|
+
for (const group of value) {
|
|
2900
|
+
if (!group || typeof group !== "object") {
|
|
2901
|
+
continue;
|
|
2902
|
+
}
|
|
2903
|
+
const hookItems = group.hooks;
|
|
2904
|
+
if (!Array.isArray(hookItems)) {
|
|
2905
|
+
continue;
|
|
2906
|
+
}
|
|
2907
|
+
for (const hook of hookItems) {
|
|
2908
|
+
if (!hook || typeof hook !== "object") {
|
|
2909
|
+
continue;
|
|
2910
|
+
}
|
|
2911
|
+
const command = hook.command;
|
|
2912
|
+
if (typeof command === "string") {
|
|
2913
|
+
const eventCommands = commandsByEvent.get(event) ?? [];
|
|
2914
|
+
eventCommands.push(command);
|
|
2915
|
+
commandsByEvent.set(event, eventCommands);
|
|
2916
|
+
}
|
|
2917
|
+
}
|
|
2918
|
+
}
|
|
2919
|
+
}
|
|
2920
|
+
return commandsByEvent;
|
|
2921
|
+
}
|
|
2922
|
+
function isUnexpectedManagedHookCommand(command, expectedCommands, cwd, meta, platform, manifestManagedCommands) {
|
|
2923
|
+
if (expectedCommands.has(command)) {
|
|
2924
|
+
return false;
|
|
2925
|
+
}
|
|
2926
|
+
if (manifestManagedCommands.has(command)) {
|
|
2927
|
+
return true;
|
|
2928
|
+
}
|
|
2929
|
+
const hookPath = extractHookPathFromCommand(command);
|
|
2930
|
+
return hookPath === null ? false : isCurrentProjectManagedHookPath(cwd, hookPath, meta, platform);
|
|
2931
|
+
}
|
|
2932
|
+
function isCurrentProjectManagedHookPath(cwd, hookPath, meta, platform) {
|
|
2933
|
+
const normalizedHookPath = normalizePathForHookComparison(hookPath);
|
|
2934
|
+
const configDir = normalizePathForHookComparison(meta.templateContext.platform_config_dir);
|
|
2935
|
+
return MANAGED_HOOK_SCRIPT_NAMES[platform].some((scriptName) => {
|
|
2936
|
+
const relativeHookPath = `${configDir}/hooks/${scriptName}`;
|
|
2937
|
+
if (normalizedHookPath === relativeHookPath) {
|
|
2938
|
+
return true;
|
|
2939
|
+
}
|
|
2940
|
+
return pathAliases(
|
|
2941
|
+
normalizePathForHookComparison(path22.resolve(cwd, relativeHookPath))
|
|
2942
|
+
).includes(normalizedHookPath);
|
|
2943
|
+
});
|
|
2944
|
+
}
|
|
2945
|
+
function normalizePathForHookComparison(value) {
|
|
2946
|
+
return value.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
2947
|
+
}
|
|
2948
|
+
function pathAliases(value) {
|
|
2949
|
+
const aliases = /* @__PURE__ */ new Set([value]);
|
|
2950
|
+
if (value.startsWith("/private/var/")) {
|
|
2951
|
+
aliases.add(value.replace(/^\/private\/var\//, "/var/"));
|
|
2952
|
+
} else if (value.startsWith("/var/")) {
|
|
2953
|
+
aliases.add(`/private${value}`);
|
|
2954
|
+
}
|
|
2955
|
+
return [...aliases];
|
|
2956
|
+
}
|
|
2654
2957
|
async function resolveParentTopologyRefresh(targets, pending) {
|
|
2655
2958
|
const parent = targets.find(
|
|
2656
2959
|
(target) => target.label === "." && target.supermodule.role === "super-parent"
|