playcademy 0.12.9 → 0.13.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/constants.d.ts +5 -2
- package/dist/constants.js +8 -3
- package/dist/db.js +15 -11
- package/dist/index.d.ts +38 -1
- package/dist/index.js +1378 -263
- package/dist/templates/api/sample-custom.ts.template +46 -0
- package/dist/templates/api/sample-database.ts.template +104 -0
- package/dist/templates/api/sample-kv.ts.template +135 -0
- package/dist/templates/playcademy-gitignore.template +1 -0
- package/dist/utils.d.ts +9 -5
- package/dist/utils.js +126 -46
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -2118,15 +2118,19 @@ var init_http_server = __esm({
|
|
|
2118
2118
|
});
|
|
2119
2119
|
|
|
2120
2120
|
// src/constants/paths.ts
|
|
2121
|
-
|
|
2121
|
+
import { join as join2 } from "path";
|
|
2122
|
+
var WORKSPACE_NAME, CLI_DIRECTORIES, CLI_USER_DIRECTORIES, CLI_DEFAULT_OUTPUTS, CLI_FILES;
|
|
2122
2123
|
var init_paths = __esm({
|
|
2123
2124
|
"src/constants/paths.ts"() {
|
|
2124
2125
|
"use strict";
|
|
2126
|
+
WORKSPACE_NAME = ".playcademy";
|
|
2125
2127
|
CLI_DIRECTORIES = {
|
|
2126
2128
|
/** Root directory for CLI artifacts in workspace */
|
|
2127
|
-
WORKSPACE:
|
|
2129
|
+
WORKSPACE: WORKSPACE_NAME,
|
|
2128
2130
|
/** Database directory within workspace */
|
|
2129
|
-
DATABASE: "
|
|
2131
|
+
DATABASE: join2(WORKSPACE_NAME, "db"),
|
|
2132
|
+
/** KV storage directory within workspace */
|
|
2133
|
+
KV: join2(WORKSPACE_NAME, "kv")
|
|
2130
2134
|
};
|
|
2131
2135
|
CLI_USER_DIRECTORIES = {
|
|
2132
2136
|
/** User config directory for auth, games store, etc. */
|
|
@@ -2423,12 +2427,12 @@ var init_config = __esm({
|
|
|
2423
2427
|
// src/lib/auth/storage.ts
|
|
2424
2428
|
import { access, mkdir, readFile as readFile2, writeFile } from "node:fs/promises";
|
|
2425
2429
|
import { homedir } from "node:os";
|
|
2426
|
-
import { join as
|
|
2430
|
+
import { join as join3 } from "node:path";
|
|
2427
2431
|
function getAuthPath() {
|
|
2428
|
-
return
|
|
2432
|
+
return join3(homedir(), CLI_USER_DIRECTORIES.CONFIG, CLI_FILES.AUTH_STORE);
|
|
2429
2433
|
}
|
|
2430
2434
|
async function ensureAuthDir() {
|
|
2431
|
-
const authDir =
|
|
2435
|
+
const authDir = join3(homedir(), CLI_USER_DIRECTORIES.CONFIG);
|
|
2432
2436
|
await mkdir(authDir, { recursive: true });
|
|
2433
2437
|
}
|
|
2434
2438
|
function createEmptyEnvironmentProfiles() {
|
|
@@ -2584,7 +2588,7 @@ async function findConfigPath(configPath) {
|
|
|
2584
2588
|
throw new ConfigError(
|
|
2585
2589
|
"No Playcademy config file found in this directory or any parent directory",
|
|
2586
2590
|
void 0,
|
|
2587
|
-
`
|
|
2591
|
+
`Run 'playcademy init' to create a config file, or manually create one of: ${CONFIG_FILE_NAMES.join(", ")}`
|
|
2588
2592
|
);
|
|
2589
2593
|
}
|
|
2590
2594
|
return result.path;
|
|
@@ -3137,7 +3141,7 @@ function loadTemplate(filename) {
|
|
|
3137
3141
|
return loadTemplateString(`config/${filename}`);
|
|
3138
3142
|
}
|
|
3139
3143
|
function generateJsConfig(options) {
|
|
3140
|
-
const { name, description, emoji, customRoutesDirectory, databaseDirectory, timeback } = options;
|
|
3144
|
+
const { name, description, emoji, customRoutesDirectory, databaseDirectory, kv, timeback } = options;
|
|
3141
3145
|
let template = loadTemplate("playcademy.config.js.template");
|
|
3142
3146
|
template = template.replace("{{GAME_NAME}}", name);
|
|
3143
3147
|
const descriptionLine = description ? `
|
|
@@ -3153,6 +3157,9 @@ function generateJsConfig(options) {
|
|
|
3153
3157
|
if (databaseDirectory) {
|
|
3154
3158
|
integrationsParts.push(` database: { directory: '${databaseDirectory}' }`);
|
|
3155
3159
|
}
|
|
3160
|
+
if (kv) {
|
|
3161
|
+
integrationsParts.push(` kv: true`);
|
|
3162
|
+
}
|
|
3156
3163
|
if (timeback) {
|
|
3157
3164
|
let timebackTemplate = loadTemplate("timeback-config.js.template");
|
|
3158
3165
|
timebackTemplate = timebackTemplate.replace(
|
|
@@ -3179,7 +3186,7 @@ function generateJsConfig(options) {
|
|
|
3179
3186
|
return template;
|
|
3180
3187
|
}
|
|
3181
3188
|
function generateJsonConfig(options) {
|
|
3182
|
-
const { name, description, emoji, customRoutesDirectory, databaseDirectory, timeback } = options;
|
|
3189
|
+
const { name, description, emoji, customRoutesDirectory, databaseDirectory, kv, timeback } = options;
|
|
3183
3190
|
let template = loadTemplate("playcademy.config.json.template");
|
|
3184
3191
|
template = template.replace("{{GAME_NAME}}", name);
|
|
3185
3192
|
const descriptionLine = description ? `,
|
|
@@ -3195,6 +3202,9 @@ function generateJsonConfig(options) {
|
|
|
3195
3202
|
if (databaseDirectory) {
|
|
3196
3203
|
integrationsConfig.database = { directory: databaseDirectory };
|
|
3197
3204
|
}
|
|
3205
|
+
if (kv) {
|
|
3206
|
+
integrationsConfig.kv = true;
|
|
3207
|
+
}
|
|
3198
3208
|
if (timeback) {
|
|
3199
3209
|
const courseConfig = {
|
|
3200
3210
|
subjects: timeback.subjects,
|
|
@@ -3613,12 +3623,12 @@ var init_errors = __esm({
|
|
|
3613
3623
|
// src/lib/core/import.ts
|
|
3614
3624
|
import { mkdtempSync, rmSync } from "fs";
|
|
3615
3625
|
import { tmpdir } from "os";
|
|
3616
|
-
import { join as
|
|
3626
|
+
import { join as join4 } from "path";
|
|
3617
3627
|
import { pathToFileURL } from "url";
|
|
3618
3628
|
import * as esbuild from "esbuild";
|
|
3619
3629
|
async function importTypescriptFile(filePath, bundleOptions) {
|
|
3620
|
-
const tempDir = mkdtempSync(
|
|
3621
|
-
const outFile =
|
|
3630
|
+
const tempDir = mkdtempSync(join4(tmpdir(), "playcademy-import-"));
|
|
3631
|
+
const outFile = join4(tempDir, "bundle.mjs");
|
|
3622
3632
|
try {
|
|
3623
3633
|
await esbuild.build({
|
|
3624
3634
|
entryPoints: [filePath],
|
|
@@ -4519,7 +4529,7 @@ init_core();
|
|
|
4519
4529
|
// src/lib/db/path.ts
|
|
4520
4530
|
init_constants2();
|
|
4521
4531
|
import { copyFileSync, existsSync as existsSync5, mkdirSync, readdirSync as readdirSync2, unlinkSync } from "fs";
|
|
4522
|
-
import { join as
|
|
4532
|
+
import { join as join5 } from "path";
|
|
4523
4533
|
import Database from "better-sqlite3";
|
|
4524
4534
|
var DB_DIRECTORY = CLI_DIRECTORIES.DATABASE;
|
|
4525
4535
|
var INITIAL_DB_NAME = CLI_FILES.INITIAL_DATABASE;
|
|
@@ -4533,11 +4543,11 @@ var createEmptyDatabase = (path2) => {
|
|
|
4533
4543
|
db.close();
|
|
4534
4544
|
};
|
|
4535
4545
|
var findMiniflareDatabase = (dbDir) => {
|
|
4536
|
-
const miniflareDir =
|
|
4546
|
+
const miniflareDir = join5(dbDir, "miniflare-D1DatabaseObject");
|
|
4537
4547
|
if (!existsSync5(miniflareDir)) return null;
|
|
4538
4548
|
const sqliteFiles = readdirSync2(miniflareDir).filter((file) => file.endsWith(".sqlite"));
|
|
4539
4549
|
if (sqliteFiles.length === 0) return null;
|
|
4540
|
-
return
|
|
4550
|
+
return join5(miniflareDir, sqliteFiles[0]);
|
|
4541
4551
|
};
|
|
4542
4552
|
var migrateInitialDbToTarget = (initialPath, targetPath) => {
|
|
4543
4553
|
if (!existsSync5(initialPath)) return;
|
|
@@ -4545,7 +4555,7 @@ var migrateInitialDbToTarget = (initialPath, targetPath) => {
|
|
|
4545
4555
|
unlinkSync(initialPath);
|
|
4546
4556
|
};
|
|
4547
4557
|
function getDevDbPath() {
|
|
4548
|
-
const initialDbPath =
|
|
4558
|
+
const initialDbPath = join5(DB_DIRECTORY, INITIAL_DB_NAME);
|
|
4549
4559
|
ensureDirectoryExists(DB_DIRECTORY);
|
|
4550
4560
|
const miniflareDbPath = findMiniflareDatabase(DB_DIRECTORY);
|
|
4551
4561
|
if (miniflareDbPath) {
|
|
@@ -4605,17 +4615,16 @@ init_src();
|
|
|
4605
4615
|
init_constants2();
|
|
4606
4616
|
init_core();
|
|
4607
4617
|
import { existsSync as existsSync10 } from "node:fs";
|
|
4608
|
-
import { join as
|
|
4618
|
+
import { join as join11 } from "node:path";
|
|
4609
4619
|
|
|
4610
4620
|
// src/lib/init/database.ts
|
|
4611
4621
|
init_log();
|
|
4612
4622
|
init_slug();
|
|
4613
|
-
init_constants2();
|
|
4614
4623
|
init_core();
|
|
4615
4624
|
init_logger();
|
|
4616
4625
|
init_loader2();
|
|
4617
4626
|
import { existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
4618
|
-
import { join as
|
|
4627
|
+
import { join as join6 } from "path";
|
|
4619
4628
|
var drizzleConfigTemplate = loadTemplateString("database/drizzle-config.ts");
|
|
4620
4629
|
var dbSchemaUsersTemplate = loadTemplateString("database/db-schema-users.ts");
|
|
4621
4630
|
var dbSchemaScoresTemplate = loadTemplateString("database/db-schema-scores.ts");
|
|
@@ -4623,13 +4632,12 @@ var dbSchemaIndexTemplate = loadTemplateString("database/db-schema-index.ts");
|
|
|
4623
4632
|
var dbIndexTemplate = loadTemplateString("database/db-index.ts");
|
|
4624
4633
|
var dbTypesTemplate = loadTemplateString("database/db-types.ts");
|
|
4625
4634
|
var packageTemplate = loadTemplateString("database/package.json");
|
|
4626
|
-
var playcademyGitignoreTemplate = loadTemplateString("playcademy-gitignore");
|
|
4627
4635
|
var rootGitignoreTemplate = loadTemplateString("gitignore");
|
|
4628
4636
|
function normalizeGitignoreEntry(entry) {
|
|
4629
4637
|
return entry.replace(/^\/+/, "").replace(/\/+$/, "");
|
|
4630
4638
|
}
|
|
4631
4639
|
function ensureGitignoreEntries(workspace) {
|
|
4632
|
-
const gitignorePath =
|
|
4640
|
+
const gitignorePath = join6(workspace, ".gitignore");
|
|
4633
4641
|
if (!existsSync6(gitignorePath)) {
|
|
4634
4642
|
writeFileSync2(gitignorePath, rootGitignoreTemplate);
|
|
4635
4643
|
return;
|
|
@@ -4659,32 +4667,26 @@ async function scaffoldDatabaseSetup(options) {
|
|
|
4659
4667
|
await runStep(
|
|
4660
4668
|
"Configuring database...",
|
|
4661
4669
|
async () => {
|
|
4662
|
-
const dbDir =
|
|
4663
|
-
const schemaDir =
|
|
4670
|
+
const dbDir = join6(workspace, "db");
|
|
4671
|
+
const schemaDir = join6(dbDir, "schema");
|
|
4664
4672
|
if (!existsSync6(dbDir)) {
|
|
4665
4673
|
mkdirSync2(dbDir, { recursive: true });
|
|
4666
4674
|
}
|
|
4667
4675
|
if (!existsSync6(schemaDir)) {
|
|
4668
4676
|
mkdirSync2(schemaDir, { recursive: true });
|
|
4669
4677
|
}
|
|
4670
|
-
const usersSchemaPath =
|
|
4678
|
+
const usersSchemaPath = join6(schemaDir, "users.ts");
|
|
4671
4679
|
writeFileSync2(usersSchemaPath, dbSchemaUsersTemplate);
|
|
4672
|
-
const scoresSchemaPath =
|
|
4680
|
+
const scoresSchemaPath = join6(schemaDir, "scores.ts");
|
|
4673
4681
|
writeFileSync2(scoresSchemaPath, dbSchemaScoresTemplate);
|
|
4674
|
-
const schemaIndexPath =
|
|
4682
|
+
const schemaIndexPath = join6(schemaDir, "index.ts");
|
|
4675
4683
|
writeFileSync2(schemaIndexPath, dbSchemaIndexTemplate);
|
|
4676
|
-
const dbIndexPath =
|
|
4684
|
+
const dbIndexPath = join6(dbDir, "index.ts");
|
|
4677
4685
|
writeFileSync2(dbIndexPath, dbIndexTemplate);
|
|
4678
|
-
const dbTypesPath =
|
|
4686
|
+
const dbTypesPath = join6(dbDir, "types.ts");
|
|
4679
4687
|
writeFileSync2(dbTypesPath, dbTypesTemplate);
|
|
4680
|
-
const drizzleConfigPath =
|
|
4688
|
+
const drizzleConfigPath = join6(workspace, "drizzle.config.ts");
|
|
4681
4689
|
writeFileSync2(drizzleConfigPath, drizzleConfigTemplate);
|
|
4682
|
-
const playcademyDir = join5(workspace, CLI_DIRECTORIES.WORKSPACE);
|
|
4683
|
-
if (!existsSync6(playcademyDir)) {
|
|
4684
|
-
mkdirSync2(playcademyDir, { recursive: true });
|
|
4685
|
-
}
|
|
4686
|
-
const playcademyGitignorePath = join5(playcademyDir, ".gitignore");
|
|
4687
|
-
writeFileSync2(playcademyGitignorePath, playcademyGitignoreTemplate);
|
|
4688
4690
|
ensureGitignoreEntries(workspace);
|
|
4689
4691
|
packagesUpdated = await setupPackageJson(workspace, options.gameName);
|
|
4690
4692
|
},
|
|
@@ -4693,7 +4695,7 @@ async function scaffoldDatabaseSetup(options) {
|
|
|
4693
4695
|
return packagesUpdated;
|
|
4694
4696
|
}
|
|
4695
4697
|
async function setupPackageJson(workspace, gameName) {
|
|
4696
|
-
const pkgPath =
|
|
4698
|
+
const pkgPath = join6(workspace, "package.json");
|
|
4697
4699
|
const dbDeps = {
|
|
4698
4700
|
"drizzle-orm": "^0.42.0",
|
|
4699
4701
|
"better-sqlite3": "^12.0.0"
|
|
@@ -4733,11 +4735,16 @@ async function setupPackageJson(workspace, gameName) {
|
|
|
4733
4735
|
}
|
|
4734
4736
|
function hasDatabaseSetup() {
|
|
4735
4737
|
const workspace = getWorkspace();
|
|
4736
|
-
const drizzleConfigPath =
|
|
4737
|
-
const drizzleConfigJsPath =
|
|
4738
|
+
const drizzleConfigPath = join6(workspace, "drizzle.config.ts");
|
|
4739
|
+
const drizzleConfigJsPath = join6(workspace, "drizzle.config.js");
|
|
4738
4740
|
return existsSync6(drizzleConfigPath) || existsSync6(drizzleConfigJsPath);
|
|
4739
4741
|
}
|
|
4740
4742
|
|
|
4743
|
+
// src/lib/init/kv.ts
|
|
4744
|
+
function hasKVSetup(config) {
|
|
4745
|
+
return !!config.integrations?.kv;
|
|
4746
|
+
}
|
|
4747
|
+
|
|
4741
4748
|
// src/lib/integrations/timeback.ts
|
|
4742
4749
|
init_src();
|
|
4743
4750
|
init_core();
|
|
@@ -4868,7 +4875,7 @@ var integrationChangeDetectors = {
|
|
|
4868
4875
|
|
|
4869
4876
|
// src/lib/deploy/bundle.ts
|
|
4870
4877
|
import { existsSync as existsSync7 } from "fs";
|
|
4871
|
-
import { join as
|
|
4878
|
+
import { join as join8 } from "path";
|
|
4872
4879
|
|
|
4873
4880
|
// ../edge-play/src/entry.ts
|
|
4874
4881
|
var entry_default = "/**\n * Game Backend Entry Point\n *\n * This file is the main entry point for deployed game backends.\n * It creates a Hono app and registers all enabled integration routes.\n *\n * Bundled with esbuild and deployed to Cloudflare Workers (or AWS Lambda).\n * Config is injected at build time via esbuild's `define` option.\n */\n\nimport { Hono } from 'hono'\nimport { cors } from 'hono/cors'\n\nimport { PlaycademyClient } from '@playcademy/sdk/server'\n\nimport { ENV_VARS } from './constants'\nimport { registerBuiltinRoutes } from './register-routes'\n\nimport type { PlaycademyConfig } from '@playcademy/sdk/server'\nimport type { HonoEnv } from './types'\n\n/**\n * Config injected at build time by esbuild\n *\n * The `declare const` tells TypeScript \"this exists at runtime, trust me.\"\n * During bundling, esbuild's `define` option does literal text replacement:\n *\n * Example bundling:\n * Source: if (PLAYCADEMY_CONFIG.integrations.timeback) { ... }\n * Define: { 'PLAYCADEMY_CONFIG': JSON.stringify({ integrations: { timeback: {...} } }) }\n * Output: if ({\"integrations\":{\"timeback\":{...}}}.integrations.timeback) { ... }\n *\n * This enables tree-shaking: if timeback is not configured, those code paths are removed.\n * The bundled Worker only includes the routes that are actually enabled.\n */\ndeclare const PLAYCADEMY_CONFIG: PlaycademyConfig & {\n customRoutes?: Array<{ path: string; file: string }>\n}\n\n// XXX: Polyfill process global for SDK compatibility\n// SDK code may reference process.env without importing it\n// @ts-expect-error - Adding global for Worker environment\nglobalThis.process = {\n env: {}, // Populated per-request from Worker env bindings\n cwd: () => '/',\n}\n\nconst app = new Hono<HonoEnv>()\n\n// TODO: Harden CORS in production - restrict to trusted origins:\n// - Game's assetBundleBase (for hosted games)\n// - Game's externalUrl (for external games)\n// - Platform frontend domains (hub.playcademy.com, hub.dev.playcademy.net)\n// This would require passing game metadata through env bindings during deployment\napp.use(\n '*',\n cors({\n origin: '*', // Permissive for now\n allowMethods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],\n allowHeaders: ['Content-Type', 'Authorization'],\n }),\n)\n\nlet sdkPromise: Promise<PlaycademyClient> | null = null\n\napp.use('*', async (c, next) => {\n // Populate process.env from Worker bindings for SDK compatibility\n globalThis.process.env = {\n [ENV_VARS.PLAYCADEMY_API_KEY]: c.env.PLAYCADEMY_API_KEY,\n [ENV_VARS.GAME_ID]: c.env.GAME_ID,\n [ENV_VARS.PLAYCADEMY_BASE_URL]: c.env.PLAYCADEMY_BASE_URL,\n }\n\n // Set config for all routes\n c.set('config', PLAYCADEMY_CONFIG)\n c.set('customRoutes', PLAYCADEMY_CONFIG.customRoutes || [])\n\n await next()\n})\n\n// Initialize SDK lazily on first request\napp.use('*', async (c, next) => {\n if (!sdkPromise) {\n sdkPromise = PlaycademyClient.init({\n apiKey: c.env[ENV_VARS.PLAYCADEMY_API_KEY],\n gameId: c.env[ENV_VARS.GAME_ID],\n baseUrl: c.env[ENV_VARS.PLAYCADEMY_BASE_URL],\n config: PLAYCADEMY_CONFIG,\n })\n }\n\n c.set('sdk', await sdkPromise)\n await next()\n})\n\n/**\n * Register built-in integration routes based on enabled integrations\n *\n * This function conditionally imports and registers routes like:\n * - POST /api/integrations/timeback/end-activity (if timeback enabled)\n * - GET /api/health (always included)\n *\n * Uses dynamic imports for tree-shaking: if an integration is not enabled,\n * its route code is completely removed from the bundle.\n */\nawait registerBuiltinRoutes(app, PLAYCADEMY_CONFIG.integrations)\n\nexport default app\n";
|
|
@@ -5131,7 +5138,7 @@ init_file_loader();
|
|
|
5131
5138
|
init_core();
|
|
5132
5139
|
import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
|
|
5133
5140
|
import { tmpdir as tmpdir2 } from "os";
|
|
5134
|
-
import { join as
|
|
5141
|
+
import { join as join7, relative } from "path";
|
|
5135
5142
|
|
|
5136
5143
|
// src/lib/deploy/hash.ts
|
|
5137
5144
|
init_file_loader();
|
|
@@ -5179,7 +5186,7 @@ async function discoverRoutes(apiDir) {
|
|
|
5179
5186
|
const routes = await Promise.all(
|
|
5180
5187
|
files.map(async (file) => {
|
|
5181
5188
|
const routePath = filePathToRoutePath(file);
|
|
5182
|
-
const absolutePath =
|
|
5189
|
+
const absolutePath = join7(apiDir, file);
|
|
5183
5190
|
const relativePath = relative(getWorkspace(), absolutePath);
|
|
5184
5191
|
const methods = await detectExportedMethods(absolutePath);
|
|
5185
5192
|
return {
|
|
@@ -5239,10 +5246,10 @@ async function transpileRoute(filePath) {
|
|
|
5239
5246
|
if (!result.outputFiles?.[0]) {
|
|
5240
5247
|
throw new Error("Transpilation failed: no output");
|
|
5241
5248
|
}
|
|
5242
|
-
const tempDir =
|
|
5249
|
+
const tempDir = join7(tmpdir2(), "playcademy-dev");
|
|
5243
5250
|
await mkdir2(tempDir, { recursive: true });
|
|
5244
5251
|
const hash = hashContent(filePath).slice(0, 12);
|
|
5245
|
-
const jsPath =
|
|
5252
|
+
const jsPath = join7(tempDir, `${hash}.mjs`);
|
|
5246
5253
|
await writeFile2(jsPath, result.outputFiles[0].text);
|
|
5247
5254
|
return jsPath;
|
|
5248
5255
|
}
|
|
@@ -5270,7 +5277,7 @@ async function discoverCustomRoutes(config) {
|
|
|
5270
5277
|
const workspace = getWorkspace();
|
|
5271
5278
|
const customRoutesConfig = config.integrations?.customRoutes;
|
|
5272
5279
|
const customRoutesDir = typeof customRoutesConfig === "object" && customRoutesConfig.directory || DEFAULT_API_ROUTES_DIRECTORY;
|
|
5273
|
-
const customRoutes = await discoverRoutes(
|
|
5280
|
+
const customRoutes = await discoverRoutes(join8(workspace, customRoutesDir));
|
|
5274
5281
|
const customRouteData = customRoutes.map((r) => ({
|
|
5275
5282
|
path: r.path,
|
|
5276
5283
|
file: r.file,
|
|
@@ -5282,15 +5289,15 @@ async function discoverCustomRoutes(config) {
|
|
|
5282
5289
|
function resolveEmbeddedSourcePaths() {
|
|
5283
5290
|
const workspace = getWorkspace();
|
|
5284
5291
|
const distDir = new URL(".", import.meta.url).pathname;
|
|
5285
|
-
const embeddedEdgeSrc =
|
|
5292
|
+
const embeddedEdgeSrc = join8(distDir, "edge-play", "src");
|
|
5286
5293
|
const isBuiltPackage = existsSync7(embeddedEdgeSrc);
|
|
5287
5294
|
const monorepoRoot = getMonorepoRoot();
|
|
5288
|
-
const monorepoEdgeSrc =
|
|
5295
|
+
const monorepoEdgeSrc = join8(monorepoRoot, "packages/edge-play/src");
|
|
5289
5296
|
const edgePlaySrc = isBuiltPackage ? embeddedEdgeSrc : monorepoEdgeSrc;
|
|
5290
|
-
const cliPackageRoot = isBuiltPackage ?
|
|
5291
|
-
const cliNodeModules = isBuiltPackage ?
|
|
5292
|
-
const workspaceNodeModules =
|
|
5293
|
-
const constantsEntry = isBuiltPackage ?
|
|
5297
|
+
const cliPackageRoot = isBuiltPackage ? join8(distDir, "../../..") : join8(monorepoRoot, "packages/cli");
|
|
5298
|
+
const cliNodeModules = isBuiltPackage ? join8(cliPackageRoot, "node_modules") : monorepoRoot;
|
|
5299
|
+
const workspaceNodeModules = join8(workspace, "node_modules");
|
|
5300
|
+
const constantsEntry = isBuiltPackage ? join8(embeddedEdgeSrc, "..", "..", "constants", "src", "index.ts") : join8(monorepoRoot, "packages", "constants", "src", "index.ts");
|
|
5294
5301
|
return {
|
|
5295
5302
|
isBuiltPackage,
|
|
5296
5303
|
edgePlaySrc,
|
|
@@ -5350,16 +5357,16 @@ function createEsbuildConfig(entryCode, paths, bundleConfig, customRoutesDir, op
|
|
|
5350
5357
|
// │ Example: import * as route from '@game-api/hello.ts' │
|
|
5351
5358
|
// │ Resolves to: /user-project/server/api/hello.ts │
|
|
5352
5359
|
// └─────────────────────────────────────────────────────────────────┘
|
|
5353
|
-
"@game-api":
|
|
5360
|
+
"@game-api": join8(workspace, customRoutesDir),
|
|
5354
5361
|
// ┌─ Node.js polyfills for Cloudflare Workers ──────────────────────┐
|
|
5355
5362
|
// │ Workers don't have fs, path, os, etc. Redirect to polyfills │
|
|
5356
5363
|
// │ that throw helpful errors if user code tries to use them. │
|
|
5357
5364
|
// └─────────────────────────────────────────────────────────────────┘
|
|
5358
|
-
fs:
|
|
5359
|
-
"fs/promises":
|
|
5360
|
-
path:
|
|
5361
|
-
os:
|
|
5362
|
-
process:
|
|
5365
|
+
fs: join8(edgePlaySrc, "polyfills.js"),
|
|
5366
|
+
"fs/promises": join8(edgePlaySrc, "polyfills.js"),
|
|
5367
|
+
path: join8(edgePlaySrc, "polyfills.js"),
|
|
5368
|
+
os: join8(edgePlaySrc, "polyfills.js"),
|
|
5369
|
+
process: join8(edgePlaySrc, "polyfills.js")
|
|
5363
5370
|
},
|
|
5364
5371
|
// ──── Build Plugins ────
|
|
5365
5372
|
plugins: [textLoaderPlugin()],
|
|
@@ -5425,7 +5432,7 @@ function generateEntryCode(customRoutes, customRoutesDir) {
|
|
|
5425
5432
|
init_core();
|
|
5426
5433
|
import { existsSync as existsSync9 } from "fs";
|
|
5427
5434
|
import { createRequire } from "module";
|
|
5428
|
-
import { join as
|
|
5435
|
+
import { join as join10 } from "path";
|
|
5429
5436
|
|
|
5430
5437
|
// src/lib/init/prompts.ts
|
|
5431
5438
|
init_constants3();
|
|
@@ -5435,20 +5442,29 @@ import { bold as bold3, cyan as cyan2 } from "colorette";
|
|
|
5435
5442
|
|
|
5436
5443
|
// src/lib/init/scaffold.ts
|
|
5437
5444
|
init_src();
|
|
5445
|
+
init_constants2();
|
|
5438
5446
|
init_core();
|
|
5439
5447
|
init_loader2();
|
|
5440
5448
|
import { existsSync as existsSync8, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
5441
|
-
import { join as
|
|
5442
|
-
var
|
|
5443
|
-
var
|
|
5444
|
-
|
|
5449
|
+
import { join as join9, resolve as resolve6 } from "path";
|
|
5450
|
+
var sampleCustomRouteTemplate = loadTemplateString("api/sample-custom.ts");
|
|
5451
|
+
var sampleDatabaseRouteTemplate = loadTemplateString("api/sample-database.ts");
|
|
5452
|
+
var sampleKvRouteTemplate = loadTemplateString("api/sample-kv.ts");
|
|
5453
|
+
var playcademyGitignoreTemplate = loadTemplateString("playcademy-gitignore");
|
|
5454
|
+
async function scaffoldApiDirectory(apiDirectory, sampleRoutes) {
|
|
5445
5455
|
const apiPath = resolve6(getWorkspace(), apiDirectory);
|
|
5456
|
+
const samplePath = join9(apiPath, "sample");
|
|
5446
5457
|
await runStep(
|
|
5447
5458
|
"Scaffolding API directory",
|
|
5448
5459
|
async () => {
|
|
5449
5460
|
if (!existsSync8(apiPath)) {
|
|
5450
5461
|
mkdirSync3(apiPath, { recursive: true });
|
|
5451
|
-
|
|
5462
|
+
}
|
|
5463
|
+
if (!existsSync8(samplePath)) {
|
|
5464
|
+
mkdirSync3(samplePath, { recursive: true });
|
|
5465
|
+
}
|
|
5466
|
+
for (const route of sampleRoutes) {
|
|
5467
|
+
writeFileSync3(join9(samplePath, route.filename), route.template, "utf-8");
|
|
5452
5468
|
}
|
|
5453
5469
|
},
|
|
5454
5470
|
"API directory scaffolded"
|
|
@@ -5461,10 +5477,29 @@ function validateApiDirectoryDoesNotExist(value) {
|
|
|
5461
5477
|
}
|
|
5462
5478
|
return true;
|
|
5463
5479
|
}
|
|
5464
|
-
|
|
5480
|
+
function ensurePlaycademyGitignore() {
|
|
5481
|
+
const workspace = getWorkspace();
|
|
5482
|
+
const playcademyDir = join9(workspace, CLI_DIRECTORIES.WORKSPACE);
|
|
5483
|
+
if (!existsSync8(playcademyDir)) {
|
|
5484
|
+
mkdirSync3(playcademyDir, { recursive: true });
|
|
5485
|
+
}
|
|
5486
|
+
const gitignorePath = join9(playcademyDir, ".gitignore");
|
|
5487
|
+
writeFileSync3(gitignorePath, playcademyGitignoreTemplate);
|
|
5488
|
+
}
|
|
5489
|
+
async function scaffoldIntegrations(customRoutes, database, kv, gameName) {
|
|
5490
|
+
ensurePlaycademyGitignore();
|
|
5465
5491
|
if (customRoutes) {
|
|
5466
|
-
const
|
|
5467
|
-
|
|
5492
|
+
const sampleRoutes = [
|
|
5493
|
+
// Always include basic custom route example
|
|
5494
|
+
{ filename: "custom.ts", template: sampleCustomRouteTemplate }
|
|
5495
|
+
];
|
|
5496
|
+
if (database) {
|
|
5497
|
+
sampleRoutes.push({ filename: "database.ts", template: sampleDatabaseRouteTemplate });
|
|
5498
|
+
}
|
|
5499
|
+
if (kv) {
|
|
5500
|
+
sampleRoutes.push({ filename: "kv.ts", template: sampleKvRouteTemplate });
|
|
5501
|
+
}
|
|
5502
|
+
await scaffoldApiDirectory(customRoutes.directory, sampleRoutes);
|
|
5468
5503
|
}
|
|
5469
5504
|
if (database) {
|
|
5470
5505
|
return await scaffoldDatabaseSetup({ gameName });
|
|
@@ -5568,6 +5603,13 @@ async function promptForDatabase() {
|
|
|
5568
5603
|
});
|
|
5569
5604
|
return { directory };
|
|
5570
5605
|
}
|
|
5606
|
+
async function promptForKV() {
|
|
5607
|
+
const wantsKV = await confirm({
|
|
5608
|
+
message: "KV storage?",
|
|
5609
|
+
default: false
|
|
5610
|
+
});
|
|
5611
|
+
return wantsKV;
|
|
5612
|
+
}
|
|
5571
5613
|
async function promptForCustomRoutes(requiresRoutes = false) {
|
|
5572
5614
|
let wantsCustomRoutes = requiresRoutes;
|
|
5573
5615
|
if (!requiresRoutes) {
|
|
@@ -5594,10 +5636,12 @@ async function promptForCustomRoutes(requiresRoutes = false) {
|
|
|
5594
5636
|
async function promptForIntegrations() {
|
|
5595
5637
|
const timeback = await promptForTimeBackIntegration();
|
|
5596
5638
|
const database = await promptForDatabase();
|
|
5597
|
-
const
|
|
5639
|
+
const kv = await promptForKV();
|
|
5640
|
+
const customRoutes = await promptForCustomRoutes(!!database || kv);
|
|
5598
5641
|
return {
|
|
5599
5642
|
timeback,
|
|
5600
5643
|
database,
|
|
5644
|
+
kv,
|
|
5601
5645
|
customRoutes
|
|
5602
5646
|
};
|
|
5603
5647
|
}
|
|
@@ -5675,19 +5719,26 @@ function displaySuccessMessage(context2) {
|
|
|
5675
5719
|
}
|
|
5676
5720
|
|
|
5677
5721
|
// src/lib/deploy/schema.ts
|
|
5722
|
+
function getDrizzleKitApiExports() {
|
|
5723
|
+
const require2 = createRequire(import.meta.url);
|
|
5724
|
+
const drizzleKitApi = require2("drizzle-kit/api");
|
|
5725
|
+
const { generateSQLiteDrizzleJson, generateSQLiteMigration } = drizzleKitApi;
|
|
5726
|
+
return {
|
|
5727
|
+
generateSQLiteDrizzleJson,
|
|
5728
|
+
generateSQLiteMigration
|
|
5729
|
+
};
|
|
5730
|
+
}
|
|
5678
5731
|
async function getSchemaInfo(previousSchemaSnapshot) {
|
|
5679
5732
|
const workspace = getWorkspace();
|
|
5680
5733
|
if (!hasDatabaseSetup()) {
|
|
5681
5734
|
return null;
|
|
5682
5735
|
}
|
|
5683
|
-
const schemaPath =
|
|
5736
|
+
const schemaPath = join10(workspace, "db/schema/index.ts");
|
|
5684
5737
|
if (!existsSync9(schemaPath)) {
|
|
5685
5738
|
return null;
|
|
5686
5739
|
}
|
|
5687
5740
|
try {
|
|
5688
|
-
const
|
|
5689
|
-
const drizzleKitApi = require2("drizzle-kit/api");
|
|
5690
|
-
const { generateSQLiteDrizzleJson, generateSQLiteMigration } = drizzleKitApi;
|
|
5741
|
+
const { generateSQLiteDrizzleJson, generateSQLiteMigration } = getDrizzleKitApiExports();
|
|
5691
5742
|
const currentSchema = await importTypescriptDefault(schemaPath);
|
|
5692
5743
|
const nextJson = await generateSQLiteDrizzleJson(currentSchema);
|
|
5693
5744
|
const prevJson = previousSchemaSnapshot ? previousSchemaSnapshot : await generateSQLiteDrizzleJson({});
|
|
@@ -5730,7 +5781,7 @@ var CUSTOM_ROUTES_EXTENSIONS_WITH_DOT = [".ts", ".js", ".mjs", ".cjs"];
|
|
|
5730
5781
|
function getCustomRoutesDirectory(projectPath, config) {
|
|
5731
5782
|
const customRoutes = config?.integrations?.customRoutes;
|
|
5732
5783
|
const customRoutesDir = typeof customRoutes === "object" && customRoutes.directory || DEFAULT_API_ROUTES_DIRECTORY;
|
|
5733
|
-
return
|
|
5784
|
+
return join11(projectPath, customRoutesDir);
|
|
5734
5785
|
}
|
|
5735
5786
|
function hasLocalCustomRoutes(projectPath, config) {
|
|
5736
5787
|
const customRoutesDir = getCustomRoutesDirectory(projectPath, config);
|
|
@@ -5742,7 +5793,7 @@ async function getCustomRoutesHash(projectPath, config) {
|
|
|
5742
5793
|
}
|
|
5743
5794
|
async function getCustomRoutesSize(projectPath, config) {
|
|
5744
5795
|
const { stat: stat3, readdir } = await import("node:fs/promises");
|
|
5745
|
-
const { join:
|
|
5796
|
+
const { join: join29 } = await import("node:path");
|
|
5746
5797
|
const customRoutesDir = getCustomRoutesDirectory(projectPath, config);
|
|
5747
5798
|
if (!existsSync10(customRoutesDir)) {
|
|
5748
5799
|
return null;
|
|
@@ -5751,7 +5802,7 @@ async function getCustomRoutesSize(projectPath, config) {
|
|
|
5751
5802
|
async function calculateDirSize(dir) {
|
|
5752
5803
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
5753
5804
|
for (const entry of entries) {
|
|
5754
|
-
const fullPath =
|
|
5805
|
+
const fullPath = join29(dir, entry.name);
|
|
5755
5806
|
if (entry.isDirectory()) {
|
|
5756
5807
|
await calculateDirSize(fullPath);
|
|
5757
5808
|
} else if (entry.isFile()) {
|
|
@@ -5787,16 +5838,22 @@ async function getBackendSize(config) {
|
|
|
5787
5838
|
return null;
|
|
5788
5839
|
}
|
|
5789
5840
|
}
|
|
5790
|
-
function getBackendBindings(slug) {
|
|
5841
|
+
function getBackendBindings(config, slug) {
|
|
5791
5842
|
const deploymentId = getDeploymentId(slug);
|
|
5792
5843
|
const bindings = {};
|
|
5793
5844
|
if (hasDatabaseSetup()) {
|
|
5794
5845
|
bindings.database = [deploymentId];
|
|
5795
5846
|
}
|
|
5847
|
+
if (hasKVSetup(config)) {
|
|
5848
|
+
bindings.keyValue = [deploymentId];
|
|
5849
|
+
}
|
|
5796
5850
|
return Object.keys(bindings).length > 0 ? bindings : void 0;
|
|
5797
5851
|
}
|
|
5798
5852
|
async function deployGameBackend(client, slug, config, projectPath, previousCustomRoutesHash, fullConfig, forceBackend, previousIntegrationKeys, previousSchemaSnapshot, debug) {
|
|
5799
5853
|
try {
|
|
5854
|
+
if (fullConfig === void 0 || fullConfig === null) {
|
|
5855
|
+
return null;
|
|
5856
|
+
}
|
|
5800
5857
|
const hasCustomRoutes2 = hasLocalCustomRoutes(projectPath, fullConfig);
|
|
5801
5858
|
const currentIntegrationKeys = getIntegrationKeys(fullConfig?.integrations);
|
|
5802
5859
|
const hasIntegrations = currentIntegrationKeys.length > 0;
|
|
@@ -5819,10 +5876,10 @@ async function deployGameBackend(client, slug, config, projectPath, previousCust
|
|
|
5819
5876
|
}
|
|
5820
5877
|
const bundle = await runStep(
|
|
5821
5878
|
"Bundling backend",
|
|
5822
|
-
async () => bundleBackend(fullConfig
|
|
5879
|
+
async () => bundleBackend(fullConfig),
|
|
5823
5880
|
"Backend bundled"
|
|
5824
5881
|
);
|
|
5825
|
-
const bindings = getBackendBindings(slug);
|
|
5882
|
+
const bindings = getBackendBindings(fullConfig, slug);
|
|
5826
5883
|
const schemaInfo = await getSchemaInfo(previousSchemaSnapshot);
|
|
5827
5884
|
if (debug && schemaInfo) {
|
|
5828
5885
|
logger.newLine();
|
|
@@ -6121,10 +6178,10 @@ import { dim as dim4 } from "colorette";
|
|
|
6121
6178
|
init_file_loader();
|
|
6122
6179
|
init_constants2();
|
|
6123
6180
|
init_core();
|
|
6124
|
-
import { join as
|
|
6181
|
+
import { join as join12, relative as relative2 } from "path";
|
|
6125
6182
|
function findSingleBuildZip() {
|
|
6126
6183
|
const workspace = getWorkspace();
|
|
6127
|
-
const playcademyDir =
|
|
6184
|
+
const playcademyDir = join12(workspace, CLI_DIRECTORIES.WORKSPACE);
|
|
6128
6185
|
const zipFiles = findFilesByExtension(playcademyDir, "zip");
|
|
6129
6186
|
if (zipFiles.length === 1) {
|
|
6130
6187
|
return zipFiles[0] ? relative2(workspace, zipFiles[0]) : null;
|
|
@@ -6303,6 +6360,18 @@ async function promptForMissingConfig(context2) {
|
|
|
6303
6360
|
if (hasDatabase) {
|
|
6304
6361
|
parts.push("Database");
|
|
6305
6362
|
}
|
|
6363
|
+
if (context2.fullConfig?.integrations?.kv) {
|
|
6364
|
+
parts.push("KV Storage");
|
|
6365
|
+
}
|
|
6366
|
+
if (hasIntegrations && context2.fullConfig?.integrations) {
|
|
6367
|
+
const integrationNames = getUserFacingIntegrationNames(
|
|
6368
|
+
context2.fullConfig.integrations
|
|
6369
|
+
);
|
|
6370
|
+
const otherIntegrations = integrationNames.filter(
|
|
6371
|
+
(name) => name.toLowerCase() !== "kv"
|
|
6372
|
+
);
|
|
6373
|
+
parts.push(...otherIntegrations);
|
|
6374
|
+
}
|
|
6306
6375
|
if (hasCustomRoutes2) {
|
|
6307
6376
|
const customRoutesDir = getCustomRoutesDirectory(
|
|
6308
6377
|
context2.projectPath,
|
|
@@ -6310,17 +6379,11 @@ async function promptForMissingConfig(context2) {
|
|
|
6310
6379
|
);
|
|
6311
6380
|
const routes = await discoverRoutes(customRoutesDir);
|
|
6312
6381
|
if (routes.length > 0) {
|
|
6313
|
-
parts.push(
|
|
6382
|
+
parts.push(`Custom Routes (${routes.length})`);
|
|
6314
6383
|
}
|
|
6315
6384
|
}
|
|
6316
|
-
if (hasIntegrations && context2.fullConfig?.integrations) {
|
|
6317
|
-
const integrationNames = getUserFacingIntegrationNames(
|
|
6318
|
-
context2.fullConfig.integrations
|
|
6319
|
-
);
|
|
6320
|
-
parts.push(...integrationNames);
|
|
6321
|
-
}
|
|
6322
6385
|
if (parts.length > 0) {
|
|
6323
|
-
logger.success(`Integrations: ${parts.join("
|
|
6386
|
+
logger.success(`Integrations: ${parts.join(", ")}`);
|
|
6324
6387
|
}
|
|
6325
6388
|
}
|
|
6326
6389
|
} else if (config.gameType === "external" && context2.deployBackend !== false) {
|
|
@@ -6960,12 +7023,12 @@ init_constants2();
|
|
|
6960
7023
|
import { existsSync as existsSync12 } from "node:fs";
|
|
6961
7024
|
import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "node:fs/promises";
|
|
6962
7025
|
import { homedir as homedir2 } from "node:os";
|
|
6963
|
-
import { join as
|
|
7026
|
+
import { join as join13 } from "node:path";
|
|
6964
7027
|
function getGamesStorePath() {
|
|
6965
|
-
return
|
|
7028
|
+
return join13(homedir2(), CLI_USER_DIRECTORIES.CONFIG, CLI_FILES.GAMES_STORE);
|
|
6966
7029
|
}
|
|
6967
7030
|
async function ensureConfigDir() {
|
|
6968
|
-
const configDir =
|
|
7031
|
+
const configDir = join13(homedir2(), CLI_USER_DIRECTORIES.CONFIG);
|
|
6969
7032
|
await mkdir3(configDir, { recursive: true });
|
|
6970
7033
|
}
|
|
6971
7034
|
async function loadGameStore() {
|
|
@@ -7456,12 +7519,12 @@ async function saveDeploymentState(game, backendDeployment, context2) {
|
|
|
7456
7519
|
init_constants2();
|
|
7457
7520
|
init_core();
|
|
7458
7521
|
import { existsSync as existsSync14 } from "fs";
|
|
7459
|
-
import { join as
|
|
7522
|
+
import { join as join14 } from "path";
|
|
7460
7523
|
function hasCustomRoutes(config) {
|
|
7461
7524
|
const workspace = getWorkspace();
|
|
7462
7525
|
const customRoutesConfig = config?.integrations?.customRoutes;
|
|
7463
7526
|
const customRoutesDir = typeof customRoutesConfig === "object" && customRoutesConfig.directory || DEFAULT_API_ROUTES_DIRECTORY;
|
|
7464
|
-
return existsSync14(
|
|
7527
|
+
return existsSync14(join14(workspace, customRoutesDir));
|
|
7465
7528
|
}
|
|
7466
7529
|
function needsBackend(config) {
|
|
7467
7530
|
return !!config?.integrations || hasCustomRoutes(config);
|
|
@@ -7512,13 +7575,13 @@ init_constants2();
|
|
|
7512
7575
|
init_core();
|
|
7513
7576
|
import { existsSync as existsSync15, readFileSync as readFileSync4, unlinkSync as unlinkSync2 } from "fs";
|
|
7514
7577
|
import { mkdir as mkdir4, unlink, writeFile as writeFile4 } from "fs/promises";
|
|
7515
|
-
import { join as
|
|
7578
|
+
import { join as join15 } from "path";
|
|
7516
7579
|
function getDevServerPidPath() {
|
|
7517
|
-
return
|
|
7580
|
+
return join15(getWorkspace(), CLI_DIRECTORIES.WORKSPACE, CLI_FILES.DEV_SERVER_PID);
|
|
7518
7581
|
}
|
|
7519
7582
|
async function createDevServerPidFile() {
|
|
7520
7583
|
const pidPath = getDevServerPidFile();
|
|
7521
|
-
const pidDir =
|
|
7584
|
+
const pidDir = join15(getWorkspace(), CLI_DIRECTORIES.WORKSPACE);
|
|
7522
7585
|
await mkdir4(pidDir, { recursive: true });
|
|
7523
7586
|
await writeFile4(pidPath, process.pid.toString());
|
|
7524
7587
|
}
|
|
@@ -7558,7 +7621,7 @@ function getDevServerPidFile() {
|
|
|
7558
7621
|
// src/lib/dev/reload.ts
|
|
7559
7622
|
init_constants2();
|
|
7560
7623
|
init_core();
|
|
7561
|
-
import { join as
|
|
7624
|
+
import { join as join16, relative as relative3 } from "path";
|
|
7562
7625
|
import chokidar from "chokidar";
|
|
7563
7626
|
import { bold as bold4, cyan as cyan3, dim as dim6, green as green3 } from "colorette";
|
|
7564
7627
|
function formatTime() {
|
|
@@ -7575,9 +7638,9 @@ function startHotReload(onReload, options = {}) {
|
|
|
7575
7638
|
const customRoutesConfig = options.config?.integrations?.customRoutes;
|
|
7576
7639
|
const customRoutesDir = typeof customRoutesConfig === "object" && customRoutesConfig.directory || DEFAULT_API_ROUTES_DIRECTORY;
|
|
7577
7640
|
const watchPaths = [
|
|
7578
|
-
|
|
7579
|
-
|
|
7580
|
-
|
|
7641
|
+
join16(workspace, customRoutesDir),
|
|
7642
|
+
join16(workspace, "playcademy.config.js"),
|
|
7643
|
+
join16(workspace, "playcademy.config.json")
|
|
7581
7644
|
];
|
|
7582
7645
|
const watcher = chokidar.watch(watchPaths, {
|
|
7583
7646
|
persistent: true,
|
|
@@ -7619,28 +7682,28 @@ function startHotReload(onReload, options = {}) {
|
|
|
7619
7682
|
// src/lib/dev/server.ts
|
|
7620
7683
|
init_src2();
|
|
7621
7684
|
init_constants2();
|
|
7685
|
+
init_loader();
|
|
7622
7686
|
init_core();
|
|
7623
7687
|
import { mkdir as mkdir5 } from "fs/promises";
|
|
7624
|
-
import { join as
|
|
7688
|
+
import { join as join17 } from "path";
|
|
7625
7689
|
import { Miniflare } from "miniflare";
|
|
7626
7690
|
async function startDevServer(options) {
|
|
7627
7691
|
const {
|
|
7628
7692
|
port,
|
|
7629
|
-
config,
|
|
7693
|
+
config: providedConfig,
|
|
7630
7694
|
platformUrl = process.env.PLAYCADEMY_BASE_URL || "http://localhost:5174"
|
|
7631
7695
|
} = options;
|
|
7696
|
+
const config = providedConfig ?? await loadConfig();
|
|
7632
7697
|
const hasSandboxTimebackCreds = !!process.env.TIMEBACK_API_CLIENT_ID;
|
|
7633
|
-
const devConfig = config
|
|
7698
|
+
const devConfig = config.integrations?.timeback && !hasSandboxTimebackCreds ? { ...config, integrations: { ...config.integrations, timeback: void 0 } } : config;
|
|
7634
7699
|
const bundle = await bundleBackend(devConfig, {
|
|
7635
7700
|
sourcemap: false,
|
|
7636
7701
|
minify: false
|
|
7637
7702
|
});
|
|
7638
|
-
const
|
|
7639
|
-
|
|
7640
|
-
|
|
7641
|
-
|
|
7642
|
-
throw new Error(`Failed to create database directory: ${getErrorMessage(error)}`);
|
|
7643
|
-
}
|
|
7703
|
+
const hasDatabase = hasDatabaseSetup();
|
|
7704
|
+
const dbDir = hasDatabase ? await ensureDatabaseDirectory() : void 0;
|
|
7705
|
+
const hasKV = hasKVSetup(config);
|
|
7706
|
+
const kvDir = hasKV ? await ensureKvDirectory() : void 0;
|
|
7644
7707
|
const mf = new Miniflare({
|
|
7645
7708
|
port,
|
|
7646
7709
|
modules: [
|
|
@@ -7655,14 +7718,39 @@ async function startDevServer(options) {
|
|
|
7655
7718
|
GAME_ID: CORE_GAME_UUIDS.PLAYGROUND,
|
|
7656
7719
|
PLAYCADEMY_BASE_URL: platformUrl
|
|
7657
7720
|
},
|
|
7658
|
-
d1Databases: ["DB"],
|
|
7721
|
+
d1Databases: hasDatabase ? ["DB"] : [],
|
|
7659
7722
|
d1Persist: dbDir,
|
|
7723
|
+
kvNamespaces: hasKV ? ["KV"] : [],
|
|
7724
|
+
kvPersist: kvDir,
|
|
7660
7725
|
compatibilityDate: CLOUDFLARE_COMPATIBILITY_DATE
|
|
7661
7726
|
});
|
|
7727
|
+
if (hasDatabase) {
|
|
7728
|
+
await initializeDatabase(mf);
|
|
7729
|
+
}
|
|
7730
|
+
return mf;
|
|
7731
|
+
}
|
|
7732
|
+
async function ensureDatabaseDirectory() {
|
|
7733
|
+
const dbDir = join17(getWorkspace(), CLI_DIRECTORIES.DATABASE);
|
|
7734
|
+
try {
|
|
7735
|
+
await mkdir5(dbDir, { recursive: true });
|
|
7736
|
+
} catch (error) {
|
|
7737
|
+
throw new Error(`Failed to create database directory: ${getErrorMessage(error)}`);
|
|
7738
|
+
}
|
|
7739
|
+
return dbDir;
|
|
7740
|
+
}
|
|
7741
|
+
async function ensureKvDirectory() {
|
|
7742
|
+
const kvDir = join17(getWorkspace(), CLI_DIRECTORIES.KV);
|
|
7743
|
+
try {
|
|
7744
|
+
await mkdir5(kvDir, { recursive: true });
|
|
7745
|
+
} catch (error) {
|
|
7746
|
+
throw new Error(`Failed to create KV directory: ${getErrorMessage(error)}`);
|
|
7747
|
+
}
|
|
7748
|
+
return kvDir;
|
|
7749
|
+
}
|
|
7750
|
+
async function initializeDatabase(mf) {
|
|
7662
7751
|
const d1 = await mf.getD1Database("DB");
|
|
7663
7752
|
await d1.exec("SELECT 1");
|
|
7664
7753
|
await createDevServerPidFile();
|
|
7665
|
-
return mf;
|
|
7666
7754
|
}
|
|
7667
7755
|
|
|
7668
7756
|
// src/lib/timeback/cleanup.ts
|
|
@@ -7843,6 +7931,7 @@ var initCommand = new Command2("init").description("Initialize a playcademy.conf
|
|
|
7843
7931
|
const {
|
|
7844
7932
|
timeback: timebackConfig,
|
|
7845
7933
|
database,
|
|
7934
|
+
kv,
|
|
7846
7935
|
customRoutes
|
|
7847
7936
|
} = await promptForIntegrations();
|
|
7848
7937
|
let depsAdded = false;
|
|
@@ -7857,6 +7946,7 @@ var initCommand = new Command2("init").description("Initialize a playcademy.conf
|
|
|
7857
7946
|
const scaffoldDepsAdded = await scaffoldIntegrations(
|
|
7858
7947
|
customRoutes,
|
|
7859
7948
|
database,
|
|
7949
|
+
kv,
|
|
7860
7950
|
gameInfo.name
|
|
7861
7951
|
);
|
|
7862
7952
|
if (scaffoldDepsAdded) depsAdded = true;
|
|
@@ -7872,6 +7962,7 @@ var initCommand = new Command2("init").description("Initialize a playcademy.conf
|
|
|
7872
7962
|
emoji: gameInfo.emoji,
|
|
7873
7963
|
customRoutesDirectory: customRoutes?.directory ?? void 0,
|
|
7874
7964
|
databaseDirectory: database?.directory ?? void 0,
|
|
7965
|
+
kv: kv ?? void 0,
|
|
7875
7966
|
timeback: timebackConfig ?? void 0
|
|
7876
7967
|
}) : generateJsonConfig({
|
|
7877
7968
|
name: gameInfo.name,
|
|
@@ -7879,6 +7970,7 @@ var initCommand = new Command2("init").description("Initialize a playcademy.conf
|
|
|
7879
7970
|
emoji: gameInfo.emoji,
|
|
7880
7971
|
customRoutesDirectory: customRoutes?.directory ?? void 0,
|
|
7881
7972
|
databaseDirectory: database?.directory ?? void 0,
|
|
7973
|
+
kv: kv ?? void 0,
|
|
7882
7974
|
timeback: timebackConfig ?? void 0
|
|
7883
7975
|
});
|
|
7884
7976
|
writeFileSync5(resolve10(getWorkspace(), configFileName), configContent, "utf-8");
|
|
@@ -7904,7 +7996,6 @@ async function addPlaycademySdk() {
|
|
|
7904
7996
|
if (!pkg.dependencies) pkg.dependencies = {};
|
|
7905
7997
|
pkg.dependencies["@playcademy/sdk"] = "latest";
|
|
7906
7998
|
writeFileSync5(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
7907
|
-
logger.success("Added @playcademy/sdk to dependencies");
|
|
7908
7999
|
return true;
|
|
7909
8000
|
}
|
|
7910
8001
|
async function installDependencies() {
|
|
@@ -8579,27 +8670,14 @@ devCommand.addCommand(getStatusCommand);
|
|
|
8579
8670
|
import { Command as Command13 } from "commander";
|
|
8580
8671
|
|
|
8581
8672
|
// src/commands/api/init.ts
|
|
8582
|
-
init_file_loader();
|
|
8583
8673
|
init_constants2();
|
|
8584
|
-
import { readFileSync as readFileSync6, writeFileSync as writeFileSync6 } from "fs";
|
|
8585
8674
|
import { input as input5 } from "@inquirer/prompts";
|
|
8675
|
+
init_writer();
|
|
8586
8676
|
init_loader2();
|
|
8587
8677
|
async function runApiInit() {
|
|
8588
8678
|
try {
|
|
8589
8679
|
logger.newLine();
|
|
8590
|
-
const
|
|
8591
|
-
searchUp: false,
|
|
8592
|
-
maxLevels: 0
|
|
8593
|
-
});
|
|
8594
|
-
if (!configFile) {
|
|
8595
|
-
logger.error("No playcademy.config file found");
|
|
8596
|
-
logger.newLine();
|
|
8597
|
-
logger.admonition("tip", "Getting Started", [
|
|
8598
|
-
"Run `playcademy init` to create a config file first"
|
|
8599
|
-
]);
|
|
8600
|
-
logger.newLine();
|
|
8601
|
-
process.exit(1);
|
|
8602
|
-
}
|
|
8680
|
+
const config = await loadConfig();
|
|
8603
8681
|
logger.highlight("Add Custom API Routes");
|
|
8604
8682
|
logger.newLine();
|
|
8605
8683
|
const apiDir = await input5({
|
|
@@ -8612,48 +8690,39 @@ async function runApiInit() {
|
|
|
8612
8690
|
return validateApiDirectoryDoesNotExist(value);
|
|
8613
8691
|
}
|
|
8614
8692
|
});
|
|
8615
|
-
const
|
|
8616
|
-
|
|
8617
|
-
|
|
8618
|
-
|
|
8619
|
-
|
|
8693
|
+
const sampleRoutes = [
|
|
8694
|
+
// Always include basic custom route example
|
|
8695
|
+
{ filename: "custom.ts", template: loadTemplateString("api/sample-custom.ts") }
|
|
8696
|
+
];
|
|
8697
|
+
if (hasDatabaseSetup()) {
|
|
8698
|
+
sampleRoutes.push({
|
|
8699
|
+
filename: "database.ts",
|
|
8700
|
+
template: loadTemplateString("api/sample-database.ts")
|
|
8701
|
+
});
|
|
8702
|
+
}
|
|
8703
|
+
if (hasKVSetup(config)) {
|
|
8704
|
+
sampleRoutes.push({
|
|
8705
|
+
filename: "kv.ts",
|
|
8706
|
+
template: loadTemplateString("api/sample-kv.ts")
|
|
8707
|
+
});
|
|
8708
|
+
}
|
|
8709
|
+
await scaffoldApiDirectory(apiDir, sampleRoutes);
|
|
8620
8710
|
if (!config.integrations?.customRoutes) {
|
|
8621
|
-
const
|
|
8622
|
-
|
|
8623
|
-
|
|
8624
|
-
|
|
8625
|
-
|
|
8626
|
-
if (!parsed.integrations) parsed.integrations = {};
|
|
8627
|
-
parsed.integrations.customRoutes = { directory: apiDir };
|
|
8628
|
-
updatedContent = JSON.stringify(parsed, null, 4);
|
|
8629
|
-
} else {
|
|
8630
|
-
if (configContent.includes("integrations:")) {
|
|
8631
|
-
const customRoutesConfig = `
|
|
8632
|
-
customRoutes: { directory: '${apiDir}' },`;
|
|
8633
|
-
updatedContent = configContent.replace(
|
|
8634
|
-
/(integrations:\s*{)/,
|
|
8635
|
-
`$1${customRoutesConfig}`
|
|
8636
|
-
);
|
|
8637
|
-
} else {
|
|
8638
|
-
const integrationsConfig = `
|
|
8639
|
-
integrations: {
|
|
8640
|
-
customRoutes: { directory: '${apiDir}' },
|
|
8641
|
-
},`;
|
|
8642
|
-
updatedContent = configContent.replace(
|
|
8643
|
-
/(\n\s*})(\s*)$/,
|
|
8644
|
-
`${integrationsConfig}$1$2`
|
|
8645
|
-
);
|
|
8711
|
+
const configPath = await findConfigPath();
|
|
8712
|
+
await updateConfigFile(configPath, {
|
|
8713
|
+
integrations: {
|
|
8714
|
+
...config.integrations,
|
|
8715
|
+
customRoutes: { directory: apiDir }
|
|
8646
8716
|
}
|
|
8647
|
-
}
|
|
8648
|
-
|
|
8649
|
-
logger.success(`Updated ${configFile.path.split("/").pop()}`);
|
|
8717
|
+
});
|
|
8718
|
+
logger.success(`Updated ${configPath.split("/").pop()}`);
|
|
8650
8719
|
}
|
|
8651
8720
|
logger.newLine();
|
|
8652
8721
|
logger.success("Custom API routes added!");
|
|
8653
8722
|
logger.newLine();
|
|
8654
8723
|
logger.admonition("tip", "Next Steps", [
|
|
8655
|
-
`1. Review
|
|
8656
|
-
`2. Create
|
|
8724
|
+
`1. Review sample routes in ${apiDir}/sample/`,
|
|
8725
|
+
`2. Create custom routes in ${apiDir}/`,
|
|
8657
8726
|
"3. Run `playcademy dev` to test locally",
|
|
8658
8727
|
"4. Run `playcademy deploy --backend` to deploy"
|
|
8659
8728
|
]);
|
|
@@ -8702,7 +8771,7 @@ async function runDbDiff() {
|
|
|
8702
8771
|
// src/commands/db/init.ts
|
|
8703
8772
|
init_file_loader();
|
|
8704
8773
|
init_constants2();
|
|
8705
|
-
import { readFileSync as
|
|
8774
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync6 } from "fs";
|
|
8706
8775
|
import { input as input6 } from "@inquirer/prompts";
|
|
8707
8776
|
async function runDbInit() {
|
|
8708
8777
|
try {
|
|
@@ -8733,9 +8802,10 @@ async function runDbInit() {
|
|
|
8733
8802
|
return true;
|
|
8734
8803
|
}
|
|
8735
8804
|
});
|
|
8805
|
+
ensurePlaycademyGitignore();
|
|
8736
8806
|
await scaffoldDatabaseSetup({ gameName: config.name });
|
|
8737
8807
|
if (!config.integrations?.database) {
|
|
8738
|
-
const configContent =
|
|
8808
|
+
const configContent = readFileSync6(configFile.path, "utf-8");
|
|
8739
8809
|
const isJson = configFile.path.endsWith(".json");
|
|
8740
8810
|
let updatedContent;
|
|
8741
8811
|
if (isJson) {
|
|
@@ -8758,7 +8828,7 @@ async function runDbInit() {
|
|
|
8758
8828
|
);
|
|
8759
8829
|
}
|
|
8760
8830
|
}
|
|
8761
|
-
|
|
8831
|
+
writeFileSync6(configFile.path, updatedContent, "utf-8");
|
|
8762
8832
|
logger.success(`Updated ${configFile.path.split("/").pop()}`);
|
|
8763
8833
|
}
|
|
8764
8834
|
logger.newLine();
|
|
@@ -8783,13 +8853,13 @@ init_src();
|
|
|
8783
8853
|
init_constants2();
|
|
8784
8854
|
import { spawn } from "child_process";
|
|
8785
8855
|
import { existsSync as existsSync16, rmSync as rmSync2 } from "fs";
|
|
8786
|
-
import { join as
|
|
8856
|
+
import { join as join18 } from "path";
|
|
8787
8857
|
import { confirm as confirm6 } from "@inquirer/prompts";
|
|
8788
8858
|
import { Miniflare as Miniflare2 } from "miniflare";
|
|
8789
8859
|
async function runDbReset() {
|
|
8790
8860
|
try {
|
|
8791
8861
|
const workspace = getWorkspace();
|
|
8792
|
-
const dbDir =
|
|
8862
|
+
const dbDir = join18(workspace, CLI_DIRECTORIES.DATABASE);
|
|
8793
8863
|
if (!existsSync16(dbDir)) {
|
|
8794
8864
|
logger.warn("No database found to reset");
|
|
8795
8865
|
logger.newLine();
|
|
@@ -8894,7 +8964,7 @@ async function runDbReset() {
|
|
|
8894
8964
|
init_package_manager();
|
|
8895
8965
|
import { execSync as execSync4 } from "child_process";
|
|
8896
8966
|
import { existsSync as existsSync17 } from "fs";
|
|
8897
|
-
import { join as
|
|
8967
|
+
import { join as join19 } from "path";
|
|
8898
8968
|
async function runDbSeed(options) {
|
|
8899
8969
|
const workspace = getWorkspace();
|
|
8900
8970
|
try {
|
|
@@ -8903,7 +8973,7 @@ async function runDbSeed(options) {
|
|
|
8903
8973
|
logger.newLine();
|
|
8904
8974
|
}
|
|
8905
8975
|
if (options.file) {
|
|
8906
|
-
const seedPath =
|
|
8976
|
+
const seedPath = join19(workspace, options.file);
|
|
8907
8977
|
if (!existsSync17(seedPath)) {
|
|
8908
8978
|
logger.error(`Seed file not found: ${options.file}`);
|
|
8909
8979
|
logger.newLine();
|
|
@@ -8929,102 +8999,1143 @@ dbCommand.command("reset").description("Reset local development database (delete
|
|
|
8929
8999
|
dbCommand.command("seed [file]").description("Seed local database with initial data").option("-r, --reset", "Reset database before seeding").action((file, options) => runDbSeed({ file, reset: options.reset }));
|
|
8930
9000
|
dbCommand.command("diff").description("Show schema changes since last deployment").action(runDbDiff);
|
|
8931
9001
|
|
|
8932
|
-
// src/commands/
|
|
8933
|
-
import { Command as
|
|
9002
|
+
// src/commands/kv/index.ts
|
|
9003
|
+
import { Command as Command15 } from "commander";
|
|
8934
9004
|
|
|
8935
|
-
// src/commands/
|
|
9005
|
+
// src/commands/kv/clear.ts
|
|
8936
9006
|
init_string();
|
|
8937
|
-
|
|
8938
|
-
|
|
9007
|
+
init_constants2();
|
|
9008
|
+
import { join as join20 } from "path";
|
|
9009
|
+
import { confirm as confirm7 } from "@inquirer/prompts";
|
|
9010
|
+
import { Miniflare as Miniflare3 } from "miniflare";
|
|
9011
|
+
async function runKVClear(options = {}) {
|
|
8939
9012
|
try {
|
|
8940
|
-
|
|
8941
|
-
|
|
8942
|
-
|
|
8943
|
-
|
|
8944
|
-
if (
|
|
8945
|
-
|
|
8946
|
-
|
|
9013
|
+
if (!options.raw && !options.json) {
|
|
9014
|
+
logger.newLine();
|
|
9015
|
+
}
|
|
9016
|
+
if (options.remote) {
|
|
9017
|
+
if (!options.raw && !options.json) {
|
|
9018
|
+
logger.newLine();
|
|
9019
|
+
logger.warn("Remote KV operations are not yet implemented");
|
|
9020
|
+
logger.newLine();
|
|
9021
|
+
logger.admonition("info", "Coming Soon", [
|
|
9022
|
+
"Remote KV support is on the roadmap and will be available soon.",
|
|
9023
|
+
"For now, KV commands only work with local development storage."
|
|
9024
|
+
]);
|
|
9025
|
+
logger.newLine();
|
|
8947
9026
|
}
|
|
9027
|
+
process.exit(1);
|
|
8948
9028
|
}
|
|
8949
|
-
|
|
8950
|
-
|
|
8951
|
-
|
|
8952
|
-
|
|
8953
|
-
|
|
8954
|
-
|
|
8955
|
-
|
|
8956
|
-
|
|
9029
|
+
const config = await loadConfig();
|
|
9030
|
+
if (!hasKVSetup(config)) {
|
|
9031
|
+
if (!options.raw && !options.json) {
|
|
9032
|
+
logger.error("KV storage is not configured");
|
|
9033
|
+
logger.newLine();
|
|
9034
|
+
logger.admonition("tip", "Getting Started", [
|
|
9035
|
+
"Run `playcademy kv init` to enable KV storage"
|
|
9036
|
+
]);
|
|
9037
|
+
logger.newLine();
|
|
9038
|
+
}
|
|
9039
|
+
process.exit(1);
|
|
8957
9040
|
}
|
|
8958
|
-
|
|
8959
|
-
|
|
8960
|
-
|
|
8961
|
-
|
|
8962
|
-
|
|
8963
|
-
|
|
8964
|
-
|
|
8965
|
-
|
|
9041
|
+
const bundle = await bundleBackend(config, {
|
|
9042
|
+
sourcemap: false,
|
|
9043
|
+
minify: false
|
|
9044
|
+
});
|
|
9045
|
+
const kvDir = join20(getWorkspace(), CLI_DIRECTORIES.KV);
|
|
9046
|
+
const mf = new Miniflare3({
|
|
9047
|
+
modules: [
|
|
9048
|
+
{
|
|
9049
|
+
type: "ESModule",
|
|
9050
|
+
path: "index.mjs",
|
|
9051
|
+
contents: bundle.code
|
|
9052
|
+
}
|
|
9053
|
+
],
|
|
9054
|
+
kvNamespaces: ["KV"],
|
|
9055
|
+
kvPersist: kvDir,
|
|
9056
|
+
compatibilityDate: CLOUDFLARE_COMPATIBILITY_DATE
|
|
9057
|
+
});
|
|
9058
|
+
try {
|
|
9059
|
+
const kv = await mf.getKVNamespace("KV");
|
|
9060
|
+
const listResult = await kv.list();
|
|
9061
|
+
const keyCount = listResult.keys?.length || 0;
|
|
9062
|
+
if (keyCount === 0) {
|
|
9063
|
+
if (options.json) {
|
|
9064
|
+
logger.json({
|
|
9065
|
+
success: true,
|
|
9066
|
+
deleted: 0,
|
|
9067
|
+
message: "No keys to clear"
|
|
9068
|
+
});
|
|
9069
|
+
} else if (options.raw) {
|
|
9070
|
+
logger.raw("0");
|
|
9071
|
+
} else {
|
|
9072
|
+
logger.info("No keys found in KV namespace");
|
|
9073
|
+
logger.newLine();
|
|
9074
|
+
}
|
|
9075
|
+
return;
|
|
9076
|
+
}
|
|
9077
|
+
if (!options.force && !options.raw && !options.json) {
|
|
9078
|
+
logger.warn(`This will delete ${keyCount} ${pluralize(keyCount, "key")}`);
|
|
9079
|
+
logger.newLine();
|
|
9080
|
+
const confirmed = await confirm7({
|
|
9081
|
+
message: "Are you sure you want to clear all keys?",
|
|
9082
|
+
default: false
|
|
8966
9083
|
});
|
|
9084
|
+
if (!confirmed) {
|
|
9085
|
+
logger.info("Cancelled");
|
|
9086
|
+
logger.newLine();
|
|
9087
|
+
return;
|
|
9088
|
+
}
|
|
9089
|
+
logger.newLine();
|
|
8967
9090
|
}
|
|
8968
|
-
|
|
8969
|
-
|
|
8970
|
-
|
|
9091
|
+
const keys = listResult.keys || [];
|
|
9092
|
+
for (const key of keys) {
|
|
9093
|
+
await kv.delete(key.name);
|
|
9094
|
+
}
|
|
9095
|
+
if (options.json) {
|
|
9096
|
+
logger.json({
|
|
9097
|
+
success: true,
|
|
9098
|
+
deleted: keyCount
|
|
9099
|
+
});
|
|
9100
|
+
} else if (options.raw) {
|
|
9101
|
+
logger.raw(keyCount.toString());
|
|
9102
|
+
} else {
|
|
9103
|
+
logger.success(`Deleted ${keyCount} ${pluralize(keyCount, "key")}`);
|
|
8971
9104
|
logger.newLine();
|
|
8972
9105
|
}
|
|
9106
|
+
} finally {
|
|
9107
|
+
await mf.dispose();
|
|
8973
9108
|
}
|
|
8974
9109
|
} catch (error) {
|
|
8975
|
-
|
|
9110
|
+
if (!options.raw && !options.json) {
|
|
9111
|
+
logger.error(
|
|
9112
|
+
`Failed to clear KV: ${error instanceof Error ? error.message : String(error)}`
|
|
9113
|
+
);
|
|
9114
|
+
logger.newLine();
|
|
9115
|
+
}
|
|
8976
9116
|
process.exit(1);
|
|
8977
9117
|
}
|
|
8978
9118
|
}
|
|
8979
|
-
var listCommand2 = new Command15("list").alias("ls").description("List all stored authentication profiles").action(listProfilesAction);
|
|
8980
9119
|
|
|
8981
|
-
// src/commands/
|
|
8982
|
-
|
|
8983
|
-
import {
|
|
8984
|
-
|
|
8985
|
-
|
|
8986
|
-
const environment = ensureEnvironment(env, logger);
|
|
9120
|
+
// src/commands/kv/delete.ts
|
|
9121
|
+
init_constants2();
|
|
9122
|
+
import { join as join21 } from "path";
|
|
9123
|
+
import { Miniflare as Miniflare4 } from "miniflare";
|
|
9124
|
+
async function runKVDelete(key, options = {}) {
|
|
8987
9125
|
try {
|
|
8988
|
-
|
|
8989
|
-
const profilesMap = await listProfiles();
|
|
8990
|
-
const envProfiles = profilesMap.get(environment) || [];
|
|
8991
|
-
if (!envProfiles.includes(name)) {
|
|
8992
|
-
logger.error(`Profile "${name}" not found in ${environment}`);
|
|
9126
|
+
if (!options.raw && !options.json) {
|
|
8993
9127
|
logger.newLine();
|
|
9128
|
+
}
|
|
9129
|
+
if (options.remote) {
|
|
9130
|
+
if (!options.raw && !options.json) {
|
|
9131
|
+
logger.newLine();
|
|
9132
|
+
logger.warn("Remote KV operations are not yet implemented");
|
|
9133
|
+
logger.newLine();
|
|
9134
|
+
logger.admonition("info", "Coming Soon", [
|
|
9135
|
+
"Remote KV support is on the roadmap and will be available soon.",
|
|
9136
|
+
"For now, KV commands only work with local development storage."
|
|
9137
|
+
]);
|
|
9138
|
+
logger.newLine();
|
|
9139
|
+
}
|
|
8994
9140
|
process.exit(1);
|
|
8995
9141
|
}
|
|
8996
|
-
|
|
8997
|
-
|
|
8998
|
-
|
|
8999
|
-
|
|
9000
|
-
|
|
9001
|
-
|
|
9142
|
+
if (!key) {
|
|
9143
|
+
if (!options.raw && !options.json) {
|
|
9144
|
+
logger.error("Key is required");
|
|
9145
|
+
logger.newLine();
|
|
9146
|
+
logger.admonition("tip", "Usage", ["`playcademy kv delete <key>`"]);
|
|
9147
|
+
logger.newLine();
|
|
9148
|
+
}
|
|
9149
|
+
process.exit(1);
|
|
9150
|
+
}
|
|
9151
|
+
const config = await loadConfig();
|
|
9152
|
+
if (!hasKVSetup(config)) {
|
|
9153
|
+
if (!options.raw && !options.json) {
|
|
9154
|
+
logger.error("KV storage is not configured");
|
|
9155
|
+
logger.newLine();
|
|
9156
|
+
logger.admonition("tip", "Getting Started", [
|
|
9157
|
+
"Run `playcademy kv init` to enable KV storage"
|
|
9158
|
+
]);
|
|
9159
|
+
logger.newLine();
|
|
9160
|
+
}
|
|
9161
|
+
process.exit(1);
|
|
9162
|
+
}
|
|
9163
|
+
const bundle = await bundleBackend(config, {
|
|
9164
|
+
sourcemap: false,
|
|
9165
|
+
minify: false
|
|
9166
|
+
});
|
|
9167
|
+
const kvDir = join21(getWorkspace(), CLI_DIRECTORIES.KV);
|
|
9168
|
+
const mf = new Miniflare4({
|
|
9169
|
+
modules: [
|
|
9170
|
+
{
|
|
9171
|
+
type: "ESModule",
|
|
9172
|
+
path: "index.mjs",
|
|
9173
|
+
contents: bundle.code
|
|
9174
|
+
}
|
|
9175
|
+
],
|
|
9176
|
+
kvNamespaces: ["KV"],
|
|
9177
|
+
kvPersist: kvDir,
|
|
9178
|
+
compatibilityDate: CLOUDFLARE_COMPATIBILITY_DATE
|
|
9179
|
+
});
|
|
9180
|
+
try {
|
|
9181
|
+
const kv = await mf.getKVNamespace("KV");
|
|
9182
|
+
await kv.delete(key);
|
|
9183
|
+
if (options.json) {
|
|
9184
|
+
logger.json({
|
|
9185
|
+
key,
|
|
9186
|
+
success: true
|
|
9187
|
+
});
|
|
9188
|
+
} else if (options.raw) {
|
|
9189
|
+
logger.raw("ok");
|
|
9190
|
+
} else {
|
|
9191
|
+
logger.success(`Deleted key: ${key}`);
|
|
9192
|
+
logger.newLine();
|
|
9193
|
+
}
|
|
9194
|
+
} finally {
|
|
9195
|
+
await mf.dispose();
|
|
9196
|
+
}
|
|
9002
9197
|
} catch (error) {
|
|
9003
|
-
|
|
9198
|
+
if (!options.raw && !options.json) {
|
|
9199
|
+
logger.error(
|
|
9200
|
+
`Failed to delete key: ${error instanceof Error ? error.message : String(error)}`
|
|
9201
|
+
);
|
|
9202
|
+
logger.newLine();
|
|
9203
|
+
}
|
|
9204
|
+
process.exit(1);
|
|
9004
9205
|
}
|
|
9005
|
-
}
|
|
9206
|
+
}
|
|
9006
9207
|
|
|
9007
|
-
// src/commands/
|
|
9008
|
-
|
|
9009
|
-
import {
|
|
9010
|
-
import {
|
|
9011
|
-
|
|
9012
|
-
"Remove all authentication profiles across all environments (requires confirmation)"
|
|
9013
|
-
).alias("clear").action(async () => {
|
|
9208
|
+
// src/commands/kv/get.ts
|
|
9209
|
+
init_constants2();
|
|
9210
|
+
import { join as join22 } from "path";
|
|
9211
|
+
import { Miniflare as Miniflare5 } from "miniflare";
|
|
9212
|
+
async function runKVGet(key, options = {}) {
|
|
9014
9213
|
try {
|
|
9015
|
-
|
|
9016
|
-
let totalProfiles = 0;
|
|
9017
|
-
for (const profiles of profilesMap.values()) {
|
|
9018
|
-
totalProfiles += profiles.length;
|
|
9019
|
-
}
|
|
9020
|
-
if (totalProfiles === 0) {
|
|
9021
|
-
logger.newLine();
|
|
9022
|
-
logger.warn("No authentication profiles found");
|
|
9214
|
+
if (!options.raw && !options.json) {
|
|
9023
9215
|
logger.newLine();
|
|
9024
|
-
return;
|
|
9025
9216
|
}
|
|
9026
|
-
|
|
9027
|
-
|
|
9217
|
+
if (options.remote) {
|
|
9218
|
+
if (!options.raw && !options.json) {
|
|
9219
|
+
logger.newLine();
|
|
9220
|
+
logger.warn("Remote KV operations are not yet implemented");
|
|
9221
|
+
logger.newLine();
|
|
9222
|
+
logger.admonition("info", "Coming Soon", [
|
|
9223
|
+
"Remote KV support is on the roadmap and will be available soon.",
|
|
9224
|
+
"For now, KV commands only work with local development storage."
|
|
9225
|
+
]);
|
|
9226
|
+
logger.newLine();
|
|
9227
|
+
}
|
|
9228
|
+
process.exit(1);
|
|
9229
|
+
}
|
|
9230
|
+
if (!key) {
|
|
9231
|
+
if (!options.raw && !options.json) {
|
|
9232
|
+
logger.error("Key is required");
|
|
9233
|
+
logger.newLine();
|
|
9234
|
+
logger.admonition("tip", "Usage", ["`playcademy kv get <key>`"]);
|
|
9235
|
+
logger.newLine();
|
|
9236
|
+
}
|
|
9237
|
+
process.exit(1);
|
|
9238
|
+
}
|
|
9239
|
+
const config = await loadConfig();
|
|
9240
|
+
if (!hasKVSetup(config)) {
|
|
9241
|
+
if (!options.raw && !options.json) {
|
|
9242
|
+
logger.error("KV storage is not configured");
|
|
9243
|
+
logger.newLine();
|
|
9244
|
+
logger.admonition("tip", "Getting Started", [
|
|
9245
|
+
"Run `playcademy kv init` to enable KV storage"
|
|
9246
|
+
]);
|
|
9247
|
+
logger.newLine();
|
|
9248
|
+
}
|
|
9249
|
+
process.exit(1);
|
|
9250
|
+
}
|
|
9251
|
+
const bundle = await bundleBackend(config, {
|
|
9252
|
+
sourcemap: false,
|
|
9253
|
+
minify: false
|
|
9254
|
+
});
|
|
9255
|
+
const kvDir = join22(getWorkspace(), CLI_DIRECTORIES.KV);
|
|
9256
|
+
const mf = new Miniflare5({
|
|
9257
|
+
modules: [
|
|
9258
|
+
{
|
|
9259
|
+
type: "ESModule",
|
|
9260
|
+
path: "index.mjs",
|
|
9261
|
+
contents: bundle.code
|
|
9262
|
+
}
|
|
9263
|
+
],
|
|
9264
|
+
kvNamespaces: ["KV"],
|
|
9265
|
+
kvPersist: kvDir,
|
|
9266
|
+
compatibilityDate: CLOUDFLARE_COMPATIBILITY_DATE
|
|
9267
|
+
});
|
|
9268
|
+
try {
|
|
9269
|
+
const kv = await mf.getKVNamespace("KV");
|
|
9270
|
+
const value = await kv.get(key);
|
|
9271
|
+
if (value === null) {
|
|
9272
|
+
if (!options.raw && !options.json) {
|
|
9273
|
+
logger.warn(`Key '${key}' not found`);
|
|
9274
|
+
logger.newLine();
|
|
9275
|
+
logger.admonition("tip", "Hint", [
|
|
9276
|
+
"Use `playcademy kv list` to see all available keys"
|
|
9277
|
+
]);
|
|
9278
|
+
logger.newLine();
|
|
9279
|
+
}
|
|
9280
|
+
process.exit(1);
|
|
9281
|
+
}
|
|
9282
|
+
let parsedValue;
|
|
9283
|
+
let isJson = false;
|
|
9284
|
+
try {
|
|
9285
|
+
parsedValue = JSON.parse(value);
|
|
9286
|
+
isJson = true;
|
|
9287
|
+
} catch {
|
|
9288
|
+
parsedValue = value;
|
|
9289
|
+
}
|
|
9290
|
+
if (options.json) {
|
|
9291
|
+
if (isJson) {
|
|
9292
|
+
logger.json(parsedValue);
|
|
9293
|
+
} else {
|
|
9294
|
+
logger.json(value);
|
|
9295
|
+
}
|
|
9296
|
+
return;
|
|
9297
|
+
}
|
|
9298
|
+
if (options.raw) {
|
|
9299
|
+
logger.raw(value);
|
|
9300
|
+
return;
|
|
9301
|
+
}
|
|
9302
|
+
logger.success(`Key: ${key}`);
|
|
9303
|
+
logger.newLine();
|
|
9304
|
+
if (isJson) {
|
|
9305
|
+
logger.json(parsedValue, 1);
|
|
9306
|
+
} else {
|
|
9307
|
+
logger.data("Value", value, 1);
|
|
9308
|
+
}
|
|
9309
|
+
logger.newLine();
|
|
9310
|
+
} finally {
|
|
9311
|
+
await mf.dispose();
|
|
9312
|
+
}
|
|
9313
|
+
} catch (error) {
|
|
9314
|
+
if (!options.raw && !options.json) {
|
|
9315
|
+
logger.error(
|
|
9316
|
+
`Failed to get key: ${error instanceof Error ? error.message : String(error)}`
|
|
9317
|
+
);
|
|
9318
|
+
logger.newLine();
|
|
9319
|
+
}
|
|
9320
|
+
process.exit(1);
|
|
9321
|
+
}
|
|
9322
|
+
}
|
|
9323
|
+
|
|
9324
|
+
// src/commands/kv/init.ts
|
|
9325
|
+
init_writer();
|
|
9326
|
+
async function runKVInit() {
|
|
9327
|
+
try {
|
|
9328
|
+
logger.newLine();
|
|
9329
|
+
const config = await loadConfig();
|
|
9330
|
+
if (hasKVSetup(config)) {
|
|
9331
|
+
logger.success("KV storage is already configured");
|
|
9332
|
+
logger.newLine();
|
|
9333
|
+
logger.admonition("tip", "Already Set Up", [
|
|
9334
|
+
"Your playcademy.config file already has KV integration enabled",
|
|
9335
|
+
"You can start using `c.env.KV` in your API routes"
|
|
9336
|
+
]);
|
|
9337
|
+
logger.newLine();
|
|
9338
|
+
return;
|
|
9339
|
+
}
|
|
9340
|
+
ensurePlaycademyGitignore();
|
|
9341
|
+
logger.highlight("Add KV Storage Integration");
|
|
9342
|
+
logger.newLine();
|
|
9343
|
+
const configPath = await findConfigPath();
|
|
9344
|
+
await updateConfigFile(configPath, {
|
|
9345
|
+
integrations: {
|
|
9346
|
+
...config.integrations,
|
|
9347
|
+
kv: true
|
|
9348
|
+
}
|
|
9349
|
+
});
|
|
9350
|
+
logger.success("Added KV integration to config");
|
|
9351
|
+
logger.newLine();
|
|
9352
|
+
logger.admonition("tip", "Usage Example", [
|
|
9353
|
+
"Access KV in your API routes:",
|
|
9354
|
+
"",
|
|
9355
|
+
"```typescript",
|
|
9356
|
+
"export async function GET(c: Context) {",
|
|
9357
|
+
" // Read from KV",
|
|
9358
|
+
` const data = await c.env.KV.get('user:123')`,
|
|
9359
|
+
"",
|
|
9360
|
+
" // Write to KV",
|
|
9361
|
+
` await c.env.KV.put('user:123', JSON.stringify({ score: 100 }))`,
|
|
9362
|
+
"",
|
|
9363
|
+
" // Delete from KV",
|
|
9364
|
+
` await c.env.KV.delete('user:123')`,
|
|
9365
|
+
"",
|
|
9366
|
+
" return c.json({ success: true })",
|
|
9367
|
+
"}",
|
|
9368
|
+
"```"
|
|
9369
|
+
]);
|
|
9370
|
+
logger.newLine();
|
|
9371
|
+
logger.remark("Run `playcademy dev` to start using KV in local development");
|
|
9372
|
+
logger.newLine();
|
|
9373
|
+
} catch (error) {
|
|
9374
|
+
logger.error(
|
|
9375
|
+
`Failed to add KV integration: ${error instanceof Error ? error.message : String(error)}`
|
|
9376
|
+
);
|
|
9377
|
+
logger.newLine();
|
|
9378
|
+
process.exit(1);
|
|
9379
|
+
}
|
|
9380
|
+
}
|
|
9381
|
+
|
|
9382
|
+
// src/commands/kv/inspect.ts
|
|
9383
|
+
init_constants2();
|
|
9384
|
+
import { join as join23 } from "path";
|
|
9385
|
+
import { Miniflare as Miniflare6 } from "miniflare";
|
|
9386
|
+
async function runKVInspect(key, options = {}) {
|
|
9387
|
+
try {
|
|
9388
|
+
if (!options.raw && !options.json) {
|
|
9389
|
+
logger.newLine();
|
|
9390
|
+
}
|
|
9391
|
+
if (options.remote) {
|
|
9392
|
+
if (!options.raw && !options.json) {
|
|
9393
|
+
logger.newLine();
|
|
9394
|
+
logger.warn("Remote KV operations are not yet implemented");
|
|
9395
|
+
logger.newLine();
|
|
9396
|
+
logger.admonition("info", "Coming Soon", [
|
|
9397
|
+
"Remote KV support is on the roadmap and will be available soon.",
|
|
9398
|
+
"For now, KV commands only work with local development storage."
|
|
9399
|
+
]);
|
|
9400
|
+
logger.newLine();
|
|
9401
|
+
}
|
|
9402
|
+
process.exit(1);
|
|
9403
|
+
}
|
|
9404
|
+
if (!key) {
|
|
9405
|
+
if (!options.raw && !options.json) {
|
|
9406
|
+
logger.error("Key is required");
|
|
9407
|
+
logger.newLine();
|
|
9408
|
+
logger.admonition("tip", "Usage", ["`playcademy kv inspect <key>`"]);
|
|
9409
|
+
logger.newLine();
|
|
9410
|
+
}
|
|
9411
|
+
process.exit(1);
|
|
9412
|
+
}
|
|
9413
|
+
const config = await loadConfig();
|
|
9414
|
+
if (!hasKVSetup(config)) {
|
|
9415
|
+
if (!options.raw && !options.json) {
|
|
9416
|
+
logger.error("KV storage is not configured");
|
|
9417
|
+
logger.newLine();
|
|
9418
|
+
logger.admonition("tip", "Getting Started", [
|
|
9419
|
+
"Run `playcademy kv init` to enable KV storage"
|
|
9420
|
+
]);
|
|
9421
|
+
logger.newLine();
|
|
9422
|
+
}
|
|
9423
|
+
process.exit(1);
|
|
9424
|
+
}
|
|
9425
|
+
const bundle = await bundleBackend(config, {
|
|
9426
|
+
sourcemap: false,
|
|
9427
|
+
minify: false
|
|
9428
|
+
});
|
|
9429
|
+
const kvDir = join23(getWorkspace(), CLI_DIRECTORIES.KV);
|
|
9430
|
+
const mf = new Miniflare6({
|
|
9431
|
+
modules: [
|
|
9432
|
+
{
|
|
9433
|
+
type: "ESModule",
|
|
9434
|
+
path: "index.mjs",
|
|
9435
|
+
contents: bundle.code
|
|
9436
|
+
}
|
|
9437
|
+
],
|
|
9438
|
+
kvNamespaces: ["KV"],
|
|
9439
|
+
kvPersist: kvDir,
|
|
9440
|
+
compatibilityDate: CLOUDFLARE_COMPATIBILITY_DATE
|
|
9441
|
+
});
|
|
9442
|
+
try {
|
|
9443
|
+
const kv = await mf.getKVNamespace("KV");
|
|
9444
|
+
const value = await kv.get(key);
|
|
9445
|
+
if (value === null) {
|
|
9446
|
+
const metadata2 = {
|
|
9447
|
+
key,
|
|
9448
|
+
exists: false
|
|
9449
|
+
};
|
|
9450
|
+
if (options.json) {
|
|
9451
|
+
logger.json(metadata2);
|
|
9452
|
+
} else if (!options.raw) {
|
|
9453
|
+
logger.warn(`Key '${key}' not found`);
|
|
9454
|
+
logger.newLine();
|
|
9455
|
+
logger.admonition("tip", "Hint", [
|
|
9456
|
+
"Use `playcademy kv list` to see all available keys"
|
|
9457
|
+
]);
|
|
9458
|
+
logger.newLine();
|
|
9459
|
+
}
|
|
9460
|
+
process.exit(1);
|
|
9461
|
+
}
|
|
9462
|
+
const size = new Blob([value]).size;
|
|
9463
|
+
let parsedValue;
|
|
9464
|
+
let valueType = "string";
|
|
9465
|
+
try {
|
|
9466
|
+
parsedValue = JSON.parse(value);
|
|
9467
|
+
valueType = "json";
|
|
9468
|
+
} catch {
|
|
9469
|
+
parsedValue = value;
|
|
9470
|
+
}
|
|
9471
|
+
const metadata = {
|
|
9472
|
+
key,
|
|
9473
|
+
exists: true,
|
|
9474
|
+
size,
|
|
9475
|
+
value: parsedValue,
|
|
9476
|
+
valueType
|
|
9477
|
+
};
|
|
9478
|
+
if (options.json) {
|
|
9479
|
+
logger.json(metadata);
|
|
9480
|
+
} else if (options.raw) {
|
|
9481
|
+
logger.raw(size.toString());
|
|
9482
|
+
} else {
|
|
9483
|
+
logger.success(`Key: ${key}`);
|
|
9484
|
+
logger.newLine();
|
|
9485
|
+
const sizeKB = (size / 1024).toFixed(2);
|
|
9486
|
+
const sizeDisplay = size >= 1024 ? `${sizeKB} KB` : `${size} bytes`;
|
|
9487
|
+
logger.data("Size", sizeDisplay, 1);
|
|
9488
|
+
logger.data("Type", valueType === "json" ? "JSON" : "String", 1);
|
|
9489
|
+
logger.newLine();
|
|
9490
|
+
logger.data("Value", "", 1);
|
|
9491
|
+
if (valueType === "json") {
|
|
9492
|
+
logger.json(parsedValue, 2);
|
|
9493
|
+
} else {
|
|
9494
|
+
const lines = value.split("\n");
|
|
9495
|
+
const maxLines = 5;
|
|
9496
|
+
const preview = lines.slice(0, maxLines);
|
|
9497
|
+
for (const line of preview) {
|
|
9498
|
+
logger.dim(line, 2);
|
|
9499
|
+
}
|
|
9500
|
+
if (lines.length > maxLines) {
|
|
9501
|
+
logger.dim(`... (${lines.length - maxLines} more lines)`, 2);
|
|
9502
|
+
}
|
|
9503
|
+
}
|
|
9504
|
+
logger.newLine();
|
|
9505
|
+
}
|
|
9506
|
+
} finally {
|
|
9507
|
+
await mf.dispose();
|
|
9508
|
+
}
|
|
9509
|
+
} catch (error) {
|
|
9510
|
+
if (!options.raw && !options.json) {
|
|
9511
|
+
logger.error(
|
|
9512
|
+
`Failed to inspect key: ${error instanceof Error ? error.message : String(error)}`
|
|
9513
|
+
);
|
|
9514
|
+
logger.newLine();
|
|
9515
|
+
}
|
|
9516
|
+
process.exit(1);
|
|
9517
|
+
}
|
|
9518
|
+
}
|
|
9519
|
+
|
|
9520
|
+
// src/commands/kv/list.ts
|
|
9521
|
+
init_string();
|
|
9522
|
+
init_constants2();
|
|
9523
|
+
import { join as join24 } from "path";
|
|
9524
|
+
import { Miniflare as Miniflare7 } from "miniflare";
|
|
9525
|
+
async function runKVList(options = {}) {
|
|
9526
|
+
try {
|
|
9527
|
+
if (!options.raw && !options.json) {
|
|
9528
|
+
logger.newLine();
|
|
9529
|
+
}
|
|
9530
|
+
if (options.remote) {
|
|
9531
|
+
if (!options.raw && !options.json) {
|
|
9532
|
+
logger.newLine();
|
|
9533
|
+
logger.warn("Remote KV operations are not yet implemented");
|
|
9534
|
+
logger.newLine();
|
|
9535
|
+
logger.admonition("info", "Coming Soon", [
|
|
9536
|
+
"Remote KV support is on the roadmap and will be available soon.",
|
|
9537
|
+
"For now, KV commands only work with local development storage."
|
|
9538
|
+
]);
|
|
9539
|
+
logger.newLine();
|
|
9540
|
+
}
|
|
9541
|
+
process.exit(1);
|
|
9542
|
+
}
|
|
9543
|
+
const config = await loadConfig();
|
|
9544
|
+
if (!hasKVSetup(config)) {
|
|
9545
|
+
if (!options.raw && !options.json) {
|
|
9546
|
+
logger.error("KV storage is not configured");
|
|
9547
|
+
logger.newLine();
|
|
9548
|
+
logger.admonition("tip", "Getting Started", [
|
|
9549
|
+
"Run `playcademy kv init` to enable KV storage"
|
|
9550
|
+
]);
|
|
9551
|
+
logger.newLine();
|
|
9552
|
+
}
|
|
9553
|
+
process.exit(1);
|
|
9554
|
+
}
|
|
9555
|
+
const bundle = await bundleBackend(config, {
|
|
9556
|
+
sourcemap: false,
|
|
9557
|
+
minify: false
|
|
9558
|
+
});
|
|
9559
|
+
const kvDir = join24(getWorkspace(), CLI_DIRECTORIES.KV);
|
|
9560
|
+
const mf = new Miniflare7({
|
|
9561
|
+
modules: [
|
|
9562
|
+
{
|
|
9563
|
+
type: "ESModule",
|
|
9564
|
+
path: "index.mjs",
|
|
9565
|
+
contents: bundle.code
|
|
9566
|
+
}
|
|
9567
|
+
],
|
|
9568
|
+
kvNamespaces: ["KV"],
|
|
9569
|
+
kvPersist: kvDir,
|
|
9570
|
+
compatibilityDate: CLOUDFLARE_COMPATIBILITY_DATE
|
|
9571
|
+
});
|
|
9572
|
+
try {
|
|
9573
|
+
const kv = await mf.getKVNamespace("KV");
|
|
9574
|
+
const listResult = await kv.list();
|
|
9575
|
+
const keyNames = listResult.keys?.map((k) => k.name) || [];
|
|
9576
|
+
if (options.json) {
|
|
9577
|
+
logger.json(keyNames);
|
|
9578
|
+
return;
|
|
9579
|
+
}
|
|
9580
|
+
if (options.raw) {
|
|
9581
|
+
for (const keyName of keyNames) {
|
|
9582
|
+
logger.raw(keyName);
|
|
9583
|
+
}
|
|
9584
|
+
return;
|
|
9585
|
+
}
|
|
9586
|
+
if (keyNames.length === 0) {
|
|
9587
|
+
logger.info("No keys found in KV namespace");
|
|
9588
|
+
} else {
|
|
9589
|
+
logger.success(`Found ${keyNames.length} ${pluralize(keyNames.length, "key")}`);
|
|
9590
|
+
logger.newLine();
|
|
9591
|
+
for (const keyName of keyNames) {
|
|
9592
|
+
logger.bold(keyName, 1);
|
|
9593
|
+
}
|
|
9594
|
+
}
|
|
9595
|
+
logger.newLine();
|
|
9596
|
+
} finally {
|
|
9597
|
+
await mf.dispose();
|
|
9598
|
+
}
|
|
9599
|
+
} catch (error) {
|
|
9600
|
+
if (!options.raw && !options.json) {
|
|
9601
|
+
logger.error(
|
|
9602
|
+
`Failed to list KV keys: ${error instanceof Error ? error.message : String(error)}`
|
|
9603
|
+
);
|
|
9604
|
+
logger.newLine();
|
|
9605
|
+
}
|
|
9606
|
+
process.exit(1);
|
|
9607
|
+
}
|
|
9608
|
+
}
|
|
9609
|
+
|
|
9610
|
+
// src/commands/kv/seed.ts
|
|
9611
|
+
init_file_loader();
|
|
9612
|
+
init_string();
|
|
9613
|
+
init_constants2();
|
|
9614
|
+
import { join as join25 } from "path";
|
|
9615
|
+
import { confirm as confirm8 } from "@inquirer/prompts";
|
|
9616
|
+
import { Miniflare as Miniflare8 } from "miniflare";
|
|
9617
|
+
async function runKVSeed(seedFile, options = {}) {
|
|
9618
|
+
try {
|
|
9619
|
+
if (!options.raw && !options.json) {
|
|
9620
|
+
logger.newLine();
|
|
9621
|
+
}
|
|
9622
|
+
if (options.remote) {
|
|
9623
|
+
if (!options.raw && !options.json) {
|
|
9624
|
+
logger.newLine();
|
|
9625
|
+
logger.warn("Remote KV operations are not yet implemented");
|
|
9626
|
+
logger.newLine();
|
|
9627
|
+
logger.admonition("info", "Coming Soon", [
|
|
9628
|
+
"Remote KV support is on the roadmap and will be available soon.",
|
|
9629
|
+
"For now, KV commands only work with local development storage."
|
|
9630
|
+
]);
|
|
9631
|
+
logger.newLine();
|
|
9632
|
+
}
|
|
9633
|
+
process.exit(1);
|
|
9634
|
+
}
|
|
9635
|
+
if (!seedFile) {
|
|
9636
|
+
if (!options.raw && !options.json) {
|
|
9637
|
+
logger.error("Seed file is required");
|
|
9638
|
+
logger.newLine();
|
|
9639
|
+
logger.admonition("tip", "Usage", [
|
|
9640
|
+
"`playcademy kv seed <file>`",
|
|
9641
|
+
"",
|
|
9642
|
+
"Example:",
|
|
9643
|
+
"`playcademy kv seed seeds/kv.json`"
|
|
9644
|
+
]);
|
|
9645
|
+
logger.newLine();
|
|
9646
|
+
}
|
|
9647
|
+
process.exit(1);
|
|
9648
|
+
}
|
|
9649
|
+
const config = await loadConfig();
|
|
9650
|
+
if (!hasKVSetup(config)) {
|
|
9651
|
+
if (!options.raw && !options.json) {
|
|
9652
|
+
logger.error("KV storage is not configured");
|
|
9653
|
+
logger.newLine();
|
|
9654
|
+
logger.admonition("tip", "Getting Started", [
|
|
9655
|
+
"Run `playcademy kv init` to enable KV storage"
|
|
9656
|
+
]);
|
|
9657
|
+
logger.newLine();
|
|
9658
|
+
}
|
|
9659
|
+
process.exit(1);
|
|
9660
|
+
}
|
|
9661
|
+
const workspace = getWorkspace();
|
|
9662
|
+
const seedData = await loadFile(seedFile, {
|
|
9663
|
+
cwd: workspace,
|
|
9664
|
+
parseJson: true
|
|
9665
|
+
});
|
|
9666
|
+
if (!seedData) {
|
|
9667
|
+
if (!options.raw && !options.json) {
|
|
9668
|
+
logger.error(`Seed file not found: ${seedFile}`);
|
|
9669
|
+
logger.newLine();
|
|
9670
|
+
}
|
|
9671
|
+
process.exit(1);
|
|
9672
|
+
}
|
|
9673
|
+
if (typeof seedData !== "object" || seedData === null || Array.isArray(seedData)) {
|
|
9674
|
+
if (!options.raw && !options.json) {
|
|
9675
|
+
logger.error("Seed file must contain a JSON object");
|
|
9676
|
+
logger.newLine();
|
|
9677
|
+
}
|
|
9678
|
+
process.exit(1);
|
|
9679
|
+
}
|
|
9680
|
+
const bundle = await bundleBackend(config, {
|
|
9681
|
+
sourcemap: false,
|
|
9682
|
+
minify: false
|
|
9683
|
+
});
|
|
9684
|
+
const kvDir = join25(workspace, CLI_DIRECTORIES.KV);
|
|
9685
|
+
const mf = new Miniflare8({
|
|
9686
|
+
modules: [
|
|
9687
|
+
{
|
|
9688
|
+
type: "ESModule",
|
|
9689
|
+
path: "index.mjs",
|
|
9690
|
+
contents: bundle.code
|
|
9691
|
+
}
|
|
9692
|
+
],
|
|
9693
|
+
kvNamespaces: ["KV"],
|
|
9694
|
+
kvPersist: kvDir,
|
|
9695
|
+
compatibilityDate: CLOUDFLARE_COMPATIBILITY_DATE
|
|
9696
|
+
});
|
|
9697
|
+
try {
|
|
9698
|
+
const kv = await mf.getKVNamespace("KV");
|
|
9699
|
+
const keysToSeed = Object.keys(seedData);
|
|
9700
|
+
if (!options.force && !options.replace && !options.raw && !options.json) {
|
|
9701
|
+
const existingKeys = [];
|
|
9702
|
+
for (const key of keysToSeed) {
|
|
9703
|
+
const existingValue = await kv.get(key);
|
|
9704
|
+
if (existingValue !== null) {
|
|
9705
|
+
existingKeys.push(key);
|
|
9706
|
+
}
|
|
9707
|
+
}
|
|
9708
|
+
if (existingKeys.length > 0) {
|
|
9709
|
+
logger.warn(
|
|
9710
|
+
`${existingKeys.length} ${pluralize(existingKeys.length, "key")} will be overwritten`
|
|
9711
|
+
);
|
|
9712
|
+
logger.newLine();
|
|
9713
|
+
const confirmed = await confirm8({
|
|
9714
|
+
message: "Continue seeding?",
|
|
9715
|
+
default: false
|
|
9716
|
+
});
|
|
9717
|
+
if (!confirmed) {
|
|
9718
|
+
logger.info("Cancelled");
|
|
9719
|
+
logger.newLine();
|
|
9720
|
+
return;
|
|
9721
|
+
}
|
|
9722
|
+
logger.newLine();
|
|
9723
|
+
}
|
|
9724
|
+
}
|
|
9725
|
+
if (options.replace) {
|
|
9726
|
+
const listResult = await kv.list();
|
|
9727
|
+
const existingKeys = listResult.keys || [];
|
|
9728
|
+
for (const key of existingKeys) {
|
|
9729
|
+
await kv.delete(key.name);
|
|
9730
|
+
}
|
|
9731
|
+
}
|
|
9732
|
+
let seededCount = 0;
|
|
9733
|
+
for (const key of keysToSeed) {
|
|
9734
|
+
const value = seedData[key];
|
|
9735
|
+
const valueString = typeof value === "string" ? value : JSON.stringify(value);
|
|
9736
|
+
await kv.put(key, valueString);
|
|
9737
|
+
seededCount++;
|
|
9738
|
+
}
|
|
9739
|
+
if (options.json) {
|
|
9740
|
+
logger.json({
|
|
9741
|
+
success: true,
|
|
9742
|
+
seeded: seededCount,
|
|
9743
|
+
...options.replace && { cleared: true }
|
|
9744
|
+
});
|
|
9745
|
+
} else if (options.raw) {
|
|
9746
|
+
logger.raw(seededCount.toString());
|
|
9747
|
+
} else {
|
|
9748
|
+
if (options.replace) {
|
|
9749
|
+
logger.success("Cleared existing keys");
|
|
9750
|
+
}
|
|
9751
|
+
logger.success(`Seeded ${seededCount} ${pluralize(seededCount, "key")}`);
|
|
9752
|
+
logger.newLine();
|
|
9753
|
+
}
|
|
9754
|
+
} finally {
|
|
9755
|
+
await mf.dispose();
|
|
9756
|
+
}
|
|
9757
|
+
} catch (error) {
|
|
9758
|
+
if (!options.raw && !options.json) {
|
|
9759
|
+
logger.error(
|
|
9760
|
+
`Failed to seed KV: ${error instanceof Error ? error.message : String(error)}`
|
|
9761
|
+
);
|
|
9762
|
+
logger.newLine();
|
|
9763
|
+
}
|
|
9764
|
+
process.exit(1);
|
|
9765
|
+
}
|
|
9766
|
+
}
|
|
9767
|
+
|
|
9768
|
+
// src/commands/kv/set.ts
|
|
9769
|
+
init_file_loader();
|
|
9770
|
+
init_constants2();
|
|
9771
|
+
import { join as join26 } from "path";
|
|
9772
|
+
import { Miniflare as Miniflare9 } from "miniflare";
|
|
9773
|
+
async function runKVSet(key, value, options = {}) {
|
|
9774
|
+
try {
|
|
9775
|
+
if (!options.raw && !options.json) {
|
|
9776
|
+
logger.newLine();
|
|
9777
|
+
}
|
|
9778
|
+
if (options.remote) {
|
|
9779
|
+
if (!options.raw && !options.json) {
|
|
9780
|
+
logger.newLine();
|
|
9781
|
+
logger.warn("Remote KV operations are not yet implemented");
|
|
9782
|
+
logger.newLine();
|
|
9783
|
+
logger.admonition("info", "Coming Soon", [
|
|
9784
|
+
"Remote KV support is on the roadmap and will be available soon.",
|
|
9785
|
+
"For now, KV commands only work with local development storage."
|
|
9786
|
+
]);
|
|
9787
|
+
logger.newLine();
|
|
9788
|
+
}
|
|
9789
|
+
process.exit(1);
|
|
9790
|
+
}
|
|
9791
|
+
if (!key) {
|
|
9792
|
+
if (!options.raw && !options.json) {
|
|
9793
|
+
logger.error("Key is required");
|
|
9794
|
+
logger.newLine();
|
|
9795
|
+
logger.admonition("tip", "Usage", [
|
|
9796
|
+
"`playcademy kv set <key> <value>`",
|
|
9797
|
+
"`playcademy kv set <key> --file <path>`"
|
|
9798
|
+
]);
|
|
9799
|
+
logger.newLine();
|
|
9800
|
+
}
|
|
9801
|
+
process.exit(1);
|
|
9802
|
+
}
|
|
9803
|
+
const config = await loadConfig();
|
|
9804
|
+
if (!hasKVSetup(config)) {
|
|
9805
|
+
if (!options.raw && !options.json) {
|
|
9806
|
+
logger.error("KV storage is not configured");
|
|
9807
|
+
logger.newLine();
|
|
9808
|
+
logger.admonition("tip", "Getting Started", [
|
|
9809
|
+
"Run `playcademy kv init` to enable KV storage"
|
|
9810
|
+
]);
|
|
9811
|
+
logger.newLine();
|
|
9812
|
+
}
|
|
9813
|
+
process.exit(1);
|
|
9814
|
+
}
|
|
9815
|
+
let valueToSet;
|
|
9816
|
+
if (options.file) {
|
|
9817
|
+
const workspace = getWorkspace();
|
|
9818
|
+
const fileContent = await loadFile(options.file, { cwd: workspace });
|
|
9819
|
+
if (!fileContent) {
|
|
9820
|
+
if (!options.raw && !options.json) {
|
|
9821
|
+
logger.error(`File not found: ${options.file}`);
|
|
9822
|
+
logger.newLine();
|
|
9823
|
+
}
|
|
9824
|
+
process.exit(1);
|
|
9825
|
+
}
|
|
9826
|
+
valueToSet = fileContent;
|
|
9827
|
+
} else if (value !== void 0) {
|
|
9828
|
+
valueToSet = value;
|
|
9829
|
+
} else {
|
|
9830
|
+
if (!options.raw && !options.json) {
|
|
9831
|
+
logger.error("Value is required (either as argument or via --file)");
|
|
9832
|
+
logger.newLine();
|
|
9833
|
+
logger.admonition("tip", "Usage", [
|
|
9834
|
+
"`playcademy kv set <key> <value>`",
|
|
9835
|
+
"`playcademy kv set <key> --file <path>`"
|
|
9836
|
+
]);
|
|
9837
|
+
logger.newLine();
|
|
9838
|
+
}
|
|
9839
|
+
process.exit(1);
|
|
9840
|
+
}
|
|
9841
|
+
const bundle = await bundleBackend(config, {
|
|
9842
|
+
sourcemap: false,
|
|
9843
|
+
minify: false
|
|
9844
|
+
});
|
|
9845
|
+
const kvDir = join26(getWorkspace(), CLI_DIRECTORIES.KV);
|
|
9846
|
+
const mf = new Miniflare9({
|
|
9847
|
+
modules: [
|
|
9848
|
+
{
|
|
9849
|
+
type: "ESModule",
|
|
9850
|
+
path: "index.mjs",
|
|
9851
|
+
contents: bundle.code
|
|
9852
|
+
}
|
|
9853
|
+
],
|
|
9854
|
+
kvNamespaces: ["KV"],
|
|
9855
|
+
kvPersist: kvDir,
|
|
9856
|
+
compatibilityDate: CLOUDFLARE_COMPATIBILITY_DATE
|
|
9857
|
+
});
|
|
9858
|
+
try {
|
|
9859
|
+
const kv = await mf.getKVNamespace("KV");
|
|
9860
|
+
await kv.put(key, valueToSet);
|
|
9861
|
+
if (options.json) {
|
|
9862
|
+
logger.json({
|
|
9863
|
+
key,
|
|
9864
|
+
success: true
|
|
9865
|
+
});
|
|
9866
|
+
} else if (options.raw) {
|
|
9867
|
+
logger.raw("ok");
|
|
9868
|
+
} else {
|
|
9869
|
+
logger.success(`Set key: ${key}`);
|
|
9870
|
+
logger.newLine();
|
|
9871
|
+
}
|
|
9872
|
+
} finally {
|
|
9873
|
+
await mf.dispose();
|
|
9874
|
+
}
|
|
9875
|
+
} catch (error) {
|
|
9876
|
+
if (!options.raw && !options.json) {
|
|
9877
|
+
logger.error(
|
|
9878
|
+
`Failed to set key: ${error instanceof Error ? error.message : String(error)}`
|
|
9879
|
+
);
|
|
9880
|
+
logger.newLine();
|
|
9881
|
+
}
|
|
9882
|
+
process.exit(1);
|
|
9883
|
+
}
|
|
9884
|
+
}
|
|
9885
|
+
|
|
9886
|
+
// src/commands/kv/stats.ts
|
|
9887
|
+
init_string();
|
|
9888
|
+
init_constants2();
|
|
9889
|
+
import { join as join27 } from "path";
|
|
9890
|
+
import { Miniflare as Miniflare10 } from "miniflare";
|
|
9891
|
+
async function runKVStats(options = {}) {
|
|
9892
|
+
try {
|
|
9893
|
+
if (!options.raw && !options.json) {
|
|
9894
|
+
logger.newLine();
|
|
9895
|
+
}
|
|
9896
|
+
if (options.remote) {
|
|
9897
|
+
if (!options.raw && !options.json) {
|
|
9898
|
+
logger.newLine();
|
|
9899
|
+
logger.warn("Remote KV operations are not yet implemented");
|
|
9900
|
+
logger.newLine();
|
|
9901
|
+
logger.admonition("info", "Coming Soon", [
|
|
9902
|
+
"Remote KV support is on the roadmap and will be available soon.",
|
|
9903
|
+
"For now, KV commands only work with local development storage."
|
|
9904
|
+
]);
|
|
9905
|
+
logger.newLine();
|
|
9906
|
+
}
|
|
9907
|
+
process.exit(1);
|
|
9908
|
+
}
|
|
9909
|
+
const config = await loadConfig();
|
|
9910
|
+
if (!hasKVSetup(config)) {
|
|
9911
|
+
if (!options.raw && !options.json) {
|
|
9912
|
+
logger.error("KV storage is not configured");
|
|
9913
|
+
logger.newLine();
|
|
9914
|
+
logger.admonition("tip", "Getting Started", [
|
|
9915
|
+
"Run `playcademy kv init` to enable KV storage"
|
|
9916
|
+
]);
|
|
9917
|
+
logger.newLine();
|
|
9918
|
+
}
|
|
9919
|
+
process.exit(1);
|
|
9920
|
+
}
|
|
9921
|
+
const bundle = await bundleBackend(config, {
|
|
9922
|
+
sourcemap: false,
|
|
9923
|
+
minify: false
|
|
9924
|
+
});
|
|
9925
|
+
const kvDir = join27(getWorkspace(), CLI_DIRECTORIES.KV);
|
|
9926
|
+
const mf = new Miniflare10({
|
|
9927
|
+
modules: [
|
|
9928
|
+
{
|
|
9929
|
+
type: "ESModule",
|
|
9930
|
+
path: "index.mjs",
|
|
9931
|
+
contents: bundle.code
|
|
9932
|
+
}
|
|
9933
|
+
],
|
|
9934
|
+
kvNamespaces: ["KV"],
|
|
9935
|
+
kvPersist: kvDir,
|
|
9936
|
+
compatibilityDate: CLOUDFLARE_COMPATIBILITY_DATE
|
|
9937
|
+
});
|
|
9938
|
+
try {
|
|
9939
|
+
const kv = await mf.getKVNamespace("KV");
|
|
9940
|
+
const listResult = await kv.list();
|
|
9941
|
+
const keys = listResult.keys || [];
|
|
9942
|
+
let totalSize = 0;
|
|
9943
|
+
let largestKey;
|
|
9944
|
+
const prefixes = {};
|
|
9945
|
+
for (const key of keys) {
|
|
9946
|
+
const value = await kv.get(key.name);
|
|
9947
|
+
const size = value ? new Blob([value]).size : 0;
|
|
9948
|
+
totalSize += size;
|
|
9949
|
+
if (!largestKey || size > largestKey.size) {
|
|
9950
|
+
largestKey = { name: key.name, size };
|
|
9951
|
+
}
|
|
9952
|
+
const prefixMatch = key.name.match(/^([^:]+):/);
|
|
9953
|
+
const prefix = prefixMatch ? `${prefixMatch[1]}:*` : "(no prefix)";
|
|
9954
|
+
prefixes[prefix] = (prefixes[prefix] || 0) + 1;
|
|
9955
|
+
}
|
|
9956
|
+
const stats = {
|
|
9957
|
+
totalKeys: keys.length,
|
|
9958
|
+
totalSize,
|
|
9959
|
+
largestKey,
|
|
9960
|
+
prefixes
|
|
9961
|
+
};
|
|
9962
|
+
if (options.json) {
|
|
9963
|
+
logger.json(stats);
|
|
9964
|
+
} else if (options.raw) {
|
|
9965
|
+
logger.raw(stats.totalKeys.toString());
|
|
9966
|
+
} else {
|
|
9967
|
+
if (stats.totalKeys === 0) {
|
|
9968
|
+
logger.info("No keys found in KV namespace");
|
|
9969
|
+
logger.newLine();
|
|
9970
|
+
return;
|
|
9971
|
+
}
|
|
9972
|
+
logger.data("Total Keys", stats.totalKeys.toString(), 1);
|
|
9973
|
+
const sizeKB = (stats.totalSize / 1024).toFixed(2);
|
|
9974
|
+
const sizeMB = (stats.totalSize / 1024 / 1024).toFixed(2);
|
|
9975
|
+
const sizeDisplay = stats.totalSize >= 1024 * 1024 ? `${sizeMB} MB` : `${sizeKB} KB`;
|
|
9976
|
+
logger.data("Total Size", sizeDisplay, 1);
|
|
9977
|
+
if (stats.largestKey) {
|
|
9978
|
+
const largestKB = (stats.largestKey.size / 1024).toFixed(2);
|
|
9979
|
+
logger.data("Largest Key", `${stats.largestKey.name} (${largestKB} KB)`, 1);
|
|
9980
|
+
}
|
|
9981
|
+
const sortedPrefixes = Object.entries(stats.prefixes).sort((a, b) => b[1] - a[1]);
|
|
9982
|
+
if (sortedPrefixes.length > 0) {
|
|
9983
|
+
logger.newLine();
|
|
9984
|
+
logger.data("Keys by Prefix:", "", 1);
|
|
9985
|
+
for (const [prefix, count] of sortedPrefixes) {
|
|
9986
|
+
logger.dim(`${prefix.padEnd(20)} ${count} ${pluralize(count, "key")}`, 2);
|
|
9987
|
+
}
|
|
9988
|
+
}
|
|
9989
|
+
logger.newLine();
|
|
9990
|
+
}
|
|
9991
|
+
} finally {
|
|
9992
|
+
await mf.dispose();
|
|
9993
|
+
}
|
|
9994
|
+
} catch (error) {
|
|
9995
|
+
if (!options.raw && !options.json) {
|
|
9996
|
+
logger.error(
|
|
9997
|
+
`Failed to get KV stats: ${error instanceof Error ? error.message : String(error)}`
|
|
9998
|
+
);
|
|
9999
|
+
logger.newLine();
|
|
10000
|
+
}
|
|
10001
|
+
process.exit(1);
|
|
10002
|
+
}
|
|
10003
|
+
}
|
|
10004
|
+
|
|
10005
|
+
// src/commands/kv/index.ts
|
|
10006
|
+
var kvCommand = new Command15("kv").description("Manage KV storage integration").action(() => {
|
|
10007
|
+
kvCommand.help();
|
|
10008
|
+
});
|
|
10009
|
+
kvCommand.command("init").description("Add KV storage integration to your project").action(runKVInit);
|
|
10010
|
+
kvCommand.command("list").alias("ls").description("List keys in local KV namespace (dev mode)").option("--raw", "Output key names one per line, no formatting").option("--json", "Output as JSON array").option("--remote", "Use remote KV storage (not yet implemented)").option(
|
|
10011
|
+
"--env <environment>",
|
|
10012
|
+
"Environment to use with --remote: staging (default) or production"
|
|
10013
|
+
).action(runKVList);
|
|
10014
|
+
kvCommand.command("stats").description("Show statistics about local KV namespace (dev mode)").option("--raw", "Output total key count only").option("--json", "Output statistics as JSON").option("--remote", "Use remote KV storage (not yet implemented)").option(
|
|
10015
|
+
"--env <environment>",
|
|
10016
|
+
"Environment to use with --remote: staging (default) or production"
|
|
10017
|
+
).action(runKVStats);
|
|
10018
|
+
kvCommand.command("get <key>").description("Get value for a specific key in local KV namespace (dev mode)").option("--raw", "Output raw value without formatting").option("--json", "Force JSON pretty-printing (default for valid JSON)").option("--remote", "Use remote KV storage (not yet implemented)").option(
|
|
10019
|
+
"--env <environment>",
|
|
10020
|
+
"Environment to use with --remote: staging (default) or production"
|
|
10021
|
+
).action(runKVGet);
|
|
10022
|
+
kvCommand.command("inspect <key>").description("Inspect metadata and value for a specific key in local KV namespace (dev mode)").option("--raw", "Output size in bytes only").option("--json", "Output metadata as JSON").option("--remote", "Use remote KV storage (not yet implemented)").option(
|
|
10023
|
+
"--env <environment>",
|
|
10024
|
+
"Environment to use with --remote: staging (default) or production"
|
|
10025
|
+
).action(runKVInspect);
|
|
10026
|
+
kvCommand.command("set <key> [value]").description("Set a key-value pair in local KV namespace (dev mode)").option("--raw", "Output minimal confirmation").option("--json", "Output result as JSON").option("--file <path>", "Read value from file").option("--remote", "Use remote KV storage (not yet implemented)").option(
|
|
10027
|
+
"--env <environment>",
|
|
10028
|
+
"Environment to use with --remote: staging (default) or production"
|
|
10029
|
+
).action(runKVSet);
|
|
10030
|
+
kvCommand.command("delete <key>").alias("del").alias("rm").description("Delete a specific key from local KV namespace (dev mode)").option("--raw", "Output minimal confirmation").option("--json", "Output result as JSON").option("--remote", "Use remote KV storage (not yet implemented)").option(
|
|
10031
|
+
"--env <environment>",
|
|
10032
|
+
"Environment to use with --remote: staging (default) or production"
|
|
10033
|
+
).action(runKVDelete);
|
|
10034
|
+
kvCommand.command("clear").description("Clear all keys from local KV namespace (dev mode)").option("--raw", "Output count of deleted keys").option("--json", "Output result as JSON").option("-f, --force", "Skip confirmation prompt").option("--remote", "Use remote KV storage (not yet implemented)").option(
|
|
10035
|
+
"--env <environment>",
|
|
10036
|
+
"Environment to use with --remote: staging (default) or production"
|
|
10037
|
+
).action(runKVClear);
|
|
10038
|
+
kvCommand.command("seed <file>").description("Seed KV namespace with key-value pairs from JSON file (dev mode)").option("--raw", "Output count of seeded keys").option("--json", "Output result as JSON").option("--replace", "Clear existing keys before seeding").option("-f, --force", "Skip confirmation prompt").option("--remote", "Use remote KV storage (not yet implemented)").option(
|
|
10039
|
+
"--env <environment>",
|
|
10040
|
+
"Environment to use with --remote: staging (default) or production"
|
|
10041
|
+
).action(runKVSeed);
|
|
10042
|
+
|
|
10043
|
+
// src/commands/profiles/index.ts
|
|
10044
|
+
import { Command as Command19 } from "commander";
|
|
10045
|
+
|
|
10046
|
+
// src/commands/profiles/list.ts
|
|
10047
|
+
init_string();
|
|
10048
|
+
import { Command as Command16 } from "commander";
|
|
10049
|
+
async function listProfilesAction() {
|
|
10050
|
+
try {
|
|
10051
|
+
const profilesMap = await listProfiles();
|
|
10052
|
+
logger.newLine();
|
|
10053
|
+
let hasAnyProfiles = false;
|
|
10054
|
+
for (const [, profiles] of profilesMap.entries()) {
|
|
10055
|
+
if (profiles.length > 0) {
|
|
10056
|
+
hasAnyProfiles = true;
|
|
10057
|
+
break;
|
|
10058
|
+
}
|
|
10059
|
+
}
|
|
10060
|
+
if (!hasAnyProfiles) {
|
|
10061
|
+
logger.warn("No authentication profiles found");
|
|
10062
|
+
logger.newLine();
|
|
10063
|
+
logger.admonition("tip", "Getting Started", [
|
|
10064
|
+
"Run `playcademy login` to create your first profile"
|
|
10065
|
+
]);
|
|
10066
|
+
logger.newLine();
|
|
10067
|
+
return;
|
|
10068
|
+
}
|
|
10069
|
+
for (const [environment, profiles] of profilesMap.entries()) {
|
|
10070
|
+
if (profiles.length === 0) continue;
|
|
10071
|
+
const tableData = [];
|
|
10072
|
+
for (const profileName of profiles) {
|
|
10073
|
+
const profile = await getProfile(environment, profileName);
|
|
10074
|
+
tableData.push({
|
|
10075
|
+
Profile: profileName,
|
|
10076
|
+
Email: profile?.email ?? ""
|
|
10077
|
+
});
|
|
10078
|
+
}
|
|
10079
|
+
if (tableData.length > 0) {
|
|
10080
|
+
const envTitle = capitalize(environment);
|
|
10081
|
+
logger.table(tableData, envTitle);
|
|
10082
|
+
logger.newLine();
|
|
10083
|
+
}
|
|
10084
|
+
}
|
|
10085
|
+
} catch (error) {
|
|
10086
|
+
logger.error(`Failed to list profiles: ${getErrorMessage(error)}`);
|
|
10087
|
+
process.exit(1);
|
|
10088
|
+
}
|
|
10089
|
+
}
|
|
10090
|
+
var listCommand2 = new Command16("list").alias("ls").description("List all stored authentication profiles").action(listProfilesAction);
|
|
10091
|
+
|
|
10092
|
+
// src/commands/profiles/remove.ts
|
|
10093
|
+
import { bold as bold6 } from "colorette";
|
|
10094
|
+
import { Command as Command17 } from "commander";
|
|
10095
|
+
var removeCommand = new Command17("remove").alias("rm").description('Remove an authentication profile (defaults to "default")').argument("[name]", "Profile name to remove", "default").option("--env <environment>", "Environment to remove profile from (staging or production)").action(async (name, options) => {
|
|
10096
|
+
const { env } = options;
|
|
10097
|
+
const environment = ensureEnvironment(env, logger);
|
|
10098
|
+
try {
|
|
10099
|
+
logger.newLine();
|
|
10100
|
+
const profilesMap = await listProfiles();
|
|
10101
|
+
const envProfiles = profilesMap.get(environment) || [];
|
|
10102
|
+
if (!envProfiles.includes(name)) {
|
|
10103
|
+
logger.error(`Profile "${name}" not found in ${environment}`);
|
|
10104
|
+
logger.newLine();
|
|
10105
|
+
process.exit(1);
|
|
10106
|
+
}
|
|
10107
|
+
await removeProfile(environment, name);
|
|
10108
|
+
logger.admonition("note", "Removed!", [
|
|
10109
|
+
`Profile ${bold6(name)} removed from ${environment}`,
|
|
10110
|
+
environment === "production" ? `To re-authenticate run \`playcademy login --env ${environment}\`` : "To re-authenticate run `playcademy login`"
|
|
10111
|
+
]);
|
|
10112
|
+
logger.newLine();
|
|
10113
|
+
} catch (error) {
|
|
10114
|
+
logAndExit(error, logger, { prefix: "Failed to remove profile" });
|
|
10115
|
+
}
|
|
10116
|
+
});
|
|
10117
|
+
|
|
10118
|
+
// src/commands/profiles/reset.ts
|
|
10119
|
+
init_string();
|
|
10120
|
+
import { confirm as confirm9 } from "@inquirer/prompts";
|
|
10121
|
+
import { Command as Command18 } from "commander";
|
|
10122
|
+
var resetCommand = new Command18("reset").description(
|
|
10123
|
+
"Remove all authentication profiles across all environments (requires confirmation)"
|
|
10124
|
+
).alias("clear").action(async () => {
|
|
10125
|
+
try {
|
|
10126
|
+
const profilesMap = await listProfiles();
|
|
10127
|
+
let totalProfiles = 0;
|
|
10128
|
+
for (const profiles of profilesMap.values()) {
|
|
10129
|
+
totalProfiles += profiles.length;
|
|
10130
|
+
}
|
|
10131
|
+
if (totalProfiles === 0) {
|
|
10132
|
+
logger.newLine();
|
|
10133
|
+
logger.warn("No authentication profiles found");
|
|
10134
|
+
logger.newLine();
|
|
10135
|
+
return;
|
|
10136
|
+
}
|
|
10137
|
+
logger.newLine();
|
|
10138
|
+
logger.warn(
|
|
9028
10139
|
`This will remove ${totalProfiles} authentication ${pluralize(totalProfiles, "profile")} across all environments:`
|
|
9029
10140
|
);
|
|
9030
10141
|
logger.newLine();
|
|
@@ -9044,7 +10155,7 @@ var resetCommand = new Command17("reset").description(
|
|
|
9044
10155
|
logger.newLine();
|
|
9045
10156
|
}
|
|
9046
10157
|
}
|
|
9047
|
-
const confirmed = await
|
|
10158
|
+
const confirmed = await confirm9({
|
|
9048
10159
|
message: "Are you sure you want to remove all profiles?",
|
|
9049
10160
|
default: false
|
|
9050
10161
|
});
|
|
@@ -9082,19 +10193,19 @@ var resetCommand = new Command17("reset").description(
|
|
|
9082
10193
|
});
|
|
9083
10194
|
|
|
9084
10195
|
// src/commands/profiles/index.ts
|
|
9085
|
-
var profilesCommand = new
|
|
10196
|
+
var profilesCommand = new Command19("profiles").description("Manage authentication profiles").action(listProfilesAction);
|
|
9086
10197
|
profilesCommand.addCommand(listCommand2);
|
|
9087
10198
|
profilesCommand.addCommand(removeCommand);
|
|
9088
10199
|
profilesCommand.addCommand(resetCommand);
|
|
9089
10200
|
|
|
9090
10201
|
// src/commands/timeback/index.ts
|
|
9091
|
-
import { Command as
|
|
10202
|
+
import { Command as Command25 } from "commander";
|
|
9092
10203
|
|
|
9093
10204
|
// src/commands/timeback/cleanup.ts
|
|
9094
10205
|
init_src();
|
|
9095
|
-
import { confirm as
|
|
9096
|
-
import { Command as
|
|
9097
|
-
var cleanupCommand = new
|
|
10206
|
+
import { confirm as confirm10 } from "@inquirer/prompts";
|
|
10207
|
+
import { Command as Command20 } from "commander";
|
|
10208
|
+
var cleanupCommand = new Command20("cleanup").description("Remove TimeBack integration for your game").option(
|
|
9098
10209
|
"--env <environment>",
|
|
9099
10210
|
"Environment to remove TimeBack integration from (staging or production)"
|
|
9100
10211
|
).action(async (options) => {
|
|
@@ -9116,7 +10227,7 @@ var cleanupCommand = new Command19("cleanup").description("Remove TimeBack integ
|
|
|
9116
10227
|
return;
|
|
9117
10228
|
}
|
|
9118
10229
|
displayCleanupWarning(integration, game.displayName, logger);
|
|
9119
|
-
const confirmed = await
|
|
10230
|
+
const confirmed = await confirm10({
|
|
9120
10231
|
message: "Are you sure you want to remove TimeBack integration?",
|
|
9121
10232
|
default: false
|
|
9122
10233
|
});
|
|
@@ -9145,8 +10256,8 @@ var cleanupCommand = new Command19("cleanup").description("Remove TimeBack integ
|
|
|
9145
10256
|
});
|
|
9146
10257
|
|
|
9147
10258
|
// src/commands/timeback/init.ts
|
|
9148
|
-
import { Command as
|
|
9149
|
-
var initCommand2 = new
|
|
10259
|
+
import { Command as Command21 } from "commander";
|
|
10260
|
+
var initCommand2 = new Command21("init").description("Add TimeBack integration to your project").action(async () => {
|
|
9150
10261
|
try {
|
|
9151
10262
|
logger.newLine();
|
|
9152
10263
|
const configPath = await findConfigPath().catch(() => {
|
|
@@ -9199,8 +10310,8 @@ var initCommand2 = new Command20("init").description("Add TimeBack integration t
|
|
|
9199
10310
|
|
|
9200
10311
|
// src/commands/timeback/setup.ts
|
|
9201
10312
|
init_src();
|
|
9202
|
-
import { Command as
|
|
9203
|
-
var setupCommand = new
|
|
10313
|
+
import { Command as Command22 } from "commander";
|
|
10314
|
+
var setupCommand = new Command22("setup").description("Set up TimeBack integration for your game").option("--dry-run", "Validate configuration without creating resources").option("--verbose, -v", "Output detailed information").option(
|
|
9204
10315
|
"--env <environment>",
|
|
9205
10316
|
"Environment to set up TimeBack integration in (staging or production)"
|
|
9206
10317
|
).action(async (options) => {
|
|
@@ -9292,10 +10403,10 @@ var setupCommand = new Command21("setup").description("Set up TimeBack integrati
|
|
|
9292
10403
|
// src/commands/timeback/update.ts
|
|
9293
10404
|
init_src();
|
|
9294
10405
|
init_string();
|
|
9295
|
-
import { confirm as
|
|
10406
|
+
import { confirm as confirm11 } from "@inquirer/prompts";
|
|
9296
10407
|
import { green as green4, red as red3 } from "colorette";
|
|
9297
|
-
import { Command as
|
|
9298
|
-
var updateCommand = new
|
|
10408
|
+
import { Command as Command23 } from "commander";
|
|
10409
|
+
var updateCommand = new Command23("update").description("Update TimeBack integration configuration for your game").option("--verbose, -v", "Output detailed information").option(
|
|
9299
10410
|
"--env <environment>",
|
|
9300
10411
|
"Environment to update TimeBack integration in (staging or production)"
|
|
9301
10412
|
).action(async (options) => {
|
|
@@ -9373,7 +10484,7 @@ var updateCommand = new Command22("update").description("Update TimeBack integra
|
|
|
9373
10484
|
logger.data(change.label, `${red3(change.current)} \u2192 ${green4(change.next)}`, 1);
|
|
9374
10485
|
}
|
|
9375
10486
|
logger.newLine();
|
|
9376
|
-
const confirmed = await
|
|
10487
|
+
const confirmed = await confirm11({
|
|
9377
10488
|
message: `Update ${changeDetails.length} ${pluralize(changeDetails.length, "field")} in TimeBack?`,
|
|
9378
10489
|
default: false
|
|
9379
10490
|
});
|
|
@@ -9413,8 +10524,8 @@ var updateCommand = new Command22("update").description("Update TimeBack integra
|
|
|
9413
10524
|
|
|
9414
10525
|
// src/commands/timeback/verify.ts
|
|
9415
10526
|
init_src();
|
|
9416
|
-
import { Command as
|
|
9417
|
-
var verifyCommand = new
|
|
10527
|
+
import { Command as Command24 } from "commander";
|
|
10528
|
+
var verifyCommand = new Command24("verify").description("Verify TimeBack integration for your game").option("--verbose, -v", "Output detailed resource information").option(
|
|
9418
10529
|
"--env <environment>",
|
|
9419
10530
|
"Environment to verify TimeBack integration in (staging or production)"
|
|
9420
10531
|
).action(async (options) => {
|
|
@@ -9464,7 +10575,7 @@ var verifyCommand = new Command23("verify").description("Verify TimeBack integra
|
|
|
9464
10575
|
});
|
|
9465
10576
|
|
|
9466
10577
|
// src/commands/timeback/index.ts
|
|
9467
|
-
var timebackCommand = new
|
|
10578
|
+
var timebackCommand = new Command25("timeback").description(
|
|
9468
10579
|
"TimeBack integration management"
|
|
9469
10580
|
);
|
|
9470
10581
|
timebackCommand.addCommand(initCommand2);
|
|
@@ -9474,15 +10585,15 @@ timebackCommand.addCommand(verifyCommand);
|
|
|
9474
10585
|
timebackCommand.addCommand(cleanupCommand);
|
|
9475
10586
|
|
|
9476
10587
|
// src/commands/debug/index.ts
|
|
9477
|
-
import { Command as
|
|
10588
|
+
import { Command as Command27 } from "commander";
|
|
9478
10589
|
|
|
9479
10590
|
// src/commands/debug/bundle.ts
|
|
9480
10591
|
init_src();
|
|
9481
10592
|
init_constants2();
|
|
9482
|
-
import { writeFileSync as
|
|
9483
|
-
import { join as
|
|
9484
|
-
import { Command as
|
|
9485
|
-
var bundleCommand = new
|
|
10593
|
+
import { writeFileSync as writeFileSync7 } from "fs";
|
|
10594
|
+
import { join as join28 } from "path";
|
|
10595
|
+
import { Command as Command26 } from "commander";
|
|
10596
|
+
var bundleCommand = new Command26("bundle").description("Bundle and inspect the game backend worker code (for debugging)").option("-o, --output <path>", "Output file path", CLI_DEFAULT_OUTPUTS.WORKER_BUNDLE).option("--minify", "Minify the output").option("--sourcemap", "Include source maps").action(async (options) => {
|
|
9486
10597
|
try {
|
|
9487
10598
|
const workspace = getWorkspace();
|
|
9488
10599
|
logger.newLine();
|
|
@@ -9510,8 +10621,8 @@ var bundleCommand = new Command25("bundle").description("Bundle and inspect the
|
|
|
9510
10621
|
}),
|
|
9511
10622
|
(result) => `Bundled ${formatSize(result.code.length)}`
|
|
9512
10623
|
);
|
|
9513
|
-
const outputPath =
|
|
9514
|
-
|
|
10624
|
+
const outputPath = join28(workspace, options.output);
|
|
10625
|
+
writeFileSync7(outputPath, bundle.code, "utf-8");
|
|
9515
10626
|
logger.success(`Bundle saved to ${options.output}`);
|
|
9516
10627
|
logger.newLine();
|
|
9517
10628
|
logger.highlight("Bundle Analysis");
|
|
@@ -9551,7 +10662,7 @@ var bundleCommand = new Command25("bundle").description("Bundle and inspect the
|
|
|
9551
10662
|
});
|
|
9552
10663
|
|
|
9553
10664
|
// src/commands/debug/index.ts
|
|
9554
|
-
var debugCommand = new
|
|
10665
|
+
var debugCommand = new Command27("debug").description("Debug and inspect game builds").addCommand(bundleCommand);
|
|
9555
10666
|
|
|
9556
10667
|
// src/index.ts
|
|
9557
10668
|
var __dirname = dirname4(fileURLToPath2(import.meta.url));
|
|
@@ -9568,6 +10679,7 @@ program.addCommand(meCommand);
|
|
|
9568
10679
|
program.addCommand(devCommand);
|
|
9569
10680
|
program.addCommand(apiCommand);
|
|
9570
10681
|
program.addCommand(dbCommand);
|
|
10682
|
+
program.addCommand(kvCommand);
|
|
9571
10683
|
program.addCommand(gamesCommand);
|
|
9572
10684
|
program.addCommand(deployCommand);
|
|
9573
10685
|
program.addCommand(timebackCommand);
|
|
@@ -9607,6 +10719,7 @@ export {
|
|
|
9607
10719
|
displaySuccessMessage,
|
|
9608
10720
|
ensureEnvironment,
|
|
9609
10721
|
ensureGameExists,
|
|
10722
|
+
ensurePlaycademyGitignore,
|
|
9610
10723
|
findConfigPath,
|
|
9611
10724
|
formatSize,
|
|
9612
10725
|
generateEntryCode,
|
|
@@ -9627,6 +10740,7 @@ export {
|
|
|
9627
10740
|
getDeployedGame,
|
|
9628
10741
|
getDeploymentId,
|
|
9629
10742
|
getDevServerPidPath,
|
|
10743
|
+
getDrizzleKitApiExports,
|
|
9630
10744
|
getEnvironment,
|
|
9631
10745
|
getErrorMessage,
|
|
9632
10746
|
getGameFromConfig,
|
|
@@ -9647,6 +10761,7 @@ export {
|
|
|
9647
10761
|
hasCustomRoutes,
|
|
9648
10762
|
hasCustomRoutesChanged,
|
|
9649
10763
|
hasDatabaseSetup,
|
|
10764
|
+
hasKVSetup,
|
|
9650
10765
|
hasLocalCustomRoutes,
|
|
9651
10766
|
hasMandatoryFieldsMissing,
|
|
9652
10767
|
hasOptionalFieldsMissing,
|