playcademy 0.27.1-beta.9 → 0.28.0

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/bin.js CHANGED
@@ -27,6 +27,10 @@ var CLI_DEFAULT_OUTPUTS = {
27
27
  WORKER_BUNDLE: join(WORKSPACE_NAME, "worker-bundle.js")
28
28
  };
29
29
 
30
+ // src/lib/upgrade/constants.ts
31
+ var INSTALL_SCRIPT_URL = "https://playcademy.net/cli";
32
+ var INSTALL_SCRIPT_URL_WINDOWS = "https://playcademy.net/cli.ps1";
33
+
30
34
  // src/bin.ts
31
35
  var exe = process.platform === "win32" ? "playcademy.exe" : "playcademy";
32
36
  function findBinary() {
@@ -57,6 +61,7 @@ if (binary) {
57
61
  }
58
62
  var args = process.argv.slice(2);
59
63
  var command = args.length ? `playcademy ${args.join(" ")}` : "playcademy";
64
+ var installCommand = process.platform === "win32" ? `irm ${INSTALL_SCRIPT_URL_WINDOWS} | iex` : `curl -fsSL ${INSTALL_SCRIPT_URL} | bash`;
60
65
  var g = red("\u2502");
61
66
  console.error(
62
67
  [
@@ -66,7 +71,7 @@ console.error(
66
71
  g,
67
72
  `${g} Install it separately:`,
68
73
  g,
69
- `${g} ${green("curl -fsSL https://playcademy.net/cli | bash")}`,
74
+ `${g} ${green(installCommand)}`,
70
75
  g,
71
76
  `${g} Then re-run your command:`,
72
77
  g,
package/dist/bucket.js CHANGED
@@ -44,7 +44,7 @@ var GAME_API_ROUTES_DIRECTORY = join2(GAME_SERVER_ROOT_DIRECTORY, "api");
44
44
  // ../better-auth/package.json
45
45
  var package_default = {
46
46
  name: "@playcademy/better-auth",
47
- version: "0.0.20-beta.7",
47
+ version: "0.0.20",
48
48
  type: "module",
49
49
  exports: {
50
50
  "./server": {
package/dist/cli.js CHANGED
@@ -1207,7 +1207,7 @@ var SAMPLE_BUCKET_FILENAME = "bucket.ts";
1207
1207
  // ../better-auth/package.json
1208
1208
  var package_default = {
1209
1209
  name: "@playcademy/better-auth",
1210
- version: "0.0.20-beta.7",
1210
+ version: "0.0.20",
1211
1211
  type: "module",
1212
1212
  exports: {
1213
1213
  "./server": {
@@ -3007,7 +3007,7 @@ import { existsSync as existsSync11, mkdirSync as mkdirSync5, readFileSync as re
3007
3007
  import { join as join13 } from "node:path";
3008
3008
 
3009
3009
  // src/version.ts
3010
- var cliVersion = false ? "0.0.0-dev" : "0.27.1-beta.9";
3010
+ var cliVersion = false ? "0.0.0-dev" : "0.28.0";
3011
3011
 
3012
3012
  // src/lib/init/database.ts
3013
3013
  var drizzleConfigTemplate = loadTemplateString("database/drizzle-config.ts");
package/dist/constants.js CHANGED
@@ -21,7 +21,7 @@ var SAMPLE_BUCKET_FILENAME = "bucket.ts";
21
21
  // ../better-auth/package.json
22
22
  var package_default = {
23
23
  name: "@playcademy/better-auth",
24
- version: "0.0.20-beta.7",
24
+ version: "0.0.20",
25
25
  type: "module",
26
26
  exports: {
27
27
  "./server": {
package/dist/db.js CHANGED
@@ -2335,7 +2335,7 @@ var GAME_API_ROUTES_DIRECTORY = join2(GAME_SERVER_ROOT_DIRECTORY, "api");
2335
2335
  // ../better-auth/package.json
2336
2336
  var package_default = {
2337
2337
  name: "@playcademy/better-auth",
2338
- version: "0.0.20-beta.7",
2338
+ version: "0.0.20",
2339
2339
  type: "module",
2340
2340
  exports: {
2341
2341
  "./server": {
package/dist/index.d.ts CHANGED
@@ -1574,9 +1574,11 @@ interface LoadConfigResult {
1574
1574
  * Log Streaming Types
1575
1575
  */
1576
1576
  interface LogEntry {
1577
+ id?: number;
1577
1578
  timestamp: number;
1578
1579
  scriptName: string;
1579
1580
  outcome: string;
1581
+ rayId?: string;
1580
1582
  logs: {
1581
1583
  level: string;
1582
1584
  message: unknown[];
@@ -1593,23 +1595,38 @@ interface LogEntry {
1593
1595
  status: number;
1594
1596
  };
1595
1597
  }
1598
+ interface LogFilters {
1599
+ status?: {
1600
+ min: number;
1601
+ max: number;
1602
+ };
1603
+ method?: string;
1604
+ path?: string;
1605
+ level?: string;
1606
+ ray?: string;
1607
+ grep?: string;
1608
+ }
1609
+ interface LogHistoryQuery {
1610
+ since?: number;
1611
+ until?: number;
1612
+ limit?: number;
1613
+ filters: LogFilters;
1614
+ }
1615
+ interface LogHistoryResult {
1616
+ entries: LogEntry[];
1617
+ truncated: boolean;
1618
+ }
1596
1619
  interface LogStreamUrlOptions {
1597
- /** Worker ID to stream logs from */
1598
1620
  workerId: string;
1599
- /** Authentication token */
1600
1621
  token: string;
1601
- /** Include recent log history */
1602
- history?: boolean;
1603
1622
  }
1604
1623
  interface LogStreamConfig {
1605
- /** WebSocket URL to connect to */
1606
1624
  url: string;
1607
- /** Game slug (for display purposes) */
1608
1625
  slug: string;
1609
- /** Environment (for display purposes) */
1610
1626
  environment: 'local' | 'staging' | 'production';
1611
- /** Output raw JSON with full timestamps */
1612
1627
  json?: boolean;
1628
+ matches?: (entry: LogEntry) => boolean;
1629
+ backfill?: () => Promise<LogHistoryResult>;
1613
1630
  }
1614
1631
 
1615
1632
  /** A component with a newer version available than what's installed. */
@@ -1753,4 +1770,4 @@ interface RunShellOptions {
1753
1770
  env?: NodeJS.ProcessEnv;
1754
1771
  }
1755
1772
 
1756
- export type { ApiConfig, ApiErrorResponse, ApiKeyListItem, ApiKeyWithSecret, ApiRequestOptions, ApplySecretsOptions, AssetManifest, AuthProfile, AuthStore, AuthStrategy, AvailableUpdate, BackendDiff, BaseKVOptions, BlockedCode, BucketAdapter, BucketGetOptions, BucketListOptions, BucketMigrateMode, BucketObjectRef, BucketPutOptions, BucketSyncOptions, BuildDiff, BundleOptions, CallbackServerResult, CheckSecretsOptions, CollectedFile, CompatibilityKey, CompatibilityManifest, ConfigDiff, CreateApiKeyResponse, CustomRoutesIntegrationOptions, DashboardConfig, DatabaseIntegrationOptions, DependencyCheck, DeployConfig, DeployNewGameOptions, DeployedGameInfo, DeploymentChanges, DeploymentContext, DeploymentDiffOptions, DeploymentPlan, DeploymentResult, DiscoveredDependency, EngineConfig, EngineType, EnvironmentAuthProfiles, GameBackendBundle, GameBackendDeploymentMetadata, GameBackendFeatures, GameDevServerOptions, GameRuntimePaths, GameStore, HashedFile, InstalledVersions, IntegrationChangeDetector, IntegrationChangeDetectors, IntegrationConfigChange, IntegrationsConfig, IntegrationsDiff, KVClearOptions, KVDeleteOptions, KVDumpOptions, KVGetOptions, KVInspectOptions, KVListOptions, KVSeedOptions, KVSetOptions, KVStatsOptions, KeyMetadata, KeyStats, LoadConfigResult, LogEntry, LogStreamConfig, LogStreamUrlOptions, LoginCredentials, LoginResponse, MissingSecret, PlaycademyConfig, PluginLogger, PreviewOptions, PreviewResponse, ProjectDirectoryInfo, QueueConfig, RunShellOptions, RuntimeManifest, ScaffoldResult, SecretCommandOptions, SecretListOptions, SecretPushOptions, SecretsSyncOptions, SecretsSyncResult, SignInResponse, SsoCallbackData, Template, TemplateFramework, TemplateHook, TemplateHookOptions, TemplateSource, TimebackBaseConfig, TimebackCourseConfigWithOverrides, TimebackIntegrationConfig, TokenType, UpdateCommand, UpdateExistingGameOptions, Violation, WorkspaceDependencies };
1773
+ export type { ApiConfig, ApiErrorResponse, ApiKeyListItem, ApiKeyWithSecret, ApiRequestOptions, ApplySecretsOptions, AssetManifest, AuthProfile, AuthStore, AuthStrategy, AvailableUpdate, BackendDiff, BaseKVOptions, BlockedCode, BucketAdapter, BucketGetOptions, BucketListOptions, BucketMigrateMode, BucketObjectRef, BucketPutOptions, BucketSyncOptions, BuildDiff, BundleOptions, CallbackServerResult, CheckSecretsOptions, CollectedFile, CompatibilityKey, CompatibilityManifest, ConfigDiff, CreateApiKeyResponse, CustomRoutesIntegrationOptions, DashboardConfig, DatabaseIntegrationOptions, DependencyCheck, DeployConfig, DeployNewGameOptions, DeployedGameInfo, DeploymentChanges, DeploymentContext, DeploymentDiffOptions, DeploymentPlan, DeploymentResult, DiscoveredDependency, EngineConfig, EngineType, EnvironmentAuthProfiles, GameBackendBundle, GameBackendDeploymentMetadata, GameBackendFeatures, GameDevServerOptions, GameRuntimePaths, GameStore, HashedFile, InstalledVersions, IntegrationChangeDetector, IntegrationChangeDetectors, IntegrationConfigChange, IntegrationsConfig, IntegrationsDiff, KVClearOptions, KVDeleteOptions, KVDumpOptions, KVGetOptions, KVInspectOptions, KVListOptions, KVSeedOptions, KVSetOptions, KVStatsOptions, KeyMetadata, KeyStats, LoadConfigResult, LogEntry, LogFilters, LogHistoryQuery, LogHistoryResult, LogStreamConfig, LogStreamUrlOptions, LoginCredentials, LoginResponse, MissingSecret, PlaycademyConfig, PluginLogger, PreviewOptions, PreviewResponse, ProjectDirectoryInfo, QueueConfig, RunShellOptions, RuntimeManifest, ScaffoldResult, SecretCommandOptions, SecretListOptions, SecretPushOptions, SecretsSyncOptions, SecretsSyncResult, SignInResponse, SsoCallbackData, Template, TemplateFramework, TemplateHook, TemplateHookOptions, TemplateSource, TimebackBaseConfig, TimebackCourseConfigWithOverrides, TimebackIntegrationConfig, TokenType, UpdateCommand, UpdateExistingGameOptions, Violation, WorkspaceDependencies };
package/dist/index.js CHANGED
@@ -1546,7 +1546,7 @@ var require_parseParams = __commonJS({
1546
1546
  var require_basename = __commonJS({
1547
1547
  "../../node_modules/.bun/@fastify+busboy@3.2.0/node_modules/@fastify/busboy/lib/utils/basename.js"(exports, module) {
1548
1548
  "use strict";
1549
- module.exports = function basename5(path4) {
1549
+ module.exports = function basename6(path4) {
1550
1550
  if (typeof path4 !== "string") {
1551
1551
  return "";
1552
1552
  }
@@ -1573,7 +1573,7 @@ var require_multipart = __commonJS({
1573
1573
  var Dicer = require_Dicer();
1574
1574
  var parseParams = require_parseParams();
1575
1575
  var decodeText = require_decodeText();
1576
- var basename5 = require_basename();
1576
+ var basename6 = require_basename();
1577
1577
  var getLimit = require_getLimit();
1578
1578
  var RE_BOUNDARY = /^boundary$/i;
1579
1579
  var RE_FIELD = /^form-data$/i;
@@ -1690,7 +1690,7 @@ var require_multipart = __commonJS({
1690
1690
  } else if (RE_FILENAME.test(parsed[i][0])) {
1691
1691
  filename = parsed[i][1];
1692
1692
  if (!preservePath) {
1693
- filename = basename5(filename);
1693
+ filename = basename6(filename);
1694
1694
  }
1695
1695
  }
1696
1696
  }
@@ -2346,7 +2346,7 @@ var SAMPLE_BUCKET_FILENAME = "bucket.ts";
2346
2346
  // ../better-auth/package.json
2347
2347
  var package_default = {
2348
2348
  name: "@playcademy/better-auth",
2349
- version: "0.0.20-beta.7",
2349
+ version: "0.0.20",
2350
2350
  type: "module",
2351
2351
  exports: {
2352
2352
  "./server": {
@@ -3132,8 +3132,8 @@ function writeBlank() {
3132
3132
  }
3133
3133
  var FG_RESET = "\x1B[39m";
3134
3134
  function nestColor(content, color2) {
3135
- const open = color2("").replace(FG_RESET, "");
3136
- return color2(open ? content.replaceAll(FG_RESET, open) : content);
3135
+ const open2 = color2("").replace(FG_RESET, "");
3136
+ return color2(open2 ? content.replaceAll(FG_RESET, open2) : content);
3137
3137
  }
3138
3138
  function customTransform(text4) {
3139
3139
  let result = text4;
@@ -3785,6 +3785,9 @@ function logAndExit(error, options = {}) {
3785
3785
  process.stderr.write("\n");
3786
3786
  process.exit(code);
3787
3787
  }
3788
+ function emitJsonError(message2) {
3789
+ logger.stdout(JSON.stringify({ success: false, error: { message: message2 } }, null, 2));
3790
+ }
3788
3791
 
3789
3792
  // src/lib/core/exit.ts
3790
3793
  function exitAfterStdoutDrain(code) {
@@ -6402,7 +6405,7 @@ import { existsSync as existsSync10, mkdirSync as mkdirSync2, readFileSync as re
6402
6405
  import { join as join13 } from "node:path";
6403
6406
 
6404
6407
  // src/version.ts
6405
- var cliVersion = false ? "0.0.0-dev" : "0.27.1-beta.9";
6408
+ var cliVersion = false ? "0.0.0-dev" : "0.28.0";
6406
6409
 
6407
6410
  // src/lib/init/database.ts
6408
6411
  var drizzleConfigTemplate = loadTemplateString("database/drizzle-config.ts");
@@ -19822,16 +19825,6 @@ var verification = pgTable("verification", {
19822
19825
  withTimezone: true
19823
19826
  }).notNull()
19824
19827
  });
19825
- var ssoProvider = pgTable("sso_provider", {
19826
- id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
19827
- issuer: text("issuer").notNull(),
19828
- oidcConfig: text("oidc_config"),
19829
- samlConfig: text("saml_config"),
19830
- userId: text("user_id").references(() => users.id, { onDelete: "cascade" }),
19831
- providerId: text("provider_id").notNull().unique(),
19832
- organizationId: text("organization_id"),
19833
- domain: text("domain").notNull()
19834
- });
19835
19828
 
19836
19829
  // ../data/src/domains/game/table.ts
19837
19830
  var gamePlatformEnum = pgEnum2("game_platform", ["web", "godot", "unity"]);
@@ -22868,6 +22861,14 @@ function createLocalAdapter(bucket) {
22868
22861
  };
22869
22862
  }
22870
22863
 
22864
+ // src/lib/logs/collector.ts
22865
+ function buildCollectorUrl(workerId, endpoint, token) {
22866
+ const base = endpoint === "stream" ? LOG_COLLECTOR_URL.replace("https://", "wss://") : LOG_COLLECTOR_URL;
22867
+ const url = new URL(`${base}/logs/${workerId}/${endpoint}`);
22868
+ url.searchParams.set("token", token);
22869
+ return url;
22870
+ }
22871
+
22871
22872
  // src/lib/logs/display.ts
22872
22873
  import { blue as blue3, bold as bold14, cyan as cyan8, dim as dim14, gray as gray2, green as green9, magenta as magenta3, red as red6, yellow as yellow7 } from "colorette";
22873
22874
  var LEVEL_COLORS = {
@@ -22886,14 +22887,14 @@ var OUTCOME_COLORS = {
22886
22887
  scriptNotFound: red6,
22887
22888
  unknown: gray2
22888
22889
  };
22890
+ var TIME_FORMAT = {
22891
+ hour: "numeric",
22892
+ minute: "2-digit",
22893
+ second: "2-digit",
22894
+ hour12: true
22895
+ };
22889
22896
  function formatTime2(timestamp4) {
22890
- const date = new Date(timestamp4);
22891
- return date.toLocaleTimeString("en-US", {
22892
- hour: "numeric",
22893
- minute: "2-digit",
22894
- second: "2-digit",
22895
- hour12: true
22896
- });
22897
+ return new Date(timestamp4).toLocaleTimeString("en-US", TIME_FORMAT);
22897
22898
  }
22898
22899
  var HTTP_STATUS_TEXT = {
22899
22900
  200: "OK",
@@ -22997,65 +22998,379 @@ function displayLogEntry(entry) {
22997
22998
  logger.raw(`${time} ${level} ${exception.name}: ${exception.message}`);
22998
22999
  }
22999
23000
  }
23001
+ function printLogEntry(entry, json) {
23002
+ if (json) {
23003
+ logger.stdout(JSON.stringify(entry));
23004
+ return;
23005
+ }
23006
+ displayLogEntry(entry);
23007
+ }
23008
+ function formatDateTime(timestamp4) {
23009
+ return new Date(timestamp4).toLocaleString("en-US", {
23010
+ month: "short",
23011
+ day: "numeric",
23012
+ ...TIME_FORMAT
23013
+ });
23014
+ }
23015
+ function historyTruncatedMessage(fetched) {
23016
+ const oldest = fetched[0];
23017
+ if (!oldest) {
23018
+ return "History truncated before any entry fit the response limit; narrow the window with --since/--until";
23019
+ }
23020
+ return `History truncated: the newest ${fetched.length} entries were returned, from ${formatDateTime(oldest.timestamp)} onward; narrow the window with --since/--until to reach older entries`;
23021
+ }
23022
+ function emptyHistoryMessage(truncated, grepActive) {
23023
+ if (!truncated) {
23024
+ return "No matching log entries";
23025
+ }
23026
+ if (grepActive) {
23027
+ return "No matches in the searched window; older entries were cut by the response limit and not searched";
23028
+ }
23029
+ return "No matching log entries in the searched window; older entries were cut by the response limit";
23030
+ }
23031
+ function noteHistoryTruncated(fetched, json) {
23032
+ const message2 = historyTruncatedMessage(fetched);
23033
+ if (json) {
23034
+ process.stderr.write(`${message2}
23035
+ `);
23036
+ } else {
23037
+ logger.newLine();
23038
+ logger.dim(message2);
23039
+ logger.newLine();
23040
+ }
23041
+ }
23042
+
23043
+ // src/lib/logs/filters.ts
23044
+ var STATUS_EXACT_PATTERN = /^[1-5]\d{2}$/;
23045
+ var STATUS_CLASS_PATTERN = /^[1-5]xx$/i;
23046
+ var METHOD_PATTERN = /^[A-Za-z]+$/;
23047
+ var RAY_PATTERN = /^[0-9a-f]{16}(-[a-z]+)?$/i;
23048
+ var LEVEL_PATTERN = /^[a-z]+$/;
23049
+ function parseLogFilters(options) {
23050
+ const filters = {};
23051
+ if (options.status !== void 0) {
23052
+ const status = parseStatusFilter(options.status);
23053
+ if (!status) {
23054
+ throw new Error(
23055
+ `Invalid --status value "${options.status}" (use a code like 401 or a class like 4xx)`
23056
+ );
23057
+ }
23058
+ filters.status = status;
23059
+ }
23060
+ if (options.method !== void 0) {
23061
+ if (!METHOD_PATTERN.test(options.method)) {
23062
+ throw new Error(`Invalid --method value "${options.method}"`);
23063
+ }
23064
+ filters.method = options.method.toUpperCase();
23065
+ }
23066
+ if (options.path !== void 0) {
23067
+ filters.path = options.path;
23068
+ }
23069
+ if (options.level !== void 0) {
23070
+ const level = options.level.toLowerCase();
23071
+ if (!LEVEL_PATTERN.test(level)) {
23072
+ throw new Error(
23073
+ `Invalid --level value "${options.level}" (e.g. log, info, warn, error, debug)`
23074
+ );
23075
+ }
23076
+ filters.level = level;
23077
+ }
23078
+ if (options.ray !== void 0) {
23079
+ if (!RAY_PATTERN.test(options.ray)) {
23080
+ throw new Error(`Invalid --ray value "${options.ray}" (expected a Cloudflare ray id)`);
23081
+ }
23082
+ filters.ray = normalizeRayId(options.ray);
23083
+ }
23084
+ if (options.grep !== void 0) {
23085
+ filters.grep = options.grep;
23086
+ }
23087
+ return filters;
23088
+ }
23089
+ function parseStatusFilter(input2) {
23090
+ if (STATUS_EXACT_PATTERN.test(input2)) {
23091
+ const code = Number.parseInt(input2, 10);
23092
+ return { min: code, max: code };
23093
+ }
23094
+ if (STATUS_CLASS_PATTERN.test(input2)) {
23095
+ const hundreds = Number.parseInt(input2[0], 10);
23096
+ return { min: hundreds * 100, max: hundreds * 100 + 99 };
23097
+ }
23098
+ return null;
23099
+ }
23100
+ function statusFilterToParam(status) {
23101
+ if (status.min === status.max) {
23102
+ return String(status.min);
23103
+ }
23104
+ return `${Math.floor(status.min / 100)}xx`;
23105
+ }
23106
+ function normalizeRayId(input2) {
23107
+ return input2.toLowerCase().split("-")[0];
23108
+ }
23109
+ function matchesFilters(entry, filters) {
23110
+ if (filters.status) {
23111
+ const status = entry.response?.status;
23112
+ if (status === void 0 || status < filters.status.min || status > filters.status.max) {
23113
+ return false;
23114
+ }
23115
+ }
23116
+ if (filters.method && entry.request?.method !== filters.method) {
23117
+ return false;
23118
+ }
23119
+ if (filters.path && !(entryPathname(entry) ?? "").startsWith(filters.path)) {
23120
+ return false;
23121
+ }
23122
+ if (filters.level && !entryHasLevel(entry, filters.level)) {
23123
+ return false;
23124
+ }
23125
+ if (filters.ray && (!entry.rayId || normalizeRayId(entry.rayId) !== filters.ray)) {
23126
+ return false;
23127
+ }
23128
+ if (filters.grep && !entrySearchText(entry).toLowerCase().includes(filters.grep.toLowerCase())) {
23129
+ return false;
23130
+ }
23131
+ return true;
23132
+ }
23133
+ function entrySearchText(entry) {
23134
+ const parts = [entry.outcome];
23135
+ if (entry.request) {
23136
+ parts.push(entry.request.method, entry.request.url);
23137
+ }
23138
+ if (entry.response) {
23139
+ parts.push(String(entry.response.status));
23140
+ }
23141
+ if (entry.rayId) {
23142
+ parts.push(entry.rayId);
23143
+ }
23144
+ for (const log2 of entry.logs) {
23145
+ for (const message2 of log2.message) {
23146
+ parts.push(typeof message2 === "string" ? message2 : JSON.stringify(message2) ?? "");
23147
+ }
23148
+ }
23149
+ for (const exception of entry.exceptions) {
23150
+ parts.push(exception.name, exception.message);
23151
+ }
23152
+ return parts.join("\n");
23153
+ }
23154
+ function entryPathname(entry) {
23155
+ if (!entry.request) {
23156
+ return null;
23157
+ }
23158
+ try {
23159
+ return new URL(entry.request.url).pathname;
23160
+ } catch {
23161
+ return null;
23162
+ }
23163
+ }
23164
+ function entryHasLevel(entry, level) {
23165
+ if (entry.logs.some((log2) => log2.level === level)) {
23166
+ return true;
23167
+ }
23168
+ return level === "error" && entry.exceptions.length > 0;
23169
+ }
23170
+
23171
+ // src/lib/logs/history.ts
23172
+ var HISTORY_FETCH_TIMEOUT_MS = 3e4;
23173
+ function buildLogHistoryUrl(request) {
23174
+ const { workerId, token, query } = request;
23175
+ const url = buildCollectorUrl(workerId, "history", token);
23176
+ const params = url.searchParams;
23177
+ if (query.since !== void 0) {
23178
+ params.set("since", String(query.since));
23179
+ }
23180
+ if (query.until !== void 0) {
23181
+ params.set("until", String(query.until));
23182
+ }
23183
+ if (query.limit !== void 0) {
23184
+ params.set("limit", String(query.limit));
23185
+ }
23186
+ if (query.filters.status) {
23187
+ params.set("status", statusFilterToParam(query.filters.status));
23188
+ }
23189
+ if (query.filters.method) {
23190
+ params.set("method", query.filters.method);
23191
+ }
23192
+ if (query.filters.path) {
23193
+ params.set("path", query.filters.path);
23194
+ }
23195
+ if (query.filters.level) {
23196
+ params.set("level", query.filters.level);
23197
+ }
23198
+ if (query.filters.ray) {
23199
+ params.set("ray", query.filters.ray);
23200
+ }
23201
+ return url.toString();
23202
+ }
23203
+ async function fetchLogHistory(request) {
23204
+ const response = await fetch(buildLogHistoryUrl(request), {
23205
+ signal: AbortSignal.timeout(HISTORY_FETCH_TIMEOUT_MS)
23206
+ });
23207
+ if (!response.ok) {
23208
+ const body = await response.text().catch(() => "");
23209
+ const detail = body.trim();
23210
+ throw new Error(
23211
+ `Log history request failed with status ${response.status}${detail ? `: ${detail}` : ""}`
23212
+ );
23213
+ }
23214
+ return {
23215
+ entries: parseHistoryBody(await response.text()),
23216
+ truncated: response.headers.get("x-log-history-truncated") === "true"
23217
+ };
23218
+ }
23219
+ function parseHistoryBody(body) {
23220
+ const entries = [];
23221
+ for (const line of body.split("\n")) {
23222
+ if (line.length > 0) {
23223
+ try {
23224
+ entries.push(JSON.parse(line));
23225
+ } catch {
23226
+ }
23227
+ }
23228
+ }
23229
+ return entries;
23230
+ }
23000
23231
 
23001
23232
  // src/lib/logs/stream.ts
23002
23233
  import WebSocket from "ws";
23003
23234
  function buildLogStreamUrl(options) {
23004
- const { workerId, token, history } = options;
23005
- const wsBaseUrl = LOG_COLLECTOR_URL.replace("https://", "wss://");
23006
- const url = new URL(`${wsBaseUrl}/logs/${workerId}/stream`);
23007
- url.searchParams.set("token", token);
23008
- if (history) {
23009
- url.searchParams.set("history", "true");
23010
- }
23011
- return url.toString();
23235
+ return buildCollectorUrl(options.workerId, "stream", options.token).toString();
23012
23236
  }
23237
+ var BACKFILL_EXIT_GRACE_MS = HISTORY_FETCH_TIMEOUT_MS + 5e3;
23238
+ var MAX_PENDING_LINES = 1e4;
23013
23239
  async function connectToLogStream(config) {
23014
- const { url, slug, environment, json } = config;
23240
+ const { url, slug, environment, json, matches, backfill } = config;
23015
23241
  const ws = new WebSocket(url);
23242
+ let pending = backfill ? [] : null;
23243
+ let droppedPending = 0;
23244
+ let printedMaxId = 0;
23245
+ let backfillTask = null;
23246
+ let interrupted = false;
23247
+ let exiting = false;
23248
+ function finishAndExit(code, failure, report) {
23249
+ if (exiting) {
23250
+ return;
23251
+ }
23252
+ exiting = true;
23253
+ void (async () => {
23254
+ if (backfillTask && !interrupted) {
23255
+ await Promise.race([
23256
+ backfillTask,
23257
+ new Promise((resolve18) => setTimeout(resolve18, BACKFILL_EXIT_GRACE_MS))
23258
+ ]);
23259
+ }
23260
+ report?.();
23261
+ logger.newLine();
23262
+ if (json && code !== 0 && failure) {
23263
+ emitJsonError(failure);
23264
+ }
23265
+ await exitAfterStdoutDrain(code);
23266
+ })();
23267
+ }
23268
+ function emit(raw) {
23269
+ let entry;
23270
+ try {
23271
+ entry = JSON.parse(raw);
23272
+ } catch {
23273
+ if (!matches) {
23274
+ if (json) {
23275
+ process.stderr.write(`${raw}
23276
+ `);
23277
+ } else {
23278
+ logger.stdout(raw);
23279
+ }
23280
+ }
23281
+ return;
23282
+ }
23283
+ if (entry.id !== void 0 && entry.id <= printedMaxId) {
23284
+ return;
23285
+ }
23286
+ if (matches && !matches(entry)) {
23287
+ return;
23288
+ }
23289
+ printLogEntry(entry, json);
23290
+ }
23291
+ async function runBackfill(fetchHistory) {
23292
+ try {
23293
+ const { entries, truncated } = await fetchHistory();
23294
+ for (const entry of entries) {
23295
+ if (!matches || matches(entry)) {
23296
+ printLogEntry(entry, json);
23297
+ }
23298
+ printedMaxId = Math.max(printedMaxId, entry.id ?? 0);
23299
+ }
23300
+ if (truncated) {
23301
+ noteHistoryTruncated(entries, json);
23302
+ }
23303
+ } catch (error) {
23304
+ const failure = `Failed to fetch log history: ${errorMessage(error)}`;
23305
+ logger.newLine();
23306
+ logger.error(failure);
23307
+ finishAndExit(1, failure);
23308
+ return;
23309
+ }
23310
+ const buffered = pending ?? [];
23311
+ pending = null;
23312
+ for (const raw of buffered) {
23313
+ emit(raw);
23314
+ }
23315
+ if (droppedPending > 0) {
23316
+ const note = `Dropped the oldest ${droppedPending} live entries buffered during backfill`;
23317
+ if (json) {
23318
+ process.stderr.write(`${note}
23319
+ `);
23320
+ } else {
23321
+ logger.dim(note);
23322
+ logger.newLine();
23323
+ }
23324
+ }
23325
+ }
23016
23326
  ws.on("open", () => {
23017
23327
  if (json) {
23018
23328
  process.stderr.write(`Connected to ${environment} logs for "${slug}"
23019
23329
 
23020
23330
  `);
23021
23331
  process.stderr.write(" Press ctrl+c to stop\n\n");
23022
- return;
23332
+ } else {
23333
+ logger.success(`Connected to ${environment} logs for "${slug}"`);
23334
+ logger.newLine();
23335
+ logger.dim("Press ctrl+c to stop", 1);
23336
+ logger.newLine();
23337
+ }
23338
+ if (backfill) {
23339
+ backfillTask = runBackfill(backfill);
23023
23340
  }
23024
- logger.success(`Connected to ${environment} logs for "${slug}"`);
23025
- logger.newLine();
23026
- logger.dim("Press ctrl+c to stop", 1);
23027
- logger.newLine();
23028
23341
  });
23029
23342
  ws.on("message", (data) => {
23030
- try {
23031
- const entry = JSON.parse(data.toString());
23032
- if (json) {
23033
- logger.stdout(JSON.stringify(entry));
23034
- } else {
23035
- displayLogEntry(entry);
23036
- }
23037
- } catch {
23038
- if (json) {
23039
- process.stderr.write(`${data.toString()}
23040
- `);
23041
- } else {
23042
- logger.stdout(data.toString());
23343
+ const raw = data.toString();
23344
+ if (pending) {
23345
+ if (pending.length >= MAX_PENDING_LINES) {
23346
+ pending.shift();
23347
+ droppedPending += 1;
23043
23348
  }
23349
+ pending.push(raw);
23350
+ return;
23044
23351
  }
23352
+ emit(raw);
23045
23353
  });
23046
23354
  ws.on("error", (error) => {
23047
- logger.newLine();
23048
- logger.error(`WebSocket error: ${error.message}`);
23049
- process.exit(1);
23355
+ const failure = `WebSocket error: ${error.message}`;
23356
+ finishAndExit(1, failure, () => {
23357
+ logger.newLine();
23358
+ logger.error(failure);
23359
+ });
23050
23360
  });
23051
23361
  ws.on("close", (code, reason) => {
23052
- logger.newLine();
23053
- if (code !== 1e3) {
23054
- logger.warn(`Connection closed: ${code} ${reason.toString()}`);
23362
+ if (code === 1e3) {
23363
+ finishAndExit(0);
23364
+ return;
23055
23365
  }
23056
- process.exit(0);
23366
+ const failure = `Connection closed: ${code} ${reason.toString()}`;
23367
+ finishAndExit(1, failure, () => {
23368
+ logger.newLine();
23369
+ logger.warn(failure);
23370
+ });
23057
23371
  });
23058
23372
  process.on("SIGINT", () => {
23373
+ interrupted = true;
23059
23374
  logger.newLine();
23060
23375
  ws.close(1e3, "User interrupted");
23061
23376
  });
@@ -23063,6 +23378,55 @@ async function connectToLogStream(config) {
23063
23378
  });
23064
23379
  }
23065
23380
 
23381
+ // src/lib/logs/time.ts
23382
+ var DURATION_PATTERN = /^(\d+)([smhd])$/;
23383
+ var CALENDAR_DATE_PATTERN = /^(\d{4})-(\d{2})-(\d{2})(?:T|$)/;
23384
+ var DATE_ONLY_LENGTH = "YYYY-MM-DD".length;
23385
+ var UNIT_MS = {
23386
+ s: 1e3,
23387
+ m: 60 * 1e3,
23388
+ h: 60 * 60 * 1e3,
23389
+ d: 24 * 60 * 60 * 1e3
23390
+ };
23391
+ function parseTimeInput(input2, now = Date.now()) {
23392
+ const duration = input2.match(DURATION_PATTERN);
23393
+ if (duration) {
23394
+ return now - Number.parseInt(duration[1], 10) * UNIT_MS[duration[2]];
23395
+ }
23396
+ const absolute = parseAbsoluteTime(input2);
23397
+ if (absolute === null) {
23398
+ throw new Error(
23399
+ `Invalid time value "${input2}" (use a duration like 30m, 2h, 7d, or an ISO timestamp)`
23400
+ );
23401
+ }
23402
+ return absolute;
23403
+ }
23404
+ function parseAbsoluteTime(input2) {
23405
+ if (/^\d+$/.test(input2)) {
23406
+ return null;
23407
+ }
23408
+ const calendar = input2.match(CALENDAR_DATE_PATTERN);
23409
+ if (calendar) {
23410
+ const year2 = Number.parseInt(calendar[1], 10);
23411
+ const month = Number.parseInt(calendar[2], 10);
23412
+ const day2 = Number.parseInt(calendar[3], 10);
23413
+ if (!isRealCalendarDate(year2, month, day2)) {
23414
+ return null;
23415
+ }
23416
+ if (input2.length === DATE_ONLY_LENGTH) {
23417
+ return new Date(year2, month - 1, day2).getTime();
23418
+ }
23419
+ }
23420
+ const parsed = Date.parse(input2);
23421
+ return Number.isNaN(parsed) ? null : parsed;
23422
+ }
23423
+ function isRealCalendarDate(year2, month, day2) {
23424
+ if (month < 1 || month > 12 || day2 < 1) {
23425
+ return false;
23426
+ }
23427
+ return day2 <= new Date(year2, month, 0).getDate();
23428
+ }
23429
+
23066
23430
  // src/lib/timeback/display.ts
23067
23431
  import { bold as bold15, greenBright as greenBright2, redBright as redBright3 } from "colorette";
23068
23432
  function displayIntegrationDetails(integration) {
@@ -23476,9 +23840,6 @@ ${error.suggestion}` : error.message;
23476
23840
  return hostname.trim().toLowerCase();
23477
23841
  }
23478
23842
 
23479
- // src/lib/upgrade/constants.ts
23480
- var INSTALL_SCRIPT_URL = "https://playcademy.net/cli";
23481
-
23482
23843
  // src/lib/upgrade/detect.ts
23483
23844
  import { execSync as execSync9 } from "node:child_process";
23484
23845
  var PACKAGE_MANAGERS = ["bun", "pnpm", "yarn", "npm"];
@@ -23500,7 +23861,7 @@ function getInstallMethod() {
23500
23861
  return "standalone";
23501
23862
  }
23502
23863
  if (executorIsPackageRunner()) {
23503
- return "unknown";
23864
+ return "package-runner";
23504
23865
  }
23505
23866
  const execPath = process.execPath.toLowerCase();
23506
23867
  const sortedManagers = [...PACKAGE_MANAGERS].toSorted((a, b) => {
@@ -23523,6 +23884,69 @@ function getInstallMethod() {
23523
23884
  return "unknown";
23524
23885
  }
23525
23886
 
23887
+ // src/lib/upgrade/guards.ts
23888
+ import { bold as bold17 } from "colorette";
23889
+
23890
+ // src/lib/upgrade/constants.ts
23891
+ var INSTALL_SCRIPT_URL = "https://playcademy.net/cli";
23892
+ var INSTALL_SCRIPT_URL_WINDOWS = "https://playcademy.net/cli.ps1";
23893
+
23894
+ // src/lib/upgrade/guards.ts
23895
+ function installInstructionLines() {
23896
+ if (process.platform === "win32") {
23897
+ return [
23898
+ "Install the standalone binary from PowerShell:",
23899
+ "",
23900
+ ` \`irm ${INSTALL_SCRIPT_URL_WINDOWS} | iex\``
23901
+ ];
23902
+ }
23903
+ return [
23904
+ "Install the standalone binary with:",
23905
+ "",
23906
+ ` \`curl -fsSL ${INSTALL_SCRIPT_URL} | bash\``
23907
+ ];
23908
+ }
23909
+ function handleNonStandaloneInstall(installMethod) {
23910
+ if (installMethod === "package-runner") {
23911
+ logger.admonition("info", "Nothing To Upgrade", [
23912
+ "This run came through a package runner (bunx or npx), which fetches",
23913
+ "the requested version each time. There is no installed CLI to upgrade."
23914
+ ]);
23915
+ process.exit(0);
23916
+ }
23917
+ if (installMethod === "unknown") {
23918
+ logger.admonition("warning", "Unknown Install", [
23919
+ "Could not determine how this CLI was installed, so nothing was",
23920
+ "changed. If it came from a package manager, uninstall it first:",
23921
+ "",
23922
+ " `npm uninstall -g playcademy`",
23923
+ "",
23924
+ ...installInstructionLines()
23925
+ ]);
23926
+ process.exit(1);
23927
+ }
23928
+ const uninstallCommands = {
23929
+ bun: "bun remove -g playcademy",
23930
+ npm: "npm uninstall -g playcademy",
23931
+ pnpm: "pnpm remove -g playcademy",
23932
+ yarn: "yarn global remove playcademy"
23933
+ };
23934
+ const uninstallCmd = uninstallCommands[installMethod];
23935
+ logger.admonition("warning", "Legacy Install Detected", [
23936
+ `The CLI was installed via ${bold17(installMethod)}, which is no longer supported.`,
23937
+ "",
23938
+ "Uninstall it first (it would shadow the standalone binary):",
23939
+ "",
23940
+ ` \`${uninstallCmd}\``,
23941
+ "",
23942
+ ...installInstructionLines()
23943
+ ]);
23944
+ process.exit(0);
23945
+ }
23946
+
23947
+ // src/lib/upgrade/perform.ts
23948
+ import { bold as bold18, dim as dim15, green as green10, red as red7, yellow as yellow8 } from "colorette";
23949
+
23526
23950
  // src/lib/upgrade/standalone.ts
23527
23951
  import { spawn as spawn2 } from "node:child_process";
23528
23952
  function formatExit(name, code, signal) {
@@ -23635,6 +24059,275 @@ function upgradeStandalone(targetVersion, channel) {
23635
24059
  });
23636
24060
  });
23637
24061
  }
24062
+
24063
+ // src/lib/upgrade/verify.ts
24064
+ import { spawnSync } from "node:child_process";
24065
+ function extractReportedVersion(stdout2) {
24066
+ const lastLine = stdout2.trim().split("\n").at(-1)?.trim();
24067
+ if (!lastLine) {
24068
+ return null;
24069
+ }
24070
+ try {
24071
+ return normalizeVersion(lastLine);
24072
+ } catch {
24073
+ return null;
24074
+ }
24075
+ }
24076
+ function verifyInstalledBinary(expectedVersion) {
24077
+ const expected = normalizeVersion(expectedVersion);
24078
+ const result = spawnSync(process.execPath, ["--version"], {
24079
+ encoding: "utf8",
24080
+ timeout: 3e4
24081
+ });
24082
+ if (result.error || result.status !== 0) {
24083
+ const rollbackHint = process.platform === "win32" ? " The previous binary was renamed to .old next to it if you need to roll back." : "";
24084
+ throw new Error(
24085
+ `The installed binary at ${process.execPath} failed to run: ${result.error?.message ?? `exit code ${result.status}`}.${rollbackHint}`
24086
+ );
24087
+ }
24088
+ const reported = extractReportedVersion(result.stdout ?? "");
24089
+ if (reported !== expected) {
24090
+ throw new Error(
24091
+ `Upgrade did not take effect: ${process.execPath} reports ${reported ?? "no version"}, expected ${expected}. The install may have written to a different location than the binary you run.`
24092
+ );
24093
+ }
24094
+ }
24095
+
24096
+ // src/lib/upgrade/windows.ts
24097
+ import { createHash as createHash6 } from "node:crypto";
24098
+ import { existsSync as existsSync38 } from "node:fs";
24099
+ import { copyFile, mkdir as mkdir9, open, rename, rm as rm3 } from "node:fs/promises";
24100
+ import { homedir as homedir3 } from "node:os";
24101
+ import { basename as basename5, dirname as dirname9, join as join57 } from "node:path";
24102
+ var COMPANION_TOOLS = ["workerd", "esbuild"];
24103
+ function windowsArtifacts(arch = process.arch) {
24104
+ if (arch !== "x64") {
24105
+ throw new Error(
24106
+ `Unsupported architecture for the Windows upgrade: ${arch} (only x64 builds are published)`
24107
+ );
24108
+ }
24109
+ return ["playcademy", ...COMPANION_TOOLS].map((tool) => ({
24110
+ tool,
24111
+ remote: `${tool}-windows-x64.exe`
24112
+ }));
24113
+ }
24114
+ function cliDestinations(execPath, canonical, existingLocalBin) {
24115
+ const seen = /* @__PURE__ */ new Set([execPath.toLowerCase()]);
24116
+ const secondary = [];
24117
+ for (const candidate of [canonical, ...existingLocalBin ? [existingLocalBin] : []]) {
24118
+ const key = candidate.toLowerCase();
24119
+ if (!seen.has(key)) {
24120
+ seen.add(key);
24121
+ secondary.push(candidate);
24122
+ }
24123
+ }
24124
+ return [...secondary, execPath];
24125
+ }
24126
+ function resolveCliDestinations() {
24127
+ const canonical = nativeBinaryPath("playcademy");
24128
+ const localBin = join57(homedir3(), ".local", "bin", basename5(canonical));
24129
+ return cliDestinations(process.execPath, canonical, existsSync38(localBin) ? localBin : null);
24130
+ }
24131
+ function parseChecksums(text4) {
24132
+ const checksums = /* @__PURE__ */ new Map();
24133
+ for (const line of text4.split("\n")) {
24134
+ const match = line.trim().match(/^([0-9a-fA-F]{64})\s+(\S+)$/);
24135
+ if (match) {
24136
+ checksums.set(match[2], match[1].toLowerCase());
24137
+ }
24138
+ }
24139
+ return checksums;
24140
+ }
24141
+ async function fetchText(url) {
24142
+ const response = await fetch(url);
24143
+ if (!response.ok) {
24144
+ throw new Error(`Download failed (HTTP ${response.status}): ${url}`);
24145
+ }
24146
+ return await response.text();
24147
+ }
24148
+ async function downloadVerified(url, expected, download) {
24149
+ const response = await fetch(url);
24150
+ if (!response.ok || !response.body) {
24151
+ throw new Error(`Download failed (HTTP ${response.status}): ${url}`);
24152
+ }
24153
+ const hash = createHash6("sha256");
24154
+ const handle = await open(download, "w");
24155
+ try {
24156
+ for await (const chunk of response.body) {
24157
+ hash.update(chunk);
24158
+ const { bytesWritten } = await handle.write(chunk);
24159
+ if (bytesWritten !== chunk.length) {
24160
+ throw new Error(
24161
+ `Short write staging ${download}: ${bytesWritten} of ${chunk.length} bytes`
24162
+ );
24163
+ }
24164
+ }
24165
+ } finally {
24166
+ await handle.close();
24167
+ }
24168
+ const digest = hash.digest("hex");
24169
+ if (digest !== expected) {
24170
+ await rm3(download, { force: true }).catch(() => void 0);
24171
+ throw new Error(`Checksum mismatch for ${url}: expected ${expected}, got ${digest}`);
24172
+ }
24173
+ }
24174
+ async function upgradeWindows(targetVersion, channel) {
24175
+ const baseUrl = `${getDownloadBaseUrl(channel)}/v${targetVersion}`;
24176
+ const checksums = parseChecksums(await fetchText(`${baseUrl}/checksums.txt`));
24177
+ const plans = windowsArtifacts().map(({ tool, remote }) => {
24178
+ const expected = checksums.get(remote);
24179
+ if (!expected) {
24180
+ throw new Error(`No checksum for ${remote} in the v${targetVersion} release`);
24181
+ }
24182
+ const dests = tool === "playcademy" ? resolveCliDestinations() : [nativeBinaryPath(tool)];
24183
+ return { remote, expected, dests };
24184
+ });
24185
+ await Promise.all(
24186
+ [...new Set(plans.flatMap((plan) => plan.dests.map((dest) => dirname9(dest))))].map(
24187
+ (dir) => mkdir9(dir, { recursive: true })
24188
+ )
24189
+ );
24190
+ await Promise.all(
24191
+ plans.map(async (plan) => {
24192
+ const download = `${plan.dests[0]}.download`;
24193
+ await downloadVerified(`${baseUrl}/${plan.remote}`, plan.expected, download);
24194
+ await Promise.all(
24195
+ plan.dests.slice(1).map((dest) => copyFile(download, `${dest}.download`))
24196
+ );
24197
+ })
24198
+ );
24199
+ for (const plan of plans.toReversed()) {
24200
+ for (const dest of plan.dests) {
24201
+ await swapInPlace(dest, `${dest}.download`);
24202
+ }
24203
+ }
24204
+ }
24205
+ async function swapInPlace(dest, download) {
24206
+ await rm3(`${dest}.old`, { force: true });
24207
+ let movedAside = true;
24208
+ try {
24209
+ await rename(dest, `${dest}.old`);
24210
+ } catch (error) {
24211
+ if (error.code !== "ENOENT") {
24212
+ throw error;
24213
+ }
24214
+ movedAside = false;
24215
+ }
24216
+ try {
24217
+ await rename(download, dest);
24218
+ } catch (error) {
24219
+ if (movedAside) {
24220
+ await rename(`${dest}.old`, dest).catch(() => void 0);
24221
+ }
24222
+ throw error;
24223
+ }
24224
+ await rm3(`${dest}.old`, { force: true }).catch(() => void 0);
24225
+ }
24226
+
24227
+ // src/lib/upgrade/perform.ts
24228
+ function printUpgradeHeader(currentVersion, desiredVersion, isDowngrade) {
24229
+ const targetColor = isDowngrade ? yellow8 : green10;
24230
+ console.log(
24231
+ ` ${bold18("Playcademy CLI")} ${bold18("(")}${red7(currentVersion)} \u2192 ${targetColor(desiredVersion)}${bold18(")")}`
24232
+ );
24233
+ logger.newLine();
24234
+ }
24235
+ function reportUpToDate(desiredVersion) {
24236
+ console.log(
24237
+ ` ${green10("\u2714")} ${bold18("Playcademy CLI")} ${dim15(`${desiredVersion} is already up to date`)}`
24238
+ );
24239
+ logger.newLine();
24240
+ }
24241
+ async function performUpgrade(resolved) {
24242
+ const { channel, currentVersion, desiredVersion } = resolved;
24243
+ const isDowngrade = resolved.direction > 0;
24244
+ printUpgradeHeader(currentVersion, desiredVersion, isDowngrade);
24245
+ const shouldUpgrade = await confirm({
24246
+ message: `Download and install ${bold18(desiredVersion)}?`,
24247
+ default: true,
24248
+ nonInteractiveHint: "Pass --yes to confirm the upgrade."
24249
+ });
24250
+ if (!shouldUpgrade) {
24251
+ logger.newLine();
24252
+ process.exit(0);
24253
+ }
24254
+ const doneLabel = isDowngrade ? `Installed ${bold18(desiredVersion)}` : `Upgraded to ${bold18(desiredVersion)}`;
24255
+ await runStep(
24256
+ "Downloading and installing...",
24257
+ async () => {
24258
+ if (process.platform === "win32") {
24259
+ await upgradeWindows(desiredVersion, channel);
24260
+ } else {
24261
+ await upgradeStandalone(desiredVersion, channel);
24262
+ }
24263
+ },
24264
+ doneLabel
24265
+ );
24266
+ await runStep(
24267
+ "Verifying installation",
24268
+ async () => verifyInstalledBinary(desiredVersion),
24269
+ "Installation verified"
24270
+ );
24271
+ }
24272
+
24273
+ // src/lib/upgrade/resolve.ts
24274
+ function parseChannelFlag(opts) {
24275
+ const flags = [opts?.beta && "beta", opts?.stable && "stable", opts?.channel].filter(Boolean);
24276
+ if (flags.length > 1) {
24277
+ logger.error("Cannot combine --beta, --stable, and --channel.");
24278
+ logger.newLine();
24279
+ process.exit(1);
24280
+ }
24281
+ if (opts?.beta) {
24282
+ return { channel: "beta", persist: false };
24283
+ }
24284
+ if (opts?.stable) {
24285
+ return { channel: "stable", persist: false };
24286
+ }
24287
+ if (opts?.channel) {
24288
+ const value = opts.channel.toLowerCase();
24289
+ if (!RELEASE_CHANNELS.includes(value)) {
24290
+ logger.error(`Unknown channel "${opts.channel}". Use "stable" or "beta".`);
24291
+ logger.newLine();
24292
+ process.exit(1);
24293
+ }
24294
+ return { channel: value, persist: true };
24295
+ }
24296
+ }
24297
+ async function resolveUpgrade(targetVersion, channelOverride) {
24298
+ const target = await resolveTarget(targetVersion, channelOverride);
24299
+ const currentVersion = cliVersion.replace(/-dev$/, "");
24300
+ return {
24301
+ ...target,
24302
+ currentVersion,
24303
+ direction: compareVersions(currentVersion, target.desiredVersion)
24304
+ };
24305
+ }
24306
+ async function resolveTarget(targetVersion, channelOverride) {
24307
+ if (targetVersion) {
24308
+ const version3 = normalizeVersion(targetVersion);
24309
+ const channel2 = channelFromVersion(version3);
24310
+ await assertVersionExists(version3, channel2);
24311
+ return {
24312
+ channel: channel2,
24313
+ desiredVersion: version3,
24314
+ persistAs: channelOverride?.persist ? channelOverride.channel : void 0
24315
+ };
24316
+ }
24317
+ if (channelOverride) {
24318
+ return {
24319
+ channel: channelOverride.channel,
24320
+ desiredVersion: await fetchLatestVersion(channelOverride.channel),
24321
+ persistAs: channelOverride.persist ? channelOverride.channel : void 0
24322
+ };
24323
+ }
24324
+ const persisted = await loadPersistedChannel();
24325
+ const channel = resolveChannel(cliVersion, persisted);
24326
+ return {
24327
+ channel,
24328
+ desiredVersion: await fetchLatestVersion(channel)
24329
+ };
24330
+ }
23638
24331
  export {
23639
24332
  ASSET_DEV_ROUTE_PREFIX,
23640
24333
  ConfigError,
@@ -23643,7 +24336,7 @@ export {
23643
24336
  FilteredLog,
23644
24337
  GAME_METADATA_DRIFT_LABELS,
23645
24338
  GAME_METRICS_RESOLVER_PATH,
23646
- INSTALL_SCRIPT_URL,
24339
+ HISTORY_FETCH_TIMEOUT_MS,
23647
24340
  MANAGED_PACKAGES,
23648
24341
  MANIFEST_OBJECT_KEY,
23649
24342
  METRICS_RESOLVER_EXTENSIONS,
@@ -23661,9 +24354,9 @@ export {
23661
24354
  applySecretsChanges,
23662
24355
  assertDestructiveConfirmToken,
23663
24356
  assertRemoteWhenEnvGiven,
23664
- assertVersionExists,
23665
24357
  blockDeploy,
23666
24358
  bootstrapDashboardAdmin,
24359
+ buildCollectorUrl,
23667
24360
  buildDatabasePlan,
23668
24361
  buildDatabaseResetPayload,
23669
24362
  buildDeployBaseline,
@@ -23673,6 +24366,7 @@ export {
23673
24366
  buildGameRouteImportStatements,
23674
24367
  buildLocalTimebackConfigFromPlatform,
23675
24368
  buildLocalTimebackConfigWithPlatformCourses,
24369
+ buildLogHistoryUrl,
23676
24370
  buildLogStreamUrl,
23677
24371
  buildManifest,
23678
24372
  buildMetricsResolverCode,
@@ -23691,7 +24385,6 @@ export {
23691
24385
  bundleStubWorker,
23692
24386
  calculateConfigDiff,
23693
24387
  calculateDeploymentPlan,
23694
- channelFromVersion,
23695
24388
  checkAndPromptSecretsSync,
23696
24389
  checkDependencies,
23697
24390
  checkTimebackSetup,
@@ -23702,7 +24395,6 @@ export {
23702
24395
  collectFiles,
23703
24396
  collectSyncFiles,
23704
24397
  compareIntegrationKeys,
23705
- compareVersions,
23706
24398
  computeMigrationChecksum,
23707
24399
  confirm,
23708
24400
  confirmContinueWithDashboardOnlyFieldChanges,
@@ -23766,6 +24458,8 @@ export {
23766
24458
  displaySuccessMessage,
23767
24459
  displayTimebackConfigDiff,
23768
24460
  displayVerificationSummary,
24461
+ emitJsonError,
24462
+ emptyHistoryMessage,
23769
24463
  engines,
23770
24464
  ensureBucketDirectory,
23771
24465
  ensureDashboardTypes,
@@ -23776,6 +24470,7 @@ export {
23776
24470
  ensurePlaycademyGitignore,
23777
24471
  ensurePlaycademyTypes,
23778
24472
  ensureRootGitignore,
24473
+ entrySearchText,
23779
24474
  executeSeedFile,
23780
24475
  executeUpdates,
23781
24476
  exitAfterStdoutDrain,
@@ -23783,7 +24478,7 @@ export {
23783
24478
  fetchCompatibilityManifest,
23784
24479
  fetchDashboardUrl,
23785
24480
  fetchLatestNpmVersion,
23786
- fetchLatestVersion,
24481
+ fetchLogHistory,
23787
24482
  fetchPlatformTimebackConfigs,
23788
24483
  fetchSecretsDiff,
23789
24484
  fetchStateForDiff,
@@ -23823,7 +24518,6 @@ export {
23823
24518
  getDatabaseDirectory,
23824
24519
  getDeployedGame,
23825
24520
  getDirectorySize,
23826
- getDownloadBaseUrl,
23827
24521
  getDrizzleKitApiExports,
23828
24522
  getEnrollmentGuardedDrifts,
23829
24523
  getEnvironment,
@@ -23854,6 +24548,7 @@ export {
23854
24548
  handleGodotBuildPrompt,
23855
24549
  handleIntegrationConfigChanges,
23856
24550
  handleNetworkError,
24551
+ handleNonStandaloneInstall,
23857
24552
  handleSeedError,
23858
24553
  hasAuthSetup,
23859
24554
  hasBucketSetup,
@@ -23879,6 +24574,7 @@ export {
23879
24574
  hashFile2 as hashFile,
23880
24575
  hashFiles,
23881
24576
  hashSchemaSnapshot,
24577
+ historyTruncatedMessage,
23882
24578
  importSeedModule,
23883
24579
  importTypescriptDefault,
23884
24580
  importTypescriptFile,
@@ -23923,13 +24619,13 @@ export {
23923
24619
  loadGameStore,
23924
24620
  loadGitignorePatterns,
23925
24621
  loadJournalMigration,
23926
- loadPersistedChannel,
23927
24622
  localOnlySecretsDiff,
23928
24623
  logAndExit,
23929
24624
  logDeploymentPlanDebug,
23930
24625
  logger,
23931
24626
  matchCoursesToIntegrations,
23932
24627
  matchRestorePoint,
24628
+ matchesFilters,
23933
24629
  matchesGitignorePattern,
23934
24630
  maybeShowUpdateNudge,
23935
24631
  migrateManagedLines,
@@ -23939,23 +24635,31 @@ export {
23939
24635
  normalizeDashboardConfig,
23940
24636
  normalizeEnvironment,
23941
24637
  normalizeGitignoreEntry,
23942
- normalizeVersion,
24638
+ normalizeRayId,
24639
+ noteHistoryTruncated,
23943
24640
  openLocalAssetBucket,
23944
24641
  openLocalBucket,
23945
24642
  outOfOrderLines,
23946
24643
  outputDryRunResults,
23947
24644
  outputSyncResults,
24645
+ parseChannelFlag,
23948
24646
  parseCompatibilityManifest,
24647
+ parseHistoryBody,
24648
+ parseLogFilters,
23949
24649
  parsePresignedUploadError,
23950
24650
  parsePruneSecretsRequest,
24651
+ parseStatusFilter,
24652
+ parseTimeInput,
23951
24653
  password,
23952
24654
  pendingMigrations,
24655
+ performUpgrade,
23953
24656
  persistChannel,
23954
24657
  pickBaselineTag,
23955
24658
  pickResolution,
23956
24659
  prepareDeploymentContext,
23957
24660
  printDebugInfo,
23958
24661
  printGameDevBanner,
24662
+ printLogEntry,
23959
24663
  processConfigVariables,
23960
24664
  promptDestructiveConfirmToken,
23961
24665
  promptForAuthStrategies,
@@ -23999,6 +24703,7 @@ export {
23999
24703
  reportPushBaselineRecorded,
24000
24704
  reportRestoreOutcome,
24001
24705
  reportSeedResult,
24706
+ reportUpToDate,
24002
24707
  requireAuthenticatedClient,
24003
24708
  requireConfigFile,
24004
24709
  requireDashboardConfig,
@@ -24013,7 +24718,6 @@ export {
24013
24718
  resolveBookmark,
24014
24719
  resolveBucketDirectory,
24015
24720
  resolveBucketKey,
24016
- resolveChannel,
24017
24721
  resolveDashboardConfig,
24018
24722
  resolveDashboardContext,
24019
24723
  resolveGameFromConfig,
@@ -24025,6 +24729,7 @@ export {
24025
24729
  resolvePruneSecrets,
24026
24730
  resolveSchemaAdoption,
24027
24731
  resolveSchemaStrategy,
24732
+ resolveUpgrade,
24028
24733
  restoreUnsupportedLines,
24029
24734
  rollbackDisplayName,
24030
24735
  runInit,
@@ -24056,6 +24761,7 @@ export {
24056
24761
  startDashboardDevServer,
24057
24762
  startGameDevServer,
24058
24763
  startHotReload,
24764
+ statusFilterToParam,
24059
24765
  syncBucket,
24060
24766
  timebackChangeDetector,
24061
24767
  transpileTypeScript,
@@ -24066,7 +24772,6 @@ export {
24066
24772
  updateExistingCourses,
24067
24773
  updateTimebackConfig,
24068
24774
  updateViteConfig,
24069
- upgradeStandalone,
24070
24775
  validateAndNormalizeHostname,
24071
24776
  validateApiDirectoryDoesNotExist,
24072
24777
  validateBuildPath,
@@ -1,7 +1,7 @@
1
1
  {
2
- "cliVersion": "0.27.1-beta.9",
3
- "sdkVersion": "0.15.1-beta.7",
4
- "runtimeBuildId": "78ef292fb25f",
5
- "inputFingerprint": "78ef292fb25f862f09b3e66182f32e85449f7ab8d053c011d201b2759c9f5b7c",
2
+ "cliVersion": "0.28.0",
3
+ "sdkVersion": "0.16.0",
4
+ "runtimeBuildId": "a31368310480",
5
+ "inputFingerprint": "a31368310480aebd9dd80e86275df8fdc37cde99a5791b87ccb75eb81d69cb40",
6
6
  "entry": "index.js"
7
7
  }
@@ -235,9 +235,9 @@ var init_list = __esm({
235
235
  // ../edge-play/src/game/lib/metadata.ts
236
236
  function getRuntimeMetadata() {
237
237
  return {
238
- cliVersion: true ? "0.27.1-beta.9" : "0.0.0-dev",
239
- sdkVersion: true ? "0.15.1-beta.7" : "0.0.0-dev",
240
- buildId: true ? "78ef292fb25f" : "dev-source"
238
+ cliVersion: true ? "0.28.0" : "0.0.0-dev",
239
+ sdkVersion: true ? "0.16.0" : "0.0.0-dev",
240
+ buildId: true ? "a31368310480" : "dev-source"
241
241
  };
242
242
  }
243
243
  var init_metadata = __esm({
@@ -1,7 +1,7 @@
1
1
  {
2
- "cliVersion": "0.27.1-beta.9",
3
- "sdkVersion": "0.15.1-beta.7",
4
- "runtimeBuildId": "78ef292fb25f",
5
- "inputFingerprint": "78ef292fb25f862f09b3e66182f32e85449f7ab8d053c011d201b2759c9f5b7c",
2
+ "cliVersion": "0.28.0",
3
+ "sdkVersion": "0.16.0",
4
+ "runtimeBuildId": "a31368310480",
5
+ "inputFingerprint": "a31368310480aebd9dd80e86275df8fdc37cde99a5791b87ccb75eb81d69cb40",
6
6
  "entry": "index.js"
7
7
  }
package/dist/utils.js CHANGED
@@ -369,7 +369,7 @@ var DASHBOARD_API_ROUTES_DIRECTORY = "api";
369
369
  // ../better-auth/package.json
370
370
  var package_default = {
371
371
  name: "@playcademy/better-auth",
372
- version: "0.0.20-beta.7",
372
+ version: "0.0.20",
373
373
  type: "module",
374
374
  exports: {
375
375
  "./server": {
@@ -2774,7 +2774,7 @@ import { existsSync as existsSync9, mkdirSync as mkdirSync2, writeFileSync as wr
2774
2774
  import { dirname as dirname4, join as join14 } from "node:path";
2775
2775
 
2776
2776
  // src/version.ts
2777
- var cliVersion = false ? "0.0.0-dev" : "0.27.1-beta.9";
2777
+ var cliVersion = false ? "0.0.0-dev" : "0.28.0";
2778
2778
 
2779
2779
  // src/lib/build/binary-resource.ts
2780
2780
  function writeFileTree(baseDir, files) {
package/dist/version.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/version.ts
2
- var cliVersion = false ? "0.0.0-dev" : "0.27.1-beta.9";
2
+ var cliVersion = false ? "0.0.0-dev" : "0.28.0";
3
3
  export {
4
4
  cliVersion
5
5
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "playcademy",
3
- "version": "0.27.1-beta.9",
3
+ "version": "0.28.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -55,7 +55,7 @@
55
55
  },
56
56
  "dependencies": {
57
57
  "@inquirer/prompts": "^7.8.6",
58
- "@playcademy/sdk": "0.15.1-beta.7",
58
+ "@playcademy/sdk": "0.16.0",
59
59
  "chokidar": "^4.0.3",
60
60
  "colorette": "^2.0.20",
61
61
  "commander": "^14.0.1",