diff --git a/src/fingerprint.ts b/src/fingerprint.ts index 89b649d99..dd12e49b8 100644 --- a/src/fingerprint.ts +++ b/src/fingerprint.ts @@ -167,9 +167,13 @@ export class Fingerprint { // because we can't know if there was an undeclared input that this script // depends on. allDependenciesAreFullyTracked && - // A no-op script. Can't produce output, so always trackable. + // A no-command script. Doesn't ever do anything itsef, so always fully + // tracked. (script.command === undefined || - // A one-shot script. Trackable if we know both its inputs and outputs. + // A service. Fully tracked if we know its inputs. Can't produce output. + (script.service && script.files !== undefined) || + // A standard script. Fully tracked if we know both its inputs and + // outputs. (script.files !== undefined && script.output !== undefined)); const fingerprint = new Fingerprint(); diff --git a/src/test/service.test.ts b/src/test/service.test.ts index fa2c1db0b..6f31d1ecd 100644 --- a/src/test/service.test.ts +++ b/src/test/service.test.ts @@ -734,4 +734,79 @@ test( }) ); +test( + 'service fingerprint is trackable despite never having outputs', + timeout(async ({rig}) => { + // consumer + // | + // v + // service + + const consumer = await rig.newCommand(); + const service = await rig.newCommand(); + await rig.writeAtomic({ + 'package.json': { + scripts: { + consumer: 'wireit', + service: 'wireit', + }, + wireit: { + consumer: { + command: consumer.command, + dependencies: ['service'], + files: [], + output: [], + }, + service: { + command: service.command, + service: true, + files: ['input'], + }, + }, + }, + }); + + // Run 1. Nothing cached yet. + { + await rig.write('input', '0'); + const wireit = rig.exec('npm run consumer'); + const serviceInv = await service.nextInvocation(); + const consumerInv = await consumer.nextInvocation(); + consumerInv.exit(0); + await serviceInv.closed; + await consumerInv.closed; + const {code} = await wireit.exit; + assert.equal(code, 0); + assert.equal(consumer.numInvocations, 1); + assert.equal(service.numInvocations, 1); + } + + // Run 2. No input change. Consumer output is cached, service never needs to + // start. + { + const wireit = rig.exec('npm run consumer'); + const {code} = await wireit.exit; + assert.equal(code, 0); + assert.equal(consumer.numInvocations, 1); + assert.equal(service.numInvocations, 1); + } + + // Run 3. Service input changed. That affects the service fingerprint and + // transitively affects the consumer fingerprint, so both need to run. + { + await rig.write('input', '1'); + const wireit = rig.exec('npm run consumer'); + const serviceInv = await service.nextInvocation(); + const consumerInv = await consumer.nextInvocation(); + consumerInv.exit(0); + await serviceInv.closed; + await consumerInv.closed; + const {code} = await wireit.exit; + assert.equal(code, 0); + assert.equal(consumer.numInvocations, 2); + assert.equal(service.numInvocations, 2); + } + }) +); + test.run();