Skip to content

Commit 0e28e5e

Browse files
committed
[FEATURE] set up tests to validate imports work for all app
1 parent a28b6b4 commit 0e28e5e

File tree

5 files changed

+193
-4
lines changed

5 files changed

+193
-4
lines changed

.github/workflows/publish-packages.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,12 @@ jobs:
4646
run: pnpm install -r --no-frozen-lockfile
4747
- name: Compile TypeScript
4848
run: pnpm run build
49+
# Pre-publish validation
50+
- name: Validate Package Imports and Dependencies
51+
run: |
52+
node scripts/validate-packages.js
53+
env:
54+
NODE_ENV: production
4955
# See https://pnpm.io/using-changesets
5056
- name: Setup npmrc for pnpm publish
5157
run: echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > .npmrc

components/netlify/actions/get-site/get-site.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ export default {
44
key: "netlify-get-site",
55
name: "Get Site",
66
description: "Get a specified site. [See docs](https://docs.netlify.com/api/get-started/#get-sites)",
7-
version: "0.1.0",
7+
version: "0.1.1",
88
type: "action",
99
props: {
1010
netlify,

components/netlify/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@pipedream/netlify",
3-
"version": "0.4.1",
3+
"version": "0.4.2",
44
"description": "Pipedream Netlify Components",
55
"main": "netlify.app.mjs",
66
"keywords": [

pnpm-lock.yaml

Lines changed: 0 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

scripts/validate-packages.js

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
const fs = require('fs');
2+
const path = require('path');
3+
const { execSync } = require('child_process');
4+
5+
function validatePackages() {
6+
const componentsDir = 'components';
7+
const apps = fs.readdirSync(componentsDir).filter(dir => {
8+
const packagePath = path.join(componentsDir, dir, 'package.json');
9+
return fs.existsSync(packagePath);
10+
});
11+
12+
const results = {
13+
validated: [],
14+
failed: [],
15+
skipped: []
16+
};
17+
18+
console.log(`🔍 Validating ${apps.length} component packages...\n`);
19+
20+
for (const app of apps) {
21+
const packagePath = path.join(componentsDir, app, 'package.json');
22+
let packageJson = null;
23+
let packageName = app; // Use app name as fallback
24+
25+
try {
26+
// Parse package.json
27+
const packageJsonContent = fs.readFileSync(packagePath, 'utf8');
28+
packageJson = JSON.parse(packageJsonContent);
29+
packageName = packageJson.name || `${app} (no name)`;
30+
31+
// Only validate @pipedream/* packages with publishConfig
32+
if (!packageName || !packageName.startsWith('@pipedream/')) {
33+
results.skipped.push({ app, packageName, reason: 'Not a @pipedream package' });
34+
continue;
35+
}
36+
37+
if (!packageJson.publishConfig?.access) {
38+
results.skipped.push({ app, packageName, reason: 'No publishConfig.access' });
39+
continue;
40+
}
41+
42+
console.log(`📦 Validating ${packageName}...`);
43+
44+
// Run all validations
45+
validatePackageJson(packageJson, app);
46+
validateMainFile(packageJson, app);
47+
validateDependencies(packageJson, app);
48+
validateImport(packageName, app, packageJson);
49+
50+
results.validated.push({ app, packageName });
51+
console.log(`✅ ${packageName} - VALID\n`);
52+
53+
} catch (error) {
54+
results.failed.push({
55+
app,
56+
packageName,
57+
error: error.message
58+
});
59+
console.error(`❌ ${app} (${packageName}) - FAILED: ${error.message}\n`);
60+
}
61+
}
62+
63+
// Print summary
64+
printSummary(results);
65+
66+
// Exit with error if any validations failed
67+
if (results.failed.length > 0) {
68+
console.error(`\n💥 Validation failed for ${results.failed.length} packages. See errors above.`);
69+
process.exit(1);
70+
}
71+
72+
console.log(`\n🎉 All ${results.validated.length} packages validated successfully!`);
73+
}
74+
75+
function validatePackageJson(packageJson, app) {
76+
const required = ['name', 'version', 'main'];
77+
78+
for (const field of required) {
79+
if (!packageJson[field]) {
80+
throw new Error(`Missing required field: ${field}`);
81+
}
82+
}
83+
84+
// Validate version format
85+
if (!/^\d+\.\d+\.\d+/.test(packageJson.version)) {
86+
throw new Error(`Invalid version format: ${packageJson.version}`);
87+
}
88+
89+
// Validate publishConfig
90+
if (!packageJson.publishConfig?.access) {
91+
throw new Error('Missing publishConfig.access for public package');
92+
}
93+
}
94+
95+
function validateMainFile(packageJson, app) {
96+
const mainFile = path.join('components', app, packageJson.main);
97+
98+
if (!fs.existsSync(mainFile)) {
99+
throw new Error(`Main file not found: ${packageJson.main}`);
100+
}
101+
102+
// Check if main file has basic export structure
103+
const content = fs.readFileSync(mainFile, 'utf8');
104+
if (!content.includes('export') && !content.includes('module.exports')) {
105+
throw new Error(`Main file ${packageJson.main} has no exports`);
106+
}
107+
}
108+
109+
function validateDependencies(packageJson, app) {
110+
if (!packageJson.dependencies) return;
111+
112+
// Check for common problematic dependencies
113+
const problematic = Object.keys(packageJson.dependencies).filter(dep => {
114+
// Add checks for dependencies that commonly cause issues
115+
return dep.includes('node-gyp') || dep.includes('native');
116+
});
117+
118+
if (problematic.length > 0) {
119+
console.warn(`⚠️ Potentially problematic dependencies: ${problematic.join(', ')}`);
120+
}
121+
122+
// Validate @pipedream/platform version if present
123+
const platformDep = packageJson.dependencies['@pipedream/platform'];
124+
if (platformDep && !platformDep.match(/^[\^~]?\d+\.\d+\.\d+/)) {
125+
throw new Error(`Invalid @pipedream/platform version: ${platformDep}`);
126+
}
127+
}
128+
129+
function validateImport(packageName, app) {
130+
// Create a temporary test file to validate import
131+
const testFile = path.join('components', app, '__import_test__.mjs');
132+
const testContent = `
133+
try {
134+
const pkg = await import("${packageName}");
135+
console.log("Import successful for ${packageName}");
136+
process.exit(0);
137+
} catch (error) {
138+
console.error("Import failed for ${packageName}:", error.message);
139+
process.exit(1);
140+
}`;
141+
142+
try {
143+
fs.writeFileSync(testFile, testContent);
144+
145+
// Run the import test
146+
execSync(`node ${testFile}`, {
147+
stdio: 'pipe',
148+
cwd: process.cwd(),
149+
timeout: 10000 // 10 second timeout
150+
});
151+
152+
} catch (error) {
153+
throw new Error(`Import test failed: ${error.message}`);
154+
} finally {
155+
// Clean up test file
156+
if (fs.existsSync(testFile)) {
157+
fs.unlinkSync(testFile);
158+
}
159+
}
160+
}
161+
162+
function printSummary(results) {
163+
console.log('\n📊 VALIDATION SUMMARY');
164+
console.log('='.repeat(50));
165+
console.log(`✅ Validated: ${results.validated.length}`);
166+
console.log(`❌ Failed: ${results.failed.length}`);
167+
console.log(`⏭️ Skipped: ${results.skipped.length}`);
168+
169+
if (results.failed.length > 0) {
170+
console.log('\n❌ FAILED PACKAGES:');
171+
results.failed.forEach(({ app, packageName, error }) => {
172+
console.log(` • ${packageName} (${app}): ${error}`);
173+
});
174+
}
175+
176+
if (results.validated.length > 0) {
177+
console.log('\n✅ VALIDATED PACKAGES:');
178+
results.validated.forEach(({ packageName }) => {
179+
console.log(` • ${packageName}`);
180+
});
181+
}
182+
}
183+
184+
// Run validation
185+
validatePackages();

0 commit comments

Comments
 (0)