@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 +1 -1
- package/bin/webjs.js +4 -4
- package/lib/create.js +97 -29
- package/package.json +1 -1
- package/templates/.env.example +1 -1
- package/templates/CONVENTIONS.md +1 -1
package/README.md
CHANGED
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
|
|
16
|
-
webjs start [--port
|
|
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 ||
|
|
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 ||
|
|
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
|
-
//
|
|
58
|
-
// packages/
|
|
59
|
-
//
|
|
60
|
-
//
|
|
61
|
-
//
|
|
62
|
-
|
|
63
|
-
|
|
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
|
|
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/`.
|
|
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
|
|
98
|
-
if (
|
|
99
|
-
|
|
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
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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
|
|
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:
|
|
1031
|
+
# → http://localhost:8080
|
|
964
1032
|
|
|
965
1033
|
Optional:
|
|
966
1034
|
${uiNote}
|
package/package.json
CHANGED
package/templates/.env.example
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# Opinionated defaults: set these and the framework auto-configures.
|
|
5
5
|
|
|
6
6
|
# ── Server ──────────────────────────────────────────────────────────
|
|
7
|
-
PORT=
|
|
7
|
+
PORT=8080
|
|
8
8
|
|
|
9
9
|
# ── Auth (required for authentication) ──────────────────────────────
|
|
10
10
|
# Generate: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
|
package/templates/CONVENTIONS.md
CHANGED
|
@@ -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:
|
|
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.
|