shiply-cli 0.18.1 → 0.18.3

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  import { cp, mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';
2
2
  import { tmpdir } from 'node:os';
3
- import { join } from 'node:path';
3
+ import { dirname, isAbsolute, join, relative } from 'node:path';
4
4
  import { build } from 'esbuild';
5
5
  const BUNDLE_COMPAT_DATE = '2025-05-05';
6
6
  /** Cloudflare runtime built-ins (`cloudflare:workers`, …) and `node:*` (provided
@@ -8,10 +8,12 @@ const BUNDLE_COMPAT_DATE = '2025-05-05';
8
8
  * else, including the framework server code that an adapter's `_worker.js`
9
9
  * imports from OUTSIDE its output dir, gets inlined. */
10
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],
11
+ /** esbuild-bundle a worker entry into one self-contained ESM module.
12
+ * When `wrapDefaultFetch` is set, the entry exports a NAMED `fetch` (Qwik's
13
+ * Pages adapter); we bundle a synthetic entry that re-exports it as the Workers
14
+ * `export default { fetch }` shape. */
15
+ async function bundleWorker(entryAbs, wrapDefaultFetch = false) {
16
+ const common = {
15
17
  bundle: true,
16
18
  format: 'esm',
17
19
  platform: 'neutral',
@@ -19,7 +21,17 @@ async function bundleWorker(entryAbs) {
19
21
  external: WORKER_EXTERNALS,
20
22
  write: false,
21
23
  logLevel: 'silent',
22
- });
24
+ };
25
+ const result = wrapDefaultFetch
26
+ ? await build({
27
+ ...common,
28
+ stdin: {
29
+ contents: `import { fetch } from ${JSON.stringify(entryAbs.split('\\').join('/'))}\nexport default { fetch }\n`,
30
+ resolveDir: dirname(entryAbs),
31
+ loader: 'js',
32
+ },
33
+ })
34
+ : await build({ ...common, entryPoints: [entryAbs] });
23
35
  return result.outputFiles[0].text;
24
36
  }
25
37
  /** Stage a temp publish dir: a single bundled worker under `.shiply/bundle/`,
@@ -34,22 +46,19 @@ export async function prepareBundle(sourceDir, ssr) {
34
46
  const out = await mkdtemp(join(tmpdir(), 'shiply-bundle-'));
35
47
  const bundleDir = join(out, '.shiply', 'bundle');
36
48
  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 });
49
+ // Bundle the worker entry into one self-contained ESM module.
50
+ await writeFile(join(bundleDir, 'index.js'), await bundleWorker(join(sourceDir, ssr.workerEntry), ssr.wrapDefaultFetch));
51
+ // Copy static assets, if any. The worker entry may live inside the assets dir
52
+ // (SvelteKit, Astro) or in a sibling (Nitro's .output/server vs .output/public);
53
+ // when inside, drop it so it isn't double-served.
54
+ if (ssr.assetsDir) {
55
+ const assetsAbs = join(sourceDir, ssr.assetsDir);
56
+ await cp(assetsAbs, out, { recursive: true });
57
+ const entryRel = relative(assetsAbs, join(sourceDir, ssr.workerEntry));
58
+ if (entryRel && !entryRel.startsWith('..') && !isAbsolute(entryRel)) {
59
+ await rm(join(out, entryRel.split(/[\\/]/)[0]), { recursive: true, force: true });
42
60
  }
43
61
  }
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
62
  await writeManifest(out, {
54
63
  framework: ssr.framework,
55
64
  entry: 'index.js',
@@ -1,5 +1,5 @@
1
- import { readFile, readdir, stat } from 'node:fs/promises';
2
- import { join, relative } from 'node:path';
1
+ import { readFile, stat } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
3
  import { parse as parseToml } from 'smol-toml';
4
4
  const NODE_COMPAT = ['nodejs_compat'];
5
5
  const exists = (p) => stat(p).then(() => true, () => false);
@@ -12,55 +12,72 @@ async function deps(dir) {
12
12
  return {};
13
13
  }
14
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. */
15
+ /** Detect a built SSR project in `dir` (framework adapter output or a wrangler
16
+ * worker). Returns null for plain static dirs. Framework-specific detectors
17
+ * run before the generic wrangler one. */
29
18
  export async function detectSsr(dir) {
30
19
  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
- }
20
+ // SvelteKit — adapter-cloudflare: `_worker.js` + sibling assets.
21
+ if ('@sveltejs/adapter-cloudflare' in d && (await exists(join(dir, '.svelte-kit/cloudflare/_worker.js')))) {
22
+ return {
23
+ framework: 'sveltekit',
24
+ workerEntry: '.svelte-kit/cloudflare/_worker.js',
25
+ assetsDir: '.svelte-kit/cloudflare',
26
+ compatibilityFlags: NODE_COMPAT,
27
+ };
43
28
  }
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
- }
29
+ // Astro — @astrojs/cloudflare: `dist/_worker.js/index.js` + dist assets.
30
+ if ('@astrojs/cloudflare' in d && (await exists(join(dir, 'dist/_worker.js/index.js')))) {
31
+ return {
32
+ framework: 'astro',
33
+ workerEntry: 'dist/_worker.js/index.js',
34
+ assetsDir: 'dist',
35
+ compatibilityFlags: NODE_COMPAT,
36
+ };
60
37
  }
