@patchstack/connect 0.5.3 → 0.5.4

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/protect.cjs CHANGED
@@ -158,6 +158,46 @@ var init_types = __esm({
158
158
  }
159
159
  code;
160
160
  cause;
161
+ /**
162
+ * For a `VALIDATION_ERROR`, the request fields the server refused, as it named them. Read from the
163
+ * structured `errors` object a validation response carries, so a caller deciding what to do about a
164
+ * refusal keys on the field rather than on the wording of a sentence.
165
+ */
166
+ fields = [];
167
+ };
168
+ }
169
+ });
170
+
171
+ // src/hosting.ts
172
+ function detectHostingPlatform(env = process.env) {
173
+ for (const rule of RULES) {
174
+ const evidence = rule.any.filter((name) => present(env, name));
175
+ if (evidence.length > 0) {
176
+ return { platform: rule.platform, evidence };
177
+ }
178
+ }
179
+ return { platform: null, evidence: [] };
180
+ }
181
+ var RULES, present;
182
+ var init_hosting = __esm({
183
+ "src/hosting.ts"() {
184
+ "use strict";
185
+ RULES = [
186
+ { platform: "netlify", any: ["NETLIFY", "NETLIFY_BUILD_BASE", "DEPLOY_PRIME_URL"] },
187
+ { platform: "vercel", any: ["VERCEL", "VERCEL_ENV", "VERCEL_URL"] },
188
+ { platform: "cloudflare", any: ["CF_PAGES", "CF_PAGES_URL", "CLOUDFLARE_ACCOUNT_ID"] },
189
+ { platform: "aws", any: ["AWS_APP_ID", "AWS_LAMBDA_FUNCTION_NAME", "AWS_EXECUTION_ENV"] },
190
+ { platform: "render", any: ["RENDER", "RENDER_SERVICE_ID", "RENDER_EXTERNAL_URL"] },
191
+ { platform: "railway", any: ["RAILWAY_ENVIRONMENT", "RAILWAY_ENVIRONMENT_NAME", "RAILWAY_PROJECT_ID"] },
192
+ { platform: "fly", any: ["FLY_APP_NAME", "FLY_REGION", "FLY_ALLOC_ID"] },
193
+ { platform: "heroku", any: ["DYNO", "HEROKU_APP_NAME"] },
194
+ { platform: "google-cloud", any: ["K_SERVICE", "GAE_APPLICATION", "GAE_SERVICE"] },
195
+ { platform: "deno-deploy", any: ["DENO_DEPLOYMENT_ID"] },
196
+ { platform: "azure", any: ["WEBSITE_SITE_NAME", "WEBSITE_INSTANCE_ID"] }
197
+ ];
198
+ present = (env, name) => {
199
+ const value = env[name];
200
+ return value !== void 0 && value !== "";
161
201
  };
162
202
  }
163
203
  });
@@ -183,14 +223,22 @@ function authFailureMessage(status, config) {
183
223
  }
184
224
  return null;
185
225
  }
