@grunnverk/tree-execution 1.5.0 → 1.5.2

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/dist/tree.d.ts CHANGED
@@ -2,11 +2,12 @@
2
2
  import type { TreeExecutionConfig } from './types/config.js';
3
3
  import type { PackageInfo } from '@grunnverk/tree-core';
4
4
  export declare const __resetGlobalState: () => void;
5
- export declare const executePackage: (packageName: string, packageInfo: PackageInfo, commandToRun: string, runConfig: TreeExecutionConfig, isDryRun: boolean, index: number, total: number, allPackageNames: Set<string>, isBuiltInCommand?: boolean) => Promise<{
5
+ export declare const executePackage: (packageName: string, packageInfo: PackageInfo, commandToRun: string, runConfig: TreeExecutionConfig, isDryRun: boolean, index: number, total: number, allPackageNames: Set<string>, isBuiltInCommand?: boolean, context?: any) => Promise<{
6
6
  success: boolean;
7
7
  error?: any;
8
8
  isTimeoutError?: boolean;
9
9
  skippedNoChanges?: boolean;
10
+ skipReason?: "no-changes" | "already-published" | "other";
10
11
  logFile?: string;
11
12
  }>;
12
13
  export declare const execute: (runConfig: TreeExecutionConfig) => Promise<string>;
@@ -1 +1 @@
1
- {"version":3,"file":"tree.d.ts","sourceRoot":"","sources":["../src/tree.ts"],"names":[],"mappings":";AAwCA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAE7D,OAAO,KAAK,EACR,WAAW,EAEd,MAAM,sBAAsB,CAAC;AA6D9B,eAAO,MAAM,kBAAkB,YAG9B,CAAC;AAimBF,eAAO,MAAM,cAAc,GACvB,aAAa,MAAM,EACnB,aAAa,WAAW,EACxB,cAAc,MAAM,EACpB,WAAW,mBAAmB,EAC9B,UAAU,OAAO,EACjB,OAAO,MAAM,EACb,OAAO,MAAM,EACb,iBAAiB,GAAG,CAAC,MAAM,CAAC,EAC5B,mBAAkB,OAAe,KAClC,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,GAAG,CAAC;IAAC,cAAc,CAAC,EAAE,OAAO,CAAC;IAAC,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,CAyanH,CAAC;AA4HF,eAAO,MAAM,OAAO,GAAU,WAAW,mBAAmB,KAAG,OAAO,CAAC,MAAM,CA2oD5E,CAAC"}
1
+ {"version":3,"file":"tree.d.ts","sourceRoot":"","sources":["../src/tree.ts"],"names":[],"mappings":";AAwCA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAE7D,OAAO,KAAK,EACR,WAAW,EAEd,MAAM,sBAAsB,CAAC;AA8D9B,eAAO,MAAM,kBAAkB,YAG9B,CAAC;AAimBF,eAAO,MAAM,cAAc,GACvB,aAAa,MAAM,EACnB,aAAa,WAAW,EACxB,cAAc,MAAM,EACpB,WAAW,mBAAmB,EAC9B,UAAU,OAAO,EACjB,OAAO,MAAM,EACb,OAAO,MAAM,EACb,iBAAiB,GAAG,CAAC,MAAM,CAAC,EAC5B,mBAAkB,OAAe,EACjC,UAAU,GAAG,KACd,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,GAAG,CAAC;IAAC,cAAc,CAAC,EAAE,OAAO,CAAC;IAAC,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAAC,UAAU,CAAC,EAAE,YAAY,GAAG,mBAAmB,GAAG,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,CAsc9K,CAAC;AA4HF,eAAO,MAAM,OAAO,GAAU,WAAW,mBAAmB,KAAG,OAAO,CAAC,MAAM,CA2oD5E,CAAC"}
package/dist/tree.js CHANGED
@@ -45,6 +45,7 @@ import { getOutputPath, PerformanceTimer, isInGitRepository, runGitWithLock, opt
45
45
  // Built-in commands - using stubs for now
46
46
  // TODO: Refactor to use callbacks/dependency injection
47
47
  import { Updates, Commit, Link, Unlink } from './util/commandStubs.js';
48
+ import { escapeShellArg } from './util/shellEscape.js';
48
49
  // Define constants locally
49
50
  const DEFAULT_OUTPUT_DIRECTORY = 'output/kodrdriv';
50
51
  // Global state to track published versions during tree execution - protected by mutex
@@ -567,7 +568,8 @@ const formatSubprojectError = (packageName, error, _packageInfo, _position, _tot
567
568
  // Note: PackageInfo, DependencyGraph, scanForPackageJsonFiles, parsePackageJson,
568
569
  // buildDependencyGraph, and topologicalSort are now imported from ../util/dependencyGraph
569
570
  // Execute a single package and return execution result
570
- export const executePackage = async (packageName, packageInfo, commandToRun, runConfig, isDryRun, index, total, allPackageNames, isBuiltInCommand = false) => {
571
+ export const executePackage = async (packageName, packageInfo, commandToRun, runConfig, isDryRun, index, total, allPackageNames, isBuiltInCommand = false, context // PackageExecutionContext - optional for backward compatibility
572
+ ) => {
571
573
  const packageLogger = createPackageLogger(packageName, index + 1, total, isDryRun);
572
574
  const packageDir = packageInfo.path;
573
575
  const logger = getLogger();
@@ -609,6 +611,7 @@ export const executePackage = async (packageName, packageInfo, commandToRun, run
609
611
  }
610
612
  // Track if publish was skipped due to no changes
611
613
  let publishWasSkipped = false;
614
+ let publishSkipReason = 'no-changes';
612
615
  // Track execution timing
613
616
  const executionTimer = new PerformanceTimer(`Package ${packageName} execution`);
614
617
  let executionDuration;
@@ -789,7 +792,20 @@ export const executePackage = async (packageName, packageInfo, commandToRun, run
789
792
  else {
790
793
  commandTimeoutMs = 300000; // 5 minutes default for other commands
791
794
  }
792
- const commandPromise = runWithLogging(effectiveCommand, packageLogger, {}, showOutput, logFilePath);
795
+ // Pass context through environment variables for parallel execution isolation
796
+ const contextEnv = {};
797
+ if (context) {
798
+ contextEnv.KODRDRIV_CONTEXT_PACKAGE_NAME = context.packageName;
799
+ contextEnv.KODRDRIV_CONTEXT_REPOSITORY_URL = context.repositoryUrl;
800
+ contextEnv.KODRDRIV_CONTEXT_REPOSITORY_OWNER = context.repositoryOwner;
801
+ contextEnv.KODRDRIV_CONTEXT_REPOSITORY_NAME = context.repositoryName;
802
+ contextEnv.KODRDRIV_CONTEXT_GIT_REMOTE = context.gitRemote;
803
+ if (runConfig.debug) {
804
+ packageLogger.debug(`Using isolated execution context for ${context.packageName}`);
805
+ packageLogger.debug(` Repository: ${context.repositoryOwner}/${context.repositoryName}`);
806
+ }
807
+ }
808
+ const commandPromise = runWithLogging(effectiveCommand, packageLogger, contextEnv, showOutput, logFilePath);
793
809
  const commandTimeoutPromise = new Promise((_, reject) => {
794
810
  setTimeout(() => reject(new Error(`Command timed out after ${commandTimeoutMs / 60000} minutes`)), commandTimeoutMs);
795
811
  });
@@ -804,6 +820,11 @@ export const executePackage = async (packageName, packageInfo, commandToRun, run
804
820
  (stderr && stderr.includes('KODRDRIV_PUBLISH_SKIPPED')))) {
805
821
  packageLogger.info('Publish skipped for this package; will not record or propagate a version.');
806
822
  publishWasSkipped = true;
823
+ // Parse skip reason if available
824
+ const reasonMatch = (stdout || stderr || '').match(/KODRDRIV_PUBLISH_SKIP_REASON:(\S+)/);
825
+ if (reasonMatch) {
826
+ publishSkipReason = reasonMatch[1];
827
+ }
807
828
  }
808
829
  }
809
830
  catch (error) {
@@ -904,7 +925,8 @@ export const executePackage = async (packageName, packageInfo, commandToRun, run
904
925
  // Show completion status (for publish/commit commands, this supplements the timing message above)
905
926
  if (runConfig.debug || runConfig.verbose) {
906
927
  if (publishWasSkipped) {
907
- packageLogger.info(`⊘ Skipped (no code changes)`);
928
+ const reasonText = publishSkipReason === 'already-published' ? 'already published' : 'no code changes';
929
+ packageLogger.info(`⊘ Skipped (${reasonText})`);
908
930
  }
909
931
  else {
910
932
  packageLogger.info(`✅ Completed successfully`);
@@ -915,7 +937,8 @@ export const executePackage = async (packageName, packageInfo, commandToRun, run
915
937
  // Include timing if available
916
938
  const timeStr = executionDuration !== undefined ? ` (${(executionDuration / 1000).toFixed(1)}s)` : '';
917
939
  if (publishWasSkipped) {
918
- logger.info(`[${index + 1}/${total}] ${packageName}: Skipped (no code changes)`);
940
+ const reasonText = publishSkipReason === 'already-published' ? 'already published' : 'no code changes';
941
+ logger.info(`[${index + 1}/${total}] ${packageName}: ⊘ Skipped (${reasonText})`);
919
942
  }
920
943
  else {
921
944
  logger.info(`[${index + 1}/${total}] ${packageName}: ✅ Completed${timeStr}`);
@@ -925,7 +948,12 @@ export const executePackage = async (packageName, packageInfo, commandToRun, run
925
948
  if (executionDuration === undefined) {
926
949
  executionDuration = executionTimer.end();
927
950
  }
928
- return { success: true, skippedNoChanges: publishWasSkipped, logFile: logFilePath };
951
+ return {
952
+ success: true,
953
+ skippedNoChanges: publishWasSkipped,
954
+ skipReason: publishWasSkipped ? publishSkipReason : undefined,
955
+ logFile: logFilePath
956
+ };
929
957
  }
930
958
  catch (error) {
931
959
  // Record timing even on error
@@ -2011,19 +2039,19 @@ export const execute = async (runConfig) => {
2011
2039
  globalOptions.push('--overrides');
2012
2040
  // Propagate global options with values
2013
2041
  if (runConfig.model)
2014
- globalOptions.push(`--model "${runConfig.model}"`);
2042
+ globalOptions.push(`--model ${escapeShellArg(runConfig.model)}`);
2015
2043
  if (runConfig.configDirectory)
2016
- globalOptions.push(`--config-dir "${runConfig.configDirectory}"`);
2044
+ globalOptions.push(`--config-dir ${escapeShellArg(runConfig.configDirectory)}`);
2017
2045
  if (runConfig.outputDirectory)
2018
- globalOptions.push(`--output-dir "${runConfig.outputDirectory}"`);
2046
+ globalOptions.push(`--output-dir ${escapeShellArg(runConfig.outputDirectory)}`);
2019
2047
  if (runConfig.preferencesDirectory)
2020
- globalOptions.push(`--preferences-dir "${runConfig.preferencesDirectory}"`);
2048
+ globalOptions.push(`--preferences-dir ${escapeShellArg(runConfig.preferencesDirectory)}`);
2021
2049
  // Build the command with global options
2022
2050
  const optionsString = globalOptions.length > 0 ? ` ${globalOptions.join(' ')}` : '';
2023
2051
  // Add package argument for link/unlink/updates commands
2024
2052
  const packageArg = runConfig.tree?.packageArgument;
2025
2053
  const packageArgString = (packageArg && (builtInCommand === 'link' || builtInCommand === 'unlink' || builtInCommand === 'updates'))
2026
- ? ` "${packageArg}"`
2054
+ ? ` ${escapeShellArg(packageArg)}`
2027
2055
  : '';
2028
2056
  // Add command-specific options
2029
2057
  let commandSpecificOptions = '';
@@ -2063,15 +2091,15 @@ export const execute = async (runConfig) => {
2063
2091
  commandSpecificOptions += ` --max-diff-bytes ${runConfig.commit.maxDiffBytes}`;
2064
2092
  }
2065
2093
  if (runConfig.commit?.direction) {
2066
- commandSpecificOptions += ` --direction "${runConfig.commit.direction}"`;
2094
+ commandSpecificOptions += ` --direction ${escapeShellArg(runConfig.commit.direction)}`;
2067
2095
  }
2068
2096
  if (runConfig.commit?.context) {
2069
- commandSpecificOptions += ` --context "${runConfig.commit.context}"`;
2097
+ commandSpecificOptions += ` --context ${escapeShellArg(runConfig.commit.context)}`;
2070
2098
  }
2071
2099
  // Push option can be boolean or string (remote name)
2072
2100
  if (runConfig.commit?.push) {
2073
2101
  if (typeof runConfig.commit.push === 'string') {
2074
- commandSpecificOptions += ` --push "${runConfig.commit.push}"`;
2102
+ commandSpecificOptions += ` --push ${escapeShellArg(runConfig.commit.push)}`;
2075
2103
  }
2076
2104
  else {
2077
2105
  commandSpecificOptions += ' --push';
@@ -2079,7 +2107,7 @@ export const execute = async (runConfig) => {
2079
2107
  }
2080
2108
  // Model-specific options for commit
2081
2109
  if (runConfig.commit?.model) {
2082
- commandSpecificOptions += ` --model "${runConfig.commit.model}"`;
2110
+ commandSpecificOptions += ` --model ${escapeShellArg(runConfig.commit.model)}`;
2083
2111
  }
2084
2112
  if (runConfig.commit?.openaiReasoning) {
2085
2113
  commandSpecificOptions += ` --openai-reasoning ${runConfig.commit.openaiReasoning}`;
@@ -2103,16 +2131,16 @@ export const execute = async (runConfig) => {
2103
2131
  commandSpecificOptions += ' --interactive';
2104
2132
  }
2105
2133
  if (runConfig.release?.from) {
2106
- commandSpecificOptions += ` --from "${runConfig.release.from}"`;
2134
+ commandSpecificOptions += ` --from ${escapeShellArg(runConfig.release.from)}`;
2107
2135
  }
2108
2136
  if (runConfig.release?.to) {
2109
- commandSpecificOptions += ` --to "${runConfig.release.to}"`;
2137
+ commandSpecificOptions += ` --to ${escapeShellArg(runConfig.release.to)}`;
2110
2138
  }
2111
2139
  if (runConfig.release?.focus) {
2112
- commandSpecificOptions += ` --focus "${runConfig.release.focus}"`;
2140
+ commandSpecificOptions += ` --focus ${escapeShellArg(runConfig.release.focus)}`;
2113
2141
  }
2114
2142
  if (runConfig.release?.context) {
2115
- commandSpecificOptions += ` --context "${runConfig.release.context}"`;
2143
+ commandSpecificOptions += ` --context ${escapeShellArg(runConfig.release.context)}`;
2116
2144
  }
2117
2145
  if (runConfig.release?.messageLimit) {
2118
2146
  commandSpecificOptions += ` --message-limit ${runConfig.release.messageLimit}`;
@@ -2128,7 +2156,7 @@ export const execute = async (runConfig) => {
2128
2156
  }
2129
2157
  // Model-specific options for release
2130
2158
  if (runConfig.release?.model) {
2131
- commandSpecificOptions += ` --model "${runConfig.release.model}"`;
2159
+ commandSpecificOptions += ` --model ${escapeShellArg(runConfig.release.model)}`;
2132
2160
  }
2133
2161
  if (runConfig.release?.openaiReasoning) {
2134
2162
  commandSpecificOptions += ` --openai-reasoning ${runConfig.release.openaiReasoning}`;
@@ -2157,7 +2185,7 @@ export const execute = async (runConfig) => {
2157
2185
  }
2158
2186
  // Link/Unlink externals
2159
2187
  if ((builtInCommand === 'link' || builtInCommand === 'unlink') && runConfig.tree?.externals && runConfig.tree.externals.length > 0) {
2160
- commandSpecificOptions += ` --externals ${runConfig.tree.externals.join(' ')}`;
2188
+ commandSpecificOptions += ` --externals ${runConfig.tree.externals.map(e => escapeShellArg(e)).join(' ')}`;
2161
2189
  }
2162
2190
  commandToRun = `kodrdriv ${builtInCommand}${optionsString}${packageArgString}${commandSpecificOptions}`;
2163
2191
  isBuiltInCommand = true;
@@ -2354,7 +2382,7 @@ export const execute = async (runConfig) => {
2354
2382
  // Execute
2355
2383
  const result = await adapter.execute();
2356
2384
  // Format and return result
2357
- const formattedResult = formatParallelResult(result);
2385
+ const formattedResult = formatParallelResult(result, commandToRun);
2358
2386
  return formattedResult;
2359
2387
  }
2360
2388
  // Sequential execution