@patchstack/connect 0.1.0 → 0.2.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/README.md +68 -8
- package/dist/cli.js +198 -44
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +176 -25
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +27 -5
- package/dist/index.d.ts +27 -5
- package/dist/index.js +172 -22
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,23 +6,51 @@ Connect a JavaScript / Node.js application to [Patchstack](https://patchstack.co
|
|
|
6
6
|
|
|
7
7
|
Copy and paste this prompt into any AI coding assistant (Cursor, v0, Bolt, Lovable, Claude Code, etc.):
|
|
8
8
|
|
|
9
|
-
> Add `@patchstack/connect` to this project as a dev dependency
|
|
9
|
+
> Add `@patchstack/connect` to this project as a dev dependency, then add `"prebuild": "patchstack-connect scan"` to `package.json` `scripts`. The first time the script runs, Patchstack provisions a site automatically from the lockfile and saves the new site UUID to `.patchstackrc.json` — no dashboard signup or manual UUID required. Commit `.patchstackrc.json` so every developer / CI run targets the same site.
|
|
10
10
|
|
|
11
|
-
## Quick start
|
|
11
|
+
## Quick start (zero configuration)
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
14
|
npm install --save-dev @patchstack/connect
|
|
15
|
-
npx @patchstack/connect init <your-site-uuid>
|
|
16
15
|
npx @patchstack/connect scan
|
|
17
16
|
```
|
|
18
17
|
|
|
19
|
-
|
|
18
|
+
That's it. The first `scan`:
|
|
19
|
+
|
|
20
|
+
1. Reads your `package-lock.json`.
|
|
21
|
+
2. POSTs the package list to Patchstack with **no** UUID.
|
|
22
|
+
3. Patchstack provisions a fresh site and returns its UUID.
|
|
23
|
+
4. The connector writes the UUID to `.patchstackrc.json` so the next `scan` targets the same site.
|
|
24
|
+
|
|
25
|
+
Then wire it into builds:
|
|
26
|
+
|
|
27
|
+
```jsonc
|
|
28
|
+
// package.json
|
|
29
|
+
{
|
|
30
|
+
"scripts": {
|
|
31
|
+
"prebuild": "patchstack-connect scan"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Quick start (existing site)
|
|
37
|
+
|
|
38
|
+
If you already created an "Application" site in the Patchstack dashboard, pre-seed the UUID:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npm install --save-dev @patchstack/connect
|
|
42
|
+
npx @patchstack/connect init <your-site-uuid>
|
|
43
|
+
npx @patchstack/connect scan
|
|
44
|
+
```
|
|
20
45
|
|
|
21
46
|
## CLI
|
|
22
47
|
|
|
23
48
|
```
|
|
24
|
-
patchstack-connect
|
|
25
|
-
|
|
49
|
+
patchstack-connect scan [options] Scan the lockfile and POST to Patchstack.
|
|
50
|
+
If no UUID is configured the server provisions
|
|
51
|
+
one and the connector persists it.
|
|
52
|
+
patchstack-connect init <site-uuid> Optional: pre-seed .patchstackrc.json with
|
|
53
|
+
an existing site UUID
|
|
26
54
|
patchstack-connect status [options] Show current configuration
|
|
27
55
|
patchstack-connect help Print help
|
|
28
56
|
|
|
@@ -43,7 +71,7 @@ Precedence (highest wins):
|
|
|
43
71
|
Environment variables:
|
|
44
72
|
|
|
45
73
|
- `PATCHSTACK_SITE_UUID` — the site UUID from your Patchstack dashboard
|
|
46
|
-
- `PATCHSTACK_ENDPOINT` — override the API endpoint (default `
|
|
74
|
+
- `PATCHSTACK_ENDPOINT` — override the API endpoint (default `http://api.patchstack.com/monitor/pulse/manifest`)
|
|
47
75
|
- `PATCHSTACK_TIMEOUT_MS` — request timeout in milliseconds (default `30000`)
|
|
48
76
|
|
|
49
77
|
`.patchstackrc.json` example:
|
|
@@ -84,10 +112,14 @@ That's the entire payload. No source code, no environment variables, no file pat
|
|
|
84
112
|
|
|
85
113
|
## Supported lockfiles
|
|
86
114
|
|
|
87
|
-
- ✅ `package-lock.json` (npm v6 / v2 / v3)
|
|
115
|
+
- ✅ `package-lock.json` (npm v6 / v2 / v3) — parsed directly
|
|
116
|
+
- ✅ `bun.lockb` (binary) — package list resolved by walking `node_modules/`
|
|
117
|
+
- ✅ `bun.lock` (text) — same fallback; direct parsing coming
|
|
88
118
|
- ❌ `yarn.lock` — coming soon
|
|
89
119
|
- ❌ `pnpm-lock.yaml` — coming soon
|
|
90
120
|
|
|
121
|
+
If both a Bun lockfile and `node_modules/` are present, the connector walks `node_modules/` to enumerate the installed packages. Run `bun install` (or `npm install`) before scanning so the directory is populated.
|
|
122
|
+
|
|
91
123
|
## Development
|
|
92
124
|
|
|
93
125
|
```bash
|
|
@@ -97,6 +129,34 @@ npm test
|
|
|
97
129
|
npm run build
|
|
98
130
|
```
|
|
99
131
|
|
|
132
|
+
## Release process
|
|
133
|
+
|
|
134
|
+
Pull requests run typecheck, tests, build, package verification, and a production dependency audit in GitHub Actions.
|
|
135
|
+
|
|
136
|
+
Publishing runs when a GitHub Release is published. The release tag must match the package version in `package.json` with a leading `v`. For example, `package.json` version `0.2.0` must be released with tag `v0.2.0`; otherwise the workflow fails before publishing.
|
|
137
|
+
|
|
138
|
+
To publish a release:
|
|
139
|
+
|
|
140
|
+
1. Bump the package version, for example `npm version 0.2.0 --no-git-tag-version`.
|
|
141
|
+
2. Commit `package.json` and `package-lock.json`.
|
|
142
|
+
3. Merge the version bump to `main`.
|
|
143
|
+
4. Create and publish a GitHub Release tagged `v0.2.0`.
|
|
144
|
+
5. The `Publish` workflow verifies the package, then runs `npm publish --provenance --access public`.
|
|
145
|
+
|
|
146
|
+
Before the first release, configure npm trusted publishing for this package:
|
|
147
|
+
|
|
148
|
+
1. Merge `.github/workflows/publish.yml` to `main`.
|
|
149
|
+
2. Open the `@patchstack/connect` package settings on npmjs.com.
|
|
150
|
+
3. In **Trusted publishing**, choose **GitHub Actions**.
|
|
151
|
+
4. Configure:
|
|
152
|
+
- Organization/user: `patchstack`
|
|
153
|
+
- Repository: `connect`
|
|
154
|
+
- Workflow filename: `publish.yml`
|
|
155
|
+
- Environment name: `npm`
|
|
156
|
+
5. In GitHub repository settings, create an `npm` environment. Optional but recommended: require reviewer approval for that environment.
|
|
157
|
+
|
|
158
|
+
Do not add an npm publish token to GitHub secrets for this workflow. Trusted publishing uses GitHub OIDC short-lived credentials. After the first trusted publish succeeds, npm recommends setting package publishing access to require two-factor authentication and disallow tokens.
|
|
159
|
+
|
|
100
160
|
## License
|
|
101
161
|
|
|
102
162
|
MIT
|
package/dist/cli.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/parsers/index.ts
|
|
4
4
|
import { access } from "fs/promises";
|
|
5
|
-
import
|
|
5
|
+
import path3 from "path";
|
|
6
6
|
|
|
7
7
|
// src/types.ts
|
|
8
8
|
var PatchstackError = class extends Error {
|
|
@@ -102,20 +102,150 @@ function isDirectV2(pkgPath) {
|
|
|
102
102
|
return nmCount === 1;
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
+
// src/parsers/node_modules.ts
|
|
106
|
+
import { lstat, readFile as readFile2, readdir, stat } from "fs/promises";
|
|
107
|
+
import path2 from "path";
|
|
108
|
+
async function walkNodeModules(cwd) {
|
|
109
|
+
const root = path2.join(cwd, "node_modules");
|
|
110
|
+
try {
|
|
111
|
+
const info = await stat(root);
|
|
112
|
+
if (!info.isDirectory()) {
|
|
113
|
+
throw new PatchstackError(
|
|
114
|
+
`${root} exists but is not a directory.`,
|
|
115
|
+
"LOCKFILE_NOT_FOUND"
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
} catch (cause) {
|
|
119
|
+
if (cause instanceof PatchstackError) {
|
|
120
|
+
throw cause;
|
|
121
|
+
}
|
|
122
|
+
throw new PatchstackError(
|
|
123
|
+
`node_modules/ not found at ${cwd}. Install dependencies first (e.g. \`bun install\` or \`npm install\`).`,
|
|
124
|
+
"LOCKFILE_NOT_FOUND",
|
|
125
|
+
cause
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
const entries = [];
|
|
129
|
+
await walk(root, entries, 0);
|
|
130
|
+
return entries;
|
|
131
|
+
}
|
|
132
|
+
async function walk(dir, acc, depth) {
|
|
133
|
+
let names;
|
|
134
|
+
try {
|
|
135
|
+
names = await readdir(dir);
|
|
136
|
+
} catch {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
for (const name of names) {
|
|
140
|
+
if (name.startsWith(".")) {
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
const fullPath = path2.join(dir, name);
|
|
144
|
+
if (!await isPlainDirectory(fullPath)) {
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
if (name.startsWith("@")) {
|
|
148
|
+
let subNames;
|
|
149
|
+
try {
|
|
150
|
+
subNames = await readdir(fullPath);
|
|
151
|
+
} catch {
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
for (const sub of subNames) {
|
|
155
|
+
if (sub.startsWith(".")) {
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
const scopedDir = path2.join(fullPath, sub);
|
|
159
|
+
if (!await isPlainDirectory(scopedDir)) {
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
await readPackage(scopedDir, depth, acc);
|
|
163
|
+
await walkNested(scopedDir, acc, depth);
|
|
164
|
+
}
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
await readPackage(fullPath, depth, acc);
|
|
168
|
+
await walkNested(fullPath, acc, depth);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
async function readPackage(pkgDir, depth, acc) {
|
|
172
|
+
let raw;
|
|
173
|
+
try {
|
|
174
|
+
raw = await readFile2(path2.join(pkgDir, "package.json"), "utf8");
|
|
175
|
+
} catch {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
let parsed;
|
|
179
|
+
try {
|
|
180
|
+
parsed = JSON.parse(raw);
|
|
181
|
+
} catch {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
if (typeof parsed.name !== "string" || parsed.name.length === 0) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
if (typeof parsed.version !== "string" || parsed.version.length === 0) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
acc.push({
|
|
191
|
+
name: parsed.name,
|
|
192
|
+
version: parsed.version,
|
|
193
|
+
direct: depth === 0
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
async function walkNested(pkgDir, acc, depth) {
|
|
197
|
+
const nested = path2.join(pkgDir, "node_modules");
|
|
198
|
+
if (!await isPlainDirectory(nested)) {
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
await walk(nested, acc, depth + 1);
|
|
202
|
+
}
|
|
203
|
+
async function isPlainDirectory(dir) {
|
|
204
|
+
try {
|
|
205
|
+
const info = await lstat(dir);
|
|
206
|
+
return info.isDirectory() && !info.isSymbolicLink();
|
|
207
|
+
} catch {
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
105
212
|
// src/parsers/index.ts
|
|
106
213
|
async function detectLockfile(cwd) {
|
|
107
|
-
const npmLock =
|
|
214
|
+
const npmLock = path3.join(cwd, "package-lock.json");
|
|
108
215
|
if (await exists(npmLock)) {
|
|
109
|
-
return {
|
|
110
|
-
|
|
111
|
-
|
|
216
|
+
return {
|
|
217
|
+
ecosystem: "npm",
|
|
218
|
+
filePath: npmLock,
|
|
219
|
+
filename: "package-lock.json",
|
|
220
|
+
strategy: "npm-lockfile"
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
const bunLock = path3.join(cwd, "bun.lock");
|
|
224
|
+
if (await exists(bunLock)) {
|
|
225
|
+
return {
|
|
226
|
+
ecosystem: "npm",
|
|
227
|
+
filePath: bunLock,
|
|
228
|
+
filename: "bun.lock",
|
|
229
|
+
strategy: "node-modules-walk"
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
const bunLockB = path3.join(cwd, "bun.lockb");
|
|
233
|
+
if (await exists(bunLockB)) {
|
|
234
|
+
return {
|
|
235
|
+
ecosystem: "npm",
|
|
236
|
+
filePath: bunLockB,
|
|
237
|
+
filename: "bun.lockb",
|
|
238
|
+
strategy: "node-modules-walk"
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
const yarnLock = path3.join(cwd, "yarn.lock");
|
|
112
242
|
if (await exists(yarnLock)) {
|
|
113
243
|
throw new PatchstackError(
|
|
114
244
|
"yarn.lock detected but not yet supported. Run `npm install` to generate a package-lock.json, or open an issue at github.com/patchstack/connect.",
|
|
115
245
|
"LOCKFILE_UNSUPPORTED"
|
|
116
246
|
);
|
|
117
247
|
}
|
|
118
|
-
const pnpmLock =
|
|
248
|
+
const pnpmLock = path3.join(cwd, "pnpm-lock.yaml");
|
|
119
249
|
if (await exists(pnpmLock)) {
|
|
120
250
|
throw new PatchstackError(
|
|
121
251
|
"pnpm-lock.yaml detected but not yet supported. Open an issue at github.com/patchstack/connect to request support.",
|
|
@@ -123,15 +253,23 @@ async function detectLockfile(cwd) {
|
|
|
123
253
|
);
|
|
124
254
|
}
|
|
125
255
|
throw new PatchstackError(
|
|
126
|
-
`No lockfile found in ${cwd}. Expected one of: package-lock.json, yarn.lock, pnpm-lock.yaml.`,
|
|
256
|
+
`No lockfile found in ${cwd}. Expected one of: package-lock.json, bun.lock, bun.lockb, yarn.lock, pnpm-lock.yaml.`,
|
|
127
257
|
"LOCKFILE_NOT_FOUND"
|
|
128
258
|
);
|
|
129
259
|
}
|
|
130
260
|
async function scanLockfile(cwd) {
|
|
131
261
|
const detected = await detectLockfile(cwd);
|
|
132
|
-
const packages = await
|
|
262
|
+
const packages = await runStrategy(detected, cwd);
|
|
133
263
|
return { ecosystem: detected.ecosystem, packages };
|
|
134
264
|
}
|
|
265
|
+
async function runStrategy(detected, cwd) {
|
|
266
|
+
switch (detected.strategy) {
|
|
267
|
+
case "npm-lockfile":
|
|
268
|
+
return parseNpmLockfile(detected.filePath);
|
|
269
|
+
case "node-modules-walk":
|
|
270
|
+
return walkNodeModules(cwd);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
135
273
|
async function exists(filePath) {
|
|
136
274
|
try {
|
|
137
275
|
await access(filePath);
|
|
@@ -244,11 +382,11 @@ function compareSegments(a, b) {
|
|
|
244
382
|
}
|
|
245
383
|
|
|
246
384
|
// src/client.ts
|
|
247
|
-
var DEFAULT_ENDPOINT = "
|
|
385
|
+
var DEFAULT_ENDPOINT = "http://api.patchstack.com/monitor/pulse/manifest";
|
|
248
386
|
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
249
387
|
function buildEndpointUrl(base, siteUuid) {
|
|
250
388
|
const trimmed = base.replace(/\/$/, "");
|
|
251
|
-
return `${trimmed}/${encodeURIComponent(siteUuid)}
|
|
389
|
+
return siteUuid !== void 0 && siteUuid !== null && siteUuid.length > 0 ? `${trimmed}/${encodeURIComponent(siteUuid)}` : trimmed;
|
|
252
390
|
}
|
|
253
391
|
async function postManifest(config, payload) {
|
|
254
392
|
const url = buildEndpointUrl(config.endpoint, config.siteUuid);
|
|
@@ -317,8 +455,8 @@ function isTimeoutError(cause) {
|
|
|
317
455
|
}
|
|
318
456
|
|
|
319
457
|
// src/config.ts
|
|
320
|
-
import { readFile as
|
|
321
|
-
import
|
|
458
|
+
import { readFile as readFile3, writeFile } from "fs/promises";
|
|
459
|
+
import path4 from "path";
|
|
322
460
|
var CONFIG_FILENAME = ".patchstackrc.json";
|
|
323
461
|
async function resolveConfig(options) {
|
|
324
462
|
const fromFile = await readConfigFile(options.cwd);
|
|
@@ -326,31 +464,39 @@ async function resolveConfig(options) {
|
|
|
326
464
|
const siteUuid = options.cliSiteUuid ?? fromEnv.siteUuid ?? fromFile.siteUuid ?? null;
|
|
327
465
|
const endpoint = options.cliEndpoint ?? fromEnv.endpoint ?? fromFile.endpoint ?? DEFAULT_ENDPOINT;
|
|
328
466
|
const timeoutMs = fromEnv.timeoutMs ?? fromFile.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
329
|
-
if (siteUuid
|
|
467
|
+
if (siteUuid !== null && siteUuid.length > 0 && !isUuid(siteUuid)) {
|
|
330
468
|
throw new PatchstackError(
|
|
331
|
-
|
|
332
|
-
"
|
|
469
|
+
`Site UUID "${siteUuid}" does not look like a valid UUID.`,
|
|
470
|
+
"CONFIG_INVALID"
|
|
333
471
|
);
|
|
334
472
|
}
|
|
335
|
-
if (
|
|
473
|
+
if (options.requireSiteUuid && (siteUuid === null || siteUuid.length === 0)) {
|
|
336
474
|
throw new PatchstackError(
|
|
337
|
-
|
|
338
|
-
"
|
|
475
|
+
"No site UUID configured. Run `patchstack-connect scan` to provision one, or set PATCHSTACK_SITE_UUID.",
|
|
476
|
+
"CONFIG_MISSING"
|
|
339
477
|
);
|
|
340
478
|
}
|
|
341
|
-
return {
|
|
479
|
+
return {
|
|
480
|
+
siteUuid: siteUuid === null || siteUuid.length === 0 ? null : siteUuid,
|
|
481
|
+
endpoint,
|
|
482
|
+
timeoutMs
|
|
483
|
+
};
|
|
342
484
|
}
|
|
343
485
|
async function writeConfigFile(cwd, config) {
|
|
344
|
-
const target =
|
|
486
|
+
const target = path4.join(cwd, CONFIG_FILENAME);
|
|
345
487
|
const content = JSON.stringify(config, null, 2) + "\n";
|
|
346
488
|
await writeFile(target, content, "utf8");
|
|
347
489
|
return target;
|
|
348
490
|
}
|
|
491
|
+
async function persistSiteUuid(cwd, siteUuid) {
|
|
492
|
+
const existing = await readConfigFile(cwd);
|
|
493
|
+
return writeConfigFile(cwd, { ...existing, siteUuid });
|
|
494
|
+
}
|
|
349
495
|
async function readConfigFile(cwd) {
|
|
350
|
-
const target =
|
|
496
|
+
const target = path4.join(cwd, CONFIG_FILENAME);
|
|
351
497
|
let raw;
|
|
352
498
|
try {
|
|
353
|
-
raw = await
|
|
499
|
+
raw = await readFile3(target, "utf8");
|
|
354
500
|
} catch (err) {
|
|
355
501
|
if (err.code === "ENOENT") {
|
|
356
502
|
return {};
|
|
@@ -398,8 +544,11 @@ function isUuid(value) {
|
|
|
398
544
|
var HELP = `@patchstack/connect \u2014 scan your lockfile and report packages to Patchstack.
|
|
399
545
|
|
|
400
546
|
Usage:
|
|
401
|
-
patchstack-connect
|
|
402
|
-
|
|
547
|
+
patchstack-connect scan [options] Scan lockfile and POST to Patchstack.
|
|
548
|
+
If no UUID is configured, the server
|
|
549
|
+
provisions one and we persist it.
|
|
550
|
+
patchstack-connect init <site-uuid> Optional: pre-seed .patchstackrc.json
|
|
551
|
+
with an existing site UUID
|
|
403
552
|
patchstack-connect status [options] Show current configuration
|
|
404
553
|
patchstack-connect help Print this message
|
|
405
554
|
|
|
@@ -410,15 +559,15 @@ Options (for scan and status):
|
|
|
410
559
|
|
|
411
560
|
Environment:
|
|
412
561
|
PATCHSTACK_SITE_UUID Site UUID
|
|
413
|
-
PATCHSTACK_ENDPOINT API endpoint (default:
|
|
562
|
+
PATCHSTACK_ENDPOINT API endpoint (default: http://api.patchstack.com/monitor/pulse/manifest)
|
|
414
563
|
PATCHSTACK_TIMEOUT_MS Request timeout in ms (default: 30000)
|
|
415
564
|
|
|
416
565
|
Precedence: CLI flag > environment variable > .patchstackrc.json.
|
|
417
566
|
|
|
418
567
|
Examples:
|
|
419
|
-
npx @patchstack/connect init 550e8400-e29b-41d4-a716-446655440000
|
|
420
568
|
npx @patchstack/connect scan
|
|
421
569
|
npx @patchstack/connect scan --dry-run
|
|
570
|
+
npx @patchstack/connect init 550e8400-e29b-41d4-a716-446655440000
|
|
422
571
|
npx @patchstack/connect scan --site-uuid 550e8400-...-446655440000
|
|
423
572
|
`;
|
|
424
573
|
var VALUE_FLAGS = /* @__PURE__ */ new Set(["site-uuid", "endpoint"]);
|
|
@@ -493,7 +642,12 @@ async function runScan(args) {
|
|
|
493
642
|
}
|
|
494
643
|
if (dryRun) {
|
|
495
644
|
console.log("");
|
|
496
|
-
|
|
645
|
+
if (config.siteUuid === null) {
|
|
646
|
+
console.log("--dry-run: no site UUID configured. A real run would provision one.");
|
|
647
|
+
} else {
|
|
648
|
+
console.log(`--dry-run: not posting to Patchstack (site UUID ${config.siteUuid}).`);
|
|
649
|
+
}
|
|
650
|
+
console.log("Payload preview:");
|
|
497
651
|
const preview = JSON.stringify(payload, null, 2).split("\n");
|
|
498
652
|
console.log(preview.slice(0, Math.min(preview.length, 30)).join("\n"));
|
|
499
653
|
if (preview.length > 30) {
|
|
@@ -501,7 +655,15 @@ async function runScan(args) {
|
|
|
501
655
|
}
|
|
502
656
|
return 0;
|
|
503
657
|
}
|
|
658
|
+
const provisioning = config.siteUuid === null;
|
|
659
|
+
if (provisioning) {
|
|
660
|
+
console.log("No site UUID configured \u2014 provisioning a new Patchstack site from this manifest\u2026");
|
|
661
|
+
}
|
|
504
662
|
const response = await postManifest(config, payload);
|
|
663
|
+
if (provisioning && response.uuid !== void 0 && response.uuid.length > 0) {
|
|
664
|
+
const target = await persistSiteUuid(process.cwd(), response.uuid);
|
|
665
|
+
console.log(`Provisioned site ${response.uuid}. Saved UUID to ${target}.`);
|
|
666
|
+
}
|
|
505
667
|
if (response.stored) {
|
|
506
668
|
console.log(`Stored manifest #${response.manifest_id} (checksum ${response.checksum}).`);
|
|
507
669
|
} else if (response.reason === "duplicate") {
|
|
@@ -512,23 +674,15 @@ async function runScan(args) {
|
|
|
512
674
|
return 0;
|
|
513
675
|
}
|
|
514
676
|
async function runStatus(args) {
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
return 0;
|
|
525
|
-
} catch (err) {
|
|
526
|
-
if (err instanceof PatchstackError && err.code === "CONFIG_MISSING") {
|
|
527
|
-
console.log("Not configured. Run `patchstack-connect init <site-uuid>` to get started.");
|
|
528
|
-
return 0;
|
|
529
|
-
}
|
|
530
|
-
throw err;
|
|
531
|
-
}
|
|
677
|
+
const config = await resolveConfig({
|
|
678
|
+
cwd: process.cwd(),
|
|
679
|
+
cliSiteUuid: getStringFlag(args.flags, "site-uuid"),
|
|
680
|
+
cliEndpoint: getStringFlag(args.flags, "endpoint")
|
|
681
|
+
});
|
|
682
|
+
console.log(`Site UUID: ${config.siteUuid ?? "(none yet \u2014 the next `scan` will provision one)"}`);
|
|
683
|
+
console.log(`Endpoint: ${config.endpoint}`);
|
|
684
|
+
console.log(`Timeout: ${config.timeoutMs}ms`);
|
|
685
|
+
return 0;
|
|
532
686
|
}
|
|
533
687
|
async function main() {
|
|
534
688
|
const args = parseArgs(process.argv);
|