186
- function buildManifestBody(config, payload) {
226
+ function buildManifestBody(config, payload, env = process.env) {
227
+ const hosting = config.environment === "local" ? null : detectHostingPlatform(env);
187
228
  return {
188
229
  ...payload,
189
230
  environment: config.environment,
190
231
  ...typeof config.siteUrl === "string" && config.siteUrl !== "" ? { url: config.siteUrl } : {},
191
- ...typeof config.siteName === "string" && config.siteName !== "" ? { name: config.siteName } : {}
232
+ ...typeof config.siteName === "string" && config.siteName !== "" ? { name: config.siteName } : {},
233
+ ...hosting !== null && hosting.platform !== null ? { hosting } : {}
192
234
  };
193
235
  }
236
+ function validationFields(body) {
237
+ if (typeof body !== "object" || body === null) return [];
238
+ const errors = body.errors;
239
+ if (typeof errors !== "object" || errors === null || Array.isArray(errors)) return [];
240
+ return Object.keys(errors);
241
+ }
194
242
  async function postManifest(config, payload) {
195
243
  const url = buildEndpointUrl(config.endpoint, config.siteUuid);
196
244
  const timeoutMs = config.timeoutMs;
@@ -235,10 +283,12 @@ async function postManifest(config, payload) {
235
283
  );
236
284
  }
237
285
  if (response.status === 422) {
238
- throw new PatchstackError(
286
+ const refused2 = new PatchstackError(
239
287
  body?.message ?? "Patchstack rejected the manifest payload (validation failed).",
240
288
  "VALIDATION_ERROR"
241
289
  );
290
+ refused2.fields = validationFields(body);
291
+ throw refused2;
242
292
  }
243
293
  const refused = authFailureMessage(response.status, config);
244
294
  if (refused !== null) {
@@ -266,6 +316,7 @@ var init_client = __esm({
266
316
  "src/client.ts"() {
267
317
  "use strict";
268
318
  init_types();
319
+ init_hosting();
269
320
  init_pulse_token();
270
321
  init_build_id();
271
322
  DEFAULT_ENDPOINT = "https://api.patchstack.com/monitor/pulse/manifest";
@@ -274,6 +325,54 @@ var init_client = __esm({
274
325
  }
275
326
  });
276
327
 
328
+ // src/environment.ts
329
+ function inferEnvironment(env = process.env) {
330
+ for (const discriminator of DISCRIMINATORS) {
331
+ const verdict = discriminator.read(env);
332
+ if (verdict !== null) {
333
+ return { environment: verdict.environment, evidence: [`${discriminator.platform}: ${verdict.evidence}`] };
334
+ }
335
+ }
336
+ return { environment: "local", evidence: [] };
337
+ }
338
+ var set, DISCRIMINATORS;
339
+ var init_environment = __esm({
340
+ "src/environment.ts"() {
341
+ "use strict";
342
+ set = (value) => value !== void 0 && value !== "";
343
+ DISCRIMINATORS = [
344
+ {
345
+ platform: "vercel",
346
+ read: (env) => {
347
+ if (!set(env.VERCEL) || !set(env.VERCEL_ENV)) return null;
348
+ return env.VERCEL_ENV === "production" ? { environment: "production", evidence: "VERCEL_ENV=production" } : { environment: "sandbox", evidence: `VERCEL_ENV=${env.VERCEL_ENV}` };
349
+ }
350
+ },
351
+ {
352
+ platform: "netlify",
353
+ read: (env) => {
354
+ if (env.NETLIFY !== "true" || !set(env.CONTEXT)) return null;
355
+ return env.CONTEXT === "production" ? { environment: "production", evidence: "CONTEXT=production" } : { environment: "sandbox", evidence: `CONTEXT=${env.CONTEXT}` };
356
+ }
357
+ },
358
+ {
359
+ platform: "render",
360
+ read: (env) => {
361
+ if (env.RENDER !== "true") return null;
362
+ return env.IS_PULL_REQUEST === "true" ? { environment: "sandbox", evidence: "IS_PULL_REQUEST=true" } : { environment: "production", evidence: "RENDER=true, not a pull request" };
363
+ }
364
+ },
365
+ {
366
+ platform: "railway",
367
+ read: (env) => {
368
+ if (!set(env.RAILWAY_ENVIRONMENT_NAME)) return null;
369
+ return env.RAILWAY_ENVIRONMENT_NAME === "production" ? { environment: "production", evidence: "RAILWAY_ENVIRONMENT_NAME=production" } : { environment: "sandbox", evidence: `RAILWAY_ENVIRONMENT_NAME=${env.RAILWAY_ENVIRONMENT_NAME}` };
370
+ }
371
+ }
372
+ ];
373
+ }
374
+ });
375
+
277
376
  // src/site-url.ts
278
377
  function isUnpublishableHost(hostname) {
279
378
  const host = hostname.toLowerCase().replace(/\.+$/, "");
@@ -485,11 +584,13 @@ async function resolveConfig(options) {
485
584
  const environmentRaw = fromEnv.environment ?? fromFile.environment;
486
585
  if (environmentRaw !== void 0 && !isEnvironment(environmentRaw)) {
487
586
  throw new PatchstackError(
488
- `Environment must be "production" or "sandbox"; got "${environmentRaw}".`,
587
+ `Environment must be "production", "sandbox" or "local"; got "${environmentRaw}".`,
489
588
  "CONFIG_INVALID"
490
589
  );
491
590
  }
492
- const environment = environmentRaw ?? DEFAULT_ENVIRONMENT;
591
+ const inferred = environmentRaw === void 0 ? inferEnvironment(process.env) : null;
592
+ const environment = environmentRaw ?? inferred.environment;
593
+ const environmentEvidence = inferred?.evidence ?? [];
493
594
  if (siteUuid !== null && siteUuid.length > 0 && !isUuid(siteUuid)) {
494
595
  throw new PatchstackError(
495
596
  `Site UUID "${siteUuid}" does not look like a valid UUID.`,
@@ -515,6 +616,7 @@ async function resolveConfig(options) {
515
616
  endpoint,
516
617
  timeoutMs,
517
618
  environment,
619
+ environmentEvidence,
518
620
  widget: fromFile.widget !== false,
519
621
  claimToken
520
622
  };
@@ -580,9 +682,9 @@ function isUuid(value) {
580
682
  return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value);
581
683
  }
582
684
  function isEnvironment(value) {
583
- return value === "production" || value === "sandbox";
685
+ return value === "production" || value === "sandbox" || value === "local";
584
686
  }
585
- var import_promises2, import_node_path2, CONFIG_FILENAME, SECRET_FILENAME, DEFAULT_ENVIRONMENT;
687
+ var import_promises2, import_node_path2, CONFIG_FILENAME, SECRET_FILENAME;
586
688
  var init_config = __esm({
587
689
  "src/config.ts"() {
588
690
  "use strict";
@@ -590,11 +692,11 @@ var init_config = __esm({
590
692
  import_node_path2 = __toESM(require("path"), 1);
591
693
  init_types();
592
694
  init_client();
695
+ init_environment();
593
696
  init_site_url();
594
697
  init_site_name();
595
698
  CONFIG_FILENAME = ".patchstackrc.json";
596
699
  SECRET_FILENAME = ".patchstackrc.local.json";
597
- DEFAULT_ENVIRONMENT = "production";
598
700
  }
599
701
  });
600
702
 
@@ -631,8 +733,8 @@ async function readDeclaredDependencyNames(cwd) {
631
733
  return [...names];
632
734
  }
633
735
  function missingDependencies(declared, packages) {
634
- const present = new Set(packages.map((entry) => entry.name));
635
- return declared.filter((name) => !present.has(name));
736
+ const present2 = new Set(packages.map((entry) => entry.name));
737
+ return declared.filter((name) => !present2.has(name));
636
738
  }
637
739
  function disagreements(packages, other) {
638
740
  const otherVersions = /* @__PURE__ */ new Map();
@@ -6985,8 +7087,8 @@ async function createProtection(options = {}) {
6985
7087
  hardened = true;
6986
7088
  try {
6987
7089
  if (res.headersSent || typeof res.setHeader !== "function") return null;
6988
- const set = typeof res.getHeaders === "function" ? res.getHeaders() : {};
6989
- const headers = effective && effective.headers ? { ...set, ...effective.headers } : set;
7090
+ const set2 = typeof res.getHeaders === "function" ? res.getHeaders() : {};
7091
+ const headers = effective && effective.headers ? { ...set2, ...effective.headers } : set2;
6990
7092
  const status = effective && effective.status !== void 0 ? effective.status : res.statusCode;
6991
7093
  const r = screenText("", { status, headers }, reqCtx, hardensWithoutBody);
6992
7094
  answeredWithoutBody = true;
@@ -7698,8 +7800,8 @@ function applyHeaderMutation(headers, rule) {
7698
7800
  const ensure = rule.ensure === true;
7699
7801
  for (const [name, value] of Object.entries(rule.set_headers ?? {})) {
7700
7802
  const key = String(name).toLowerCase();
7701
- const present = headers[key] != null && headers[key] !== "";
7702
- if (ensure && present) continue;
7803
+ const present2 = headers[key] != null && headers[key] !== "";
7804
+ if (ensure && present2) continue;
7703
7805
  headers[key] = String(value);
7704
7806
  }
7705
7807
  return;