diff --git a/.gitignore b/.gitignore index 01ce5e521df5..ed938f639b57 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,5 @@ coverage/ .temp/ storybook-static/ .nx +dev-test-npm/ +packages/*/*.tgz 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/package.json b/package.json index 9f27667a3a17..7dcf0ed23fe4 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", @@ -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": "rimraf dev-test-npm && node scripts/check-package-integrity.cjs" }, "browserslist": [ "last 2 Chrome versions", 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, + ` + +
+ +