Skip to content

Commit f1ecf13

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

File tree

5 files changed

+218
-4
lines changed

5 files changed

+218
-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: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
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, packageJson) {
130+
const mainFile = path.resolve('components', app, packageJson.main);
131+
132+
// First, check if file exists
133+
if (!fs.existsSync(mainFile)) {
134+
throw new Error(`Main file not found: ${packageJson.main}`);
135+
}
136+
137+
// Syntax check
138+
try {
139+
execSync(`node --check ${mainFile}`, {
140+
stdio: 'pipe',
141+
timeout: 5000
142+
});
143+
} catch (error) {
144+
throw new Error(`Syntax error in main file: ${error.message}`);
145+
}
146+
147+
// Import test using file path instead of package name
148+
const testFile = path.join('components', app, '__import_test__.mjs');
149+
const testContent = `
150+
try {
151+
// Import from local file path using file:// protocol
152+
const pkg = await import("file://${mainFile}");
153+
154+
if (!pkg.default) {
155+
throw new Error("No default export found");
156+
}
157+
158+
const component = pkg.default;
159+
if (typeof component !== 'object') {
160+
throw new Error("Default export is not an object");
161+
}
162+
163+
console.log("✓ Import successful for ${packageName}");
164+
process.exit(0);
165+
166+
} catch (error) {
167+
console.error("Import failed for ${packageName}:", error.message);
168+
process.exit(1);
169+
}`;
170+
171+
try {
172+
fs.writeFileSync(testFile, testContent);
173+
execSync(`node ${testFile}`, {
174+
stdio: 'pipe',
175+
cwd: process.cwd(),
176+
timeout: 10000
177+
});
178+
} catch (error) {
179+
throw new Error(`Import test failed: ${error.message}`);
180+
} finally {
181+
if (fs.existsSync(testFile)) {
182+
fs.unlinkSync(testFile);
183+
}
184+
}
185+
}
186+
187+
function printSummary(results) {
188+
console.log('\n📊 VALIDATION SUMMARY');
189+
console.log('='.repeat(50));
190+
console.log(`✅ Validated: ${results.validated.length}`);
191+
console.log(`❌ Failed: ${results.failed.length}`);
192+
console.log(`⏭️ Skipped: ${results.skipped.length}`);
193+
194+
if (results.failed.length > 0) {
195+
console.log('\n❌ FAILED PACKAGES:');
196+
results.failed.forEach(({ app, packageName, error }) => {
197+
console.log(` • ${packageName} (${app}): ${error}`);
198+
});
199+
}
200+
201+
if (results.validated.length > 0) {
202+
console.log('\n✅ VALIDATED PACKAGES:');
203+
results.validated.forEach(({ packageName }) => {
204+
console.log(` • ${packageName}`);
205+
});
206+
}
207+
}
208+
209+
// Run validation
210+
validatePackages();

0 commit comments

Comments
 (0)