playcademy 0.12.2 → 0.12.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -47,6 +47,9 @@
47
47
  </svg>
48
48
  </button>
49
49
 
50
+ <!-- Pixel trail container -->
51
+ <div id="pixelTrail" class="absolute inset-0 z-0"></div>
52
+
50
53
  <!-- Subtle background enhancements: vignette + grid (theme-aware) -->
51
54
  <div class="pointer-events-none absolute inset-0 z-0">
52
55
  <!-- Light mode vignette (white on black) -->
@@ -165,5 +168,98 @@
165
168
  localStorage.setItem('theme', willBeDark ? 'dark' : 'light')
166
169
  })
167
170
  </script>
171
+
172
+ <script>
173
+ // Pixel Trail Effect
174
+ const pixelTrail = document.getElementById('pixelTrail')
175
+ const PIXEL_SIZE = 20 // px
176
+ const FADE_DURATION = 500 // ms
177
+
178
+ let pixelGrid = {}
179
+ let columns = 0
180
+ let rows = 0
181
+
182
+ function initPixelGrid() {
183
+ const width = window.innerWidth
184
+ const height = window.innerHeight
185
+ columns = Math.ceil(width / PIXEL_SIZE)
186
+ rows = Math.ceil(height / PIXEL_SIZE)
187
+
188
+ // Clear existing grid
189
+ pixelTrail.innerHTML = ''
190
+ pixelGrid = {}
191
+
192
+ // Create pixel grid
193
+ for (let row = 0; row < rows; row++) {
194
+ for (let col = 0; col < columns; col++) {
195
+ const pixel = document.createElement('div')
196
+ pixel.className = 'absolute pointer-events-none transition-opacity'
197
+ pixel.style.width = `${PIXEL_SIZE}px`
198
+ pixel.style.height = `${PIXEL_SIZE}px`
199
+ pixel.style.left = `${col * PIXEL_SIZE}px`
200
+ pixel.style.top = `${row * PIXEL_SIZE}px`
201
+ pixel.style.opacity = '0'
202
+ pixel.style.transitionDuration = `${FADE_DURATION}ms`
203
+
204
+ // Add a subtle background color based on theme
205
+ const isDark = html.classList.contains('dark')
206
+ pixel.style.backgroundColor = isDark
207
+ ? 'rgba(0, 0, 0, 0.4)'
208
+ : 'rgba(255, 255, 255, 0.4)'
209
+
210
+ pixelTrail.appendChild(pixel)
211
+ pixelGrid[`${col}-${row}`] = pixel
212
+ }
213
+ }
214
+ }
215
+
216
+ function animatePixel(col, row) {
217
+ const key = `${col}-${row}`
218
+ const pixel = pixelGrid[key]
219
+ if (!pixel) return
220
+
221
+ // Cancel any ongoing animation
222
+ clearTimeout(pixel.fadeTimeout)
223
+
224
+ // Show pixel
225
+ pixel.style.opacity = '1'
226
+
227
+ // Fade out after duration
228
+ pixel.fadeTimeout = setTimeout(() => {
229
+ pixel.style.opacity = '0'
230
+ }, FADE_DURATION)
231
+ }
232
+
233
+ function handleMouseMove(e) {
234
+ const col = Math.floor(e.clientX / PIXEL_SIZE)
235
+ const row = Math.floor(e.clientY / PIXEL_SIZE)
236
+ animatePixel(col, row)
237
+ }
238
+
239
+ // Initialize on load
240
+ initPixelGrid()
241
+
242
+ // Add event listeners
243
+ document.body.addEventListener('mousemove', handleMouseMove)
244
+
245
+ // Reinitialize on window resize
246
+ let resizeTimeout
247
+ window.addEventListener('resize', () => {
248
+ clearTimeout(resizeTimeout)
249
+ resizeTimeout = setTimeout(initPixelGrid, 200)
250
+ })
251
+
252
+ // Update pixel colors when theme changes
253
+ const originalSetTheme = setTheme
254
+ setTheme = function (isDark) {
255
+ originalSetTheme(isDark)
256
+ // Update all pixel colors
257
+ Object.values(pixelGrid).forEach(pixel => {
258
+ pixel.style.backgroundColor = isDark
259
+ ? 'rgba(0, 0, 0, 0.4)'
260
+ : 'rgba(255, 255, 255, 0.4)'
261
+ })
262
+ }
263
+ </script>
168
264
  </body>
