@specific.dev/cli 0.1.225 → 0.1.227

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 (96) 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 +1 -1
  4. package/dist/admin/__next.!KGRlZmF1bHQp.txt +1 -1
  5. package/dist/admin/__next._full.txt +1 -1
  6. package/dist/admin/__next._head.txt +1 -1
  7. package/dist/admin/__next._index.txt +1 -1
  8. package/dist/admin/__next._tree.txt +1 -1
  9. package/dist/admin/_not-found/__next._full.txt +1 -1
  10. package/dist/admin/_not-found/__next._head.txt +1 -1
  11. package/dist/admin/_not-found/__next._index.txt +1 -1
  12. package/dist/admin/_not-found/__next._not-found.__PAGE__.txt +1 -1
  13. package/dist/admin/_not-found/__next._not-found.txt +1 -1
  14. package/dist/admin/_not-found/__next._tree.txt +1 -1
  15. package/dist/admin/_not-found/index.html +1 -1
  16. package/dist/admin/_not-found/index.txt +1 -1
  17. package/dist/admin/crons/__next.!KGRlZmF1bHQp.crons.__PAGE__.txt +1 -1
  18. package/dist/admin/crons/__next.!KGRlZmF1bHQp.crons.txt +1 -1
  19. package/dist/admin/crons/__next.!KGRlZmF1bHQp.txt +1 -1
  20. package/dist/admin/crons/__next._full.txt +1 -1
  21. package/dist/admin/crons/__next._head.txt +1 -1
  22. package/dist/admin/crons/__next._index.txt +1 -1
  23. package/dist/admin/crons/__next._tree.txt +1 -1
  24. package/dist/admin/crons/index.html +1 -1
  25. package/dist/admin/crons/index.txt +1 -1
  26. package/dist/admin/databases/__next.!KGRlZmF1bHQp.databases.__PAGE__.txt +1 -1
  27. package/dist/admin/databases/__next.!KGRlZmF1bHQp.databases.txt +1 -1
  28. package/dist/admin/databases/__next.!KGRlZmF1bHQp.txt +1 -1
  29. package/dist/admin/databases/__next._full.txt +1 -1
  30. package/dist/admin/databases/__next._head.txt +1 -1
  31. package/dist/admin/databases/__next._index.txt +1 -1
  32. package/dist/admin/databases/__next._tree.txt +1 -1
  33. package/dist/admin/databases/index.html +1 -1
  34. package/dist/admin/databases/index.txt +1 -1
  35. package/dist/admin/fullscreen/__next._full.txt +1 -1
  36. package/dist/admin/fullscreen/__next._head.txt +1 -1
  37. package/dist/admin/fullscreen/__next._index.txt +1 -1
  38. package/dist/admin/fullscreen/__next._tree.txt +1 -1
  39. package/dist/admin/fullscreen/__next.fullscreen.__PAGE__.txt +1 -1
  40. package/dist/admin/fullscreen/__next.fullscreen.txt +1 -1
  41. package/dist/admin/fullscreen/databases/__next._full.txt +1 -1
  42. package/dist/admin/fullscreen/databases/__next._head.txt +1 -1
  43. package/dist/admin/fullscreen/databases/__next._index.txt +1 -1
  44. package/dist/admin/fullscreen/databases/__next._tree.txt +1 -1
  45. package/dist/admin/fullscreen/databases/__next.fullscreen.databases.__PAGE__.txt +1 -1
  46. package/dist/admin/fullscreen/databases/__next.fullscreen.databases.txt +1 -1
  47. package/dist/admin/fullscreen/databases/__next.fullscreen.txt +1 -1
  48. package/dist/admin/fullscreen/databases/index.html +1 -1
  49. package/dist/admin/fullscreen/databases/index.txt +1 -1
  50. package/dist/admin/fullscreen/index.html +1 -1
  51. package/dist/admin/fullscreen/index.txt +1 -1
  52. package/dist/admin/index.html +1 -1
  53. package/dist/admin/index.txt +1 -1
  54. package/dist/admin/mail/__next.!KGRlZmF1bHQp.mail.__PAGE__.txt +1 -1
  55. package/dist/admin/mail/__next.!KGRlZmF1bHQp.mail.txt +1 -1
  56. package/dist/admin/mail/__next.!KGRlZmF1bHQp.txt +1 -1
  57. package/dist/admin/mail/__next._full.txt +1 -1
  58. package/dist/admin/mail/__next._head.txt +1 -1
  59. package/dist/admin/mail/__next._index.txt +1 -1
  60. package/dist/admin/mail/__next._tree.txt +1 -1
  61. package/dist/admin/mail/index.html +1 -1
  62. package/dist/admin/mail/index.txt +1 -1
  63. package/dist/admin/services/__next.!KGRlZmF1bHQp.services.__PAGE__.txt +1 -1
  64. package/dist/admin/services/__next.!KGRlZmF1bHQp.services.txt +1 -1
  65. package/dist/admin/services/__next.!KGRlZmF1bHQp.txt +1 -1
  66. package/dist/admin/services/__next._full.txt +1 -1
  67. package/dist/admin/services/__next._head.txt +1 -1
  68. package/dist/admin/services/__next._index.txt +1 -1
  69. package/dist/admin/services/__next._tree.txt +1 -1
  70. package/dist/admin/services/index.html +1 -1
  71. package/dist/admin/services/index.txt +1 -1
  72. package/dist/admin/storage/__next.!KGRlZmF1bHQp.storage.__PAGE__.txt +1 -1
  73. package/dist/admin/storage/__next.!KGRlZmF1bHQp.storage.txt +1 -1
  74. package/dist/admin/storage/__next.!KGRlZmF1bHQp.txt +1 -1
  75. package/dist/admin/storage/__next._full.txt +1 -1
  76. package/dist/admin/storage/__next._head.txt +1 -1
  77. package/dist/admin/storage/__next._index.txt +1 -1
  78. package/dist/admin/storage/__next._tree.txt +1 -1
  79. package/dist/admin/storage/index.html +1 -1
  80. package/dist/admin/storage/index.txt +1 -1
  81. package/dist/admin/workflows/__next.!KGRlZmF1bHQp.txt +1 -1
  82. package/dist/admin/workflows/__next.!KGRlZmF1bHQp.workflows.__PAGE__.txt +1 -1
  83. package/dist/admin/workflows/__next.!KGRlZmF1bHQp.workflows.txt +1 -1
  84. package/dist/admin/workflows/__next._full.txt +1 -1
  85. package/dist/admin/workflows/__next._head.txt +1 -1
  86. package/dist/admin/workflows/__next._index.txt +1 -1
  87. package/dist/admin/workflows/__next._tree.txt +1 -1
  88. package/dist/admin/workflows/index.html +1 -1
  89. package/dist/admin/workflows/index.txt +1 -1
  90. package/dist/cli.js +242 -38
  91. package/dist/docs/observability.md +31 -2
  92. package/dist/docs/postgres.md +1 -0
  93. package/package.json +1 -1
  94. /package/dist/admin/_next/static/{qZML8zQZ-3IhVYlWFyLkg → OM2zkE6eRNMAcnrnJnUK2}/_buildManifest.js +0 -0
  95. /package/dist/admin/_next/static/{qZML8zQZ-3IhVYlWFyLkg → OM2zkE6eRNMAcnrnJnUK2}/_clientMiddlewareManifest.json +0 -0
  96. /package/dist/admin/_next/static/{qZML8zQZ-3IhVYlWFyLkg → OM2zkE6eRNMAcnrnJnUK2}/_ssgManifest.js +0 -0
