Skip to content

Commit a7cee50

Browse files
committed
feat: first seemingly passing integrity test
1 parent 83f0756 commit a7cee50

File tree

3 files changed

+329
-242
lines changed

3 files changed

+329
-242
lines changed

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
"format": "npm run lint:js -- --fix --quiet && npm run format:prettier -- --write",
3535
"format:prettier": "prettier \"{{packages,scripts}/**/,}*.{js,jsx,ts,tsx,css}\"",
3636
"prepare": "husky install",
37-
"check-package-integrity": "node scripts/check-package-integrity.js"
37+
"check-package-integrity": "rimraf dev-test-npm && node scripts/check-package-integrity.cjs"
3838
},
3939
"browserslist": [
4040
"last 2 Chrome versions",
@@ -179,4 +179,4 @@
179179
"react-redux": "^7.2.0"
180180
},
181181
"version": "0.0.0"
182-
}
182+
}
Lines changed: 327 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,327 @@
1+
const { spawnSync } = require('child_process');
2+
const fs = require('fs');
3+
const path = require('path');
4+
5+
const TEST_PROJECT_PATH = path.resolve(__dirname, '../dev-test-npm');
6+
const rootPath = path.resolve(__dirname, '..');
7+
const VITE_PID_FILE = path.join(TEST_PROJECT_PATH, '.vite-server.pid');
8+
9+
function run(command, options = {}) {
10+
console.log(`Running: ${command}`);
11+
const result = spawnSync(command, { shell: true, stdio: 'inherit', ...options });
12+
if (result.status !== 0) {
13+
throw new Error(`Command failed: ${command}`);
14+
}
15+
}
16+
17+
function killViteServer() {
18+
// Kill any previous Vite server from a failed run
19+
if (fs.existsSync(VITE_PID_FILE)) {
20+
try {
21+
const pid = fs.readFileSync(VITE_PID_FILE, 'utf8').trim();
22+
console.log(`Killing previous Vite server (PID: ${pid})...`);
23+
if (process.platform === 'win32') {
24+
spawnSync('taskkill', ['/F', '/PID', pid, '/T'], { shell: true, stdio: 'ignore' });
25+
} else {
26+
spawnSync('kill', ['-9', pid], { stdio: 'ignore' });
27+
}
28+
fs.unlinkSync(VITE_PID_FILE);
29+
} catch (err) {
30+
// Ignore errors, process may already be dead
31+
}
32+
}
33+
}
34+
35+
async function waitForServer(url, timeout = 30000) {
36+
const startTime = Date.now();
37+
while (Date.now() - startTime < timeout) {
38+
try {
39+
const response = await fetch(url);
40+
if (response.ok) return true;
41+
} catch (e) {
42+
// Server not ready yet
43+
}
44+
await new Promise(resolve => setTimeout(resolve, 100));
45+
}
46+
throw new Error(`Server at ${url} did not start within ${timeout}ms`);
47+
}
48+
49+
(async () => {
50+
try {
51+
// Kill any lingering Vite server from previous run
52+
killViteServer();
53+
// 0. Ensure monorepo dependencies are installed
54+
console.log('\n=== Installing monorepo dependencies ===');
55+
//run('npm install', { cwd: rootPath });
56+
57+
// 1. Build all packages in the monorepo (this builds dependencies in correct order)
58+
console.log('\n=== Building all packages ===');
59+
//run('npm run build', { cwd: rootPath });
60+
61+
// 2. Verify decap-cms-app build outputs exist
62+
const appPackagePath = path.join(rootPath, 'packages', 'decap-cms-app');
63+
const mainFile = path.join(appPackagePath, 'dist', 'decap-cms-app.js');
64+
const esmFile = path.join(appPackagePath, 'dist', 'esm', 'index.js');
65+
66+
console.log('\n=== Checking build outputs ===');
67+
console.log('Main file exists:', fs.existsSync(mainFile));
68+
console.log('ESM file exists:', fs.existsSync(esmFile));
69+
70+
if (!fs.existsSync(mainFile)) {
71+
throw new Error(`Build failed: ${mainFile} was not created`);
72+
}
73+
74+
// 3. Create a fresh test project
75+
if (fs.existsSync(TEST_PROJECT_PATH)) {
76+
fs.rmSync(TEST_PROJECT_PATH, { recursive: true, force: true });
77+
}
78+
fs.mkdirSync(TEST_PROJECT_PATH, { recursive: true });
79+
80+
// 4. Initialize package.json WITHOUT "type": "module" for the main script
81+
const packageJson = {
82+
name: 'decap-cms-integrity-test',
83+
version: '1.0.0',
84+
private: true
85+
};
86+
fs.writeFileSync(
87+
path.join(TEST_PROJECT_PATH, 'package.json'),
88+
JSON.stringify(packageJson, null, 2)
89+
);
90+
91+
// 5. Read React version from root package.json
92+
const rootPackageJson = JSON.parse(fs.readFileSync(path.join(rootPath, 'package.json'), 'utf8'));
93+
const reactVersion = rootPackageJson.devDependencies.react || '^19.1.0';
94+
const reactDomVersion = rootPackageJson.devDependencies['react-dom'] || '^19.1.0';
95+
96+
// 6. Install decap-cms-app and its dependencies
97+
console.log('\n=== Installing test dependencies ===');
98+
console.log(`Using React version: ${reactVersion}, React DOM version: ${reactDomVersion}`);
99+
100+
// Install decap-cms-app from the built package (using npm pack + install)
101+
console.log('Packing decap-cms-app...');
102+
run('npm pack', { cwd: appPackagePath });
103+
104+
const packageTarball = fs.readdirSync(appPackagePath).find(f => f.endsWith('.tgz'));
105+
if (!packageTarball) {
106+
throw new Error('Failed to create package tarball');
107+
}
108+
109+
const tarballPath = path.join(appPackagePath, packageTarball);
110+
console.log(`Installing from ${tarballPath}...`);
111+
112+
run(`npm install vite playwright "${tarballPath}"`, { cwd: TEST_PROJECT_PATH });
113+
114+
// 7. Install Playwright browsers if needed
115+
console.log('\n=== Setting up Playwright ===');
116+
run('npx playwright install chromium', { cwd: TEST_PROJECT_PATH });
117+
118+
// 8. Create index.html
119+
const htmlFilePath = path.join(TEST_PROJECT_PATH, 'index.html');
120+
fs.writeFileSync(
121+
htmlFilePath,
122+
`<!DOCTYPE html>
123+
<html>
124+
<head>
125+
<meta charset="UTF-8">
126+
<title>Decap CMS Integrity Test</title>
127+
</head>
128+
<body>
129+
<div id="root"></div>
130+
<div id="test-results"></div>
131+
<script type="module" src="/main.js"></script>
132+
</body>
133+
</html>`
134+
);
135+
136+
// 8a. Create minimal config.yml to avoid warnings
137+
const publicDir = path.join(TEST_PROJECT_PATH, 'public');
138+
fs.mkdirSync(publicDir, { recursive: true });
139+
const configYmlPath = path.join(publicDir, 'config.yml');
140+
fs.writeFileSync(
141+
configYmlPath,
142+
`backend:
143+
name: test-repo
144+
145+
media_folder: "static/images"
146+
public_folder: "/images"
147+
148+
collections:
149+
- name: "test"
150+
label: "Test"
151+
folder: "content/test"
152+
create: true
153+
fields:
154+
- { label: "Title", name: "title", widget: "string" }
155+
`
156+
);
157+
158+
// 9. Create main.js with EXACT documentation pattern
159+
const mainJsPath = path.join(TEST_PROJECT_PATH, 'main.js');
160+
fs.writeFileSync(
161+
mainJsPath,
162+
`// Test the exact usage pattern from the documentation:
163+
// npm install decap-cms-app --save
164+
import CMS from "decap-cms-app";
165+
// Initialize the CMS object
166+
CMS.init();
167+
// Now the registry is available via the CMS object.
168+
const MyTemplate = {};
169+
CMS.registerPreviewTemplate("my-template", MyTemplate);
170+
171+
// Test verification
172+
const results = document.getElementById('test-results');
173+
const log = (msg) => {
174+
console.log(msg);
175+
const p = document.createElement('p');
176+
p.textContent = msg;
177+
results.appendChild(p);
178+
};
179+
180+
try {
181+
log('=== Testing decap-cms-app npm package ===');
182+
log('✓ import CMS from "decap-cms-app" - SUCCESS');
183+
log('✓ CMS.init() - SUCCESS');
184+
log('✓ CMS.registerPreviewTemplate() - SUCCESS');
185+
log('TEST_PASSED');
186+
} catch (error) {
187+
log('✗ Test failed: ' + error.message);
188+
log('TEST_FAILED');
189+
}
190+
`
191+
);
192+
193+
// 10. Create vite.config.js
194+
const viteConfigPath = path.join(TEST_PROJECT_PATH, 'vite.config.js');
195+
fs.writeFileSync(
196+
viteConfigPath,
197+
`export default {
198+
server: {
199+
port: 8765
200+
}
201+
};
202+
`
203+
);
204+
205+
// 11. Create Playwright test script
206+
const playwrightTestPath = path.join(TEST_PROJECT_PATH, 'run-test.js');
207+
fs.writeFileSync(
208+
playwrightTestPath,
209+
`import { chromium } from 'playwright';
210+
211+
(async () => {
212+
const browser = await chromium.launch({ headless: true });
213+
const page = await browser.newPage();
214+
215+
// Capture console logs and errors
216+
page.on('console', msg => {
217+
const type = msg.type();
218+
const text = msg.text();
219+
if (type === 'error') {
220+
console.error('[Browser Error]', text);
221+
} else {
222+
console.log('[Browser]', text);
223+
}
224+
});
225+
page.on('pageerror', error => console.error('[Browser PageError]', error.message));
226+
227+
try {
228+
await page.goto('http://localhost:8765', { waitUntil: 'networkidle', timeout: 30000 });
229+
230+
await page.waitForFunction(() => {
231+
const results = document.getElementById('test-results');
232+
const text = results ? results.textContent : '';
233+
return text.includes('TEST_PASSED') || text.includes('TEST_FAILED');
234+
}, { timeout: 30000 });
235+
236+
const results = await page.textContent('#test-results');
237+
await browser.close();
238+
239+
if (results.includes('TEST_PASSED')) {
240+
console.log('\\n✓ Package integrity verified');
241+
console.log('\\nPackage works exactly as documented:');
242+
console.log(' npm install decap-cms-app --save');
243+
console.log(' import CMS from "decap-cms-app";');
244+
console.log(' CMS.init();');
245+
console.log(' CMS.registerPreviewTemplate("my-template", MyTemplate);');
246+
process.exit(0);
247+
} else {
248+
console.error('\\n✗ Test failed');
249+
process.exit(1);
250+
}
251+
} catch (error) {
252+
console.error('\\n✗ Test error:', error.message);
253+
await browser.close();
254+
process.exit(1);
255+
}
256+
})();`
257+
);
258+
259+
// 12. Start Vite dev server in background and run test
260+
console.log('\n=== Starting Vite dev server and running test ===');
261+
262+
const viteProcess = require('child_process').spawn('npx', ['vite'], {
263+
cwd: TEST_PROJECT_PATH,
264+
shell: true,
265+
stdio: 'pipe'
266+
});
267+
268+
// Save PID for cleanup
269+
fs.writeFileSync(VITE_PID_FILE, viteProcess.pid.toString());
270+
271+
// Ensure cleanup on exit
272+
const cleanup = () => {
273+
if (viteProcess && !viteProcess.killed) {
274+
if (process.platform === 'win32') {
275+
// On Windows, kill the entire process tree
276+
spawnSync('taskkill', ['/F', '/PID', viteProcess.pid.toString(), '/T'], {
277+
shell: true,
278+
stdio: 'ignore'
279+
});
280+
} else {
281+
viteProcess.kill('SIGTERM');
282+
setTimeout(() => {
283+
if (!viteProcess.killed) viteProcess.kill('SIGKILL');
284+
}, 1000);
285+
}
286+
}
287+
if (fs.existsSync(VITE_PID_FILE)) {
288+
fs.unlinkSync(VITE_PID_FILE);
289+
}
290+
};
291+
process.on('exit', cleanup);
292+
process.on('SIGINT', () => {
293+
cleanup();
294+
process.exit(130);
295+
});
296+
process.on('SIGTERM', () => {
297+
cleanup();
298+
process.exit(143);
299+
});
300+
301+
// Wait for Vite to start
302+
await new Promise((resolve, reject) => {
303+
const timeout = setTimeout(() => reject(new Error('Vite server timeout')), 30000);
304+
viteProcess.stdout.on('data', (data) => {
305+
const output = data.toString();
306+
process.stdout.write(output);
307+
if (output.includes('Local:') || output.includes('localhost:8765') || output.includes('ready in')) {
308+
clearTimeout(timeout);
309+
setTimeout(resolve, 1000); // Give it 1 more second
310+
}
311+
});
312+
viteProcess.stderr.on('data', (data) => process.stderr.write(data.toString()));
313+
});
314+
315+
try {
316+
run('node run-test.js', { cwd: TEST_PROJECT_PATH });
317+
console.log('\n✓ Integrity check completed successfully!');
318+
} finally {
319+
cleanup();
320+
}
321+
322+
process.exit(0);
323+
} catch (err) {
324+
console.error('\n✗ Integrity check failed:', err.message);
325+
process.exit(1);
326+
}
327+
})();

0 commit comments

Comments
 (0)