@meetploy/cli 1.14.1 → 1.16.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/dashboard-dist/assets/{main-qA3kxECS.css → main-B-euJxpr.css} +1 -1
- package/dist/dashboard-dist/assets/main-duAiLjPq.js +339 -0
- package/dist/dashboard-dist/index.html +2 -2
- package/dist/dev.js +190 -14
- package/dist/index.js +440 -40
- package/package.json +1 -1
- package/dist/dashboard-dist/assets/main-UY1Z1kG0.js +0 -334
package/dist/index.js
CHANGED
|
@@ -8,11 +8,11 @@ import { parse } from 'yaml';
|
|
|
8
8
|
import { build } from 'esbuild';
|
|
9
9
|
import { watch } from 'chokidar';
|
|
10
10
|
import { randomBytes, randomUUID, createHmac, pbkdf2Sync, timingSafeEqual, createHash } from 'crypto';
|
|
11
|
+
import { homedir, tmpdir } from 'os';
|
|
11
12
|
import { getCookie, deleteCookie, setCookie } from 'hono/cookie';
|
|
12
13
|
import { URL, fileURLToPath } from 'url';
|
|
13
14
|
import { serve } from '@hono/node-server';
|
|
14
15
|
import { Hono } from 'hono';
|
|
15
|
-
import { homedir, tmpdir } from 'os';
|
|
16
16
|
import Database from 'better-sqlite3';
|
|
17
17
|
import { spawn, exec } from 'child_process';
|
|
18
18
|
import createClient from 'openapi-fetch';
|
|
@@ -183,10 +183,24 @@ function validatePloyConfig(config, configFile = "ploy.yaml", options = {}) {
|
|
|
183
183
|
validatedConfig.out = validateRelativePath(config.out, "out", configFile);
|
|
184
184
|
validatedConfig.base = validateRelativePath(config.base, "base", configFile);
|
|
185
185
|
validatedConfig.main = validateRelativePath(config.main, "main", configFile);
|
|
186
|
+
if (config.env !== void 0) {
|
|
187
|
+
if (typeof config.env !== "object" || config.env === null) {
|
|
188
|
+
throw new Error(`'env' in ${configFile} must be a map of KEY: value`);
|
|
189
|
+
}
|
|
190
|
+
for (const [key, value] of Object.entries(config.env)) {
|
|
191
|
+
if (!BINDING_NAME_REGEX.test(key)) {
|
|
192
|
+
throw new Error(`Invalid env key '${key}' in ${configFile}. Env keys must be uppercase with underscores (e.g., FOO, MY_VAR)`);
|
|
193
|
+
}
|
|
194
|
+
if (typeof value !== "string") {
|
|
195
|
+
throw new Error(`Env key '${key}' in ${configFile} must have a string value`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
186
199
|
validateBindings(config.db, "db", configFile);
|
|
187
200
|
validateBindings(config.queue, "queue", configFile);
|
|
188
201
|
validateBindings(config.cache, "cache", configFile);
|
|
189
202
|
validateBindings(config.state, "state", configFile);
|
|
203
|
+
validateBindings(config.fs, "fs", configFile);
|
|
190
204
|
validateBindings(config.workflow, "workflow", configFile);
|
|
191
205
|
if (config.ai !== void 0 && typeof config.ai !== "boolean") {
|
|
192
206
|
throw new Error(`'ai' in ${configFile} must be a boolean`);
|
|
@@ -263,7 +277,50 @@ function readAndValidatePloyConfigSync(projectDir, configPath, validationOptions
|
|
|
263
277
|
return validatePloyConfig(config, configFile, validationOptions);
|
|
264
278
|
}
|
|
265
279
|
function hasBindings(config) {
|
|
266
|
-
return !!(config.db ?? config.queue ?? config.cache ?? config.state ?? config.workflow ?? config.ai ?? config.auth);
|
|
280
|
+
return !!(config.env ?? config.db ?? config.queue ?? config.cache ?? config.state ?? config.fs ?? config.workflow ?? config.ai ?? config.auth);
|
|
281
|
+
}
|
|
282
|
+
function parseDotEnv(content) {
|
|
283
|
+
const result = {};
|
|
284
|
+
for (const line of content.split("\n")) {
|
|
285
|
+
const trimmed = line.trim();
|
|
286
|
+
if (!trimmed || trimmed.startsWith("#")) {
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
const eqIndex = trimmed.indexOf("=");
|
|
290
|
+
if (eqIndex === -1) {
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
const key = trimmed.slice(0, eqIndex).trim();
|
|
294
|
+
let value = trimmed.slice(eqIndex + 1).trim();
|
|
295
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
296
|
+
value = value.slice(1, -1);
|
|
297
|
+
}
|
|
298
|
+
result[key] = value;
|
|
299
|
+
}
|
|
300
|
+
return result;
|
|
301
|
+
}
|
|
302
|
+
function loadDotEnvSync(projectDir) {
|
|
303
|
+
const envPath = join(projectDir, ".env");
|
|
304
|
+
if (!existsSync(envPath)) {
|
|
305
|
+
return {};
|
|
306
|
+
}
|
|
307
|
+
const content = readFileSync(envPath, "utf-8");
|
|
308
|
+
return parseDotEnv(content);
|
|
309
|
+
}
|
|
310
|
+
function resolveEnvVars(configEnv, dotEnv, processEnv) {
|
|
311
|
+
const result = {};
|
|
312
|
+
for (const [key, value] of Object.entries(configEnv)) {
|
|
313
|
+
if (value.startsWith("$")) {
|
|
314
|
+
const refName = value.slice(1);
|
|
315
|
+
const resolved = dotEnv[refName] ?? processEnv[refName];
|
|
316
|
+
if (resolved !== void 0) {
|
|
317
|
+
result[key] = resolved;
|
|
318
|
+
}
|
|
319
|
+
} else {
|
|
320
|
+
result[key] = value;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
return result;
|
|
267
324
|
}
|
|
268
325
|
function getWorkerEntryPoint(projectDir, config) {
|
|
269
326
|
if (config.main) {
|
|
@@ -306,10 +363,13 @@ __export(cli_exports, {
|
|
|
306
363
|
getWorkerEntryPoint: () => getWorkerEntryPoint,
|
|
307
364
|
hasBindings: () => hasBindings,
|
|
308
365
|
isPnpmWorkspace: () => isPnpmWorkspace,
|
|
366
|
+
loadDotEnvSync: () => loadDotEnvSync,
|
|
367
|
+
parseDotEnv: () => parseDotEnv,
|
|
309
368
|
readAndValidatePloyConfig: () => readAndValidatePloyConfig,
|
|
310
369
|
readAndValidatePloyConfigSync: () => readAndValidatePloyConfigSync,
|
|
311
370
|
readPloyConfig: () => readPloyConfig,
|
|
312
371
|
readPloyConfigSync: () => readPloyConfigSync,
|
|
372
|
+
resolveEnvVars: () => resolveEnvVars,
|
|
313
373
|
validatePloyConfig: () => validatePloyConfig
|
|
314
374
|
});
|
|
315
375
|
var init_cli = __esm({
|
|
@@ -698,6 +758,124 @@ var init_db_runtime = __esm({
|
|
|
698
758
|
}
|
|
699
759
|
});
|
|
700
760
|
|
|
761
|
+
// ../emulator/dist/runtime/fs-runtime.js
|
|
762
|
+
var FS_RUNTIME_CODE;
|
|
763
|
+
var init_fs_runtime = __esm({
|
|
764
|
+
"../emulator/dist/runtime/fs-runtime.js"() {
|
|
765
|
+
FS_RUNTIME_CODE = `
|
|
766
|
+
interface FileStoragePutOptions {
|
|
767
|
+
contentType?: string;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
interface FileStorageListOptions {
|
|
771
|
+
prefix?: string;
|
|
772
|
+
limit?: number;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
interface FileStorageObject {
|
|
776
|
+
body: string;
|
|
777
|
+
contentType: string;
|
|
778
|
+
size: number;
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
interface FileStorageKey {
|
|
782
|
+
key: string;
|
|
783
|
+
size: number;
|
|
784
|
+
contentType: string;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
interface FileStorageListResult {
|
|
788
|
+
keys: FileStorageKey[];
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
interface FileStorageBinding {
|
|
792
|
+
put: (key: string, value: string, options?: FileStoragePutOptions) => Promise<void>;
|
|
793
|
+
get: (key: string) => Promise<FileStorageObject | null>;
|
|
794
|
+
delete: (key: string) => Promise<void>;
|
|
795
|
+
list: (options?: FileStorageListOptions) => Promise<FileStorageListResult>;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
export function initializeFileStorage(fsName: string, serviceUrl: string): FileStorageBinding {
|
|
799
|
+
return {
|
|
800
|
+
async put(key: string, value: string, options?: FileStoragePutOptions): Promise<void> {
|
|
801
|
+
const response = await fetch(serviceUrl + "/fs/put", {
|
|
802
|
+
method: "POST",
|
|
803
|
+
headers: { "Content-Type": "application/json" },
|
|
804
|
+
body: JSON.stringify({
|
|
805
|
+
fsName,
|
|
806
|
+
key,
|
|
807
|
+
value,
|
|
808
|
+
contentType: options?.contentType || "application/octet-stream",
|
|
809
|
+
}),
|
|
810
|
+
});
|
|
811
|
+
|
|
812
|
+
if (!response.ok) {
|
|
813
|
+
const errorText = await response.text();
|
|
814
|
+
throw new Error("File storage put failed: " + errorText);
|
|
815
|
+
}
|
|
816
|
+
},
|
|
817
|
+
|
|
818
|
+
async get(key: string): Promise<FileStorageObject | null> {
|
|
819
|
+
const response = await fetch(serviceUrl + "/fs/get", {
|
|
820
|
+
method: "POST",
|
|
821
|
+
headers: { "Content-Type": "application/json" },
|
|
822
|
+
body: JSON.stringify({ fsName, key }),
|
|
823
|
+
});
|
|
824
|
+
|
|
825
|
+
if (!response.ok) {
|
|
826
|
+
const errorText = await response.text();
|
|
827
|
+
throw new Error("File storage get failed: " + errorText);
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
const result = await response.json();
|
|
831
|
+
if (result.found === false) {
|
|
832
|
+
return null;
|
|
833
|
+
}
|
|
834
|
+
return {
|
|
835
|
+
body: result.body,
|
|
836
|
+
contentType: result.contentType,
|
|
837
|
+
size: result.size,
|
|
838
|
+
};
|
|
839
|
+
},
|
|
840
|
+
|
|
841
|
+
async delete(key: string): Promise<void> {
|
|
842
|
+
const response = await fetch(serviceUrl + "/fs/delete", {
|
|
843
|
+
method: "POST",
|
|
844
|
+
headers: { "Content-Type": "application/json" },
|
|
845
|
+
body: JSON.stringify({ fsName, key }),
|
|
846
|
+
});
|
|
847
|
+
|
|
848
|
+
if (!response.ok) {
|
|
849
|
+
const errorText = await response.text();
|
|
850
|
+
throw new Error("File storage delete failed: " + errorText);
|
|
851
|
+
}
|
|
852
|
+
},
|
|
853
|
+
|
|
854
|
+
async list(options?: FileStorageListOptions): Promise<FileStorageListResult> {
|
|
855
|
+
const response = await fetch(serviceUrl + "/fs/list", {
|
|
856
|
+
method: "POST",
|
|
857
|
+
headers: { "Content-Type": "application/json" },
|
|
858
|
+
body: JSON.stringify({
|
|
859
|
+
fsName,
|
|
860
|
+
prefix: options?.prefix,
|
|
861
|
+
limit: options?.limit,
|
|
862
|
+
}),
|
|
863
|
+
});
|
|
864
|
+
|
|
865
|
+
if (!response.ok) {
|
|
866
|
+
const errorText = await response.text();
|
|
867
|
+
throw new Error("File storage list failed: " + errorText);
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
const result = await response.json();
|
|
871
|
+
return { keys: result.keys };
|
|
872
|
+
},
|
|
873
|
+
};
|
|
874
|
+
}
|
|
875
|
+
`;
|
|
876
|
+
}
|
|
877
|
+
});
|
|
878
|
+
|
|
701
879
|
// ../emulator/dist/runtime/queue-runtime.js
|
|
702
880
|
var QUEUE_RUNTIME_CODE;
|
|
703
881
|
var init_queue_runtime = __esm({
|
|
@@ -1035,7 +1213,7 @@ export async function executeWorkflow<TInput, TOutput, TEnv>(
|
|
|
1035
1213
|
`;
|
|
1036
1214
|
}
|
|
1037
1215
|
});
|
|
1038
|
-
function generateWrapperCode(config, mockServiceUrl) {
|
|
1216
|
+
function generateWrapperCode(config, mockServiceUrl, envVars) {
|
|
1039
1217
|
const imports = [];
|
|
1040
1218
|
const bindings = [];
|
|
1041
1219
|
if (config.db) {
|
|
@@ -1062,6 +1240,12 @@ function generateWrapperCode(config, mockServiceUrl) {
|
|
|
1062
1240
|
bindings.push(` ${bindingName}: initializeState("${stateName}", "${mockServiceUrl}"),`);
|
|
1063
1241
|
}
|
|
1064
1242
|
}
|
|
1243
|
+
if (config.fs) {
|
|
1244
|
+
imports.push('import { initializeFileStorage } from "__ploy_fs_runtime__";');
|
|
1245
|
+
for (const [bindingName, fsName] of Object.entries(config.fs)) {
|
|
1246
|
+
bindings.push(` ${bindingName}: initializeFileStorage("${fsName}", "${mockServiceUrl}"),`);
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1065
1249
|
if (config.workflow) {
|
|
1066
1250
|
imports.push('import { initializeWorkflow, createStepContext, executeWorkflow } from "__ploy_workflow_runtime__";');
|
|
1067
1251
|
for (const [bindingName, workflowName] of Object.entries(config.workflow)) {
|
|
@@ -1125,6 +1309,11 @@ function generateWrapperCode(config, mockServiceUrl) {
|
|
|
1125
1309
|
}
|
|
1126
1310
|
}
|
|
1127
1311
|
}` : "";
|
|
1312
|
+
const envVarsEntries = envVars && Object.keys(envVars).length > 0 ? Object.entries(envVars).map(([key, value]) => ` ${JSON.stringify(key)}: ${JSON.stringify(value)}`).join(",\n") : "";
|
|
1313
|
+
const envVarsCode = envVarsEntries ? `
|
|
1314
|
+
injectedEnv.vars = {
|
|
1315
|
+
${envVarsEntries}
|
|
1316
|
+
};` : "";
|
|
1128
1317
|
return `${imports.join("\n")}
|
|
1129
1318
|
|
|
1130
1319
|
const ployBindings = {
|
|
@@ -1133,7 +1322,7 @@ ${bindings.join("\n")}
|
|
|
1133
1322
|
|
|
1134
1323
|
export default {
|
|
1135
1324
|
async fetch(request, env, ctx) {
|
|
1136
|
-
const injectedEnv = { ...env, ...ployBindings }
|
|
1325
|
+
const injectedEnv = { ...env, ...ployBindings };${envVarsCode}
|
|
1137
1326
|
${workflowHandlerCode}
|
|
1138
1327
|
${queueHandlerCode}
|
|
1139
1328
|
|
|
@@ -1146,7 +1335,7 @@ export default {
|
|
|
1146
1335
|
|
|
1147
1336
|
async scheduled(event, env, ctx) {
|
|
1148
1337
|
if (userWorker.scheduled) {
|
|
1149
|
-
const injectedEnv = { ...env, ...ployBindings }
|
|
1338
|
+
const injectedEnv = { ...env, ...ployBindings };${envVarsCode}
|
|
1150
1339
|
return userWorker.scheduled(event, injectedEnv, ctx);
|
|
1151
1340
|
}
|
|
1152
1341
|
}
|
|
@@ -1173,6 +1362,10 @@ function createRuntimePlugin(_config) {
|
|
|
1173
1362
|
path: "__ploy_state_runtime__",
|
|
1174
1363
|
namespace: "ploy-runtime"
|
|
1175
1364
|
}));
|
|
1365
|
+
build2.onResolve({ filter: /^__ploy_fs_runtime__$/ }, () => ({
|
|
1366
|
+
path: "__ploy_fs_runtime__",
|
|
1367
|
+
namespace: "ploy-runtime"
|
|
1368
|
+
}));
|
|
1176
1369
|
build2.onResolve({ filter: /^__ploy_workflow_runtime__$/ }, () => ({
|
|
1177
1370
|
path: "__ploy_workflow_runtime__",
|
|
1178
1371
|
namespace: "ploy-runtime"
|
|
@@ -1193,6 +1386,10 @@ function createRuntimePlugin(_config) {
|
|
|
1193
1386
|
contents: STATE_RUNTIME_CODE,
|
|
1194
1387
|
loader: "ts"
|
|
1195
1388
|
}));
|
|
1389
|
+
build2.onLoad({ filter: /^__ploy_fs_runtime__$/, namespace: "ploy-runtime" }, () => ({
|
|
1390
|
+
contents: FS_RUNTIME_CODE,
|
|
1391
|
+
loader: "ts"
|
|
1392
|
+
}));
|
|
1196
1393
|
build2.onLoad({ filter: /^__ploy_workflow_runtime__$/, namespace: "ploy-runtime" }, () => ({
|
|
1197
1394
|
contents: WORKFLOW_RUNTIME_CODE,
|
|
1198
1395
|
loader: "ts"
|
|
@@ -1201,8 +1398,8 @@ function createRuntimePlugin(_config) {
|
|
|
1201
1398
|
};
|
|
1202
1399
|
}
|
|
1203
1400
|
async function bundleWorker(options) {
|
|
1204
|
-
const { projectDir, tempDir, entryPoint, config, mockServiceUrl } = options;
|
|
1205
|
-
const wrapperCode = generateWrapperCode(config, mockServiceUrl);
|
|
1401
|
+
const { projectDir, tempDir, entryPoint, config, mockServiceUrl, envVars } = options;
|
|
1402
|
+
const wrapperCode = generateWrapperCode(config, mockServiceUrl, envVars);
|
|
1206
1403
|
const wrapperPath = join(tempDir, "wrapper.ts");
|
|
1207
1404
|
writeFileSync(wrapperPath, wrapperCode);
|
|
1208
1405
|
const bundlePath = join(tempDir, "worker.bundle.js");
|
|
@@ -1235,6 +1432,7 @@ var init_bundler = __esm({
|
|
|
1235
1432
|
"../emulator/dist/bundler/bundler.js"() {
|
|
1236
1433
|
init_cache_runtime();
|
|
1237
1434
|
init_db_runtime();
|
|
1435
|
+
init_fs_runtime();
|
|
1238
1436
|
init_queue_runtime();
|
|
1239
1437
|
init_state_runtime();
|
|
1240
1438
|
init_workflow_runtime();
|
|
@@ -1412,6 +1610,20 @@ var init_watcher = __esm({
|
|
|
1412
1610
|
}
|
|
1413
1611
|
});
|
|
1414
1612
|
|
|
1613
|
+
// ../emulator/dist/config/env.js
|
|
1614
|
+
function resolveEnvVars2(projectDir, config) {
|
|
1615
|
+
if (!config.env) {
|
|
1616
|
+
return {};
|
|
1617
|
+
}
|
|
1618
|
+
const dotEnv = loadDotEnvSync(projectDir);
|
|
1619
|
+
return resolveEnvVars(config.env, dotEnv, process.env);
|
|
1620
|
+
}
|
|
1621
|
+
var init_env = __esm({
|
|
1622
|
+
"../emulator/dist/config/env.js"() {
|
|
1623
|
+
init_cli();
|
|
1624
|
+
}
|
|
1625
|
+
});
|
|
1626
|
+
|
|
1415
1627
|
// ../emulator/dist/config/ploy-config.js
|
|
1416
1628
|
function readPloyConfig2(projectDir, configPath) {
|
|
1417
1629
|
const config = readPloyConfigSync(projectDir, configPath);
|
|
@@ -1481,6 +1693,34 @@ var init_workerd_config = __esm({
|
|
|
1481
1693
|
"../emulator/dist/config/workerd-config.js"() {
|
|
1482
1694
|
}
|
|
1483
1695
|
});
|
|
1696
|
+
function getProjectHash(projectDir) {
|
|
1697
|
+
return createHash("sha256").update(projectDir).digest("hex").slice(0, 12);
|
|
1698
|
+
}
|
|
1699
|
+
function getTempDir(projectDir) {
|
|
1700
|
+
const hash = getProjectHash(projectDir);
|
|
1701
|
+
return join(tmpdir(), `ploy-emulator-${hash}`);
|
|
1702
|
+
}
|
|
1703
|
+
function getDataDir(projectDir) {
|
|
1704
|
+
return join(projectDir, ".ploy");
|
|
1705
|
+
}
|
|
1706
|
+
function ensureDir(dir) {
|
|
1707
|
+
mkdirSync(dir, { recursive: true });
|
|
1708
|
+
}
|
|
1709
|
+
function ensureTempDir(projectDir) {
|
|
1710
|
+
const tempDir = getTempDir(projectDir);
|
|
1711
|
+
ensureDir(tempDir);
|
|
1712
|
+
return tempDir;
|
|
1713
|
+
}
|
|
1714
|
+
function ensureDataDir(projectDir) {
|
|
1715
|
+
const dataDir = getDataDir(projectDir);
|
|
1716
|
+
ensureDir(dataDir);
|
|
1717
|
+
ensureDir(join(dataDir, "db"));
|
|
1718
|
+
return dataDir;
|
|
1719
|
+
}
|
|
1720
|
+
var init_paths = __esm({
|
|
1721
|
+
"../emulator/dist/utils/paths.js"() {
|
|
1722
|
+
}
|
|
1723
|
+
});
|
|
1484
1724
|
function generateId() {
|
|
1485
1725
|
return randomBytes(16).toString("hex");
|
|
1486
1726
|
}
|
|
@@ -1793,6 +2033,7 @@ function createDashboardRoutes(app, dbManager2, config) {
|
|
|
1793
2033
|
queue: config.queue,
|
|
1794
2034
|
cache: config.cache,
|
|
1795
2035
|
state: config.state,
|
|
2036
|
+
fs: config.fs,
|
|
1796
2037
|
workflow: config.workflow,
|
|
1797
2038
|
auth: config.auth
|
|
1798
2039
|
});
|
|
@@ -2287,6 +2528,37 @@ function createDashboardRoutes(app, dbManager2, config) {
|
|
|
2287
2528
|
return c.json({ error: err instanceof Error ? err.message : String(err) }, 500);
|
|
2288
2529
|
}
|
|
2289
2530
|
});
|
|
2531
|
+
app.get("/api/fs/:binding/entries", (c) => {
|
|
2532
|
+
const binding = c.req.param("binding");
|
|
2533
|
+
const fsName = config.fs?.[binding];
|
|
2534
|
+
const limit = parseInt(c.req.query("limit") || "20", 10);
|
|
2535
|
+
const offset = parseInt(c.req.query("offset") || "0", 10);
|
|
2536
|
+
if (!fsName) {
|
|
2537
|
+
return c.json({ error: "File storage binding not found" }, 404);
|
|
2538
|
+
}
|
|
2539
|
+
try {
|
|
2540
|
+
const db = dbManager2.emulatorDb;
|
|
2541
|
+
const total = db.prepare(`SELECT COUNT(*) as count FROM fs_entries WHERE fs_name = ?`).get(fsName).count;
|
|
2542
|
+
const entries = db.prepare(`SELECT key, size, content_type, created_at
|
|
2543
|
+
FROM fs_entries
|
|
2544
|
+
WHERE fs_name = ?
|
|
2545
|
+
ORDER BY key ASC
|
|
2546
|
+
LIMIT ? OFFSET ?`).all(fsName, limit, offset);
|
|
2547
|
+
return c.json({
|
|
2548
|
+
entries: entries.map((e) => ({
|
|
2549
|
+
key: e.key,
|
|
2550
|
+
size: e.size,
|
|
2551
|
+
contentType: e.content_type,
|
|
2552
|
+
createdAt: new Date(e.created_at * 1e3).toISOString()
|
|
2553
|
+
})),
|
|
2554
|
+
total,
|
|
2555
|
+
limit,
|
|
2556
|
+
offset
|
|
2557
|
+
});
|
|
2558
|
+
} catch (err) {
|
|
2559
|
+
return c.json({ error: err instanceof Error ? err.message : String(err) }, 500);
|
|
2560
|
+
}
|
|
2561
|
+
});
|
|
2290
2562
|
if (hasDashboard) {
|
|
2291
2563
|
app.get("/assets/*", (c) => {
|
|
2292
2564
|
const path = c.req.path;
|
|
@@ -2437,6 +2709,125 @@ var init_db_service = __esm({
|
|
|
2437
2709
|
"../emulator/dist/services/db-service.js"() {
|
|
2438
2710
|
}
|
|
2439
2711
|
});
|
|
2712
|
+
function createFsHandlers(db, dataDir) {
|
|
2713
|
+
const fsBaseDir = join(dataDir, "fs");
|
|
2714
|
+
mkdirSync(fsBaseDir, { recursive: true });
|
|
2715
|
+
function getFsDir(fsName) {
|
|
2716
|
+
const dir = join(fsBaseDir, fsName);
|
|
2717
|
+
mkdirSync(dir, { recursive: true });
|
|
2718
|
+
return dir;
|
|
2719
|
+
}
|
|
2720
|
+
function encodedKeyPath(fsDir, key) {
|
|
2721
|
+
return join(fsDir, encodeURIComponent(key));
|
|
2722
|
+
}
|
|
2723
|
+
const putHandler = async (c) => {
|
|
2724
|
+
try {
|
|
2725
|
+
const body = await c.req.json();
|
|
2726
|
+
const { fsName, key, value, contentType } = body;
|
|
2727
|
+
if (!fsName || !key) {
|
|
2728
|
+
return c.json({ error: "Missing required fields: fsName, key, value" }, 400);
|
|
2729
|
+
}
|
|
2730
|
+
const fsDir = getFsDir(fsName);
|
|
2731
|
+
const filePath = encodedKeyPath(fsDir, key);
|
|
2732
|
+
writeFileSync(filePath, value, "utf-8");
|
|
2733
|
+
const size = Buffer.byteLength(value, "utf-8");
|
|
2734
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
2735
|
+
db.prepare(`INSERT OR REPLACE INTO fs_entries (fs_name, key, content_type, size, created_at) VALUES (?, ?, ?, ?, ?)`).run(fsName, key, contentType ?? "application/octet-stream", size, now);
|
|
2736
|
+
return c.json({ success: true });
|
|
2737
|
+
} catch (err) {
|
|
2738
|
+
error(`[fs-service] put error: ${err instanceof Error ? err.message : String(err)}`);
|
|
2739
|
+
return c.json({
|
|
2740
|
+
error: `put failed: ${err instanceof Error ? err.message : String(err)}`
|
|
2741
|
+
}, 500);
|
|
2742
|
+
}
|
|
2743
|
+
};
|
|
2744
|
+
const getHandler = async (c) => {
|
|
2745
|
+
try {
|
|
2746
|
+
const body = await c.req.json();
|
|
2747
|
+
const { fsName, key } = body;
|
|
2748
|
+
if (!fsName || !key) {
|
|
2749
|
+
return c.json({ error: "Missing required fields: fsName, key" }, 400);
|
|
2750
|
+
}
|
|
2751
|
+
const row = db.prepare(`SELECT content_type, size FROM fs_entries WHERE fs_name = ? AND key = ?`).get(fsName, key);
|
|
2752
|
+
if (!row) {
|
|
2753
|
+
return c.json({ found: false });
|
|
2754
|
+
}
|
|
2755
|
+
const fsDir = getFsDir(fsName);
|
|
2756
|
+
const filePath = encodedKeyPath(fsDir, key);
|
|
2757
|
+
if (!existsSync(filePath)) {
|
|
2758
|
+
db.prepare(`DELETE FROM fs_entries WHERE fs_name = ? AND key = ?`).run(fsName, key);
|
|
2759
|
+
return c.json({ found: false });
|
|
2760
|
+
}
|
|
2761
|
+
const fileContent = readFileSync(filePath, "utf-8");
|
|
2762
|
+
return c.json({
|
|
2763
|
+
found: true,
|
|
2764
|
+
body: fileContent,
|
|
2765
|
+
contentType: row.content_type,
|
|
2766
|
+
size: row.size
|
|
2767
|
+
});
|
|
2768
|
+
} catch (err) {
|
|
2769
|
+
error(`[fs-service] get error: ${err instanceof Error ? err.message : String(err)}`);
|
|
2770
|
+
return c.json({
|
|
2771
|
+
error: `get failed: ${err instanceof Error ? err.message : String(err)}`
|
|
2772
|
+
}, 500);
|
|
2773
|
+
}
|
|
2774
|
+
};
|
|
2775
|
+
const deleteHandler = async (c) => {
|
|
2776
|
+
try {
|
|
2777
|
+
const body = await c.req.json();
|
|
2778
|
+
const { fsName, key } = body;
|
|
2779
|
+
if (!fsName || !key) {
|
|
2780
|
+
return c.json({ error: "Missing required fields: fsName, key" }, 400);
|
|
2781
|
+
}
|
|
2782
|
+
db.prepare(`DELETE FROM fs_entries WHERE fs_name = ? AND key = ?`).run(fsName, key);
|
|
2783
|
+
const fsDir = getFsDir(fsName);
|
|
2784
|
+
const filePath = encodedKeyPath(fsDir, key);
|
|
2785
|
+
if (existsSync(filePath)) {
|
|
2786
|
+
unlinkSync(filePath);
|
|
2787
|
+
}
|
|
2788
|
+
return c.json({ success: true });
|
|
2789
|
+
} catch (err) {
|
|
2790
|
+
error(`[fs-service] delete error: ${err instanceof Error ? err.message : String(err)}`);
|
|
2791
|
+
return c.json({
|
|
2792
|
+
error: `delete failed: ${err instanceof Error ? err.message : String(err)}`
|
|
2793
|
+
}, 500);
|
|
2794
|
+
}
|
|
2795
|
+
};
|
|
2796
|
+
const listHandler = async (c) => {
|
|
2797
|
+
try {
|
|
2798
|
+
const body = await c.req.json();
|
|
2799
|
+
const { fsName, prefix, limit } = body;
|
|
2800
|
+
if (!fsName) {
|
|
2801
|
+
return c.json({ error: "Missing required field: fsName" }, 400);
|
|
2802
|
+
}
|
|
2803
|
+
const effectiveLimit = limit ?? 1e3;
|
|
2804
|
+
const keys = prefix ? db.prepare(`SELECT key, size, content_type FROM fs_entries WHERE fs_name = ? AND key LIKE ? ORDER BY key ASC LIMIT ?`).all(fsName, `${prefix}%`, effectiveLimit) : db.prepare(`SELECT key, size, content_type FROM fs_entries WHERE fs_name = ? ORDER BY key ASC LIMIT ?`).all(fsName, effectiveLimit);
|
|
2805
|
+
return c.json({
|
|
2806
|
+
keys: keys.map((k) => ({
|
|
2807
|
+
key: k.key,
|
|
2808
|
+
size: k.size,
|
|
2809
|
+
contentType: k.content_type
|
|
2810
|
+
}))
|
|
2811
|
+
});
|
|
2812
|
+
} catch (err) {
|
|
2813
|
+
error(`[fs-service] list error: ${err instanceof Error ? err.message : String(err)}`);
|
|
2814
|
+
return c.json({
|
|
2815
|
+
error: `list failed: ${err instanceof Error ? err.message : String(err)}`
|
|
2816
|
+
}, 500);
|
|
2817
|
+
}
|
|
2818
|
+
};
|
|
2819
|
+
return {
|
|
2820
|
+
putHandler,
|
|
2821
|
+
getHandler,
|
|
2822
|
+
deleteHandler,
|
|
2823
|
+
listHandler
|
|
2824
|
+
};
|
|
2825
|
+
}
|
|
2826
|
+
var init_fs_service = __esm({
|
|
2827
|
+
"../emulator/dist/services/fs-service.js"() {
|
|
2828
|
+
init_logger();
|
|
2829
|
+
}
|
|
2830
|
+
});
|
|
2440
2831
|
function createQueueHandlers(db) {
|
|
2441
2832
|
const sendHandler = async (c) => {
|
|
2442
2833
|
try {
|
|
@@ -2870,6 +3261,14 @@ async function startMockServer(dbManager2, config, options = {}) {
|
|
|
2870
3261
|
app.post("/state/set", stateHandlers.setHandler);
|
|
2871
3262
|
app.post("/state/delete", stateHandlers.deleteHandler);
|
|
2872
3263
|
}
|
|
3264
|
+
if (config.fs) {
|
|
3265
|
+
const dataDir = getDataDir(process.cwd());
|
|
3266
|
+
const fsHandlers = createFsHandlers(dbManager2.emulatorDb, dataDir);
|
|
3267
|
+
app.post("/fs/put", fsHandlers.putHandler);
|
|
3268
|
+
app.post("/fs/get", fsHandlers.getHandler);
|
|
3269
|
+
app.post("/fs/delete", fsHandlers.deleteHandler);
|
|
3270
|
+
app.post("/fs/list", fsHandlers.listHandler);
|
|
3271
|
+
}
|
|
2873
3272
|
if (config.auth) {
|
|
2874
3273
|
const authHandlers = createAuthHandlers(dbManager2.emulatorDb);
|
|
2875
3274
|
app.post("/auth/signup", authHandlers.signupHandler);
|
|
@@ -2901,44 +3300,18 @@ async function startMockServer(dbManager2, config, options = {}) {
|
|
|
2901
3300
|
var DEFAULT_MOCK_SERVER_PORT;
|
|
2902
3301
|
var init_mock_server = __esm({
|
|
2903
3302
|
"../emulator/dist/services/mock-server.js"() {
|
|
3303
|
+
init_paths();
|
|
2904
3304
|
init_auth_service();
|
|
2905
3305
|
init_cache_service();
|
|
2906
3306
|
init_dashboard_routes();
|
|
2907
3307
|
init_db_service();
|
|
3308
|
+
init_fs_service();
|
|
2908
3309
|
init_queue_service();
|
|
2909
3310
|
init_state_service();
|
|
2910
3311
|
init_workflow_service();
|
|
2911
3312
|
DEFAULT_MOCK_SERVER_PORT = 4003;
|
|
2912
3313
|
}
|
|
2913
3314
|
});
|
|
2914
|
-
function getProjectHash(projectDir) {
|
|
2915
|
-
return createHash("sha256").update(projectDir).digest("hex").slice(0, 12);
|
|
2916
|
-
}
|
|
2917
|
-
function getTempDir(projectDir) {
|
|
2918
|
-
const hash = getProjectHash(projectDir);
|
|
2919
|
-
return join(tmpdir(), `ploy-emulator-${hash}`);
|
|
2920
|
-
}
|
|
2921
|
-
function getDataDir(projectDir) {
|
|
2922
|
-
return join(projectDir, ".ploy");
|
|
2923
|
-
}
|
|
2924
|
-
function ensureDir(dir) {
|
|
2925
|
-
mkdirSync(dir, { recursive: true });
|
|
2926
|
-
}
|
|
2927
|
-
function ensureTempDir(projectDir) {
|
|
2928
|
-
const tempDir = getTempDir(projectDir);
|
|
2929
|
-
ensureDir(tempDir);
|
|
2930
|
-
return tempDir;
|
|
2931
|
-
}
|
|
2932
|
-
function ensureDataDir(projectDir) {
|
|
2933
|
-
const dataDir = getDataDir(projectDir);
|
|
2934
|
-
ensureDir(dataDir);
|
|
2935
|
-
ensureDir(join(dataDir, "db"));
|
|
2936
|
-
return dataDir;
|
|
2937
|
-
}
|
|
2938
|
-
var init_paths = __esm({
|
|
2939
|
-
"../emulator/dist/utils/paths.js"() {
|
|
2940
|
-
}
|
|
2941
|
-
});
|
|
2942
3315
|
function initializeDatabases(projectDir) {
|
|
2943
3316
|
const dataDir = ensureDataDir(projectDir);
|
|
2944
3317
|
const d1Databases = /* @__PURE__ */ new Map();
|
|
@@ -3080,6 +3453,16 @@ CREATE TABLE IF NOT EXISTS state_entries (
|
|
|
3080
3453
|
value TEXT NOT NULL,
|
|
3081
3454
|
PRIMARY KEY (state_name, key)
|
|
3082
3455
|
);
|
|
3456
|
+
|
|
3457
|
+
-- File storage entries table (metadata for stored files)
|
|
3458
|
+
CREATE TABLE IF NOT EXISTS fs_entries (
|
|
3459
|
+
fs_name TEXT NOT NULL,
|
|
3460
|
+
key TEXT NOT NULL,
|
|
3461
|
+
content_type TEXT NOT NULL DEFAULT 'application/octet-stream',
|
|
3462
|
+
size INTEGER NOT NULL DEFAULT 0,
|
|
3463
|
+
created_at INTEGER DEFAULT (strftime('%s', 'now')),
|
|
3464
|
+
PRIMARY KEY (fs_name, key)
|
|
3465
|
+
);
|
|
3083
3466
|
`;
|
|
3084
3467
|
}
|
|
3085
3468
|
});
|
|
@@ -3093,6 +3476,7 @@ var init_emulator = __esm({
|
|
|
3093
3476
|
"../emulator/dist/emulator.js"() {
|
|
3094
3477
|
init_bundler();
|
|
3095
3478
|
init_watcher();
|
|
3479
|
+
init_env();
|
|
3096
3480
|
init_ploy_config2();
|
|
3097
3481
|
init_workerd_config();
|
|
3098
3482
|
init_mock_server();
|
|
@@ -3110,6 +3494,7 @@ var init_emulator = __esm({
|
|
|
3110
3494
|
workerdProcess = null;
|
|
3111
3495
|
fileWatcher = null;
|
|
3112
3496
|
queueProcessor = null;
|
|
3497
|
+
resolvedEnvVars = {};
|
|
3113
3498
|
constructor(options = {}) {
|
|
3114
3499
|
const port = options.port ?? 8787;
|
|
3115
3500
|
this.options = {
|
|
@@ -3127,6 +3512,10 @@ var init_emulator = __esm({
|
|
|
3127
3512
|
try {
|
|
3128
3513
|
this.config = readPloyConfig2(this.projectDir, this.options.configPath);
|
|
3129
3514
|
debug(`Loaded config: ${JSON.stringify(this.config)}`, this.options.verbose);
|
|
3515
|
+
this.resolvedEnvVars = resolveEnvVars2(this.projectDir, this.config);
|
|
3516
|
+
if (Object.keys(this.resolvedEnvVars).length > 0) {
|
|
3517
|
+
debug(`Resolved env vars: ${Object.keys(this.resolvedEnvVars).join(", ")}`, this.options.verbose);
|
|
3518
|
+
}
|
|
3130
3519
|
this.tempDir = ensureTempDir(this.projectDir);
|
|
3131
3520
|
ensureDataDir(this.projectDir);
|
|
3132
3521
|
debug(`Temp dir: ${this.tempDir}`, this.options.verbose);
|
|
@@ -3174,6 +3563,9 @@ var init_emulator = __esm({
|
|
|
3174
3563
|
}
|
|
3175
3564
|
success(`Emulator running at http://${this.options.host}:${String(this.options.port)}`);
|
|
3176
3565
|
log(` Dashboard: http://${this.options.host}:${String(this.mockServer.port)}`);
|
|
3566
|
+
if (Object.keys(this.resolvedEnvVars).length > 0) {
|
|
3567
|
+
log(` Env vars: ${Object.keys(this.resolvedEnvVars).join(", ")}`);
|
|
3568
|
+
}
|
|
3177
3569
|
if (this.config.db) {
|
|
3178
3570
|
log(` DB bindings: ${Object.keys(this.config.db).join(", ")}`);
|
|
3179
3571
|
}
|
|
@@ -3183,6 +3575,9 @@ var init_emulator = __esm({
|
|
|
3183
3575
|
if (this.config.cache) {
|
|
3184
3576
|
log(` Cache bindings: ${Object.keys(this.config.cache).join(", ")}`);
|
|
3185
3577
|
}
|
|
3578
|
+
if (this.config.fs) {
|
|
3579
|
+
log(` File Storage bindings: ${Object.keys(this.config.fs).join(", ")}`);
|
|
3580
|
+
}
|
|
3186
3581
|
if (this.config.workflow) {
|
|
3187
3582
|
log(` Workflow bindings: ${Object.keys(this.config.workflow).join(", ")}`);
|
|
3188
3583
|
}
|
|
@@ -3202,7 +3597,8 @@ var init_emulator = __esm({
|
|
|
3202
3597
|
tempDir: this.tempDir,
|
|
3203
3598
|
entryPoint,
|
|
3204
3599
|
config: this.config,
|
|
3205
|
-
mockServiceUrl
|
|
3600
|
+
mockServiceUrl,
|
|
3601
|
+
envVars: this.resolvedEnvVars
|
|
3206
3602
|
});
|
|
3207
3603
|
}
|
|
3208
3604
|
async startWorkerd(configPath) {
|
|
@@ -3228,9 +3624,7 @@ var init_emulator = __esm({
|
|
|
3228
3624
|
let stderrOutput = "";
|
|
3229
3625
|
this.workerdProcess.stdout?.on("data", (data) => {
|
|
3230
3626
|
const output = data.toString();
|
|
3231
|
-
|
|
3232
|
-
process.stdout.write(output);
|
|
3233
|
-
}
|
|
3627
|
+
log(`[workerd stdout] ${output.trim()}`);
|
|
3234
3628
|
if (!started && (output.includes("Listening") || output.includes("running"))) {
|
|
3235
3629
|
started = true;
|
|
3236
3630
|
resolve(void 0);
|
|
@@ -5166,6 +5560,12 @@ async function updateTsConfigInclude(cwd, outputPath) {
|
|
|
5166
5560
|
function generateEnvType(config) {
|
|
5167
5561
|
const imports = [];
|
|
5168
5562
|
const properties = [];
|
|
5563
|
+
if (config.env && Object.keys(config.env).length > 0) {
|
|
5564
|
+
const varProps = Object.keys(config.env).map((key) => ` ${key}: string;`).join("\n");
|
|
5565
|
+
properties.push(` vars: {
|
|
5566
|
+
${varProps}
|
|
5567
|
+
};`);
|
|
5568
|
+
}
|
|
5169
5569
|
if (config.ai) {
|
|
5170
5570
|
properties.push(" AI_URL: string;");
|
|
5171
5571
|
properties.push(" AI_TOKEN: string;");
|
|
@@ -5241,7 +5641,7 @@ async function typesCommand(options = {}) {
|
|
|
5241
5641
|
console.error("Error: ploy.yaml not found in current directory");
|
|
5242
5642
|
process.exit(1);
|
|
5243
5643
|
}
|
|
5244
|
-
const hasBindings2 = config.ai || config.db || config.queue || config.cache || config.state || config.workflow;
|
|
5644
|
+
const hasBindings2 = config.env || config.ai || config.db || config.queue || config.cache || config.state || config.workflow;
|
|
5245
5645
|
if (!hasBindings2) {
|
|
5246
5646
|
console.log("No bindings found in ploy.yaml. Generating empty Env.");
|
|
5247
5647
|
}
|