playcademy 0.16.3 → 0.16.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.
package/dist/cli.js CHANGED
@@ -653,7 +653,7 @@ function setupGlobalErrorHandlers() {
653
653
  import { execSync as execSync5 } from "child_process";
654
654
  import { writeFileSync as writeFileSync10 } from "fs";
655
655
  import { resolve as resolve7 } from "path";
656
- import { confirm as confirm4 } from "@inquirer/prompts";
656
+ import { confirm as confirm5 } from "@inquirer/prompts";
657
657
 
658
658
  // ../utils/src/ansi.ts
659
659
  var colors = {
@@ -2965,7 +2965,7 @@ import { join as join13 } from "path";
2965
2965
  // package.json with { type: 'json' }
2966
2966
  var package_default2 = {
2967
2967
  name: "playcademy",
2968
- version: "0.16.2",
2968
+ version: "0.16.3",
2969
2969
  type: "module",
2970
2970
  exports: {
2971
2971
  ".": {
@@ -3448,6 +3448,16 @@ function hasLocalCustomRoutes(projectPath, config) {
3448
3448
  return existsSync13(customRoutesDir);
3449
3449
  }
3450
3450
 
3451
+ // src/lib/deploy/hash.ts
3452
+ init_file_loader();
3453
+
3454
+ // src/lib/secrets/diff.ts
3455
+ import { green as green3, red as red3, yellow as yellow4 } from "colorette";
3456
+
3457
+ // src/lib/secrets/sync.ts
3458
+ import { confirm as confirm4 } from "@inquirer/prompts";
3459
+ import { bold as bold5 } from "colorette";
3460
+
3451
3461
  // src/lib/init/bucket.ts
3452
3462
  function hasBucketSetup(config) {
3453
3463
  return !!config.integrations?.bucket;
@@ -3725,7 +3735,7 @@ async function runInit(options = {}) {
3725
3735
  `Configuration file already exists: ${relativePath}`
3726
3736
  ]);
3727
3737
  logger.newLine();
3728
- const shouldOverwrite = await confirm4({
3738
+ const shouldOverwrite = await confirm5({
3729
3739
  message: "Do you want to overwrite it?",
3730
3740
  default: false
3731
3741
  });
@@ -19,3 +19,18 @@ export const WORKER_NAMING = {
19
19
  /** Suffix for staging worker hostnames (e.g., "bamboo-staging.playcademy.gg") */
20
20
  STAGING_SUFFIX: '-staging',
21
21
  } as const
22
+
23
+ /**
24
+ * Prefix for user-defined secrets in Cloudflare Worker bindings.
25
+ *
26
+ * Cloudflare bindings are flat, so we namespace user secrets with this prefix
27
+ * to distinguish them from platform bindings (like GAME_ID, PLAYCADEMY_API_KEY).
28
+ *
29
+ * Usage:
30
+ * - When setting secrets: `bindings[SECRETS_PREFIX + key] = value`
31
+ * - When reading secrets: strip prefix and expose as `c.env.secrets.KEY`
32
+ *
33
+ * @see reconstructSecrets in edge-play/entry/setup.ts
34
+ * @see prefixSecrets in edge-play/entry/setup.ts
35
+ */
36
+ export const SECRETS_PREFIX = 'secrets_'
package/dist/db.d.ts CHANGED
@@ -63,6 +63,7 @@ interface SeedWorkerBundle {
63
63
  * - Imports the user's seed function from the specified path
64
64
  * - Captures console output (log/warn/error/info) with timestamps
65
65
  * - Parses D1/SQLite errors into structured details
66
+ * - Reconstructs secrets from env bindings (secrets_KEY -> c.env.secrets.KEY)
66
67
  * - Returns success/error as JSON with logs and timing
67
68
  *
68
69
  * @param seedFilePath - Absolute path to user's seed file
package/dist/db.js CHANGED
@@ -436,13 +436,9 @@ async function getParseD1ErrorSource() {
436
436
  function createLogCapture(startTime) {
437
437
  const logs = [];
438
438
  const originalConsole = {
439
- // eslint-disable-next-line no-console
440
439
  log: console.log.bind(console),
441
- // eslint-disable-next-line no-console
442
440
  warn: console.warn.bind(console),
443
- // eslint-disable-next-line no-console
444
441
  error: console.error.bind(console),
445
- // eslint-disable-next-line no-console
446
442
  info: console.info.bind(console)
447
443
  };
448
444
  const isSilent = typeof process !== "undefined" && typeof process.env !== "undefined" && process.env.LOG_SILENT === "true";
@@ -502,12 +498,26 @@ function buildErrorResponse(error, logs, duration, details) {
502
498
  duration
503
499
  };
504
500
  }
501
+ function reconstructSecrets(env) {
502
+ const secrets = {};
503
+ const prefix = "secrets_";
504
+ for (const key of Object.keys(env)) {
505
+ if (key.startsWith(prefix)) {
506
+ const secretKey = key.slice(prefix.length);
507
+ secrets[secretKey] = env[key];
508
+ }
509
+ }
510
+ return secrets;
511
+ }
505
512
  function createSeedFetchHandler(seedFn, parseD1ErrorFn) {
506
513
  return async function fetch(req, env, ctx) {
507
514
  const startTime = Date.now();
508
515
  const { logs, restore } = createLogCapture(startTime);
509
516
  try {
510
- const c = { env, ctx, req };
517
+ const envRecord = env;
518
+ const secrets = reconstructSecrets(envRecord);
519
+ const enrichedEnv = { ...envRecord, secrets };
520
+ const c = { env: enrichedEnv, ctx, req };
511
521
  await seedFn(c);
512
522
  const duration = Date.now() - startTime;
513
523
  restore();
@@ -529,6 +539,9 @@ async function getBuildSuccessResponseSource() {
529
539
  async function getBuildErrorResponseSource() {
530
540
  return transpileTypeScript(buildErrorResponse.toString());
531
541
  }
542
+ async function getReconstructSecretsSource() {
543
+ return transpileTypeScript(reconstructSecrets.toString());
544
+ }
532
545
  async function getCreateSeedFetchHandlerSource() {
533
546
  return transpileTypeScript(createSeedFetchHandler.toString());
534
547
  }
@@ -540,12 +553,14 @@ async function createSeedWorkerEntry(seedFilePath) {
540
553
  createLogCaptureSource,
541
554
  buildSuccessResponseSource,
542
555
  buildErrorResponseSource,
556
+ reconstructSecretsSource,
543
557
  createSeedFetchHandlerSource
544
558
  ] = await Promise.all([
545
559
  getParseD1ErrorSource(),
546
560
  getCreateLogCaptureSource(),
547
561
  getBuildSuccessResponseSource(),
548
562
  getBuildErrorResponseSource(),
563
+ getReconstructSecretsSource(),
549
564
  getCreateSeedFetchHandlerSource()
550
565
  ]);
551
566
  return `import { seed } from '${seedFilePath}'
@@ -558,6 +573,8 @@ ${buildSuccessResponseSource}
558
573
 
559
574
  ${buildErrorResponseSource}
560
575
 
576
+ ${reconstructSecretsSource}
577
+
561
578
  ${createSeedFetchHandlerSource}
562
579
 
563
580
  export default { fetch: createSeedFetchHandler(seed, parseD1Error) }
@@ -4,6 +4,8 @@
4
4
  * Helper functions for preparing the Worker environment
5
5
  */
6
6
 
7
+ import { SECRETS_PREFIX } from '@playcademy/constants'
8
+
7
9
  import { ENV_VARS } from '../constants'
8
10
 
9
11
  import type { ServerEnv } from '../types'
@@ -20,17 +22,21 @@ export function populateProcessEnv(env: ServerEnv): void {
20
22
  }
21
23
 
22
24
  /**
23
- * Reconstruct secrets object from flat Cloudflare bindings
25
+ * Reconstruct secrets object from flat Cloudflare bindings.
24
26
  *
25
27
  * Cloudflare bindings are flat (secrets_KEY_NAME), but we expose them
26
28
  * as c.env.secrets.KEY_NAME for better developer ergonomics.
29
+ *
30
+ * @param env - Raw Cloudflare Worker environment bindings
31
+ * @returns Object with secrets keyed by their original names (without prefix)
27
32
  */
28
33
  export function reconstructSecrets(env: Record<string, unknown>): Record<string, string> {
29
34
  const secrets: Record<string, string> = {}
35
+ const prefixLength = SECRETS_PREFIX.length
30
36
 
31
37
  for (const key in env) {
32
- if (key.startsWith('secrets_')) {
33
- const secretKey = key.slice(8) // Remove 'secrets_' prefix
38
+ if (key.startsWith(SECRETS_PREFIX)) {
39
+ const secretKey = key.slice(prefixLength)
34
40
  secrets[secretKey] = env[key] as string
35
41
  }
36
42
  }
@@ -38,6 +44,25 @@ export function reconstructSecrets(env: Record<string, unknown>): Record<string,
38
44
  return secrets
39
45
  }
40
46
 
47
+ /**
48
+ * Add the secrets prefix to a record of secrets.
49
+ *
50
+ * Use this when setting up Cloudflare Worker bindings to namespace
51
+ * user secrets separately from platform bindings.
52
+ *
53
+ * @param secrets - Record of secret key-value pairs
54
+ * @returns Record with prefixed keys (secrets_KEY)
55
+ */
56
+ export function prefixSecrets(secrets: Record<string, string>): Record<string, string> {
57
+ const prefixed: Record<string, string> = {}
58
+
59
+ for (const [key, value] of Object.entries(secrets)) {
60
+ prefixed[SECRETS_PREFIX + key] = value
61
+ }
62
+
63
+ return prefixed
64
+ }
65
+
41
66
  /**
42
67
  * Setup process global polyfill for SDK compatibility
43
68
  *
package/dist/index.d.ts CHANGED
@@ -882,6 +882,21 @@ type ExternalGame = BaseGame & {
882
882
  };
883
883
  type Game = HostedGame | ExternalGame;
884
884
 
885
+ /**
886
+ * Secrets Diff Utilities
887
+ *
888
+ * Computes and displays differences between local and remote secrets
889
+ */
890
+ /**
891
+ * Represents the diff between local and remote secrets
892
+ */
893
+ interface SecretsDiff {
894
+ added: string[];
895
+ updated: string[];
896
+ unchanged: string[];
897
+ removed: string[];
898
+ }
899
+
885
900
  /**
886
901
  * Integration-related types
887
902
  */
@@ -964,6 +979,7 @@ interface DeployedGameInfo {
964
979
  schemaSnapshot?: unknown;
965
980
  integrationKeys?: string[];
966
981
  integrationsHash?: string;
982
+ secretsHashes?: Record<string, string>;
967
983
  }
968
984
  interface GameStore {
969
985
  /** Staging environment game deployments, keyed by absolute project path */
@@ -1003,6 +1019,8 @@ interface DeploymentContext {
1003
1019
  previousResources?: string[];
1004
1020
  previousIntegrationKeys?: string[];
1005
1021
  previousIntegrationsHash?: string;
1022
+ localSecrets?: Record<string, string>;
1023
+ remoteSecretKeys?: string[];
1006
1024
  isDryRun: boolean;
1007
1025
  deployBackend: boolean;
1008
1026
  forceBackend: boolean;
@@ -1022,6 +1040,7 @@ interface DeploymentChanges {
1022
1040
  changes: IntegrationConfigChange[];
1023
1041
  }>;
1024
1042
  schema?: boolean;
1043
+ secrets?: SecretsDiff;
1025
1044
  }
1026
1045
  /**
1027
1046
  * Plan for what needs to be deployed
@@ -1259,6 +1278,7 @@ interface DeploymentDiffOptions {
1259
1278
  build?: BuildDiff;
1260
1279
  backend?: BackendDiff;
1261
1280
  integrations?: IntegrationsDiff;
1281
+ secrets?: SecretsDiff;
1262
1282
  }
1263
1283
 
1264
1284
  /**
@@ -1542,6 +1562,7 @@ interface ProjectDirectoryInfo {
1542
1562
  *
1543
1563
  * Options for secret management CLI commands.
1544
1564
  */
1565
+
1545
1566
  /**
1546
1567
  * Base options shared by all secret commands.
1547
1568
  */
@@ -1550,19 +1571,79 @@ interface SecretCommandOptions {
1550
1571
  env?: string;
1551
1572
  }
1552
1573
  /**
1553
- * Options for the `secret set` command.
1574
+ * Options for the `secrets list` command.
1554
1575
  */
1555
- type SecretSetOptions = SecretCommandOptions;
1576
+ type SecretListOptions = SecretCommandOptions;
1556
1577
  /**
1557
- * Options for the `secret list` command.
1578
+ * Options for the `secrets push` command.
1558
1579
  */
1559
- type SecretListOptions = SecretCommandOptions;
1580
+ type SecretPushOptions = SecretCommandOptions;
1560
1581
  /**
1561
- * Options for the `secret delete` command.
1582
+ * Options for checking secrets sync status.
1562
1583
  */
1563
- interface SecretDeleteOptions extends SecretCommandOptions {
1564
- /** Skip confirmation prompt */
1565
- force?: boolean;
1584
+ interface SecretsSyncOptions {
1585
+ /** Absolute path to the project workspace */
1586
+ workspace: string;
1587
+ /** Project/game slug */
1588
+ gameSlug: string;
1589
+ /** Target environment (staging or production) */
1590
+ environment: string;
1591
+ /** Authenticated API client */
1592
+ client: PlaycademyClient;
1593
+ /** Cached hashes from last push (for accurate diff) */
1594
+ cachedHashes?: Record<string, string>;
1595
+ }
1596
+ /**
1597
+ * Options for checking and prompting secrets sync.
1598
+ */
1599
+ interface CheckSecretsOptions extends SecretsSyncOptions {
1600
+ /**
1601
+ * Context for the sync check - affects messaging
1602
+ * - 'blocking': Used before operations that require secrets (e.g., db seed)
1603
+ * Shows error on decline, returns cancelled state
1604
+ * - 'optional': Used after operations complete (e.g., deploy)
1605
+ * Shows remark on decline, non-blocking
1606
+ */
1607
+ mode: 'blocking' | 'optional';
1608
+ }
1609
+ /**
1610
+ * A missing secret required by an integration.
1611
+ */
1612
+ interface MissingSecret {
1613
+ /** The secret key that's missing */
1614
+ key: string;
1615
+ /** The integration that requires it */
1616
+ integration: string;
1617
+ /** Documentation URL for the integration */
1618
+ docs: string;
1619
+ }
1620
+ /**
1621
+ * Result from checking secrets sync status.
1622
+ */
1623
+ interface SecretsSyncResult {
1624
+ /** Local secrets from .env file */
1625
+ localSecrets: Record<string, string>;
1626
+ /** Whether secrets were synced (or already in sync) */
1627
+ synced: boolean;
1628
+ /** Whether user cancelled the operation */
1629
+ cancelled: boolean;
1630
+ }
1631
+ /**
1632
+ * Options for applying secret changes to remote.
1633
+ */
1634
+ interface ApplySecretsOptions {
1635
+ /** The computed diff to apply */
1636
+ diff: SecretsDiff;
1637
+ /** Local secrets with values (needed for setting secrets) */
1638
+ localSecrets: Record<string, string>;
1639
+ /** Game slug for API calls */
1640
+ gameSlug: string;
1641
+ /** Authenticated API client */
1642
+ client: PlaycademyClient;
1643
+ /** Environment name for display */
1644
+ environment: string;
1645
+ /** Workspace path for saving hashes */
1646
+ workspace: string;
1566
1647
  }
1567
1648
 
1568
- export type { ApiConfig, ApiErrorResponse, ApiKeyListItem, ApiKeyWithSecret, ApiRequestOptions, AuthProfile, AuthStore, AuthStrategy, BackendBundle, BackendDeploymentMetadata, BackendDiff, BackendFeatures, BaseKVOptions, BucketBulkOptions, BucketDeleteOptions, BucketGetOptions, BucketListOptions, BucketPutOptions, BuildDiff, BulkCollectionResult, BundleOptions, CallbackServerResult, CollectedFile, ComponentConfig, ComponentResourceConfig, ConfigDiff, CreateApiKeyResponse, CustomRoutesIntegrationOptions, DatabaseIntegrationOptions, DeployConfig, DeployNewGameOptions, DeployedGameInfo, DeploymentChanges, DeploymentContext, DeploymentDiffOptions, DeploymentPlan, DeploymentResult, DevServerOptions, EmbeddedSourcePaths, EngineConfig, EngineType, EnvironmentAuthProfiles, GameStore, IntegrationChangeDetector, IntegrationChangeDetectors, IntegrationConfigChange, IntegrationsConfig, IntegrationsDiff, KVClearOptions, KVDeleteOptions, KVGetOptions, KVInspectOptions, KVListOptions, KVSeedOptions, KVSetOptions, KVStatsOptions, KeyMetadata, KeyStats, LoadConfigResult, LogEntry, LogStreamConfig, LogStreamUrlOptions, LoginCredentials, LoginResponse, OrganizationConfig, PlaycademyConfig, PluginLogger, PreviewOptions, PreviewResponse, ProjectDirectoryInfo, ResourceConfig, ScaffoldResult, SecretCommandOptions, SecretDeleteOptions, SecretListOptions, SecretSetOptions, SignInResponse, SsoCallbackData, Template, TemplateFramework, TemplateHook, TemplateHookOptions, TemplateSource, TimebackBaseConfig, TimebackCourseConfig, TimebackCourseConfigWithOverrides, TimebackGrade, TimebackIntegrationConfig, TimebackSourcedIds, TimebackSubject, TokenType, UpdateExistingGameOptions };
1649
+ export type { ApiConfig, ApiErrorResponse, ApiKeyListItem, ApiKeyWithSecret, ApiRequestOptions, ApplySecretsOptions, AuthProfile, AuthStore, AuthStrategy, BackendBundle, BackendDeploymentMetadata, BackendDiff, BackendFeatures, BaseKVOptions, BucketBulkOptions, BucketDeleteOptions, BucketGetOptions, BucketListOptions, BucketPutOptions, BuildDiff, BulkCollectionResult, BundleOptions, CallbackServerResult, CheckSecretsOptions, CollectedFile, ComponentConfig, ComponentResourceConfig, ConfigDiff, CreateApiKeyResponse, CustomRoutesIntegrationOptions, DatabaseIntegrationOptions, DeployConfig, DeployNewGameOptions, DeployedGameInfo, DeploymentChanges, DeploymentContext, DeploymentDiffOptions, DeploymentPlan, DeploymentResult, DevServerOptions, EmbeddedSourcePaths, EngineConfig, EngineType, EnvironmentAuthProfiles, GameStore, IntegrationChangeDetector, IntegrationChangeDetectors, IntegrationConfigChange, IntegrationsConfig, IntegrationsDiff, KVClearOptions, KVDeleteOptions, KVGetOptions, KVInspectOptions, KVListOptions, KVSeedOptions, KVSetOptions, KVStatsOptions, KeyMetadata, KeyStats, LoadConfigResult, LogEntry, LogStreamConfig, LogStreamUrlOptions, LoginCredentials, LoginResponse, MissingSecret, OrganizationConfig, PlaycademyConfig, PluginLogger, PreviewOptions, PreviewResponse, ProjectDirectoryInfo, ResourceConfig, ScaffoldResult, SecretCommandOptions, SecretListOptions, SecretPushOptions, SecretsSyncOptions, SecretsSyncResult, SignInResponse, SsoCallbackData, Template, TemplateFramework, TemplateHook, TemplateHookOptions, TemplateSource, TimebackBaseConfig, TimebackCourseConfig, TimebackCourseConfigWithOverrides, TimebackGrade, TimebackIntegrationConfig, TimebackSourcedIds, TimebackSubject, TokenType, UpdateExistingGameOptions };