@webjsdev/cli 0.8.5 → 0.9.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/README.md CHANGED
@@ -18,7 +18,7 @@ Then scaffold a new app anywhere:
18
18
  ```sh
19
19
  webjs create my-app
20
20
  cd my-app && npm run dev
21
- # → http://localhost:3000
21
+ # → http://localhost:8080
22
22
  ```
23
23
 
24
24
  One-shot without a global install, two ways:
package/bin/webjs.js CHANGED
@@ -12,8 +12,8 @@ const [cmd, ...rest] = process.argv.slice(2);
12
12
  const TEMPLATES = ['full-stack', 'api', 'saas'];
13
13
 
14
14
  const USAGE = `webjs commands:
15
- webjs dev [--port 3000] Start dev server with live reload
16
- webjs start [--port 3000] Start production server (serves source directly, no build step)
15
+ webjs dev [--port 8080] Start dev server with live reload
16
+ webjs start [--port 8080] Start production server (serves source directly, no build step)
17
17
  webjs test [--server|--browser] Run server + browser tests
18
18
  webjs check Validate app against conventions
19
19
  webjs create <name> [--template full-stack|api|saas] [--no-install] Scaffold a new webjs app
@@ -41,7 +41,7 @@ async function main() {
41
41
  // If we're already inside the --watch child, start the server directly.
42
42
  if (process.env.__WEBJS_DEV_CHILD === '1') {
43
43
  const { startServer } = await import('@webjsdev/server');
44
- const port = Number(flag(rest, '--port', process.env.PORT || 3000));
44
+ const port = Number(flag(rest, '--port', process.env.PORT || 8080));
45
45
  await startServer({ appDir: process.cwd(), port, dev: true });
46
46
  break;
47
47
  }
@@ -81,7 +81,7 @@ async function main() {
81
81
  }
82
82
  case 'start': {
83
83
  const { startServer } = await import('@webjsdev/server');
84
- const port = Number(flag(rest, '--port', process.env.PORT || 3000));
84
+ const port = Number(flag(rest, '--port', process.env.PORT || 8080));
85
85
  await startServer({ appDir: process.cwd(), port, dev: false });
86
86
  break;
87
87
  }
package/lib/create.js CHANGED
@@ -15,6 +15,7 @@ import { mkdir, writeFile, readFile, cp } from 'node:fs/promises';
15
15
  import { join, resolve, dirname } from 'node:path';
16
16
  import { fileURLToPath } from 'node:url';
17
17
  import { existsSync } from 'node:fs';
18
+ import { createRequire } from 'node:module';
18
19
  import { spawnSync } from 'node:child_process';
19
20
 
20
21
  /**
@@ -54,14 +55,33 @@ const TEMPLATES = resolve(__dirname, '..', 'templates');
54
55
  // directly from disk at create time so the scaffolded app boots ready for
55
56
  // `webjs ui add` without an HTTP round-trip during scaffolding.
56
57
  //
57
- // Layout in the monorepo:
58
- // packages/cli/lib/create.js ← __dirname
59
- // packages/ui/packages/registry/components/*.ts
60
- // packages/ui/packages/registry/lib/utils.ts
61
- // packages/ui/packages/registry/themes/index.css
62
- const UI_REGISTRY_ROOT = resolve(
63
- __dirname, '..', '..', 'ui', 'packages', 'registry',
64
- );
58
+ // <ui-pkg-root>/packages/registry/components/*.ts
59
+ // <ui-pkg-root>/packages/registry/lib/utils.ts
60
+ // <ui-pkg-root>/packages/registry/themes/index.css
61
+ //
62
+ // Locate <ui-pkg-root> via Node's module resolver rather than path
63
+ // arithmetic off __dirname. The old `__dirname/../../ui/packages/registry`
64
+ // form assumed @webjsdev/ui was a hoisted sibling of @webjsdev/cli under
65
+ // node_modules/@webjsdev/. That holds for npm/yarn's default hoisting, but
66
+ // breaks on nested layouts (pnpm's isolated linker, `npm install
67
+ // --install-strategy=nested`, some CI setups), where @webjsdev/ui lives at
68
+ // node_modules/@webjsdev/cli/node_modules/@webjsdev/ui and the arithmetic
69
+ // resolves to a path that doesn't exist, failing `webjs create`.
70
+ //
71
+ // @webjsdev/ui's package.json isn't reachable via require.resolve (its
72
+ // `exports` map doesn't expose `./package.json`), so resolve the package
73
+ // entry and walk up to the directory that owns its package.json.
74
+ function resolveUiRegistryRoot() {
75
+ const require = createRequire(import.meta.url);
76
+ let dir = dirname(require.resolve('@webjsdev/ui'));
77
+ while (!existsSync(join(dir, 'package.json'))) {
78
+ const parent = dirname(dir);
79
+ if (parent === dir) break; // reached filesystem root; give up
80
+ dir = parent;
81
+ }
82
+ return resolve(dir, 'packages', 'registry');
83
+ }
84
+ const UI_REGISTRY_ROOT = resolveUiRegistryRoot();
65
85
 
