@wasao/kagemusha 0.1.1 ā 0.3.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 +168 -79
- package/dist/commands/add.d.ts +6 -0
- package/dist/commands/add.d.ts.map +1 -0
- package/dist/commands/add.js +26 -0
- package/dist/commands/add.js.map +1 -0
- package/dist/commands/capture.d.ts +3 -2
- package/dist/commands/capture.d.ts.map +1 -1
- package/dist/commands/capture.js +256 -20
- package/dist/commands/capture.js.map +1 -1
- package/dist/commands/discover.d.ts +2 -0
- package/dist/commands/discover.d.ts.map +1 -0
- package/dist/commands/discover.js +62 -0
- package/dist/commands/discover.js.map +1 -0
- package/dist/commands/edit.d.ts +1 -1
- package/dist/commands/edit.d.ts.map +1 -1
- package/dist/commands/edit.js +82 -26
- package/dist/commands/edit.js.map +1 -1
- package/dist/commands/init.d.ts +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +240 -105
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/list.d.ts +2 -0
- package/dist/commands/list.d.ts.map +1 -0
- package/dist/commands/list.js +33 -0
- package/dist/commands/list.js.map +1 -0
- package/dist/commands/login.d.ts +6 -0
- package/dist/commands/login.d.ts.map +1 -0
- package/dist/commands/login.js +131 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/validate.js +1 -1
- package/dist/commands/validate.js.map +1 -1
- package/dist/editor/inject-script/annotations.d.ts +11 -0
- package/dist/editor/inject-script/annotations.d.ts.map +1 -0
- package/dist/editor/inject-script/annotations.js +409 -0
- package/dist/editor/inject-script/annotations.js.map +1 -0
- package/dist/editor/inject-script/bridge.d.ts +13 -0
- package/dist/editor/inject-script/bridge.d.ts.map +1 -0
- package/dist/editor/inject-script/bridge.js +33 -0
- package/dist/editor/inject-script/bridge.js.map +1 -0
- package/dist/editor/inject-script/crop.d.ts +9 -0
- package/dist/editor/inject-script/crop.d.ts.map +1 -0
- package/dist/editor/inject-script/crop.js +236 -0
- package/dist/editor/inject-script/crop.js.map +1 -0
- package/dist/editor/inject-script/dom.d.ts +7 -0
- package/dist/editor/inject-script/dom.d.ts.map +1 -0
- package/dist/editor/inject-script/dom.js +32 -0
- package/dist/editor/inject-script/dom.js.map +1 -0
- package/dist/editor/inject-script/index.d.ts +2 -0
- package/dist/editor/inject-script/index.d.ts.map +1 -0
- package/dist/editor/inject-script/index.js +56 -0
- package/dist/editor/inject-script/index.js.map +1 -0
- package/dist/editor/inject-script/record.d.ts +5 -0
- package/dist/editor/inject-script/record.d.ts.map +1 -0
- package/dist/editor/inject-script/record.js +398 -0
- package/dist/editor/inject-script/record.js.map +1 -0
- package/dist/editor/inject-script/selector.d.ts +6 -0
- package/dist/editor/inject-script/selector.d.ts.map +1 -0
- package/dist/editor/inject-script/selector.js +112 -0
- package/dist/editor/inject-script/selector.js.map +1 -0
- package/dist/editor/inject-script/state.d.ts +27 -0
- package/dist/editor/inject-script/state.d.ts.map +1 -0
- package/dist/editor/inject-script/state.js +26 -0
- package/dist/editor/inject-script/state.js.map +1 -0
- package/dist/editor/inject-script/svg.d.ts +7 -0
- package/dist/editor/inject-script/svg.d.ts.map +1 -0
- package/dist/editor/inject-script/svg.js +39 -0
- package/dist/editor/inject-script/svg.js.map +1 -0
- package/dist/editor/inject-script/toolbar.d.ts +14 -0
- package/dist/editor/inject-script/toolbar.d.ts.map +1 -0
- package/dist/editor/inject-script/toolbar.js +240 -0
- package/dist/editor/inject-script/toolbar.js.map +1 -0
- package/dist/editor/inject-script/types.d.ts +102 -0
- package/dist/editor/inject-script/types.d.ts.map +1 -0
- package/dist/editor/inject-script/types.js +5 -0
- package/dist/editor/inject-script/types.js.map +1 -0
- package/dist/editor/inject-script.js +1276 -353
- package/dist/index.js +34 -16
- package/dist/index.js.map +1 -1
- package/dist/lib/annotate.d.ts +2 -2
- package/dist/lib/annotate.d.ts.map +1 -1
- package/dist/lib/annotate.js +35 -43
- package/dist/lib/annotate.js.map +1 -1
- package/dist/lib/auth.d.ts +18 -0
- package/dist/lib/auth.d.ts.map +1 -0
- package/dist/lib/auth.js +45 -0
- package/dist/lib/auth.js.map +1 -0
- package/dist/lib/aws-error.d.ts +7 -0
- package/dist/lib/aws-error.d.ts.map +1 -0
- package/dist/lib/aws-error.js +74 -0
- package/dist/lib/aws-error.js.map +1 -0
- package/dist/lib/canonical.d.ts +54 -0
- package/dist/lib/canonical.d.ts.map +1 -0
- package/dist/lib/canonical.js +152 -0
- package/dist/lib/canonical.js.map +1 -0
- package/dist/lib/config.d.ts +2 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +23 -13
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/crawl.d.ts +1 -1
- package/dist/lib/crawl.d.ts.map +1 -1
- package/dist/lib/crawl.js +213 -20
- package/dist/lib/crawl.js.map +1 -1
- package/dist/lib/definition.d.ts +2 -0
- package/dist/lib/definition.d.ts.map +1 -0
- package/dist/lib/definition.js +6 -0
- package/dist/lib/definition.js.map +1 -0
- package/dist/lib/diff.d.ts +71 -0
- package/dist/lib/diff.d.ts.map +1 -0
- package/dist/lib/diff.js +40 -0
- package/dist/lib/diff.js.map +1 -0
- package/dist/lib/login-error.d.ts +10 -0
- package/dist/lib/login-error.d.ts.map +1 -0
- package/dist/lib/login-error.js +13 -0
- package/dist/lib/login-error.js.map +1 -0
- package/dist/lib/page-ready.d.ts +18 -0
- package/dist/lib/page-ready.d.ts.map +1 -0
- package/dist/lib/page-ready.js +19 -0
- package/dist/lib/page-ready.js.map +1 -0
- package/dist/lib/screenshot.d.ts +11 -2
- package/dist/lib/screenshot.d.ts.map +1 -1
- package/dist/lib/screenshot.js +73 -64
- package/dist/lib/screenshot.js.map +1 -1
- package/dist/lib/staging.d.ts +7 -0
- package/dist/lib/staging.d.ts.map +1 -0
- package/dist/lib/staging.js +21 -0
- package/dist/lib/staging.js.map +1 -0
- package/dist/types.d.ts +10 -23
- package/dist/types.d.ts.map +1 -1
- package/package.json +20 -3
- package/dist/commands/preview.d.ts +0 -6
- package/dist/commands/preview.d.ts.map +0 -1
- package/dist/commands/preview.js +0 -33
- package/dist/commands/preview.js.map +0 -1
- package/dist/commands/run.d.ts +0 -6
- package/dist/commands/run.d.ts.map +0 -1
- package/dist/commands/run.js +0 -39
- package/dist/commands/run.js.map +0 -1
- package/dist/editor/editor/editor.html +0 -313
- package/dist/editor/editor/inject.ts +0 -385
- package/dist/editor/editor.html +0 -338
- package/dist/editor/inject-script.cjs +0 -398
- package/dist/editor/inject-script.cjs.map +0 -1
- package/dist/editor/inject-script.d.cts +0 -2
- package/dist/editor/inject-script.d.cts.map +0 -1
- package/dist/editor/inject-script.d.ts +0 -2
- package/dist/editor/inject-script.d.ts.map +0 -1
- package/dist/editor/inject-script.js.map +0 -1
- package/dist/editor/inject.d.ts +0 -2
- package/dist/editor/inject.d.ts.map +0 -1
- package/dist/editor/inject.js +0 -385
- package/dist/editor/inject.js.map +0 -1
- package/dist/lib/upload.d.ts +0 -9
- package/dist/lib/upload.d.ts.map +0 -1
- package/dist/lib/upload.js +0 -43
- package/dist/lib/upload.js.map +0 -1
package/dist/commands/init.js
CHANGED
|
@@ -3,8 +3,11 @@ import path from "node:path";
|
|
|
3
3
|
import chalk from "chalk";
|
|
4
4
|
import inquirer from "inquirer";
|
|
5
5
|
import { stringify as toYaml } from "yaml";
|
|
6
|
+
import { hasAuthState } from "../lib/auth.js";
|
|
7
|
+
import { loadDefinitions, saveDefinitions } from "../lib/config.js";
|
|
6
8
|
import { discoverPages } from "../lib/crawl.js";
|
|
7
|
-
|
|
9
|
+
import { deriveIdFromPath } from "../lib/definition.js";
|
|
10
|
+
export const initCommand = async () => {
|
|
8
11
|
console.log(chalk.bold("\nš„· Kagemusha ā Setup\n"));
|
|
9
12
|
const cwd = process.cwd();
|
|
10
13
|
if (fs.existsSync(path.join(cwd, "kagemusha.config.yaml"))) {
|
|
@@ -26,48 +29,6 @@ export async function initCommand() {
|
|
|
26
29
|
message: "Target URL (the app to take screenshots of):",
|
|
27
30
|
default: "http://localhost:3000",
|
|
28
31
|
});
|
|
29
|
-
const { needsAuth } = await inquirer.prompt({
|
|
30
|
-
type: "confirm",
|
|
31
|
-
name: "needsAuth",
|
|
32
|
-
message: "Does the app require login?",
|
|
33
|
-
default: true,
|
|
34
|
-
});
|
|
35
|
-
let loginUrl = "/login";
|
|
36
|
-
let emailSelector = "#email";
|
|
37
|
-
let passwordSelector = "#password";
|
|
38
|
-
let submitSelector = "button[type='submit']";
|
|
39
|
-
if (needsAuth) {
|
|
40
|
-
const authAnswers = await inquirer.prompt([
|
|
41
|
-
{
|
|
42
|
-
type: "input",
|
|
43
|
-
name: "loginUrl",
|
|
44
|
-
message: "Login page path:",
|
|
45
|
-
default: "/login",
|
|
46
|
-
},
|
|
47
|
-
{
|
|
48
|
-
type: "input",
|
|
49
|
-
name: "emailSelector",
|
|
50
|
-
message: "Email input selector:",
|
|
51
|
-
default: "#email",
|
|
52
|
-
},
|
|
53
|
-
{
|
|
54
|
-
type: "input",
|
|
55
|
-
name: "passwordSelector",
|
|
56
|
-
message: "Password input selector:",
|
|
57
|
-
default: "#password",
|
|
58
|
-
},
|
|
59
|
-
{
|
|
60
|
-
type: "input",
|
|
61
|
-
name: "submitSelector",
|
|
62
|
-
message: "Login button selector:",
|
|
63
|
-
default: "button[type='submit']",
|
|
64
|
-
},
|
|
65
|
-
]);
|
|
66
|
-
loginUrl = authAnswers.loginUrl;
|
|
67
|
-
emailSelector = authAnswers.emailSelector;
|
|
68
|
-
passwordSelector = authAnswers.passwordSelector;
|
|
69
|
-
submitSelector = authAnswers.submitSelector;
|
|
70
|
-
}
|
|
71
32
|
const { destination } = await inquirer.prompt({
|
|
72
33
|
type: "list",
|
|
73
34
|
name: "destination",
|
|
@@ -110,50 +71,54 @@ export async function initCommand() {
|
|
|
110
71
|
app: { baseUrl },
|
|
111
72
|
screenshot: {
|
|
112
73
|
defaultViewport: { width: 1280, height: 720, deviceScaleFactor: 2 },
|
|
113
|
-
defaultDiffThreshold: 0.
|
|
74
|
+
defaultDiffThreshold: 0.005,
|
|
114
75
|
},
|
|
76
|
+
publish: destination === "local"
|
|
77
|
+
? { destination: "local", outputDir }
|
|
78
|
+
: { destination: "s3", cdnBucket, cdnBaseUrl },
|
|
115
79
|
};
|
|
116
|
-
if (destination === "local") {
|
|
117
|
-
config.publish = {
|
|
118
|
-
destination: "local",
|
|
119
|
-
outputDir,
|
|
120
|
-
};
|
|
121
|
-
}
|
|
122
|
-
else {
|
|
123
|
-
config.publish = {
|
|
124
|
-
destination: "s3",
|
|
125
|
-
cdnBucket,
|
|
126
|
-
cdnBaseUrl,
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
if (needsAuth) {
|
|
130
|
-
config.auth = {
|
|
131
|
-
loginUrl,
|
|
132
|
-
steps: [
|
|
133
|
-
{
|
|
134
|
-
action: "type",
|
|
135
|
-
selector: emailSelector,
|
|
136
|
-
text: "${KAGEMUSHA_DEMO_EMAIL}",
|
|
137
|
-
},
|
|
138
|
-
{
|
|
139
|
-
action: "type",
|
|
140
|
-
selector: passwordSelector,
|
|
141
|
-
text: "${KAGEMUSHA_DEMO_PASSWORD}",
|
|
142
|
-
},
|
|
143
|
-
{ action: "click", selector: submitSelector },
|
|
144
|
-
{ action: "waitForNavigation" },
|
|
145
|
-
],
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
80
|
// Write config
|
|
149
81
|
fs.writeFileSync(path.join(cwd, "kagemusha.config.yaml"), toYaml(config, { lineWidth: 120 }));
|
|
150
82
|
console.log(chalk.green("\nā Created kagemusha.config.yaml"));
|
|
83
|
+
// Update .gitignore so canonical/staging artifacts don't pollute the repo.
|
|
84
|
+
// Canonical lives in S3 (or a local outputDir for testing) ā never in git.
|
|
85
|
+
updateGitignore(cwd, outputDir);
|
|
151
86
|
// Step 2: Discover pages and create screenshot definitions
|
|
152
|
-
|
|
153
|
-
|
|
87
|
+
// Check if login is needed
|
|
88
|
+
if (!hasAuthState(cwd)) {
|
|
89
|
+
const { needsLogin } = await inquirer.prompt({
|
|
90
|
+
type: "confirm",
|
|
91
|
+
name: "needsLogin",
|
|
92
|
+
message: "Does this app require login?",
|
|
93
|
+
default: true,
|
|
94
|
+
});
|
|
95
|
+
if (needsLogin) {
|
|
96
|
+
const { ciAuto } = await inquirer.prompt({
|
|
97
|
+
type: "confirm",
|
|
98
|
+
name: "ciAuto",
|
|
99
|
+
message: "Generate a login.js skeleton for headless / CI auto-login? (recommended)",
|
|
100
|
+
default: true,
|
|
101
|
+
});
|
|
102
|
+
if (ciAuto) {
|
|
103
|
+
const skeletonPath = path.join(cwd, ".kagemusha", "login.mjs");
|
|
104
|
+
if (!fs.existsSync(skeletonPath)) {
|
|
105
|
+
fs.mkdirSync(path.dirname(skeletonPath), { recursive: true });
|
|
106
|
+
fs.writeFileSync(skeletonPath, generateLoginSkeleton());
|
|
107
|
+
console.log(chalk.green("ā Created .kagemusha/login.mjs (edit before use)"));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
console.log(chalk.blue("\nš Opening browser for login...\n"));
|
|
111
|
+
const { loginCommand } = await import("./login.js");
|
|
112
|
+
await loginCommand();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
console.log(chalk.green(" ā Using saved login session\n"));
|
|
117
|
+
}
|
|
118
|
+
console.log(chalk.blue(`š Scanning ${baseUrl} for pages...\n`));
|
|
154
119
|
let pages = [];
|
|
155
120
|
try {
|
|
156
|
-
pages = await discoverPages(baseUrl);
|
|
121
|
+
pages = await discoverPages(baseUrl, cwd);
|
|
157
122
|
}
|
|
158
123
|
catch {
|
|
159
124
|
console.log(chalk.yellow(" Could not auto-discover pages.\n"));
|
|
@@ -192,9 +157,32 @@ export async function initCommand() {
|
|
|
192
157
|
addMore = more;
|
|
193
158
|
}
|
|
194
159
|
}
|
|
160
|
+
// Preserve existing definitions; merge new ones (skip ID duplicates)
|
|
161
|
+
const existing = loadDefinitions(cwd);
|
|
162
|
+
let merged = existing;
|
|
163
|
+
let resetExisting = false;
|
|
164
|
+
if (existing.length > 0) {
|
|
165
|
+
const { keepExisting } = await inquirer.prompt({
|
|
166
|
+
type: "confirm",
|
|
167
|
+
name: "keepExisting",
|
|
168
|
+
message: `${existing.length} existing definition(s) found. Keep them and merge new selections?`,
|
|
169
|
+
default: true,
|
|
170
|
+
});
|
|
171
|
+
if (!keepExisting) {
|
|
172
|
+
console.log(chalk.yellow(` ā ${existing.length} existing definition(s) will be replaced.`));
|
|
173
|
+
merged = [];
|
|
174
|
+
resetExisting = true;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
const existingIds = new Set(merged.map((d) => d.id));
|
|
178
|
+
let added = 0;
|
|
195
179
|
for (const pagePath of selectedPaths) {
|
|
196
180
|
const id = deriveIdFromPath(pagePath);
|
|
197
|
-
|
|
181
|
+
if (existingIds.has(id)) {
|
|
182
|
+
console.log(chalk.gray(` ā· ${id} (already exists, skipped)`));
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
const def = {
|
|
198
186
|
id,
|
|
199
187
|
name: id,
|
|
200
188
|
url: pagePath,
|
|
@@ -202,9 +190,13 @@ export async function initCommand() {
|
|
|
202
190
|
hideElements: [],
|
|
203
191
|
decorations: [],
|
|
204
192
|
};
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
193
|
+
merged.push(def);
|
|
194
|
+
existingIds.add(id);
|
|
195
|
+
added++;
|
|
196
|
+
console.log(chalk.green(` ā ${id}`));
|
|
197
|
+
}
|
|
198
|
+
if (added > 0 || resetExisting) {
|
|
199
|
+
saveDefinitions(merged, cwd);
|
|
208
200
|
}
|
|
209
201
|
console.log("");
|
|
210
202
|
// Step 3: GitHub Actions workflow
|
|
@@ -219,49 +211,192 @@ export async function initCommand() {
|
|
|
219
211
|
fs.mkdirSync(workflowDir, { recursive: true });
|
|
220
212
|
fs.writeFileSync(path.join(workflowDir, "kagemusha.yml"), generateWorkflowTemplate());
|
|
221
213
|
console.log(chalk.green("ā Created .github/workflows/kagemusha.yml"));
|
|
214
|
+
// Notification formatter is user-editable (= belongs to the project,
|
|
215
|
+
// not to kagemusha). Place a Slack-shaped skeleton next to login.mjs.
|
|
216
|
+
const notifyJqPath = path.join(cwd, ".kagemusha", "notify-slack.jq");
|
|
217
|
+
if (!fs.existsSync(notifyJqPath)) {
|
|
218
|
+
fs.mkdirSync(path.dirname(notifyJqPath), { recursive: true });
|
|
219
|
+
fs.writeFileSync(notifyJqPath, generateNotifySlackJq());
|
|
220
|
+
console.log(chalk.green("ā Created .kagemusha/notify-slack.jq (edit to customize format)"));
|
|
221
|
+
}
|
|
222
222
|
}
|
|
223
223
|
console.log(chalk.bold.green("\nā
Setup complete!\n"));
|
|
224
224
|
console.log(chalk.gray("Next steps:"));
|
|
225
|
-
console.log(chalk.gray(" npx kagemusha
|
|
226
|
-
console.log(chalk.gray(" npx kagemusha
|
|
227
|
-
console.log(chalk.gray(" npx kagemusha
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
|
|
225
|
+
console.log(chalk.gray(" npx kagemusha capture ā Capture & publish changed"));
|
|
226
|
+
console.log(chalk.gray(" npx kagemusha capture --dry-run ā Preview diffs only"));
|
|
227
|
+
console.log(chalk.gray(" npx kagemusha edit ā Edit annotations\n"));
|
|
228
|
+
};
|
|
229
|
+
// login.mjs 㯠team ć§å
±ęćććć¹ć (selector / ććć¼å®ē¾©ćÆ team property)
|
|
230
|
+
// ćŖć®ć§ęå³ēć« gitignore ććŖćć
|
|
231
|
+
const GITIGNORE_ENTRIES = [
|
|
232
|
+
".kagemusha/.staging/",
|
|
233
|
+
".kagemusha/.cache/",
|
|
234
|
+
".kagemusha/auth-state.json",
|
|
235
|
+
".kagemusha/auth-meta.json",
|
|
236
|
+
"reports/",
|
|
237
|
+
];
|
|
238
|
+
// Strip trailing slash so `screenshots` and `screenshots/` are treated as the
|
|
239
|
+
// same gitignore entry (and we don't append duplicates).
|
|
240
|
+
const stripTrailingSlash = (s) => s.replace(/\/+$/, "");
|
|
241
|
+
const updateGitignore = (cwd, outputDir) => {
|
|
242
|
+
const gitignorePath = path.join(cwd, ".gitignore");
|
|
243
|
+
const existing = fs.existsSync(gitignorePath)
|
|
244
|
+
? fs.readFileSync(gitignorePath, "utf8")
|
|
245
|
+
: "";
|
|
246
|
+
const present = new Set(existing
|
|
247
|
+
.split("\n")
|
|
248
|
+
.map((l) => l.trim())
|
|
249
|
+
.filter((l) => l.length > 0)
|
|
250
|
+
.map(stripTrailingSlash));
|
|
251
|
+
const normalizedOutputDir = outputDir
|
|
252
|
+
.replace(/^\.\//, "")
|
|
253
|
+
.replace(/\/+$/, "");
|
|
254
|
+
const entries = [...GITIGNORE_ENTRIES, `${normalizedOutputDir}/`];
|
|
255
|
+
const additions = entries.filter((e) => !present.has(stripTrailingSlash(e)));
|
|
256
|
+
if (additions.length === 0)
|
|
257
|
+
return;
|
|
258
|
+
const block = ["", "# kagemusha", ...additions, ""];
|
|
259
|
+
const next = existing.endsWith("\n") || existing === ""
|
|
260
|
+
? existing + block.join("\n")
|
|
261
|
+
: `${existing}\n${block.join("\n")}`;
|
|
262
|
+
fs.writeFileSync(gitignorePath, next);
|
|
263
|
+
console.log(chalk.green(`ā Updated .gitignore (added ${additions.length} kagemusha entries)`));
|
|
264
|
+
};
|
|
265
|
+
const generateLoginSkeleton = () => `// Custom login flow ā runs headless, called by \`kagemusha login\`.
|
|
266
|
+
// Saved storage state is reused by \`kagemusha capture\` for all definitions.
|
|
267
|
+
//
|
|
268
|
+
// Examples:
|
|
269
|
+
// - Form login: fill email/password, submit, wait for redirect
|
|
270
|
+
// - HTTP basic: set extraHTTPHeaders or httpCredentials in kagemusha.config.yaml
|
|
271
|
+
// - Token-based: inject Authorization header via extraHTTPHeaders
|
|
272
|
+
// - SSO / OAuth: fall back to interactive \`kagemusha login\` (delete this file)
|
|
273
|
+
//
|
|
274
|
+
// The page already has \`baseURL\` set from kagemusha.config.yaml, so relative
|
|
275
|
+
// paths work in \`page.goto('/login')\`.
|
|
276
|
+
//
|
|
277
|
+
// ā ļø Env var naming:
|
|
278
|
+
// The example below uses MY_APP_EMAIL / MY_APP_PASSWORD as placeholders.
|
|
279
|
+
// Rename them to whatever fits your project (e.g. STAGING_EMAIL, WEVOX_TEST_EMAIL).
|
|
280
|
+
// Avoid:
|
|
281
|
+
// - Generic names like EMAIL / PASSWORD (collide with shell rc / other tools)
|
|
282
|
+
// - KAGEMUSHA_* prefix (reserved for kagemusha's own future config / GUI auth)
|
|
283
|
+
|
|
284
|
+
/** @param {import('playwright-chromium').Page} page */
|
|
285
|
+
export const login = async (page) => {
|
|
286
|
+
await page.goto("/login");
|
|
287
|
+
|
|
288
|
+
await page.fill('input[name="email"]', process.env.MY_APP_EMAIL ?? "");
|
|
289
|
+
await page.fill('input[name="password"]', process.env.MY_APP_PASSWORD ?? "");
|
|
290
|
+
await page.click('button[type="submit"]');
|
|
291
|
+
|
|
292
|
+
// Wait until we leave the login URL (= login succeeded).
|
|
293
|
+
await page.waitForURL((url) => !url.pathname.startsWith("/login"));
|
|
294
|
+
};
|
|
295
|
+
`;
|
|
296
|
+
const generateWorkflowTemplate = () => `name: Kagemusha - Screenshot Update
|
|
231
297
|
|
|
298
|
+
# Triggered when main is updated (= push or PR merge land on main).
|
|
232
299
|
on:
|
|
233
|
-
|
|
234
|
-
types: [closed]
|
|
300
|
+
push:
|
|
235
301
|
branches: [main]
|
|
236
302
|
workflow_dispatch:
|
|
237
303
|
|
|
304
|
+
# Cancel in-progress runs when a newer merge arrives ā kagemusha always
|
|
305
|
+
# captures the full set, so the latest run subsumes any earlier one.
|
|
306
|
+
concurrency:
|
|
307
|
+
group: kagemusha
|
|
308
|
+
cancel-in-progress: true
|
|
309
|
+
|
|
310
|
+
# OIDC is recommended over long-lived access keys.
|
|
311
|
+
# To use OIDC instead: replace the AWS env vars below with
|
|
312
|
+
# \`uses: aws-actions/configure-aws-credentials@v4\` + \`role-to-assume\` and
|
|
313
|
+
# add \`permissions: { id-token: write, contents: read }\`.
|
|
314
|
+
|
|
238
315
|
jobs:
|
|
239
316
|
update-screenshots:
|
|
240
|
-
if: github.event.pull_request.merged == true || github.event_name == 'workflow_dispatch'
|
|
241
317
|
runs-on: ubuntu-latest
|
|
242
318
|
steps:
|
|
243
319
|
- uses: actions/checkout@v4
|
|
244
|
-
with:
|
|
245
|
-
fetch-depth: 0
|
|
246
320
|
- uses: actions/setup-node@v4
|
|
247
321
|
with:
|
|
248
322
|
node-version: 20
|
|
249
323
|
- run: npm ci
|
|
250
324
|
- run: npx playwright install chromium
|
|
251
325
|
|
|
252
|
-
|
|
326
|
+
# If your app needs login, kagemusha auto-runs .kagemusha/login.mjs
|
|
327
|
+
# (generated by \`kagemusha init\`) to create a fresh session.
|
|
328
|
+
# Set login credentials as secrets and pass them as env below.
|
|
329
|
+
#
|
|
330
|
+
# For SSO / MFA apps where scripted login isn't possible, use:
|
|
331
|
+
# - name: Restore login session
|
|
332
|
+
# if: env.KAGEMUSHA_STORAGE_STATE != ''
|
|
333
|
+
# run: mkdir -p .kagemusha && echo "$KAGEMUSHA_STORAGE_STATE" | base64 --decode > .kagemusha/auth-state.json
|
|
334
|
+
# env:
|
|
335
|
+
# KAGEMUSHA_STORAGE_STATE: \${{ secrets.KAGEMUSHA_STORAGE_STATE }}
|
|
336
|
+
|
|
337
|
+
# Pulls canonical from S3, diffs against fresh capture,
|
|
338
|
+
# pushes only what changed back to S3. No screenshots/ commit needed.
|
|
339
|
+
# Region is auto-detected from publish.cdnBaseUrl in kagemusha.config.yaml.
|
|
340
|
+
- run: npx kagemusha capture
|
|
253
341
|
env:
|
|
254
|
-
KAGEMUSHA_DEMO_EMAIL: \${{ secrets.KAGEMUSHA_DEMO_EMAIL }}
|
|
255
|
-
KAGEMUSHA_DEMO_PASSWORD: \${{ secrets.KAGEMUSHA_DEMO_PASSWORD }}
|
|
256
342
|
AWS_ACCESS_KEY_ID: \${{ secrets.AWS_ACCESS_KEY_ID }}
|
|
257
343
|
AWS_SECRET_ACCESS_KEY: \${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
|
344
|
+
# Rename these to match what your .kagemusha/login.mjs reads.
|
|
345
|
+
# Avoid generic names (EMAIL) or KAGEMUSHA_* (reserved).
|
|
346
|
+
MY_APP_EMAIL: \${{ secrets.MY_APP_EMAIL }}
|
|
347
|
+
MY_APP_PASSWORD: \${{ secrets.MY_APP_PASSWORD }}
|
|
348
|
+
|
|
349
|
+
# Keep summary.json as artifact for later review
|
|
350
|
+
- uses: actions/upload-artifact@v4
|
|
351
|
+
with:
|
|
352
|
+
name: kagemusha-reports
|
|
353
|
+
path: reports/
|
|
354
|
+
if-no-files-found: ignore
|
|
355
|
+
|
|
356
|
+
# Slack notification: one message per changed/new screenshot.
|
|
357
|
+
# Slack unfurls image URLs per-message, so the preview comes out
|
|
358
|
+
# clean even with many updates. Format defined in
|
|
359
|
+
# .kagemusha/notify-slack.jq ā each line of jq output becomes one
|
|
360
|
+
# POST body. Test locally:
|
|
361
|
+
# jq -c -f .kagemusha/notify-slack.jq reports/summary.json
|
|
362
|
+
- name: Slack notify
|
|
363
|
+
env:
|
|
364
|
+
SLACK_WEBHOOK_URL: \${{ secrets.SLACK_WEBHOOK_URL }}
|
|
365
|
+
run: |
|
|
366
|
+
[ -n "$SLACK_WEBHOOK_URL" ] || exit 0
|
|
367
|
+
jq -c -f .kagemusha/notify-slack.jq reports/summary.json | while IFS= read -r payload; do
|
|
368
|
+
[ -z "$payload" ] && continue
|
|
369
|
+
curl -sS -X POST "$SLACK_WEBHOOK_URL" \\
|
|
370
|
+
-H 'Content-Type: application/json' \\
|
|
371
|
+
--data "$payload"
|
|
372
|
+
done
|
|
373
|
+
`;
|
|
374
|
+
const generateNotifySlackJq = () => `# Slack notification formatter for kagemusha.
|
|
375
|
+
# Called by .github/workflows/kagemusha.yml on reports/summary.json.
|
|
376
|
+
# Emits ONE Slack payload object per changed/new screenshot ā the
|
|
377
|
+
# workflow loops over the lines and POSTs each as a separate message.
|
|
378
|
+
# Slack unfurls image URLs per-message, so before/after previews render
|
|
379
|
+
# cleanly even when many pages changed.
|
|
380
|
+
#
|
|
381
|
+
# Each emitted object is a full Slack chat.postMessage body, so you can
|
|
382
|
+
# customize freely (add blocks, attachments, channel override, etc).
|
|
383
|
+
#
|
|
384
|
+
# Test locally:
|
|
385
|
+
# jq -c -f .kagemusha/notify-slack.jq reports/summary.json
|
|
386
|
+
|
|
387
|
+
.results[]
|
|
388
|
+
| select(.status == "changed" or .status == "new")
|
|
389
|
+
| {
|
|
390
|
+
text: (
|
|
391
|
+
if .status == "changed" then
|
|
392
|
+
"šø *\\(.id)* changed (\\((.diffPercentage * 100 | floor) / 100)%)" +
|
|
393
|
+
(if .urls.before then "\\nBefore: \\(.urls.before)" else "" end) +
|
|
394
|
+
(if .urls.after then "\\nAfter: \\(.urls.after)" else "" end)
|
|
395
|
+
else
|
|
396
|
+
"šø *\\(.id)* added" +
|
|
397
|
+
(if .urls.after then "\\n\\(.urls.after)" else "" end)
|
|
398
|
+
end
|
|
399
|
+
)
|
|
400
|
+
}
|
|
258
401
|
`;
|
|
259
|
-
}
|
|
260
|
-
function deriveIdFromPath(urlPath) {
|
|
261
|
-
return (urlPath
|
|
262
|
-
.replace(/^\//, "")
|
|
263
|
-
.replace(/\.\w+$/, "")
|
|
264
|
-
.replace(/[/\\]/g, "-")
|
|
265
|
-
.replace(/[^a-zA-Z0-9-]/g, "") || "page");
|
|
266
|
-
}
|
|
267
402
|
//# sourceMappingURL=init.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,QAAQ,MAAM,UAAU,CAAC;AAChC,OAAO,EAAE,SAAS,IAAI,MAAM,EAAE,MAAM,MAAM,CAAC;AAC3C,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list.d.ts","sourceRoot":"","sources":["../../src/commands/list.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,WAAW,QAAa,OAAO,CAAC,IAAI,CAsChD,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { findProjectRoot, loadDefinitions } from "../lib/config.js";
|
|
3
|
+
export const listCommand = async () => {
|
|
4
|
+
const projectRoot = findProjectRoot();
|
|
5
|
+
const definitions = loadDefinitions(projectRoot);
|
|
6
|
+
if (definitions.length === 0) {
|
|
7
|
+
console.log(chalk.yellow("\nNo definitions found.\n"));
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
// Group by URL
|
|
11
|
+
const byUrl = new Map();
|
|
12
|
+
for (const def of definitions) {
|
|
13
|
+
const list = byUrl.get(def.url) ?? [];
|
|
14
|
+
list.push(def);
|
|
15
|
+
byUrl.set(def.url, list);
|
|
16
|
+
}
|
|
17
|
+
console.log(chalk.bold(`\nš ${definitions.length} definition(s) across ${byUrl.size} URL(s)\n`));
|
|
18
|
+
for (const [url, defs] of byUrl) {
|
|
19
|
+
console.log(chalk.blue(` ${url}`));
|
|
20
|
+
for (const def of defs) {
|
|
21
|
+
const decorations = def.decorations?.length ?? 0;
|
|
22
|
+
const meta = [
|
|
23
|
+
def.capture.mode,
|
|
24
|
+
decorations > 0 ? `${decorations} annotations` : null,
|
|
25
|
+
]
|
|
26
|
+
.filter(Boolean)
|
|
27
|
+
.join(", ");
|
|
28
|
+
console.log(` ${chalk.white(def.id)} ${chalk.dim(meta)}`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
console.log("");
|
|
32
|
+
};
|
|
33
|
+
//# sourceMappingURL=list.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list.js","sourceRoot":"","sources":["../../src/commands/list.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAGpE,MAAM,CAAC,MAAM,WAAW,GAAG,KAAK,IAAmB,EAAE;IACpD,MAAM,WAAW,GAAG,eAAe,EAAE,CAAC;IACtC,MAAM,WAAW,GAAG,eAAe,CAAC,WAAW,CAAC,CAAC;IAEjD,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC9B,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,2BAA2B,CAAC,CAAC,CAAC;QACvD,OAAO;IACR,CAAC;IAED,eAAe;IACf,MAAM,KAAK,GAAG,IAAI,GAAG,EAAkC,CAAC;IACxD,KAAK,MAAM,GAAG,IAAI,WAAW,EAAE,CAAC;QAC/B,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;QACtC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACf,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IAC1B,CAAC;IAED,OAAO,CAAC,GAAG,CACV,KAAK,CAAC,IAAI,CACT,QAAQ,WAAW,CAAC,MAAM,yBAAyB,KAAK,CAAC,IAAI,WAAW,CACxE,CACD,CAAC;IAEF,KAAK,MAAM,CAAC,GAAG,EAAE,IAAI,CAAC,IAAI,KAAK,EAAE,CAAC;QACjC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,GAAG,EAAE,CAAC,CAAC,CAAC;QACpC,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;YACxB,MAAM,WAAW,GAAG,GAAG,CAAC,WAAW,EAAE,MAAM,IAAI,CAAC,CAAC;YACjD,MAAM,IAAI,GAAG;gBACZ,GAAG,CAAC,OAAO,CAAC,IAAI;gBAChB,WAAW,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,WAAW,cAAc,CAAC,CAAC,CAAC,IAAI;aACrD;iBACC,MAAM,CAAC,OAAO,CAAC;iBACf,IAAI,CAAC,IAAI,CAAC,CAAC;YAEb,OAAO,CAAC,GAAG,CAAC,OAAO,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,KAAK,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC/D,CAAC;IACF,CAAC;IACD,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;AACjB,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"AAoBA,UAAU,YAAY;IACrB,MAAM,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,eAAO,MAAM,YAAY,GACxB,UAAS,YAAiB,KACxB,OAAO,CAAC,IAAI,CAoBd,CAAC"}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
import { defaultContextOptions, getAuthMetaPath, getAuthStatePath, resolveLoginScriptPath, } from "../lib/auth.js";
|
|
6
|
+
import { findProjectRoot, loadConfig } from "../lib/config.js";
|
|
7
|
+
import { LoginError } from "../lib/login-error.js";
|
|
8
|
+
export const loginCommand = async (options = {}) => {
|
|
9
|
+
const projectRoot = findProjectRoot();
|
|
10
|
+
const config = loadConfig(projectRoot);
|
|
11
|
+
const scriptPath = resolveLoginScriptPath(config, projectRoot);
|
|
12
|
+
try {
|
|
13
|
+
if (scriptPath) {
|
|
14
|
+
await runScriptedLogin(scriptPath, config, projectRoot, options);
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
await runInteractiveLogin(config, projectRoot);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
catch (e) {
|
|
21
|
+
if (e instanceof LoginError) {
|
|
22
|
+
// Friendly message + screenshot already printed inside runScriptedLogin.
|
|
23
|
+
// Just signal failure via exit code without dumping a stack.
|
|
24
|
+
process.exitCode = 1;
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
throw e;
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
const runScriptedLogin = async (scriptPath, config, projectRoot, options = {}) => {
|
|
31
|
+
console.log(chalk.bold("\nš„· Kagemusha ā Login (scripted)\n"));
|
|
32
|
+
console.log(chalk.gray(` using: ${path.relative(projectRoot, scriptPath)}`));
|
|
33
|
+
if (options.headed) {
|
|
34
|
+
console.log(chalk.gray(` mode: headed (debug)\n`));
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
console.log("");
|
|
38
|
+
}
|
|
39
|
+
const mod = (await import(pathToFileURL(scriptPath).href));
|
|
40
|
+
const loginFn = mod.login ?? mod.default;
|
|
41
|
+
if (typeof loginFn !== "function") {
|
|
42
|
+
throw new Error(`${scriptPath} must export a 'login(page)' function (named or default export).`);
|
|
43
|
+
}
|
|
44
|
+
const { chromium } = await import("playwright-chromium");
|
|
45
|
+
const browser = await chromium.launch({ headless: !options.headed });
|
|
46
|
+
// projectRoot=undefined ā no storageState applied (we are creating one).
|
|
47
|
+
const context = await browser.newContext(defaultContextOptions(config, undefined));
|
|
48
|
+
const page = await context.newPage();
|
|
49
|
+
try {
|
|
50
|
+
try {
|
|
51
|
+
await loginFn(page);
|
|
52
|
+
}
|
|
53
|
+
catch (e) {
|
|
54
|
+
console.error(chalk.red(`\nā Login script threw: ${formatError(e)}`));
|
|
55
|
+
console.error(chalk.gray(` Last URL: ${page.url()}`));
|
|
56
|
+
console.error(chalk.yellow(`\nHint:\n` +
|
|
57
|
+
` - Re-run with --headed to watch the flow live: \`kagemusha login --headed\`\n` +
|
|
58
|
+
` - Verify form selectors in .kagemusha/login.mjs match the live page\n` +
|
|
59
|
+
` - Check that the credentials env vars are correct`));
|
|
60
|
+
throw new LoginError(`login script threw: ${formatError(e)}`);
|
|
61
|
+
}
|
|
62
|
+
const landingUrl = page.url();
|
|
63
|
+
const landingPath = new URL(landingUrl).pathname;
|
|
64
|
+
// Verify login actually succeeded ā landing on /login* means we failed silently.
|
|
65
|
+
if (landingPath.startsWith("/login") || landingPath === "/") {
|
|
66
|
+
console.error(chalk.red(`\nā Login script completed but the page is still on ${landingPath}.`));
|
|
67
|
+
console.error(chalk.yellow(`\nHint:\n` +
|
|
68
|
+
` - Re-run with \`kagemusha login --headed\` to see the live flow\n` +
|
|
69
|
+
` - Verify selectors / credentials in .kagemusha/login.mjs`));
|
|
70
|
+
throw new LoginError(`stuck on ${landingPath} after login script`);
|
|
71
|
+
}
|
|
72
|
+
// Verify the storageState actually has cookies (= a session was established).
|
|
73
|
+
const state = await context.storageState();
|
|
74
|
+
const cookieCount = state.cookies?.length ?? 0;
|
|
75
|
+
if (cookieCount === 0) {
|
|
76
|
+
console.warn(chalk.yellow(`ā Login script finished but no cookies were set ā the saved session may be empty.`));
|
|
77
|
+
}
|
|
78
|
+
const authStatePath = getAuthStatePath(projectRoot);
|
|
79
|
+
fs.mkdirSync(path.dirname(authStatePath), { recursive: true });
|
|
80
|
+
await context.storageState({ path: authStatePath });
|
|
81
|
+
const metaPath = getAuthMetaPath(projectRoot);
|
|
82
|
+
fs.writeFileSync(metaPath, JSON.stringify({ loginPath: "/login", landingPath }, null, 2));
|
|
83
|
+
console.log(chalk.bold.green("\nā
Session saved"));
|
|
84
|
+
console.log(chalk.gray(` Landing: ${landingPath} (${cookieCount} cookies)\n`));
|
|
85
|
+
}
|
|
86
|
+
finally {
|
|
87
|
+
await browser.close();
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
const formatError = (e) => e instanceof Error ? e.message : String(e);
|
|
91
|
+
const runInteractiveLogin = async (config, projectRoot) => {
|
|
92
|
+
console.log(chalk.bold("\nš„· Kagemusha ā Login\n"));
|
|
93
|
+
const inquirer = await import("inquirer");
|
|
94
|
+
const { loginPath } = await inquirer.default.prompt({
|
|
95
|
+
type: "input",
|
|
96
|
+
name: "loginPath",
|
|
97
|
+
message: "Login page path (e.g. /login):",
|
|
98
|
+
default: "/login",
|
|
99
|
+
});
|
|
100
|
+
const loginUrl = new URL(loginPath, config.app.baseUrl).toString();
|
|
101
|
+
const { chromium } = await import("playwright-chromium");
|
|
102
|
+
const browser = await chromium.launch({ headless: false });
|
|
103
|
+
const context = await browser.newContext(defaultContextOptions(config, undefined));
|
|
104
|
+
const page = await context.newPage();
|
|
105
|
+
console.log(chalk.blue(`š Opening ${loginUrl}...`));
|
|
106
|
+
console.log(chalk.gray(" Log in manually in the browser.\n"));
|
|
107
|
+
await page.goto(loginUrl, { waitUntil: "networkidle" });
|
|
108
|
+
await inquirer.default.prompt({
|
|
109
|
+
type: "confirm",
|
|
110
|
+
name: "done",
|
|
111
|
+
message: "Done logging in?",
|
|
112
|
+
default: true,
|
|
113
|
+
});
|
|
114
|
+
const landingUrl = page.url();
|
|
115
|
+
const landingPath = new URL(landingUrl).pathname;
|
|
116
|
+
if (landingPath === loginPath) {
|
|
117
|
+
console.log(chalk.yellow(`\nā Still on ${loginPath} ā looks like login wasn't completed.`));
|
|
118
|
+
console.log(chalk.yellow(" Session not saved. Re-run 'kagemusha login' after signing in.\n"));
|
|
119
|
+
await browser.close();
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
const authStatePath = getAuthStatePath(projectRoot);
|
|
123
|
+
fs.mkdirSync(path.dirname(authStatePath), { recursive: true });
|
|
124
|
+
await context.storageState({ path: authStatePath });
|
|
125
|
+
const metaPath = getAuthMetaPath(projectRoot);
|
|
126
|
+
fs.writeFileSync(metaPath, JSON.stringify({ loginPath, landingPath }, null, 2));
|
|
127
|
+
await browser.close();
|
|
128
|
+
console.log(chalk.bold.green("\nā
Session saved"));
|
|
129
|
+
console.log(chalk.gray(` Landing page: ${landingPath}\n`));
|
|
130
|
+
};
|
|
131
|
+
//# sourceMappingURL=login.js.map
|