@screenshotscout/cli 0.1.0
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/LICENSE +21 -0
- package/README.md +236 -0
- package/SECURITY.md +13 -0
- package/dist/capture-options.js +476 -0
- package/dist/capture-url.js +23 -0
- package/dist/capture.js +17 -0
- package/dist/cli-usage-error.js +8 -0
- package/dist/cli.js +96 -0
- package/dist/help.js +59 -0
- package/dist/index.js +3 -0
- package/dist/input.js +332 -0
- package/dist/result.js +51 -0
- package/dist/sdk.js +23 -0
- package/package.json +63 -0
package/dist/input.js
ADDED
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { parseArgs } from "node:util";
|
|
3
|
+
import { CAPTURE_OPTION_DEFINITIONS, } from "./capture-options.js";
|
|
4
|
+
import { CliUsageError } from "./cli-usage-error.js";
|
|
5
|
+
const OPTIONS_INPUT_NAME = "options";
|
|
6
|
+
const METHOD_INPUT_NAME = "method";
|
|
7
|
+
const OUTPUT_INPUT_NAME = "output";
|
|
8
|
+
const OMIT_VALUE = Symbol("omit-value");
|
|
9
|
+
const DEFINITIONS = Object.values(CAPTURE_OPTION_DEFINITIONS);
|
|
10
|
+
const DEFINITIONS_BY_CLI_NAME = new Map(DEFINITIONS.map((definition) => [definition.cliName, definition]));
|
|
11
|
+
const DEFINITIONS_BY_API_NAME = new Map(DEFINITIONS.map((definition) => [definition.apiName, definition]));
|
|
12
|
+
const CAPTURE_PARSE_OPTIONS = createParseOptions(false);
|
|
13
|
+
const CAPTURE_COMMAND_PARSE_OPTIONS = createParseOptions(true);
|
|
14
|
+
const DEFAULT_INPUT_READER = {
|
|
15
|
+
readFile: (path) => readFile(path, "utf8"),
|
|
16
|
+
readStdin: readStandardInput,
|
|
17
|
+
};
|
|
18
|
+
/** Parse capture flags and merge an optional snake_case JSON options object. */
|
|
19
|
+
export async function parseCaptureInput(args, inputReader = DEFAULT_INPUT_READER) {
|
|
20
|
+
const parsed = await parseCaptureInputInternal(args, inputReader, false);
|
|
21
|
+
return {
|
|
22
|
+
captureOptions: parsed.captureOptions,
|
|
23
|
+
positionals: parsed.positionals,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/** Parse the capture command's URL, capture options, and two CLI controls. */
|
|
27
|
+
export async function parseCaptureCommandInput(args, inputReader = DEFAULT_INPUT_READER) {
|
|
28
|
+
const parsed = await parseCaptureInputInternal(args, inputReader, true);
|
|
29
|
+
if (parsed.positionals.length !== 1) {
|
|
30
|
+
throw new CliUsageError("Command 'capture' requires exactly one URL.");
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
captureOptions: parsed.captureOptions,
|
|
34
|
+
method: parsed.method,
|
|
35
|
+
outputPath: parsed.outputPath,
|
|
36
|
+
targetUrl: parsed.positionals[0],
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/** Parse the capture-url command's URL and capture options. */
|
|
40
|
+
export async function parseCaptureUrlCommandInput(args, inputReader = DEFAULT_INPUT_READER) {
|
|
41
|
+
const parsed = await parseCaptureInputInternal(args, inputReader, false);
|
|
42
|
+
if (parsed.positionals.length !== 1) {
|
|
43
|
+
throw new CliUsageError("Command 'capture-url' requires exactly one URL.");
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
captureOptions: parsed.captureOptions,
|
|
47
|
+
targetUrl: parsed.positionals[0],
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
async function parseCaptureInputInternal(args, inputReader, includeCaptureControls) {
|
|
51
|
+
const parsedCliInput = parseCliInput(args, includeCaptureControls);
|
|
52
|
+
let fileOptions = {};
|
|
53
|
+
if (parsedCliInput.optionsPath !== undefined) {
|
|
54
|
+
const text = parsedCliInput.optionsPath === "-"
|
|
55
|
+
? await inputReader.readStdin()
|
|
56
|
+
: await inputReader.readFile(parsedCliInput.optionsPath);
|
|
57
|
+
fileOptions = parseOptionsObject(text);
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
captureOptions: {
|
|
61
|
+
...fileOptions,
|
|
62
|
+
...parsedCliInput.captureOptions,
|
|
63
|
+
},
|
|
64
|
+
...(parsedCliInput.method === undefined
|
|
65
|
+
? {}
|
|
66
|
+
: { method: parsedCliInput.method }),
|
|
67
|
+
...(parsedCliInput.outputPath === undefined
|
|
68
|
+
? {}
|
|
69
|
+
: { outputPath: parsedCliInput.outputPath }),
|
|
70
|
+
positionals: parsedCliInput.positionals,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function createParseOptions(includeCaptureControls) {
|
|
74
|
+
const options = {};
|
|
75
|
+
for (const definition of DEFINITIONS) {
|
|
76
|
+
options[definition.cliName] = definition.multiple
|
|
77
|
+
? { type: "string", multiple: true }
|
|
78
|
+
: { type: definition.kind === "boolean" ? "boolean" : "string" };
|
|
79
|
+
}
|
|
80
|
+
options[OPTIONS_INPUT_NAME] = { type: "string" };
|
|
81
|
+
if (includeCaptureControls) {
|
|
82
|
+
options[METHOD_INPUT_NAME] = { type: "string" };
|
|
83
|
+
options[OUTPUT_INPUT_NAME] = { type: "string", short: "o" };
|
|
84
|
+
}
|
|
85
|
+
return options;
|
|
86
|
+
}
|
|
87
|
+
function parseCliInput(args, includeCaptureControls) {
|
|
88
|
+
const normalized = normalizeArguments(args);
|
|
89
|
+
let parsed;
|
|
90
|
+
try {
|
|
91
|
+
parsed = parseNormalizedArguments(normalized.args, includeCaptureControls
|
|
92
|
+
? CAPTURE_COMMAND_PARSE_OPTIONS
|
|
93
|
+
: CAPTURE_PARSE_OPTIONS);
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
throw usageErrorFrom(error);
|
|
97
|
+
}
|
|
98
|
+
const captureOptions = {};
|
|
99
|
+
const occurrences = new Map();
|
|
100
|
+
let method;
|
|
101
|
+
let optionsPath;
|
|
102
|
+
let outputPath;
|
|
103
|
+
for (const token of parsed.tokens) {
|
|
104
|
+
if (token.kind !== "option")
|
|
105
|
+
continue;
|
|
106
|
+
const count = (occurrences.get(token.name) ?? 0) + 1;
|
|
107
|
+
occurrences.set(token.name, count);
|
|
108
|
+
if (token.name === OPTIONS_INPUT_NAME) {
|
|
109
|
+
if (count > 1)
|
|
110
|
+
throw duplicateOption(token.rawName);
|
|
111
|
+
optionsPath = requiredTokenValue(token);
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (token.name === METHOD_INPUT_NAME) {
|
|
115
|
+
if (count > 1)
|
|
116
|
+
throw duplicateOption(token.rawName);
|
|
117
|
+
method = parseCaptureMethod(requiredTokenValue(token));
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (token.name === OUTPUT_INPUT_NAME) {
|
|
121
|
+
if (count > 1)
|
|
122
|
+
throw duplicateOption(token.rawName);
|
|
123
|
+
outputPath = requiredTokenValue(token);
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
const definition = DEFINITIONS_BY_CLI_NAME.get(token.name);
|
|
127
|
+
if (definition === undefined) {
|
|
128
|
+
throw new Error(`Parser returned an unconfigured option '${token.name}'.`);
|
|
129
|
+
}
|
|
130
|
+
if (!definition.multiple && count > 1) {
|
|
131
|
+
throw duplicateOption(token.rawName);
|
|
132
|
+
}
|
|
133
|
+
applyCliValue(captureOptions, definition, token, normalized.booleanValues);
|
|
134
|
+
}
|
|
135
|
+
const result = {
|
|
136
|
+
captureOptions,
|
|
137
|
+
positionals: [...parsed.positionals],
|
|
138
|
+
};
|
|
139
|
+
return {
|
|
140
|
+
...result,
|
|
141
|
+
...(method === undefined ? {} : { method }),
|
|
142
|
+
...(optionsPath === undefined ? {} : { optionsPath }),
|
|
143
|
+
...(outputPath === undefined ? {} : { outputPath }),
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
function parseNormalizedArguments(args, options = CAPTURE_PARSE_OPTIONS) {
|
|
147
|
+
return parseArgs({
|
|
148
|
+
args,
|
|
149
|
+
allowPositionals: true,
|
|
150
|
+
options,
|
|
151
|
+
strict: true,
|
|
152
|
+
tokens: true,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
function normalizeArguments(args) {
|
|
156
|
+
const normalized = [];
|
|
157
|
+
const booleanValues = new Map();
|
|
158
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
159
|
+
const argument = args[index];
|
|
160
|
+
if (argument === undefined)
|
|
161
|
+
continue;
|
|
162
|
+
if (argument === "--") {
|
|
163
|
+
normalized.push(...args.slice(index));
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
if (!argument.startsWith("--")) {
|
|
167
|
+
normalized.push(argument);
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
const separatorIndex = argument.indexOf("=");
|
|
171
|
+
const cliName = argument.slice(2, separatorIndex === -1 ? undefined : separatorIndex);
|
|
172
|
+
const definition = DEFINITIONS_BY_CLI_NAME.get(cliName);
|
|
173
|
+
if (definition?.kind !== "boolean") {
|
|
174
|
+
const nextArgument = args[index + 1];
|
|
175
|
+
if (definition !== undefined &&
|
|
176
|
+
separatorIndex === -1 &&
|
|
177
|
+
nextArgument !== undefined &&
|
|
178
|
+
nextArgument.startsWith("-") &&
|
|
179
|
+
!nextArgument.startsWith("--")) {
|
|
180
|
+
normalized.push(`${argument}=${nextArgument}`);
|
|
181
|
+
index += 1;
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
normalized.push(argument);
|
|
185
|
+
}
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
const normalizedIndex = normalized.length;
|
|
189
|
+
if (separatorIndex === -1) {
|
|
190
|
+
const nextArgument = args[index + 1];
|
|
191
|
+
if (nextArgument === "true" || nextArgument === "false") {
|
|
192
|
+
throw new CliUsageError(`Option '--${cliName}' does not accept a space-separated boolean value.`);
|
|
193
|
+
}
|
|
194
|
+
normalized.push(argument);
|
|
195
|
+
booleanValues.set(normalizedIndex, true);
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
const value = argument.slice(separatorIndex + 1);
|
|
199
|
+
if (value !== "true" && value !== "false") {
|
|
200
|
+
throw new CliUsageError(`Option '--${cliName}' accepts only 'true' or 'false' as an inline value.`);
|
|
201
|
+
}
|
|
202
|
+
normalized.push(`--${cliName}`);
|
|
203
|
+
booleanValues.set(normalizedIndex, value === "true");
|
|
204
|
+
}
|
|
205
|
+
return { args: normalized, booleanValues };
|
|
206
|
+
}
|
|
207
|
+
function applyCliValue(output, definition, token, booleanValues) {
|
|
208
|
+
if (definition.kind === "boolean") {
|
|
209
|
+
const value = booleanValues.get(token.index);
|
|
210
|
+
if (value === undefined) {
|
|
211
|
+
throw new Error(`Boolean option '${token.rawName}' was not normalized.`);
|
|
212
|
+
}
|
|
213
|
+
output[definition.sdkName] = value;
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
const rawValue = requiredTokenValue(token);
|
|
217
|
+
if (definition.multiple) {
|
|
218
|
+
const current = output[definition.sdkName];
|
|
219
|
+
if (current === undefined) {
|
|
220
|
+
output[definition.sdkName] = [rawValue];
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
if (!Array.isArray(current)) {
|
|
224
|
+
throw new Error(`Repeated option '${token.rawName}' has invalid state.`);
|
|
225
|
+
}
|
|
226
|
+
current.push(rawValue);
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
output[definition.sdkName] =
|
|
230
|
+
definition.kind === "number"
|
|
231
|
+
? parseFiniteNumber(rawValue, token.rawName)
|
|
232
|
+
: rawValue;
|
|
233
|
+
}
|
|
234
|
+
function requiredTokenValue(token) {
|
|
235
|
+
if (token.value === undefined) {
|
|
236
|
+
throw new Error(`String option '${token.rawName}' has no parsed value.`);
|
|
237
|
+
}
|
|
238
|
+
return token.value;
|
|
239
|
+
}
|
|
240
|
+
function parseFiniteNumber(value, optionName) {
|
|
241
|
+
const parsed = Number(value);
|
|
242
|
+
if (value.trim() === "" || !Number.isFinite(parsed)) {
|
|
243
|
+
throw new CliUsageError(`Option '${optionName}' requires a finite number.`);
|
|
244
|
+
}
|
|
245
|
+
return parsed;
|
|
246
|
+
}
|
|
247
|
+
function parseCaptureMethod(value) {
|
|
248
|
+
if (value !== "get" && value !== "post") {
|
|
249
|
+
throw new CliUsageError("Option '--method' must be 'get' or 'post'.");
|
|
250
|
+
}
|
|
251
|
+
return value;
|
|
252
|
+
}
|
|
253
|
+
function parseOptionsObject(text) {
|
|
254
|
+
let parsed;
|
|
255
|
+
try {
|
|
256
|
+
parsed = JSON.parse(text);
|
|
257
|
+
}
|
|
258
|
+
catch (error) {
|
|
259
|
+
const detail = error instanceof Error ? ` ${error.message}` : "";
|
|
260
|
+
throw new CliUsageError(`Options input is not valid JSON.${detail}`, {
|
|
261
|
+
cause: error,
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
265
|
+
throw new CliUsageError("Options input must be a JSON object.");
|
|
266
|
+
}
|
|
267
|
+
const output = {};
|
|
268
|
+
for (const [apiName, value] of Object.entries(parsed)) {
|
|
269
|
+
const definition = DEFINITIONS_BY_API_NAME.get(apiName);
|
|
270
|
+
if (definition === undefined)
|
|
271
|
+
throw unknownOption(apiName);
|
|
272
|
+
const parsedValue = parseOptionsObjectValue(definition, value);
|
|
273
|
+
if (parsedValue !== OMIT_VALUE) {
|
|
274
|
+
output[definition.sdkName] = parsedValue;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return output;
|
|
278
|
+
}
|
|
279
|
+
function parseOptionsObjectValue(definition, value) {
|
|
280
|
+
if (value === null)
|
|
281
|
+
return OMIT_VALUE;
|
|
282
|
+
if (definition.multiple) {
|
|
283
|
+
if (!Array.isArray(value) ||
|
|
284
|
+
!value.every((item) => typeof item === "string")) {
|
|
285
|
+
throw invalidOptionsType(definition.apiName, "an array of strings");
|
|
286
|
+
}
|
|
287
|
+
return value.length === 0 ? OMIT_VALUE : [...value];
|
|
288
|
+
}
|
|
289
|
+
if (definition.kind === "string") {
|
|
290
|
+
if (typeof value !== "string") {
|
|
291
|
+
throw invalidOptionsType(definition.apiName, "a string");
|
|
292
|
+
}
|
|
293
|
+
return value;
|
|
294
|
+
}
|
|
295
|
+
if (definition.kind === "number") {
|
|
296
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
297
|
+
throw invalidOptionsType(definition.apiName, "a finite number");
|
|
298
|
+
}
|
|
299
|
+
return value;
|
|
300
|
+
}
|
|
301
|
+
if (typeof value !== "boolean") {
|
|
302
|
+
throw invalidOptionsType(definition.apiName, "a boolean");
|
|
303
|
+
}
|
|
304
|
+
return value;
|
|
305
|
+
}
|
|
306
|
+
function invalidOptionsType(apiName, expected) {
|
|
307
|
+
return new CliUsageError(`Option '${apiName}' in the options input must be ${expected}.`);
|
|
308
|
+
}
|
|
309
|
+
function unknownOption(name) {
|
|
310
|
+
return new CliUsageError(`Unknown option '${name}'.`);
|
|
311
|
+
}
|
|
312
|
+
function duplicateOption(name) {
|
|
313
|
+
return new CliUsageError(`Option '${name}' may only be specified once.`);
|
|
314
|
+
}
|
|
315
|
+
function usageErrorFrom(error) {
|
|
316
|
+
return new CliUsageError(error instanceof Error ? error.message : String(error), { cause: error });
|
|
317
|
+
}
|
|
318
|
+
async function readStandardInput() {
|
|
319
|
+
const chunks = [];
|
|
320
|
+
for await (const chunk of process.stdin) {
|
|
321
|
+
if (typeof chunk === "string") {
|
|
322
|
+
chunks.push(Buffer.from(chunk));
|
|
323
|
+
}
|
|
324
|
+
else if (chunk instanceof Uint8Array) {
|
|
325
|
+
chunks.push(chunk);
|
|
326
|
+
}
|
|
327
|
+
else {
|
|
328
|
+
throw new TypeError("Standard input produced a non-byte chunk.");
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
332
|
+
}
|
package/dist/result.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { writeFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
const DEFAULT_CAPTURE_RESULT_IO = {
|
|
4
|
+
currentDirectory: () => process.cwd(),
|
|
5
|
+
writeFile: (path, bytes) => writeFile(path, bytes),
|
|
6
|
+
writeStandardOutput,
|
|
7
|
+
};
|
|
8
|
+
/** Save or display one successful SDK capture response without transforming it. */
|
|
9
|
+
export async function writeCaptureResult(response, outputPath, io = DEFAULT_CAPTURE_RESULT_IO) {
|
|
10
|
+
const bytes = response.kind === "binary" ? response.bytes : response.rawResponse.body;
|
|
11
|
+
if (outputPath === "-" ||
|
|
12
|
+
(outputPath === undefined && response.kind === "json")) {
|
|
13
|
+
await io.writeStandardOutput(bytes);
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
const destination = outputPath ??
|
|
17
|
+
join(io.currentDirectory(), `screenshot${captureFileExtension(response.rawResponse.contentType)}`);
|
|
18
|
+
await io.writeFile(destination, bytes);
|
|
19
|
+
}
|
|
20
|
+
/** Map only a returned response Content-Type to an automatic file extension. */
|
|
21
|
+
export function captureFileExtension(contentType) {
|
|
22
|
+
const mediaType = contentType?.split(";", 1)[0]?.trim().toLowerCase();
|
|
23
|
+
switch (mediaType) {
|
|
24
|
+
case "image/png":
|
|
25
|
+
return ".png";
|
|
26
|
+
case "image/jpeg":
|
|
27
|
+
return ".jpg";
|
|
28
|
+
case "image/webp":
|
|
29
|
+
return ".webp";
|
|
30
|
+
case "image/gif":
|
|
31
|
+
return ".gif";
|
|
32
|
+
case "image/tiff":
|
|
33
|
+
return ".tiff";
|
|
34
|
+
case "application/pdf":
|
|
35
|
+
return ".pdf";
|
|
36
|
+
default:
|
|
37
|
+
return ".bin";
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function writeStandardOutput(bytes) {
|
|
41
|
+
return new Promise((resolve, reject) => {
|
|
42
|
+
process.stdout.write(bytes, (error) => {
|
|
43
|
+
if (error === null || error === undefined) {
|
|
44
|
+
resolve();
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
reject(error);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
}
|
package/dist/sdk.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { CaptureHttpMethod, ScreenshotScoutClient, } from "@screenshotscout/sdk";
|
|
2
|
+
const DEFAULT_CLIENT_FACTORY = (options) => new ScreenshotScoutClient(options);
|
|
3
|
+
/** Construct the official SDK client from the two approved environment names. */
|
|
4
|
+
export function createScreenshotScoutClient(environment = process.env, clientFactory = DEFAULT_CLIENT_FACTORY) {
|
|
5
|
+
const accessKey = environment.SCREENSHOTSCOUT_ACCESS_KEY;
|
|
6
|
+
const secretKey = environment.SCREENSHOTSCOUT_SECRET_KEY;
|
|
7
|
+
const options = {
|
|
8
|
+
// Missing and invalid values deliberately reach the SDK's own validation.
|
|
9
|
+
accessKey: accessKey,
|
|
10
|
+
...(secretKey === undefined ? {} : { secretKey }),
|
|
11
|
+
};
|
|
12
|
+
return clientFactory(options);
|
|
13
|
+
}
|
|
14
|
+
/** Make one capture call with only an explicitly selected request method. */
|
|
15
|
+
export function captureWithScreenshotScoutClient(client, targetUrl, captureOptions, method) {
|
|
16
|
+
if (method === undefined) {
|
|
17
|
+
return client.capture(targetUrl, captureOptions);
|
|
18
|
+
}
|
|
19
|
+
const captureRequestOptions = {
|
|
20
|
+
method: method === "get" ? CaptureHttpMethod.GET : CaptureHttpMethod.POST,
|
|
21
|
+
};
|
|
22
|
+
return client.capture(targetUrl, captureOptions, captureRequestOptions);
|
|
23
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@screenshotscout/cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Official command-line interface for the Screenshot Scout screenshot API.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"screenshotscout": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"README.md",
|
|
12
|
+
"LICENSE",
|
|
13
|
+
"SECURITY.md"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "npm run clean && tsc -p tsconfig.build.json",
|
|
17
|
+
"clean": "node -e \"const fs = require('node:fs'); fs.rmSync('dist', { force: true, recursive: true });\"",
|
|
18
|
+
"format": "prettier --write .",
|
|
19
|
+
"format:check": "prettier --check .",
|
|
20
|
+
"lint": "eslint .",
|
|
21
|
+
"pretest": "npm run build",
|
|
22
|
+
"test": "tsx --test test/**/*.test.ts",
|
|
23
|
+
"typecheck": "tsc -p tsconfig.json"
|
|
24
|
+
},
|
|
25
|
+
"author": "Oleksii Velykyi",
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "git+https://github.com/screenshotscout/screenshotscout-cli.git"
|
|
30
|
+
},
|
|
31
|
+
"homepage": "https://screenshotscout.com/docs/cli",
|
|
32
|
+
"bugs": {
|
|
33
|
+
"url": "https://github.com/screenshotscout/screenshotscout-cli/issues"
|
|
34
|
+
},
|
|
35
|
+
"keywords": [
|
|
36
|
+
"cli",
|
|
37
|
+
"command-line",
|
|
38
|
+
"screenshot",
|
|
39
|
+
"screenshot-api",
|
|
40
|
+
"screenshotscout",
|
|
41
|
+
"url-to-image",
|
|
42
|
+
"webpage-screenshot",
|
|
43
|
+
"website-screenshot"
|
|
44
|
+
],
|
|
45
|
+
"publishConfig": {
|
|
46
|
+
"access": "public"
|
|
47
|
+
},
|
|
48
|
+
"engines": {
|
|
49
|
+
"node": ">=22"
|
|
50
|
+
},
|
|
51
|
+
"dependencies": {
|
|
52
|
+
"@screenshotscout/sdk": "0.1.2"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@eslint/js": "10.0.1",
|
|
56
|
+
"@types/node": "22.20.1",
|
|
57
|
+
"eslint": "10.8.1",
|
|
58
|
+
"prettier": "3.9.6",
|
|
59
|
+
"tsx": "4.23.11",
|
|
60
|
+
"typescript": "6.0.3",
|
|
61
|
+
"typescript-eslint": "8.66.0"
|
|
62
|
+
}
|
|
63
|
+
}
|