shiply-cli 0.17.0 → 0.18.1
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/bundle.js +64 -0
- package/dist/index.js +123 -94
- package/dist/ssr-adapters.js +95 -0
- package/package.json +7 -3
- package/skill/SKILL.md +10 -0
package/dist/bundle.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { cp, mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { build } from 'esbuild';
|
|
5
|
+
const BUNDLE_COMPAT_DATE = '2025-05-05';
|
|
6
|
+
/** Cloudflare runtime built-ins (`cloudflare:workers`, …) and `node:*` (provided
|
|
7
|
+
* by `nodejs_compat`) resolve at the edge — they must stay external. Everything
|
|
8
|
+
* else, including the framework server code that an adapter's `_worker.js`
|
|
9
|
+
* imports from OUTSIDE its output dir, gets inlined. */
|
|
10
|
+
const WORKER_EXTERNALS = ['cloudflare:*', 'node:*'];
|
|
11
|
+
/** esbuild-bundle a worker entry into one self-contained ESM module. */
|
|
12
|
+
async function bundleWorker(entryAbs) {
|
|
13
|
+
const result = await build({
|
|
14
|
+
entryPoints: [entryAbs],
|
|
15
|
+
bundle: true,
|
|
16
|
+
format: 'esm',
|
|
17
|
+
platform: 'neutral',
|
|
18
|
+
target: 'es2022',
|
|
19
|
+
external: WORKER_EXTERNALS,
|
|
20
|
+
write: false,
|
|
21
|
+
logLevel: 'silent',
|
|
22
|
+
});
|
|
23
|
+
return result.outputFiles[0].text;
|
|
24
|
+
}
|
|
25
|
+
/** Stage a temp publish dir: a single bundled worker under `.shiply/bundle/`,
|
|
26
|
+
* static assets at the root, plus `.shiply/bundle.json`. Returns the dir; the
|
|
27
|
+
* caller removes it after publish. The existing publish pipeline handles the rest.
|
|
28
|
+
*
|
|
29
|
+
* BOTH tiers esbuild-bundle the entry. Adapter output (SvelteKit, Astro) is NOT
|
|
30
|
+
* self-contained — `_worker.js` imports server code from sibling dirs
|
|
31
|
+
* (`../output/server`, `../cloudflare-tmp`) — so we bundle it exactly as
|
|
32
|
+
* `wrangler deploy` would, rather than upload it as-is. */
|
|
33
|
+
export async function prepareBundle(sourceDir, ssr) {
|
|
34
|
+
const out = await mkdtemp(join(tmpdir(), 'shiply-bundle-'));
|
|
35
|
+
const bundleDir = join(out, '.shiply', 'bundle');
|
|
36
|
+
await mkdir(bundleDir, { recursive: true });
|
|
37
|
+
if (ssr.tier0) {
|
|
38
|
+
// Tier 0: bundle the wrangler `main` entry; optional static assets dir.
|
|
39
|
+
await writeFile(join(bundleDir, 'index.js'), await bundleWorker(join(ssr.assetsDir, ssr.workerMain)));
|
|
40
|
+
if (ssr.staticDir) {
|
|
41
|
+
await cp(join(sourceDir, ssr.staticDir), out, { recursive: true });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
// Tier 1: bundle the adapter's worker entry into one module, then copy the
|
|
46
|
+
// rest of the output dir as static assets (minus the now-bundled entry).
|
|
47
|
+
await writeFile(join(bundleDir, 'index.js'), await bundleWorker(join(ssr.assetsDir, ssr.entry)));
|
|
48
|
+
await cp(ssr.assetsDir, out, { recursive: true });
|
|
49
|
+
// Drop the original worker entry (file, or Astro's `_worker.js/` dir) so it
|
|
50
|
+
// isn't double-served as a static asset.
|
|
51
|
+
await rm(join(out, ssr.entry.split('/')[0]), { recursive: true, force: true });
|
|
52
|
+
}
|
|
53
|
+
await writeManifest(out, {
|
|
54
|
+
framework: ssr.framework,
|
|
55
|
+
entry: 'index.js',
|
|
56
|
+
modules: ['index.js'],
|
|
57
|
+
compatibilityDate: ssr.compatibilityDate ?? BUNDLE_COMPAT_DATE,
|
|
58
|
+
compatibilityFlags: ssr.compatibilityFlags,
|
|
59
|
+
});
|
|
60
|
+
return out;
|
|
61
|
+
}
|
|
62
|
+
async function writeManifest(out, m) {
|
|
63
|
+
await writeFile(join(out, '.shiply', 'bundle.json'), JSON.stringify({ v: 1, ...m }, null, 2));
|
|
64
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -4,9 +4,12 @@ import { dirname, join } from 'node:path';
|
|
|
4
4
|
import { createInterface } from 'node:readline/promises';
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
6
|
import { parseArgs } from 'node:util';
|
|
7
|
+
import { rm } from 'node:fs/promises';
|
|
7
8
|
import { confetti, shouldCelebrate } from './confetti.js';
|
|
8
9
|
import { runDetect } from './detect.js';
|
|
9
10
|
import { resolvePayloadDir } from './framework.js';
|
|
11
|
+
import { detectSsr } from './ssr-adapters.js';
|
|
12
|
+
import { prepareBundle } from './bundle.js';
|
|
10
13
|
import { runClaimVerify } from './claim.js';
|
|
11
14
|
import { dbAttach, dbBranch, dbBranches, dbCreate, dbDelete, dbDeleteBranch, dbLs, dbMerge, dbMigrate, dbSql, } from './db.js';
|
|
12
15
|
import { driveGet, driveLs, drivePublish, drivePut, driveRm } from './drive.js';
|
|
@@ -207,6 +210,7 @@ async function main() {
|
|
|
207
210
|
allowPositionals: true,
|
|
208
211
|
options: {
|
|
209
212
|
spa: { type: 'boolean' },
|
|
213
|
+
'no-ssr': { type: 'boolean' },
|
|
210
214
|
framework: { type: 'string' },
|
|
211
215
|
'claim-token': { type: 'string' },
|
|
212
216
|
key: { type: 'string' },
|
|
@@ -267,108 +271,133 @@ async function main() {
|
|
|
267
271
|
if (!dir)
|
|
268
272
|
throw new Error(`usage: shiply ${cmd} <dir>`);
|
|
269
273
|
const apiKey = values.anonymous ? undefined : (values.key ?? (await loadApiKey()));
|
|
270
|
-
//
|
|
271
|
-
//
|
|
272
|
-
//
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
274
|
+
// SSR / worker-bundle lane: a built Cloudflare-adapter project (SvelteKit,
|
|
275
|
+
// Astro) or a wrangler worker (Hono/raw fetch) ships as a worker bundle.
|
|
276
|
+
// Skipped when --no-ssr or --framework (static override) is passed.
|
|
277
|
+
const ssr = values['no-ssr'] || values.framework ? null : await detectSsr(dir);
|
|
278
|
+
let stagingDir = null;
|
|
279
|
+
// Any static site works — publish the BUILD OUTPUT, not the source.
|
|
280
|
+
// resolvePayloadDir picks the right dir (source as-is, a matched
|
|
281
|
+
// framework's build output, or a pre-built fallback like dist/).
|
|
282
|
+
let publishDir;
|
|
283
|
+
let spa;
|
|
284
|
+
let stateDir;
|
|
285
|
+
if (ssr) {
|
|
286
|
+
stagingDir = await prepareBundle(dir, ssr);
|
|
287
|
+
publishDir = stagingDir;
|
|
288
|
+
spa = false; // SSR: an asset miss must 404 so the worker handles the route
|
|
289
|
+
stateDir = dir; // persist .shiply.json in the SOURCE dir, not the temp dir
|
|
290
|
+
if (!bareJsonFlag)
|
|
291
|
+
console.log(`✔ ${ssr.framework} (SSR) detected — deploying worker bundle`);
|
|
278
292
|
}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
let slug = (alias?.owned && apiKey ? alias.slug : undefined) ?? (state?.owned && apiKey ? state.slug : undefined);
|
|
288
|
-
if (values['new-site']) {
|
|
289
|
-
claimToken = values['claim-token'];
|
|
290
|
-
slug = undefined;
|
|
291
|
-
}
|
|
292
|
-
if (cmd === 'update' && !claimToken && !slug) {
|
|
293
|
-
throw new Error('nothing to update here — publish first (shiply remembers the site in .shiply.json), or pass --claim-token');
|
|
293
|
+
else {
|
|
294
|
+
const resolved = await resolvePayloadDir(dir, { framework: values.framework });
|
|
295
|
+
publishDir = resolved.dir;
|
|
296
|
+
spa = values.spa ?? (resolved.hint?.spa ? true : undefined);
|
|
297
|
+
stateDir = publishDir;
|
|
298
|
+
if (resolved.hint && publishDir !== dir && !bareJsonFlag) {
|
|
299
|
+
console.log(`✔ ${resolved.hint.framework} project detected — publishing ${publishDir}${resolved.hint.spa ? ' with SPA mode' : ''}`);
|
|
300
|
+
}
|
|
294
301
|
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
apiKey
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
//
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
302
|
+
try {
|
|
303
|
+
// same command, same URL: reuse this directory's site automatically
|
|
304
|
+
const state = await readState(stateDir);
|
|
305
|
+
// --as <name>: a stable named preview. The alias registry remembers which
|
|
306
|
+
// slug this name last published to, so the URL is reused every iteration —
|
|
307
|
+
// independent of which directory the bytes came from.
|
|
308
|
+
const aliasName = values.as;
|
|
309
|
+
const alias = aliasName ? await readPreview(aliasName) : null;
|
|
310
|
+
let claimToken = values['claim-token'] ?? alias?.claimToken ?? state?.claimToken;
|
|
311
|
+
let slug = (alias?.owned && apiKey ? alias.slug : undefined) ?? (state?.owned && apiKey ? state.slug : undefined);
|
|
312
|
+
if (values['new-site']) {
|
|
313
|
+
claimToken = values['claim-token'];
|
|
314
|
+
slug = undefined;
|
|
315
|
+
}
|
|
316
|
+
if (cmd === 'update' && !claimToken && !slug) {
|
|
317
|
+
throw new Error('nothing to update here — publish first (shiply remembers the site in .shiply.json), or pass --claim-token');
|
|
318
|
+
}
|
|
319
|
+
const updating = Boolean(claimToken || slug);
|
|
320
|
+
// Auto-attach a previously-created database on publish. Anonymous
|
|
321
|
+
// publishes can't attach (no auth subject) — silently skipped server-side.
|
|
322
|
+
const attachDatabaseId = state?.databaseId && apiKey ? state.databaseId : undefined;
|
|
323
|
+
// Opt-in: bind THIS publish version to a Neon branch's URI. Server
|
|
324
|
+
// validates ownership + that the branch lives under a project attached
|
|
325
|
+
// to this site; a bogus/foreign id silently falls back to the parent URI.
|
|
326
|
+
const previewBranchDbId = values['preview-branch'];
|
|
327
|
+
const res = await publish(publishDir, {
|
|
328
|
+
apiKey,
|
|
329
|
+
base: values.base,
|
|
330
|
+
spaMode: spa,
|
|
331
|
+
claimToken,
|
|
332
|
+
slug,
|
|
333
|
+
attachDatabaseId,
|
|
334
|
+
previewBranchDbId,
|
|
328
335
|
});
|
|
329
|
-
|
|
330
|
-
if (bareJsonFlag) {
|
|
331
|
-
// Single-line machine output — no banners, no confetti, no readiness chatter.
|
|
332
|
-
process.stdout.write(`${JSON.stringify({
|
|
336
|
+
await writeState(stateDir, {
|
|
333
337
|
slug: res.slug,
|
|
334
338
|
siteUrl: res.siteUrl,
|
|
335
|
-
siteId
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
})
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
const host = new URL(res.siteUrl).hostname;
|
|
350
|
-
const live = await fetch(res.siteUrl, { method: 'GET', redirect: 'follow' });
|
|
351
|
-
void live.body?.cancel();
|
|
352
|
-
if (live.status < 400) {
|
|
353
|
-
console.log(`SITE_READY url=${res.siteUrl} status=${live.status}`);
|
|
354
|
-
if (shouldCelebrate(Boolean(values['no-confetti'])))
|
|
355
|
-
console.log(confetti(`⛵ ${host} IS LIVE ⛵`));
|
|
339
|
+
// siteId only present on 0.14.4+ servers — prefer the new response,
|
|
340
|
+
// fall back to a prior state file so re-publishes don't drop it.
|
|
341
|
+
...(res.siteId ? { siteId: res.siteId } : state?.siteId ? { siteId: state.siteId } : {}),
|
|
342
|
+
...(res.claimToken ? { claimToken: res.claimToken } : state?.claimToken && updating ? { claimToken: state.claimToken } : {}),
|
|
343
|
+
owned: !res.anonymous,
|
|
344
|
+
...(state?.databaseId ? { databaseId: state.databaseId } : {}),
|
|
345
|
+
});
|
|
346
|
+
if (aliasName) {
|
|
347
|
+
await writePreview(aliasName, {
|
|
348
|
+
slug: res.slug,
|
|
349
|
+
owned: !res.anonymous,
|
|
350
|
+
...(res.siteId ? { siteId: res.siteId } : {}),
|
|
351
|
+
...(res.claimToken ? { claimToken: res.claimToken } : alias?.claimToken ? { claimToken: alias.claimToken } : {}),
|
|
352
|
+
});
|
|
356
353
|
}
|
|
354
|
+
if (bareJsonFlag) {
|
|
355
|
+
// Single-line machine output — no banners, no confetti, no readiness chatter.
|
|
356
|
+
process.stdout.write(`${JSON.stringify({
|
|
357
|
+
slug: res.slug,
|
|
358
|
+
siteUrl: res.siteUrl,
|
|
359
|
+
siteId: res.siteId ?? null,
|
|
360
|
+
uploaded: res.uploaded,
|
|
361
|
+
skipped: res.skipped,
|
|
362
|
+
anonymous: res.anonymous,
|
|
363
|
+
expiresAt: res.expiresAt,
|
|
364
|
+
updated: updating,
|
|
365
|
+
})}\n`);
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
const skipped = res.skipped > 0 ? ` (${res.skipped} unchanged, skipped)` : '';
|
|
369
|
+
console.log(`✔ ${updating ? `updated ${res.slug} in place` : 'published'} — ${res.uploaded} file${res.uploaded === 1 ? '' : 's'}${skipped}`);
|
|
370
|
+
console.log(`\n ${res.siteUrl}\n`);
|
|
371
|
+
// confirm the site actually serves, then celebrate
|
|
372
|
+
try {
|
|
373
|
+
const host = new URL(res.siteUrl).hostname;
|
|
374
|
+
const live = await fetch(res.siteUrl, { method: 'GET', redirect: 'follow' });
|
|
375
|
+
void live.body?.cancel();
|
|
376
|
+
if (live.status < 400) {
|
|
377
|
+
console.log(`SITE_READY url=${res.siteUrl} status=${live.status}`);
|
|
378
|
+
if (shouldCelebrate(Boolean(values['no-confetti'])))
|
|
379
|
+
console.log(confetti(`⛵ ${host} IS LIVE ⛵`));
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
catch {
|
|
383
|
+
/* serving check is best-effort */
|
|
384
|
+
}
|
|
385
|
+
if (!updating) {
|
|
386
|
+
console.log(` saved .shiply.json — run \`shiply publish ${dir}\` again to UPDATE this same site`);
|
|
387
|
+
console.log(` (gitignore .shiply.json if this folder is public — it can update the site)`);
|
|
388
|
+
}
|
|
389
|
+
if (res.anonymous) {
|
|
390
|
+
console.log(` anonymous site — expires ${res.expiresAt ?? 'in 24h'}`);
|
|
391
|
+
if (res.claimUrl)
|
|
392
|
+
console.log(` claim it to KEEP it (free account): ${res.claimUrl}`);
|
|
393
|
+
console.log(` tip: run \`shiply login\` first to publish permanent sites`);
|
|
394
|
+
}
|
|
395
|
+
return;
|
|
357
396
|
}
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
if (!updating) {
|
|
362
|
-
console.log(` saved .shiply.json — run \`shiply publish ${dir}\` again to UPDATE this same site`);
|
|
363
|
-
console.log(` (gitignore .shiply.json if this folder is public — it can update the site)`);
|
|
364
|
-
}
|
|
365
|
-
if (res.anonymous) {
|
|
366
|
-
console.log(` anonymous site — expires ${res.expiresAt ?? 'in 24h'}`);
|
|
367
|
-
if (res.claimUrl)
|
|
368
|
-
console.log(` claim it to KEEP it (free account): ${res.claimUrl}`);
|
|
369
|
-
console.log(` tip: run \`shiply login\` first to publish permanent sites`);
|
|
397
|
+
finally {
|
|
398
|
+
if (stagingDir)
|
|
399
|
+
await rm(stagingDir, { recursive: true, force: true });
|
|
370
400
|
}
|
|
371
|
-
return;
|
|
372
401
|
}
|
|
373
402
|
case 'status': {
|
|
374
403
|
if (!dir) {
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { readFile, readdir, stat } from 'node:fs/promises';
|
|
2
|
+
import { join, relative } from 'node:path';
|
|
3
|
+
import { parse as parseToml } from 'smol-toml';
|
|
4
|
+
const NODE_COMPAT = ['nodejs_compat'];
|
|
5
|
+
const exists = (p) => stat(p).then(() => true, () => false);
|
|
6
|
+
async function deps(dir) {
|
|
7
|
+
try {
|
|
8
|
+
const pkg = JSON.parse(await readFile(join(dir, 'package.json'), 'utf8'));
|
|
9
|
+
return { ...pkg.dependencies, ...pkg.devDependencies };
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return {};
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/** Recursively list every file under `root`, as posix paths relative to `base`. */
|
|
16
|
+
async function listFiles(root, base = root) {
|
|
17
|
+
const out = [];
|
|
18
|
+
for (const e of await readdir(root, { withFileTypes: true })) {
|
|
19
|
+
const abs = join(root, e.name);
|
|
20
|
+
if (e.isDirectory())
|
|
21
|
+
out.push(...(await listFiles(abs, base)));
|
|
22
|
+
else
|
|
23
|
+
out.push(relative(base, abs).split('\\').join('/'));
|
|
24
|
+
}
|
|
25
|
+
return out;
|
|
26
|
+
}
|
|
27
|
+
/** Detect a built SSR project (Cloudflare-adapter output or a wrangler worker)
|
|
28
|
+
* in `dir`. Returns null for plain static dirs. */
|
|
29
|
+
export async function detectSsr(dir) {
|
|
30
|
+
const d = await deps(dir);
|
|
31
|
+
// Tier 1: SvelteKit (single _worker.js + sibling assets)
|
|
32
|
+
if ('@sveltejs/adapter-cloudflare' in d) {
|
|
33
|
+
const out = join(dir, '.svelte-kit', 'cloudflare');
|
|
34
|
+
if (await exists(join(out, '_worker.js'))) {
|
|
35
|
+
return {
|
|
36
|
+
framework: 'sveltekit',
|
|
37
|
+
assetsDir: out,
|
|
38
|
+
entry: '_worker.js',
|
|
39
|
+
modules: ['_worker.js'],
|
|
40
|
+
compatibilityFlags: NODE_COMPAT,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
// Tier 1: Astro (dist/_worker.js/ multi-module + dist assets)
|
|
45
|
+
if ('@astrojs/cloudflare' in d) {
|
|
46
|
+
const out = join(dir, 'dist');
|
|
47
|
+
const workerDir = join(out, '_worker.js');
|
|
48
|
+
if (await exists(join(workerDir, 'index.js'))) {
|
|
49
|
+
const modules = (await listFiles(workerDir))
|
|
50
|
+
.filter((p) => p.endsWith('.js'))
|
|
51
|
+
.map((p) => `_worker.js/${p}`);
|
|
52
|
+
return {
|
|
53
|
+
framework: 'astro',
|
|
54
|
+
assetsDir: out,
|
|
55
|
+
entry: '_worker.js/index.js',
|
|
56
|
+
modules,
|
|
57
|
+
compatibilityFlags: NODE_COMPAT,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
// Tier 0: generic Cloudflare Worker via wrangler config
|
|
62
|
+
return detectWrangler(dir);
|
|
63
|
+
}
|
|
64
|
+
async function detectWrangler(dir) {
|
|
65
|
+
let cfg = null;
|
|
66
|
+
if (await exists(join(dir, 'wrangler.toml'))) {
|
|
67
|
+
cfg = parseToml(await readFile(join(dir, 'wrangler.toml'), 'utf8'));
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
for (const f of ['wrangler.json', 'wrangler.jsonc']) {
|
|
71
|
+
if (await exists(join(dir, f))) {
|
|
72
|
+
cfg = JSON.parse(stripJsonc(await readFile(join(dir, f), 'utf8')));
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (!cfg || typeof cfg.main !== 'string')
|
|
78
|
+
return null;
|
|
79
|
+
const staticDir = cfg.assets?.directory ?? cfg.site?.bucket;
|
|
80
|
+
return {
|
|
81
|
+
framework: 'cloudflare-worker',
|
|
82
|
+
assetsDir: dir,
|
|
83
|
+
tier0: true,
|
|
84
|
+
workerMain: cfg.main,
|
|
85
|
+
staticDir: typeof staticDir === 'string' ? staticDir : undefined,
|
|
86
|
+
compatibilityDate: typeof cfg.compatibility_date === 'string' ? cfg.compatibility_date : undefined,
|
|
87
|
+
compatibilityFlags: Array.isArray(cfg.compatibility_flags) && cfg.compatibility_flags.length
|
|
88
|
+
? cfg.compatibility_flags
|
|
89
|
+
: NODE_COMPAT,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
/** Strip // and block comments so wrangler.jsonc parses with JSON.parse. */
|
|
93
|
+
function stripJsonc(s) {
|
|
94
|
+
return s.replace(/\/\*[\s\S]*?\*\//g, '').replace(/(^|[^:])\/\/.*$/gm, '$1');
|
|
95
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shiply-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.1",
|
|
4
4
|
"description": "Publish static sites to shiply.now from the command line — instant web hosting for agents.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -31,8 +31,12 @@
|
|
|
31
31
|
],
|
|
32
32
|
"homepage": "https://shiply.now",
|
|
33
33
|
"devDependencies": {
|
|
34
|
+
"@types/node": "^22.15.0",
|
|
34
35
|
"typescript": "^5.8.0",
|
|
35
|
-
"vitest": "^3.1.0"
|
|
36
|
-
|
|
36
|
+
"vitest": "^3.1.0"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"esbuild": "^0.25.12",
|
|
40
|
+
"smol-toml": "^1.7.0"
|
|
37
41
|
}
|
|
38
42
|
}
|
package/skill/SKILL.md
CHANGED
|
@@ -144,6 +144,16 @@ so repeat publishes reuse the URL automatically. `--new-site` opts out.
|
|
|
144
144
|
Gitignore `.shiply.json` in public repos. Parse `SITE_READY` / `SSL_READY`
|
|
145
145
|
lines for automation; exit code 0 = ready.
|
|
146
146
|
|
|
147
|
+
### SSR / server frameworks (live: SvelteKit, Astro, Hono/raw Workers)
|
|
148
|
+
|
|
149
|
+
`shiply publish` auto-detects a built Cloudflare-adapter project (SvelteKit
|
|
150
|
+
`@sveltejs/adapter-cloudflare`, Astro `@astrojs/cloudflare`) or a wrangler
|
|
151
|
+
worker (`wrangler.toml` with `main` — Hono, itty-router, raw `fetch`) and
|
|
152
|
+
deploys the worker bundle with `nodejs_compat`, serving static assets from the
|
|
153
|
+
edge. **Build first** (`npm run build`), then `shiply publish .`. Requires
|
|
154
|
+
sign-in + Developer plan. Force the static path with `--framework=<name>`, or
|
|
155
|
+
skip SSR detection entirely with `--no-ssr`. Next.js: waitlist (not yet live).
|
|
156
|
+
|
|
147
157
|
## Manage & verify sites (CLI)
|
|
148
158
|
|
|
149
159
|
List, delete, roll back, verify, and promote owned sites — all over the same
|