66
86
  /**
67
87
  * Read a single @webjsdev/ui registry component, rewrite its relative import
@@ -72,11 +92,10 @@ const UI_REGISTRY_ROOT = resolve(
72
92
  * utils/ folder: `../../lib/utils/cn.ts`.
73
93
  *
74
94
  * @param {string} name component name without `.ts` (e.g. 'button')
75
- * @returns {Promise<string|null>} source or null if not found
95
+ * @returns {Promise<string>} source with import rewritten
76
96
  */
77
97
  async function readUiComponent(name) {
78
98
  const src = join(UI_REGISTRY_ROOT, 'components', `${name}.ts`);
79
- if (!existsSync(src)) return null;
80
99
  const raw = await readFile(src, 'utf8');
81
100
  return raw
82
101
  .replaceAll("'../lib/utils.ts'", "'../../lib/utils/cn.ts'")
@@ -85,7 +104,10 @@ async function readUiComponent(name) {
85
104
 
86
105
  /**
87
106
  * Copy a list of @webjsdev/ui registry components into the scaffolded app
88
- * under `components/ui/`. Silently skips any name that isn't in the registry.
107
+ * under `components/ui/`. Throws if any name is missing from the registry,
108
+ * since the scaffold's generated pages import these by name and a missing
109
+ * file would produce ERR_MODULE_NOT_FOUND at first request. Caller must
110
+ * have already invoked assertUiRegistryAvailable().
89
111
  *
90
112
  * @param {string} appDir destination app root
91
113
  * @param {string[]} names list of component file basenames (without `.ts`)
@@ -94,9 +116,16 @@ async function copyUiComponents(appDir, names) {
94
116
  const uiDir = join(appDir, 'components', 'ui');
95
117
  await mkdir(uiDir, { recursive: true });
96
118
  for (const n of names) {
97
- const content = await readUiComponent(n);
98
- if (content == null) continue;
99
- await writeFile(join(uiDir, `${n}.ts`), content);
119
+ const src = join(UI_REGISTRY_ROOT, 'components', `${n}.ts`);
120
+ if (!existsSync(src)) {
121
+ throw new Error(
122
+ `@webjsdev/ui registry is missing component '${n}.ts' at ${src}. ` +
123
+ `The scaffold's example pages import this component by name. ` +
124
+ `Either the registry was published incompletely or the scaffold's ` +
125
+ `component list is out of sync with the registry.`,
126
+ );
127
+ }
128
+ await writeFile(join(uiDir, `${n}.ts`), await readUiComponent(n));
100
129
  }
101
130
  }
102
131
 
@@ -110,13 +139,15 @@ async function copyUiComponents(appDir, names) {
110
139
  * @param {string} appDir
111
140
  */
112
141
  async function writeUiBootstrap(appDir) {
142
+ // Caller (scaffoldApp) has already invoked assertUiRegistryAvailable(),
143
+ // so the source files below are guaranteed to exist.
144
+
113
145
  // 1) lib/utils/cn.ts: the cn() helper
114
- const utilsSrc = join(UI_REGISTRY_ROOT, 'lib', 'utils.ts');
115
- if (existsSync(utilsSrc)) {
116
- const content = await readFile(utilsSrc, 'utf8');
117
- await mkdir(join(appDir, 'lib', 'utils'), { recursive: true });
118
- await writeFile(join(appDir, 'lib', 'utils', 'cn.ts'), content);
119
- }
146
+ const utilsContent = await readFile(
147
+ join(UI_REGISTRY_ROOT, 'lib', 'utils.ts'), 'utf8',
148
+ );
149
+ await mkdir(join(appDir, 'lib', 'utils'), { recursive: true });
150
+ await writeFile(join(appDir, 'lib', 'utils', 'cn.ts'), utilsContent);
120
151
 
121
152
  // 2) components.json: the same shape `webjsui init` writes for webjs
122
153
  // projects (see packages/ui/src/utils/detect-project.js). The utils alias
@@ -144,12 +175,11 @@ async function writeUiBootstrap(appDir) {
144
175
 
145
176
  // 3) app/globals.css: copy the neutral theme verbatim. components.json
146
177
  // references this path, and future `webjs ui add` calls append to it.
147
- const themeSrc = join(UI_REGISTRY_ROOT, 'themes', 'index.css');
148
- if (existsSync(themeSrc)) {
149
- const css = await readFile(themeSrc, 'utf8');
150
- await mkdir(join(appDir, 'app'), { recursive: true });
151
- await writeFile(join(appDir, 'app', 'globals.css'), css);
152
- }
178
+ const css = await readFile(
179
+ join(UI_REGISTRY_ROOT, 'themes', 'index.css'), 'utf8',
180
+ );
181
+ await mkdir(join(appDir, 'app'), { recursive: true });
182
+ await writeFile(join(appDir, 'app', 'globals.css'), css);
153
183
  }
154
184
 
155
185
  /**
@@ -159,14 +189,45 @@ async function writeUiBootstrap(appDir) {
159
189
  * tokens (`--color-primary`, `--color-card`, …) the registry components
160
190
  * consume are available at runtime without a build step.
161
191
  *
162
- * @returns {Promise<string>} theme CSS source, or '' if registry missing
192
+ * @returns {Promise<string>} theme CSS source
163
193
  */
164
194
  async function readThemeCss() {
165
195
  const src = join(UI_REGISTRY_ROOT, 'themes', 'index.css');
166
- if (!existsSync(src)) return '';
167
196
  return await readFile(src, 'utf8');
168
197
  }
169
198
 
199
+ /**
200
+ * Fail loudly when the @webjsdev/ui registry is not on disk. The scaffold
201
+ * reads component sources, the cn() helper, and the shadcn theme from
202
+ * UI_REGISTRY_ROOT and weaves them into a generated app/page.ts that
203
+ * imports `components/ui/button.ts`. If the registry is missing, the
204
+ * generated app boots to ERR_MODULE_NOT_FOUND on first request, which is
205
+ * a confusing failure for end-users to debug.
206
+ *
207
+ * The check guards against an @webjsdev/ui published tarball that forgets
208
+ * to ship `packages/registry/` in its `files` array (the bug fixed in
209
+ * the same commit), and against a corrupted node_modules install.
210
+ */
211
+ function assertUiRegistryAvailable() {
212
+ const required = [
213
+ join(UI_REGISTRY_ROOT, 'components'),
214
+ join(UI_REGISTRY_ROOT, 'lib', 'utils.ts'),
215
+ join(UI_REGISTRY_ROOT, 'themes', 'index.css'),
216
+ ];
217
+ const missing = required.filter((p) => !existsSync(p));
218
+ if (missing.length === 0) return;
219
+ throw new Error(
220
+ `@webjsdev/ui registry sources not found at ${UI_REGISTRY_ROOT}.\n` +
221
+ `Missing:\n${missing.map((p) => ` - ${p}`).join('\n')}\n\n` +
222
+ `The scaffold reads component sources from the installed @webjsdev/ui ` +
223
+ `package. If you see this from a fresh \`npm create webjs\`, the ` +
224
+ `published @webjsdev/ui tarball is missing \`packages/registry/\` ` +
225
+ `(check its package.json \`files\` array). Please file an issue at ` +
226
+ `https://github.com/webjsdev/webjs/issues with your @webjsdev/ui ` +
227
+ `version (\`npm ls @webjsdev/ui\`).`,
228
+ );
229
+ }
230
+
170
231
  /**
171
232
  * @param {string} name App directory name
172
233
  * @param {string} cwd Current working directory
@@ -513,6 +574,13 @@ export type ActionResult<T> =
513
574
  await cp(uiSrc, join(utilsDir, 'ui.ts'));
514
575
  }
515
576
 
577
+ // Fail loudly if the @webjsdev/ui registry sources aren't on disk.
578
+ // Without this, downstream copy helpers would silently skip and the
579
+ // generated app would boot to ERR_MODULE_NOT_FOUND on the first page
580
+ // render (the import in app/page.ts below points at a file we didn't
581
+ // write).
582
+ assertUiRegistryAvailable();
583
+
516
584
  // Pre-initialise @webjsdev/ui so the scaffold boots ready for
517
585
  // `webjs ui add <name>`: writes components.json + lib/utils/cn.ts +
518
586
  // app/globals.css (the shadcn theme).
@@ -960,7 +1028,7 @@ For AI agents, read this before editing scaffolded files:
960
1028
  console.log(`
961
1029
  Next steps:
962
1030
  ${runCommand}
963
- # → http://localhost:3000
1031
+ # → http://localhost:8080
964
1032
 
965
1033
  Optional:
966
1034
  ${uiNote}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webjsdev/cli",
3
- "version": "0.8.5",
3
+ "version": "0.9.0",
4
4
  "type": "module",
5
5
  "description": "webjs CLI - dev, start, create, db",
6
6
  "bin": {
@@ -4,7 +4,7 @@
4
4
  # Opinionated defaults: set these and the framework auto-configures.
5
5
 
6
6
  # ── Server ──────────────────────────────────────────────────────────
7
- PORT=3000
7
+ PORT=8080
8
8
 
9
9
  # ── Auth (required for authentication) ──────────────────────────────
10
10
  # Generate: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
@@ -213,7 +213,7 @@ variables control infrastructure (no config files needed):
213
213
  | `AUTH_SECRET` | Required for auth JWT signing (32+ random chars) |
214
214
  | `AUTH_GOOGLE_ID` | Google OAuth client ID (optional) |
215
215
  | `AUTH_GITHUB_ID` | GitHub OAuth client ID (optional) |
216
- | `PORT` | Server port (default: 3000) |
216
+ | `PORT` | Server port (default: 8080) |
217
217
  | `WEBJS_PUBLIC_*` | Any env var starting with this prefix is exposed to the browser as `process.env.WEBJS_PUBLIC_X`. Components can read it directly. No build step, no transform. Use for API base URLs, Stripe publishable keys, analytics IDs, anything that is intended to be visible client-side. |
218
218
 
219
219
  **Server-only by default.** Any env var without the `WEBJS_PUBLIC_` prefix never reaches the browser. Reading `process.env.DATABASE_URL` from a component returns `undefined`, the same as a typo. The prefix is fail-closed: secrets cannot accidentally leak.