From f490f639add829e123313e3229f9184c33fb81e0 Mon Sep 17 00:00:00 2001 From: jjspace <8007967+jjspace@users.noreply.github.com> Date: Fri, 19 Sep 2025 11:29:55 -0400 Subject: [PATCH 01/22] central sandcastle build function, bundle esm packages --- gulpfile.js | 29 +++++ package.json | 4 +- packages/sandcastle/scripts/buildStatic.js | 120 ++++++++++++++++++ packages/sandcastle/src/SandcastleEditor.tsx | 109 +++++++++++++--- packages/sandcastle/standalone.html | 8 -- packages/sandcastle/templates/bucket.html | 8 -- packages/sandcastle/tsconfig.node.json | 8 +- packages/sandcastle/vite-env.d.ts | 2 +- packages/sandcastle/vite-plugins.js | 62 +++++++++ packages/sandcastle/vite.config.app.ts | 49 ++++++- packages/sandcastle/vite.config.ci.ts | 49 ++++++- packages/sandcastle/vite.config.dev.ts | 88 ++++++++++++- .../{vite.config.ts => vite.config.js} | 38 +++--- packages/sandcastle/vite.config.prod.ts | 65 +++++++++- scripts/build.js | 76 +++++++++++ 15 files changed, 635 insertions(+), 80 deletions(-) create mode 100644 packages/sandcastle/scripts/buildStatic.js create mode 100644 packages/sandcastle/vite-plugins.js rename packages/sandcastle/{vite.config.ts => vite.config.js} (61%) diff --git a/gulpfile.js b/gulpfile.js index f8bd21c73713..a9839f5635f8 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -31,6 +31,11 @@ import { createJsHintOptions, defaultESBuildOptions, } from "./scripts/build.js"; +import { fileURLToPath } from "url"; +import { + buildStatic, + createSandcastleConfig, +} from "./packages/sandcastle/scripts/buildStatic.js"; // Determines the scope of the workspace packages. If the scope is set to cesium, the workspaces should be @cesium/engine. // This should match the scope of the dependencies of the root level package.json. @@ -1741,6 +1746,30 @@ async function buildSandcastle() { return Promise.all(streams.map((s) => finished(s))); } +export async function buildNewSandcastle() { + const __dirname = dirname(fileURLToPath(import.meta.url)); + const newConfig = createSandcastleConfig({ + outDir: join(__dirname, "./Apps/Sandcastle2"), + viteBase: "/Apps/Sandcastle2", + cesiumBaseUrl: "/Build/CesiumUnminified", + imports: { + cesium: { + path: "/Source/Cesium.js", + typesPath: "/Source/Cesium.d.ts", + }, + "@cesium/engine": { + path: "/packages/engine/Build/Unminified/index.js", + typesPath: "/packages/engine/index.d.ts", + }, + "@cesium/widgets": { + path: "/packages/widgets/Build/Unminified/index.js", + typesPath: "/packages/widgets/index.d.ts", + }, + }, + }); + await buildStatic(newConfig); +} + async function buildCesiumViewer() { const cesiumViewerOutputDirectory = isProduction ? "Build/CesiumViewer" diff --git a/package.json b/package.json index 04b278537435..47252ae19941 100644 --- a/package.json +++ b/package.json @@ -115,7 +115,7 @@ "build-ts": "gulp buildTs", "build-third-party": "gulp buildThirdParty", "build-apps": "gulp buildApps", - "build-sandcastle": "npm run build-app --workspace packages/sandcastle", + "build-sandcastle": "gulp buildNewSandcastle", "clean": "gulp clean", "cloc": "gulp cloc", "coverage": "gulp coverage", @@ -171,4 +171,4 @@ } } } -} \ No newline at end of file +} diff --git a/packages/sandcastle/scripts/buildStatic.js b/packages/sandcastle/scripts/buildStatic.js new file mode 100644 index 000000000000..60dc007434e5 --- /dev/null +++ b/packages/sandcastle/scripts/buildStatic.js @@ -0,0 +1,120 @@ +import { build, defineConfig } from "vite"; +import baseConfig from "../vite.config.js"; +import { fileURLToPath } from "url"; +import { viteStaticCopy } from "vite-plugin-static-copy"; +import { dirname, join } from "path"; +import { cesiumPathReplace, insertImportMap } from "../vite-plugins.js"; + +/** @import { UserConfig } from 'vite' */ + +/** + * @typedef {Object} ImportObject + * @property {string} path The path to use for the import map. ie the path the app can expect to find this at + * @property {string} typesPath The path to use for intellisense types in monaco + */ + +/** + * @typedef {Object} ImportList + */ + +/** + * Check if the given key is in the imports list and throw an error if not + * @param {ImportList} imports + * @param {string} name + */ +function checkForImport(imports, name) { + if (!imports[name]) { + throw new Error(`Missing import for ${name}`); + } +} + +const __dirname = dirname(fileURLToPath(import.meta.url)); + +/** + * Create the Vite configuration for building Sandcastle. + * Set where it should build to and the base path for vite and CesiumJS files. + * + * Most importantly specify the paths the app can find the library imports + * + * @param {object} options + * @param {string} options.outDir Path to build files into + * @param {string} options.viteBase Base path for files/routes + * @param {string} options.cesiumBaseUrl Base path for CesiumJS. This should include the CesiumJS assets and workers etc. + * @param {string} [options.commitSha] Optional commit hash to display in the top right of the application + * @param {ImportList} options.imports Set of imports to add to the import map for the iframe and standalone html pages. These paths should match where it can access it within the current environment. + * @param {{src: string, dest: string}[]} [options.copyExtraFiles] Extra paths passed to viteStaticCopy. Use this to consolodate files for a singular static deployment (ie during production). Source paths should be absolute. + * @returns + */ +export function createSandcastleConfig({ + outDir, + viteBase, + cesiumBaseUrl, + commitSha, + imports, + copyExtraFiles = [], +}) { + /** @type {UserConfig} */ + const config = { ...baseConfig }; + + config.base = viteBase; + + config.build = { + ...config.build, + outDir: outDir, + }; + + const copyPlugin = viteStaticCopy({ + targets: [ + { src: "templates/Sandcastle.(d.ts|js)", dest: "templates" }, + ...copyExtraFiles, + ], + }); + + checkForImport(imports, "cesium"); + checkForImport(imports, "@cesium/engine"); + checkForImport(imports, "@cesium/widgets"); + if (imports["Sandcastle"]) { + throw new Error( + "Don't specify the Sandcastle import this is taken care of internally", + ); + } + + /** @type {Object} */ + const importMap = { + Sandcastle: `${viteBase}/templates/Sandcastle.js`, + }; + /** @type {Object} */ + const typePaths = { + Sandcastle: "templates/Sandcastle.d.ts", + }; + for (const [key, value] of Object.entries(imports)) { + importMap[key] = value.path; + typePaths[key] = value.typesPath; + } + + config.define = { + ...config.define, + __VITE_TYPE_IMPORT_PATHS__: JSON.stringify(typePaths), + __COMMIT_SHA__: JSON.stringify(commitSha ?? undefined), + }; + + const plugins = config.plugins ?? []; + config.plugins = [ + ...plugins, + copyPlugin, + cesiumPathReplace(cesiumBaseUrl), + insertImportMap(importMap, ["bucket.html", "standalone.html"]), + ]; + + return defineConfig(config); +} + +/** + * @param {UserConfig} config + */ +export async function buildStatic(config) { + await build({ + ...config, + root: join(__dirname, "../"), + }); +} diff --git a/packages/sandcastle/src/SandcastleEditor.tsx b/packages/sandcastle/src/SandcastleEditor.tsx index bc686f1fa2b7..18063bc19a1c 100644 --- a/packages/sandcastle/src/SandcastleEditor.tsx +++ b/packages/sandcastle/src/SandcastleEditor.tsx @@ -51,9 +51,6 @@ self.MonacoEnvironment = { // open network access loader.config({ monaco }); -const TYPES_URL = `${__PAGE_BASE_URL__}Source/Cesium.d.ts`; -const SANDCASTLE_TYPES_URL = `templates/Sandcastle.d.ts`; - export type SandcastleEditorRef = { formatCode(): void; }; @@ -192,25 +189,97 @@ function SandcastleEditor({ async function setTypes(monaco: Monaco) { // https://microsoft.github.io/monaco-editor/playground.html?source=v0.52.2#example-extending-language-services-configure-javascript-defaults - const cesiumTypes = await (await fetch(TYPES_URL)).text(); - // define a "global" variable so types work even with out the import statement - const cesiumTypesWithGlobal = `${cesiumTypes}\nvar Cesium: typeof import('cesium');`; - monaco.languages.typescript.javascriptDefaults.addExtraLib( - cesiumTypesWithGlobal, - "ts:cesium.d.ts", + const typeImportPaths = __VITE_TYPE_IMPORT_PATHS__ ?? {}; + + const typeImports: { + url: string; + filename: string; + transformTypes?: (typesContent: string) => string; + }[] = [ + { + url: typeImportPaths["cesium"], + filename: "ts:cesium.d.ts", + transformTypes(typesContent: string) { + // define a "global" variable so types work even with out the import statement + return `${typesContent}\nvar Cesium: typeof import('cesium');`; + }, + }, + { + url: typeImportPaths["Sandcastle"], + filename: "ts:sandcastle.d.ts", + transformTypes(typesContent: string) { + return `declare module 'Sandcastle' { + ${typesContent} + } + var Sandcastle: typeof import('Sandcastle').default;`; + }, + }, + { + url: typeImportPaths["@cesium/engine"], + filename: "ts:cesium-engine.d.ts", + }, + { + url: typeImportPaths["@cesium/widgets"], + filename: "ts:cesium-widgets.d.ts", + transformTypes(typesContent) { + // Monaco expects the import statements to be inside the module so + // move the module declaration to the top of the "file" + return `declare module "@cesium/widgets" { + ${typesContent.replace('declare module "@cesium/widgets" {', "")}`; + }, + }, + ]; + + const extraImportNames = Object.keys(typeImportPaths).filter( + (name) => + !["cesium", "Sandcastle", "@cesium/engine", "@cesium/widgets"].includes( + name, + ), ); + for (const extraName of extraImportNames) { + typeImports.push({ + url: typeImportPaths[extraName], + filename: `ts:${extraName.replace(/@\//, "-")}.d.ts`, + transformTypes(typesContent) { + // TODO: this feels a little messy and still very targeted at our own modules, is there a way to improve? + // I was experimenting with setting the transform from Vite but that doesn't work with functions + + // Monaco expects the import statements to be inside the module so + // move the module declaration to the top of the "file" + if (typesContent.trim().startsWith("import")) { + const declareModuleLine = typesContent.match( + /declare module "([\w@\-\/]+)" {/gm, + )?.[0]; + if (declareModuleLine) { + return `${declareModuleLine} + ${typesContent.replace(declareModuleLine, "")}`; + } + } + return typesContent; + }, + }); + } - const sandcastleTypes = await (await fetch(SANDCASTLE_TYPES_URL)).text(); - // surround in a module so the import statement works nicely - // also define a "global" so types show even if you don't have the import - const sandcastleModuleTypes = `declare module 'Sandcastle' { - ${sandcastleTypes} - } - var Sandcastle: typeof import('Sandcastle').default;`; - - monaco.languages.typescript.javascriptDefaults.addExtraLib( - sandcastleModuleTypes, - "ts:sandcastle.d.ts", + await Promise.allSettled( + typeImports.map(async (typeImport) => { + const { url, transformTypes, filename } = typeImport; + if (!url) { + return; + } + try { + const responseText = await (await fetch(url)).text(); + const typesContent = transformTypes + ? transformTypes(responseText) + : responseText; + monaco.languages.typescript.javascriptDefaults.addExtraLib( + typesContent, + filename, + ); + } catch (error) { + console.error(`Unable to load types for ${filename} at ${url}`); + console.error(error); + } + }), ); } diff --git a/packages/sandcastle/standalone.html b/packages/sandcastle/standalone.html index a6694686596d..a454fda10f74 100644 --- a/packages/sandcastle/standalone.html +++ b/packages/sandcastle/standalone.html @@ -8,14 +8,6 @@ content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no" /> Cesium Demo - diff --git a/packages/sandcastle/templates/bucket.html b/packages/sandcastle/templates/bucket.html index e56946aca7bf..99ee48356fbf 100644 --- a/packages/sandcastle/templates/bucket.html +++ b/packages/sandcastle/templates/bucket.html @@ -8,14 +8,6 @@ content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no" /> Cesium Demo - @@ -96,7 +99,11 @@ unstable_loadStyles(document); - + diff --git a/packages/sandcastle/vite.config.js b/packages/sandcastle/vite.config.js index 63b869b51973..272c89575e2d 100644 --- a/packages/sandcastle/vite.config.js +++ b/packages/sandcastle/vite.config.js @@ -37,5 +37,16 @@ const baseConfig = { return undefined; }, }, + experimental: { + renderBuiltUrl(filename, { hostId }) { + // the standalone.html file needs to stay at the root path + // for legacy reasons however the tag makes it behave + // as if it's nested inside the `/templates/` directory. + // we need to adjust the vite built asset paths to "un-nest" this change + if (hostId.endsWith("standalone.html")) { + return `../${filename}`; + } + }, + }, }; export default baseConfig; From aaf4ae1bd19887f05c031396b8d4df90e34e0a92 Mon Sep 17 00:00:00 2001 From: jjspace <8007967+jjspace@users.noreply.github.com> Date: Tue, 7 Oct 2025 13:15:20 -0400 Subject: [PATCH 16/22] move and trim build config --- .github/workflows/deploy.yml | 5 +- gulpfile.js | 118 ++++------------------------------ package.json | 1 - packages/sandcastle/README.md | 1 - scripts/build.js | 96 ++++++++++++++++++++++++++- 5 files changed, 109 insertions(+), 112 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index d7112d2f161f..9a3b50ea95e8 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -2,7 +2,7 @@ name: deploy on: push: branches-ignore: - - 'cesium.com' + - "cesium.com" - production concurrency: group: deploy-${{ github.ref }} @@ -22,14 +22,13 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_REPO: ${{ github.repository }} GITHUB_SHA: ${{ github.sha }} - BASE_URL: /cesium/${{ github.ref_name }}/ DEPLOYED_URL: https://ci-builds.cesium.com/cesium/${{ github.ref_name }}/ steps: - uses: actions/checkout@v5 - name: install node 22 uses: actions/setup-node@v5 with: - node-version: '22' + node-version: "22" - name: npm install run: npm install - name: set the version in package.json diff --git a/gulpfile.js b/gulpfile.js index 40ce2334da4a..01fb37fd2f50 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -31,9 +31,8 @@ import { createJsHintOptions, defaultESBuildOptions, buildSandcastleGallery, + buildNewSandcastleApp, } from "./scripts/build.js"; -import { fileURLToPath } from "url"; -import { buildStatic, createSandcastleConfig } from "@cesium/sandcastle"; // Determines the scope of the workspace packages. If the scope is set to cesium, the workspaces should be @cesium/engine. // This should match the scope of the dependencies of the root level package.json. @@ -291,103 +290,8 @@ export async function buildTs() { } export const buildNewSandcastle = gulp.series( - async function buildNewSandcastleApp() { - const __dirname = dirname(fileURLToPath(import.meta.url)); - let config; - if (isProduction) { - const cesiumSource = "Build/CesiumUnminified"; - const cesiumBaseUrl = "Build/CesiumUnminified"; - - config = createSandcastleConfig({ - outDir: join(__dirname, "Build/Sandcastle2"), - basePath: "", - cesiumBaseUrl: "/Build/CesiumUnminified", - cesiumVersion: version, - imports: { - cesium: { - path: "/js/Cesium.js", - typesPath: "/js/Cesium.d.ts", - }, - "@cesium/engine": { - path: "/js/engine/index.js", - typesPath: "/js/engine/index.d.ts", - }, - "@cesium/widgets": { - path: "/js/widgets/index.js", - typesPath: "/js/widgets/index.d.ts", - }, - }, - copyExtraFiles: [ - { - src: join(__dirname, `${cesiumSource}/ThirdParty`), - dest: cesiumBaseUrl, - }, - { - src: join(__dirname, `${cesiumSource}/Workers`), - dest: cesiumBaseUrl, - }, - { - src: join(__dirname, `${cesiumSource}/Assets`), - dest: cesiumBaseUrl, - }, - { - src: join(__dirname, `${cesiumSource}/Widgets`), - dest: cesiumBaseUrl, - }, - { - src: join(__dirname, `${cesiumSource}/*.(js|cjs)`), - dest: cesiumBaseUrl, - }, - { src: join(__dirname, "Apps/SampleData"), dest: "Apps" }, - { src: join(__dirname, "Apps/SampleData"), dest: "" }, - { src: join(__dirname, `Source/Cesium.(d.ts|js)`), dest: "js" }, - { - src: join(__dirname, `packages/engine/index.d.ts`), - dest: "js/engine", - }, - { - src: join(__dirname, `packages/engine/Build/Unminified/index.js`), - dest: "js/engine", - }, - { - src: join(__dirname, `packages/widgets/index.d.ts`), - dest: "js/widgets", - }, - { - src: join(__dirname, `packages/widgets/Build/Unminified/index.js`), - dest: "js/widgets", - }, - ], - }); - } else { - // Use this when the static files are hosted at a "nested" URL like in CI - const pathPrefix = (path) => - join(process.env.BASE_URL ?? "../../../", path); - - config = createSandcastleConfig({ - outDir: join(__dirname, "Apps/Sandcastle2"), - basePath: "./", - cesiumBaseUrl: pathPrefix("Build/CesiumUnminified"), - cesiumVersion: version, - commitSha: JSON.stringify(process.env.GITHUB_SHA ?? undefined), - imports: { - cesium: { - path: pathPrefix("Source/Cesium.js"), - typesPath: pathPrefix("Source/Cesium.d.ts"), - }, - "@cesium/engine": { - path: pathPrefix("packages/engine/Build/Unminified/index.js"), - typesPath: pathPrefix("packages/engine/index.d.ts"), - }, - "@cesium/widgets": { - path: pathPrefix("packages/widgets/Build/Unminified/index.js"), - typesPath: pathPrefix("packages/widgets/index.d.ts"), - }, - }, - }); - } - - return buildStatic(config); + async function buildSandcastleApp() { + return buildNewSandcastleApp(isProduction); }, async function buildGallery() { return buildSandcastleGallery(!isProduction); @@ -780,7 +684,6 @@ export const makeZip = gulp.series( "!**/*.gitignore", "!Specs/e2e/*-snapshots/**", "!Apps/Sandcastle/gallery/development/**", - "!Apps/Sandcastle2/**", ], { encoding: false, @@ -827,13 +730,18 @@ export async function deployStatus() { const deployUrl = `${devDeployUrl}`; const zipUrl = `${deployUrl}Cesium-${version}.zip`; const npmUrl = `${deployUrl}cesium-${version}.tgz`; - const coverageUrl = `${devDeployUrl}Build/Coverage/index.html`; + const coverageUrl = `${deployUrl}Build/Coverage/index.html`; return Promise.all([ - setStatus(status, deployUrl, message, "deployment"), - setStatus(status, zipUrl, message, "zip file"), - setStatus(status, npmUrl, message, "npm package"), - setStatus(status, coverageUrl, message, "coverage results"), + setStatus(status, deployUrl, message, "deploy / artifact: deployment"), + setStatus(status, zipUrl, message, "deploy / artifact: zip file"), + setStatus(status, npmUrl, message, "deploy / artifact: npm package"), + setStatus( + status, + coverageUrl, + message, + "deploy / artifact: coverage results", + ), ]); } diff --git a/package.json b/package.json index 0296468639cd..29cc47624651 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,6 @@ }, "devDependencies": { "@cesium/eslint-config": "^12.0.0", - "@cesium/sandcastle": "^0.0.2", "@playwright/test": "^1.41.1", "chokidar": "^4.0.1", "cloc": "^2.6.0-cloc", diff --git a/packages/sandcastle/README.md b/packages/sandcastle/README.md index cf96638a0074..7f7c88147595 100644 --- a/packages/sandcastle/README.md +++ b/packages/sandcastle/README.md @@ -6,7 +6,6 @@ This package is the application for Sandcastle. - `npm run dev`: run the development server - `npm run build-app`: build to static files in `/Apps/Sandcastle2` for hosting/access from the root cesium dev server - - If the env variable `BASE_URL` is set it will be used as the app's base path and prefixed on all required paths in the application. This is useful for building to a "nested" url like we do in CI where the base is `[hostname]/cesium/branch/` instead of just `[hostname]/` Linting and style is managed under the project root's scripts. diff --git a/scripts/build.js b/scripts/build.js index 0015f4421fbd..e4519ed719f1 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -675,6 +675,99 @@ export async function getSandcastleConfig() { }; } +async function importSandcastleBuildFunctions() { + // Import asynchronously, for now, because this script is not included or run in the release zip; + const buildGalleryScriptPath = path.join( + __dirname, + "../packages/sandcastle/index.js", + ); + return await import(pathToFileURL(buildGalleryScriptPath).href); +} + +export async function buildNewSandcastleApp(isProduction) { + const { join, dirname } = path; + const __dirname = dirname(fileURLToPath(import.meta.url)); + const { createSandcastleConfig, buildStatic } = + await importSandcastleBuildFunctions(); + const version = await getVersion(); + let config; + if (isProduction) { + const cesiumSource = join(__dirname, "../Build/CesiumUnminified"); + const cesiumBaseUrl = "Build/CesiumUnminified"; + + config = createSandcastleConfig({ + outDir: join(__dirname, "../Build/Sandcastle2"), + basePath: "./", + cesiumBaseUrl: "/Build/CesiumUnminified", + cesiumVersion: version, + imports: { + cesium: { + path: "/js/Cesium.js", + typesPath: "/js/Cesium.d.ts", + }, + "@cesium/engine": { + path: "/js/engine/index.js", + typesPath: "/js/engine/index.d.ts", + }, + "@cesium/widgets": { + path: "/js/widgets/index.js", + typesPath: "/js/widgets/index.d.ts", + }, + }, + copyExtraFiles: [ + { src: `${cesiumSource}/ThirdParty`, dest: cesiumBaseUrl }, + { src: `${cesiumSource}/Workers`, dest: cesiumBaseUrl }, + { src: `${cesiumSource}/Assets`, dest: cesiumBaseUrl }, + { src: `${cesiumSource}/Widgets`, dest: cesiumBaseUrl }, + { src: `${cesiumSource}/*.(js|cjs)`, dest: cesiumBaseUrl }, + { src: join(__dirname, "../Apps/SampleData"), dest: "Apps" }, + { src: join(__dirname, "../Apps/SampleData"), dest: "" }, + { src: join(__dirname, "../Source/Cesium.(d.ts|js)"), dest: "js" }, + { + src: join(__dirname, "../packages/engine/index.d.ts"), + dest: "js/engine", + }, + { + src: join(__dirname, "../packages/engine/Build/Unminified/index.js"), + dest: "js/engine", + }, + { + src: join(__dirname, "../packages/widgets/index.d.ts"), + dest: "js/widgets", + }, + { + src: join(__dirname, "../packages/widgets/Build/Unminified/index.js"), + dest: "js/widgets", + }, + ], + }); + } else { + config = createSandcastleConfig({ + outDir: join(__dirname, "../Apps/Sandcastle2"), + basePath: "./", + cesiumBaseUrl: "../../../Build/CesiumUnminified", + cesiumVersion: version, + commitSha: JSON.stringify(process.env.GITHUB_SHA ?? undefined), + imports: { + cesium: { + path: "../../../Source/Cesium.js", + typesPath: "../../../Source/Cesium.d.ts", + }, + "@cesium/engine": { + path: "../../../packages/engine/Build/Unminified/index.js", + typesPath: "../../../packages/engine/index.d.ts", + }, + "@cesium/widgets": { + path: "../../../packages/widgets/Build/Unminified/index.js", + typesPath: "../../../packages/widgets/index.d.ts", + }, + }, + }); + } + + return buildStatic(config); +} + /** * Indexes Sandcastle gallery files and writes gallery files to the configured Sandcastle output directory. * @param {boolean} [includeDevelopment=true] true if gallery items flagged as development should be included. @@ -696,8 +789,7 @@ export async function buildSandcastleGallery(includeDevelopment) { metadata, } = gallery ?? {}; - // Import asynchronously, for now, because this following script is not included in the release zip; However, this script will not be run from the release zip - const { buildGalleryList } = await import("@cesium/sandcastle"); + const { buildGalleryList } = await importSandcastleBuildFunctions(); await buildGalleryList({ rootDirectory, From b695f5de555bd4c3fb8f3eb327f3d54ffc862752 Mon Sep 17 00:00:00 2001 From: jjspace <8007967+jjspace@users.noreply.github.com> Date: Tue, 7 Oct 2025 13:15:41 -0400 Subject: [PATCH 17/22] adjust prod GH workflow --- .github/workflows/prod.yml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.github/workflows/prod.yml b/.github/workflows/prod.yml index d9489762a441..571c956aea34 100644 --- a/.github/workflows/prod.yml +++ b/.github/workflows/prod.yml @@ -41,13 +41,10 @@ jobs: run: npm install - name: build website release run: npm run website-release - - name: build apps - run: npm run build-apps - # TODO: review these steps - name: build types run: npm run build-ts - - name: build prod sandcastle - run: npm run build-prod -w packages/sandcastle -- -l warn + - name: build apps + run: npm run build-apps - name: deploy to cesium.com if: ${{ env.AWS_ACCESS_KEY_ID != '' }} # Download zip from the Github release and unzip to Build/release/ From 61d330af7da29ac4088bf59c7afa302f53e49d14 Mon Sep 17 00:00:00 2001 From: jjspace <8007967+jjspace@users.noreply.github.com> Date: Tue, 7 Oct 2025 13:29:24 -0400 Subject: [PATCH 18/22] correctly build sandcastle for zip file --- gulpfile.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/gulpfile.js b/gulpfile.js index 01fb37fd2f50..54b9697125bf 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -597,7 +597,12 @@ async function pruneScriptsForZip(packageJsonPath) { export const makeZip = gulp.series( release, - buildNewSandcastle, + async function buildSandcastleApp() { + return buildNewSandcastleApp(false); + }, + async function buildGallery() { + return buildSandcastleGallery(false); + }, async function createZipFile() { //For now we regenerate the JS glsl to force it to be unminified in the release zip //See https://github.com/CesiumGS/cesium/pull/3106#discussion_r42793558 for discussion. From f3ecf5ba963a9839705f9b2ec9fa4318a34fa184 Mon Sep 17 00:00:00 2001 From: jjspace <8007967+jjspace@users.noreply.github.com> Date: Tue, 7 Oct 2025 13:29:55 -0400 Subject: [PATCH 19/22] build sandcastle on server start when it doesn't exist --- server.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/server.js b/server.js index fa760dd3c8f2..431a5b390d91 100644 --- a/server.js +++ b/server.js @@ -1,7 +1,7 @@ import fs from "fs"; import path from "path"; import { performance } from "perf_hooks"; -import { URL } from "url"; +import { fileURLToPath, URL } from "url"; import chokidar from "chokidar"; import compression from "compression"; @@ -20,6 +20,7 @@ import { buildCesium, getSandcastleConfig, buildSandcastleGallery, + buildNewSandcastleApp, } from "./scripts/build.js"; const argv = yargs(process.argv) @@ -105,6 +106,14 @@ const throttle = (callback) => { let contexts; if (!production) { contexts = await generateDevelopmentBuild(); + const __dirname = path.dirname(fileURLToPath(import.meta.url)); + if (!fs.existsSync(path.join(__dirname, "/Apps/Sandcastle2/index.html"))) { + // Sandcastle takes a bit of time to build and is unlikely to change often + // Only build it when we detect it doesn't exist to save on dev time + console.log("Building Sandcastle..."); + await buildNewSandcastleApp(false); + await buildSandcastleGallery(); + } } const app = express(); From 00dce5a3f1969c634a69717da86186f3a97e38e2 Mon Sep 17 00:00:00 2001 From: jjspace <8007967+jjspace@users.noreply.github.com> Date: Tue, 7 Oct 2025 13:33:10 -0400 Subject: [PATCH 20/22] update release index url to new sandcastle --- index.release.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.release.html b/index.release.html index 0cf60e9f9de2..ef8ad8a45484 100644 --- a/index.release.html +++ b/index.release.html @@ -90,7 +90,7 @@

