@specific.dev/cli 0.1.143 → 0.1.146

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.
Files changed (107) hide show
  1. package/dist/admin/404/index.html +1 -1
  2. package/dist/admin/404.html +1 -1
  3. package/dist/admin/__next.!KGRlZmF1bHQp.__PAGE__.txt +4 -4
  4. package/dist/admin/__next.!KGRlZmF1bHQp.txt +7 -7
  5. package/dist/admin/__next._full.txt +16 -16
  6. package/dist/admin/__next._head.txt +4 -4
  7. package/dist/admin/__next._index.txt +6 -6
  8. package/dist/admin/__next._tree.txt +2 -2
  9. package/dist/admin/_next/static/chunks/4894dd6189acd52e.js +1 -0
  10. package/dist/admin/_next/static/chunks/48cbaceebda5df57.js +1 -0
  11. package/dist/admin/_next/static/chunks/6d4c1852135da048.js +5 -0
  12. package/dist/admin/_next/static/chunks/{a308451471d4cb39.js → 7c2bbd09e9922443.js} +1 -1
  13. package/dist/admin/_next/static/chunks/7e5c5b91b30c68a0.js +7 -0
  14. package/dist/admin/_next/static/chunks/9835cc4e14c5aee3.js +1 -0
  15. package/dist/admin/_next/static/chunks/b44581875c51fcd8.js +1 -0
  16. package/dist/admin/_next/static/chunks/b6749b7e46ea7447.css +4 -0
  17. package/dist/admin/_next/static/chunks/b69f189d24b42cc4.js +1 -0
  18. package/dist/admin/_next/static/chunks/c52b2ebc474a0535.js +1 -0
  19. package/dist/admin/_next/static/chunks/c82a52b3c62e7d18.js +1 -0
  20. package/dist/admin/_next/static/chunks/dc5c264e3262a84a.js +1 -0
  21. package/dist/admin/_not-found/__next._full.txt +11 -11
  22. package/dist/admin/_not-found/__next._head.txt +4 -4
  23. package/dist/admin/_not-found/__next._index.txt +6 -6
  24. package/dist/admin/_not-found/__next._not-found.__PAGE__.txt +2 -2
  25. package/dist/admin/_not-found/__next._not-found.txt +3 -3
  26. package/dist/admin/_not-found/__next._tree.txt +2 -2
  27. package/dist/admin/_not-found/index.html +1 -1
  28. package/dist/admin/_not-found/index.txt +11 -11
  29. package/dist/admin/databases/__next.!KGRlZmF1bHQp.databases.__PAGE__.txt +4 -4
  30. package/dist/admin/databases/__next.!KGRlZmF1bHQp.databases.txt +3 -3
  31. package/dist/admin/databases/__next.!KGRlZmF1bHQp.txt +7 -7
  32. package/dist/admin/databases/__next._full.txt +16 -16
  33. package/dist/admin/databases/__next._head.txt +4 -4
  34. package/dist/admin/databases/__next._index.txt +6 -6
  35. package/dist/admin/databases/__next._tree.txt +2 -2
  36. package/dist/admin/databases/index.html +1 -1
  37. package/dist/admin/databases/index.txt +16 -16
  38. package/dist/admin/fullscreen/__next._full.txt +12 -12
  39. package/dist/admin/fullscreen/__next._head.txt +4 -4
  40. package/dist/admin/fullscreen/__next._index.txt +6 -6
  41. package/dist/admin/fullscreen/__next._tree.txt +2 -2
  42. package/dist/admin/fullscreen/__next.fullscreen.__PAGE__.txt +4 -4
  43. package/dist/admin/fullscreen/__next.fullscreen.txt +3 -3
  44. package/dist/admin/fullscreen/databases/__next._full.txt +12 -12
  45. package/dist/admin/fullscreen/databases/__next._head.txt +4 -4
  46. package/dist/admin/fullscreen/databases/__next._index.txt +6 -6
  47. package/dist/admin/fullscreen/databases/__next._tree.txt +2 -2
  48. package/dist/admin/fullscreen/databases/__next.fullscreen.databases.__PAGE__.txt +4 -4
  49. package/dist/admin/fullscreen/databases/__next.fullscreen.databases.txt +3 -3
  50. package/dist/admin/fullscreen/databases/__next.fullscreen.txt +3 -3
  51. package/dist/admin/fullscreen/databases/index.html +1 -1
  52. package/dist/admin/fullscreen/databases/index.txt +12 -12
  53. package/dist/admin/fullscreen/index.html +1 -1
  54. package/dist/admin/fullscreen/index.txt +12 -12
  55. package/dist/admin/index.html +1 -1
  56. package/dist/admin/index.txt +16 -16
  57. package/dist/admin/mail/__next.!KGRlZmF1bHQp.mail.__PAGE__.txt +4 -4
  58. package/dist/admin/mail/__next.!KGRlZmF1bHQp.mail.txt +3 -3
  59. package/dist/admin/mail/__next.!KGRlZmF1bHQp.txt +7 -7
  60. package/dist/admin/mail/__next._full.txt +16 -16
  61. package/dist/admin/mail/__next._head.txt +4 -4
  62. package/dist/admin/mail/__next._index.txt +6 -6
  63. package/dist/admin/mail/__next._tree.txt +2 -2
  64. package/dist/admin/mail/index.html +1 -1
  65. package/dist/admin/mail/index.txt +16 -16
  66. package/dist/admin/services/__next.!KGRlZmF1bHQp.services.__PAGE__.txt +4 -4
  67. package/dist/admin/services/__next.!KGRlZmF1bHQp.services.txt +3 -3
  68. package/dist/admin/services/__next.!KGRlZmF1bHQp.txt +7 -7
  69. package/dist/admin/services/__next._full.txt +16 -16
  70. package/dist/admin/services/__next._head.txt +4 -4
  71. package/dist/admin/services/__next._index.txt +6 -6
  72. package/dist/admin/services/__next._tree.txt +2 -2
  73. package/dist/admin/services/index.html +1 -1
  74. package/dist/admin/services/index.txt +16 -16
  75. package/dist/admin/storage/__next.!KGRlZmF1bHQp.storage.__PAGE__.txt +9 -0
  76. package/dist/admin/storage/__next.!KGRlZmF1bHQp.storage.txt +4 -0
  77. package/dist/admin/storage/__next.!KGRlZmF1bHQp.txt +8 -0
  78. package/dist/admin/storage/__next._full.txt +27 -0
  79. package/dist/admin/storage/__next._head.txt +6 -0
  80. package/dist/admin/storage/__next._index.txt +7 -0
  81. package/dist/admin/storage/__next._tree.txt +5 -0
  82. package/dist/admin/storage/index.html +1 -0
  83. package/dist/admin/storage/index.txt +27 -0
  84. package/dist/admin/workflows/__next.!KGRlZmF1bHQp.txt +7 -7
  85. package/dist/admin/workflows/__next.!KGRlZmF1bHQp.workflows.__PAGE__.txt +4 -4
  86. package/dist/admin/workflows/__next.!KGRlZmF1bHQp.workflows.txt +3 -3
  87. package/dist/admin/workflows/__next._full.txt +16 -16
  88. package/dist/admin/workflows/__next._head.txt +4 -4
  89. package/dist/admin/workflows/__next._index.txt +6 -6
  90. package/dist/admin/workflows/__next._tree.txt +2 -2
  91. package/dist/admin/workflows/index.html +1 -1
  92. package/dist/admin/workflows/index.txt +16 -16
  93. package/dist/cli.js +488 -24
  94. package/dist/docs/redis.md +1 -1
  95. package/package.json +2 -1
  96. package/dist/admin/_next/static/chunks/29131741af270a46.js +0 -1
  97. package/dist/admin/_next/static/chunks/4e5f2d0efc90ca9f.js +0 -5
  98. package/dist/admin/_next/static/chunks/562ce7f5a20cc20f.js +0 -1
  99. package/dist/admin/_next/static/chunks/63accf2b484df522.js +0 -1
  100. package/dist/admin/_next/static/chunks/7058ba3be99e5848.js +0 -1
  101. package/dist/admin/_next/static/chunks/969af33935e3a7d5.css +0 -4
  102. package/dist/admin/_next/static/chunks/c0853089f2cd9ccb.js +0 -1
  103. package/dist/admin/_next/static/chunks/db49f609ddd8888b.js +0 -1
  104. package/dist/admin/_next/static/chunks/e4ac6f714e409140.js +0 -1
  105. /package/dist/admin/_next/static/{D-FFm4sI-NGxWLJmxd3IX → g6SjZi_LuWK7FrmmMr5PE}/_buildManifest.js +0 -0
  106. /package/dist/admin/_next/static/{D-FFm4sI-NGxWLJmxd3IX → g6SjZi_LuWK7FrmmMr5PE}/_clientMiddlewareManifest.json +0 -0
  107. /package/dist/admin/_next/static/{D-FFm4sI-NGxWLJmxd3IX → g6SjZi_LuWK7FrmmMr5PE}/_ssgManifest.js +0 -0
