@patchstack/connect 0.5.3 → 0.5.5
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/AGENT-INSTALL.md +4 -4
- package/README.md +5 -5
- package/dist/cli.js +3802 -3306
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +150 -28
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +68 -55
- package/dist/index.d.ts +68 -55
- package/dist/index.js +149 -27
- package/dist/index.js.map +1 -1
- package/dist/protect.cjs +200 -67
- package/dist/protect.cjs.map +1 -1
- package/dist/protect.js +1 -1
- package/dist/{refresh-manifest-3QGLYXV5.js → refresh-manifest-2ALIJGZ3.js} +152 -33
- package/dist/refresh-manifest-2ALIJGZ3.js.map +1 -0
- package/package.json +1 -1
- package/dist/refresh-manifest-3QGLYXV5.js.map +0 -1
package/dist/index.d.cts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
type Ecosystem = 'npm' | 'composer';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
3
|
+
* Where a manifest was built. `production` only when the hosting platform's own discriminator says this
|
|
4
|
+
* build is the production one; `sandbox` for a preview it names as such, or a hosted builder's workspace;
|
|
5
|
+
* `local` for everything else, a developer's machine included — inventory, never evidence of a live site.
|
|
6
|
+
* Inferred when nothing sets it (see `environment.ts`); `PATCHSTACK_ENVIRONMENT` or `"environment"` in
|
|
7
|
+
* .patchstackrc.json overrides.
|
|
7
8
|
*/
|
|
8
|
-
type Environment = 'production' | 'sandbox';
|
|
9
|
+
type Environment = 'production' | 'sandbox' | 'local';
|
|
9
10
|
interface PackageEntry {
|
|
10
11
|
name: string;
|
|
11
12
|
version: string;
|
|
@@ -59,8 +60,14 @@ interface Config {
|
|
|
59
60
|
siteName?: string | null;
|
|
60
61
|
endpoint: string;
|
|
61
62
|
timeoutMs: number;
|
|
62
|
-
/** Environment to report the manifest under.
|
|
63
|
+
/** Environment to report the manifest under. Inferred from the build environment when not stated. */
|
|
63
64
|
environment: Environment;
|
|
65
|
+
/**
|
|
66
|
+
* What decided `environment` when nothing set it: the hosting platform's own production or preview
|
|
67
|
+
* discriminator. Empty when it was stated, and empty for `local`, which is decided by the absence of
|
|
68
|
+
* any such evidence.
|
|
69
|
+
*/
|
|
70
|
+
environmentEvidence?: string[];
|
|
64
71
|
/**
|
|
65
72
|
* Whether the connector manages the disclosure-widget tag (source shell on
|
|
66
73
|
* `scan`, built HTML on `mark-build`). Defaults to true; persist
|
|
@@ -109,6 +116,12 @@ interface StoreManifestResponse {
|
|
|
109
116
|
declare class PatchstackError extends Error {
|
|
110
117
|
readonly code: 'CONFIG_MISSING' | 'CONFIG_INVALID' | 'LOCKFILE_NOT_FOUND' | 'LOCKFILE_UNSUPPORTED' | 'LOCKFILE_PARSE_ERROR' | 'NETWORK_ERROR' | 'NETWORK_TIMEOUT' | 'SITE_NOT_FOUND' | 'UNAUTHORIZED' | 'VALIDATION_ERROR' | 'SERVER_ERROR';
|
|
111
118
|
readonly cause?: unknown | undefined;
|
|
119
|
+
/**
|
|
120
|
+
* For a `VALIDATION_ERROR`, the request fields the server refused, as it named them. Read from the
|
|
121
|
+
* structured `errors` object a validation response carries, so a caller deciding what to do about a
|
|
122
|
+
* refusal keys on the field rather than on the wording of a sentence.
|
|
123
|
+
*/
|
|
124
|
+
fields: readonly string[];
|
|
112
125
|
constructor(message: string, code: 'CONFIG_MISSING' | 'CONFIG_INVALID' | 'LOCKFILE_NOT_FOUND' | 'LOCKFILE_UNSUPPORTED' | 'LOCKFILE_PARSE_ERROR' | 'NETWORK_ERROR' | 'NETWORK_TIMEOUT' | 'SITE_NOT_FOUND' | 'UNAUTHORIZED' | 'VALIDATION_ERROR' | 'SERVER_ERROR', cause?: unknown | undefined);
|
|
113
126
|
}
|
|
114
127
|
|
|
@@ -185,6 +198,55 @@ interface WireOptions {
|
|
|
185
198
|
declare function buildWirePayload(manifest: Manifest, options?: WireOptions): NormalizeResult;
|
|
186
199
|
declare function compareVersions(a: string, b: string): number;
|
|
187
200
|
|
|
201
|
+
/**
|
|
202
|
+
* A best-effort description of the stack a build was produced with, derived
|
|
203
|
+
* entirely from the lockfile (ground truth) plus the build-time environment.
|
|
204
|
+
*
|
|
205
|
+
* The disclosure widget reads this from `window.__PATCHSTACK_STACK__` (injected
|
|
206
|
+
* by `mark-build`) and reports it to Patchstack, so we learn how the sites we
|
|
207
|
+
* protect are actually built and hosted — across every "vibe" platform — without
|
|
208
|
+
* shipping a runtime probe onto the host server. Every field is a coarse label
|
|
209
|
+
* or a bare key *name*: no versions beyond the framework, and never an env value.
|
|
210
|
+
*/
|
|
211
|
+
interface StackDescriptor {
|
|
212
|
+
/** App / meta-framework, e.g. "next", "nuxt", "tanstack-start", "remix". */
|
|
213
|
+
framework: string | null;
|
|
214
|
+
/** UI runtime, e.g. "react", "vue", "svelte", "solid". */
|
|
215
|
+
ui: string | null;
|
|
216
|
+
/** Build tool / bundler, e.g. "vite", "webpack", "rspack". */
|
|
217
|
+
bundler: string | null;
|
|
218
|
+
/** Deployment-runtime hint from deps, e.g. "cloudflare-workers", "vercel". */
|
|
219
|
+
runtime: string | null;
|
|
220
|
+
/** The vibe/builder platform that generated the project, e.g. "lovable". */
|
|
221
|
+
builder: string | null;
|
|
222
|
+
/** Package ecosystem the manifest came from. */
|
|
223
|
+
ecosystem: 'npm';
|
|
224
|
+
/** Hosting-related build-environment variable NAMES (never their values). */
|
|
225
|
+
hostingEnvKeys: string[];
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* An environment, described structurally so it needs no ambient types.
|
|
229
|
+
*
|
|
230
|
+
* `@types/node` is not a dependency of this package, so nothing in its public surface may name
|
|
231
|
+
* `NodeJS.ProcessEnv`: a consumer without those types cannot compile against a signature that does.
|
|
232
|
+
* `process.env` satisfies this, and so does a plain object.
|
|
233
|
+
*/
|
|
234
|
+
type EnvLike = Record<string, string | undefined>;
|
|
235
|
+
/**
|
|
236
|
+
* Return the sorted NAMES of hosting-related environment variables present in
|
|
237
|
+
* `env`. Only names are surfaced — never values — so a build fingerprint can
|
|
238
|
+
* distinguish "deployed on Cloudflare" from "deployed on Vercel" without ever
|
|
239
|
+
* disclosing a secret.
|
|
240
|
+
*/
|
|
241
|
+
declare function collectHostingEnvKeys(env?: EnvLike): string[];
|
|
242
|
+
/**
|
|
243
|
+
* Derive a {@link StackDescriptor} from the wire packages and the build
|
|
244
|
+
* environment. Never throws: an unrecognised stack just yields nulls.
|
|
245
|
+
*/
|
|
246
|
+
declare function detectStack(packages: readonly WirePackage[], env?: EnvLike): StackDescriptor;
|
|
247
|
+
/** True when the descriptor carries no useful signal (nothing worth injecting). */
|
|
248
|
+
declare function isEmptyStack(stack: StackDescriptor): boolean;
|
|
249
|
+
|
|
188
250
|
declare const DEFAULT_ENDPOINT = "https://api.patchstack.com/monitor/pulse/manifest";
|
|
189
251
|
declare function buildEndpointUrl(base: string, siteUuid?: string | null): string;
|
|
190
252
|
/**
|
|
@@ -295,55 +357,6 @@ declare function persistApiKey(cwd: string, apiKey: string): Promise<SecretFileR
|
|
|
295
357
|
*/
|
|
296
358
|
declare function persistPulseAuth(cwd: string, pulseAuth: string): Promise<SecretFileResult>;
|
|
297
359
|
|
|
298
|
-
/**
|
|
299
|
-
* A best-effort description of the stack a build was produced with, derived
|
|
300
|
-
* entirely from the lockfile (ground truth) plus the build-time environment.
|
|
301
|
-
*
|
|
302
|
-
* The disclosure widget reads this from `window.__PATCHSTACK_STACK__` (injected
|
|
303
|
-
* by `mark-build`) and reports it to Patchstack, so we learn how the sites we
|
|
304
|
-
* protect are actually built and hosted — across every "vibe" platform — without
|
|
305
|
-
* shipping a runtime probe onto the host server. Every field is a coarse label
|
|
306
|
-
* or a bare key *name*: no versions beyond the framework, and never an env value.
|
|
307
|
-
*/
|
|
308
|
-
interface StackDescriptor {
|
|
309
|
-
/** App / meta-framework, e.g. "next", "nuxt", "tanstack-start", "remix". */
|
|
310
|
-
framework: string | null;
|
|
311
|
-
/** UI runtime, e.g. "react", "vue", "svelte", "solid". */
|
|
312
|
-
ui: string | null;
|
|
313
|
-
/** Build tool / bundler, e.g. "vite", "webpack", "rspack". */
|
|
314
|
-
bundler: string | null;
|
|
315
|
-
/** Deployment-runtime hint from deps, e.g. "cloudflare-workers", "vercel". */
|
|
316
|
-
runtime: string | null;
|
|
317
|
-
/** The vibe/builder platform that generated the project, e.g. "lovable". */
|
|
318
|
-
builder: string | null;
|
|
319
|
-
/** Package ecosystem the manifest came from. */
|
|
320
|
-
ecosystem: 'npm';
|
|
321
|
-
/** Hosting-related build-environment variable NAMES (never their values). */
|
|
322
|
-
hostingEnvKeys: string[];
|
|
323
|
-
}
|
|
324
|
-
/**
|
|
325
|
-
* An environment, described structurally so it needs no ambient types.
|
|
326
|
-
*
|
|
327
|
-
* `@types/node` is not a dependency of this package, so nothing in its public surface may name
|
|
328
|
-
* `NodeJS.ProcessEnv`: a consumer without those types cannot compile against a signature that does.
|
|
329
|
-
* `process.env` satisfies this, and so does a plain object.
|
|
330
|
-
*/
|
|
331
|
-
type EnvLike = Record<string, string | undefined>;
|
|
332
|
-
/**
|
|
333
|
-
* Return the sorted NAMES of hosting-related environment variables present in
|
|
334
|
-
* `env`. Only names are surfaced — never values — so a build fingerprint can
|
|
335
|
-
* distinguish "deployed on Cloudflare" from "deployed on Vercel" without ever
|
|
336
|
-
* disclosing a secret.
|
|
337
|
-
*/
|
|
338
|
-
declare function collectHostingEnvKeys(env?: EnvLike): string[];
|
|
339
|
-
/**
|
|
340
|
-
* Derive a {@link StackDescriptor} from the wire packages and the build
|
|
341
|
-
* environment. Never throws: an unrecognised stack just yields nulls.
|
|
342
|
-
*/
|
|
343
|
-
declare function detectStack(packages: readonly WirePackage[], env?: EnvLike): StackDescriptor;
|
|
344
|
-
/** True when the descriptor carries no useful signal (nothing worth injecting). */
|
|
345
|
-
declare function isEmptyStack(stack: StackDescriptor): boolean;
|
|
346
|
-
|
|
347
360
|
declare const WIDGET_SCRIPT_URL = "https://cdn.patchstack.com/patchstack-widget.js";
|
|
348
361
|
/** Attribute that tags the connector-managed widget tag so re-runs update it. */
|
|
349
362
|
declare const WIDGET_MARKER_ATTR = "data-patchstack-connect-widget";
|
package/dist/index.d.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
type Ecosystem = 'npm' | 'composer';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
3
|
+
* Where a manifest was built. `production` only when the hosting platform's own discriminator says this
|
|
4
|
+
* build is the production one; `sandbox` for a preview it names as such, or a hosted builder's workspace;
|
|
5
|
+
* `local` for everything else, a developer's machine included — inventory, never evidence of a live site.
|
|
6
|
+
* Inferred when nothing sets it (see `environment.ts`); `PATCHSTACK_ENVIRONMENT` or `"environment"` in
|
|
7
|
+
* .patchstackrc.json overrides.
|
|
7
8
|
*/
|
|
8
|
-
type Environment = 'production' | 'sandbox';
|
|
9
|
+
type Environment = 'production' | 'sandbox' | 'local';
|
|
9
10
|
interface PackageEntry {
|
|
10
11
|
name: string;
|
|
11
12
|
version: string;
|
|
@@ -59,8 +60,14 @@ interface Config {
|
|
|
59
60
|
siteName?: string | null;
|
|
60
61
|
endpoint: string;
|
|
61
62
|
timeoutMs: number;
|
|
62
|
-
/** Environment to report the manifest under.
|
|
63
|
+
/** Environment to report the manifest under. Inferred from the build environment when not stated. */
|
|
63
64
|
environment: Environment;
|
|
65
|
+
/**
|
|
66
|
+
* What decided `environment` when nothing set it: the hosting platform's own production or preview
|
|
67
|
+
* discriminator. Empty when it was stated, and empty for `local`, which is decided by the absence of
|
|
68
|
+
* any such evidence.
|
|
69
|
+
*/
|
|
70
|
+
environmentEvidence?: string[];
|
|
64
71
|
/**
|
|
65
72
|
* Whether the connector manages the disclosure-widget tag (source shell on
|
|
66
73
|
* `scan`, built HTML on `mark-build`). Defaults to true; persist
|
|
@@ -109,6 +116,12 @@ interface StoreManifestResponse {
|
|
|
109
116
|
declare class PatchstackError extends Error {
|
|
110
117
|
readonly code: 'CONFIG_MISSING' | 'CONFIG_INVALID' | 'LOCKFILE_NOT_FOUND' | 'LOCKFILE_UNSUPPORTED' | 'LOCKFILE_PARSE_ERROR' | 'NETWORK_ERROR' | 'NETWORK_TIMEOUT' | 'SITE_NOT_FOUND' | 'UNAUTHORIZED' | 'VALIDATION_ERROR' | 'SERVER_ERROR';
|
|
111
118
|
readonly cause?: unknown | undefined;
|
|
119
|
+
/**
|
|
120
|
+
* For a `VALIDATION_ERROR`, the request fields the server refused, as it named them. Read from the
|
|
121
|
+
* structured `errors` object a validation response carries, so a caller deciding what to do about a
|
|
122
|
+
* refusal keys on the field rather than on the wording of a sentence.
|
|
123
|
+
*/
|
|
124
|
+
fields: readonly string[];
|
|
112
125
|
constructor(message: string, code: 'CONFIG_MISSING' | 'CONFIG_INVALID' | 'LOCKFILE_NOT_FOUND' | 'LOCKFILE_UNSUPPORTED' | 'LOCKFILE_PARSE_ERROR' | 'NETWORK_ERROR' | 'NETWORK_TIMEOUT' | 'SITE_NOT_FOUND' | 'UNAUTHORIZED' | 'VALIDATION_ERROR' | 'SERVER_ERROR', cause?: unknown | undefined);
|
|
113
126
|
}
|
|
114
127
|
|
|
@@ -185,6 +198,55 @@ interface WireOptions {
|
|
|
185
198
|
declare function buildWirePayload(manifest: Manifest, options?: WireOptions): NormalizeResult;
|
|
186
199
|
declare function compareVersions(a: string, b: string): number;
|
|
187
200
|
|
|
201
|
+
/**
|
|
202
|
+
* A best-effort description of the stack a build was produced with, derived
|
|
203
|
+
* entirely from the lockfile (ground truth) plus the build-time environment.
|
|
204
|
+
*
|
|
205
|
+
* The disclosure widget reads this from `window.__PATCHSTACK_STACK__` (injected
|
|
206
|
+
* by `mark-build`) and reports it to Patchstack, so we learn how the sites we
|
|
207
|
+
* protect are actually built and hosted — across every "vibe" platform — without
|
|
208
|
+
* shipping a runtime probe onto the host server. Every field is a coarse label
|
|
209
|
+
* or a bare key *name*: no versions beyond the framework, and never an env value.
|
|
210
|
+
*/
|
|
211
|
+
interface StackDescriptor {
|
|
212
|
+
/** App / meta-framework, e.g. "next", "nuxt", "tanstack-start", "remix". */
|
|
213
|
+
framework: string | null;
|
|
214
|
+
/** UI runtime, e.g. "react", "vue", "svelte", "solid". */
|
|
215
|
+
ui: string | null;
|
|
216
|
+
/** Build tool / bundler, e.g. "vite", "webpack", "rspack". */
|
|
217
|
+
bundler: string | null;
|
|
218
|
+
/** Deployment-runtime hint from deps, e.g. "cloudflare-workers", "vercel". */
|
|
219
|
+
runtime: string | null;
|
|
220
|
+
/** The vibe/builder platform that generated the project, e.g. "lovable". */
|
|
221
|
+
builder: string | null;
|
|
222
|
+
/** Package ecosystem the manifest came from. */
|
|
223
|
+
ecosystem: 'npm';
|
|
224
|
+
/** Hosting-related build-environment variable NAMES (never their values). */
|
|
225
|
+
hostingEnvKeys: string[];
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* An environment, described structurally so it needs no ambient types.
|
|
229
|
+
*
|
|
230
|
+
* `@types/node` is not a dependency of this package, so nothing in its public surface may name
|
|
231
|
+
* `NodeJS.ProcessEnv`: a consumer without those types cannot compile against a signature that does.
|
|
232
|
+
* `process.env` satisfies this, and so does a plain object.
|
|
233
|
+
*/
|
|
234
|
+
type EnvLike = Record<string, string | undefined>;
|
|
235
|
+
/**
|
|
236
|
+
* Return the sorted NAMES of hosting-related environment variables present in
|
|
237
|
+
* `env`. Only names are surfaced — never values — so a build fingerprint can
|
|
238
|
+
* distinguish "deployed on Cloudflare" from "deployed on Vercel" without ever
|
|
239
|
+
* disclosing a secret.
|
|
240
|
+
*/
|
|
241
|
+
declare function collectHostingEnvKeys(env?: EnvLike): string[];
|
|
242
|
+
/**
|
|
243
|
+
* Derive a {@link StackDescriptor} from the wire packages and the build
|
|
244
|
+
* environment. Never throws: an unrecognised stack just yields nulls.
|
|
245
|
+
*/
|
|
246
|
+
declare function detectStack(packages: readonly WirePackage[], env?: EnvLike): StackDescriptor;
|
|
247
|
+
/** True when the descriptor carries no useful signal (nothing worth injecting). */
|
|
248
|
+
declare function isEmptyStack(stack: StackDescriptor): boolean;
|
|
249
|
+
|
|
188
250
|
declare const DEFAULT_ENDPOINT = "https://api.patchstack.com/monitor/pulse/manifest";
|
|
189
251
|
declare function buildEndpointUrl(base: string, siteUuid?: string | null): string;
|
|
190
252
|
/**
|
|
@@ -295,55 +357,6 @@ declare function persistApiKey(cwd: string, apiKey: string): Promise<SecretFileR
|
|
|
295
357
|
*/
|
|
296
358
|
declare function persistPulseAuth(cwd: string, pulseAuth: string): Promise<SecretFileResult>;
|
|
297
359
|
|
|
298
|
-
/**
|
|
299
|
-
* A best-effort description of the stack a build was produced with, derived
|
|
300
|
-
* entirely from the lockfile (ground truth) plus the build-time environment.
|
|
301
|
-
*
|
|
302
|
-
* The disclosure widget reads this from `window.__PATCHSTACK_STACK__` (injected
|
|
303
|
-
* by `mark-build`) and reports it to Patchstack, so we learn how the sites we
|
|
304
|
-
* protect are actually built and hosted — across every "vibe" platform — without
|
|
305
|
-
* shipping a runtime probe onto the host server. Every field is a coarse label
|
|
306
|
-
* or a bare key *name*: no versions beyond the framework, and never an env value.
|
|
307
|
-
*/
|
|
308
|
-
interface StackDescriptor {
|
|
309
|
-
/** App / meta-framework, e.g. "next", "nuxt", "tanstack-start", "remix". */
|
|
310
|
-
framework: string | null;
|
|
311
|
-
/** UI runtime, e.g. "react", "vue", "svelte", "solid". */
|
|
312
|
-
ui: string | null;
|
|
313
|
-
/** Build tool / bundler, e.g. "vite", "webpack", "rspack". */
|
|
314
|
-
bundler: string | null;
|
|
315
|
-
/** Deployment-runtime hint from deps, e.g. "cloudflare-workers", "vercel". */
|
|
316
|
-
runtime: string | null;
|
|
317
|
-
/** The vibe/builder platform that generated the project, e.g. "lovable". */
|
|
318
|
-
builder: string | null;
|
|
319
|
-
/** Package ecosystem the manifest came from. */
|
|
320
|
-
ecosystem: 'npm';
|
|
321
|
-
/** Hosting-related build-environment variable NAMES (never their values). */
|
|
322
|
-
hostingEnvKeys: string[];
|
|
323
|
-
}
|
|
324
|
-
/**
|
|
325
|
-
* An environment, described structurally so it needs no ambient types.
|
|
326
|
-
*
|
|
327
|
-
* `@types/node` is not a dependency of this package, so nothing in its public surface may name
|
|
328
|
-
* `NodeJS.ProcessEnv`: a consumer without those types cannot compile against a signature that does.
|
|
329
|
-
* `process.env` satisfies this, and so does a plain object.
|
|
330
|
-
*/
|
|
331
|
-
type EnvLike = Record<string, string | undefined>;
|
|
332
|
-
/**
|
|
333
|
-
* Return the sorted NAMES of hosting-related environment variables present in
|
|
334
|
-
* `env`. Only names are surfaced — never values — so a build fingerprint can
|
|
335
|
-
* distinguish "deployed on Cloudflare" from "deployed on Vercel" without ever
|
|
336
|
-
* disclosing a secret.
|
|
337
|
-
*/
|
|
338
|
-
declare function collectHostingEnvKeys(env?: EnvLike): string[];
|
|
339
|
-
/**
|
|
340
|
-
* Derive a {@link StackDescriptor} from the wire packages and the build
|
|
341
|
-
* environment. Never throws: an unrecognised stack just yields nulls.
|
|
342
|
-
*/
|
|
343
|
-
declare function detectStack(packages: readonly WirePackage[], env?: EnvLike): StackDescriptor;
|
|
344
|
-
/** True when the descriptor carries no useful signal (nothing worth injecting). */
|
|
345
|
-
declare function isEmptyStack(stack: StackDescriptor): boolean;
|
|
346
|
-
|
|
347
360
|
declare const WIDGET_SCRIPT_URL = "https://cdn.patchstack.com/patchstack-widget.js";
|
|
348
361
|
/** Attribute that tags the connector-managed widget tag so re-runs update it. */
|
|
349
362
|
declare const WIDGET_MARKER_ATTR = "data-patchstack-connect-widget";
|
package/dist/index.js
CHANGED
|
@@ -12,6 +12,12 @@ var PatchstackError = class extends Error {
|
|
|
12
12
|
}
|
|
13
13
|
code;
|
|
14
14
|
cause;
|
|
15
|
+
/**
|
|
16
|
+
* For a `VALIDATION_ERROR`, the request fields the server refused, as it named them. Read from the
|
|
17
|
+
* structured `errors` object a validation response carries, so a caller deciding what to do about a
|
|
18
|
+
* refusal keys on the field rather than on the wording of a sentence.
|
|
19
|
+
*/
|
|
20
|
+
fields = [];
|
|
15
21
|
};
|
|
16
22
|
|
|
17
23
|
// src/parsers/consistency.ts
|
|
@@ -49,8 +55,8 @@ async function readDeclaredDependencyNames(cwd) {
|
|
|
49
55
|
return [...names];
|
|
50
56
|
}
|
|
51
57
|
function missingDependencies(declared, packages) {
|
|
52
|
-
const
|
|
53
|
-
return declared.filter((name) => !
|
|
58
|
+
const present2 = new Set(packages.map((entry) => entry.name));
|
|
59
|
+
return declared.filter((name) => !present2.has(name));
|
|
54
60
|
}
|
|
55
61
|
function disagreements(packages, other) {
|
|
56
62
|
const otherVersions = /* @__PURE__ */ new Map();
|
|
@@ -1185,12 +1191,40 @@ function compareSegments(a, b) {
|
|
|
1185
1191
|
return 0;
|
|
1186
1192
|
}
|
|
1187
1193
|
|
|
1194
|
+
// src/hosting.ts
|
|
1195
|
+
var RULES = [
|
|
1196
|
+
{ platform: "netlify", any: ["NETLIFY", "NETLIFY_BUILD_BASE", "DEPLOY_PRIME_URL"] },
|
|
1197
|
+
{ platform: "vercel", any: ["VERCEL", "VERCEL_ENV", "VERCEL_URL"] },
|
|
1198
|
+
{ platform: "cloudflare", any: ["CF_PAGES", "CF_PAGES_URL", "CLOUDFLARE_ACCOUNT_ID"] },
|
|
1199
|
+
{ platform: "aws", any: ["AWS_APP_ID", "AWS_LAMBDA_FUNCTION_NAME", "AWS_EXECUTION_ENV"] },
|
|
1200
|
+
{ platform: "render", any: ["RENDER", "RENDER_SERVICE_ID", "RENDER_EXTERNAL_URL"] },
|
|
1201
|
+
{ platform: "railway", any: ["RAILWAY_ENVIRONMENT", "RAILWAY_ENVIRONMENT_NAME", "RAILWAY_PROJECT_ID"] },
|
|
1202
|
+
{ platform: "fly", any: ["FLY_APP_NAME", "FLY_REGION", "FLY_ALLOC_ID"] },
|
|
1203
|
+
{ platform: "heroku", any: ["DYNO", "HEROKU_APP_NAME"] },
|
|
1204
|
+
{ platform: "google-cloud", any: ["K_SERVICE", "GAE_APPLICATION", "GAE_SERVICE"] },
|
|
1205
|
+
{ platform: "deno-deploy", any: ["DENO_DEPLOYMENT_ID"] },
|
|
1206
|
+
{ platform: "azure", any: ["WEBSITE_SITE_NAME", "WEBSITE_INSTANCE_ID"] }
|
|
1207
|
+
];
|
|
1208
|
+
var present = (env, name) => {
|
|
1209
|
+
const value = env[name];
|
|
1210
|
+
return value !== void 0 && value !== "";
|
|
1211
|
+
};
|
|
1212
|
+
function detectHostingPlatform(env = process.env) {
|
|
1213
|
+
for (const rule of RULES) {
|
|
1214
|
+
const evidence = rule.any.filter((name) => present(env, name));
|
|
1215
|
+
if (evidence.length > 0) {
|
|
1216
|
+
return { platform: rule.platform, evidence };
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
return { platform: null, evidence: [] };
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1188
1222
|
// src/pulse-token.ts
|
|
1189
1223
|
var TOKEN_SKEW_MS = 6e4;
|
|
1190
1224
|
function buildTokenUrl(manifestEndpoint) {
|
|
1191
1225
|
const url = new URL(manifestEndpoint);
|
|
1192
|
-
const
|
|
1193
|
-
url.pathname =
|
|
1226
|
+
const path10 = url.pathname.replace(/\/$/, "");
|
|
1227
|
+
url.pathname = path10.endsWith("/manifest") ? `${path10.slice(0, -"/manifest".length)}/token` : "/monitor/pulse/token";
|
|
1194
1228
|
url.search = "";
|
|
1195
1229
|
url.hash = "";
|
|
1196
1230
|
return url.toString();
|
|
@@ -1295,14 +1329,22 @@ function buildClaimUrl(endpoint, siteUuid) {
|
|
|
1295
1329
|
const origin = new URL(endpoint).origin;
|
|
1296
1330
|
return `${origin}/monitor/claim?site=${encodeURIComponent(siteUuid)}`;
|
|
1297
1331
|
}
|
|
1298
|
-
function buildManifestBody(config, payload) {
|
|
1332
|
+
function buildManifestBody(config, payload, env = process.env) {
|
|
1333
|
+
const hosting = config.environment === "local" ? null : detectHostingPlatform(env);
|
|
1299
1334
|
return {
|
|
1300
1335
|
...payload,
|
|
1301
1336
|
environment: config.environment,
|
|
1302
1337
|
...typeof config.siteUrl === "string" && config.siteUrl !== "" ? { url: config.siteUrl } : {},
|
|
1303
|
-
...typeof config.siteName === "string" && config.siteName !== "" ? { name: config.siteName } : {}
|
|
1338
|
+
...typeof config.siteName === "string" && config.siteName !== "" ? { name: config.siteName } : {},
|
|
1339
|
+
...hosting !== null && hosting.platform !== null ? { hosting } : {}
|
|
1304
1340
|
};
|
|
1305
1341
|
}
|
|
1342
|
+
function validationFields(body) {
|
|
1343
|
+
if (typeof body !== "object" || body === null) return [];
|
|
1344
|
+
const errors = body.errors;
|
|
1345
|
+
if (typeof errors !== "object" || errors === null || Array.isArray(errors)) return [];
|
|
1346
|
+
return Object.keys(errors);
|
|
1347
|
+
}
|
|
1306
1348
|
async function postManifest(config, payload) {
|
|
1307
1349
|
const url = buildEndpointUrl(config.endpoint, config.siteUuid);
|
|
1308
1350
|
const timeoutMs = config.timeoutMs;
|
|
@@ -1347,10 +1389,12 @@ async function postManifest(config, payload) {
|
|
|
1347
1389
|
);
|
|
1348
1390
|
}
|
|
1349
1391
|
if (response.status === 422) {
|
|
1350
|
-
|
|
1392
|
+
const refused2 = new PatchstackError(
|
|
1351
1393
|
body?.message ?? "Patchstack rejected the manifest payload (validation failed).",
|
|
1352
1394
|
"VALIDATION_ERROR"
|
|
1353
1395
|
);
|
|
1396
|
+
refused2.fields = validationFields(body);
|
|
1397
|
+
throw refused2;
|
|
1354
1398
|
}
|
|
1355
1399
|
const refused = authFailureMessage(response.status, config);
|
|
1356
1400
|
if (refused !== null) {
|
|
@@ -1376,7 +1420,80 @@ function isTimeoutError(cause) {
|
|
|
1376
1420
|
|
|
1377
1421
|
// src/config.ts
|
|
1378
1422
|
import { readFile as readFile8, writeFile, chmod } from "fs/promises";
|
|
1379
|
-
import
|
|
1423
|
+
import path8 from "path";
|
|
1424
|
+
|
|
1425
|
+
// src/environment.ts
|
|
1426
|
+
import { readFileSync } from "fs";
|
|
1427
|
+
import path6 from "path";
|
|
1428
|
+
var set = (value) => value !== void 0 && value !== "";
|
|
1429
|
+
var DISCRIMINATORS = [
|
|
1430
|
+
{
|
|
1431
|
+
platform: "vercel",
|
|
1432
|
+
read: (env) => {
|
|
1433
|
+
if (!set(env.VERCEL) || !set(env.VERCEL_ENV)) return null;
|
|
1434
|
+
return env.VERCEL_ENV === "production" ? { environment: "production", evidence: "VERCEL_ENV=production" } : { environment: "sandbox", evidence: `VERCEL_ENV=${env.VERCEL_ENV}` };
|
|
1435
|
+
}
|
|
1436
|
+
},
|
|
1437
|
+
{
|
|
1438
|
+
platform: "netlify",
|
|
1439
|
+
read: (env) => {
|
|
1440
|
+
if (env.NETLIFY !== "true" || !set(env.CONTEXT)) return null;
|
|
1441
|
+
return env.CONTEXT === "production" ? { environment: "production", evidence: "CONTEXT=production" } : { environment: "sandbox", evidence: `CONTEXT=${env.CONTEXT}` };
|
|
1442
|
+
}
|
|
1443
|
+
},
|
|
1444
|
+
{
|
|
1445
|
+
platform: "render",
|
|
1446
|
+
read: (env) => {
|
|
1447
|
+
if (env.RENDER !== "true") return null;
|
|
1448
|
+
return env.IS_PULL_REQUEST === "true" ? { environment: "sandbox", evidence: "IS_PULL_REQUEST=true" } : { environment: "production", evidence: "RENDER=true, not a pull request" };
|
|
1449
|
+
}
|
|
1450
|
+
},
|
|
1451
|
+
{
|
|
1452
|
+
platform: "railway",
|
|
1453
|
+
read: (env) => {
|
|
1454
|
+
if (!set(env.RAILWAY_ENVIRONMENT_NAME)) return null;
|
|
1455
|
+
return env.RAILWAY_ENVIRONMENT_NAME === "production" ? { environment: "production", evidence: "RAILWAY_ENVIRONMENT_NAME=production" } : { environment: "sandbox", evidence: `RAILWAY_ENVIRONMENT_NAME=${env.RAILWAY_ENVIRONMENT_NAME}` };
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
];
|
|
1459
|
+
var HOSTED_BUILDER_PACKAGES = [
|
|
1460
|
+
// `lovable-tagger` covers projects from before ~Aug 2026; current templates ship the
|
|
1461
|
+
// `@lovable.dev/*` scoped tooling instead.
|
|
1462
|
+
{ builder: "lovable", exact: ["lovable-tagger"], prefixes: ["@lovable.dev/"] },
|
|
1463
|
+
{ builder: "replit", exact: [], prefixes: ["@replit/"] }
|
|
1464
|
+
];
|
|
1465
|
+
function detectHostedBuilder(cwd) {
|
|
1466
|
+
let declared;
|
|
1467
|
+
try {
|
|
1468
|
+
const pkg = JSON.parse(readFileSync(path6.join(cwd, "package.json"), "utf8"));
|
|
1469
|
+
declared = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
1470
|
+
} catch {
|
|
1471
|
+
return null;
|
|
1472
|
+
}
|
|
1473
|
+
const names = Object.keys(declared);
|
|
1474
|
+
for (const rule of HOSTED_BUILDER_PACKAGES) {
|
|
1475
|
+
const hit = names.some(
|
|
1476
|
+
(name) => rule.exact.includes(name) || rule.prefixes.some((prefix) => name.startsWith(prefix))
|
|
1477
|
+
);
|
|
1478
|
+
if (hit) return rule.builder;
|
|
1479
|
+
}
|
|
1480
|
+
return null;
|
|
1481
|
+
}
|
|
1482
|
+
function inferEnvironment(env = process.env, builder = null) {
|
|
1483
|
+
for (const discriminator of DISCRIMINATORS) {
|
|
1484
|
+
const verdict = discriminator.read(env);
|
|
1485
|
+
if (verdict !== null) {
|
|
1486
|
+
return { environment: verdict.environment, evidence: [`${discriminator.platform}: ${verdict.evidence}`] };
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
if (builder !== null) {
|
|
1490
|
+
return {
|
|
1491
|
+
environment: "production",
|
|
1492
|
+
evidence: [`${builder}: a build in a ${builder} project is its publish step`]
|
|
1493
|
+
};
|
|
1494
|
+
}
|
|
1495
|
+
return { environment: "local", evidence: [] };
|
|
1496
|
+
}
|
|
1380
1497
|
|
|
1381
1498
|
// src/site-url.ts
|
|
1382
1499
|
var RESERVED_SUFFIXES = [
|
|
@@ -1447,7 +1564,7 @@ function detectSiteUrl(env = process.env) {
|
|
|
1447
1564
|
|
|
1448
1565
|
// src/site-name.ts
|
|
1449
1566
|
import { readFile as readFile7 } from "fs/promises";
|
|
1450
|
-
import
|
|
1567
|
+
import path7 from "path";
|
|
1451
1568
|
var NAME_MAX_LENGTH = 191;
|
|
1452
1569
|
var TEMPLATE_PACKAGE_NAMES = /* @__PURE__ */ new Set([
|
|
1453
1570
|
// Lovable
|
|
@@ -1524,13 +1641,13 @@ async function readIfPresent(file) {
|
|
|
1524
1641
|
}
|
|
1525
1642
|
}
|
|
1526
1643
|
async function detectSiteName(cwd) {
|
|
1527
|
-
for (const shell of ["index.html",
|
|
1528
|
-
const html = await readIfPresent(
|
|
1644
|
+
for (const shell of ["index.html", path7.join("public", "index.html")]) {
|
|
1645
|
+
const html = await readIfPresent(path7.join(cwd, shell));
|
|
1529
1646
|
if (html === null) continue;
|
|
1530
1647
|
const name = nameFromHtmlTitle(html);
|
|
1531
1648
|
if (name !== null) return { name, source: "index.html" };
|
|
1532
1649
|
}
|
|
1533
|
-
const manifest = await readIfPresent(
|
|
1650
|
+
const manifest = await readIfPresent(path7.join(cwd, "package.json"));
|
|
1534
1651
|
if (manifest !== null) {
|
|
1535
1652
|
try {
|
|
1536
1653
|
const parsed = JSON.parse(manifest);
|
|
@@ -1547,7 +1664,6 @@ async function detectSiteName(cwd) {
|
|
|
1547
1664
|
// src/config.ts
|
|
1548
1665
|
var CONFIG_FILENAME = ".patchstackrc.json";
|
|
1549
1666
|
var SECRET_FILENAME = ".patchstackrc.local.json";
|
|
1550
|
-
var DEFAULT_ENVIRONMENT = "production";
|
|
1551
1667
|
async function resolveSiteIdentity(cwd, fromEnv, fromFile) {
|
|
1552
1668
|
const configuredUrl = stated(fromEnv.url) ?? stated(fromFile.url);
|
|
1553
1669
|
const configuredName = stated(fromEnv.name) ?? stated(fromFile.name);
|
|
@@ -1580,11 +1696,13 @@ async function resolveConfig(options) {
|
|
|
1580
1696
|
const environmentRaw = fromEnv.environment ?? fromFile.environment;
|
|
1581
1697
|
if (environmentRaw !== void 0 && !isEnvironment(environmentRaw)) {
|
|
1582
1698
|
throw new PatchstackError(
|
|
1583
|
-
`Environment must be "production" or "
|
|
1699
|
+
`Environment must be "production", "sandbox" or "local"; got "${environmentRaw}".`,
|
|
1584
1700
|
"CONFIG_INVALID"
|
|
1585
1701
|
);
|
|
1586
1702
|
}
|
|
1587
|
-
const
|
|
1703
|
+
const inferred = environmentRaw === void 0 ? inferEnvironment(process.env, detectHostedBuilder(options.cwd)) : null;
|
|
1704
|
+
const environment = environmentRaw ?? inferred.environment;
|
|
1705
|
+
const environmentEvidence = inferred?.evidence ?? [];
|
|
1588
1706
|
if (siteUuid !== null && siteUuid.length > 0 && !isUuid(siteUuid)) {
|
|
1589
1707
|
throw new PatchstackError(
|
|
1590
1708
|
`Site UUID "${siteUuid}" does not look like a valid UUID.`,
|
|
@@ -1610,18 +1728,19 @@ async function resolveConfig(options) {
|
|
|
1610
1728
|
endpoint,
|
|
1611
1729
|
timeoutMs,
|
|
1612
1730
|
environment,
|
|
1731
|
+
environmentEvidence,
|
|
1613
1732
|
widget: fromFile.widget !== false,
|
|
1614
1733
|
claimToken
|
|
1615
1734
|
};
|
|
1616
1735
|
}
|
|
1617
1736
|
async function writeConfigFile(cwd, config) {
|
|
1618
|
-
const target =
|
|
1737
|
+
const target = path8.join(cwd, CONFIG_FILENAME);
|
|
1619
1738
|
const content = JSON.stringify(config, null, 2) + "\n";
|
|
1620
1739
|
await writeFile(target, content, "utf8");
|
|
1621
1740
|
return target;
|
|
1622
1741
|
}
|
|
1623
1742
|
async function writeSecretFile(cwd, secrets) {
|
|
1624
|
-
const target =
|
|
1743
|
+
const target = path8.join(cwd, SECRET_FILENAME);
|
|
1625
1744
|
await writeFile(target, JSON.stringify(secrets, null, 2) + "\n", { encoding: "utf8", mode: 384 });
|
|
1626
1745
|
try {
|
|
1627
1746
|
await chmod(target, 384);
|
|
@@ -1631,7 +1750,7 @@ async function writeSecretFile(cwd, secrets) {
|
|
|
1631
1750
|
return { path: target, ...ignore };
|
|
1632
1751
|
}
|
|
1633
1752
|
async function ensureIgnored(cwd, entry) {
|
|
1634
|
-
const target =
|
|
1753
|
+
const target = path8.join(cwd, ".gitignore");
|
|
1635
1754
|
let existing = "";
|
|
1636
1755
|
try {
|
|
1637
1756
|
existing = await readFile8(target, "utf8");
|
|
@@ -1698,7 +1817,7 @@ async function readConfigFile(cwd) {
|
|
|
1698
1817
|
return readJsonFile(cwd, CONFIG_FILENAME);
|
|
1699
1818
|
}
|
|
1700
1819
|
async function readJsonFile(cwd, filename) {
|
|
1701
|
-
const target =
|
|
1820
|
+
const target = path8.join(cwd, filename);
|
|
1702
1821
|
let raw;
|
|
1703
1822
|
try {
|
|
1704
1823
|
raw = await readFile8(target, "utf8");
|
|
@@ -1751,7 +1870,7 @@ function isUuid(value) {
|
|
|
1751
1870
|
return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value);
|
|
1752
1871
|
}
|
|
1753
1872
|
function isEnvironment(value) {
|
|
1754
|
-
return value === "production" || value === "sandbox";
|
|
1873
|
+
return value === "production" || value === "sandbox" || value === "local";
|
|
1755
1874
|
}
|
|
1756
1875
|
|
|
1757
1876
|
// src/stack.ts
|
|
@@ -1768,6 +1887,9 @@ var STACK_RULES = [
|
|
|
1768
1887
|
{ category: "framework", pkg: "@sveltejs/kit", label: "sveltekit" },
|
|
1769
1888
|
{ category: "framework", pkg: "@builder.io/qwik-city", label: "qwik-city" },
|
|
1770
1889
|
{ category: "framework", pkg: "gatsby", label: "gatsby" },
|
|
1890
|
+
{ category: "framework", pkg: "@11ty/eleventy", label: "eleventy" },
|
|
1891
|
+
{ category: "framework", pkg: "@docusaurus/core", label: "docusaurus" },
|
|
1892
|
+
{ category: "framework", pkg: "vitepress", label: "vitepress" },
|
|
1771
1893
|
{ category: "framework", pkg: "express", label: "express" },
|
|
1772
1894
|
{ category: "framework", pkg: "fastify", label: "fastify" },
|
|
1773
1895
|
// UI runtimes.
|
|
@@ -1817,10 +1939,10 @@ function collectHostingEnvKeys(env = process.env) {
|
|
|
1817
1939
|
return Object.keys(env).filter((key) => HOSTING_ENV_PATTERNS.some((pattern) => pattern.test(key))).sort();
|
|
1818
1940
|
}
|
|
1819
1941
|
function detectStack(packages, env = process.env) {
|
|
1820
|
-
const
|
|
1942
|
+
const present2 = new Set(packages.map((pkg) => pkg.name));
|
|
1821
1943
|
const firstMatch = (category) => {
|
|
1822
1944
|
for (const rule of STACK_RULES) {
|
|
1823
|
-
if (rule.category === category &&
|
|
1945
|
+
if (rule.category === category && present2.has(rule.pkg)) {
|
|
1824
1946
|
return rule.label;
|
|
1825
1947
|
}
|
|
1826
1948
|
}
|
|
@@ -1841,8 +1963,8 @@ function isEmptyStack(stack) {
|
|
|
1841
1963
|
}
|
|
1842
1964
|
|
|
1843
1965
|
// src/widget.ts
|
|
1844
|
-
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
1845
|
-
import
|
|
1966
|
+
import { existsSync, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
1967
|
+
import path9 from "path";
|
|
1846
1968
|
var WIDGET_SCRIPT_URL = "https://cdn.patchstack.com/patchstack-widget.js";
|
|
1847
1969
|
var WIDGET_MARKER_ATTR = "data-patchstack-connect-widget";
|
|
1848
1970
|
var WIDGET_NEEDLE = "patchstack-widget";
|
|
@@ -1880,7 +2002,7 @@ ${indent}</body>`;
|
|
|
1880
2002
|
}
|
|
1881
2003
|
function findSourceShell(cwd) {
|
|
1882
2004
|
for (const candidate of SOURCE_SHELL_CANDIDATES) {
|
|
1883
|
-
if (existsSync(
|
|
2005
|
+
if (existsSync(path9.join(cwd, candidate))) {
|
|
1884
2006
|
return candidate;
|
|
1885
2007
|
}
|
|
1886
2008
|
}
|
|
@@ -1891,8 +2013,8 @@ function ensureSourceWidget(cwd, siteUuid) {
|
|
|
1891
2013
|
if (shell === null) {
|
|
1892
2014
|
return { shell: null, action: "no-shell" };
|
|
1893
2015
|
}
|
|
1894
|
-
const file =
|
|
1895
|
-
const before =
|
|
2016
|
+
const file = path9.join(cwd, shell);
|
|
2017
|
+
const before = readFileSync2(file, "utf8");
|
|
1896
2018
|
const { html, action } = ensureWidgetInHtml(before, siteUuid);
|
|
1897
2019
|
if (html !== before) {
|
|
1898
2020
|
writeFileSync(file, html);
|