@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 +797 -0
- package/dist/index.js +1270 -0
- package/icon.svg +12 -0
- package/manifest.json +24 -0
- package/package.json +15 -4
- package/README.md +0 -3
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 };
|