package/dist/cli.js CHANGED
@@ -185198,6 +185198,16 @@ import * as http from "http";
185198
185198
  import * as fs7 from "fs";
185199
185199
  import * as path7 from "path";
185200
185200
  import { fileURLToPath } from "url";
185201
+ import {
185202
+ CopyObjectCommand,
185203
+ DeleteObjectCommand,
185204
+ DeleteObjectsCommand,
185205
+ GetObjectCommand,
185206
+ HeadObjectCommand,
185207
+ ListObjectsV2Command,
185208
+ PutObjectCommand,
185209
+ S3Client
185210
+ } from "@aws-sdk/client-s3";
185201
185211
  import * as net3 from "net";
185202
185212
  import * as fs8 from "fs";
185203
185213
  import * as path9 from "path";
@@ -369113,9 +369123,9 @@ var postgresBinary = {
369113
369123
  };
369114
369124
  var redisBinary = {
369115
369125
  name: "redis",
369116
- versions: ["8.4.0"],
369126
+ versions: ["8.1.7"],
369117
369127
  // URL: binaries.specific.dev/{SOFTWARE}/{VERSION}/{ARCH}.tar.gz
369118
- urlTemplate: "https://binaries.specific.dev/redis/{version}/{arch}.tar.gz",
369128
+ urlTemplate: "https://binaries.specific.dev/valkey/{version}/{arch}.tar.gz",
369119
369129
  platformMapping: {
369120
369130
  darwin: {
369121
369131
  arm64: { platform: "macos", arch: "macos_arm" },
@@ -369128,8 +369138,7 @@ var redisBinary = {
369128
369138
  },
369129
369139
  // Binaries are at the root of the archive
369130
369140
  stripComponents: 0,
369131
- // Core Redis executable
369132
- executables: ["redis-server"]
369141
+ executables: ["valkey-server"]
369133
369142
  };
369134
369143
  var electricBinary = {
369135
369144
  name: "electric",
@@ -369449,16 +369458,35 @@ async function startPostgres(pg, port, dataDir, onProgress) {
369449
369458
  }
369450
369459
  };
369451
369460
  }
369452
- async function startRedis(redis, port, onProgress) {
369461
+ async function startRedis(redis, port, dataDir, onProgress) {
369453
369462
  const binary = await ensureBinary(redisBinary, void 0, onProgress);
369454
369463
  const host = "127.0.0.1";
369464
+ const redisDataPath = path5.join(process.cwd(), dataDir, "redis", redis.name);
369465
+ fs5.mkdirSync(redisDataPath, { recursive: true });
369466
+ const stalePath = path5.join(redisDataPath, "dump.rdb");
369467
+ if (fs5.existsSync(stalePath)) {
369468
+ fs5.rmSync(stalePath, { force: true });
369469
+ }
369455
369470
  if (await isPortInUse(host, port)) {
369456
369471
  await reclaimSpecificOrphanOnPort(port);
369457
369472
  await waitForPortFree(host, port, 1e3);
369458
369473
  }
369459
369474
  const redisProc = spawn(
369460
- binary.executables["redis-server"],
369461
- ["--port", String(port), "--bind", host, "--daemonize", "no"],
369475
+ binary.executables["valkey-server"],
369476
+ [
369477
+ "--port",
369478
+ String(port),
369479
+ "--bind",
369480
+ host,
369481
+ "--daemonize",
369482
+ "no",
369483
+ "--dir",
369484
+ redisDataPath,
369485
+ "--save",
369486
+ "",
369487
+ "--appendonly",
369488
+ "no"
369489
+ ],
369462
369490
  {
369463
369491
  stdio: ["ignore", "pipe", "pipe"],
369464
369492
  detached: true
@@ -370448,6 +370476,417 @@ function killTrackedProcess(pid, detached, isProcessRunning) {
370448
370476
  } catch {
370449
370477
  }
370450
370478
  }
370479
+ var MAX_KEY_LENGTH = 1024;
370480
+ var PROXY_URL_EXPIRES_IN = 3600;
370481
+ function validateKey(key) {
370482
+ if (!key || key.length > MAX_KEY_LENGTH) return "Invalid key length";
370483
+ if (key.includes("..")) return "Key must not contain '..'";
370484
+ if (key.includes("\0")) return "Key must not contain null bytes";
370485
+ if (key.startsWith("/")) return "Key must not start with '/'";
370486
+ return null;
370487
+ }
370488
+ var clientCache = /* @__PURE__ */ new Map();
370489
+ function getClient(info) {
370490
+ const cacheKey = `${info.host}:${info.port}:${info.accessKey}`;
370491
+ const cached2 = clientCache.get(cacheKey);
370492
+ if (cached2) return cached2;
370493
+ const client2 = new S3Client({
370494
+ endpoint: `http://${info.host}:${info.port}`,
370495
+ region: "us-east-1",
370496
+ credentials: {
370497
+ accessKeyId: info.accessKey,
370498
+ secretAccessKey: info.secretKey
370499
+ },
370500
+ forcePathStyle: true
370501
+ });
370502
+ clientCache.set(cacheKey, client2);
370503
+ return client2;
370504
+ }
370505
+ function sendJson(res, status, body) {
370506
+ res.setHeader("Content-Type", "application/json");
370507
+ res.writeHead(status);
370508
+ res.end(JSON.stringify(body));
370509
+ }
370510
+ function sendError(res, status, message) {
370511
+ sendJson(res, status, { error: message });
370512
+ }
370513
+ async function readJsonBody(req) {
370514
+ const chunks = [];
370515
+ for await (const chunk of req) {
370516
+ chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
370517
+ }
370518
+ const text = Buffer.concat(chunks).toString("utf8");
370519
+ return text.length > 0 ? JSON.parse(text) : {};
370520
+ }
370521
+ async function handleListObjects(client2, bucket, url, res) {
370522
+ const prefix = url.searchParams.get("prefix") ?? "";
370523
+ const cursor = url.searchParams.get("cursor") ?? void 0;
370524
+ const maxKeys = Math.min(
370525
+ Math.max(parseInt(url.searchParams.get("maxKeys") ?? "100", 10) || 100, 1),
370526
+ 1e3
370527
+ );
370528
+ const response = await client2.send(
370529
+ new ListObjectsV2Command({
370530
+ Bucket: bucket,
370531
+ Prefix: prefix,
370532
+ Delimiter: "/",
370533
+ MaxKeys: maxKeys,
370534
+ ContinuationToken: cursor || void 0
370535
+ })
370536
+ );
370537
+ sendJson(res, 200, {
370538
+ objects: (response.Contents ?? []).map((obj) => ({
370539
+ key: obj.Key,
370540
+ size: obj.Size ?? 0,
370541
+ lastModified: obj.LastModified?.toISOString() ?? "",
370542
+ etag: obj.ETag
370543
+ })),
370544
+ prefixes: (response.CommonPrefixes ?? []).map((p) => p.Prefix),
370545
+ nextCursor: response.NextContinuationToken ?? null,
370546
+ prefix
370547
+ });
370548
+ }
370549
+ async function handleMetrics(client2, bucket, res) {
370550
+ let totalObjects = 0;
370551
+ let totalSize = 0;
370552
+ let continuationToken;
370553
+ let pages = 0;
370554
+ const MAX_PAGES = 100;
370555
+ do {
370556
+ const response = await client2.send(
370557
+ new ListObjectsV2Command({
370558
+ Bucket: bucket,
370559
+ ContinuationToken: continuationToken
370560
+ })
370561
+ );
370562
+ totalObjects += response.KeyCount ?? 0;
370563
+ for (const obj of response.Contents ?? []) {
370564
+ totalSize += obj.Size ?? 0;
370565
+ }
370566
+ continuationToken = response.NextContinuationToken;
370567
+ pages++;
370568
+ } while (continuationToken && pages < MAX_PAGES);
370569
+ sendJson(res, 200, {
370570
+ totalObjects,
370571
+ totalSize,
370572
+ bucketName: bucket,
370573
+ ...pages >= MAX_PAGES ? { truncated: true } : {}
370574
+ });
370575
+ }
370576
+ async function handleGetObject(client2, bucket, key, res) {
370577
+ try {
370578
+ const response = await client2.send(
370579
+ new HeadObjectCommand({ Bucket: bucket, Key: key })
370580
+ );
370581
+ sendJson(res, 200, {
370582
+ key,
370583
+ size: response.ContentLength ?? 0,
370584
+ lastModified: response.LastModified?.toISOString(),
370585
+ contentType: response.ContentType,
370586
+ etag: response.ETag,
370587
+ metadata: response.Metadata
370588
+ });
370589
+ } catch (error) {
370590
+ const name = error.name;
370591
+ if (name === "NotFound" || name === "NoSuchKey") {
370592
+ sendError(res, 404, `Object '${key}' not found`);
370593
+ return;
370594
+ }
370595
+ throw error;
370596
+ }
370597
+ }
370598
+ async function handleDeleteObject(client2, bucket, key, res) {
370599
+ if (key.endsWith("/")) {
370600
+ const allKeys = [];
370601
+ let continuationToken;
370602
+ do {
370603
+ const listResponse = await client2.send(
370604
+ new ListObjectsV2Command({
370605
+ Bucket: bucket,
370606
+ Prefix: key,
370607
+ ContinuationToken: continuationToken
370608
+ })
370609
+ );
370610
+ for (const obj of listResponse.Contents ?? []) {
370611
+ if (obj.Key) allKeys.push(obj.Key);
370612
+ }
370613
+ continuationToken = listResponse.NextContinuationToken;
370614
+ } while (continuationToken);
370615
+ const batches = [];
370616
+ for (let i = 0; i < allKeys.length; i += 1e3) {
370617
+ batches.push(
370618
+ client2.send(
370619
+ new DeleteObjectsCommand({
370620
+ Bucket: bucket,
370621
+ Delete: {
370622
+ Objects: allKeys.slice(i, i + 1e3).map((k) => ({ Key: k })),
370623
+ Quiet: true
370624
+ }
370625
+ })
370626
+ )
370627
+ );
370628
+ }
370629
+ await Promise.all(batches);
370630
+ } else {
370631
+ await client2.send(new DeleteObjectCommand({ Bucket: bucket, Key: key }));
370632
+ }
370633
+ sendJson(res, 200, { success: true });
370634
+ }
370635
+ async function handleCreateFolder(client2, bucket, body, res) {
370636
+ const path172 = body.path;
370637
+ if (typeof path172 !== "string") {
370638
+ sendError(res, 400, "Missing path");
370639
+ return;
370640
+ }
370641
+ const folderKey = path172.endsWith("/") ? path172 : `${path172}/`;
370642
+ const keyError = validateKey(folderKey);
370643
+ if (keyError) {
370644
+ sendError(res, 400, keyError);
370645
+ return;
370646
+ }
370647
+ await client2.send(
370648
+ new PutObjectCommand({ Bucket: bucket, Key: folderKey, Body: "" })
370649
+ );
370650
+ sendJson(res, 200, { key: folderKey });
370651
+ }
370652
+ async function handleRename(client2, bucket, body, res) {
370653
+ const { sourceKey, destinationKey } = body;
370654
+ if (typeof sourceKey !== "string" || typeof destinationKey !== "string") {
370655
+ sendError(res, 400, "Missing sourceKey or destinationKey");
370656
+ return;
370657
+ }
370658
+ for (const k of [sourceKey, destinationKey]) {
370659
+ const err = validateKey(k);
370660
+ if (err) {
370661
+ sendError(res, 400, err);
370662
+ return;
370663
+ }
370664
+ }
370665
+ if (sourceKey.endsWith("/")) {
370666
+ const allKeys = [];
370667
+ let continuationToken;
370668
+ do {
370669
+ const listResponse = await client2.send(
370670
+ new ListObjectsV2Command({
370671
+ Bucket: bucket,
370672
+ Prefix: sourceKey,
370673
+ ContinuationToken: continuationToken
370674
+ })
370675
+ );
370676
+ for (const obj of listResponse.Contents ?? []) {
370677
+ if (obj.Key) allKeys.push(obj.Key);
370678
+ }
370679
+ continuationToken = listResponse.NextContinuationToken;
370680
+ } while (continuationToken);
370681
+ if (allKeys.length === 0) {
370682
+ sendError(res, 404, "Folder is empty or does not exist");
370683
+ return;
370684
+ }
370685
+ await Promise.all(
370686
+ allKeys.map(
370687
+ (k) => client2.send(
370688
+ new CopyObjectCommand({
370689
+ Bucket: bucket,
370690
+ CopySource: `${bucket}/${encodeURI(k)}`,
370691
+ Key: destinationKey + k.slice(sourceKey.length)
370692
+ })
370693
+ )
370694
+ )
370695
+ );
370696
+ const deleteBatches = [];
370697
+ for (let i = 0; i < allKeys.length; i += 1e3) {
370698
+ deleteBatches.push(
370699
+ client2.send(
370700
+ new DeleteObjectsCommand({
370701
+ Bucket: bucket,
370702
+ Delete: {
370703
+ Objects: allKeys.slice(i, i + 1e3).map((k) => ({ Key: k })),
370704
+ Quiet: true
370705
+ }
370706
+ })
370707
+ )
370708
+ );
370709
+ }
370710
+ await Promise.all(deleteBatches);
370711
+ } else {
370712
+ await client2.send(
370713
+ new CopyObjectCommand({
370714
+ Bucket: bucket,
370715
+ CopySource: `${bucket}/${encodeURI(sourceKey)}`,
370716
+ Key: destinationKey
370717
+ })
370718
+ );
370719
+ await client2.send(
370720
+ new DeleteObjectCommand({ Bucket: bucket, Key: sourceKey })
370721
+ );
370722
+ }
370723
+ sendJson(res, 200, { key: destinationKey });
370724
+ }
370725
+ async function handleUploadData(client2, bucket, key, req, res) {
370726
+ const chunks = [];
370727
+ for await (const chunk of req) {
370728
+ chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
370729
+ }
370730
+ const body = Buffer.concat(chunks);
370731
+ const contentType = req.headers["content-type"];
370732
+ await client2.send(
370733
+ new PutObjectCommand({
370734
+ Bucket: bucket,
370735
+ Key: key,
370736
+ Body: body,
370737
+ ContentType: typeof contentType === "string" ? contentType : void 0
370738
+ })
370739
+ );
370740
+ res.writeHead(200);
370741
+ res.end();
370742
+ }
370743
+ async function handleDownloadData(client2, bucket, key, res) {
370744
+ try {
370745
+ const response = await client2.send(
370746
+ new GetObjectCommand({ Bucket: bucket, Key: key })
370747
+ );
370748
+ if (response.ContentType) {
370749
+ res.setHeader("Content-Type", response.ContentType);
370750
+ }
370751
+ if (response.ContentLength !== void 0) {
370752
+ res.setHeader("Content-Length", String(response.ContentLength));
370753
+ }
370754
+ res.writeHead(200);
370755
+ const body = response.Body;
370756
+ if (!body) {
370757
+ res.end();
370758
+ return;
370759
+ }
370760
+ if (typeof body.pipe === "function") {
370761
+ body.pipe(res);
370762
+ } else {
370763
+ const stream = body.transformToWebStream();
370764
+ const reader = stream.getReader();
370765
+ while (true) {
370766
+ const { done, value } = await reader.read();
370767
+ if (done) break;
370768
+ if (value) res.write(Buffer.from(value));
370769
+ }
370770
+ res.end();
370771
+ }
370772
+ } catch (error) {
370773
+ const name = error.name;
370774
+ if (name === "NotFound" || name === "NoSuchKey") {
370775
+ if (!res.headersSent) {
370776
+ sendError(res, 404, `Object '${key}' not found`);
370777
+ } else {
370778
+ res.end();
370779
+ }
370780
+ return;
370781
+ }
370782
+ throw error;
370783
+ }
370784
+ }
370785
+ function handlePresigned(storageName, body, res) {
370786
+ const { key, action, expiresIn } = body;
370787
+ if (typeof key !== "string" || action !== "upload" && action !== "download") {
370788
+ sendError(res, 400, "Missing key or invalid action");
370789
+ return;
370790
+ }
370791
+ const keyError = validateKey(key);
370792
+ if (keyError) {
370793
+ sendError(res, 400, keyError);
370794
+ return;
370795
+ }
370796
+ const url = `/api/storage/${encodeURIComponent(storageName)}/data?key=${encodeURIComponent(key)}`;
370797
+ sendJson(res, 200, {
370798
+ url,
370799
+ expiresIn: typeof expiresIn === "number" ? expiresIn : PROXY_URL_EXPIRES_IN
370800
+ });
370801
+ }
370802
+ async function handleStorageRequest(req, res, pathname, url, getStorageInstance) {
370803
+ const match = /^\/api\/storage\/([^/]+)(\/.*)?$/.exec(pathname);
370804
+ if (!match) return false;
370805
+ const storageName = decodeURIComponent(match[1]);
370806
+ const sub = match[2] ?? "";
370807
+ const info = getStorageInstance(storageName);
370808
+ if (!info) {
370809
+ sendError(res, 404, `Storage '${storageName}' not found`);
370810
+ return true;
370811
+ }
370812
+ const client2 = getClient(info);
370813
+ const bucket = info.bucket;
370814
+ try {
370815
+ if (sub === "/objects" && req.method === "GET") {
370816
+ await handleListObjects(client2, bucket, url, res);
370817
+ return true;
370818
+ }
370819
+ if (sub === "/metrics" && req.method === "GET") {
370820
+ await handleMetrics(client2, bucket, res);
370821
+ return true;
370822
+ }
370823
+ if (sub === "/object") {
370824
+ const key = url.searchParams.get("key");
370825
+ if (!key) {
370826
+ sendError(res, 400, "Missing key");
370827
+ return true;
370828
+ }
370829
+ const keyError = validateKey(key);
370830
+ if (keyError) {
370831
+ sendError(res, 400, keyError);
370832
+ return true;
370833
+ }
370834
+ if (req.method === "GET") {
370835
+ await handleGetObject(client2, bucket, key, res);
370836
+ return true;
370837
+ }
370838
+ if (req.method === "DELETE") {
370839
+ await handleDeleteObject(client2, bucket, key, res);
370840
+ return true;
370841
+ }
370842
+ }
370843
+ if (sub === "/data") {
370844
+ const key = url.searchParams.get("key");
370845
+ if (!key) {
370846
+ sendError(res, 400, "Missing key");
370847
+ return true;
370848
+ }
370849
+ const keyError = validateKey(key);
370850
+ if (keyError) {
370851
+ sendError(res, 400, keyError);
370852
+ return true;
370853
+ }
370854
+ if (req.method === "PUT") {
370855
+ await handleUploadData(client2, bucket, key, req, res);
370856
+ return true;
370857
+ }
370858
+ if (req.method === "GET") {
370859
+ await handleDownloadData(client2, bucket, key, res);
370860
+ return true;
370861
+ }
370862
+ }
370863
+ if (sub === "/folder" && req.method === "POST") {
370864
+ const body = await readJsonBody(req);
370865
+ await handleCreateFolder(client2, bucket, body, res);
370866
+ return true;
370867
+ }
370868
+ if (sub === "/rename" && req.method === "POST") {
370869
+ const body = await readJsonBody(req);
370870
+ await handleRename(client2, bucket, body, res);
370871
+ return true;
370872
+ }
370873
+ if (sub === "/presigned" && req.method === "POST") {
370874
+ const body = await readJsonBody(req);
370875
+ handlePresigned(storageName, body, res);
370876
+ return true;
370877
+ }
370878
+ sendError(res, 404, "Not found");
370879
+ return true;
370880
+ } catch (error) {
370881
+ writeLog("admin:storage", `Storage handler error: ${error}`);
370882
+ if (!res.headersSent) {
370883
+ sendError(res, 500, error instanceof Error ? error.message : "Internal error");
370884
+ } else {
370885
+ res.end();
370886
+ }
370887
+ return true;
370888
+ }
370889
+ }
370451
370890
  var __dirname = path7.dirname(fileURLToPath(import.meta.url));
370452
370891
  var adminDir = path7.join(__dirname, "admin");
370453
370892
  var _embeddedAdmin = null;
@@ -370636,12 +371075,15 @@ function proxyRequest(req, res, targetPort) {
370636
371075
  });
370637
371076
  req.pipe(proxyReq, { end: true });
370638
371077
  }
370639
- async function startAdminServer(getState, listenPort = 0, getLogsDir = () => null) {
371078
+ async function startAdminServer(getState, listenPort = 0, getLogsDir = () => null, getStorageInstance = () => void 0) {
370640
371079
  return new Promise((resolve62, reject) => {
370641
371080
  const server = http.createServer((req, res) => {
370642
371081
  const url = new URL(req.url || "/", "http://localhost");
370643
371082
  res.setHeader("Access-Control-Allow-Origin", "*");
370644
- res.setHeader("Access-Control-Allow-Methods", "GET, OPTIONS");
371083
+ res.setHeader(
371084
+ "Access-Control-Allow-Methods",
371085
+ "GET, POST, PUT, DELETE, OPTIONS"
371086
+ );
370645
371087
  res.setHeader("Access-Control-Allow-Headers", "Content-Type");
370646
371088
  if (req.method === "OPTIONS") {
370647
371089
  res.writeHead(204);
@@ -370715,6 +371157,10 @@ async function startAdminServer(getState, listenPort = 0, getLogsDir = () => nul
370715
371157
  res.end(JSON.stringify(body));
370716
371158
  return;
370717
371159
  }
371160
+ if (pathname.startsWith("/api/storage/")) {
371161
+ void handleStorageRequest(req, res, pathname, url, getStorageInstance);
371162
+ return;
371163
+ }
370718
371164
  if (pathname.startsWith("/temporal-ui")) {
370719
371165
  const state = getState();
370720
371166
  if (state.temporalUiPort) {
@@ -371713,7 +372159,7 @@ async function startResources(options2) {
371713
372159
  const port = portAllocator.allocate(`redis:${redis.name}`);
371714
372160
  log(`Starting redis "${redis.name}" on port ${port}`);
371715
372161
  callbacks.onResourceStarting?.(redis.name, "redis");
371716
- const instance = await startRedis(redis, port, (progress) => {
372162
+ const instance = await startRedis(redis, port, dataDir, (progress) => {
371717
372163
  callbacks.onResourceProgress?.(redis.name, progress);
371718
372164
  });
371719
372165
  resources.set(redis.name, instance);
@@ -373342,7 +373788,20 @@ Add them to the config block in specific.local`
373342
373788
  const adminServer = await startAdminServer(
373343
373789
  getState,
373344
373790
  adminPort,
373345
- () => this.serviceLogFiles?.logsDir ?? null
373791
+ () => this.serviceLogFiles?.logsDir ?? null,
373792
+ (name) => {
373793
+ const r = this.resources.get(name);
373794
+ if (!r || r.type !== "storage" || !r.bucket || !r.accessKey || !r.secretKey) {
373795
+ return void 0;
373796
+ }
373797
+ return {
373798
+ host: r.host,
373799
+ port: r.port,
373800
+ bucket: r.bucket,
373801
+ accessKey: r.accessKey,
373802
+ secretKey: r.secretKey
373803
+ };
373804
+ }
373346
373805
  );
373347
373806
  this.adminServer = adminServer;
373348
373807
  this.systemLog(
@@ -373932,7 +374391,7 @@ function getProjectId() {
373932
374391
  }
373933
374392
  return void 0;
373934
374393
  }
373935
- function getClient() {
374394
+ function getClient2() {
373936
374395
  if (!isEnabled()) return null;
373937
374396
  if (!client) {
373938
374397
  client = new PostHog("phc_qNQCEUXh6ErdciQRRmeG2xlVvwFjkcW6A5bnOFJ8vXZ", {
@@ -373945,7 +374404,7 @@ function getClient() {
373945
374404
  return client;
373946
374405
  }
373947
374406
  function trackEvent(event, properties) {
373948
- const ph = getClient();
374407
+ const ph = getClient2();
373949
374408
  if (!ph) return;
373950
374409
  const userId = getUserId();
373951
374410
  if (userId && !identified) {
@@ -373966,7 +374425,7 @@ function trackEvent(event, properties) {
373966
374425
  event,
373967
374426
  properties: {
373968
374427
  ...properties,
373969
- cli_version: "0.1.143",
374428
+ cli_version: "0.1.146",
373970
374429
  platform: process.platform,
373971
374430
  node_version: process.version,
373972
374431
  project_id: getProjectId()
@@ -376209,6 +376668,7 @@ function DeployUI({ envFlag, preview, config }) {
376209
376668
  setState((s) => ({
376210
376669
  ...s,
376211
376670
  phase: "creating-tarball",
376671
+ environmentId: match.id,
376212
376672
  environmentName: match.name,
376213
376673
  environments
376214
376674
  }));
@@ -376222,6 +376682,7 @@ function DeployUI({ envFlag, preview, config }) {
376222
376682
  setState((s) => ({
376223
376683
  ...s,
376224
376684
  phase: "creating-tarball",
376685
+ environmentId: match.id,
376225
376686
  environmentName: match.name,
376226
376687
  environments
376227
376688
  }));
@@ -376233,6 +376694,7 @@ function DeployUI({ envFlag, preview, config }) {
376233
376694
  setState((s) => ({
376234
376695
  ...s,
376235
376696
  phase: "creating-tarball",
376697
+ environmentId: environments[0].id,
376236
376698
  environmentName: environments[0].name,
376237
376699
  environments
376238
376700
  }));
@@ -376281,6 +376743,7 @@ function DeployUI({ envFlag, preview, config }) {
376281
376743
  setState((s) => ({
376282
376744
  ...s,
376283
376745
  phase: "creating-tarball",
376746
+ environmentId: previewEnv.id,
376284
376747
  environmentName: previewEnv.name
376285
376748
  }));
376286
376749
  } catch (err) {
@@ -376302,6 +376765,7 @@ function DeployUI({ envFlag, preview, config }) {
376302
376765
  setState((s) => ({
376303
376766
  ...s,
376304
376767
  phase: "creating-tarball",
376768
+ environmentId: env2.id,
376305
376769
  environmentName: env2.name
376306
376770
  }));
376307
376771
  },
@@ -376453,7 +376917,7 @@ function DeployUI({ envFlag, preview, config }) {
376453
376917
  }, [state]);
376454
376918
  const environment = state.environmentName || "prod";
376455
376919
  useEffect6(() => {
376456
- if (state.phase !== "creating-tarball" || !state.projectId || !state.environmentName) return;
376920
+ if (state.phase !== "creating-tarball" || !state.projectId || !state.environmentId || !state.environmentName) return;
376457
376921
  let cancelled = false;
376458
376922
  async function runDeploy() {
376459
376923
  const projectDir = process.cwd();
@@ -376489,7 +376953,7 @@ function DeployUI({ envFlag, preview, config }) {
376489
376953
  try {
376490
376954
  writeLog("deploy", `Creating deployment for project ${state.projectId}`);
376491
376955
  const gitInfo = getGitInfo(projectDir);
376492
- deployment2 = await client2.createDeployment(state.projectId, state.environmentName, {
376956
+ deployment2 = await client2.createDeployment(state.projectId, state.environmentId, {
376493
376957
  triggeredBy: "cli",
376494
376958
  ...gitInfo && {
376495
376959
  gitCommitSha: gitInfo.commitSha,
@@ -376954,7 +377418,7 @@ async function runDeployPipeline(options2) {
376954
377418
  const projects = await client2.listProjects();
376955
377419
  const project = projects.find((p) => p.id === projectId);
376956
377420
  const environments = project?.environments ?? [];
376957
- let environmentName;
377421
+ let environmentId;
376958
377422
  if (options2.environment) {
376959
377423
  const match = findEnvironmentByNameOrId(environments, options2.environment);
376960
377424
  if (!match) {
@@ -376964,15 +377428,15 @@ async function runDeployPipeline(options2) {
376964
377428
  );
376965
377429
  process.exit(1);
376966
377430
  }
376967
- environmentName = match.name;
377431
+ environmentId = match.id;
376968
377432
  writeEnvironmentId(match.id);
376969
377433
  } else if (hasEnvironmentId(projectDir)) {
376970
377434
  const savedEnvId = readEnvironmentId(projectDir);
376971
377435
  const env2 = environments.find((e) => e.id === savedEnvId);
376972
377436
  if (env2) {
376973
- environmentName = env2.name;
377437
+ environmentId = env2.id;
376974
377438
  } else if (environments.length === 1) {
376975
- environmentName = environments[0].name;
377439
+ environmentId = environments[0].id;
376976
377440
  writeEnvironmentId(environments[0].id);
376977
377441
  } else if (environments.length === 0) {
376978
377442
  console.error("Error: No environments found for this project");
@@ -376987,7 +377451,7 @@ async function runDeployPipeline(options2) {
376987
377451
  }
376988
377452
  } else {
376989
377453
  if (environments.length === 1) {
376990
- environmentName = environments[0].name;
377454
+ environmentId = environments[0].id;
376991
377455
  writeEnvironmentId(environments[0].id);
376992
377456
  } else if (environments.length === 0) {
376993
377457
  console.error("Error: No environments found for this project");
@@ -377034,7 +377498,7 @@ async function runDeployPipeline(options2) {
377034
377498
  let deployment;
377035
377499
  try {
377036
377500
  const gitInfo = getGitInfo(projectDir);
377037
- deployment = await client2.createDeployment(projectId, environmentName, {
377501
+ deployment = await client2.createDeployment(projectId, environmentId, {
377038
377502
  triggeredBy: "cli",
377039
377503
  ...gitInfo && {
377040
377504
  gitCommitSha: gitInfo.commitSha,
@@ -378077,7 +378541,7 @@ function compareVersions(a, b) {
378077
378541
  return 0;
378078
378542
  }
378079
378543
  async function checkForUpdate() {
378080
- const currentVersion = "0.1.143";
378544
+ const currentVersion = "0.1.146";
378081
378545
  const response = await fetch(`${BINARIES_BASE_URL}/latest?t=${Date.now()}`);
378082
378546
  if (!response.ok) {
378083
378547
  throw new Error(`Failed to check for updates: HTTP ${response.status}`);
@@ -378592,7 +379056,7 @@ function friendlyErrorMessage(raw) {
378592
379056
  var program = new Command();
378593
379057
  var env = "production";
378594
379058
  var envLabel = env !== "production" ? `[${env.toUpperCase()}] ` : "";
378595
- program.name("specific").description(`${envLabel}Infrastructure-as-code for coding agents`).version("0.1.143").enablePositionalOptions();
379059
+ program.name("specific").description(`${envLabel}Infrastructure-as-code for coding agents`).version("0.1.146").enablePositionalOptions();
378596
379060
  program.command("init").description("Initialize project for use with a coding agent").option("--agent <name...>", "Agents to configure (cursor, claude, codex, other)").addHelpText("after", `
378597
379061
  Examples:
378598
379062
  $ specific init
@@ -1,6 +1,6 @@
1
1
  # Redis
2
2
 
3
- Managed Redis instances.
3
+ Managed Redis-compatible cache instances. Backed by [Valkey](https://valkey.io), the BSD-3-Clause open-source fork of Redis 7.2 — works with any Redis client (`ioredis`, `node-redis`, `redis-cli`).
4
4
 
5
5
  ```hcl
6
6
  redis "cache" {}