169
265
  </html>
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { UserInfo, ApiKey, BackendDeploymentResponse } from '@playcademy/data/types';
2
+ import { SchemaInfo } from '@playcademy/cloudflare';
2
3
  import { OrganizationConfig, CourseConfig, ComponentConfig, ResourceConfig, ComponentResourceConfig } from '@playcademy/timeback/types';
3
4
  export { ComponentConfig, ComponentResourceConfig, CourseConfig, DerivedComponentConfig, DerivedComponentResourceConfig, DerivedCourseConfig, DerivedOrganizationConfig, DerivedResourceConfig, DerivedTimebackConfig, OrganizationConfig, ResourceConfig, TimebackGrade, TimebackSourcedIds, TimebackSubject } from '@playcademy/timeback/types';
4
5
  import { PlaycademyClient } from '@playcademy/sdk';
@@ -222,6 +223,32 @@ interface PlaycademyConfig {
222
223
  integrations?: IntegrationsConfig;
223
224
  }
224
225
 
226
+ /**
227
+ * Resource bindings for backend deployment
228
+ * Provider-agnostic abstraction for cloud resources
229
+ */
230
+ interface BackendResourceBindings {
231
+ /** SQL database instances to create and bind (maps to D1 on Cloudflare) */
232
+ database?: string[];
233
+ /** Key-value store namespaces to create and bind (maps to KV on Cloudflare) */
234
+ keyValue?: string[];
235
+ /** Object storage buckets to bind (maps to R2 on Cloudflare) */
236
+ bucket?: string[];
237
+ }
238
+ /**
239
+ * Backend deployment bundle for uploading to Playcademy platform
240
+ */
241
+ interface BackendDeploymentBundle {
242
+ /** Bundled JavaScript code ready for deployment */
243
+ code: string;
244
+ /** Game configuration */
245
+ config: PlaycademyConfig;
246
+ /** Optional resource bindings (database, storage, etc.) */
247
+ bindings?: BackendResourceBindings;
248
+ /** Optional schema information for database setup */
249
+ schema?: SchemaInfo;
250
+ }
251
+
225
252
  type GameMetadata = {
226
253
  description?: string;
227
254
  emoji?: string;
@@ -620,6 +647,43 @@ interface UpdateExistingGameOptions {
620
647
  previousBackendSize?: number;
621
648
  verbose?: boolean;
622
649
  }
650
+ /**
651
+ * Backend bundle with CLI-specific metadata
652
+ * Extends SDK's BackendDeploymentBundle with route discovery metadata
653
+ */
654
+ interface BackendBundle extends BackendDeploymentBundle {
655
+ /** Discovered custom routes (for CLI display and tracking) */
656
+ customRoutes: Array<{
657
+ path: string;
658
+ file: string;
659
+ methods?: string[];
660
+ }>;
661
+ }
662
+ /**
663
+ * Bundle configuration options
664
+ */
665
+ interface BundleOptions {
666
+ /** Include source map for debugging (default: false) */
667
+ sourcemap?: boolean;
668
+ /** Minify output (default: false) */
669
+ minify?: boolean;
670
+ }
671
+ /**
672
+ * Resolved paths for bundling embedded sources
673
+ * Used to support both monorepo development and published package scenarios
674
+ */
675
+ interface EmbeddedSourcePaths {
676
+ /** Whether we're running from a published package (vs monorepo dev) */
677
+ isBuiltPackage: boolean;
678
+ /** Path to edge-play sources (embedded or monorepo) */
679
+ edgePlaySrc: string;
680
+ /** Path to constants entry point (embedded or monorepo) */
681
+ constantsEntry: string;
682
+ /** User's workspace node_modules */
683
+ workspaceNodeModules: string;
684
+ /** CLI's node_modules (monorepo root in dev, same as workspace in published) */
685
+ cliNodeModules: string;
686
+ }
623
687
 
624
688
  /**
625
689
  * Types for the CLI local HTTP server that handles OAuth callbacks
@@ -801,4 +865,4 @@ interface DeploymentDiffOptions {
801
865
  integrations?: IntegrationsDiff;
802
866
  }
803
867
 
804
- export type { ApiConfig, ApiErrorResponse, ApiKeyListItem, ApiKeyWithSecret, ApiRequestOptions, AuthProfile, AuthStore, BackendDeploymentWithHash, BackendDiff, BuildDiff, CallbackServerResult, ConfigDiff, CreateApiKeyResponse, CustomRoutesIntegrationOptions, DatabaseIntegrationOptions, DeployConfig, DeployNewGameOptions, DeployedGameInfo, DeploymentChanges, DeploymentContext, DeploymentDiffOptions, DeploymentPlan, DeploymentResult, EnvironmentAuthProfiles, GameStore, IntegrationChangeDetector, IntegrationChangeDetectors, IntegrationConfigChange, IntegrationsConfig, IntegrationsDiff, LoginCredentials, LoginResponse, PlaycademyConfig, PreviewOptions, PreviewResponse, SignInResponse, SsoCallbackData, TimebackIntegrationConfig, TokenType, UpdateExistingGameOptions };
868
+ export type { ApiConfig, ApiErrorResponse, ApiKeyListItem, ApiKeyWithSecret, ApiRequestOptions, AuthProfile, AuthStore, BackendBundle, BackendDeploymentWithHash, BackendDiff, BuildDiff, BundleOptions, CallbackServerResult, ConfigDiff, CreateApiKeyResponse, CustomRoutesIntegrationOptions, DatabaseIntegrationOptions, DeployConfig, DeployNewGameOptions, DeployedGameInfo, DeploymentChanges, DeploymentContext, DeploymentDiffOptions, DeploymentPlan, DeploymentResult, EmbeddedSourcePaths, EnvironmentAuthProfiles, GameStore, IntegrationChangeDetector, IntegrationChangeDetectors, IntegrationConfigChange, IntegrationsConfig, IntegrationsDiff, LoginCredentials, LoginResponse, PlaycademyConfig, PreviewOptions, PreviewResponse, SignInResponse, SsoCallbackData, TimebackIntegrationConfig, TokenType, UpdateExistingGameOptions };
package/dist/index.js CHANGED
@@ -5222,9 +5222,8 @@ async function registerCustomRoutes(app, routes) {
5222
5222
  }
5223
5223
 
5224
5224
  // src/lib/deploy/bundle.ts
5225
- var entryTemplate = entry_default;
5226
- async function bundleBackend(config, options = {}) {
5227
- const esbuild = await import("esbuild");
5225
+ var entryTemplate = entry_default.toString();
5226
+ async function discoverCustomRoutes(config) {
5228
5227
  const workspace = getWorkspace();
5229
5228
  const customRoutesConfig = config.integrations?.customRoutes;
5230
5229
  const customRoutesDir = typeof customRoutesConfig === "object" && customRoutesConfig.directory || DEFAULT_API_ROUTES_DIRECTORY;
@@ -5235,76 +5234,115 @@ async function bundleBackend(config, options = {}) {
5235
5234
  // Use relative path (e.g., 'server/api/test.ts'), not absolute
5236
5235
  methods: r.methods
5237
5236
  }));
5238
- const bundleConfig = {
5239
- ...config,
5240
- customRoutes: customRouteData
5241
- };
5242
- const entryCode = generateEntryCode(customRouteData, customRoutesDir);
5237
+ return { customRouteData, customRoutesDir };
5238
+ }
5239
+ function resolveEmbeddedSourcePaths() {
5240
+ const workspace = getWorkspace();
5243
5241
  const distDir = new URL(".", import.meta.url).pathname;
5244
5242
  const embeddedEdgeSrc = join6(distDir, "edge-play", "src");
5243
+ const isBuiltPackage = existsSync7(embeddedEdgeSrc);
5245
5244
  const monorepoRoot = getMonorepoRoot();
5246
5245
  const monorepoEdgeSrc = join6(monorepoRoot, "packages/edge-play/src");
5247
- const isBuiltPackage = existsSync7(embeddedEdgeSrc);
5248
5246
  const edgePlaySrc = isBuiltPackage ? embeddedEdgeSrc : monorepoEdgeSrc;
5249
5247
  const cliPackageRoot = isBuiltPackage ? join6(distDir, "../../..") : join6(monorepoRoot, "packages/cli");
5250
5248
  const cliNodeModules = isBuiltPackage ? join6(cliPackageRoot, "node_modules") : monorepoRoot;
5251
5249
  const workspaceNodeModules = join6(workspace, "node_modules");
5252
- const constantsEntry = isBuiltPackage ? join6(cliPackageRoot, "dist", "vendor", "constants", "index.ts") : join6(monorepoRoot, "packages", "constants", "src", "index.ts");
5253
- const result = await esbuild.build({
5250
+ const constantsEntry = isBuiltPackage ? join6(embeddedEdgeSrc, "..", "..", "constants", "src", "index.ts") : join6(monorepoRoot, "packages", "constants", "src", "index.ts");
5251
+ return {
5252
+ isBuiltPackage,
5253
+ edgePlaySrc,
5254
+ constantsEntry,
5255
+ workspaceNodeModules,
5256
+ cliNodeModules
5257
+ };
5258
+ }
5259
+ function createEsbuildConfig(entryCode, paths, bundleConfig, customRoutesDir, options) {
5260
+ const workspace = getWorkspace();
5261
+ const { isBuiltPackage, edgePlaySrc, constantsEntry, workspaceNodeModules, cliNodeModules } = paths;
5262
+ return {
5263
+ // ──── Input Configuration ────
5254
5264
  stdin: {
5255
5265
  contents: entryCode,
5266
+ // Generated entry code with custom route imports
5256
5267
  resolveDir: edgePlaySrc,
5257
- // For relative imports like ./register-routes
5268
+ // Resolve relative imports from edge-play/src
5258
5269
  loader: "ts"
5270
+ // Treat input as TypeScript
5259
5271
  },
5272
+ // ──── Output Configuration ────
5260
5273
  bundle: true,
5274
+ // Bundle all dependencies into single file
5261
5275
  format: "esm",
5276
+ // Output ES modules (required for Cloudflare Workers)
5262
5277
  platform: "browser",
5278
+ // Workers use browser APIs, not Node.js
5263
5279
  target: "es2022",
5280
+ // Modern JavaScript for Workers runtime
5264
5281
  write: false,
5282
+ // Return code as string (don't write to disk)
5265
5283
  sourcemap: options.sourcemap ? "inline" : false,
5266
5284
  minify: options.minify || false,
5267
5285
  logLevel: "error",
5268
- nodePaths: [workspaceNodeModules, cliNodeModules, join6(cliPackageRoot, "..")],
5269
- // Check workspace first, then CLI package and project root
5270
- loader: {
5271
- ".ts": "ts"
5272
- },
5286
+ // Only show errors (suppress warnings)
5287
+ // ──── Module Resolution ────
5288
+ // Tell esbuild where to find node_modules for bare imports
5289
+ // In dev: Need both workspace and monorepo root (hoisted deps)
5290
+ // In published: Both point to same location (/user-project/node_modules)
5291
+ nodePaths: isBuiltPackage ? [workspaceNodeModules] : [workspaceNodeModules, cliNodeModules],
5292
+ // ──── Build-time Constants ────
5293
+ // Inject the Playcademy config as a global constant
5294
+ // Code can access it via: const config = PLAYCADEMY_CONFIG
5273
5295
  define: {
5274
5296
  PLAYCADEMY_CONFIG: JSON.stringify(bundleConfig)
5275
5297
  },
5298
+ // ──── Import Aliases ────
5276
5299
  alias: {
5277
- // Map workspace-only package to embedded constants for published CLI
5300
+ // ┌─ Workspace-only package resolution ─────────────────────────────┐
5301
+ // │ @playcademy/constants is a workspace package that users don't │
5302
+ // │ install. We embed it in dist/ and alias imports to point there. │
5303
+ // └─────────────────────────────────────────────────────────────────┘
5278
5304
  "@playcademy/constants": constantsEntry,
5279
- /**
5280
- * @game-api alias maps to the user's custom routes directory
5281
- *
5282
- * This allows custom route imports in the entry code:
5283
- * import * as customRoute0 from '@game-api/hello.ts'
5284
- *
5285
- * The alias resolves to the absolute path of the custom routes directory in the
5286
- * user's game project (configured via integrations.customRoutes.directory), enabling esbuild
5287
- * to bundle custom routes into the worker.
5288
- */
5305
+ // ┌─ User's custom routes ──────────────────────────────────────────┐
5306
+ // @game-api is a virtual module that maps to the user's API dir. │
5307
+ // │ Example: import * as route from '@game-api/hello.ts' │
5308
+ // Resolves to: /user-project/server/api/hello.ts │
5309
+ // └─────────────────────────────────────────────────────────────────┘
5289
5310
  "@game-api": join6(workspace, customRoutesDir),
5290
- /**
5291
- * Node.js module polyfills for Cloudflare Workers environment
5292
- *
5293
- * Cloudflare Workers don't have Node.js APIs (fs, path, os, etc.).
5294
- * These aliases redirect Node.js imports to a polyfill that throws helpful errors.
5295
- *
5296
- * This prevents bundling errors and provides clear runtime messages if
5297
- * user code accidentally imports Node.js modules that won't work in Workers.
5298
- */
5311
+ // ┌─ Node.js polyfills for Cloudflare Workers ──────────────────────┐
5312
+ // Workers don't have fs, path, os, etc. Redirect to polyfills │
5313
+ // │ that throw helpful errors if user code tries to use them.
5314
+ // └─────────────────────────────────────────────────────────────────┘
5299
5315
  fs: join6(edgePlaySrc, "polyfills.js"),
5300
5316
  "fs/promises": join6(edgePlaySrc, "polyfills.js"),
5301
5317
  path: join6(edgePlaySrc, "polyfills.js"),
5302
5318
  os: join6(edgePlaySrc, "polyfills.js"),
5303
5319
  process: join6(edgePlaySrc, "polyfills.js")
5304
5320
  },
