From 5818d08c8db81203d22058c532f3f6223c4c3dce Mon Sep 17 00:00:00 2001 From: hip3r Date: Wed, 15 Oct 2025 22:38:36 +0200 Subject: [PATCH 1/4] feat: npm integrity script --- .gitignore | 1 + package.json | 5 ++-- scripts/check-package-integrity.sh | 37 ++++++++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 scripts/check-package-integrity.sh diff --git a/.gitignore b/.gitignore index 01ce5e521df5..962480901d79 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,4 @@ coverage/ .temp/ storybook-static/ .nx +dev-test-npm/ diff --git a/package.json b/package.json index 9f27667a3a17..9c4829e787fa 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,8 @@ "lint:format": "prettier \"{{packages,scripts}/**/,}*.{js,jsx,ts,tsx,css}\" --list-different", "format": "npm run lint:js -- --fix --quiet && npm run format:prettier -- --write", "format:prettier": "prettier \"{{packages,scripts}/**/,}*.{js,jsx,ts,tsx,css}\"", - "prepare": "husky install" + "prepare": "husky install", + "check-package-integrity": "bash scripts/check-package-integrity.sh" }, "browserslist": [ "last 2 Chrome versions", @@ -178,4 +179,4 @@ "react-redux": "^7.2.0" }, "version": "0.0.0" -} +} \ No newline at end of file diff --git a/scripts/check-package-integrity.sh b/scripts/check-package-integrity.sh new file mode 100644 index 000000000000..43f26d938a75 --- /dev/null +++ b/scripts/check-package-integrity.sh @@ -0,0 +1,37 @@ +#!/bin/bash +set -e + +export NVM_DIR="$HOME/.nvm" +[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" + +nvm use 18 +echo "Node version in script: $(node -v)" +echo "Node path in script: $(which node)" +echo "npm version in script: $(npm -v)" +echo "npm path in script: $(which npm)" + +TEST_PROJECT_PATH="./dev-test-npm/package-integrity-check" + +# 1. Clean and build all packages +npm run clean +npm run build + +# 2. Create a fresh test project +rm -rf "$TEST_PROJECT_PATH" +mkdir -p "$TEST_PROJECT_PATH" +cd "$TEST_PROJECT_PATH" +npm init -y + +# 3. Pack all packages and install them +cd ../../ +./scripts/pack-and-install.sh "$TEST_PROJECT_PATH" + +# 4. Run smoke tests (example: import main exports) +cd "$TEST_PROJECT_PATH" +echo "console.log('Integrity check: importing DecapCmsCore...'); require('decap-cms-core');" > smoke-test.js +node smoke-test.js + +# 5. Optionally, run unit/e2e tests +# npm test + +echo "Integrity check completed successfully!" From 71fdf08296c38652cda730a88b24627af4d6d667 Mon Sep 17 00:00:00 2001 From: hip3r Date: Wed, 22 Oct 2025 10:41:41 +0200 Subject: [PATCH 2/4] feat: integrity check with playwright wip --- .gitignore | 1 + package.json | 4 +- scripts/check-package-integrity.js | 193 +++++++++++++++++++++++++++++ scripts/check-package-integrity.sh | 37 ------ 4 files changed, 196 insertions(+), 39 deletions(-) create mode 100644 scripts/check-package-integrity.js delete mode 100644 scripts/check-package-integrity.sh diff --git a/.gitignore b/.gitignore index 962480901d79..ed938f639b57 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,4 @@ coverage/ storybook-static/ .nx dev-test-npm/ +packages/*/*.tgz diff --git a/package.json b/package.json index 9c4829e787fa..90824a744dd0 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "start": "npm run develop", "develop": "nx run-many -t develop --parallel=999 --output-style=stream --exclude=decap-server", "build": "npm run build:esm && nx run-many -t build", - "build:esm": "nx run-many -t build:esm", + "build:esm": "nx run-many -t build:esm --output-style=stream", "build:demo": "npm run build && ncp packages/decap-cms/dist dev-test/dist/", "build-preview": "npm run build && nx run decap-cms:build-preview --output-style=stream", "type-check": "tsc --noEmit", @@ -34,7 +34,7 @@ "format": "npm run lint:js -- --fix --quiet && npm run format:prettier -- --write", "format:prettier": "prettier \"{{packages,scripts}/**/,}*.{js,jsx,ts,tsx,css}\"", "prepare": "husky install", - "check-package-integrity": "bash scripts/check-package-integrity.sh" + "check-package-integrity": "node scripts/check-package-integrity.js" }, "browserslist": [ "last 2 Chrome versions", diff --git a/scripts/check-package-integrity.js b/scripts/check-package-integrity.js new file mode 100644 index 000000000000..61f2202f17d9 --- /dev/null +++ b/scripts/check-package-integrity.js @@ -0,0 +1,193 @@ +const { execSync, spawnSync } = require('child_process'); +const fs = require('fs'); +const path = require('path'); + +const TEST_PROJECT_PATH = path.resolve(__dirname, '../dev-test-npm'); +const rootPath = path.resolve(__dirname, '..'); + +function run(command, options = {}) { + console.log(`Running: ${command}`); + const result = spawnSync(command, { shell: true, stdio: 'inherit', ...options }); + if (result.status !== 0) { + throw new Error(`Command failed: ${command}`); + } +} + +try { + // 0. Ensure monorepo dependencies are installed + console.log('\n=== Installing monorepo dependencies ==='); + run('npm install', { cwd: rootPath }); + + // 1. Build all packages in the monorepo (this builds dependencies in correct order) + console.log('\n=== Building all packages ==='); + run('npm run build', { cwd: rootPath }); + + // 2. Force build decap-cms-app specifically (bypass nx cache) + console.log('\n=== Force building decap-cms-app ==='); + const appPackagePath = path.join(rootPath, 'packages', 'decap-cms-app'); + run('npx nx reset', { cwd: rootPath }); // Clear nx cache + run('npx nx run decap-cms-app:build --skip-nx-cache', { cwd: rootPath }); + + // 3. Verify decap-cms-app build outputs exist + const mainFile = path.join(appPackagePath, 'dist', 'decap-cms-app.js'); + const esmFile = path.join(appPackagePath, 'dist', 'esm', 'index.js'); + + console.log('\n=== Checking build outputs ==='); + console.log('Main file exists:', fs.existsSync(mainFile)); + console.log('ESM file exists:', fs.existsSync(esmFile)); + + if (!fs.existsSync(mainFile)) { + throw new Error(`Build failed: ${mainFile} was not created`); + } + + // 4. Create a fresh test project + if (fs.existsSync(TEST_PROJECT_PATH)) { + fs.rmSync(TEST_PROJECT_PATH, { recursive: true, force: true }); + } + fs.mkdirSync(TEST_PROJECT_PATH, { recursive: true }); + + // 5. Initialize package.json + const packageJson = { + name: 'decap-cms-integrity-test', + version: '1.0.0', + type: 'module', + private: true + }; + fs.writeFileSync( + path.join(TEST_PROJECT_PATH, 'package.json'), + JSON.stringify(packageJson, null, 2) + ); + + // 6. Install dependencies including Playwright + console.log('\n=== Installing test dependencies ==='); + run('npm install react@^19.1.0 react-dom@^19.1.0 playwright --save-dev', { cwd: TEST_PROJECT_PATH }); + + // 6.1. Install Playwright browsers + console.log('\n=== Installing Playwright browsers ==='); + run('npx playwright install chromium', { cwd: TEST_PROJECT_PATH }); + + // 7. Link decap-cms-app + run('npm link', { cwd: appPackagePath }); + run('npm link decap-cms-app', { cwd: TEST_PROJECT_PATH }); + + // 8. Create HTML test page + const testHtmlPath = path.join(TEST_PROJECT_PATH, 'test.html'); + fs.writeFileSync( + testHtmlPath, + ` + + + + Decap CMS Smoke Test + + +
+ + + + ` + ); + + // 9. Create Playwright test + const playwrightTestPath = path.join(TEST_PROJECT_PATH, 'smoke-test.mjs'); + fs.writeFileSync( + playwrightTestPath, + `import { chromium } from 'playwright'; + import { createServer } from 'http'; + import { readFileSync } from 'fs'; + import { join, extname } from 'path'; + import { fileURLToPath } from 'url'; + import { dirname } from 'path'; + + const __filename = fileURLToPath(import.meta.url); + const __dirname = dirname(__filename); + + // Simple HTTP server to serve files + const server = createServer((req, res) => { + const filePath = join(__dirname, req.url === '/' ? 'test.html' : req.url.slice(1)); + + const mimeTypes = { + '.html': 'text/html', + '.js': 'application/javascript', + '.mjs': 'application/javascript' + }; + + const ext = extname(filePath); + const contentType = mimeTypes[ext] || 'text/plain'; + + try { + const content = readFileSync(filePath); + res.writeHead(200, { 'Content-Type': contentType }); + res.end(content); + } catch (err) { + res.writeHead(404); + res.end('Not found'); + } + }); + + server.listen(0, async () => { + const port = server.address().port; + console.log(\`Test server running on http://localhost:\${port}\`); + + const browser = await chromium.launch(); + const page = await browser.newPage(); + + // Listen for console messages + page.on('console', msg => console.log('Browser:', msg.text())); + + // Load the test page + await page.goto(\`http://localhost:\${port}/test.html\`); + + // Wait a bit for the module to load + await page.waitForTimeout(2000); + + // Check test result + const result = await page.evaluate(() => window.testResult); + + await browser.close(); + server.close(); + + if (result && result.success) { + console.log('✓ Integrity check: decap-cms-app loaded successfully'); + console.log('✓', result.message); + console.log('✓ Smoke test passed'); + process.exit(0); + } else { + console.error('✗ Smoke test failed:', result?.error || 'Unknown error'); + console.error(result); + process.exit(1); + } + }); + ` + ); + + // 10. Run Playwright test + console.log('\n=== Running smoke test in browser ==='); + run('node smoke-test.mjs', { cwd: TEST_PROJECT_PATH }); + + console.log('\n✓ Integrity check completed successfully!'); + process.exit(0); +} catch (err) { + console.error('\n✗ Integrity check failed:', err.message); + process.exit(1); +} diff --git a/scripts/check-package-integrity.sh b/scripts/check-package-integrity.sh deleted file mode 100644 index 43f26d938a75..000000000000 --- a/scripts/check-package-integrity.sh +++ /dev/null @@ -1,37 +0,0 @@ -#!/bin/bash -set -e - -export NVM_DIR="$HOME/.nvm" -[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" - -nvm use 18 -echo "Node version in script: $(node -v)" -echo "Node path in script: $(which node)" -echo "npm version in script: $(npm -v)" -echo "npm path in script: $(which npm)" - -TEST_PROJECT_PATH="./dev-test-npm/package-integrity-check" - -# 1. Clean and build all packages -npm run clean -npm run build - -# 2. Create a fresh test project -rm -rf "$TEST_PROJECT_PATH" -mkdir -p "$TEST_PROJECT_PATH" -cd "$TEST_PROJECT_PATH" -npm init -y - -# 3. Pack all packages and install them -cd ../../ -./scripts/pack-and-install.sh "$TEST_PROJECT_PATH" - -# 4. Run smoke tests (example: import main exports) -cd "$TEST_PROJECT_PATH" -echo "console.log('Integrity check: importing DecapCmsCore...'); require('decap-cms-core');" > smoke-test.js -node smoke-test.js - -# 5. Optionally, run unit/e2e tests -# npm test - -echo "Integrity check completed successfully!" From 83f075667cd3e5cbad807af73cc3518dbae41439 Mon Sep 17 00:00:00 2001 From: hip3r Date: Thu, 13 Nov 2025 15:48:01 +0100 Subject: [PATCH 3/4] feat: trying integrity more --- package-lock.json | 2 +- scripts/check-package-integrity.js | 271 +++++++++++++++++------------ 2 files changed, 160 insertions(+), 113 deletions(-) diff --git a/package-lock.json b/package-lock.json index cd35e9d83b89..5a775d71d0b1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "decap-cms", + "name": "decap-cms-pm", "version": "0.0.0", "lockfileVersion": 3, "requires": true, diff --git a/scripts/check-package-integrity.js b/scripts/check-package-integrity.js index 61f2202f17d9..cec3c86320ca 100644 --- a/scripts/check-package-integrity.js +++ b/scripts/check-package-integrity.js @@ -1,4 +1,4 @@ -const { execSync, spawnSync } = require('child_process'); +const { spawnSync } = require('child_process'); const fs = require('fs'); const path = require('path'); @@ -16,19 +16,14 @@ function run(command, options = {}) { try { // 0. Ensure monorepo dependencies are installed console.log('\n=== Installing monorepo dependencies ==='); - run('npm install', { cwd: rootPath }); + //run('npm install', { cwd: rootPath }); // 1. Build all packages in the monorepo (this builds dependencies in correct order) console.log('\n=== Building all packages ==='); - run('npm run build', { cwd: rootPath }); + //run('npm run build', { cwd: rootPath }); - // 2. Force build decap-cms-app specifically (bypass nx cache) - console.log('\n=== Force building decap-cms-app ==='); + // 2. Verify decap-cms-app build outputs exist const appPackagePath = path.join(rootPath, 'packages', 'decap-cms-app'); - run('npx nx reset', { cwd: rootPath }); // Clear nx cache - run('npx nx run decap-cms-app:build --skip-nx-cache', { cwd: rootPath }); - - // 3. Verify decap-cms-app build outputs exist const mainFile = path.join(appPackagePath, 'dist', 'decap-cms-app.js'); const esmFile = path.join(appPackagePath, 'dist', 'esm', 'index.js'); @@ -40,13 +35,13 @@ try { throw new Error(`Build failed: ${mainFile} was not created`); } - // 4. Create a fresh test project + // 3. Create a fresh test project if (fs.existsSync(TEST_PROJECT_PATH)) { fs.rmSync(TEST_PROJECT_PATH, { recursive: true, force: true }); } fs.mkdirSync(TEST_PROJECT_PATH, { recursive: true }); - // 5. Initialize package.json + // 4. Initialize package.json const packageJson = { name: 'decap-cms-integrity-test', version: '1.0.0', @@ -58,132 +53,184 @@ try { JSON.stringify(packageJson, null, 2) ); - // 6. Install dependencies including Playwright + // 5. Read React version from root package.json + const rootPackageJson = JSON.parse(fs.readFileSync(path.join(rootPath, 'package.json'), 'utf8')); + const reactVersion = rootPackageJson.devDependencies.react || '^19.1.0'; + const reactDomVersion = rootPackageJson.devDependencies['react-dom'] || '^19.1.0'; + + // 6. Install decap-cms-app and its dependencies console.log('\n=== Installing test dependencies ==='); - run('npm install react@^19.1.0 react-dom@^19.1.0 playwright --save-dev', { cwd: TEST_PROJECT_PATH }); + console.log(`Using React version: ${reactVersion}, React DOM version: ${reactDomVersion}`); - // 6.1. Install Playwright browsers - console.log('\n=== Installing Playwright browsers ==='); - run('npx playwright install chromium', { cwd: TEST_PROJECT_PATH }); + // Install decap-cms-app from the built package (using npm pack + install) + console.log('Packing decap-cms-app...'); + run('npm pack', { cwd: appPackagePath }); + + const packageTarball = fs.readdirSync(appPackagePath).find(f => f.endsWith('.tgz')); + if (!packageTarball) { + throw new Error('Failed to create package tarball'); + } + + const tarballPath = path.join(appPackagePath, packageTarball); + console.log(`Installing from ${tarballPath}...`); - // 7. Link decap-cms-app - run('npm link', { cwd: appPackagePath }); - run('npm link decap-cms-app', { cwd: TEST_PROJECT_PATH }); + run(`npm install react@${reactVersion} react-dom@${reactDomVersion} "${tarballPath}"`, { cwd: TEST_PROJECT_PATH }); + + // 7. Install Playwright for browser testing + console.log('\n=== Installing Playwright ==='); + run('npm install playwright --save-dev', { cwd: TEST_PROJECT_PATH }); + run('npx playwright install chromium', { cwd: TEST_PROJECT_PATH }); // 8. Create HTML test page const testHtmlPath = path.join(TEST_PROJECT_PATH, 'test.html'); fs.writeFileSync( testHtmlPath, ` - - - - Decap CMS Smoke Test - - -
- - - - ` + + + + Decap CMS Smoke Test + + +
+ + + +` ); - // 9. Create Playwright test - const playwrightTestPath = path.join(TEST_PROJECT_PATH, 'smoke-test.mjs'); + // 9. Create Playwright test script + const testFilePath = path.join(TEST_PROJECT_PATH, 'smoke-test.js'); fs.writeFileSync( - playwrightTestPath, + testFilePath, `import { chromium } from 'playwright'; - import { createServer } from 'http'; - import { readFileSync } from 'fs'; - import { join, extname } from 'path'; - import { fileURLToPath } from 'url'; - import { dirname } from 'path'; - - const __filename = fileURLToPath(import.meta.url); - const __dirname = dirname(__filename); - - // Simple HTTP server to serve files - const server = createServer((req, res) => { - const filePath = join(__dirname, req.url === '/' ? 'test.html' : req.url.slice(1)); - - const mimeTypes = { - '.html': 'text/html', - '.js': 'application/javascript', - '.mjs': 'application/javascript' - }; - - const ext = extname(filePath); - const contentType = mimeTypes[ext] || 'text/plain'; - - try { - const content = readFileSync(filePath); - res.writeHead(200, { 'Content-Type': contentType }); - res.end(content); - } catch (err) { - res.writeHead(404); - res.end('Not found'); - } - }); - - server.listen(0, async () => { - const port = server.address().port; - console.log(\`Test server running on http://localhost:\${port}\`); +import { createServer } from 'http'; +import { readFileSync } from 'fs'; +import { join, extname } from 'path'; +import { fileURLToPath } from 'url'; +import { dirname } from 'path'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const server = createServer((req, res) => { + let filePath; + + if (req.url === '/') { + filePath = join(__dirname, 'test.html'); + } else if (req.url.startsWith('/node_modules/')) { + filePath = join(__dirname, req.url); + } else { + filePath = join(__dirname, req.url.slice(1)); + } - const browser = await chromium.launch(); - const page = await browser.newPage(); + const mimeTypes = { + '.html': 'text/html', + '.js': 'application/javascript', + '.mjs': 'application/javascript', + '.css': 'text/css', + '.json': 'application/json' + }; - // Listen for console messages - page.on('console', msg => console.log('Browser:', msg.text())); + const ext = extname(filePath); + const contentType = mimeTypes[ext] || 'text/plain'; - // Load the test page - await page.goto(\`http://localhost:\${port}/test.html\`); + try { + const content = readFileSync(filePath); + res.writeHead(200, { + 'Content-Type': contentType, + 'Access-Control-Allow-Origin': '*' + }); + res.end(content); + } catch (err) { + res.writeHead(404); + res.end('Not found: ' + req.url); + } +}); - // Wait a bit for the module to load - await page.waitForTimeout(2000); +server.listen(0, async () => { + const port = server.address().port; + console.log('Test server running on http://localhost:' + port); - // Check test result - const result = await page.evaluate(() => window.testResult); + let browser; + try { + browser = await chromium.launch(); + const page = await browser.newPage(); - await browser.close(); - server.close(); + page.on('console', msg => { + const type = msg.type(); + const text = msg.text(); + console.log('[Browser ' + type + ']:', text); + }); - if (result && result.success) { - console.log('✓ Integrity check: decap-cms-app loaded successfully'); - console.log('✓', result.message); - console.log('✓ Smoke test passed'); - process.exit(0); - } else { - console.error('✗ Smoke test failed:', result?.error || 'Unknown error'); - console.error(result); - process.exit(1); - } + page.on('pageerror', error => { + console.error('[Browser Error]:', error.message); }); - ` + + console.log('Loading test page...'); + await page.goto('http://localhost:' + port + '/test.html'); + + console.log('Waiting for test to complete...'); + await page.waitForTimeout(3000); + + console.log('Evaluating test result...'); + const result = await page.evaluate(() => window.testResult); + + await browser.close(); + server.close(); + + if (result && result.success) { + console.log(''); + console.log('✓ Integrity check: decap-cms-app loaded successfully'); + console.log('✓', result.message); + console.log('✓ Smoke test passed'); + console.log(''); + console.log('Package can be imported as: import CMS from "decap-cms-app"'); + process.exit(0); + } else { + console.error(''); + console.error('✗ Smoke test failed:', result?.error || 'Unknown error'); + process.exit(1); + } + } catch (error) { + console.error('✗ Playwright error:', error.message); + if (browser) await browser.close(); + server.close(); + process.exit(1); + } +}); +` ); // 10. Run Playwright test console.log('\n=== Running smoke test in browser ==='); - run('node smoke-test.mjs', { cwd: TEST_PROJECT_PATH }); + run('node smoke-test.js', { cwd: TEST_PROJECT_PATH }); console.log('\n✓ Integrity check completed successfully!'); process.exit(0); From a7cee50c3c9453f68eae81b65a98a8b68db81ef1 Mon Sep 17 00:00:00 2001 From: hip3r Date: Tue, 25 Nov 2025 15:33:15 +0100 Subject: [PATCH 4/4] feat: first seemingly passing integrity test --- package.json | 4 +- scripts/check-package-integrity.cjs | 327 ++++++++++++++++++++++++++++ scripts/check-package-integrity.js | 240 -------------------- 3 files changed, 329 insertions(+), 242 deletions(-) create mode 100644 scripts/check-package-integrity.cjs delete mode 100644 scripts/check-package-integrity.js diff --git a/package.json b/package.json index 90824a744dd0..7dcf0ed23fe4 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "format": "npm run lint:js -- --fix --quiet && npm run format:prettier -- --write", "format:prettier": "prettier \"{{packages,scripts}/**/,}*.{js,jsx,ts,tsx,css}\"", "prepare": "husky install", - "check-package-integrity": "node scripts/check-package-integrity.js" + "check-package-integrity": "rimraf dev-test-npm && node scripts/check-package-integrity.cjs" }, "browserslist": [ "last 2 Chrome versions", @@ -179,4 +179,4 @@ "react-redux": "^7.2.0" }, "version": "0.0.0" -} \ No newline at end of file +} diff --git a/scripts/check-package-integrity.cjs b/scripts/check-package-integrity.cjs new file mode 100644 index 000000000000..d1c9e1777d85 --- /dev/null +++ b/scripts/check-package-integrity.cjs @@ -0,0 +1,327 @@ +const { spawnSync } = require('child_process'); +const fs = require('fs'); +const path = require('path'); + +const TEST_PROJECT_PATH = path.resolve(__dirname, '../dev-test-npm'); +const rootPath = path.resolve(__dirname, '..'); +const VITE_PID_FILE = path.join(TEST_PROJECT_PATH, '.vite-server.pid'); + +function run(command, options = {}) { + console.log(`Running: ${command}`); + const result = spawnSync(command, { shell: true, stdio: 'inherit', ...options }); + if (result.status !== 0) { + throw new Error(`Command failed: ${command}`); + } +} + +function killViteServer() { + // Kill any previous Vite server from a failed run + if (fs.existsSync(VITE_PID_FILE)) { + try { + const pid = fs.readFileSync(VITE_PID_FILE, 'utf8').trim(); + console.log(`Killing previous Vite server (PID: ${pid})...`); + if (process.platform === 'win32') { + spawnSync('taskkill', ['/F', '/PID', pid, '/T'], { shell: true, stdio: 'ignore' }); + } else { + spawnSync('kill', ['-9', pid], { stdio: 'ignore' }); + } + fs.unlinkSync(VITE_PID_FILE); + } catch (err) { + // Ignore errors, process may already be dead + } + } +} + +async function waitForServer(url, timeout = 30000) { + const startTime = Date.now(); + while (Date.now() - startTime < timeout) { + try { + const response = await fetch(url); + if (response.ok) return true; + } catch (e) { + // Server not ready yet + } + await new Promise(resolve => setTimeout(resolve, 100)); + } + throw new Error(`Server at ${url} did not start within ${timeout}ms`); +} + +(async () => { +try { + // Kill any lingering Vite server from previous run + killViteServer(); + // 0. Ensure monorepo dependencies are installed + console.log('\n=== Installing monorepo dependencies ==='); + //run('npm install', { cwd: rootPath }); + + // 1. Build all packages in the monorepo (this builds dependencies in correct order) + console.log('\n=== Building all packages ==='); + //run('npm run build', { cwd: rootPath }); + + // 2. Verify decap-cms-app build outputs exist + const appPackagePath = path.join(rootPath, 'packages', 'decap-cms-app'); + const mainFile = path.join(appPackagePath, 'dist', 'decap-cms-app.js'); + const esmFile = path.join(appPackagePath, 'dist', 'esm', 'index.js'); + + console.log('\n=== Checking build outputs ==='); + console.log('Main file exists:', fs.existsSync(mainFile)); + console.log('ESM file exists:', fs.existsSync(esmFile)); + + if (!fs.existsSync(mainFile)) { + throw new Error(`Build failed: ${mainFile} was not created`); + } + + // 3. Create a fresh test project + if (fs.existsSync(TEST_PROJECT_PATH)) { + fs.rmSync(TEST_PROJECT_PATH, { recursive: true, force: true }); + } + fs.mkdirSync(TEST_PROJECT_PATH, { recursive: true }); + + // 4. Initialize package.json WITHOUT "type": "module" for the main script + const packageJson = { + name: 'decap-cms-integrity-test', + version: '1.0.0', + private: true + }; + fs.writeFileSync( + path.join(TEST_PROJECT_PATH, 'package.json'), + JSON.stringify(packageJson, null, 2) + ); + + // 5. Read React version from root package.json + const rootPackageJson = JSON.parse(fs.readFileSync(path.join(rootPath, 'package.json'), 'utf8')); + const reactVersion = rootPackageJson.devDependencies.react || '^19.1.0'; + const reactDomVersion = rootPackageJson.devDependencies['react-dom'] || '^19.1.0'; + + // 6. Install decap-cms-app and its dependencies + console.log('\n=== Installing test dependencies ==='); + console.log(`Using React version: ${reactVersion}, React DOM version: ${reactDomVersion}`); + + // Install decap-cms-app from the built package (using npm pack + install) + console.log('Packing decap-cms-app...'); + run('npm pack', { cwd: appPackagePath }); + + const packageTarball = fs.readdirSync(appPackagePath).find(f => f.endsWith('.tgz')); + if (!packageTarball) { + throw new Error('Failed to create package tarball'); + } + + const tarballPath = path.join(appPackagePath, packageTarball); + console.log(`Installing from ${tarballPath}...`); + + run(`npm install vite playwright "${tarballPath}"`, { cwd: TEST_PROJECT_PATH }); + + // 7. Install Playwright browsers if needed + console.log('\n=== Setting up Playwright ==='); + run('npx playwright install chromium', { cwd: TEST_PROJECT_PATH }); + + // 8. Create index.html + const htmlFilePath = path.join(TEST_PROJECT_PATH, 'index.html'); + fs.writeFileSync( + htmlFilePath, + ` + + + + Decap CMS Integrity Test + + +
+
+ + +` + ); + + // 8a. Create minimal config.yml to avoid warnings + const publicDir = path.join(TEST_PROJECT_PATH, 'public'); + fs.mkdirSync(publicDir, { recursive: true }); + const configYmlPath = path.join(publicDir, 'config.yml'); + fs.writeFileSync( + configYmlPath, + `backend: + name: test-repo + +media_folder: "static/images" +public_folder: "/images" + +collections: + - name: "test" + label: "Test" + folder: "content/test" + create: true + fields: + - { label: "Title", name: "title", widget: "string" } +` + ); + + // 9. Create main.js with EXACT documentation pattern + const mainJsPath = path.join(TEST_PROJECT_PATH, 'main.js'); + fs.writeFileSync( + mainJsPath, + `// Test the exact usage pattern from the documentation: +// npm install decap-cms-app --save +import CMS from "decap-cms-app"; +// Initialize the CMS object +CMS.init(); +// Now the registry is available via the CMS object. +const MyTemplate = {}; +CMS.registerPreviewTemplate("my-template", MyTemplate); + +// Test verification +const results = document.getElementById('test-results'); +const log = (msg) => { + console.log(msg); + const p = document.createElement('p'); + p.textContent = msg; + results.appendChild(p); +}; + +try { + log('=== Testing decap-cms-app npm package ==='); + log('✓ import CMS from "decap-cms-app" - SUCCESS'); + log('✓ CMS.init() - SUCCESS'); + log('✓ CMS.registerPreviewTemplate() - SUCCESS'); + log('TEST_PASSED'); +} catch (error) { + log('✗ Test failed: ' + error.message); + log('TEST_FAILED'); +} +` + ); + + // 10. Create vite.config.js + const viteConfigPath = path.join(TEST_PROJECT_PATH, 'vite.config.js'); + fs.writeFileSync( + viteConfigPath, + `export default { + server: { + port: 8765 + } +}; +` + ); + + // 11. Create Playwright test script + const playwrightTestPath = path.join(TEST_PROJECT_PATH, 'run-test.js'); + fs.writeFileSync( + playwrightTestPath, + `import { chromium } from 'playwright'; + +(async () => { + const browser = await chromium.launch({ headless: true }); + const page = await browser.newPage(); + + // Capture console logs and errors + page.on('console', msg => { + const type = msg.type(); + const text = msg.text(); + if (type === 'error') { + console.error('[Browser Error]', text); + } else { + console.log('[Browser]', text); + } + }); + page.on('pageerror', error => console.error('[Browser PageError]', error.message)); + + try { + await page.goto('http://localhost:8765', { waitUntil: 'networkidle', timeout: 30000 }); + + await page.waitForFunction(() => { + const results = document.getElementById('test-results'); + const text = results ? results.textContent : ''; + return text.includes('TEST_PASSED') || text.includes('TEST_FAILED'); + }, { timeout: 30000 }); + + const results = await page.textContent('#test-results'); + await browser.close(); + + if (results.includes('TEST_PASSED')) { + console.log('\\n✓ Package integrity verified'); + console.log('\\nPackage works exactly as documented:'); + console.log(' npm install decap-cms-app --save'); + console.log(' import CMS from "decap-cms-app";'); + console.log(' CMS.init();'); + console.log(' CMS.registerPreviewTemplate("my-template", MyTemplate);'); + process.exit(0); + } else { + console.error('\\n✗ Test failed'); + process.exit(1); + } + } catch (error) { + console.error('\\n✗ Test error:', error.message); + await browser.close(); + process.exit(1); + } +})();` + ); + + // 12. Start Vite dev server in background and run test + console.log('\n=== Starting Vite dev server and running test ==='); + + const viteProcess = require('child_process').spawn('npx', ['vite'], { + cwd: TEST_PROJECT_PATH, + shell: true, + stdio: 'pipe' + }); + + // Save PID for cleanup + fs.writeFileSync(VITE_PID_FILE, viteProcess.pid.toString()); + + // Ensure cleanup on exit + const cleanup = () => { + if (viteProcess && !viteProcess.killed) { + if (process.platform === 'win32') { + // On Windows, kill the entire process tree + spawnSync('taskkill', ['/F', '/PID', viteProcess.pid.toString(), '/T'], { + shell: true, + stdio: 'ignore' + }); + } else { + viteProcess.kill('SIGTERM'); + setTimeout(() => { + if (!viteProcess.killed) viteProcess.kill('SIGKILL'); + }, 1000); + } + } + if (fs.existsSync(VITE_PID_FILE)) { + fs.unlinkSync(VITE_PID_FILE); + } + }; + process.on('exit', cleanup); + process.on('SIGINT', () => { + cleanup(); + process.exit(130); + }); + process.on('SIGTERM', () => { + cleanup(); + process.exit(143); + }); + + // Wait for Vite to start + await new Promise((resolve, reject) => { + const timeout = setTimeout(() => reject(new Error('Vite server timeout')), 30000); + viteProcess.stdout.on('data', (data) => { + const output = data.toString(); + process.stdout.write(output); + if (output.includes('Local:') || output.includes('localhost:8765') || output.includes('ready in')) { + clearTimeout(timeout); + setTimeout(resolve, 1000); // Give it 1 more second + } + }); + viteProcess.stderr.on('data', (data) => process.stderr.write(data.toString())); + }); + + try { + run('node run-test.js', { cwd: TEST_PROJECT_PATH }); + console.log('\n✓ Integrity check completed successfully!'); + } finally { + cleanup(); + } + + process.exit(0); +} catch (err) { + console.error('\n✗ Integrity check failed:', err.message); + process.exit(1); +} +})(); diff --git a/scripts/check-package-integrity.js b/scripts/check-package-integrity.js deleted file mode 100644 index cec3c86320ca..000000000000 --- a/scripts/check-package-integrity.js +++ /dev/null @@ -1,240 +0,0 @@ -const { spawnSync } = require('child_process'); -const fs = require('fs'); -const path = require('path'); - -const TEST_PROJECT_PATH = path.resolve(__dirname, '../dev-test-npm'); -const rootPath = path.resolve(__dirname, '..'); - -function run(command, options = {}) { - console.log(`Running: ${command}`); - const result = spawnSync(command, { shell: true, stdio: 'inherit', ...options }); - if (result.status !== 0) { - throw new Error(`Command failed: ${command}`); - } -} - -try { - // 0. Ensure monorepo dependencies are installed - console.log('\n=== Installing monorepo dependencies ==='); - //run('npm install', { cwd: rootPath }); - - // 1. Build all packages in the monorepo (this builds dependencies in correct order) - console.log('\n=== Building all packages ==='); - //run('npm run build', { cwd: rootPath }); - - // 2. Verify decap-cms-app build outputs exist - const appPackagePath = path.join(rootPath, 'packages', 'decap-cms-app'); - const mainFile = path.join(appPackagePath, 'dist', 'decap-cms-app.js'); - const esmFile = path.join(appPackagePath, 'dist', 'esm', 'index.js'); - - console.log('\n=== Checking build outputs ==='); - console.log('Main file exists:', fs.existsSync(mainFile)); - console.log('ESM file exists:', fs.existsSync(esmFile)); - - if (!fs.existsSync(mainFile)) { - throw new Error(`Build failed: ${mainFile} was not created`); - } - - // 3. Create a fresh test project - if (fs.existsSync(TEST_PROJECT_PATH)) { - fs.rmSync(TEST_PROJECT_PATH, { recursive: true, force: true }); - } - fs.mkdirSync(TEST_PROJECT_PATH, { recursive: true }); - - // 4. Initialize package.json - const packageJson = { - name: 'decap-cms-integrity-test', - version: '1.0.0', - type: 'module', - private: true - }; - fs.writeFileSync( - path.join(TEST_PROJECT_PATH, 'package.json'), - JSON.stringify(packageJson, null, 2) - ); - - // 5. Read React version from root package.json - const rootPackageJson = JSON.parse(fs.readFileSync(path.join(rootPath, 'package.json'), 'utf8')); - const reactVersion = rootPackageJson.devDependencies.react || '^19.1.0'; - const reactDomVersion = rootPackageJson.devDependencies['react-dom'] || '^19.1.0'; - - // 6. Install decap-cms-app and its dependencies - console.log('\n=== Installing test dependencies ==='); - console.log(`Using React version: ${reactVersion}, React DOM version: ${reactDomVersion}`); - - // Install decap-cms-app from the built package (using npm pack + install) - console.log('Packing decap-cms-app...'); - run('npm pack', { cwd: appPackagePath }); - - const packageTarball = fs.readdirSync(appPackagePath).find(f => f.endsWith('.tgz')); - if (!packageTarball) { - throw new Error('Failed to create package tarball'); - } - - const tarballPath = path.join(appPackagePath, packageTarball); - console.log(`Installing from ${tarballPath}...`); - - run(`npm install react@${reactVersion} react-dom@${reactDomVersion} "${tarballPath}"`, { cwd: TEST_PROJECT_PATH }); - - // 7. Install Playwright for browser testing - console.log('\n=== Installing Playwright ==='); - run('npm install playwright --save-dev', { cwd: TEST_PROJECT_PATH }); - run('npx playwright install chromium', { cwd: TEST_PROJECT_PATH }); - - // 8. Create HTML test page - const testHtmlPath = path.join(TEST_PROJECT_PATH, 'test.html'); - fs.writeFileSync( - testHtmlPath, - ` - - - - Decap CMS Smoke Test - - -
- - - -` - ); - - // 9. Create Playwright test script - const testFilePath = path.join(TEST_PROJECT_PATH, 'smoke-test.js'); - fs.writeFileSync( - testFilePath, - `import { chromium } from 'playwright'; -import { createServer } from 'http'; -import { readFileSync } from 'fs'; -import { join, extname } from 'path'; -import { fileURLToPath } from 'url'; -import { dirname } from 'path'; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - -const server = createServer((req, res) => { - let filePath; - - if (req.url === '/') { - filePath = join(__dirname, 'test.html'); - } else if (req.url.startsWith('/node_modules/')) { - filePath = join(__dirname, req.url); - } else { - filePath = join(__dirname, req.url.slice(1)); - } - - const mimeTypes = { - '.html': 'text/html', - '.js': 'application/javascript', - '.mjs': 'application/javascript', - '.css': 'text/css', - '.json': 'application/json' - }; - - const ext = extname(filePath); - const contentType = mimeTypes[ext] || 'text/plain'; - - try { - const content = readFileSync(filePath); - res.writeHead(200, { - 'Content-Type': contentType, - 'Access-Control-Allow-Origin': '*' - }); - res.end(content); - } catch (err) { - res.writeHead(404); - res.end('Not found: ' + req.url); - } -}); - -server.listen(0, async () => { - const port = server.address().port; - console.log('Test server running on http://localhost:' + port); - - let browser; - try { - browser = await chromium.launch(); - const page = await browser.newPage(); - - page.on('console', msg => { - const type = msg.type(); - const text = msg.text(); - console.log('[Browser ' + type + ']:', text); - }); - - page.on('pageerror', error => { - console.error('[Browser Error]:', error.message); - }); - - console.log('Loading test page...'); - await page.goto('http://localhost:' + port + '/test.html'); - - console.log('Waiting for test to complete...'); - await page.waitForTimeout(3000); - - console.log('Evaluating test result...'); - const result = await page.evaluate(() => window.testResult); - - await browser.close(); - server.close(); - - if (result && result.success) { - console.log(''); - console.log('✓ Integrity check: decap-cms-app loaded successfully'); - console.log('✓', result.message); - console.log('✓ Smoke test passed'); - console.log(''); - console.log('Package can be imported as: import CMS from "decap-cms-app"'); - process.exit(0); - } else { - console.error(''); - console.error('✗ Smoke test failed:', result?.error || 'Unknown error'); - process.exit(1); - } - } catch (error) { - console.error('✗ Playwright error:', error.message); - if (browser) await browser.close(); - server.close(); - process.exit(1); - } -}); -` - ); - - // 10. Run Playwright test - console.log('\n=== Running smoke test in browser ==='); - run('node smoke-test.js', { cwd: TEST_PROJECT_PATH }); - - console.log('\n✓ Integrity check completed successfully!'); - process.exit(0); -} catch (err) { - console.error('\n✗ Integrity check failed:', err.message); - process.exit(1); -}