diff --git a/packages/model-viewer-effects/README.md b/packages/model-viewer-effects/README.md index 86e6b5aefd..f98e7bbc8c 100644 --- a/packages/model-viewer-effects/README.md +++ b/packages/model-viewer-effects/README.md @@ -46,13 +46,13 @@ npm install three @google/model-viewer @google/model-viewer-effects ```html - + diff --git a/packages/model-viewer/README.md b/packages/model-viewer/README.md index c283cc9c16..e3523ec95b 100644 --- a/packages/model-viewer/README.md +++ b/packages/model-viewer/README.md @@ -43,7 +43,7 @@ import '@google/model-viewer'; It can also be used directly from various free CDNs such as [jsDelivr](https://www.jsdelivr.com/package/npm/@google/model-viewer) and Google's own [hosted libraries](https://developers.google.com/speed/libraries#model-viewer): ```html - + ``` For more detailed usage documentation and live examples, please visit our docs diff --git a/packages/modelviewer.dev/data/docs.json b/packages/modelviewer.dev/data/docs.json index 2648e19ca0..accd066064 100644 --- a/packages/modelviewer.dev/data/docs.json +++ b/packages/modelviewer.dev/data/docs.json @@ -136,7 +136,7 @@ { "name": "lottieLoaderLocation", "htmlName": "lottieLoaderLocation", - "description": "This static, writable property sets <model-viewer>'s LottieLoader location URL. The default URL is https://cdn.jsdelivr.net/npm/three@0.174.0/examples/jsm/loaders/LottieLoader.js. It will also require the server to provide the lottie canvas module at ../libs/lottie_canvas.module.js." + "description": "This static, writable property sets <model-viewer>'s LottieLoader location URL. The default URL is https://cdn.jsdelivr.net/npm/three@{{THREEJS_VERSION}}/examples/jsm/loaders/LottieLoader.js. It will also require the server to provide the lottie canvas module at ../libs/lottie_canvas.module.js." }, { "name": "minimumRenderScale", diff --git a/packages/modelviewer.dev/data/faq.json b/packages/modelviewer.dev/data/faq.json index 084d461fb2..75bf9478fb 100644 --- a/packages/modelviewer.dev/data/faq.json +++ b/packages/modelviewer.dev/data/faq.json @@ -86,7 +86,7 @@ { "name": "How should I access <model-viewer>?", "htmlName": "cdn", - "description": "If you control your own hosting, the safest option is always to host model-viewer.min.js yourself on the same server as your site. For smaller sites and blogs, it is often more convenient to use one of various free CDNs - Google provides <model-viewer> as one of its hosted libraries, which we recommend as it is a fast and reliable CDN. Simply specify your desired version in the URL: https://ajax.googleapis.com/ajax/libs/model-viewer/4.0.0/model-viewer.min.js. We used to recommend unpkg, but it has had several serious outages recently. It can automatically pick the most recent version, but this is not a good practice as it slows loading (two requests) and ideally you should test when updating to ensure no bugs have been introduced. Another good option is jsDelivr.", + "description": "If you control your own hosting, the safest option is always to host model-viewer.min.js yourself on the same server as your site. For smaller sites and blogs, it is often more convenient to use one of various free CDNs - Google provides <model-viewer> as one of its hosted libraries, which we recommend as it is a fast and reliable CDN. Simply specify your desired version in the URL: https://ajax.googleapis.com/ajax/libs/model-viewer/{{MODELVIEWER_VERSION}}/model-viewer.min.js. We used to recommend unpkg, but it has had several serious outages recently. It can automatically pick the most recent version, but this is not a good practice as it slows loading (two requests) and ideally you should test when updating to ensure no bugs have been introduced. Another good option is jsDelivr.", "links": [ "Google Hosted Libraries", "jsDelivr" diff --git a/packages/modelviewer.dev/examples/postprocessing/index.html b/packages/modelviewer.dev/examples/postprocessing/index.html index 444c5ffbfb..54a94e67d0 100644 --- a/packages/modelviewer.dev/examples/postprocessing/index.html +++ b/packages/modelviewer.dev/examples/postprocessing/index.html @@ -33,7 +33,7 @@ @@ -114,7 +114,7 @@

Setup Post Processing

@@ -459,7 +459,7 @@

Custom Effects

+ + diff --git a/packages/modelviewer.dev/scripts/ci-before-deploy.sh b/packages/modelviewer.dev/scripts/ci-before-deploy.sh index 2cc39e433e..a6715f422d 100755 --- a/packages/modelviewer.dev/scripts/ci-before-deploy.sh +++ b/packages/modelviewer.dev/scripts/ci-before-deploy.sh @@ -135,6 +135,8 @@ done # Add a "VERSION" file containing the last git commit message git log -n 1 > $DEPLOY_ROOT/VERSION +node scripts/update-versions.js + git status --ignored popd diff --git a/packages/modelviewer.dev/scripts/update-versions.js b/packages/modelviewer.dev/scripts/update-versions.js new file mode 100644 index 0000000000..1d477329b1 --- /dev/null +++ b/packages/modelviewer.dev/scripts/update-versions.js @@ -0,0 +1,208 @@ +#!/usr/bin/env node + +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import chalk from 'chalk'; + +// ESM module resolution +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +// ============================================================ +// CONFIGURATION +// ============================================================ + +const CONFIG = { + packagePaths: { + modelViewer: path.resolve(__dirname, '../../model-viewer/package.json'), + effects: path.resolve(__dirname, '../../model-viewer-effects/package.json'), + }, + targetFiles: [ + '../dist/index.html', + '../dist/data/faq.json', + '../dist/data/docs.json', + '../dist/examples/postprocessing/index.html', + '../dist/examples/twitter/player.html', + ].map(file => path.resolve(__dirname, file)), + placeholders: { + modelViewer: '{{MODELVIEWER_VERSION}}', + three: '{{THREEJS_VERSION}}', + postprocessing: '{{POSTPROCESSING_VERSION}}', + }, +}; + +// ============================================================ +// LOGGER UTILITIES +// ============================================================ + +const logger = { + success: (msg) => console.log(chalk.green(`✓ ${msg}`)), + error: (msg) => console.log(chalk.red(`✗ ${msg}`)), + warning: (msg) => console.log(chalk.yellow(`⚠ ${msg}`)), + info: (msg) => console.log(chalk.blue(`ℹ ${msg}`)), + separator: () => console.log(''), +}; + +// ============================================================ +// FILE OPERATIONS +// ============================================================ + +/** + * Reads and parses a JSON file safely + * @param {string} filePath - Path to JSON file + * @returns {Object|null} Parsed JSON or null on error + */ +const readJsonFile = (filePath) => { + try { + if (!fs.existsSync(filePath)) { + throw new Error(`File not found: ${filePath}`); + } + const content = fs.readFileSync(filePath, 'utf8'); + return JSON.parse(content); + } catch (error) { + logger.error(`Error reading ${filePath}: ${error.message}`); + return null; + } +}; + +/** + * Replaces placeholders in a file with actual values + * @param {string} filePath - Target file path + * @param {Object} replacements - Key-value pairs for replacement + * @returns {boolean} Success status + */ +const replacePlaceholdersInFile = (filePath, replacements) => { + if (!fs.existsSync(filePath)) { + logger.warning(`File ${filePath} does not exist - skipped`); + return false; + } + + try { + let content = fs.readFileSync(filePath, 'utf8'); + let hasChanges = false; + + for (const [placeholder, value] of Object.entries(replacements)) { + if (content.includes(placeholder)) { + content = content.replaceAll(placeholder, value); + hasChanges = true; + } else { + logger.warning(`Placeholder ${placeholder} not found in ${path.basename(filePath)}`); + } + } + + if (hasChanges) { + fs.writeFileSync(filePath, content, 'utf8'); + logger.success(`Updated: ${path.basename(filePath)}`); + return true; + } + + return false; + } catch (error) { + logger.error(`Error processing ${filePath}: ${error.message}`); + return false; + } +}; + +// ============================================================ +// VERSION EXTRACTION +// ============================================================ + +/** + * Extracts package version from package.json + * @param {Object} packageJson - Parsed package.json + * @param {string} packageName - Name of the package + * @returns {string} Cleaned version string + */ +const extractPackageVersion = (packageJson, packageName) => { + const version = + packageJson.dependencies?.[packageName] || + packageJson.devDependencies?.[packageName] || + ''; + + return version.replace(/^[^\d]*/, ''); // Remove leading symbols (^, ~, etc.) +}; + +/** + * Collects all required versions from package.json files + * @returns {Object|null} Version object or null on error + */ +const collectVersions = () => { + const modelViewerPkg = readJsonFile(CONFIG.packagePaths.modelViewer); + const effectsPkg = readJsonFile(CONFIG.packagePaths.effects); + + if (!modelViewerPkg || !effectsPkg) { + return null; + } + + const versions = { + three: extractPackageVersion(modelViewerPkg, 'three'), + modelViewer: modelViewerPkg.version || '', + postprocessing: extractPackageVersion(effectsPkg, 'postprocessing'), + }; + + // Validate all versions are present + const missingVersions = Object.entries(versions) + .filter(([_, version]) => !version) + .map(([key]) => key); + + if (missingVersions.length > 0) { + logger.error(`Missing versions: ${missingVersions.join(', ')}`); + return null; + } + + return versions; +}; + +// ============================================================ +// MAIN EXECUTION +// ============================================================ + +/** + * Main execution function + */ +const main = () => { + logger.info('Starting version replacement...'); + logger.separator(); + + // Collect versions + const versions = collectVersions(); + if (!versions) { + process.exit(1); + } + + // Display versions + logger.info(`three.js: ${versions.three}`); + logger.info(`model-viewer: ${versions.modelViewer}`); + logger.info(`postprocessing: ${versions.postprocessing}`); + logger.separator(); + + // Prepare replacements + const replacements = { + [CONFIG.placeholders.three]: versions.three, + [CONFIG.placeholders.modelViewer]: versions.modelViewer, + [CONFIG.placeholders.postprocessing]: versions.postprocessing, + }; + + // Process all target files + const results = CONFIG.targetFiles.map(file => + replacePlaceholdersInFile(file, replacements) + ); + + const successCount = results.filter(Boolean).length; + const totalCount = CONFIG.targetFiles.length; + + // Final summary + logger.separator(); + if (successCount === totalCount) { + logger.success(`All ${totalCount} files updated successfully!`); + } else if (successCount > 0) { + logger.warning(`${successCount} out of ${totalCount} files updated`); + } else { + logger.error('No files were updated!'); + process.exit(1); + } +}; + +// Execute +main(); \ No newline at end of file diff --git a/packages/space-opera/src/components/best_practices/constants.ts b/packages/space-opera/src/components/best_practices/constants.ts index bd140fd1a6..0dad1cc791 100644 --- a/packages/space-opera/src/components/best_practices/constants.ts +++ b/packages/space-opera/src/components/best_practices/constants.ts @@ -28,7 +28,7 @@ export const modelViewerTemplate = ` REPLACEME - + `;