5321
+ // ──── Build Plugins ────
5305
5322
  plugins: [textLoaderPlugin()],
5323
+ // Support Bun's 'with { type: "text" }' imports
5324
+ // ──── External Dependencies ────
5306
5325
  external: []
5307
- });
5326
+ // Bundle everything (no externals for Workers)
5327
+ };
5328
+ }
5329
+ async function bundleBackend(config, options = {}) {
5330
+ const esbuild = await import("esbuild");
5331
+ const { customRouteData, customRoutesDir } = await discoverCustomRoutes(config);
5332
+ const bundleConfig = {
5333
+ ...config,
5334
+ customRoutes: customRouteData
5335
+ };
5336
+ const entryCode = generateEntryCode(customRouteData, customRoutesDir);
5337
+ const paths = resolveEmbeddedSourcePaths();
5338
+ const buildConfig = createEsbuildConfig(
5339
+ entryCode,
5340
+ paths,
5341
+ bundleConfig,
5342
+ customRoutesDir,
5343
+ options
5344
+ );
5345
+ const result = await esbuild.build(buildConfig);
5308
5346
  if (!result.outputFiles?.[0]) {
5309
5347
  throw new Error("Backend bundling failed: no output");
5310
5348
  }
@@ -5344,6 +5382,7 @@ function generateEntryCode(customRoutes, customRoutesDir) {
5344
5382
  init_core();
5345
5383
  import { existsSync as existsSync9 } from "fs";
5346
5384
  import { join as join8 } from "path";
5385
+ import { generateSQLiteDrizzleJson, generateSQLiteMigration } from "drizzle-kit/api";
5347
5386
 
5348
5387
  // src/lib/init/prompts.ts
5349
5388
  init_constants3();
@@ -5603,7 +5642,6 @@ async function getSchemaInfo(previousSchemaSnapshot) {
5603
5642
  return null;
5604
5643
  }
5605
5644
  try {
5606
- const { generateSQLiteDrizzleJson, generateSQLiteMigration } = await import("drizzle-kit/api");
5607
5645
  const schemaModule = await import(schemaPath);
5608
5646
  const currentSchema = schemaModule.default || schemaModule;
5609
5647
  const nextJson = await generateSQLiteDrizzleJson(currentSchema);
package/dist/utils.js CHANGED
@@ -1162,9 +1162,8 @@ async function transpileRoute(filePath) {
1162
1162
  }
1163
1163
 
1164
1164
  // src/lib/deploy/bundle.ts
1165
- var entryTemplate = entry_default;
1166
- async function bundleBackend(config, options = {}) {
1167
- const esbuild = await import("esbuild");
1165
+ var entryTemplate = entry_default.toString();
1166
+ async function discoverCustomRoutes(config) {
1168
1167
  const workspace = getWorkspace();
1169
1168
  const customRoutesConfig = config.integrations?.customRoutes;
1170
1169
  const customRoutesDir = typeof customRoutesConfig === "object" && customRoutesConfig.directory || DEFAULT_API_ROUTES_DIRECTORY;
@@ -1175,76 +1174,115 @@ async function bundleBackend(config, options = {}) {
1175
1174
  // Use relative path (e.g., 'server/api/test.ts'), not absolute
1176
1175
  methods: r.methods
1177
1176
  }));
1178
- const bundleConfig = {
1179
- ...config,
1180
- customRoutes: customRouteData
1181
- };
1182
- const entryCode = generateEntryCode(customRouteData, customRoutesDir);
1177
+ return { customRouteData, customRoutesDir };
1178
+ }
1179
+ function resolveEmbeddedSourcePaths() {
1180
+ const workspace = getWorkspace();
1183
1181
  const distDir = new URL(".", import.meta.url).pathname;
1184
1182
  const embeddedEdgeSrc = join2(distDir, "edge-play", "src");
1183
+ const isBuiltPackage = existsSync2(embeddedEdgeSrc);
1185
1184
  const monorepoRoot = getMonorepoRoot();
1186
1185
  const monorepoEdgeSrc = join2(monorepoRoot, "packages/edge-play/src");
1187
- const isBuiltPackage = existsSync2(embeddedEdgeSrc);
1188
1186
  const edgePlaySrc = isBuiltPackage ? embeddedEdgeSrc : monorepoEdgeSrc;
1189
1187
  const cliPackageRoot = isBuiltPackage ? join2(distDir, "../../..") : join2(monorepoRoot, "packages/cli");
1190
1188
  const cliNodeModules = isBuiltPackage ? join2(cliPackageRoot, "node_modules") : monorepoRoot;
1191
1189
  const workspaceNodeModules = join2(workspace, "node_modules");
1192
- const constantsEntry = isBuiltPackage ? join2(cliPackageRoot, "dist", "vendor", "constants", "index.ts") : join2(monorepoRoot, "packages", "constants", "src", "index.ts");
1193
- const result = await esbuild.build({
1190
+ const constantsEntry = isBuiltPackage ? join2(embeddedEdgeSrc, "..", "..", "constants", "src", "index.ts") : join2(monorepoRoot, "packages", "constants", "src", "index.ts");
1191
+ return {
1192
+ isBuiltPackage,
1193
+ edgePlaySrc,
1194
+ constantsEntry,
1195
+ workspaceNodeModules,
1196
+ cliNodeModules
1197
+ };
1198
+ }
1199
+ function createEsbuildConfig(entryCode, paths, bundleConfig, customRoutesDir, options) {
1200
+ const workspace = getWorkspace();
1201
+ const { isBuiltPackage, edgePlaySrc, constantsEntry, workspaceNodeModules, cliNodeModules } = paths;
1202
+ return {
1203
+ // ──── Input Configuration ────
1194
1204
  stdin: {
1195
1205
  contents: entryCode,
1206
+ // Generated entry code with custom route imports
1196
1207
  resolveDir: edgePlaySrc,
1197
- // For relative imports like ./register-routes
1208
+ // Resolve relative imports from edge-play/src
1198
1209
  loader: "ts"
1210
+ // Treat input as TypeScript
1199
1211
  },
1212
+ // ──── Output Configuration ────
1200
1213
  bundle: true,
1214
+ // Bundle all dependencies into single file
1201
1215
  format: "esm",
1216
+ // Output ES modules (required for Cloudflare Workers)
1202
1217
  platform: "browser",
1218
+ // Workers use browser APIs, not Node.js
1203
1219
  target: "es2022",
1220
+ // Modern JavaScript for Workers runtime
1204
1221
  write: false,
1222
+ // Return code as string (don't write to disk)
1205
1223
  sourcemap: options.sourcemap ? "inline" : false,
1206
1224
  minify: options.minify || false,
1207
1225
  logLevel: "error",
1208
- nodePaths: [workspaceNodeModules, cliNodeModules, join2(cliPackageRoot, "..")],
1209
- // Check workspace first, then CLI package and project root
1210
- loader: {
1211
- ".ts": "ts"
1212
- },
1226
+ // Only show errors (suppress warnings)
1227
+ // ──── Module Resolution ────
1228
+ // Tell esbuild where to find node_modules for bare imports
1229
+ // In dev: Need both workspace and monorepo root (hoisted deps)
1230
+ // In published: Both point to same location (/user-project/node_modules)
1231
+ nodePaths: isBuiltPackage ? [workspaceNodeModules] : [workspaceNodeModules, cliNodeModules],
1232
+ // ──── Build-time Constants ────
1233
+ // Inject the Playcademy config as a global constant
1234
+ // Code can access it via: const config = PLAYCADEMY_CONFIG
1213
1235
  define: {
1214
1236
  PLAYCADEMY_CONFIG: JSON.stringify(bundleConfig)
1215
1237
  },
1238
+ // ──── Import Aliases ────
1216
1239
  alias: {
1217
- // Map workspace-only package to embedded constants for published CLI
1240
+ // ┌─ Workspace-only package resolution ─────────────────────────────┐
1241
+ // │ @playcademy/constants is a workspace package that users don't │
1242
+ // │ install. We embed it in dist/ and alias imports to point there. │
1243
+ // └─────────────────────────────────────────────────────────────────┘
1218
1244
  "@playcademy/constants": constantsEntry,
1219
- /**
1220
- * @game-api alias maps to the user's custom routes directory
1221
- *
1222
- * This allows custom route imports in the entry code:
1223
- * import * as customRoute0 from '@game-api/hello.ts'
1224
- *
1225
- * The alias resolves to the absolute path of the custom routes directory in the
1226
- * user's game project (configured via integrations.customRoutes.directory), enabling esbuild
1227
- * to bundle custom routes into the worker.
1228
- */
1245
+ // ┌─ User's custom routes ──────────────────────────────────────────┐
1246
+ // @game-api is a virtual module that maps to the user's API dir. │
1247
+ // │ Example: import * as route from '@game-api/hello.ts' │
1248
+ // Resolves to: /user-project/server/api/hello.ts │
1249
+ // └─────────────────────────────────────────────────────────────────┘
1229
1250
  "@game-api": join2(workspace, customRoutesDir),
1230
- /**
1231
- * Node.js module polyfills for Cloudflare Workers environment
1232
- *
1233
- * Cloudflare Workers don't have Node.js APIs (fs, path, os, etc.).
1234
- * These aliases redirect Node.js imports to a polyfill that throws helpful errors.
1235
- *
1236
- * This prevents bundling errors and provides clear runtime messages if
1237
- * user code accidentally imports Node.js modules that won't work in Workers.
1238
- */
1251
+ // ┌─ Node.js polyfills for Cloudflare Workers ──────────────────────┐
1252
+ // Workers don't have fs, path, os, etc. Redirect to polyfills │
1253
+ // │ that throw helpful errors if user code tries to use them.
1254
+ // └─────────────────────────────────────────────────────────────────┘
1239
1255
  fs: join2(edgePlaySrc, "polyfills.js"),
1240
1256
  "fs/promises": join2(edgePlaySrc, "polyfills.js"),
1241
1257
  path: join2(edgePlaySrc, "polyfills.js"),
1242
1258
  os: join2(edgePlaySrc, "polyfills.js"),
1243
1259
  process: join2(edgePlaySrc, "polyfills.js")
1244
1260
  },
1261
+ // ──── Build Plugins ────
1245
1262
  plugins: [textLoaderPlugin()],
1263
+ // Support Bun's 'with { type: "text" }' imports
1264
+ // ──── External Dependencies ────
1246
1265
  external: []
1247
- });
1266
+ // Bundle everything (no externals for Workers)
1267
+ };
1268
+ }
1269
+ async function bundleBackend(config, options = {}) {
1270
+ const esbuild = await import("esbuild");
1271
+ const { customRouteData, customRoutesDir } = await discoverCustomRoutes(config);
1272
+ const bundleConfig = {
1273
+ ...config,
1274
+ customRoutes: customRouteData
1275
+ };
1276
+ const entryCode = generateEntryCode(customRouteData, customRoutesDir);
1277
+ const paths = resolveEmbeddedSourcePaths();
1278
+ const buildConfig = createEsbuildConfig(
1279
+ entryCode,
1280
+ paths,
1281
+ bundleConfig,
1282
+ customRoutesDir,
1283
+ options
1284
+ );
1285
+ const result = await esbuild.build(buildConfig);
1248
1286
  if (!result.outputFiles?.[0]) {
1249
1287
  throw new Error("Backend bundling failed: no output");
1250
1288
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "playcademy",
3
- "version": "0.12.2",
3
+ "version": "0.12.4",
4
4
  "type": "module",
5
5
  "module": "./dist/index.js",
6
6
  "main": "./dist/index.js",