@factorialco/shadowdog
Version:
<img src="https://raw.githubusercontent.com/factorialco/shadowdog/refs/heads/main/logo.png" alt="drawing" width="100"/>
233 lines (232 loc) • 11.2 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
const vitest_1 = require("vitest");
const generate_1 = require("./generate");
const events_1 = require("./events");
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const os = __importStar(require("os"));
// Helper function to create a minimal watcher config
const createWatcher = (overrides = {}) => ({
enabled: true,
files: [],
environment: [],
ignored: [],
commands: [],
...overrides,
});
// Helper function to create a minimal config
const createConfig = (overrides = {}) => ({
debounceTime: 100,
plugins: [],
defaultIgnoredFiles: [],
watchers: [],
...overrides,
});
(0, vitest_1.describe)('generate', () => {
let testDir;
(0, vitest_1.beforeEach)(async () => {
testDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'shadowdog-test-'));
process.chdir(testDir);
});
(0, vitest_1.afterEach)(async () => {
if (testDir && fs.existsSync(testDir)) {
await fs.promises.rm(testDir, { recursive: true, force: true });
}
});
(0, vitest_1.describe)('artifact verification', () => {
(0, vitest_1.it)('should wait for artifacts to be created and readable', async () => {
const config = createConfig({
watchers: [
createWatcher({
files: ['input.txt'],
commands: [
{
command: "node -e \"setTimeout(() => require('fs').writeFileSync('output.txt', 'delayed content'), 200)\"",
workingDirectory: '',
tags: [],
artifacts: [{ output: 'output.txt' }],
},
],
}),
],
});
// Create input file
await fs.promises.writeFile('input.txt', 'test input');
await (0, generate_1.generate)(config, new events_1.ShadowdogEventEmitter(), { continueOnError: false });
// Verify the artifact was created
(0, vitest_1.expect)(fs.existsSync('output.txt')).toBe(true);
const content = await fs.promises.readFile('output.txt', 'utf-8');
(0, vitest_1.expect)(content).toBe('delayed content');
}, 10000);
(0, vitest_1.it)('should throw error if artifact is not created after max retries', async () => {
// Set a very low retry count for this test
const originalEnv = process.env.SHADOWDOG_ARTIFACT_WAIT_MAX_RETRIES;
process.env.SHADOWDOG_ARTIFACT_WAIT_MAX_RETRIES = '5';
try {
const config = createConfig({
watchers: [
createWatcher({
files: ['input.txt'],
commands: [
{
command: 'node -e "console.log(\'Command completed but no artifact created\')"',
workingDirectory: '',
tags: [],
artifacts: [{ output: 'output.txt' }],
},
],
}),
],
});
// Create input file
await fs.promises.writeFile('input.txt', 'test input');
await (0, vitest_1.expect)((0, generate_1.generate)(config, new events_1.ShadowdogEventEmitter(), { continueOnError: false })).rejects.toThrow(/Artifact 'output\.txt' was not created or is not readable after task completion/);
}
finally {
if (originalEnv !== undefined) {
process.env.SHADOWDOG_ARTIFACT_WAIT_MAX_RETRIES = originalEnv;
}
else {
delete process.env.SHADOWDOG_ARTIFACT_WAIT_MAX_RETRIES;
}
}
}, 10000);
(0, vitest_1.it)('should handle dependency chains correctly with artifact verification', async () => {
// Create source file
await fs.promises.writeFile('source.rb', 'permissions updated');
const config = createConfig({
watchers: [
createWatcher({
files: ['source.rb'],
commands: [
{
command: "node -e \"const fs = require('fs'); const content = fs.readFileSync('source.rb', 'utf-8'); fs.writeFileSync('intermediate.json', 'generated from ' + content.trim())\"",
workingDirectory: '',
tags: [],
artifacts: [{ output: 'intermediate.json' }],
},
],
}),
],
});
await (0, generate_1.generate)(config, new events_1.ShadowdogEventEmitter(), { continueOnError: false });
// Verify the first artifact was created
(0, vitest_1.expect)(fs.existsSync('intermediate.json')).toBe(true);
const intermediateContent = await fs.promises.readFile('intermediate.json', 'utf-8');
(0, vitest_1.expect)(intermediateContent).toBe('generated from permissions updated');
// Now test that a dependent task can run after the first one completes
const config2 = createConfig({
watchers: [
createWatcher({
files: ['intermediate.json'],
commands: [
{
command: "node -e \"const fs = require('fs'); const content = fs.readFileSync('intermediate.json', 'utf-8'); fs.writeFileSync('final.graphql', 'schema from ' + content)\"",
workingDirectory: '',
tags: [],
artifacts: [{ output: 'final.graphql' }],
},
],
}),
],
});
await (0, generate_1.generate)(config2, new events_1.ShadowdogEventEmitter(), { continueOnError: false });
// Verify the final artifact was created
(0, vitest_1.expect)(fs.existsSync('final.graphql')).toBe(true);
const finalContent = await fs.promises.readFile('final.graphql', 'utf-8');
(0, vitest_1.expect)(finalContent).toBe('schema from generated from permissions updated');
}, 10000);
(0, vitest_1.it)('should not fail if artifact does not exist initially', async () => {
const config = createConfig({
watchers: [
createWatcher({
files: ['input.txt'],
commands: [
{
command: "node -e \"require('fs').writeFileSync('output.txt', 'content')\"",
workingDirectory: '',
tags: [],
artifacts: [{ output: 'output.txt' }],
},
],
}),
],
});
// Create input file but no existing artifact
await fs.promises.writeFile('input.txt', 'test input');
// Should not throw an error
await (0, vitest_1.expect)((0, generate_1.generate)(config, new events_1.ShadowdogEventEmitter(), { continueOnError: false })).resolves.not.toThrow();
// Verify the artifact was created
(0, vitest_1.expect)(fs.existsSync('output.txt')).toBe(true);
const content = await fs.promises.readFile('output.txt', 'utf-8');
(0, vitest_1.expect)(content).toBe('content');
}, 10000);
(0, vitest_1.it)('should reject empty files during verification', async () => {
// Set a very low retry count for this test
const originalEnv = process.env.SHADOWDOG_ARTIFACT_WAIT_MAX_RETRIES;
process.env.SHADOWDOG_ARTIFACT_WAIT_MAX_RETRIES = '5';
try {
const config = createConfig({
watchers: [
createWatcher({
files: ['input.txt'],
commands: [
{
command: "node -e \"require('fs').writeFileSync('empty-output.txt', '')\"",
workingDirectory: '',
tags: [],
artifacts: [{ output: 'empty-output.txt' }],
},
],
}),
],
});
// Create input file
await fs.promises.writeFile('input.txt', 'test input');
await (0, vitest_1.expect)((0, generate_1.generate)(config, new events_1.ShadowdogEventEmitter(), { continueOnError: false })).rejects.toThrow(/Artifact 'empty-output\.txt' was not created or is not readable after task completion/);
}
finally {
if (originalEnv !== undefined) {
process.env.SHADOWDOG_ARTIFACT_WAIT_MAX_RETRIES = originalEnv;
}
else {
delete process.env.SHADOWDOG_ARTIFACT_WAIT_MAX_RETRIES;
}
}
}, 10000);
});
});