11#!/usr/bin/env node
22
33import { spawnSync } from 'node:child_process'
4+ import { existsSync } from 'node:fs'
45
56class CommandFailedError extends Error {
67 constructor ( step , command , args , exitCode , spawnError ) {
@@ -44,6 +45,30 @@ if (!verbose) {
4445}
4546
4647const supportedModes = new Set ( [ 'changed' , 'all' ] )
48+ const qaSmokeTestPaths = [
49+ './tests/DeferredLoading.Tests.ps1' ,
50+ './tests/losslessToAdaptiveAudio.Tests.ps1' ,
51+ './tests/ProfileMode.Tests.ps1' ,
52+ './tests/Switch-Mirrors.Tests.ps1' ,
53+ './psutils/tests/error.Tests.ps1' ,
54+ './psutils/tests/filesystem.Tests.ps1' ,
55+ './psutils/tests/font.Tests.ps1' ,
56+ './psutils/tests/git.Tests.ps1' ,
57+ './psutils/tests/string.Tests.ps1' ,
58+ './psutils/tests/win.Tests.ps1' ,
59+ './psutils/tests/wrapper.Tests.ps1' ,
60+ ]
61+ const qaExcludedTestPaths = new Set ( [
62+ './psutils/tests/cache.Tests.ps1' ,
63+ './psutils/tests/hardware.Tests.ps1' ,
64+ './psutils/tests/help.Tests.ps1' ,
65+ './psutils/tests/install.Tests.ps1' ,
66+ './psutils/tests/network.Tests.ps1' ,
67+ './psutils/tests/profile_unix.Tests.ps1' ,
68+ './psutils/tests/profile_windows.Tests.ps1' ,
69+ './psutils/tests/proxy.Tests.ps1' ,
70+ './psutils/tests/test.Tests.ps1' ,
71+ ] )
4772
4873if ( ! supportedModes . has ( mode ) ) {
4974 console . error ( `[qa] unsupported mode: ${ mode } ` )
@@ -72,14 +97,14 @@ function runCommand(step, command, args, options = {}) {
7297 }
7398}
7499
75- function runPnpm ( step , pnpmArgs ) {
100+ function runPnpm ( step , pnpmArgs , options = { } ) {
76101 if ( runPnpmWithNode ) {
77- runCommand ( step , process . execPath , [ pnpmExecPath , ...pnpmArgs ] )
102+ runCommand ( step , process . execPath , [ pnpmExecPath , ...pnpmArgs ] , options )
78103 return
79104 }
80105
81106 const command = process . platform === 'win32' ? 'pnpm.cmd' : 'pnpm'
82- runCommand ( step , command , pnpmArgs )
107+ runCommand ( step , command , pnpmArgs , options )
83108}
84109
85110function runCapture ( command , args ) {
@@ -165,6 +190,119 @@ function hasPathChanges(pathspecs, sinceRef) {
165190 } )
166191}
167192
193+ function toRepoPath ( pathValue ) {
194+ return pathValue . replace ( / \\ / g, '/' ) . replace ( / ^ \. \/ / , '' )
195+ }
196+
197+ function toQaTestPath ( pathValue ) {
198+ const prefixed = pathValue . startsWith ( './' ) ? pathValue : `./${ pathValue } `
199+ return prefixed . replace ( / \\ / g, '/' )
200+ }
201+
202+ function shouldIncludeQaTest ( pathValue ) {
203+ const qaPath = toQaTestPath ( pathValue )
204+ if ( qaExcludedTestPaths . has ( qaPath ) ) {
205+ return false
206+ }
207+ const repoPath = toRepoPath ( qaPath )
208+ return existsSync ( repoPath )
209+ }
210+
211+ function collectChangedFiles ( pathspecs , sinceRef ) {
212+ const checks = [ ]
213+
214+ if ( sinceRef ) {
215+ checks . push ( [
216+ 'diff' ,
217+ '--name-only' ,
218+ '--diff-filter=ACMRT' ,
219+ `${ sinceRef } ...HEAD` ,
220+ '--' ,
221+ ...pathspecs ,
222+ ] )
223+ }
224+
225+ checks . push ( [ 'diff' , '--name-only' , '--diff-filter=ACMRT' , '--' , ...pathspecs ] )
226+ checks . push ( [
227+ 'diff' ,
228+ '--name-only' ,
229+ '--diff-filter=ACMRT' ,
230+ '--cached' ,
231+ '--' ,
232+ ...pathspecs ,
233+ ] )
234+ checks . push ( [ 'ls-files' , '--others' , '--exclude-standard' , '--' , ...pathspecs ] )
235+
236+ const changed = new Set ( )
237+
238+ for ( const args of checks ) {
239+ const result = runCapture ( 'git' , args )
240+ if ( result . status !== 0 || result . stdout . length === 0 ) {
241+ continue
242+ }
243+ for ( const line of result . stdout . split ( '\n' ) . filter ( Boolean ) ) {
244+ changed . add ( toRepoPath ( line ) )
245+ }
246+ }
247+
248+ return [ ...changed ]
249+ }
250+
251+ function addQaTestPath ( selected , testPath ) {
252+ const normalized = toQaTestPath ( testPath )
253+ if ( ! shouldIncludeQaTest ( normalized ) ) {
254+ if ( verbose ) {
255+ console . log ( `[qa:verbose] skip qa test path: ${ normalized } ` )
256+ }
257+ return
258+ }
259+ selected . add ( normalized )
260+ }
261+
262+ function resolveQaTestPaths ( modeValue , sinceRef , pathspecs ) {
263+ const selected = new Set ( )
264+ for ( const testPath of qaSmokeTestPaths ) {
265+ addQaTestPath ( selected , testPath )
266+ }
267+
268+ if ( modeValue !== 'changed' ) {
269+ return [ ...selected ]
270+ }
271+
272+ const changedFiles = collectChangedFiles ( pathspecs , sinceRef )
273+ if ( verbose ) {
274+ console . log ( `[qa:verbose] root changed files: ${ changedFiles . length } ` )
275+ }
276+
277+ for ( const changedFile of changedFiles ) {
278+ if ( changedFile . startsWith ( 'tests/' ) && changedFile . endsWith ( '.Tests.ps1' ) ) {
279+ addQaTestPath ( selected , changedFile )
280+ continue
281+ }
282+
283+ if (
284+ changedFile . startsWith ( 'psutils/tests/' ) &&
285+ changedFile . endsWith ( '.Tests.ps1' )
286+ ) {
287+ addQaTestPath ( selected , changedFile )
288+ continue
289+ }
290+
291+ if (
292+ changedFile . startsWith ( 'psutils/modules/' ) &&
293+ changedFile . endsWith ( '.psm1' )
294+ ) {
295+ const fileName = changedFile . split ( '/' ) . pop ( )
296+ const moduleName = fileName ?. replace ( / \. p s m 1 $ / i, '' )
297+ if ( moduleName ) {
298+ addQaTestPath ( selected , `./psutils/tests/${ moduleName } .Tests.ps1` )
299+ }
300+ }
301+ }
302+
303+ return [ ...selected ]
304+ }
305+
168306function runWorkspaceQa ( modeValue , sinceRef ) {
169307 const recursiveArgs = [
170308 '-r' ,
@@ -197,28 +335,43 @@ function runWorkspaceQa(modeValue, sinceRef) {
197335}
198336
199337function runRootPwshQa ( modeValue , sinceRef ) {
200- if ( modeValue === 'all' ) {
201- console . log ( '[qa] run root qa:pwsh (all)' )
202- runPnpm ( 'root-qa-pwsh-all' , [ 'run' , 'qa:pwsh' ] )
203- return
204- }
205-
206338 const pwshPathspecs = [
207339 'scripts/pwsh' ,
208340 'profile' ,
209341 'tests' ,
342+ 'psutils/modules' ,
343+ 'psutils/tests' ,
210344 'PesterConfiguration.ps1' ,
211345 'install.ps1' ,
212346 'Manage-BinScripts.ps1' ,
213347 ]
214348
349+ const qaTestPaths = resolveQaTestPaths ( modeValue , sinceRef , pwshPathspecs )
350+ const qaEnv = { ...process . env }
351+ if ( qaTestPaths . length > 0 ) {
352+ qaEnv . PWSH_TEST_PATH = qaTestPaths . join ( ';' )
353+ }
354+
355+ if ( verbose ) {
356+ console . log ( `[qa:verbose] qa test paths (${ qaTestPaths . length } )` )
357+ for ( const testPath of qaTestPaths ) {
358+ console . log ( `[qa:verbose] ${ testPath } ` )
359+ }
360+ }
361+
362+ if ( modeValue === 'all' ) {
363+ console . log ( '[qa] run root qa:pwsh (all)' )
364+ runPnpm ( 'root-qa-pwsh-all' , [ 'run' , 'qa:pwsh' ] , { env : qaEnv } )
365+ return
366+ }
367+
215368 if ( ! hasPathChanges ( pwshPathspecs , sinceRef ) ) {
216369 console . log ( '[qa] skip root qa:pwsh (no changes)' )
217370 return
218371 }
219372
220373 console . log ( '[qa] run root qa:pwsh (changed)' )
221- runPnpm ( 'root-qa-pwsh-changed' , [ 'run' , 'qa:pwsh' ] )
374+ runPnpm ( 'root-qa-pwsh-changed' , [ 'run' , 'qa:pwsh' ] , { env : qaEnv } )
222375}
223376
224377const sinceRef = mode === 'changed' ? resolveSinceRef ( ) : null
@@ -252,4 +405,4 @@ try {
252405 }
253406
254407 throw error
255- }
408+ }
0 commit comments