Local links

- Sandcastle + Sandcastle Cesium's live code editor and example gallery. Browse examples highlighting features of the Cesium API and edit and run them From dde0e07c3aa479414d66e0a90fe6aa962e0c5ef7 Mon Sep 17 00:00:00 2001 From: jjspace <8007967+jjspace@users.noreply.github.com> Date: Tue, 7 Oct 2025 15:47:11 -0400 Subject: [PATCH 21/22] small cleanup, remove excess changes --- gulpfile.js | 39 ++++++++++++-------------- packages/sandcastle/tsconfig.node.json | 2 -- packages/sandcastle/vite.config.dev.ts | 4 +-- server.js | 4 +++ 4 files changed, 24 insertions(+), 25 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index 54b9697125bf..1348da24ecf3 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -438,6 +438,17 @@ export async function buildDocsWatch() { return gulp.watch(sourceFiles, buildDocs); } +function combineForSandcastle() { + const outputDirectory = join("Build", "Sandcastle", "CesiumUnminified"); + return buildCesium({ + development: false, + minify: false, + removePragmas: false, + node: false, + outputDirectory: outputDirectory, + }); +} + export const websiteRelease = gulp.series( buildEngine, buildWidgets, @@ -449,23 +460,14 @@ export const websiteRelease = gulp.series( node: false, }); }, - function websiteReleaseBuildMinified() { + function () { return buildCesium({ minify: true, removePragmas: true, node: false, }); }, - function combineForSandcastle() { - const outputDirectory = join("Build", "Sandcastle", "CesiumUnminified"); - return buildCesium({ - development: false, - minify: false, - removePragmas: false, - node: false, - outputDirectory: outputDirectory, - }); - }, + combineForSandcastle, buildDocs, ); @@ -735,18 +737,13 @@ export async function deployStatus() { const deployUrl = `${devDeployUrl}`; const zipUrl = `${deployUrl}Cesium-${version}.zip`; const npmUrl = `${deployUrl}cesium-${version}.tgz`; - const coverageUrl = `${deployUrl}Build/Coverage/index.html`; + const coverageUrl = `${devDeployUrl}Build/Coverage/index.html`; return Promise.all([ - setStatus(status, deployUrl, message, "deploy / artifact: deployment"), - setStatus(status, zipUrl, message, "deploy / artifact: zip file"), - setStatus(status, npmUrl, message, "deploy / artifact: npm package"), - setStatus( - status, - coverageUrl, - message, - "deploy / artifact: coverage results", - ), + setStatus(status, deployUrl, message, "deployment"), + setStatus(status, zipUrl, message, "zip file"), + setStatus(status, npmUrl, message, "npm package"), + setStatus(status, coverageUrl, message, "coverage results"), ]); } diff --git a/packages/sandcastle/tsconfig.node.json b/packages/sandcastle/tsconfig.node.json index 8acbc73320b3..57179eb28b9c 100644 --- a/packages/sandcastle/tsconfig.node.json +++ b/packages/sandcastle/tsconfig.node.json @@ -21,8 +21,6 @@ "noUncheckedSideEffectImports": true }, "include": [ - "vite.config.app.ts", "vite.config.dev.ts", - "vite.config.prod.ts", ] } diff --git a/packages/sandcastle/vite.config.dev.ts b/packages/sandcastle/vite.config.dev.ts index 1c6112b4b793..9d97ace247ec 100644 --- a/packages/sandcastle/vite.config.dev.ts +++ b/packages/sandcastle/vite.config.dev.ts @@ -13,7 +13,7 @@ function getCesiumVersion() { const cesiumSource = "../../Build/CesiumUnminified"; const cesiumBaseUrl = "Build/CesiumUnminified"; -const newConfig = createSandcastleConfig({ +const config = createSandcastleConfig({ outDir: join(__dirname, "../../Build/Sandcastle2"), basePath: "", cesiumBaseUrl: "/Build/CesiumUnminified", @@ -61,5 +61,5 @@ export default defineConfig(({ command }) => { if (command === "build") { throw Error("This config should not be used to build!"); } - return newConfig; + return config; }); diff --git a/server.js b/server.js index 431a5b390d91..b897bb594c83 100644 --- a/server.js +++ b/server.js @@ -111,8 +111,12 @@ const throttle = (callback) => { // Sandcastle takes a bit of time to build and is unlikely to change often // Only build it when we detect it doesn't exist to save on dev time console.log("Building Sandcastle..."); + const startTime = performance.now(); await buildNewSandcastleApp(false); await buildSandcastleGallery(); + console.log( + `Sandcastle built in ${formatTimeSinceInSeconds(startTime)} seconds.`, + ); } } From fd8540856ebf8d3f07b685d1c0f97de3be888854 Mon Sep 17 00:00:00 2001 From: jjspace <8007967+jjspace@users.noreply.github.com> Date: Tue, 7 Oct 2025 16:04:03 -0400 Subject: [PATCH 22/22] adjust config structure --- packages/sandcastle/vite.config.dev.ts | 103 ++++++++++++++----------- 1 file changed, 57 insertions(+), 46 deletions(-) diff --git a/packages/sandcastle/vite.config.dev.ts b/packages/sandcastle/vite.config.dev.ts index 9d97ace247ec..b9eaa5d79370 100644 --- a/packages/sandcastle/vite.config.dev.ts +++ b/packages/sandcastle/vite.config.dev.ts @@ -10,56 +10,67 @@ function getCesiumVersion() { const { version } = JSON.parse(data); return version; } -const cesiumSource = "../../Build/CesiumUnminified"; -const cesiumBaseUrl = "Build/CesiumUnminified"; - -const config = createSandcastleConfig({ - outDir: join(__dirname, "../../Build/Sandcastle2"), - basePath: "", - cesiumBaseUrl: "/Build/CesiumUnminified", - cesiumVersion: getCesiumVersion(), - imports: { - cesium: { - path: "/Source/Cesium.js", - typesPath: "/Source/Cesium.d.ts", - }, - "@cesium/engine": { - path: "/packages/engine/Build/Unminified/index.js", - typesPath: "/packages/engine/index.d.ts", - }, - "@cesium/widgets": { - path: "/packages/widgets/Build/Unminified/index.js", - typesPath: "/packages/widgets/index.d.ts", - }, - }, - // IN DEV THIS DOES NOT COPY FILES it simply sets up in-memory server routes - // to the correct locations on disk in the parent directories - // This config should not be used for the actual build, only development of Sandcastle itself - copyExtraFiles: [ - { src: join(__dirname, `${cesiumSource}/ThirdParty`), dest: cesiumBaseUrl }, - { src: join(__dirname, `${cesiumSource}/Workers`), dest: cesiumBaseUrl }, - { src: join(__dirname, `${cesiumSource}/Assets`), dest: cesiumBaseUrl }, - { src: join(__dirname, `${cesiumSource}/Widgets`), dest: cesiumBaseUrl }, - { src: join(__dirname, `${cesiumSource}/*.(js|cjs)`), dest: cesiumBaseUrl }, - { src: join(__dirname, "../../Apps/SampleData"), dest: "Apps" }, - { src: join(__dirname, "../../Apps/SampleData"), dest: "" }, - { src: join(__dirname, `../../Source/Cesium.(d.ts|js)`), dest: "Source" }, - { src: join(__dirname, `../engine/index.d.ts`), dest: "packages/engine" }, - { - src: join(__dirname, `../engine/Build/Unminified/index.js`), - dest: "packages/engine/Build/Unminified", - }, - { src: join(__dirname, `../widgets/index.d.ts`), dest: "packages/widgets" }, - { - src: join(__dirname, `../widgets/Build/Unminified/index.js`), - dest: "packages/widgets/Build/Unminified", - }, - ], -}); export default defineConfig(({ command }) => { if (command === "build") { throw Error("This config should not be used to build!"); } + + const cesiumSource = "../../Build/CesiumUnminified"; + const cesiumBaseUrl = "Build/CesiumUnminified"; + + const config = createSandcastleConfig({ + outDir: join(__dirname, "../../Build/Sandcastle2"), + basePath: "", + cesiumBaseUrl: "/Build/CesiumUnminified", + cesiumVersion: getCesiumVersion(), + imports: { + cesium: { + path: "/Source/Cesium.js", + typesPath: "/Source/Cesium.d.ts", + }, + "@cesium/engine": { + path: "/packages/engine/Build/Unminified/index.js", + typesPath: "/packages/engine/index.d.ts", + }, + "@cesium/widgets": { + path: "/packages/widgets/Build/Unminified/index.js", + typesPath: "/packages/widgets/index.d.ts", + }, + }, + // IN DEV THIS DOES NOT COPY FILES it simply sets up in-memory server routes + // to the correct locations on disk in the parent directories + // This config should not be used for the actual build, only development of Sandcastle itself + copyExtraFiles: [ + { + src: join(__dirname, `${cesiumSource}/ThirdParty`), + dest: cesiumBaseUrl, + }, + { src: join(__dirname, `${cesiumSource}/Workers`), dest: cesiumBaseUrl }, + { src: join(__dirname, `${cesiumSource}/Assets`), dest: cesiumBaseUrl }, + { src: join(__dirname, `${cesiumSource}/Widgets`), dest: cesiumBaseUrl }, + { + src: join(__dirname, `${cesiumSource}/*.(js|cjs)`), + dest: cesiumBaseUrl, + }, + { src: join(__dirname, "../../Apps/SampleData"), dest: "Apps" }, + { src: join(__dirname, "../../Apps/SampleData"), dest: "" }, + { src: join(__dirname, `../../Source/Cesium.(d.ts|js)`), dest: "Source" }, + { src: join(__dirname, `../engine/index.d.ts`), dest: "packages/engine" }, + { + src: join(__dirname, `../engine/Build/Unminified/index.js`), + dest: "packages/engine/Build/Unminified", + }, + { + src: join(__dirname, `../widgets/index.d.ts`), + dest: "packages/widgets", + }, + { + src: join(__dirname, `../widgets/Build/Unminified/index.js`), + dest: "packages/widgets/Build/Unminified", + }, + ], + }); + return config; });