@daydreamui/netlify 0.0.0-stage → 0.3.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/dist/bridge.js ADDED
@@ -0,0 +1,797 @@
1
+ import { existsSync } from "node:fs";
2
+ import { randomBytes } from "node:crypto";
3
+ import { lstat, mkdir, mkdtemp, readFile, rename, rm, writeFile } from "node:fs/promises";
4
+ import os from "node:os";
5
+ import path from "node:path";
6
+ import { execFile } from "node:child_process";
7
+ var SITE_ID = /^[A-Za-z0-9][A-Za-z0-9-]{0,63}$/;
8
+ var isSiteId = (id) => SITE_ID.test(id);
9
+ function siteNameOf(folder) {
10
+ const name = folder.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 50).replace(/-+$/, "");
11
+ return name === "" ? "daydream-site" : name;
12
+ }
13
+ function projectOf(publishedOut) {
14
+ return path.resolve(publishedOut, "..", "..");
15
+ }
16
+ function deployArgs(dir, siteId) {
17
+ return [
18
+ "deploy",
19
+ "--dir",
20
+ dir,
21
+ "--no-build",
22
+ "--prod",
23
+ "--json",
24
+ "--message",
25
+ "Daydream publish",
26
+ "--site",
27
+ siteId
28
+ ];
29
+ }
30
+ function createArgs(name, team) {
31
+ return [
32
+ "sites:create",
33
+ "--name",
34
+ name,
35
+ "--disable-linking",
36
+ "--json",
37
+ ...team ? ["--account-slug", team] : []
38
+ ];
39
+ }
40
+ function parseCreated(stdout) {
41
+ const read = jsonObjectIn(stdout);
42
+ if (read === null) return null;
43
+ const url = read["ssl_url"] || read["url"];
44
+ if (typeof read["id"] !== "string" || typeof url !== "string") return null;
45
+ return {
46
+ siteId: read["id"],
47
+ siteName: typeof read["name"] === "string" ? read["name"] : "",
48
+ url
49
+ };
50
+ }
51
+ function jsonObjectIn(stdout) {
52
+ const from = stdout.indexOf("{");
53
+ const to = stdout.lastIndexOf("}");
54
+ if (from < 0 || to < from) return null;
55
+ let answer;
56
+ try {
57
+ answer = JSON.parse(stdout.slice(from, to + 1));
58
+ } catch {
59
+ return null;
60
+ }
61
+ return typeof answer === "object" && answer !== null ? answer : null;
62
+ }
63
+ function parseDeploy(stdout) {
64
+ const read = jsonObjectIn(stdout);
65
+ if (read === null) return null;
66
+ const url = read["url"] ?? read["deploy_url"];
67
+ if (typeof read["site_id"] !== "string" || typeof url !== "string") return null;
68
+ return {
69
+ siteId: read["site_id"],
70
+ siteName: typeof read["site_name"] === "string" ? read["site_name"] : "",
71
+ url
72
+ };
73
+ }
74
+ function siteArgs(siteId) {
75
+ return [
76
+ "api",
77
+ "getSite",
78
+ "--data",
79
+ JSON.stringify({ site_id: siteId })
80
+ ];
81
+ }
82
+ function parseSite(stdout) {
83
+ const read = jsonObjectIn(stdout);
84
+ if (read === null) return null;
85
+ const url = read["ssl_url"] || read["url"];
86
+ if (typeof read["name"] !== "string" || typeof url !== "string") return null;
87
+ return {
88
+ siteName: read["name"],
89
+ url
90
+ };
91
+ }
92
+ function canvasUrlOf(url, canvasIsIndex) {
93
+ const site = url.endsWith("/") ? url : `${url}/`;
94
+ return canvasIsIndex ? site : `${site}_daydream/`;
95
+ }
96
+ function cliFailure(stderr, stdout) {
97
+ const lines = `${stdout}\n${stderr}`.split("\n").map((line) => line.replace(/\u001b\[[0-9;]*m/g, "").trim().replace(/^›\s*/, "").replace(/^Error:\s*/, "")).filter((line) => line !== "");
98
+ return (lines.filter((line) => /error|fail|not |unauthor|multiple/i.test(line)).at(-1) ?? lines.at(-1) ?? "the Netlify CLI failed").slice(0, 300);
99
+ }
100
+ function cliEnv(env) {
101
+ const rest = { ...env };
102
+ delete rest["NETLIFY_SITE_ID"];
103
+ delete rest["NETLIFY_AUTH_TOKEN"];
104
+ return rest;
105
+ }
106
+ var NO_CLI = "the Netlify CLI is not installed: npm install -g netlify-cli, then netlify login";
107
+ var run = (file, args, options) => new Promise((resolve) => {
108
+ execFile(file, args, {
109
+ cwd: options.cwd,
110
+ env: options.env,
111
+ timeout: options.timeoutMs,
112
+ maxBuffer: 8388608
113
+ }, (error, stdout, stderr) => {
114
+ const failed = error;
115
+ if (failed !== null && failed.code === "ENOENT") {
116
+ resolve({ missing: true });
117
+ return;
118
+ }
119
+ resolve({
120
+ missing: false,
121
+ code: failed === null ? 0 : typeof failed.code === "number" ? failed.code : 1,
122
+ stdout,
123
+ stderr
124
+ });
125
+ });
126
+ });
127
+ async function findCli(runner, env, cwd) {
128
+ const probe = await runner("netlify", ["--version"], {
129
+ cwd,
130
+ env,
131
+ timeoutMs: 8e3
132
+ });
133
+ if (!probe.missing && probe.code === 0) return {
134
+ file: "netlify",
135
+ env
136
+ };
137
+ const asked = await runner(env["SHELL"] ?? "/bin/zsh", ["-lic", "command -v netlify"], {
138
+ cwd,
139
+ env,
140
+ timeoutMs: 8e3
141
+ });
142
+ if (asked.missing || asked.code !== 0) return null;
143
+ const file = asked.stdout.split("\n").map((line) => line.trim()).filter((line) => path.isAbsolute(line)).at(-1);
144
+ if (file === void 0) return null;
145
+ return {
146
+ file,
147
+ env: {
148
+ ...env,
149
+ PATH: `${path.dirname(file)}${path.delimiter}${env["PATH"] ?? ""}`
150
+ }
151
+ };
152
+ }
153
+ var defaults = () => ({
154
+ pages: null,
155
+ includeCanvas: true,
156
+ newSite: null
157
+ });
158
+ function isSettings(value) {
159
+ if (!value || typeof value !== "object") return false;
160
+ const s = value;
161
+ const target = s["newSite"];
162
+ return (s["pages"] === null || Array.isArray(s["pages"]) && s["pages"].length > 0 && s["pages"].every((page) => typeof page === "string" && page.length > 0)) && typeof s["includeCanvas"] === "boolean" && (target === null || typeof target === "object" && typeof target["name"] === "string" && /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(target["name"]) && target["name"].length <= 50 && typeof target["team"] === "string" && /^[a-zA-Z0-9][a-zA-Z0-9-]*$/.test(target["team"]));
163
+ }
164
+ var messageOf = (error) => error instanceof Error ? error.message : String(error);
165
+ var isHttps = (value) => {
166
+ if (typeof value !== "string") return false;
167
+ try {
168
+ return new URL(value).protocol === "https:";
169
+ } catch {
170
+ return false;
171
+ }
172
+ };
173
+ function isSite(value) {
174
+ if (typeof value !== "object" || value === null) return false;
175
+ const site = value;
176
+ return typeof site["siteId"] === "string" && site["siteId"] !== "" && typeof site["siteName"] === "string" && typeof site["url"] === "string" && typeof site["canvasUrl"] === "string";
177
+ }
178
+ var SITE_KEY = "site";
179
+ async function readDestinationVersion(file) {
180
+ const version = (await readData(file))["destinationVersion"] ?? null;
181
+ if (version !== null && typeof version !== "string") throw new StoreUnreadable(file, "invalid destination version");
182
+ return version;
183
+ }
184
+ async function readSettings(file) {
185
+ const value = (await readData(file))["settings"];
186
+ if (value === void 0) return defaults();
187
+ if (!isSettings(value)) throw new StoreUnreadable(file, "invalid publishing choices");
188
+ return value;
189
+ }
190
+ function writeSettings(file, settings, site) {
191
+ return writeKeys(file, {
192
+ settings,
193
+ ...site === void 0 ? {} : {
194
+ [SITE_KEY]: site,
195
+ destinationVersion: randomBytes(12).toString("hex")
196
+ }
197
+ });
198
+ }
199
+ var StoreUnreadable = class extends Error {
200
+ constructor(file, cause) {
201
+ super(`the plugin's storage file could not be read (${messageOf(cause)}): ${file}`);
202
+ this.name = "StoreUnreadable";
203
+ }
204
+ };
205
+ async function readData(file) {
206
+ let text;
207
+ try {
208
+ text = await readFile(file, "utf8");
209
+ } catch (error) {
210
+ if (error.code === "ENOENT") return {};
211
+ throw new StoreUnreadable(file, error);
212
+ }
213
+ let data;
214
+ try {
215
+ data = JSON.parse(text);
216
+ } catch (error) {
217
+ throw new StoreUnreadable(file, error);
218
+ }
219
+ return typeof data === "object" && data !== null && !Array.isArray(data) ? data : {};
220
+ }
221
+ async function readSite(file) {
222
+ const site = (await readData(file))[SITE_KEY];
223
+ return isSite(site) ? site : null;
224
+ }
225
+ function writeSite(file, site) {
226
+ return writeKey(file, SITE_KEY, site);
227
+ }
228
+ var OWED_KEY = "owed";
229
+ async function readOwed(userFile, projectFile) {
230
+ try {
231
+ const owed = (await readData(userFile))[OWED_KEY];
232
+ const site = typeof owed === "object" && owed !== null ? owed[projectFile] : void 0;
233
+ if (!isSite(site)) return null;
234
+ const recorded = site;
235
+ const current = await readDestinationVersion(projectFile);
236
+ if ((recorded.destinationVersion ?? null) !== current) return null;
237
+ const clean = { ...recorded };
238
+ delete clean.destinationVersion;
239
+ return {
240
+ site: clean,
241
+ destinationVersion: current
242
+ };
243
+ } catch {
244
+ return null;
245
+ }
246
+ }
247
+ async function writeOwed(userFile, projectFile, recovery) {
248
+ let data;
249
+ try {
250
+ data = await readData(userFile);
251
+ } catch {
252
+ data = {};
253
+ }
254
+ const held = data[OWED_KEY];
255
+ const owed = typeof held === "object" && held !== null && !Array.isArray(held) ? { ...held } : {};
256
+ if (recovery === null) delete owed[projectFile];
257
+ else owed[projectFile] = {
258
+ ...recovery.site,
259
+ destinationVersion: recovery.destinationVersion
260
+ };
261
+ await writeKey(userFile, OWED_KEY, Object.keys(owed).length === 0 ? null : owed);
262
+ }
263
+ async function writeKey(file, key, value) {
264
+ return writeKeys(file, { [key]: value });
265
+ }
266
+ async function writeKeys(file, values) {
267
+ if (await lstat(file).then((found) => found.isSymbolicLink(), () => false)) throw new Error(`${path.basename(file)} is a link`);
268
+ const data = await readData(file);
269
+ for (const [key, value] of Object.entries(values)) if (value === null) delete data[key];
270
+ else data[key] = value;
271
+ await mkdir(path.dirname(file), { recursive: true });
272
+ const beside = `${file}.${randomBytes(6).toString("hex")}.tmp`;
273
+ await writeFile(beside, `${JSON.stringify(data, null, 2)}\n`, { flag: "wx" });
274
+ try {
275
+ await rename(beside, file);
276
+ } catch (error) {
277
+ await rm(beside, { force: true });
278
+ throw error;
279
+ }
280
+ }
281
+ function isManualLogin(value) {
282
+ if (!value || typeof value !== "object") return false;
283
+ const login = value;
284
+ return isHttps(login.url) && typeof login.command === "string" && login.command.length > 0;
285
+ }
286
+ async function account(run, withCli) {
287
+ try {
288
+ return await withCli(async (cli, cwd) => {
289
+ const done = await run(cli.file, ["status", "--json"], {
290
+ cwd,
291
+ env: cli.env,
292
+ timeoutMs: 2e4
293
+ });
294
+ if (done.missing) throw new Error(NO_CLI);
295
+ const data = jsonObjectIn(done.stdout);
296
+ const who = data?.["account"];
297
+ if (data?.["loggedIn"] === true || typeof who?.["Name"] === "string" || typeof who?.["Email"] === "string") return {
298
+ state: "ready",
299
+ name: String(who?.["Name"] ?? who?.["Email"] ?? ""),
300
+ error: null
301
+ };
302
+ if (data?.["loggedIn"] === false || /not logged in|not signed in|session has expired/i.test(done.stdout + done.stderr)) return {
303
+ state: "signed-out",
304
+ name: "",
305
+ error: null
306
+ };
307
+ throw new Error(cliFailure(done.stderr, done.stdout));
308
+ });
309
+ } catch (error) {
310
+ const message = error instanceof Error ? error.message : String(error);
311
+ return {
312
+ state: message === "the Netlify CLI is not installed: npm install -g netlify-cli, then netlify login" ? "missing" : "error",
313
+ name: "",
314
+ error: message
315
+ };
316
+ }
317
+ }
318
+ async function list(run, cli, cwd, method, data = {}) {
319
+ const done = await run(cli.file, [
320
+ "api",
321
+ method,
322
+ "--data",
323
+ JSON.stringify(data)
324
+ ], {
325
+ cwd,
326
+ env: cli.env,
327
+ timeoutMs: 15e3
328
+ });
329
+ if (done.missing) throw new Error(NO_CLI);
330
+ if (done.code !== 0) throw new Error(cliFailure(done.stderr, done.stdout));
331
+ let value;
332
+ try {
333
+ value = JSON.parse(done.stdout);
334
+ } catch {
335
+ throw new Error("Netlify returned an unreadable project list.");
336
+ }
337
+ if (!Array.isArray(value) || value.some((entry) => entry === null || typeof entry !== "object")) throw new Error("Netlify returned an unreadable project list.");
338
+ return value;
339
+ }
340
+ async function destinations(run, withCli, page = 1) {
341
+ return withCli(async (cli, cwd) => {
342
+ const [accounts, projects] = await Promise.all([page === 1 ? list(run, cli, cwd, "listAccountsForUser") : Promise.resolve([]), list(run, cli, cwd, "listSites", {
343
+ page,
344
+ per_page: 100
345
+ })]);
346
+ return {
347
+ teams: accounts.flatMap((entry) => typeof entry["slug"] === "string" && typeof entry["name"] === "string" ? [{
348
+ slug: entry["slug"],
349
+ name: entry["name"]
350
+ }] : []),
351
+ sites: projects.flatMap((entry) => {
352
+ const url = entry["ssl_url"] || entry["url"];
353
+ return typeof entry["id"] === "string" && isSiteId(entry["id"]) && typeof entry["name"] === "string" && typeof url === "string" ? [{
354
+ siteId: entry["id"],
355
+ siteName: entry["name"],
356
+ url,
357
+ canvasUrl: ""
358
+ }] : [];
359
+ }),
360
+ more: projects.length === 100
361
+ };
362
+ });
363
+ }
364
+ async function defaultTeam(run, cli, cwd) {
365
+ const teams = await list(run, cli, cwd, "listAccountsForUser");
366
+ const chosen = teams.find((team) => team["default"] === true) ?? (teams.length === 1 ? teams[0] : void 0);
367
+ if (!chosen || typeof chosen["slug"] !== "string") throw new Error("Choose a Netlify team.");
368
+ return chosen["slug"];
369
+ }
370
+ function manualLogin(stdout) {
371
+ const data = jsonObjectIn(stdout);
372
+ const login = {
373
+ url: data?.["url"],
374
+ command: data?.["check_command"]
375
+ };
376
+ return isManualLogin(login) ? login : null;
377
+ }
378
+ var DEPLOY_TIMEOUT_MS = 6e5;
379
+ var LOOKUP_TIMEOUT_MS = 2e4;
380
+ var NO_PROJECT = "open a project to deploy it";
381
+ var PROJECT_CHANGED = "another project was opened while this one was being deployed; nothing was uploaded";
382
+ var BUSY = "a deploy is already running";
383
+ var SITE_CHANGED = "the site this project deploys to changed since the panel last asked; check again before deploying";
384
+ var UNCHECKED = "the stored site could not be checked with Netlify, so nothing is deployed to it";
385
+ function freshState() {
386
+ return {
387
+ acting: null,
388
+ deploying: false,
389
+ generation: 0,
390
+ failed: new Map(),
391
+ unsaved: new Map(),
392
+ checked: new Map(),
393
+ phase: null,
394
+ activeFile: null,
395
+ published: new Map(),
396
+ login: null
397
+ };
398
+ }
399
+ var STATE = Symbol.for("daydream.netlify.deployer");
400
+ function processState() {
401
+ const held = globalThis;
402
+ const state = held[STATE] ??= freshState();
403
+ state.phase ??= null;
404
+ state.activeFile ??= null;
405
+ state.published ??= new Map();
406
+ state.login ??= null;
407
+ return state;
408
+ }
409
+ function createDeployer(host, given = {}, state = processState()) {
410
+ const deps = {
411
+ run,
412
+ env: process.env,
413
+ readSite,
414
+ writeSite,
415
+ readOwed,
416
+ writeOwed,
417
+ readSettings,
418
+ writeSettings,
419
+ readDestinationVersion,
420
+ suffix: () => randomBytes(3).toString("hex"),
421
+ ...given
422
+ };
423
+ const suffix = () => deps.suffix();
424
+ const keyOf = (file, siteId) => `${file}\n${siteId}`;
425
+ async function withCli(use, inProject = null) {
426
+ const cwd = inProject ?? await mkdtemp(path.join(os.tmpdir(), "daydream-netlify-"));
427
+ const kept = path.join(cwd, ".netlify");
428
+ const had = inProject !== null && existsSync(kept);
429
+ try {
430
+ const cli = await findCli(deps.run, cliEnv(deps.env), cwd);
431
+ if (cli === null) throw new Error(NO_CLI);
432
+ return await use(cli, cwd);
433
+ } finally {
434
+ if (inProject === null) await rm(cwd, {
435
+ recursive: true,
436
+ force: true
437
+ });
438
+ else if (!had) await rm(kept, {
439
+ recursive: true,
440
+ force: true
441
+ });
442
+ }
443
+ }
444
+ async function knownSite(file) {
445
+ const held = state.unsaved.get(file);
446
+ if (held !== void 0) return isSite(held) ? held : held.site;
447
+ const began = state.generation;
448
+ const owed = await deps.readOwed(host.plugin.userDataFile, file);
449
+ if (began !== state.generation) return knownSite(file);
450
+ if (owed !== null) {
451
+ state.unsaved.set(file, owed);
452
+ return owed.site;
453
+ }
454
+ return deps.readSite(file);
455
+ }
456
+ async function madeSite(cli, name, team) {
457
+ const cwd = await mkdtemp(path.join(os.tmpdir(), "daydream-netlify-"));
458
+ try {
459
+ let failure = "the Netlify CLI failed";
460
+ for (const tried of [name, `${name}-${suffix()}`]) {
461
+ const done = await deps.run(cli.file, createArgs(tried, team), {
462
+ cwd,
463
+ env: cli.env,
464
+ timeoutMs: LOOKUP_TIMEOUT_MS
465
+ });
466
+ if (done.missing) throw new Error(NO_CLI);
467
+ const made = done.code === 0 ? parseCreated(done.stdout) : null;
468
+ if (made !== null) return made;
469
+ failure = cliFailure(done.stderr, done.stdout);
470
+ }
471
+ throw new Error(failure);
472
+ } finally {
473
+ await rm(cwd, {
474
+ recursive: true,
475
+ force: true
476
+ });
477
+ }
478
+ }
479
+ async function checkedSite(file, site) {
480
+ if (!isSiteId(site.siteId)) return {
481
+ site: null,
482
+ why: "its id is no site id"
483
+ };
484
+ const key = keyOf(file, site.siteId);
485
+ let answered = state.checked.get(key);
486
+ if (answered === void 0) {
487
+ try {
488
+ answered = await withCli(async (cli, cwd) => {
489
+ const done = await deps.run(cli.file, siteArgs(site.siteId), {
490
+ cwd,
491
+ env: cli.env,
492
+ timeoutMs: LOOKUP_TIMEOUT_MS
493
+ });
494
+ if (done.missing) throw new Error(NO_CLI);
495
+ const found = done.code === 0 ? parseSite(done.stdout) : null;
496
+ if (found === null) throw new Error(cliFailure(done.stderr, done.stdout));
497
+ return found;
498
+ });
499
+ } catch (error) {
500
+ return {
501
+ site: null,
502
+ why: messageOf(error)
503
+ };
504
+ }
505
+ state.checked.set(key, answered);
506
+ }
507
+ if (answered.siteName === site.siteName && answered.url === site.url) return { site };
508
+ const atIndex = canvasUrlOf(site.url, true) === site.canvasUrl;
509
+ return { site: {
510
+ ...site,
511
+ siteId: site.siteId,
512
+ siteName: answered.siteName,
513
+ url: answered.url,
514
+ canvasUrl: site.canvasUrl === "" ? "" : canvasUrlOf(answered.url, atIndex)
515
+ } };
516
+ }
517
+ async function store(file, recovery) {
518
+ try {
519
+ await deps.writeSite(file, recovery.site);
520
+ state.unsaved.delete(file);
521
+ state.failed.delete(file);
522
+ await deps.writeOwed(host.plugin.userDataFile, file, null).catch(() => {});
523
+ } catch (error) {
524
+ state.unsaved.set(file, recovery);
525
+ await deps.writeOwed(host.plugin.userDataFile, file, recovery).catch(() => {});
526
+ state.failed.set(file, `deployed, but the site could not be remembered (${messageOf(error)}); it will be tried again`);
527
+ }
528
+ }
529
+ async function work(file, asked, expected, pages) {
530
+ const settings = await deps.readSettings(file);
531
+ const destinationVersion = await deps.readDestinationVersion(file);
532
+ if (expected && JSON.stringify(settings) !== JSON.stringify(expected)) throw new Error("Publishing choices changed. Check them and publish again.");
533
+ if (pages !== void 0 && (pages.length === 0 || settings.pages === null || pages.some((page) => !settings.pages.includes(page)))) throw new Error("Choose at least one of the saved pages.");
534
+ const known = await knownSite(file);
535
+ if ((known?.siteId ?? null) !== asked) throw new Error(SITE_CHANGED);
536
+ let site = null;
537
+ if (known !== null) {
538
+ const checked = await checkedSite(file, known);
539
+ if (checked.site === null) throw new Error(`${UNCHECKED} (${checked.why})`);
540
+ site = checked.site;
541
+ }
542
+ await withCli(async (cli, cwd) => {
543
+ state.phase = "preparing";
544
+ const team = site === null ? settings.newSite?.team ?? await defaultTeam(deps.run, cli, cwd) : void 0;
545
+ const scope = {
546
+ pages: pages ?? settings.pages,
547
+ includeCanvas: settings.includeCanvas
548
+ };
549
+ const published = await host.project.publish({
550
+ ...scope.pages === null ? {} : { pages: scope.pages },
551
+ includeCanvas: settings.includeCanvas
552
+ });
553
+ if (host.plugin.dataFile !== file || !file.startsWith(projectOf(published.out) + path.sep)) throw new Error(PROJECT_CHANGED);
554
+ const made = site === null;
555
+ if (site === null) {
556
+ const created = await madeSite(cli, settings.newSite?.name ?? siteNameOf(path.basename(projectOf(published.out))), team);
557
+ site = {
558
+ ...created,
559
+ canvasUrl: settings.includeCanvas ? canvasUrlOf(created.url, published.canvasIsIndex) : ""
560
+ };
561
+ state.checked.set(keyOf(file, site.siteId), {
562
+ siteName: site.siteName,
563
+ url: site.url
564
+ });
565
+ await store(file, {
566
+ site,
567
+ destinationVersion
568
+ });
569
+ }
570
+ state.phase = "uploading";
571
+ const done = await deps.run(cli.file, deployArgs(published.out, site.siteId), {
572
+ cwd,
573
+ env: cli.env,
574
+ timeoutMs: DEPLOY_TIMEOUT_MS
575
+ });
576
+ if (done.missing) throw new Error(NO_CLI);
577
+ const deployed = done.code === 0 ? parseDeploy(done.stdout) : null;
578
+ if (deployed === null) {
579
+ const failure = cliFailure(done.stderr, done.stdout);
580
+ throw new Error(made ? `the site was made, but the upload failed (${failure}); deploy again` : failure);
581
+ }
582
+ state.checked.set(keyOf(file, deployed.siteId), {
583
+ siteName: deployed.siteName,
584
+ url: deployed.url
585
+ });
586
+ await store(file, {
587
+ destinationVersion,
588
+ site: {
589
+ ...deployed,
590
+ publishedScope: scope,
591
+ canvasUrl: settings.includeCanvas ? canvasUrlOf(deployed.url, published.canvasIsIndex) : ""
592
+ }
593
+ });
594
+ state.published.set(file, Date.now());
595
+ }, path.resolve(file, "..", "..", ".."));
596
+ }
597
+ async function status() {
598
+ const file = host.plugin.dataFile;
599
+ const running = state.deploying;
600
+ if (file === null) return {
601
+ running,
602
+ site: null,
603
+ checked: false,
604
+ error: NO_PROJECT
605
+ };
606
+ let known;
607
+ try {
608
+ known = await knownSite(file);
609
+ } catch (error) {
610
+ return {
611
+ running,
612
+ site: null,
613
+ checked: false,
614
+ error: messageOf(error)
615
+ };
616
+ }
617
+ const owed = state.unsaved.get(file);
618
+ if (owed !== void 0 && state.acting === null) await act(false, file, async () => {
619
+ const recovery = isSite(owed) ? {
620
+ site: owed,
621
+ destinationVersion: await deps.readDestinationVersion(file)
622
+ } : owed;
623
+ await store(file, recovery);
624
+ });
625
+ const said = state.failed.get(file) ?? null;
626
+ if (known === null) return {
627
+ running,
628
+ site: null,
629
+ checked: true,
630
+ error: said
631
+ };
632
+ const checked = await checkedSite(file, known);
633
+ return checked.site === null ? {
634
+ running,
635
+ site: known,
636
+ checked: false,
637
+ error: said ?? `the stored site could not be checked with Netlify, so nothing is deployed to it (${checked.why})`
638
+ } : {
639
+ running,
640
+ site: checked.site,
641
+ checked: true,
642
+ error: said
643
+ };
644
+ }
645
+ function act(deploying, file, doing) {
646
+ state.deploying = deploying;
647
+ state.activeFile = file;
648
+ state.acting = doing().catch((error) => {
649
+ state.failed.set(file, messageOf(error));
650
+ }).finally(() => {
651
+ state.acting = null;
652
+ state.deploying = false;
653
+ state.phase = null;
654
+ state.activeFile = null;
655
+ });
656
+ return state.acting;
657
+ }
658
+ function beginning() {
659
+ if (state.acting !== null) throw new Error(BUSY);
660
+ const file = host.plugin.dataFile;
661
+ if (file === null) throw new Error(NO_PROJECT);
662
+ state.failed.delete(file);
663
+ state.generation += 1;
664
+ return file;
665
+ }
666
+ return {
667
+ status,
668
+ async panel() {
669
+ const file = host.plugin.dataFile;
670
+ const shown = await status();
671
+ let settings = defaults();
672
+ try {
673
+ if (file !== null) settings = await deps.readSettings(file);
674
+ } catch (error) {
675
+ shown.checked = false;
676
+ shown.error = messageOf(error);
677
+ }
678
+ if (host.plugin.dataFile !== file) throw new Error(PROJECT_CHANGED);
679
+ return {
680
+ ...shown,
681
+ running: state.acting !== null,
682
+ project: file,
683
+ projectName: file ? siteNameOf(path.basename(path.resolve(file, "..", "..", ".."))) : "",
684
+ settings,
685
+ login: state.login,
686
+ phase: state.activeFile === file ? state.phase : null,
687
+ published: file ? state.published.get(file) ?? null : null
688
+ };
689
+ },
690
+ async account() {
691
+ const generation = state.generation;
692
+ const answer = await account(deps.run, withCli);
693
+ if (answer.state === "ready" && generation === state.generation) state.login = null;
694
+ return answer;
695
+ },
696
+ destinations: (page) => destinations(deps.run, withCli, page),
697
+ async login() {
698
+ const file = beginning();
699
+ state.phase = "login";
700
+ state.login = null;
701
+ act(false, file, () => withCli(async (cli, cwd) => {
702
+ const done = await deps.run(cli.file, ["login", "--new"], {
703
+ cwd,
704
+ env: cli.env,
705
+ timeoutMs: 18e4
706
+ });
707
+ if (done.missing) throw new Error(NO_CLI);
708
+ if (done.code !== 0) throw new Error(cliFailure(done.stderr, done.stdout));
709
+ state.login = manualLogin(done.stdout);
710
+ }));
711
+ },
712
+ async save(settings, siteId) {
713
+ if (!isSettings(settings)) throw new Error("Invalid publishing choices.");
714
+ if (siteId !== void 0 && siteId !== null && !isSiteId(siteId)) throw new Error("Choose a Netlify project.");
715
+ const file = beginning();
716
+ await act(false, file, async () => {
717
+ let site;
718
+ if (siteId === null) site = null;
719
+ else if (siteId !== void 0) {
720
+ const known = await knownSite(file);
721
+ const result = await checkedSite(file, known?.siteId === siteId ? known : {
722
+ siteId,
723
+ siteName: "",
724
+ url: "",
725
+ canvasUrl: ""
726
+ });
727
+ if (result.site === null) throw new Error(result.why);
728
+ site = result.site;
729
+ }
730
+ if (host.plugin.dataFile !== file) throw new Error(PROJECT_CHANGED);
731
+ await deps.writeSettings(file, settings, site);
732
+ if (site !== void 0) {
733
+ state.unsaved.delete(file);
734
+ state.published.delete(file);
735
+ await deps.writeOwed(host.plugin.userDataFile, file, null).catch(() => {});
736
+ }
737
+ });
738
+ const error = state.failed.get(file);
739
+ if (error) throw new Error(error);
740
+ },
741
+ async start(to, expected, pages) {
742
+ const file = beginning();
743
+ act(true, file, () => work(file, to, expected, pages));
744
+ return status();
745
+ },
746
+ async forget() {
747
+ const file = beginning();
748
+ await act(false, file, async () => {
749
+ await deps.writeOwed(host.plugin.userDataFile, file, null);
750
+ state.unsaved.delete(file);
751
+ await deps.writeSite(file, null);
752
+ });
753
+ return status();
754
+ },
755
+ settled: () => state.acting ?? Promise.resolve()
756
+ };
757
+ }
758
+ function siteOf(input) {
759
+ const site = input?.site;
760
+ return typeof site === "string" ? site : null;
761
+ }
762
+ function activate(host) {
763
+ const deployer = createDeployer(host);
764
+ function inputForProject(input) {
765
+ if (!input || typeof input !== "object" || input.project !== host.plugin.dataFile) throw new Error(PROJECT_CHANGED);
766
+ return input;
767
+ }
768
+ host.browserPart.handle("status", () => deployer.panel());
769
+ host.browserPart.handle("account", () => deployer.account());
770
+ host.browserPart.handle("destinations", (input) => {
771
+ const page = input?.page ?? 1;
772
+ if (typeof page !== "number" || !Number.isInteger(page) || page < 1) throw new Error("Invalid project list page.");
773
+ return deployer.destinations(page);
774
+ });
775
+ host.browserPart.handle("login", async (input) => {
776
+ inputForProject(input);
777
+ await deployer.login();
778
+ return deployer.panel();
779
+ });
780
+ host.browserPart.handle("deploy", async (input) => {
781
+ const asked = inputForProject(input);
782
+ if (!isSettings(asked["settings"])) throw new Error("Invalid publishing choices.");
783
+ const pages = asked["pages"];
784
+ if (pages !== void 0 && (!Array.isArray(pages) || !pages.every((page) => typeof page === "string"))) throw new Error("Invalid page selection.");
785
+ await deployer.start(siteOf(input), asked["settings"], pages);
786
+ return deployer.panel();
787
+ });
788
+ host.browserPart.handle("save", async (input) => {
789
+ const asked = inputForProject(input);
790
+ if (!isSettings(asked["settings"])) throw new Error("Invalid publishing choices.");
791
+ const site = asked["site"];
792
+ if (site !== void 0 && site !== null && typeof site !== "string") throw new Error("Invalid project.");
793
+ await deployer.save(asked["settings"], site);
794
+ return deployer.panel();
795
+ });
796
+ }
797
+ export { BUSY, NO_PROJECT, PROJECT_CHANGED, SITE_CHANGED, UNCHECKED, createDeployer, activate as default, freshState };