@reshotdev/screenshot 0.0.1-beta.1 → 0.0.1-beta.11
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 +65 -7
- package/package.json +9 -2
- package/src/commands/auth.js +108 -26
- package/src/commands/certify.js +62 -0
- package/src/commands/ci-run.js +57 -2
- package/src/commands/ci-setup.js +5 -5
- package/src/commands/doctor-release.js +67 -0
- package/src/commands/doctor-target.js +49 -0
- package/src/commands/drifts.js +5 -70
- package/src/commands/import-tests.js +13 -13
- package/src/commands/ingest.js +10 -10
- package/src/commands/init.js +16 -277
- package/src/commands/publish.js +204 -237
- package/src/commands/pull.js +253 -23
- package/src/commands/run.js +292 -12
- package/src/commands/setup-wizard.js +277 -499
- package/src/commands/setup.js +41 -13
- package/src/commands/status.js +313 -125
- package/src/commands/sync.js +28 -236
- package/src/commands/ui.js +1 -1
- package/src/commands/verify-publish.js +46 -0
- package/src/index.js +194 -94
- package/src/lib/api-client.js +121 -35
- package/src/lib/capture-engine.js +103 -7
- package/src/lib/capture-script-runner.js +359 -58
- package/src/lib/certification.js +865 -0
- package/src/lib/config.js +181 -76
- package/src/lib/record-cdp.js +288 -16
- package/src/lib/record-config.js +1 -1
- package/src/lib/release-doctor.js +313 -0
- package/src/lib/run-manifest.js +103 -0
- package/src/lib/standalone-mode.js +1 -1
- package/src/lib/storage-providers.js +4 -4
- package/src/lib/target-contract.js +292 -0
- package/src/lib/ui-api.js +6 -7
- package/web/manager/dist/assets/{index--ZgioErz.js → index-D2qqcFNN.js} +1 -1
- package/web/manager/dist/index.html +1 -1
- package/src/commands/validate-docs.js +0 -529
|
@@ -1,42 +1,12 @@
|
|
|
1
|
-
// setup-wizard.js -
|
|
2
|
-
//
|
|
3
|
-
// Individual commands (auth, init) still exist for power users
|
|
1
|
+
// setup-wizard.js - Interactive setup wizard
|
|
2
|
+
// Streamlined flow: choose lane, create config, optional Studio launch
|
|
4
3
|
|
|
5
4
|
const inquirer = require("inquirer");
|
|
6
5
|
const chalk = require("chalk");
|
|
7
6
|
const fs = require("fs-extra");
|
|
8
7
|
const path = require("path");
|
|
9
8
|
const config = require("../lib/config");
|
|
10
|
-
const {
|
|
11
|
-
validateStorageConfig,
|
|
12
|
-
getStorageSetupHelp,
|
|
13
|
-
} = require("../lib/storage-providers");
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Auto-detect documentation directories
|
|
17
|
-
*/
|
|
18
|
-
function detectDocumentationRoot() {
|
|
19
|
-
const commonPaths = [
|
|
20
|
-
"docs",
|
|
21
|
-
"documentation",
|
|
22
|
-
"content",
|
|
23
|
-
"doc",
|
|
24
|
-
"guides",
|
|
25
|
-
".vitepress/content",
|
|
26
|
-
".docusaurus/docs",
|
|
27
|
-
];
|
|
28
|
-
|
|
29
|
-
for (const dir of commonPaths) {
|
|
30
|
-
const fullPath = path.join(process.cwd(), dir);
|
|
31
|
-
if (fs.existsSync(fullPath) && fs.statSync(fullPath).isDirectory()) {
|
|
32
|
-
const files = fs.readdirSync(fullPath);
|
|
33
|
-
if (files.some((f) => f.endsWith(".md") || f.endsWith(".mdx"))) {
|
|
34
|
-
return dir;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
return null;
|
|
39
|
-
}
|
|
9
|
+
const { normalizeConfigContract } = require("../lib/target-contract");
|
|
40
10
|
|
|
41
11
|
/**
|
|
42
12
|
* Detect if this is a Git repository and if it's GitHub
|
|
@@ -91,184 +61,104 @@ function detectPlaywright() {
|
|
|
91
61
|
return { hasPlaywright: false, configFile: null };
|
|
92
62
|
}
|
|
93
63
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
include: ["**/*.md", "**/*.mdx"],
|
|
101
|
-
exclude: ["**/_*.mdx", "**/node_modules/**", "**/.next/**"],
|
|
102
|
-
strategy,
|
|
103
|
-
assetFormat: "markdown",
|
|
104
|
-
mappings: {},
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Scaffold a basic documentation structure
|
|
110
|
-
*/
|
|
111
|
-
async function scaffoldDocumentation(template = "basic") {
|
|
112
|
-
const docsDir = path.join(process.cwd(), "docs");
|
|
113
|
-
fs.ensureDirSync(docsDir);
|
|
114
|
-
|
|
115
|
-
const readmeContent = `# Documentation
|
|
116
|
-
|
|
117
|
-
This directory contains documentation for your project.
|
|
118
|
-
|
|
119
|
-
## Linking Docs to Visual Journeys
|
|
120
|
-
|
|
121
|
-
Add \`reshot_journey\` frontmatter to your markdown files to link them to Playwright test captures:
|
|
122
|
-
|
|
123
|
-
\`\`\`markdown
|
|
124
|
-
---
|
|
125
|
-
title: "Feature Name"
|
|
126
|
-
reshot_journey: "feature/workflow-name"
|
|
127
|
-
---
|
|
128
|
-
|
|
129
|
-
# Feature Documentation
|
|
130
|
-
|
|
131
|
-
Your content here...
|
|
132
|
-
\`\`\`
|
|
133
|
-
|
|
134
|
-
The journey key should match your Playwright test structure. For example:
|
|
135
|
-
- Test file: \`tests/01-auth-flows.spec.ts\` with "Login Flow" describe block
|
|
136
|
-
- Journey key: \`auth-flows/login-flow\`
|
|
137
|
-
|
|
138
|
-
## Commands
|
|
139
|
-
|
|
140
|
-
- \`reshot import-tests\` - Import existing Playwright tests
|
|
141
|
-
- \`reshot sync\` - Sync visuals and documentation
|
|
142
|
-
- \`reshot status\` - View sync status and drifts
|
|
143
|
-
- \`reshot validate\` - Validate configuration
|
|
144
|
-
- \`reshot studio\` - Open visual management UI
|
|
64
|
+
function detectSetupMode(projectConfig, useCloud) {
|
|
65
|
+
const normalizedConfig =
|
|
66
|
+
projectConfig && typeof projectConfig === "object"
|
|
67
|
+
? normalizeConfigContract(projectConfig)
|
|
68
|
+
: null;
|
|
69
|
+
const targetTier = normalizedConfig?.target?.tier || null;
|
|
145
70
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
fs.writeFileSync(path.join(docsDir, "README.md"), readmeContent);
|
|
150
|
-
|
|
151
|
-
const exampleContent = `---
|
|
152
|
-
title: "Getting Started"
|
|
153
|
-
reshot_journey: "onboarding/first-steps"
|
|
154
|
-
description: "Quick start guide for new users"
|
|
155
|
-
---
|
|
156
|
-
|
|
157
|
-
# Getting Started
|
|
158
|
-
|
|
159
|
-
Welcome! This is an example documentation file linked to the \`onboarding/first-steps\` journey.
|
|
160
|
-
|
|
161
|
-
## Overview
|
|
162
|
-
|
|
163
|
-
When Reshot detects UI changes in your Playwright tests, it will automatically propose updates to this document with new screenshots and descriptions.
|
|
164
|
-
|
|
165
|
-
## Steps
|
|
166
|
-
|
|
167
|
-
1. **Step 1**: Description of first step
|
|
168
|
-
|
|
169
|
-
<!-- Reshot will inject screenshots here when visual drift is detected -->
|
|
170
|
-
|
|
171
|
-
2. **Step 2**: Description of second step
|
|
172
|
-
|
|
173
|
-
3. **Step 3**: Description of third step
|
|
174
|
-
|
|
175
|
-
## Next Steps
|
|
71
|
+
if (targetTier === "certified") {
|
|
72
|
+
return "certified-target";
|
|
73
|
+
}
|
|
176
74
|
|
|
177
|
-
|
|
178
|
-
-
|
|
179
|
-
|
|
180
|
-
`;
|
|
75
|
+
if (targetTier === "candidate") {
|
|
76
|
+
return "candidate-target";
|
|
77
|
+
}
|
|
181
78
|
|
|
182
|
-
|
|
183
|
-
return "docs";
|
|
79
|
+
return useCloud ? "cloud-connected" : "local-only";
|
|
184
80
|
}
|
|
185
81
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
{
|
|
192
|
-
name: "Astro/Starlight",
|
|
193
|
-
files: ["astro.config.mjs", "astro.config.ts"],
|
|
194
|
-
deps: ["@astrojs/starlight"],
|
|
195
|
-
},
|
|
196
|
-
{
|
|
197
|
-
name: "Docusaurus",
|
|
198
|
-
files: ["docusaurus.config.js", "docusaurus.config.ts"],
|
|
199
|
-
deps: ["@docusaurus/core"],
|
|
200
|
-
},
|
|
201
|
-
{
|
|
202
|
-
name: "VitePress",
|
|
203
|
-
files: [".vitepress/config.js", ".vitepress/config.ts"],
|
|
204
|
-
deps: ["vitepress"],
|
|
205
|
-
},
|
|
206
|
-
{ name: "Mintlify", files: ["mint.json"], deps: [] },
|
|
207
|
-
{ name: "GitBook", files: [".gitbook.yaml"], deps: [] },
|
|
208
|
-
];
|
|
82
|
+
function getRecommendedLocalServerCommand(normalizedConfig) {
|
|
83
|
+
const explicitCommand = normalizedConfig?.target?.supportedLocalCommand;
|
|
84
|
+
if (explicitCommand) {
|
|
85
|
+
return explicitCommand;
|
|
86
|
+
}
|
|
209
87
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
for (const file of fw.files) {
|
|
213
|
-
if (fs.existsSync(path.join(process.cwd(), file))) {
|
|
214
|
-
return { framework: fw.name, detected: true };
|
|
215
|
-
}
|
|
216
|
-
}
|
|
88
|
+
if (normalizedConfig?.target?.tier === "certified") {
|
|
89
|
+
return "npm run build && npm run start";
|
|
217
90
|
}
|
|
218
91
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
const pkg = fs.readJsonSync(path.join(process.cwd(), "package.json"));
|
|
222
|
-
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
223
94
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
return { framework: fw.name, detected: true };
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
} catch {
|
|
232
|
-
// No package.json
|
|
95
|
+
function getNextRecommendedCommand(mode, useCloud) {
|
|
96
|
+
if (mode === "certified-target" || mode === "candidate-target") {
|
|
97
|
+
return "reshot doctor target";
|
|
233
98
|
}
|
|
234
99
|
|
|
235
|
-
return
|
|
100
|
+
return useCloud ? "reshot publish" : "reshot run";
|
|
236
101
|
}
|
|
237
102
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
103
|
+
function writeSetupReport({
|
|
104
|
+
mode,
|
|
105
|
+
useCloud,
|
|
106
|
+
projectId,
|
|
107
|
+
projectName,
|
|
108
|
+
configCreated,
|
|
109
|
+
playwrightDetected,
|
|
110
|
+
supportedLocalCommand,
|
|
111
|
+
}) {
|
|
112
|
+
const reportPath = path.join(
|
|
113
|
+
process.cwd(),
|
|
114
|
+
".reshot",
|
|
115
|
+
"reports",
|
|
116
|
+
"self-serve-setup.json",
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
fs.ensureDirSync(path.dirname(reportPath));
|
|
120
|
+
fs.writeJSONSync(
|
|
121
|
+
reportPath,
|
|
122
|
+
{
|
|
123
|
+
generatedAt: new Date().toISOString(),
|
|
124
|
+
mode,
|
|
125
|
+
nextRecommendedCommand: getNextRecommendedCommand(mode, useCloud),
|
|
126
|
+
useCloud,
|
|
127
|
+
projectId: projectId || null,
|
|
128
|
+
projectName: projectName || null,
|
|
129
|
+
configCreated,
|
|
130
|
+
playwrightDetected,
|
|
131
|
+
supportedEnvironment: {
|
|
132
|
+
launchSupported: "production-like localhost",
|
|
133
|
+
launchUnsupported: ["next dev"],
|
|
134
|
+
supportedLocalCommand: supportedLocalCommand || null,
|
|
135
|
+
},
|
|
136
|
+
blockingIssues: [],
|
|
137
|
+
advisories: [
|
|
138
|
+
"Run your target app with a production-like local server for launch-grade captures.",
|
|
139
|
+
],
|
|
140
|
+
nextMilestones: [
|
|
141
|
+
"reshot setup",
|
|
142
|
+
"reshot run",
|
|
143
|
+
...(useCloud ? ["reshot publish"] : []),
|
|
144
|
+
],
|
|
145
|
+
},
|
|
146
|
+
{ spaces: 2 },
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
return reportPath;
|
|
257
150
|
}
|
|
258
151
|
|
|
259
152
|
/**
|
|
260
153
|
* Main setup wizard
|
|
261
154
|
*/
|
|
262
155
|
async function setupWizard(options = {}) {
|
|
263
|
-
const { offline = false, force = false } = options;
|
|
156
|
+
const { offline = false, force = false, noStudio = false } = options;
|
|
264
157
|
|
|
265
|
-
console.log(chalk.cyan.bold("\n🚀 Reshot Setup
|
|
266
|
-
console.log(chalk.gray("Let's configure Reshot for your project.\n"));
|
|
158
|
+
console.log(chalk.cyan.bold("\n🚀 Reshot Setup\n"));
|
|
267
159
|
|
|
268
160
|
// Detect project context
|
|
269
|
-
const gitInfo = detectGitInfo();
|
|
270
161
|
const playwrightInfo = detectPlaywright();
|
|
271
|
-
const existingDocsRoot = detectDocumentationRoot();
|
|
272
162
|
|
|
273
163
|
// Check existing setup
|
|
274
164
|
let existingSettings = null;
|
|
@@ -292,7 +182,7 @@ async function setupWizard(options = {}) {
|
|
|
292
182
|
// No existing config
|
|
293
183
|
}
|
|
294
184
|
|
|
295
|
-
// If already set up and not forcing,
|
|
185
|
+
// If already set up and not forcing, show status and offer options
|
|
296
186
|
if ((isAlreadyAuthed || existingConfig) && !force) {
|
|
297
187
|
console.log(
|
|
298
188
|
chalk.yellow("⚠ Reshot is already configured in this project.\n"),
|
|
@@ -307,20 +197,73 @@ async function setupWizard(options = {}) {
|
|
|
307
197
|
if (existingConfig) {
|
|
308
198
|
console.log(
|
|
309
199
|
chalk.green(" ✔ Config found:"),
|
|
310
|
-
chalk.cyan("
|
|
200
|
+
chalk.cyan("reshot.config.json"),
|
|
311
201
|
);
|
|
312
202
|
}
|
|
313
203
|
|
|
314
|
-
const
|
|
204
|
+
const choices = [];
|
|
205
|
+
|
|
206
|
+
if (isAlreadyAuthed) {
|
|
207
|
+
choices.push({
|
|
208
|
+
name: "Re-authenticate (connect to a different project)",
|
|
209
|
+
value: "reauth",
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
choices.push({
|
|
214
|
+
name: "Reconfigure (regenerate reshot.config.json)",
|
|
215
|
+
value: "reconfig",
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
choices.push({
|
|
219
|
+
name: "Exit",
|
|
220
|
+
value: "exit",
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
const { action } = await inquirer.prompt([
|
|
315
224
|
{
|
|
316
|
-
type: "
|
|
317
|
-
name: "
|
|
318
|
-
message: "
|
|
319
|
-
|
|
225
|
+
type: "list",
|
|
226
|
+
name: "action",
|
|
227
|
+
message: "What would you like to do?",
|
|
228
|
+
choices,
|
|
320
229
|
},
|
|
321
230
|
]);
|
|
322
231
|
|
|
323
|
-
if (
|
|
232
|
+
if (action === "exit") {
|
|
233
|
+
console.log(
|
|
234
|
+
chalk.gray("\nRun"),
|
|
235
|
+
chalk.cyan("reshot studio"),
|
|
236
|
+
chalk.gray("to manage your visuals.\n"),
|
|
237
|
+
);
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (action === "reauth") {
|
|
242
|
+
console.log(chalk.gray("\nOpening browser for authentication...\n"));
|
|
243
|
+
const authCommand = require("./auth");
|
|
244
|
+
const authResult = await authCommand();
|
|
245
|
+
|
|
246
|
+
// Re-read settings after auth
|
|
247
|
+
try {
|
|
248
|
+
existingSettings = config.readSettings();
|
|
249
|
+
isAlreadyAuthed = !!(
|
|
250
|
+
existingSettings?.apiKey && existingSettings?.projectId
|
|
251
|
+
);
|
|
252
|
+
} catch {
|
|
253
|
+
throw new Error("Authentication failed. Please try again.");
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (!isAlreadyAuthed) {
|
|
257
|
+
throw new Error("Authentication was not completed.");
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
console.log(
|
|
261
|
+
chalk.green("\n✔ Connected to"),
|
|
262
|
+
chalk.cyan(existingSettings.projectName || existingSettings.projectId),
|
|
263
|
+
);
|
|
264
|
+
if (authResult?.mode) {
|
|
265
|
+
console.log(chalk.gray(` Mode: ${authResult.mode}`));
|
|
266
|
+
}
|
|
324
267
|
console.log(
|
|
325
268
|
chalk.gray("\nRun"),
|
|
326
269
|
chalk.cyan("reshot studio"),
|
|
@@ -328,19 +271,16 @@ async function setupWizard(options = {}) {
|
|
|
328
271
|
);
|
|
329
272
|
return;
|
|
330
273
|
}
|
|
274
|
+
|
|
275
|
+
// action === "reconfig" — fall through to config generation below
|
|
331
276
|
}
|
|
332
277
|
|
|
333
278
|
// ========================================
|
|
334
279
|
// STEP 1: Platform Connection
|
|
335
280
|
// ========================================
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
let useCloud = false;
|
|
339
|
-
let projectId = existingSettings?.projectId;
|
|
340
|
-
let apiKey = existingSettings?.apiKey;
|
|
341
|
-
let projectName = existingSettings?.projectName;
|
|
281
|
+
if (!isAlreadyAuthed && !offline) {
|
|
282
|
+
console.log(chalk.cyan("\n━━━ Step 1: Choose Your Lane ━━━\n"));
|
|
342
283
|
|
|
343
|
-
if (!offline) {
|
|
344
284
|
const { connectToCloud } = await inquirer.prompt([
|
|
345
285
|
{
|
|
346
286
|
type: "list",
|
|
@@ -348,33 +288,29 @@ async function setupWizard(options = {}) {
|
|
|
348
288
|
message: "How would you like to use Reshot?",
|
|
349
289
|
choices: [
|
|
350
290
|
{
|
|
351
|
-
name: `${chalk.cyan("
|
|
291
|
+
name: `${chalk.cyan("Set up hosted pipeline")} - Connect to Reshot Cloud for hosted assets, review workflows, and team collaboration`,
|
|
352
292
|
value: true,
|
|
353
293
|
},
|
|
354
294
|
{
|
|
355
|
-
name: `${chalk.gray("
|
|
295
|
+
name: `${chalk.gray("Start locally")} - Get a first capture working on your machine before you add hosted delivery`,
|
|
356
296
|
value: false,
|
|
357
297
|
},
|
|
358
298
|
],
|
|
359
|
-
default: isAlreadyAuthed ? 0 : 1,
|
|
360
299
|
},
|
|
361
300
|
]);
|
|
362
301
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
if (useCloud && !isAlreadyAuthed) {
|
|
302
|
+
if (connectToCloud) {
|
|
366
303
|
console.log(chalk.gray("\nOpening browser for authentication...\n"));
|
|
367
304
|
|
|
368
305
|
const authCommand = require("./auth");
|
|
369
|
-
await authCommand();
|
|
306
|
+
const authResult = await authCommand();
|
|
370
307
|
|
|
371
308
|
// Re-read settings after auth
|
|
372
309
|
try {
|
|
373
310
|
existingSettings = config.readSettings();
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
isAlreadyAuthed = !!(apiKey && projectId);
|
|
311
|
+
isAlreadyAuthed = !!(
|
|
312
|
+
existingSettings?.apiKey && existingSettings?.projectId
|
|
313
|
+
);
|
|
378
314
|
} catch {
|
|
379
315
|
throw new Error("Authentication failed. Please try again.");
|
|
380
316
|
}
|
|
@@ -385,105 +321,46 @@ async function setupWizard(options = {}) {
|
|
|
385
321
|
|
|
386
322
|
console.log(
|
|
387
323
|
chalk.green("\n✔ Connected to"),
|
|
388
|
-
chalk.cyan(projectName || projectId),
|
|
324
|
+
chalk.cyan(existingSettings.projectName || existingSettings.projectId),
|
|
389
325
|
);
|
|
390
|
-
|
|
326
|
+
if (authResult?.mode) {
|
|
327
|
+
console.log(chalk.gray(` Mode: ${authResult.mode}`));
|
|
328
|
+
}
|
|
329
|
+
} else {
|
|
391
330
|
console.log(
|
|
392
|
-
chalk.
|
|
393
|
-
|
|
331
|
+
chalk.gray(
|
|
332
|
+
"Running in local-only mode — hosted publish and pull stay unavailable until you run `reshot auth`.",
|
|
333
|
+
),
|
|
394
334
|
);
|
|
395
335
|
}
|
|
396
|
-
} else {
|
|
397
|
-
console.log(
|
|
336
|
+
} else if (offline) {
|
|
337
|
+
console.log(
|
|
338
|
+
chalk.gray(
|
|
339
|
+
"Running in local-only mode — hosted publish and pull stay unavailable until you run `reshot auth`.",
|
|
340
|
+
),
|
|
341
|
+
);
|
|
398
342
|
}
|
|
399
343
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
const
|
|
406
|
-
|
|
407
|
-
type: "checkbox",
|
|
408
|
-
name: "features",
|
|
409
|
-
message: "Which features would you like to enable?",
|
|
410
|
-
choices: [
|
|
411
|
-
{
|
|
412
|
-
name: "Visual capture from Playwright tests",
|
|
413
|
-
value: "visuals",
|
|
414
|
-
checked: playwrightInfo.hasPlaywright,
|
|
415
|
-
},
|
|
416
|
-
{
|
|
417
|
-
name: "Documentation sync (detect drift, auto-update docs)",
|
|
418
|
-
value: "docsync",
|
|
419
|
-
checked: !!existingDocsRoot,
|
|
420
|
-
},
|
|
421
|
-
],
|
|
422
|
-
validate: (answers) => {
|
|
423
|
-
if (answers.length === 0) {
|
|
424
|
-
return "Please select at least one feature";
|
|
425
|
-
}
|
|
426
|
-
return true;
|
|
427
|
-
},
|
|
428
|
-
},
|
|
429
|
-
]);
|
|
430
|
-
|
|
431
|
-
const enableVisuals = features.includes("visuals");
|
|
432
|
-
const enableDocSync = features.includes("docsync");
|
|
344
|
+
const useCloud = isAlreadyAuthed;
|
|
345
|
+
const projectId = existingSettings?.projectId;
|
|
346
|
+
const normalizedSetupConfig = normalizeConfigContract(
|
|
347
|
+
existingConfig && typeof existingConfig === "object" ? existingConfig : {},
|
|
348
|
+
);
|
|
349
|
+
const supportedLocalCommand =
|
|
350
|
+
getRecommendedLocalServerCommand(normalizedSetupConfig);
|
|
433
351
|
|
|
434
352
|
// ========================================
|
|
435
|
-
// STEP
|
|
353
|
+
// STEP 2: Project Defaults
|
|
436
354
|
// ========================================
|
|
437
|
-
|
|
438
|
-
let journeyMappings = {};
|
|
355
|
+
console.log(chalk.cyan("\n━━━ Step 2: Project Defaults ━━━\n"));
|
|
439
356
|
|
|
440
|
-
|
|
441
|
-
console.log(chalk.cyan("\n━━━ Step 3: Visual Capture ━━━\n"));
|
|
442
|
-
|
|
443
|
-
if (playwrightInfo.hasPlaywright) {
|
|
444
|
-
console.log(chalk.green("✔ Playwright detected"));
|
|
445
|
-
if (playwrightInfo.configFile) {
|
|
446
|
-
console.log(chalk.gray(` Config: ${playwrightInfo.configFile}`));
|
|
447
|
-
}
|
|
357
|
+
let traceDir = "./test-results";
|
|
448
358
|
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
name: "importTests",
|
|
454
|
-
message:
|
|
455
|
-
"Import existing Playwright tests to create journey mappings?",
|
|
456
|
-
default: true,
|
|
457
|
-
},
|
|
458
|
-
]);
|
|
459
|
-
|
|
460
|
-
if (importTests) {
|
|
461
|
-
console.log(chalk.gray("\nScanning Playwright tests..."));
|
|
462
|
-
try {
|
|
463
|
-
const importTestsCommand = require("./import-tests");
|
|
464
|
-
const result = await importTestsCommand({
|
|
465
|
-
interactive: false,
|
|
466
|
-
dryRun: false,
|
|
467
|
-
});
|
|
468
|
-
if (result?.journeyMappings) {
|
|
469
|
-
journeyMappings = result.journeyMappings;
|
|
470
|
-
console.log(
|
|
471
|
-
chalk.green(
|
|
472
|
-
`✔ Imported ${Object.keys(journeyMappings).length} journey mapping(s)`,
|
|
473
|
-
),
|
|
474
|
-
);
|
|
475
|
-
}
|
|
476
|
-
} catch (err) {
|
|
477
|
-
console.log(chalk.yellow(`⚠ Could not import tests: ${err.message}`));
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
} else {
|
|
481
|
-
console.log(chalk.yellow("⚠ Playwright not detected"));
|
|
482
|
-
console.log(
|
|
483
|
-
chalk.gray(" Install with: npm install -D @playwright/test"),
|
|
484
|
-
);
|
|
359
|
+
if (playwrightInfo.hasPlaywright) {
|
|
360
|
+
console.log(chalk.green("✔ Playwright detected"));
|
|
361
|
+
if (playwrightInfo.configFile) {
|
|
362
|
+
console.log(chalk.gray(` Config: ${playwrightInfo.configFile}`));
|
|
485
363
|
}
|
|
486
|
-
|
|
487
364
|
const { customTraceDir } = await inquirer.prompt([
|
|
488
365
|
{
|
|
489
366
|
type: "input",
|
|
@@ -494,245 +371,146 @@ async function setupWizard(options = {}) {
|
|
|
494
371
|
]);
|
|
495
372
|
|
|
496
373
|
traceDir = customTraceDir;
|
|
374
|
+
} else {
|
|
375
|
+
console.log(chalk.green("✔ No Playwright setup detected"));
|
|
376
|
+
console.log(
|
|
377
|
+
chalk.gray(
|
|
378
|
+
" That is okay for the local-first workflow. You can define or record scenarios and run them directly.",
|
|
379
|
+
),
|
|
380
|
+
);
|
|
497
381
|
}
|
|
498
382
|
|
|
499
383
|
// ========================================
|
|
500
|
-
//
|
|
501
|
-
// ========================================
|
|
502
|
-
let docConfig = null;
|
|
503
|
-
|
|
504
|
-
if (enableDocSync) {
|
|
505
|
-
console.log(chalk.cyan("\n━━━ Step 4: Documentation Sync ━━━\n"));
|
|
506
|
-
|
|
507
|
-
// Check for existing documentation framework
|
|
508
|
-
const docFramework = detectDocumentationFramework();
|
|
509
|
-
let docsRoot = existingDocsRoot;
|
|
510
|
-
|
|
511
|
-
if (docFramework.detected) {
|
|
512
|
-
console.log(chalk.green(`✔ ${docFramework.framework} detected`));
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
if (docsRoot) {
|
|
516
|
-
const docCount = countDocFiles(path.join(process.cwd(), docsRoot));
|
|
517
|
-
console.log(
|
|
518
|
-
chalk.green(
|
|
519
|
-
`✔ Found documentation at: ${docsRoot}/ (${docCount} files)`,
|
|
520
|
-
),
|
|
521
|
-
);
|
|
522
|
-
|
|
523
|
-
const { useDetected } = await inquirer.prompt([
|
|
524
|
-
{
|
|
525
|
-
type: "confirm",
|
|
526
|
-
name: "useDetected",
|
|
527
|
-
message: `Use ${docsRoot}/ for documentation sync?`,
|
|
528
|
-
default: true,
|
|
529
|
-
},
|
|
530
|
-
]);
|
|
531
|
-
|
|
532
|
-
if (!useDetected) {
|
|
533
|
-
docsRoot = null;
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
if (!docsRoot) {
|
|
538
|
-
const choices = [
|
|
539
|
-
{ name: "Create docs/ directory with examples", value: "scaffold" },
|
|
540
|
-
{ name: "Specify existing directory path", value: "specify" },
|
|
541
|
-
];
|
|
542
|
-
|
|
543
|
-
// Add Astro Starlight option if not already a docs framework
|
|
544
|
-
if (!docFramework.detected) {
|
|
545
|
-
choices.push({
|
|
546
|
-
name:
|
|
547
|
-
chalk.cyan("Create Astro Starlight project") +
|
|
548
|
-
chalk.gray(" (recommended for new docs)"),
|
|
549
|
-
value: "starlight",
|
|
550
|
-
});
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
const { docAction } = await inquirer.prompt([
|
|
554
|
-
{
|
|
555
|
-
type: "list",
|
|
556
|
-
name: "docAction",
|
|
557
|
-
message: "Documentation setup:",
|
|
558
|
-
choices,
|
|
559
|
-
},
|
|
560
|
-
]);
|
|
561
|
-
|
|
562
|
-
if (docAction === "scaffold") {
|
|
563
|
-
console.log(chalk.gray("\nCreating docs/ directory..."));
|
|
564
|
-
docsRoot = await scaffoldDocumentation();
|
|
565
|
-
console.log(chalk.green("✔ Created docs/ with example files"));
|
|
566
|
-
} else if (docAction === "starlight") {
|
|
567
|
-
console.log(chalk.cyan("\nTo create an Astro Starlight project:\n"));
|
|
568
|
-
console.log(
|
|
569
|
-
chalk.white(" npm create astro@latest -- --template starlight"),
|
|
570
|
-
);
|
|
571
|
-
console.log(
|
|
572
|
-
chalk.gray(
|
|
573
|
-
"\nRun that command, then re-run reshot setup to link it.\n",
|
|
574
|
-
),
|
|
575
|
-
);
|
|
576
|
-
|
|
577
|
-
// Still create a basic docs folder as fallback
|
|
578
|
-
const { createBasic } = await inquirer.prompt([
|
|
579
|
-
{
|
|
580
|
-
type: "confirm",
|
|
581
|
-
name: "createBasic",
|
|
582
|
-
message: "Create a basic docs/ folder for now?",
|
|
583
|
-
default: true,
|
|
584
|
-
},
|
|
585
|
-
]);
|
|
586
|
-
|
|
587
|
-
if (createBasic) {
|
|
588
|
-
docsRoot = await scaffoldDocumentation();
|
|
589
|
-
console.log(chalk.green("✔ Created docs/ with example files"));
|
|
590
|
-
}
|
|
591
|
-
} else {
|
|
592
|
-
const { customPath } = await inquirer.prompt([
|
|
593
|
-
{
|
|
594
|
-
type: "input",
|
|
595
|
-
name: "customPath",
|
|
596
|
-
message: "Documentation directory path:",
|
|
597
|
-
default: "./docs",
|
|
598
|
-
validate: (input) => {
|
|
599
|
-
const fullPath = path.join(process.cwd(), input);
|
|
600
|
-
if (!fs.existsSync(fullPath)) {
|
|
601
|
-
return `Directory ${input} does not exist. Create it first or use scaffolding.`;
|
|
602
|
-
}
|
|
603
|
-
return true;
|
|
604
|
-
},
|
|
605
|
-
},
|
|
606
|
-
]);
|
|
607
|
-
docsRoot = customPath;
|
|
608
|
-
}
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
// Determine strategy
|
|
612
|
-
let strategy = "git_pr";
|
|
613
|
-
|
|
614
|
-
if (useCloud && docsRoot) {
|
|
615
|
-
if (gitInfo.isGitHub) {
|
|
616
|
-
console.log(chalk.green("\n✔ GitHub repository detected"));
|
|
617
|
-
const { useGitPR } = await inquirer.prompt([
|
|
618
|
-
{
|
|
619
|
-
type: "confirm",
|
|
620
|
-
name: "useGitPR",
|
|
621
|
-
message: "Auto-create Pull Requests for documentation updates?",
|
|
622
|
-
default: true,
|
|
623
|
-
},
|
|
624
|
-
]);
|
|
625
|
-
strategy = useGitPR ? "git_pr" : "external_host";
|
|
626
|
-
} else {
|
|
627
|
-
const { strategyChoice } = await inquirer.prompt([
|
|
628
|
-
{
|
|
629
|
-
type: "list",
|
|
630
|
-
name: "strategyChoice",
|
|
631
|
-
message: "How should documentation updates be delivered?",
|
|
632
|
-
choices: [
|
|
633
|
-
{ name: "Git Pull Requests", value: "git_pr" },
|
|
634
|
-
{
|
|
635
|
-
name: "Notifications only (external CMS)",
|
|
636
|
-
value: "external_host",
|
|
637
|
-
},
|
|
638
|
-
],
|
|
639
|
-
},
|
|
640
|
-
]);
|
|
641
|
-
strategy = strategyChoice;
|
|
642
|
-
}
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
docConfig = generateDocumentationConfig(docsRoot, strategy);
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
// ========================================
|
|
649
|
-
// STEP 5: Generate Configuration
|
|
384
|
+
// Generate Configuration
|
|
650
385
|
// ========================================
|
|
651
386
|
console.log(chalk.cyan("\n━━━ Generating Configuration ━━━\n"));
|
|
652
387
|
|
|
653
388
|
const newConfig = {
|
|
654
|
-
$schema: "https://reshot.dev/schemas/
|
|
389
|
+
$schema: "https://reshot.dev/schemas/reshot-config.json",
|
|
655
390
|
version: "2.0",
|
|
656
391
|
baseUrl: existingConfig?.baseUrl || "http://localhost:3000",
|
|
657
392
|
viewport: existingConfig?.viewport || { width: 1280, height: 720 },
|
|
658
|
-
_metadata: {
|
|
659
|
-
features: {
|
|
660
|
-
visuals: enableVisuals,
|
|
661
|
-
docsync: enableDocSync,
|
|
662
|
-
},
|
|
663
|
-
},
|
|
664
393
|
};
|
|
665
394
|
|
|
666
395
|
if (useCloud && projectId) {
|
|
667
396
|
newConfig.projectId = projectId;
|
|
668
397
|
}
|
|
669
398
|
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
journeyMappings:
|
|
674
|
-
Object.keys(journeyMappings).length > 0 ? journeyMappings : undefined,
|
|
675
|
-
};
|
|
676
|
-
|
|
677
|
-
if (!useCloud) {
|
|
678
|
-
const { customAssetDir } = await inquirer.prompt([
|
|
679
|
-
{
|
|
680
|
-
type: "input",
|
|
681
|
-
name: "customAssetDir",
|
|
682
|
-
message: "Where should screenshots be saved?",
|
|
683
|
-
default: ".reshot/output",
|
|
684
|
-
},
|
|
685
|
-
]);
|
|
686
|
-
newConfig.assetDir = customAssetDir;
|
|
687
|
-
} else {
|
|
688
|
-
newConfig.assetDir = ".reshot/output";
|
|
689
|
-
}
|
|
399
|
+
newConfig.visuals = {
|
|
400
|
+
traceDir,
|
|
401
|
+
};
|
|
690
402
|
|
|
691
|
-
|
|
403
|
+
if (!useCloud) {
|
|
404
|
+
const { customAssetDir } = await inquirer.prompt([
|
|
405
|
+
{
|
|
406
|
+
type: "input",
|
|
407
|
+
name: "customAssetDir",
|
|
408
|
+
message: "Where should screenshots be saved?",
|
|
409
|
+
default: ".reshot/output",
|
|
410
|
+
},
|
|
411
|
+
]);
|
|
412
|
+
newConfig.assetDir = customAssetDir;
|
|
413
|
+
} else {
|
|
414
|
+
newConfig.assetDir = ".reshot/output";
|
|
692
415
|
}
|
|
693
416
|
|
|
694
|
-
|
|
695
|
-
newConfig.documentation = docConfig;
|
|
696
|
-
}
|
|
417
|
+
newConfig.scenarios = existingConfig?.scenarios || [];
|
|
697
418
|
|
|
698
419
|
// Write configuration
|
|
699
420
|
config.writeConfig(newConfig);
|
|
700
|
-
console.log(chalk.green("✔ Created
|
|
421
|
+
console.log(chalk.green("✔ Created reshot.config.json"));
|
|
422
|
+
|
|
423
|
+
const combinedConfig =
|
|
424
|
+
existingConfig && typeof existingConfig === "object"
|
|
425
|
+
? { ...existingConfig, ...newConfig }
|
|
426
|
+
: newConfig;
|
|
427
|
+
const normalizedCombinedConfig = normalizeConfigContract(combinedConfig);
|
|
428
|
+
const setupMode = detectSetupMode(combinedConfig, useCloud);
|
|
429
|
+
const finalSupportedLocalCommand =
|
|
430
|
+
getRecommendedLocalServerCommand(normalizedCombinedConfig) ||
|
|
431
|
+
supportedLocalCommand;
|
|
432
|
+
const reportPath = writeSetupReport({
|
|
433
|
+
mode: setupMode,
|
|
434
|
+
useCloud,
|
|
435
|
+
projectId,
|
|
436
|
+
projectName: existingSettings?.projectName,
|
|
437
|
+
configCreated: true,
|
|
438
|
+
playwrightDetected: playwrightInfo.hasPlaywright,
|
|
439
|
+
supportedLocalCommand: finalSupportedLocalCommand,
|
|
440
|
+
});
|
|
701
441
|
|
|
702
442
|
// ========================================
|
|
703
|
-
//
|
|
443
|
+
// Success & Next Steps
|
|
704
444
|
// ========================================
|
|
705
445
|
console.log(chalk.green("\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"));
|
|
706
446
|
console.log(chalk.green.bold("✔ Reshot setup complete!"));
|
|
707
447
|
console.log(chalk.green("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"));
|
|
708
448
|
|
|
709
|
-
console.log(chalk.cyan("
|
|
449
|
+
console.log(chalk.cyan("Supported launch path:\n"));
|
|
450
|
+
console.log(
|
|
451
|
+
` ${chalk.gray("Run your target app with a production-like local server before capture.")}`,
|
|
452
|
+
);
|
|
453
|
+
console.log(
|
|
454
|
+
` ${chalk.cyan(finalSupportedLocalCommand || "npm run build && npm run start")}`,
|
|
455
|
+
);
|
|
456
|
+
console.log(
|
|
457
|
+
` ${chalk.gray("Unsupported for launch reliability:")} ${chalk.yellow("next dev")}\n`,
|
|
458
|
+
);
|
|
459
|
+
|
|
460
|
+
console.log(chalk.cyan("Setup mode:\n"));
|
|
461
|
+
console.log(` ${chalk.green(setupMode)}\n`);
|
|
710
462
|
|
|
463
|
+
console.log(chalk.cyan("Next steps:\n"));
|
|
464
|
+
console.log(
|
|
465
|
+
` 1. ${chalk.gray("Review")} ${chalk.cyan("reshot.config.json")} ${chalk.gray("and add your first scenario or recording")}`,
|
|
466
|
+
);
|
|
467
|
+
console.log(
|
|
468
|
+
` 2. ${chalk.gray("Start your app in the supported environment:")}`,
|
|
469
|
+
);
|
|
711
470
|
console.log(
|
|
712
|
-
`
|
|
471
|
+
` ${chalk.cyan(finalSupportedLocalCommand || "npm run build && npm run start")}`,
|
|
713
472
|
);
|
|
473
|
+
console.log(` 3. ${chalk.gray("Generate your first local capture:")}`);
|
|
474
|
+
console.log(` ${chalk.cyan("reshot run")}`);
|
|
714
475
|
|
|
715
|
-
if (
|
|
476
|
+
if (useCloud) {
|
|
716
477
|
console.log(
|
|
717
|
-
|
|
478
|
+
`\n 4. ${chalk.gray("Upgrade to hosted assets when you are ready:")}`,
|
|
718
479
|
);
|
|
719
|
-
console.log(` ${chalk.cyan("
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
if (enableDocSync) {
|
|
480
|
+
console.log(` ${chalk.cyan("reshot publish")}`);
|
|
481
|
+
} else {
|
|
723
482
|
console.log(
|
|
724
|
-
|
|
483
|
+
`\n 4. ${chalk.gray("Connect hosted delivery later when you are ready:")}`,
|
|
725
484
|
);
|
|
485
|
+
console.log(` ${chalk.cyan("reshot auth")}`);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
if (setupMode === "certified-target" || setupMode === "candidate-target") {
|
|
726
489
|
console.log(
|
|
727
|
-
|
|
490
|
+
`\n 5. ${chalk.gray("Use advanced target checks when you need them:")}`,
|
|
728
491
|
);
|
|
492
|
+
console.log(` ${chalk.cyan("reshot doctor target")}`);
|
|
493
|
+
console.log(` ${chalk.cyan("reshot verify publish")}`);
|
|
494
|
+
if (setupMode === "certified-target") {
|
|
495
|
+
console.log(` ${chalk.cyan("reshot certify")}`);
|
|
496
|
+
}
|
|
729
497
|
}
|
|
730
498
|
|
|
731
|
-
console.log(`\n ${chalk.gray("
|
|
732
|
-
console.log(` ${chalk.cyan("reshot
|
|
499
|
+
console.log(`\n ${chalk.gray("Open Studio to inspect output locally:")}`);
|
|
500
|
+
console.log(` ${chalk.cyan("reshot studio")}`);
|
|
501
|
+
console.log(
|
|
502
|
+
`\n ${chalk.gray("Supported environments guide:")} ${chalk.cyan("https://reshot.dev/docs/cli/getting-started/supported-environments")}`,
|
|
503
|
+
);
|
|
504
|
+
console.log(
|
|
505
|
+
`\n ${chalk.gray("Setup report written to:")} ${chalk.cyan(path.relative(process.cwd(), reportPath))}\n`,
|
|
506
|
+
);
|
|
733
507
|
|
|
734
|
-
|
|
735
|
-
|
|
508
|
+
if (noStudio) {
|
|
509
|
+
console.log(
|
|
510
|
+
chalk.gray("Studio launch skipped. Run `reshot studio` when you want the local UI.\n"),
|
|
511
|
+
);
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
736
514
|
|
|
737
515
|
// Offer to launch studio
|
|
738
516
|
const { launchStudio } = await inquirer.prompt([
|
|
@@ -740,7 +518,7 @@ async function setupWizard(options = {}) {
|
|
|
740
518
|
type: "confirm",
|
|
741
519
|
name: "launchStudio",
|
|
742
520
|
message: "Launch Reshot Studio now?",
|
|
743
|
-
default:
|
|
521
|
+
default: false,
|
|
744
522
|
},
|
|
745
523
|
]);
|
|
746
524
|
|