htmlship 0.2.0 → 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/cli.js +261 -41
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -53,8 +53,16 @@ var init_errors = __esm({
|
|
|
53
53
|
|
|
54
54
|
// src/build.ts
|
|
55
55
|
import { spawnSync } from "child_process";
|
|
56
|
-
import {
|
|
57
|
-
|
|
56
|
+
import {
|
|
57
|
+
existsSync,
|
|
58
|
+
readdirSync,
|
|
59
|
+
readFileSync,
|
|
60
|
+
renameSync,
|
|
61
|
+
statSync,
|
|
62
|
+
unlinkSync,
|
|
63
|
+
writeFileSync
|
|
64
|
+
} from "fs";
|
|
65
|
+
import { dirname, extname, isAbsolute, join, relative, resolve, sep } from "path";
|
|
58
66
|
function detectPackageManager(dir) {
|
|
59
67
|
for (const [file, pm] of LOCKFILES) {
|
|
60
68
|
if (existsSync(join(dir, file))) return pm;
|
|
@@ -121,16 +129,61 @@ function exec(command, cwd, timeout, log) {
|
|
|
121
129
|
throw new HTMLShipError(`build exited with code ${r.status}: \`${command}\``);
|
|
122
130
|
}
|
|
123
131
|
}
|
|
132
|
+
function looksLikeNextJs(dir) {
|
|
133
|
+
if (existsSync(join(dir, ".next"))) return true;
|
|
134
|
+
return ["next.config.js", "next.config.mjs", "next.config.ts", "next.config.cjs"].some(
|
|
135
|
+
(f) => existsSync(join(dir, f))
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
function noOutputMessage(dir) {
|
|
139
|
+
const base = `no build output found (looked for ${OUTPUT_DIR_CANDIDATES.map((c) => `${c}/`).join(", ")}; use --out to specify)`;
|
|
140
|
+
if (looksLikeNextJs(dir)) {
|
|
141
|
+
return `${base}.
|
|
142
|
+
This looks like a Next.js app: \`next build\` writes a server build to .next/, which is not statically hostable. Add \`output: "export"\` to your next.config to emit a static out/ folder, then re-run. Note: deploy inlines a single-page static app \u2014 server-rendered or multi-route Next.js sites are not supported.`;
|
|
143
|
+
}
|
|
144
|
+
return base;
|
|
145
|
+
}
|
|
124
146
|
function resolveOutputDir(dir, override) {
|
|
125
|
-
const candidates = override ? [override] :
|
|
147
|
+
const candidates = override ? [override] : OUTPUT_DIR_CANDIDATES;
|
|
126
148
|
for (const c of candidates) {
|
|
127
149
|
const p = isAbsolute(c) ? c : join(dir, c);
|
|
128
150
|
if (existsSync(p) && statSync(p).isDirectory()) return p;
|
|
129
151
|
}
|
|
130
152
|
throw new HTMLShipError(
|
|
131
|
-
override ? `build output dir not found: ${override}` :
|
|
153
|
+
override ? `build output dir not found: ${override}` : noOutputMessage(dir)
|
|
132
154
|
);
|
|
133
155
|
}
|
|
156
|
+
function isNextServerBuild(dir) {
|
|
157
|
+
return existsSync(join(dir, "BUILD_ID")) || existsSync(join(dir, "server")) && existsSync(join(dir, "static"));
|
|
158
|
+
}
|
|
159
|
+
function noEntryMessage(outDir, htmls) {
|
|
160
|
+
if (isNextServerBuild(outDir)) {
|
|
161
|
+
return `no static HTML entry in ${outDir} \u2014 this looks like a Next.js server build, not a static site. Set \`output: "export"\` in your next.config (this emits a fully static out/ folder); changing distDir alone only renames the server build. Note: apps that use middleware or server rendering cannot be statically exported.`;
|
|
162
|
+
}
|
|
163
|
+
if (htmls.length > 1) {
|
|
164
|
+
return `no index.html in ${outDir}, but found ${htmls.length} HTML files (${htmls.slice(0, 4).join(", ")}${htmls.length > 4 ? ", \u2026" : ""}). deploy ships a single page \u2014 pass --entry <file> to choose one.`;
|
|
165
|
+
}
|
|
166
|
+
return `entry HTML not found in ${outDir} (looked for index.html); pass --entry <file> to specify it.`;
|
|
167
|
+
}
|
|
168
|
+
function resolveEntry(outDir, entryOverride) {
|
|
169
|
+
if (entryOverride) {
|
|
170
|
+
const p = join(outDir, entryOverride);
|
|
171
|
+
if (existsSync(p)) return p;
|
|
172
|
+
throw new HTMLShipError(`entry HTML not found: ${p}`);
|
|
173
|
+
}
|
|
174
|
+
for (const name of ["index.html", "200.html", "index.htm"]) {
|
|
175
|
+
const p = join(outDir, name);
|
|
176
|
+
if (existsSync(p)) return p;
|
|
177
|
+
}
|
|
178
|
+
let htmls = [];
|
|
179
|
+
try {
|
|
180
|
+
htmls = readdirSync(outDir).filter((f) => f.toLowerCase().endsWith(".html"));
|
|
181
|
+
} catch {
|
|
182
|
+
htmls = [];
|
|
183
|
+
}
|
|
184
|
+
if (htmls.length === 1) return join(outDir, htmls[0]);
|
|
185
|
+
throw new HTMLShipError(noEntryMessage(outDir, htmls));
|
|
186
|
+
}
|
|
134
187
|
function mimeFor(p) {
|
|
135
188
|
return MIME[extname(p).toLowerCase()] ?? "application/octet-stream";
|
|
136
189
|
}
|
|
@@ -232,6 +285,108 @@ function formatBytes(n) {
|
|
|
232
285
|
if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`;
|
|
233
286
|
return `${(n / (1024 * 1024)).toFixed(2)} MB`;
|
|
234
287
|
}
|
|
288
|
+
function isNextProject(dir) {
|
|
289
|
+
if (NEXT_CONFIGS.some((f) => existsSync(join(dir, f)))) return true;
|
|
290
|
+
try {
|
|
291
|
+
const pkg = JSON.parse(readFileSync(join(dir, "package.json"), "utf8"));
|
|
292
|
+
return Boolean(pkg.dependencies?.["next"] || pkg.devDependencies?.["next"]);
|
|
293
|
+
} catch {
|
|
294
|
+
return false;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
function injectNextBase(dir) {
|
|
298
|
+
const existing = NEXT_CONFIGS.find((f) => existsSync(join(dir, f)));
|
|
299
|
+
if (existing && existing.endsWith(".ts")) {
|
|
300
|
+
throw new HTMLShipError(
|
|
301
|
+
`Next .ts config isn't auto-configured yet. Temporarily set basePath and assetPrefix to "${SITE_BASE_PLACEHOLDER}" and output: "export" in next.config.ts, or convert it to next.config.mjs, then re-run.`
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
const wrapper = join(dir, "next.config.mjs");
|
|
305
|
+
const base = SITE_BASE_PLACEHOLDER;
|
|
306
|
+
if (!existing) {
|
|
307
|
+
writeFileSync(
|
|
308
|
+
wrapper,
|
|
309
|
+
`export default { output: 'export', basePath: '${base}', assetPrefix: '${base}', images: { unoptimized: true }, trailingSlash: true };
|
|
310
|
+
`
|
|
311
|
+
);
|
|
312
|
+
return () => {
|
|
313
|
+
try {
|
|
314
|
+
unlinkSync(wrapper);
|
|
315
|
+
} catch {
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
const ext = extname(existing);
|
|
320
|
+
const backupName = `next.config.__hsorig__${ext}`;
|
|
321
|
+
renameSync(join(dir, existing), join(dir, backupName));
|
|
322
|
+
writeFileSync(
|
|
323
|
+
wrapper,
|
|
324
|
+
`import orig from './${backupName}';
|
|
325
|
+
const base = '${base}';
|
|
326
|
+
const over = { output: 'export', basePath: base, assetPrefix: base, images: { ...(typeof orig === 'object' && orig ? orig.images : {}), unoptimized: true }, trailingSlash: true };
|
|
327
|
+
export default typeof orig === 'function' ? ((...a) => ({ ...orig(...a), ...over })) : { ...orig, ...over };
|
|
328
|
+
`
|
|
329
|
+
);
|
|
330
|
+
return () => {
|
|
331
|
+
try {
|
|
332
|
+
unlinkSync(wrapper);
|
|
333
|
+
} catch {
|
|
334
|
+
}
|
|
335
|
+
try {
|
|
336
|
+
renameSync(join(dir, backupName), join(dir, existing));
|
|
337
|
+
} catch {
|
|
338
|
+
}
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
function packageSite(outDir, maxBytes = 10 * 1024 * 1024) {
|
|
342
|
+
const files = [];
|
|
343
|
+
let total = 0;
|
|
344
|
+
const walk = (d) => {
|
|
345
|
+
for (const e of readdirSync(d, { withFileTypes: true })) {
|
|
346
|
+
const full = join(d, e.name);
|
|
347
|
+
if (e.isDirectory()) walk(full);
|
|
348
|
+
else if (e.isFile()) {
|
|
349
|
+
const buf = readFileSync(full);
|
|
350
|
+
total += buf.length;
|
|
351
|
+
if (total > maxBytes) {
|
|
352
|
+
throw new HTMLShipError(`site exceeds the ${maxBytes / (1024 * 1024)} MB limit`);
|
|
353
|
+
}
|
|
354
|
+
files.push({ path: relative(outDir, full).split(sep).join("/"), content: buf.toString("base64") });
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
walk(outDir);
|
|
359
|
+
return { files, bytes: total };
|
|
360
|
+
}
|
|
361
|
+
function buildAndPackageSite(dir, opts = {}) {
|
|
362
|
+
const log = opts.log ?? (() => {
|
|
363
|
+
});
|
|
364
|
+
const project = detectProject(dir, opts.buildCmd);
|
|
365
|
+
const next = !opts.buildCmd && isNextProject(dir);
|
|
366
|
+
log(`project: ${dir}`);
|
|
367
|
+
log(`pkg mgr: ${project.packageManager}`);
|
|
368
|
+
log(`framework: ${next ? "next.js (static export)" : "multi-file"}`);
|
|
369
|
+
const cleanup = next ? injectNextBase(dir) : () => {
|
|
370
|
+
};
|
|
371
|
+
try {
|
|
372
|
+
if (next) log(`base: ${SITE_BASE_PLACEHOLDER} (injected for path hosting)`);
|
|
373
|
+
runBuild(project, {
|
|
374
|
+
install: opts.install,
|
|
375
|
+
buildCmd: opts.buildCmd,
|
|
376
|
+
timeoutMs: opts.timeoutMs,
|
|
377
|
+
log
|
|
378
|
+
});
|
|
379
|
+
} finally {
|
|
380
|
+
cleanup();
|
|
381
|
+
}
|
|
382
|
+
const outDir = resolveOutputDir(dir, opts.out);
|
|
383
|
+
const entryAbs = resolveEntry(outDir, opts.entry);
|
|
384
|
+
const entry = relative(outDir, entryAbs).split(sep).join("/");
|
|
385
|
+
const { files, bytes } = packageSite(outDir);
|
|
386
|
+
log(`output: ${outDir}`);
|
|
387
|
+
log(`packaged: ${files.length} files, ${formatBytes(bytes)}`);
|
|
388
|
+
return { files, bytes, entry, framework: next ? "next" : "multi-file" };
|
|
389
|
+
}
|
|
235
390
|
function buildAndInline(dir, opts = {}) {
|
|
236
391
|
const log = opts.log ?? (() => {
|
|
237
392
|
});
|
|
@@ -246,18 +401,20 @@ function buildAndInline(dir, opts = {}) {
|
|
|
246
401
|
log
|
|
247
402
|
});
|
|
248
403
|
const outDir = resolveOutputDir(dir, opts.out);
|
|
249
|
-
const entry =
|
|
404
|
+
const entry = resolveEntry(outDir, opts.entry);
|
|
250
405
|
const result = inlineHtml(outDir, entry);
|
|
251
406
|
log(`output: ${outDir}`);
|
|
252
407
|
log(`inlined: ${formatBytes(result.bytes)} single HTML`);
|
|
253
408
|
for (const w of result.warnings) log(`warning: ${w}`);
|
|
254
409
|
return result;
|
|
255
410
|
}
|
|
256
|
-
var LOCKFILES, DEFAULT_BUILD_TIMEOUT_MS, MIME;
|
|
411
|
+
var SITE_BASE_PLACEHOLDER, NEXT_CONFIGS, LOCKFILES, DEFAULT_BUILD_TIMEOUT_MS, OUTPUT_DIR_CANDIDATES, MIME;
|
|
257
412
|
var init_build = __esm({
|
|
258
413
|
"src/build.ts"() {
|
|
259
414
|
"use strict";
|
|
260
415
|
init_errors();
|
|
416
|
+
SITE_BASE_PLACEHOLDER = "/__htmlship_base__";
|
|
417
|
+
NEXT_CONFIGS = ["next.config.mjs", "next.config.js", "next.config.cjs", "next.config.ts"];
|
|
261
418
|
LOCKFILES = [
|
|
262
419
|
["pnpm-lock.yaml", "pnpm"],
|
|
263
420
|
["yarn.lock", "yarn"],
|
|
@@ -266,6 +423,7 @@ var init_build = __esm({
|
|
|
266
423
|
["package-lock.json", "npm"]
|
|
267
424
|
];
|
|
268
425
|
DEFAULT_BUILD_TIMEOUT_MS = 5 * 6e4;
|
|
426
|
+
OUTPUT_DIR_CANDIDATES = ["dist", "build", "out"];
|
|
269
427
|
MIME = {
|
|
270
428
|
".png": "image/png",
|
|
271
429
|
".jpg": "image/jpeg",
|
|
@@ -292,7 +450,7 @@ var VERSION;
|
|
|
292
450
|
var init_version = __esm({
|
|
293
451
|
"src/version.ts"() {
|
|
294
452
|
"use strict";
|
|
295
|
-
VERSION = "0.
|
|
453
|
+
VERSION = "0.3.0";
|
|
296
454
|
}
|
|
297
455
|
});
|
|
298
456
|
|
|
@@ -352,6 +510,37 @@ var init_client = __esm({
|
|
|
352
510
|
sandboxMode: "relaxed"
|
|
353
511
|
});
|
|
354
512
|
}
|
|
513
|
+
/** Upload a multi-file static site (built locally) and get its URL. */
|
|
514
|
+
async deploySite(files, options = {}) {
|
|
515
|
+
const body = { files };
|
|
516
|
+
if (options.entry) body["entry"] = options.entry;
|
|
517
|
+
if (options.title != null) body["title"] = options.title;
|
|
518
|
+
if (options.password != null) body["password"] = options.password;
|
|
519
|
+
if (options.expiresIn != null) body["expires_in"] = options.expiresIn;
|
|
520
|
+
return await this.request("POST", "/api/v1/sites", { body });
|
|
521
|
+
}
|
|
522
|
+
/**
|
|
523
|
+
* Build a local project and deploy it, auto-choosing single-file inlining
|
|
524
|
+
* (SPA) vs. multi-file site hosting (Next.js, or when options.site is set).
|
|
525
|
+
*/
|
|
526
|
+
async deployProject(projectDir, options = {}) {
|
|
527
|
+
const useSite = options.singleFile ? false : options.site || isNextProject(projectDir);
|
|
528
|
+
if (useSite) {
|
|
529
|
+
const { files, entry } = buildAndPackageSite(projectDir, {
|
|
530
|
+
buildCmd: options.buildCmd,
|
|
531
|
+
out: options.out,
|
|
532
|
+
entry: options.entry,
|
|
533
|
+
install: options.install
|
|
534
|
+
});
|
|
535
|
+
return await this.deploySite(files, {
|
|
536
|
+
entry,
|
|
537
|
+
title: options.title ?? null,
|
|
538
|
+
password: options.password ?? null,
|
|
539
|
+
expiresIn: options.expiresIn ?? null
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
return await this.deploy(projectDir, options);
|
|
543
|
+
}
|
|
355
544
|
async get(slug) {
|
|
356
545
|
return await this.request("GET", `/api/v1/pages/${encodeURIComponent(slug)}`);
|
|
357
546
|
}
|
|
@@ -503,22 +692,24 @@ function buildMcpServer(client) {
|
|
|
503
692
|
server.registerTool(
|
|
504
693
|
"deploy_project",
|
|
505
694
|
{
|
|
506
|
-
description: "Build a local frontend project (npm/pnpm/yarn/bun) and
|
|
695
|
+
description: "Build a local frontend project (npm/pnpm/yarn/bun) and deploy the compiled app. Runs the project's build script ON THIS MACHINE. Single-page apps (Vite/CRA) are inlined into one self-contained, script-enabled page; multi-file sites (Next.js static export, auto-detected) are hosted at view.htmlship.com/{slug}/. Both run with relaxed sandboxing so their JS runs in an isolated origin. The owner_key returned is the only credential to update or delete this later \u2014 save it.",
|
|
507
696
|
inputSchema: {
|
|
508
697
|
dir: z.string().describe("Path to the project directory (must contain package.json)."),
|
|
509
698
|
build_cmd: z.string().optional().describe("Override the build command (default: detected build/build:prod script)."),
|
|
510
|
-
out: z.string().optional().describe("Build output directory (default: auto-detect dist
|
|
699
|
+
out: z.string().optional().describe("Build output directory (default: auto-detect dist/, build/, out/)."),
|
|
700
|
+
site: z.boolean().optional().describe("Force multi-file site hosting (auto-detected for Next.js)."),
|
|
511
701
|
install: z.boolean().optional().describe("Run dependency install before building."),
|
|
512
702
|
title: z.string().optional().describe("Optional human-readable title."),
|
|
513
703
|
password: z.string().optional().describe("Optional password required before viewing."),
|
|
514
704
|
expires_in: z.number().int().min(1).max(60 * 24 * 7).optional().describe("Optional TTL in minutes (1\u201310080, i.e. up to 7 days).")
|
|
515
705
|
}
|
|
516
706
|
},
|
|
517
|
-
async ({ dir, build_cmd, out, install, title, password, expires_in }) => {
|
|
707
|
+
async ({ dir, build_cmd, out, site, install, title, password, expires_in }) => {
|
|
518
708
|
try {
|
|
519
|
-
const page = await c.
|
|
709
|
+
const page = await c.deployProject(dir, {
|
|
520
710
|
buildCmd: build_cmd ?? void 0,
|
|
521
711
|
out: out ?? void 0,
|
|
712
|
+
site: site ?? void 0,
|
|
522
713
|
install: install ?? void 0,
|
|
523
714
|
title: title ?? null,
|
|
524
715
|
password: password ?? null,
|
|
@@ -634,7 +825,7 @@ init_errors();
|
|
|
634
825
|
import { createInterface } from "readline/promises";
|
|
635
826
|
|
|
636
827
|
// src/keystore.ts
|
|
637
|
-
import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync, chmodSync } from "fs";
|
|
828
|
+
import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync as writeFileSync2, chmodSync } from "fs";
|
|
638
829
|
import { homedir } from "os";
|
|
639
830
|
import { join as join2 } from "path";
|
|
640
831
|
function createKeyStore(overrideDir) {
|
|
@@ -657,7 +848,7 @@ function createKeyStore(overrideDir) {
|
|
|
657
848
|
for (const key of Object.keys(data).sort()) {
|
|
658
849
|
sorted[key] = data[key];
|
|
659
850
|
}
|
|
660
|
-
|
|
851
|
+
writeFileSync2(file, JSON.stringify(sorted, null, 2), "utf8");
|
|
661
852
|
try {
|
|
662
853
|
chmodSync(file, 384);
|
|
663
854
|
} catch {
|
|
@@ -770,43 +961,69 @@ async function tryClipboardCopy(text) {
|
|
|
770
961
|
}
|
|
771
962
|
|
|
772
963
|
// src/commands/deploy.ts
|
|
773
|
-
var
|
|
964
|
+
var MAX_INLINE_BYTES = 10 * 1024 * 1024;
|
|
774
965
|
function registerDeploy(program) {
|
|
775
966
|
program.command("deploy").description(
|
|
776
|
-
"Build a frontend project and
|
|
777
|
-
).argument("[dir]", "project directory (default: current directory)", ".").option("--build-cmd <cmd>", "Override the build command (default: detected from package.json)").option("--out <dir>", "Build output directory (default: auto-detect dist
|
|
967
|
+
"Build a frontend project and deploy it \u2014 a single-page app as one inlined page, or a multi-file site (Next.js, etc.) at view.htmlship.com/{slug}/."
|
|
968
|
+
).argument("[dir]", "project directory (default: current directory)", ".").option("--build-cmd <cmd>", "Override the build command (default: detected from package.json)").option("--out <dir>", "Build output directory (default: auto-detect dist/, build/, out/)").option("--entry <file>", "Entry HTML within the output dir (default: index.html)").option("--site", "Force multi-file site hosting (auto-detected for Next.js)").option("--single-file", "Force single-file inlining (one self-contained page)").option("--install", "Run dependency install before building").option("--dry-run", "Build, but report a summary instead of publishing").option("--title <title>", "Optional title").option("--password <password>", "Password-protect the page").option("--expires-in <minutes>", "Minutes until expiry (1\u201310080, i.e. up to 7 days)").option("--no-clipboard", "Don't copy URL to clipboard").option("-q, --quiet", "Print only the URL").action(async function(dir, opts) {
|
|
778
969
|
const projectDir = resolve2(dir);
|
|
779
970
|
const log = opts.quiet ? () => {
|
|
780
971
|
} : (m) => process.stderr.write(`${m}
|
|
781
972
|
`);
|
|
782
|
-
const { html, bytes } = buildAndInline(projectDir, {
|
|
783
|
-
buildCmd: opts.buildCmd,
|
|
784
|
-
out: opts.out,
|
|
785
|
-
entry: opts.entry,
|
|
786
|
-
install: opts.install,
|
|
787
|
-
log
|
|
788
|
-
});
|
|
789
|
-
if (bytes > MAX_BYTES) {
|
|
790
|
-
throw new HTMLShipError(
|
|
791
|
-
`inlined page is ${formatBytes(bytes)}, exceeds the ${formatBytes(MAX_BYTES)} limit`
|
|
792
|
-
);
|
|
793
|
-
}
|
|
794
|
-
if (opts.dryRun) {
|
|
795
|
-
log("dry-run: built and inlined OK; not published");
|
|
796
|
-
return;
|
|
797
|
-
}
|
|
798
973
|
const apiUrl = this.parent?.opts()?.apiUrl;
|
|
799
974
|
const client = new HTMLShipClient({ baseUrl: apiUrl });
|
|
975
|
+
const expiresIn = opts.expiresIn ? Number.parseInt(opts.expiresIn, 10) : null;
|
|
976
|
+
const useSite = opts.singleFile ? false : opts.site || isNextProject(projectDir);
|
|
800
977
|
let page;
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
978
|
+
if (useSite) {
|
|
979
|
+
const { files, entry } = buildAndPackageSite(projectDir, {
|
|
980
|
+
buildCmd: opts.buildCmd,
|
|
981
|
+
out: opts.out,
|
|
982
|
+
entry: opts.entry,
|
|
983
|
+
install: opts.install,
|
|
984
|
+
log
|
|
807
985
|
});
|
|
808
|
-
|
|
809
|
-
|
|
986
|
+
if (opts.dryRun) {
|
|
987
|
+
log("dry-run: built and packaged OK; not published");
|
|
988
|
+
return;
|
|
989
|
+
}
|
|
990
|
+
try {
|
|
991
|
+
page = await client.deploySite(files, {
|
|
992
|
+
entry,
|
|
993
|
+
title: opts.title ?? null,
|
|
994
|
+
password: opts.password ?? null,
|
|
995
|
+
expiresIn
|
|
996
|
+
});
|
|
997
|
+
} catch (err) {
|
|
998
|
+
throw new HTMLShipError(`deploy failed: ${err.message}`);
|
|
999
|
+
}
|
|
1000
|
+
} else {
|
|
1001
|
+
const { html, bytes } = buildAndInline(projectDir, {
|
|
1002
|
+
buildCmd: opts.buildCmd,
|
|
1003
|
+
out: opts.out,
|
|
1004
|
+
entry: opts.entry,
|
|
1005
|
+
install: opts.install,
|
|
1006
|
+
log
|
|
1007
|
+
});
|
|
1008
|
+
if (bytes > MAX_INLINE_BYTES) {
|
|
1009
|
+
throw new HTMLShipError(
|
|
1010
|
+
`inlined page is ${formatBytes(bytes)}, exceeds the ${formatBytes(MAX_INLINE_BYTES)} limit \u2014 try --site for multi-file hosting`
|
|
1011
|
+
);
|
|
1012
|
+
}
|
|
1013
|
+
if (opts.dryRun) {
|
|
1014
|
+
log("dry-run: built and inlined OK; not published");
|
|
1015
|
+
return;
|
|
1016
|
+
}
|
|
1017
|
+
try {
|
|
1018
|
+
page = await client.publish(html, {
|
|
1019
|
+
title: opts.title ?? null,
|
|
1020
|
+
password: opts.password ?? null,
|
|
1021
|
+
expiresIn,
|
|
1022
|
+
sandboxMode: "relaxed"
|
|
1023
|
+
});
|
|
1024
|
+
} catch (err) {
|
|
1025
|
+
throw new HTMLShipError(`deploy failed: ${err.message}`);
|
|
1026
|
+
}
|
|
810
1027
|
}
|
|
811
1028
|
const keys = createKeyStore();
|
|
812
1029
|
keys.remember(page.slug, {
|
|
@@ -825,7 +1042,10 @@ function registerDeploy(program) {
|
|
|
825
1042
|
`);
|
|
826
1043
|
process.stderr.write(`owner_key: ${page.owner_key} (saved to ${keys.file})
|
|
827
1044
|
`);
|
|
828
|
-
process.stderr.write(
|
|
1045
|
+
process.stderr.write(
|
|
1046
|
+
`sandbox: relaxed${useSite ? " \xB7 multi-file site" : ""} (scripts run in an isolated origin)
|
|
1047
|
+
`
|
|
1048
|
+
);
|
|
829
1049
|
if (page.expires_at) {
|
|
830
1050
|
process.stderr.write(`expires: ${page.expires_at}
|
|
831
1051
|
`);
|