package/dist/cli.js CHANGED
@@ -1118,11 +1118,11 @@ var init_open = __esm({
1118
1118
  }
1119
1119
  const subprocess = childProcess3.spawn(command, cliArguments, childProcessOptions);
1120
1120
  if (options2.wait) {
1121
- return new Promise((resolve10, reject) => {
1122
- subprocess.once("error", reject);
1121
+ return new Promise((resolve10, reject2) => {
1122
+ subprocess.once("error", reject2);
1123
1123
  subprocess.once("close", (exitCode) => {
1124
1124
  if (!options2.allowNonzeroExitCode && exitCode !== 0) {
1125
- reject(new Error(`Exited with code ${exitCode}`));
1125
+ reject2(new Error(`Exited with code ${exitCode}`));
1126
1126
  return;
1127
1127
  }
1128
1128
  resolve10(subprocess);
@@ -1130,13 +1130,13 @@ var init_open = __esm({
1130
1130
  });
1131
1131
  }
1132
1132
  if (isFallbackAttempt) {
1133
- return new Promise((resolve10, reject) => {
1134
- subprocess.once("error", reject);
1133
+ return new Promise((resolve10, reject2) => {
1134
+ subprocess.once("error", reject2);
1135
1135
  subprocess.once("spawn", () => {
1136
1136
  subprocess.once("close", (exitCode) => {
1137
- subprocess.off("error", reject);
1137
+ subprocess.off("error", reject2);
1138
1138
  if (exitCode !== 0) {
1139
- reject(new Error(`Exited with code ${exitCode}`));
1139
+ reject2(new Error(`Exited with code ${exitCode}`));
1140
1140
  return;
1141
1141
  }
1142
1142
  subprocess.unref();
@@ -1146,10 +1146,10 @@ var init_open = __esm({
1146
1146
  });
1147
1147
  }
1148
1148
  subprocess.unref();
1149
- return new Promise((resolve10, reject) => {
1150
- subprocess.once("error", reject);
1149
+ return new Promise((resolve10, reject2) => {
1150
+ subprocess.once("error", reject2);
1151
1151
  subprocess.once("spawn", () => {
1152
- subprocess.off("error", reject);
1152
+ subprocess.off("error", reject2);
1153
1153
  resolve10(subprocess);
1154
1154
  });
1155
1155
  });
@@ -184314,9 +184314,9 @@ var NodeFsHandler = class {
184314
184314
  this._addToNodeFs(path28, initialAdd, wh, depth + 1);
184315
184315
  }
184316
184316
  }).on(EV.ERROR, this._boundHandleError);
184317
- return new Promise((resolve10, reject) => {
184317
+ return new Promise((resolve10, reject2) => {
184318
184318
  if (!stream)
184319
- return reject();
184319
+ return reject2();
184320
184320
  stream.once(STR_END, () => {
184321
184321
  if (this.fsw.closed) {
184322
184322
  stream = void 0;
@@ -185187,6 +185187,8 @@ import * as path4 from "path";
185187
185187
  import * as os2 from "os";
185188
185188
  import { createReadStream } from "fs";
185189
185189
  import { createTarExtractor, extractTo } from "tar-vern";
185190
+ import { timingSafeEqual } from "node:crypto";
185191
+ import { S3Client } from "@aws-sdk/client-s3";
185190
185192
  import * as net from "net";
185191
185193
  import { execFile } from "child_process";
185192
185194
  import { promisify } from "util";
@@ -185207,7 +185209,7 @@ import {
185207
185209
  HeadObjectCommand,
185208
185210
  ListObjectsV2Command,
185209
185211
  PutObjectCommand,
185210
- S3Client
185212
+ S3Client as S3Client2
185211
185213
  } from "@aws-sdk/client-s3";
185212
185214
  import * as net3 from "net";
185213
185215
  import * as fs8 from "fs";
@@ -369627,9 +369629,9 @@ async function downloadFile(url, destPath, onProgress) {
369627
369629
  });
369628
369630
  }
369629
369631
  }
369630
- await new Promise((resolve62, reject) => {
369632
+ await new Promise((resolve62, reject2) => {
369631
369633
  fileStream.end((err) => {
369632
- if (err) reject(err);
369634
+ if (err) reject2(err);
369633
369635
  else resolve62();
369634
369636
  });
369635
369637
  });
@@ -369883,6 +369885,199 @@ function terminateProcess(proc, opts = {}) {
369883
369885
  }
369884
369886
  });
369885
369887
  }
369888
+ function reject(ctx, status, code) {
369889
+ ctx.status = status;
369890
+ ctx.type = "application/xml";
369891
+ ctx.body = `<?xml version="1.0" encoding="UTF-8"?><Error><Code>${code}</Code><Message>${code}</Message></Error>`;
369892
+ }
369893
+ function dateFromAmz(value) {
369894
+ const match = /^(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})Z$/.exec(value);
369895
+ if (!match) return null;
369896
+ const date = /* @__PURE__ */ new Date(
369897
+ `${match[1]}-${match[2]}-${match[3]}T${match[4]}:${match[5]}:${match[6]}Z`
369898
+ );
369899
+ return Number.isFinite(date.getTime()) ? date : null;
369900
+ }
369901
+ function storageGuard(options2) {
369902
+ const client = new S3Client({
369903
+ region: "us-east-1",
369904
+ credentials: {
369905
+ accessKeyId: options2.accessKey,
369906
+ secretAccessKey: options2.secretKey
369907
+ }
369908
+ });
369909
+ const locks = /* @__PURE__ */ new Map();
369910
+ async function authenticated(req, url) {
369911
+ const authorization = req.headers.authorization;
369912
+ const presigned = url.searchParams.has("X-Amz-Algorithm");
369913
+ if (presigned && authorization) return false;
369914
+ let credential;
369915
+ let signedHeaders;
369916
+ let signature;
369917
+ if (presigned) {
369918
+ if (url.searchParams.get("X-Amz-Algorithm") !== "AWS4-HMAC-SHA256")
369919
+ return false;
369920
+ credential = url.searchParams.get("X-Amz-Credential") ?? "";
369921
+ signedHeaders = url.searchParams.get("X-Amz-SignedHeaders") ?? "";
369922
+ signature = url.searchParams.get("X-Amz-Signature") ?? "";
369923
+ } else {
369924
+ const parts = /^AWS4-HMAC-SHA256\s+Credential=([^,]+),\s*SignedHeaders=([^,]+),\s*Signature=([a-fA-F0-9]{64})$/.exec(
369925
+ authorization ?? ""
369926
+ );
369927
+ if (!parts) return false;
369928
+ [, credential = "", signedHeaders = "", signature = ""] = parts;
369929
+ }
369930
+ const [accessKey, day, region, service, end, extra] = credential.split("/");
369931
+ if (accessKey !== options2.accessKey || !region || service !== "s3" || end !== "aws4_request" || extra !== void 0)
369932
+ return false;
369933
+ if (!/^[a-fA-F0-9]{64}$/.test(signature)) return false;
369934
+ const names = signedHeaders.split(";");
369935
+ if (!names.includes("host") || names.join(";") !== [...new Set(names)].sort().join(";"))
369936
+ return false;
369937
+ const timestamp = presigned ? url.searchParams.get("X-Amz-Date") : req.headers["x-amz-date"];
369938
+ if (typeof timestamp !== "string" || day !== timestamp.slice(0, 8))
369939
+ return false;
369940
+ const signingDate = dateFromAmz(timestamp);
369941
+ if (!signingDate) return false;
369942
+ const age = (Date.now() - signingDate.getTime()) / 1e3;
369943
+ const expiresIn = presigned ? Number(url.searchParams.get("X-Amz-Expires")) : 900;
369944
+ if (!Number.isInteger(expiresIn) || expiresIn < 1 || expiresIn > 604800 || age < -900 || age > expiresIn)
369945
+ return false;
369946
+ const headers = {};
369947
+ for (const name of names) {
369948
+ const value = req.headers[name];
369949
+ if (typeof value !== "string" || name !== name.toLowerCase())
369950
+ return false;
369951
+ headers[name] = value;
369952
+ }
369953
+ const payloadHash = req.headers["x-amz-content-sha256"] ?? url.searchParams.get("X-Amz-Content-Sha256") ?? url.searchParams.get("x-amz-content-sha256") ?? (presigned ? "UNSIGNED-PAYLOAD" : void 0);
369954
+ if (typeof payloadHash === "string")
369955
+ headers["x-amz-content-sha256"] = payloadHash;
369956
+ const query = {};
369957
+ for (const [key, value] of url.searchParams) {
369958
+ if (key === "X-Amz-Signature") continue;
369959
+ const old = query[key];
369960
+ query[key] = old === void 0 ? value : [...Array.isArray(old) ? old : [old], value];
369961
+ }
369962
+ const request2 = {
369963
+ method: req.method ?? "GET",
369964
+ protocol: url.protocol,
369965
+ hostname: url.hostname,
369966
+ port: Number(url.port),
369967
+ path: url.pathname,
369968
+ headers,
369969
+ query
369970
+ };
369971
+ const signingOptions = {
369972
+ signingDate,
369973
+ signingRegion: region,
369974
+ signingService: "s3",
369975
+ signableHeaders: new Set(names),
369976
+ unsignableHeaders: new Set(
369977
+ Object.keys(headers).filter((name) => !names.includes(name))
369978
+ )
369979
+ };
369980
+ const signer = await client.config.signer();
369981
+ let expected;
369982
+ if (presigned) {
369983
+ const result = await signer.presign(request2, {
369984
+ ...signingOptions,
369985
+ expiresIn,
369986
+ unhoistableHeaders: new Set(Object.keys(headers))
369987
+ });
369988
+ const value = result.query?.["X-Amz-Signature"];
369989
+ expected = typeof value === "string" ? value : void 0;
369990
+ } else {
369991
+ const result = await signer.sign(request2, signingOptions);
369992
+ expected = /Signature=([a-f0-9]{64})$/.exec(
369993
+ result.headers.authorization ?? ""
369994
+ )?.[1];
369995
+ }
369996
+ return Boolean(
369997
+ expected && timingSafeEqual(
369998
+ Buffer.from(signature, "hex"),
369999
+ Buffer.from(expected, "hex")
370000
+ )
370001
+ );
370002
+ }
370003
+ return async (ctx, next) => {
370004
+ const req = ctx.req;
370005
+ if (req.method === "OPTIONS") {
370006
+ await next();
370007
+ return;
370008
+ }
370009
+ let url;
370010
+ let bucket;
370011
+ let key;
370012
+ try {
370013
+ url = new URL(req.url ?? "/", `http://${req.headers.host}`);
370014
+ const parts = url.pathname.slice(1).split("/");
370015
+ bucket = decodeURIComponent(parts.shift() ?? "");
370016
+ key = decodeURIComponent(parts.join("/"));
370017
+ } catch {
370018
+ reject(ctx, 400, "InvalidRequest");
370019
+ return;
370020
+ }
370021
+ const hasAuth = Boolean(req.headers.authorization) || [...url.searchParams.keys()].some(
370022
+ (name) => /^(x-amz-|signature$|awsaccesskeyid$)/i.test(name)
370023
+ );
370024
+ const objectRead = options2.public && bucket === options2.bucket && Boolean(key) && (req.method === "GET" || req.method === "HEAD") && !["acl", "tagging", "uploadId", "uploads", "torrent"].some(
370025
+ (name) => url.searchParams.has(name)
370026
+ );
370027
+ let authorized = false;
370028
+ try {
370029
+ authorized = hasAuth && await authenticated(req, url);
370030
+ } catch {
370031
+ }
370032
+ if (hasAuth && !authorized || !authorized && !objectRead) {
370033
+ reject(ctx, 403, "AccessDenied");
370034
+ return;
370035
+ }
370036
+ const mutation = Boolean(key) && ["PUT", "POST", "DELETE"].includes(req.method ?? "");
370037
+ if (!mutation) {
370038
+ await next();
370039
+ return;
370040
+ }
370041
+ const lockKey = `${bucket}/${key}`;
370042
+ const before = locks.get(lockKey) ?? Promise.resolve();
370043
+ let release;
370044
+ const current = new Promise((resolve62) => {
370045
+ release = resolve62;
370046
+ });
370047
+ locks.set(lockKey, current);
370048
+ await before;
370049
+ try {
370050
+ const finalWrite = req.method === "PUT" && !url.searchParams.has("uploadId") || req.method === "POST" && url.searchParams.has("uploadId");
370051
+ const noneMatch = req.headers["if-none-match"];
370052
+ const match = req.headers["if-match"];
370053
+ if (finalWrite && (noneMatch !== void 0 || match !== void 0)) {
370054
+ if (noneMatch !== void 0 && noneMatch !== "*") {
370055
+ reject(ctx, 400, "InvalidArgument");
370056
+ return;
370057
+ }
370058
+ let metadata;
370059
+ try {
370060
+ metadata = await options2.store.getMetadata(bucket, key);
370061
+ } catch (error) {
370062
+ if (error.code !== "ENOENT") throw error;
370063
+ metadata = null;
370064
+ }
370065
+ if (match !== void 0 && !metadata) {
370066
+ reject(ctx, 404, "NoSuchKey");
370067
+ return;
370068
+ }
370069
+ if (noneMatch === "*" && metadata || match !== void 0 && match !== metadata?.etag) {
370070
+ reject(ctx, 412, "PreconditionFailed");
370071
+ return;
370072
+ }
370073
+ }
370074
+ await next();
370075
+ } finally {
370076
+ release();
370077
+ if (locks.get(lockKey) === current) locks.delete(lockKey);
370078
+ }
370079
+ };
370080
+ }
369886
370081
  var execFileAsync = promisify(execFile);
369887
370082
  function isPortInUse(host, port) {
369888
370083
  return new Promise((resolve62) => {
@@ -370093,9 +370288,9 @@ async function startPostgres(pg, port, dataDir, onProgress) {
370093
370288
  env2
370094
370289
  );
370095
370290
  })(),
370096
- new Promise((_, reject) => {
370291
+ new Promise((_, reject2) => {
370097
370292
  postgres.once("exit", (code, signal) => {
370098
- reject(
370293
+ reject2(
370099
370294
  new Error(
370100
370295
  `PostgreSQL "${pg.name}" exited during startup (code ${code}, signal ${signal}). Port ${port} may already be in use by another process.`
370101
370296
  )
@@ -370195,9 +370390,18 @@ async function startStorage(storage, port, dataDir) {
370195
370390
  port,
370196
370391
  directory: storageDataPath,
370197
370392
  silent: true,
370198
- allowMismatchedSignatures: true,
370393
+ allowMismatchedSignatures: false,
370199
370394
  configureBuckets: [{ name: storage.name }]
370200
370395
  });
370396
+ server.middleware.unshift(
370397
+ storageGuard({
370398
+ bucket: storage.name,
370399
+ public: storage.public ?? false,
370400
+ accessKey,
370401
+ secretKey,
370402
+ store: server.store
370403
+ })
370404
+ );
370201
370405
  await server.run();
370202
370406
  const endpoint = `http://${host}:${port}`;
370203
370407
  return {
@@ -370224,7 +370428,7 @@ async function startStorage(storage, port, dataDir) {
370224
370428
  };
370225
370429
  }
370226
370430
  async function runCommand(command, args, env2) {
370227
- return new Promise((resolve62, reject) => {
370431
+ return new Promise((resolve62, reject2) => {
370228
370432
  const proc = spawn(command, args, {
370229
370433
  stdio: ["ignore", "pipe", "pipe"],
370230
370434
  env: env2
@@ -370237,14 +370441,14 @@ async function runCommand(command, args, env2) {
370237
370441
  if (code === 0) {
370238
370442
  resolve62();
370239
370443
  } else {
370240
- reject(new Error(`Command failed with code ${code}: ${stderr}`));
370444
+ reject2(new Error(`Command failed with code ${code}: ${stderr}`));
370241
370445
  }
370242
370446
  });
370243
- proc.on("error", reject);
370447
+ proc.on("error", reject2);
370244
370448
  });
370245
370449
  }
370246
370450
  async function createPostgresDatabase(postgresPath, dataDir, dbName, env2) {
370247
- return new Promise((resolve62, reject) => {
370451
+ return new Promise((resolve62, reject2) => {
370248
370452
  const proc = spawn(
370249
370453
  postgresPath,
370250
370454
  ["--single", "-D", dataDir, "postgres"],
@@ -370260,7 +370464,7 @@ async function createPostgresDatabase(postgresPath, dataDir, dbName, env2) {
370260
370464
  proc.on("close", (code) => {
370261
370465
  resolve62();
370262
370466
  });
370263
- proc.on("error", reject);
370467
+ proc.on("error", reject2);
370264
370468
  proc.stdin?.write(`CREATE DATABASE "${dbName}";
370265
370469
  `);
370266
370470
  proc.stdin?.end();
@@ -371190,7 +371394,7 @@ function getClient(info) {
371190
371394
  const cacheKey = `${info.host}:${info.port}:${info.accessKey}`;
371191
371395
  const cached2 = clientCache.get(cacheKey);
371192
371396
  if (cached2) return cached2;
371193
- const client = new S3Client({
371397
+ const client = new S3Client2({
371194
371398
  endpoint: `http://${info.host}:${info.port}`,
371195
371399
  region: "us-east-1",
371196
371400
  credentials: {
@@ -371776,7 +371980,7 @@ function proxyRequest(req, res, targetPort) {
371776
371980
  req.pipe(proxyReq, { end: true });
371777
371981
  }
371778
371982
  async function startAdminServer(getState, listenPort = 0, getLogsDir = () => null, getStorageInstance = () => void 0) {
371779
- return new Promise((resolve62, reject) => {
371983
+ return new Promise((resolve62, reject2) => {
371780
371984
  const server = http.createServer((req, res) => {
371781
371985
  const url = new URL(req.url || "/", "http://localhost");
371782
371986
  res.setHeader("Access-Control-Allow-Origin", "*");
@@ -371873,11 +372077,11 @@ async function startAdminServer(getState, listenPort = 0, getLogsDir = () => nul
371873
372077
  }
371874
372078
  serveStaticFile(res, pathname);
371875
372079
  });
371876
- server.on("error", reject);
372080
+ server.on("error", reject2);
371877
372081
  server.listen(listenPort, "127.0.0.1", () => {
371878
372082
  const addr = server.address();
371879
372083
  if (!addr || typeof addr === "string") {
371880
- reject(new Error("Failed to get server address"));
372084
+ reject2(new Error("Failed to get server address"));
371881
372085
  return;
371882
372086
  }
371883
372087
  const port = addr.port;
@@ -372160,13 +372364,13 @@ async function startMailServer(mail, smtpPort, apiPort) {
372160
372364
  res.writeHead(404);
372161
372365
  res.end();
372162
372366
  });
372163
- await new Promise((resolve62, reject) => {
372367
+ await new Promise((resolve62, reject2) => {
372164
372368
  smtpServer.listen(smtpPort, "127.0.0.1", () => resolve62());
372165
- smtpServer.on("error", reject);
372369
+ smtpServer.on("error", reject2);
372166
372370
  });
372167
- await new Promise((resolve62, reject) => {
372371
+ await new Promise((resolve62, reject2) => {
372168
372372
  httpServer.listen(apiPort, "127.0.0.1", () => resolve62());
372169
- httpServer.on("error", reject);
372373
+ httpServer.on("error", reject2);
372170
372374
  });
372171
372375
  const stop = async () => {
372172
372376
  httpServer.closeAllConnections();
@@ -375459,7 +375663,7 @@ async function sendEvents(events) {
375459
375663
  ...event.properties,
375460
375664
  $geoip_disable: true,
375461
375665
  $lib: "specific-cli",
375462
- $lib_version: "0.1.225"
375666
+ $lib_version: "0.1.227"
375463
375667
  }
375464
375668
  }))
375465
375669
  });
@@ -375509,7 +375713,7 @@ function trackEvent(event, properties) {
375509
375713
  distinct_id: distinctId,
375510
375714
  properties: {
375511
375715
  ...properties,
375512
- cli_version: "0.1.225",
375716
+ cli_version: "0.1.227",
375513
375717
  platform: process.platform,
375514
375718
  node_version: process.version,
375515
375719
  project_id: getProjectId()
@@ -380571,7 +380775,7 @@ function compareVersions(a, b) {
380571
380775
  return 0;
380572
380776
  }
380573
380777
  async function checkForUpdate() {
380574
- const currentVersion = "0.1.225";
380778
+ const currentVersion = "0.1.227";
380575
380779
  const response = await fetch(`${BINARIES_BASE_URL}/latest?t=${Date.now()}`);
380576
380780
  if (!response.ok) {
380577
380781
  throw new Error(`Failed to check for updates: HTTP ${response.status}`);
@@ -381222,12 +381426,12 @@ async function resolveSql(arg) {
381222
381426
  return await readStdin();
381223
381427
  }
381224
381428
  async function readStdin() {
381225
- return new Promise((resolve10, reject) => {
381429
+ return new Promise((resolve10, reject2) => {
381226
381430
  let data = "";
381227
381431
  process.stdin.setEncoding("utf-8");
381228
381432
  process.stdin.on("data", (chunk) => data += chunk);
381229
381433
  process.stdin.on("end", () => resolve10(data.trim()));
381230
- process.stdin.on("error", reject);
381434
+ process.stdin.on("error", reject2);
381231
381435
  });
381232
381436
  }
381233
381437
  function resolveOutputFormat2(format) {
@@ -381628,7 +381832,7 @@ async function feedbackCommand(message) {
381628
381832
  await client.submitFeedback({
381629
381833
  message: trimmed,
381630
381834
  projectId,
381631
- cliVersion: "0.1.225"
381835
+ cliVersion: "0.1.227"
381632
381836
  });
381633
381837
  trackEvent("feedback_submitted", { hasProject: !!projectId });
381634
381838
  console.log("Thanks! We will take a look at your feedback shortly.");
@@ -381647,7 +381851,7 @@ async function feedbackCommand(message) {
381647
381851
  var program = new Command();
381648
381852
  var env = "production";
381649
381853
  var envLabel = env !== "production" ? `[${env.toUpperCase()}] ` : "";
381650
- program.name("specific").description(`${envLabel}Infrastructure-as-code for coding agents`).version("0.1.225").enablePositionalOptions();
381854
+ program.name("specific").description(`${envLabel}Infrastructure-as-code for coding agents`).version("0.1.227").enablePositionalOptions();
381651
381855
  program.command("init").description("Initialize project for use with a coding agent").option("--agent <name...>", "Agents to configure (cursor, claude, codex, other)").addHelpText("after", `
381652
381856
  Examples:
381653
381857
  $ specific init
@@ -142,11 +142,11 @@ OpenTelemetry spans. Today every row is the edge span the platform's ingress rec
142
142
  | Key | Notes |
143
143
  |---|---|
144
144
  | `http.method` | `GET`, `POST`, … |
145
- | `http.target` | Request path including the query string |
145
+ | `http.target` | Request path. Newly collected spans omit query strings, fragments, and URL userinfo; older retained rows may still contain them |
146
146
  | `http.route` | Path with ID-looking segments replaced by `{id}` (`/api/users/{id}`) and static-asset file names by `{file}.ext` (`/assets/{file}.svg`) — a heuristic, since the platform has no route manifest; group by this for per-endpoint views |
147
147
  | `http.status_code` | HTTP status as a string — `toUInt16OrZero(SpanAttributes['http.status_code']) >= 500` |
148
148
  | `http.server_name` | Hostname the request arrived on |
149
- | `http.user_agent`, `http.referer`, `http.x_forwarded_for`, `net.peer.ip` | Client details |
149
+ | `http.user_agent`, `http.x_forwarded_for`, `net.peer.ip` | Client details |
150
150
  | `nginx.upstream_header_time` | Seconds until your service sent its response headers (time to first byte) — how fast the service answered. Unaffected by streamed responses and slow client downloads, so use this for "is my service slow". A comma-separated list when nginx retried another pod; absent when the request never reached your service (nginx's own 408/499) |
151
151
  | `nginx.upstream_response_time` | Seconds until your service finished sending its response. `Duration` additionally runs until the client received the last byte. Same list/absent conventions as above |
152
152
 
@@ -154,6 +154,35 @@ Also exposed but rarely queried directly: `EnvironmentId`, `ProjectId`.
154
154
 
155
155
  Retention: up to 30 days. Your plan may further restrict how far back queries can read; when that cutoff is in effect, `specific query` prints a one-line note on stderr.
156
156
 
157
+ ### Ingress URL privacy
158
+
159
+ After the updated ingress collector configuration rolls out, newly collected
160
+ analytics and request spans omit all query strings, fragments and leading HTTP(S)
161
+ URL userinfo from URL fields and span names. Standalone `url.query` and
162
+ `url.fragment` attributes are removed. Raw Referer is dropped entirely: neither `http.referer` nor its originating
163
+ header is exported. For dashboard analytics only, nginx derives an ICANN
164
+ registrable domain (for example, `news.ycombinator.com/item?id=123` becomes
165
+ `ycombinator.com`, and `www.google.co.uk` becomes `google.co.uk`). Subdomains,
166
+ paths, query strings, fragments, ports and userinfo are not retained as referral
167
+ attribution. Private hosting suffixes are collapsed too: `tenant.github.io`
168
+ becomes `github.io`. IP addresses, unknown suffixes, credentials in the authority,
169
+ malformed URLs and duplicate headers receive no attribution. This field is not
170
+ exported to request traces. Top Referrers uses only the derived domain, with no
171
+ fallback to historical raw URLs. Self-referrals are excluded at the same domain
172
+ granularity, so other sites sharing a parent domain (including `spcf.app`) are
173
+ grouped and excluded together. Domains themselves may still contain sensitive
174
+ information; this is data minimization, not universal secret detection.
175
+ Paths, HTTP status, method and latency remain available; query-dependent RSC and
176
+ Referer-based ACME exclusions still apply to analytics, not request traces.
177
+
178
+ This is a telemetry-only policy: it does not change URLs or headers forwarded to
179
+ your application. Historical rows and copies are not deleted by this change and
180
+ can still contain unredacted data. This is not universal secret detection:
181
+ credentials in path segments, custom attributes or application logs are outside
182
+ this policy. URL-privacy transformation errors reject the incoming telemetry
183
+ payload, potentially losing otherwise valid spans; they do not reject the
184
+ application's HTTP request.
185
+
157
186
  ### `observability.events`
158
187
 
159
188
  Kubernetes events about your service pods (probe failures, restarts, out-of-memory kills, scheduling problems), in the OpenTelemetry log-record shape. Retention: up to 30 days.
@@ -48,6 +48,7 @@ The following PostgreSQL extensions are available in both development and produc
48
48
  - **btree_gist** (`btree_gist`) - GiST operator classes for B-tree types (int, text, timestamp, uuid, ...), enabling exclusion constraints (e.g. preventing overlapping time ranges per resource) and multicolumn GiST indexes that mix scalar and GiST-native types. Enable with `CREATE EXTENSION btree_gist;` in a migration. See https://www.postgresql.org/docs/current/btree-gist.html for full documentation.
49
49
  - **unaccent** (`unaccent`) - Text search dictionary that strips accents/diacritics, for accent-insensitive search and matching. Enable with `CREATE EXTENSION unaccent;` in a migration. See https://www.postgresql.org/docs/current/unaccent.html for full documentation.
50
50
  - **pgcrypto** (`pgcrypto`) - Cryptographic functions: digests and HMAC (SHA-2 family), password hashing with `crypt()`/`gen_salt()` (including bcrypt), PGP symmetric and public-key encryption, and `gen_random_uuid()`/`gen_random_bytes()`. Enable with `CREATE EXTENSION pgcrypto;` in a migration. See https://www.postgresql.org/docs/current/pgcrypto.html for full documentation.
51
+ - **uuid-ossp** (`uuid-ossp`) - UUID generation functions `uuid_generate_v1()`, `uuid_generate_v3()`, `uuid_generate_v4()`, and `uuid_generate_v5()`, for schemas and migrations written against them (e.g. Supabase or Prisma `dbgenerated("uuid_generate_v4()")`). For new code, the built-in `gen_random_uuid()` needs no extension. Enable with `CREATE EXTENSION "uuid-ossp";` in a migration (the quotes are required). See https://www.postgresql.org/docs/current/uuid-ossp.html for full documentation.
51
52
  - **pgRouting** (`pgrouting`) - Geospatial routing and network analysis for transportation networks, logistics, and path-finding. Requires PostGIS. Enable with `CREATE EXTENSION pgrouting CASCADE;` in a migration. See https://docs.pgrouting.org/ for full documentation.
52
53
 
53
54
  ## Graph data
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@specific.dev/cli",
3
- "version": "0.1.225",
3
+ "version": "0.1.227",
4
4
  "description": "CLI for Specific infrastructure-as-code",
5
5
  "type": "module",
6
6
  "main": "dist/cli.js",