61
- // Tier 0: generic Cloudflare Worker via wrangler config
38
+ // Nitro (Nuxt, SolidStart, Analog, TanStack, …) cloudflare preset:
39
+ // `.output/server/index.mjs` worker + `.output/public/` assets (separate dir).
40
+ const nitro = await detectNitro(dir);
41
+ if (nitro)
42
+ return nitro;
43
+ // Qwik City — cloudflare-pages adapter: `server/entry.cloudflare-pages.js`
44
+ // (named `fetch` export) + `dist/` client assets. Entry is self-contained.
45
+ if ('@builder.io/qwik-city' in d &&
46
+ (await exists(join(dir, 'server/entry.cloudflare-pages.js')))) {
47
+ return {
48
+ framework: 'qwik',
49
+ workerEntry: 'server/entry.cloudflare-pages.js',
50
+ assetsDir: 'dist',
51
+ wrapDefaultFetch: true,
52
+ compatibilityFlags: NODE_COMPAT,
53
+ };
54
+ }
55
+ // Generic Cloudflare Worker via a wrangler config (Hono, itty, raw fetch).
62
56
  return detectWrangler(dir);
63
57
  }
58
+ async function detectNitro(dir) {
59
+ const nitroJson = join(dir, '.output', 'nitro.json');
60
+ if (!(await exists(nitroJson)))
61
+ return null;
62
+ let preset = '';
63
+ try {
64
+ preset = String(JSON.parse(await readFile(nitroJson, 'utf8')).preset ?? '');
65
+ }
66
+ catch {
67
+ return null;
68
+ }
69
+ // 'cloudflare-module' / 'cloudflare_module' / 'cloudflare-pages' all start with it.
70
+ if (!preset.startsWith('cloudflare'))
71
+ return null;
72
+ if (!(await exists(join(dir, '.output/server/index.mjs'))))
73
+ return null;
74
+ return {
75
+ framework: 'nitro',
76
+ workerEntry: '.output/server/index.mjs',
77
+ assetsDir: '.output/public',
78
+ compatibilityFlags: NODE_COMPAT,
79
+ };
80
+ }
64
81
  async function detectWrangler(dir) {
65
82
  let cfg = null;
66
83
  if (await exists(join(dir, 'wrangler.toml'))) {
@@ -79,10 +96,8 @@ async function detectWrangler(dir) {
79
96
  const staticDir = cfg.assets?.directory ?? cfg.site?.bucket;
80
97
  return {
81
98
  framework: 'cloudflare-worker',
82
- assetsDir: dir,
83
- tier0: true,
84
- workerMain: cfg.main,
85
- staticDir: typeof staticDir === 'string' ? staticDir : undefined,
99
+ workerEntry: cfg.main,
100
+ assetsDir: typeof staticDir === 'string' ? staticDir : undefined,
86
101
  compatibilityDate: typeof cfg.compatibility_date === 'string' ? cfg.compatibility_date : undefined,
87
102
  compatibilityFlags: Array.isArray(cfg.compatibility_flags) && cfg.compatibility_flags.length
88
103
  ? cfg.compatibility_flags
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shiply-cli",
3
- "version": "0.18.1",
3
+ "version": "0.18.3",
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",