@wervt/cli 0.2.17 → 0.2.18

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/README.md CHANGED
@@ -15,6 +15,10 @@ vp exec wervt access my-app --public # or --add friend@example.com
15
15
  vp exec wervt logs my-app
16
16
  ```
17
17
 
18
+ Nitro apps without Nuxt use `@wervt/nitro`. A static site sets `"build": "… && wervt build --static dist"`
19
+ in package.json (any `export default { fetch }` module works too, without `--static`); see
20
+ [Platform contract](https://wervt.app/api/platform-contract#wervt-build).
21
+
18
22
  Without [Vite+](https://viteplus.dev), use `npx @wervt/cli` and `pnpm exec wervt`. `wervt --help`
19
23
  lists every command: `apps`, `releases`, `audit`, `rollback`, `activate`, `env`, `tokens`, `token`, `members`,
20
24
  `tasks`, `stats`, `delete`, `whoami`, … Docs: [wervt.app](https://wervt.app/getting-started/quickstart).
package/dist/index.mjs CHANGED
@@ -1,13 +1,160 @@
1
+ import { builtinModules } from "node:module";
1
2
  import { existsSync } from "node:fs";
2
- import { appendFile, mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
3
+ import { appendFile, cp, mkdir, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
3
4
  import { homedir, hostname } from "node:os";
4
- import { dirname, join, relative, resolve } from "node:path";
5
+ import { dirname, join, relative, resolve, sep } from "node:path";
5
6
  import { styleText } from "node:util";
6
7
  import * as p from "@clack/prompts";
7
8
  import { defineCommand, runMain } from "citty";
8
9
  import { detectPackageManager } from "nypm";
9
10
  import { x } from "tinyexec";
10
11
  import { packTar } from "modern-tar";
12
+ // ../core/src/manifest.ts
13
+ /** The bundle's `wervt-bundle.json`, next to `server/` and `public/`. */
14
+ const BUNDLE_MANIFEST = "wervt-bundle.json";
15
+ // src/static-server.ts
16
+ /** The source of `server/index.mjs`. */
17
+ function staticServer({ pages, fallback }) {
18
+ return `const pages = ${JSON.stringify(pages)}
19
+ const fallback = ${JSON.stringify(fallback ?? null)}
20
+ const html = (body, status = 200) =>
21
+ new Response(body, { status, headers: { 'content-type': 'text/html; charset=utf-8' } })
22
+
23
+ export default {
24
+ fetch(request) {
25
+ let path
26
+ try {
27
+ path = decodeURIComponent(new URL(request.url).pathname).replace(/^\\/+|\\/+$/g, '')
28
+ } catch {
29
+ return html(pages['404.html'] ?? 'Not found', 404)
30
+ }
31
+ for (const page of path ? [path + '/index.html', path + '.html'] : ['index.html'])
32
+ if (Object.hasOwn(pages, page)) return html(pages[page])
33
+ const wantsHtml = (request.headers.get('accept') ?? '').includes('text/html')
34
+ if (wantsHtml && fallback && Object.hasOwn(pages, fallback)) return html(pages[fallback])
35
+ return html(pages['404.html'] ?? 'Not found', 404)
36
+ },
37
+ }
38
+ `;
39
+ }
40
+ // src/build.ts
41
+ const ENTRIES = [
42
+ "server.ts",
43
+ "src/server.ts",
44
+ "src/index.ts",
45
+ "server.js",
46
+ "src/index.js"
47
+ ];
48
+ const exists$1 = (path) => stat(path).then(() => true, () => false);
49
+ async function walk$1(dir) {
50
+ const entries = await readdir(dir, { withFileTypes: true });
51
+ return (await Promise.all(entries.map(async (e) => e.isDirectory() ? walk$1(join(dir, e.name)) : [join(dir, e.name)]))).flat();
52
+ }
53
+ /** The app's `wervt` settings from its package.json. */
54
+ async function readBuildConfig(appDir) {
55
+ return JSON.parse(await readFile(join(appDir, "package.json"), "utf8").catch(() => "{}")).wervt ?? {};
56
+ }
57
+ async function build$1(appDir, options = {}) {
58
+ const config = {
59
+ ...await readBuildConfig(appDir),
60
+ ...definedOnly(options)
61
+ };
62
+ const out = join(appDir, options.out ?? ".output");
63
+ await rm(out, {
64
+ recursive: true,
65
+ force: true
66
+ });
67
+ await mkdir(join(out, "server"), { recursive: true });
68
+ let kind;
69
+ if (config.static) {
70
+ kind = "static";
71
+ await buildStatic(join(appDir, config.static), out, config.fallback);
72
+ } else {
73
+ kind = "server";
74
+ const entry = config.entry ?? await findEntry(appDir);
75
+ if (!entry) throw new Error(`No server entry. Add one of ${ENTRIES.join(", ")}, or set "wervt": { "entry" } in package.json (or "static" for a static site).`);
76
+ await bundleServer(join(appDir, entry), join(out, "server/index.mjs"));
77
+ const publicDir = join(appDir, config.public ?? "public");
78
+ if (await exists$1(publicDir)) await cp(publicDir, join(out, "public"), { recursive: true });
79
+ }
80
+ const manifest = bundleManifest(config, kind);
81
+ await writeFile(join(out, BUNDLE_MANIFEST), `${JSON.stringify(manifest, null, 2)}\n`);
82
+ return {
83
+ kind,
84
+ out,
85
+ manifest
86
+ };
87
+ }
88
+ async function findEntry(appDir) {
89
+ for (const entry of ENTRIES) if (await exists$1(join(appDir, entry))) return entry;
90
+ }
91
+ function bundleManifest(config, kind) {
92
+ const manifest = {
93
+ version: 2,
94
+ framework: config.framework ?? (kind === "static" ? "static" : "fetch")
95
+ };
96
+ if (config.database !== void 0) manifest.database = config.database;
97
+ if (config.immutable || config.noCache) manifest.public = {
98
+ immutable: config.immutable,
99
+ noCache: config.noCache
100
+ };
101
+ if (config.tasks || config.schedules) manifest.tasks = {
102
+ tasks: config.tasks ?? [],
103
+ schedules: config.schedules ?? [],
104
+ ...config.timezone ? { timezone: config.timezone } : {}
105
+ };
106
+ if (config.shapes) manifest.shapes = config.shapes;
107
+ if (config.preview) manifest.preview = config.preview;
108
+ if (config.brand) manifest.brand = config.brand;
109
+ return manifest;
110
+ }
111
+ async function buildStatic(siteDir, out, fallback = "index.html") {
112
+ if (!await exists$1(siteDir)) throw new Error(`No ${siteDir} to deploy. Build the site first.`);
113
+ await cp(siteDir, join(out, "public"), { recursive: true });
114
+ const pages = {};
115
+ for (const file of await walk$1(siteDir)) if (file.endsWith(".html")) pages[relative(siteDir, file).split(sep).join("/")] = await readFile(file, "utf8");
116
+ await writeFile(join(out, "server/index.mjs"), staticServer({
117
+ pages,
118
+ fallback: fallback || void 0
119
+ }));
120
+ }
121
+ const builtins = new Set(builtinModules);
122
+ /**
123
+ * One ES module for Supabase Edge Runtime (Deno): every dependency inlined, Node built-ins
124
+ * imported with `node:` (Deno rejects bare `crypto`), and the Node globals Deno lacks injected.
125
+ */
126
+ async function bundleServer(entry, file) {
127
+ const { build: rolldown } = await import("rolldown");
128
+ await rolldown({
129
+ input: entry,
130
+ platform: "node",
131
+ logLevel: "warn",
132
+ plugins: [{
133
+ name: "wervt:node-prefix",
134
+ resolveId(id) {
135
+ const bare = id.startsWith("node:") ? id.slice(5) : id;
136
+ if (builtins.has(bare) || builtins.has(bare.split("/")[0])) return {
137
+ id: `node:${bare}`,
138
+ external: true
139
+ };
140
+ return null;
141
+ }
142
+ }],
143
+ transform: { inject: {
144
+ Buffer: ["node:buffer", "Buffer"],
145
+ setImmediate: ["node:timers", "setImmediate"],
146
+ clearImmediate: ["node:timers", "clearImmediate"]
147
+ } },
148
+ output: {
149
+ file,
150
+ format: "esm",
151
+ codeSplitting: false
152
+ }
153
+ });
154
+ }
155
+ function definedOnly(value) {
156
+ return Object.fromEntries(Object.entries(value).filter(([, v]) => v !== void 0));
157
+ }
11
158
  // src/bundle.ts
12
159
  /** Files under `dir`. Follows symlinks: Nitro can leave traced dependencies in
13
160
  * server/node_modules as links into the local pnpm store, which won't exist on the server. */
@@ -23,14 +170,18 @@ async function gzip(data) {
23
170
  return new Uint8Array(await new Response(stream).arrayBuffer());
24
171
  }
25
172
  const exists = (path) => stat(path).then(() => true, () => false);
26
- /** Packs `.output/server`, `.output/public` and `drizzle/` into the bundle the control plane expects. */
27
- async function createBundle(appDir) {
173
+ /**
174
+ * Packs the build output's `server/`, `public/` and `wervt-bundle.json` (in `out`, `.output` by
175
+ * default) and the app's `drizzle/` into the bundle the control plane expects.
176
+ */
177
+ async function createBundle(appDir, out = ".output") {
178
+ const outDir = join(appDir, out);
28
179
  const roots = [
29
- [join(appDir, ".output/server"), "server"],
30
- [join(appDir, ".output/public"), "public"],
180
+ [join(outDir, "server"), "server"],
181
+ [join(outDir, "public"), "public"],
31
182
  [join(appDir, "drizzle"), "drizzle"]
32
183
  ];
33
- if (!await exists(roots[0][0])) throw new Error("No .output/server. Build the app first.");
184
+ if (!await exists(roots[0][0])) throw new Error(`No ${join(out, "server")}. Build the app first.`);
34
185
  const files = [];
35
186
  for (const [dir, prefix] of roots) {
36
187
  if (!await exists(dir)) continue;
@@ -46,6 +197,17 @@ async function createBundle(appDir) {
46
197
  });
47
198
  }
48
199
  }
200
+ const manifest = join(outDir, BUNDLE_MANIFEST);
201
+ if (await exists(manifest)) {
202
+ const data = await readFile(manifest);
203
+ files.push({
204
+ header: {
205
+ name: BUNDLE_MANIFEST,
206
+ size: data.byteLength
207
+ },
208
+ body: data
209
+ });
210
+ }
49
211
  return {
50
212
  data: await gzip(await packTar(files)),
51
213
  files: files.length
@@ -287,6 +449,10 @@ const deploy = defineCommand({
287
449
  default: true,
288
450
  description: "Build before deploying"
289
451
  },
452
+ out: {
453
+ type: "string",
454
+ description: "Build output to pack (default: .output)"
455
+ },
290
456
  check: {
291
457
  type: "string",
292
458
  description: "Pages to request once the release started, a 5xx fails (default: /). Shape routes are checked too; --check= turns all checks off"
@@ -319,7 +485,7 @@ const deploy = defineCommand({
319
485
  }
320
486
  const spinner = progress();
321
487
  spinner.start("Packing");
322
- const bundle = await createBundle(dir);
488
+ const bundle = await createBundle(dir, args.out);
323
489
  spinner.stop(`Packed ${bundle.files} files (${(bundle.data.byteLength / 1024).toFixed(0)} kB)`);
324
490
  spinner.start("Uploading");
325
491
  const source = await deploySource(dir);
@@ -1669,6 +1835,47 @@ const login = defineCommand({
1669
1835
  outro(`Signed in to ${url} (saved to ${file})`);
1670
1836
  }
1671
1837
  });
1838
+ const build = defineCommand({
1839
+ meta: {
1840
+ name: "build",
1841
+ description: "Build a fetch handler (Hono, plain `export default { fetch }`) or a static site into a bundle"
1842
+ },
1843
+ args: {
1844
+ dir: {
1845
+ type: "positional",
1846
+ required: false,
1847
+ description: "App directory (default: .)"
1848
+ },
1849
+ entry: {
1850
+ type: "string",
1851
+ description: "Server module (default: server.ts, src/server.ts, src/index.ts)"
1852
+ },
1853
+ static: {
1854
+ type: "string",
1855
+ description: "Folder of a static site to deploy instead of a server, e.g. dist"
1856
+ },
1857
+ fallback: {
1858
+ type: "string",
1859
+ description: "Static site: page for unknown paths (default: index.html, for SPAs); --fallback= for none"
1860
+ },
1861
+ out: {
1862
+ type: "string",
1863
+ description: "Output directory (default: .output)"
1864
+ }
1865
+ },
1866
+ async run({ args }) {
1867
+ const dir = resolveAppDir(args.dir);
1868
+ const started = performance.now();
1869
+ const result = await build$1(dir, {
1870
+ entry: args.entry,
1871
+ static: args.static,
1872
+ fallback: args.fallback === "" ? false : args.fallback,
1873
+ out: args.out
1874
+ });
1875
+ const ms = Math.round(performance.now() - started);
1876
+ console.log(`Built ${result.kind === "static" ? "a static site" : "the server"} into ${result.out} in ${ms} ms (${result.manifest.framework})`);
1877
+ }
1878
+ });
1672
1879
  runMain(defineCommand({
1673
1880
  meta: {
1674
1881
  name: "wervt",
@@ -1676,6 +1883,7 @@ runMain(defineCommand({
1676
1883
  },
1677
1884
  subCommands: {
1678
1885
  deploy,
1886
+ build,
1679
1887
  apps,
1680
1888
  releases,
1681
1889
  audit,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wervt/cli",
3
- "version": "0.2.17",
3
+ "version": "0.2.18",
4
4
  "description": "Deploy and manage apps on wervt.",
5
5
  "keywords": [
6
6
  "agents",
@@ -32,8 +32,12 @@
32
32
  "citty": "^0.2.2",
33
33
  "modern-tar": "^0.8.6",
34
34
  "nypm": "^0.6.12",
35
+ "rolldown": "^1.2.16",
35
36
  "tinyexec": "^1.3.1"
36
37
  },
38
+ "devDependencies": {
39
+ "@wervt/core": "0.0.2"
40
+ },
37
41
  "scripts": {
38
42
  "build": "vp pack"
39
43
  }