create-roxyon-app 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 BEA Technology Company
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,41 @@
1
+ # create-roxyon-app
2
+
3
+ Scaffold a Roxyon project that's ready for `roxyon deploy` and legible to any AI
4
+ assistant.
5
+
6
+ ```bash
7
+ npm create roxyon-app@latest my-app
8
+ # or
9
+ npx create-roxyon-app my-app --template lumen-baas --host my-domain.com
10
+ ```
11
+
12
+ ## Templates
13
+
14
+ | id | what |
15
+ |---|---|
16
+ | `lumen` | LumenJS SPA (vanilla JS/HTML/CSS, no build step) |
17
+ | `lumen-baas` | LumenJS SPA wired to the Roxyon BaaS — RX SDK, `rxError()` helper, an auth-free data example (`items.view`) |
18
+ | `node` | Minimal Node.js HTTP server (no deps), reads `PORT`/`HOST` from the environment |
19
+
20
+ LumenJS templates are scaffolded by `@lmjs/cli` under the hood, then overlaid —
21
+ so they always track the current framework.
22
+
23
+ ## What every project gets
24
+
25
+ - `roxyon.json` — host, folder, runtime, build command
26
+ - **`AGENTS.md`** — how to build and deploy this project, plus the LumenJS V1 /
27
+ BaaS gotchas. Also written as `CLAUDE.md`, `GEMINI.md`,
28
+ `.cursor/rules/roxyon.mdc`, `.github/copilot-instructions.md`
29
+ - `llms.txt` — pointers to the framework + platform docs
30
+ - `.roxyonignore`, `.gitignore`
31
+ - npm scripts: `dev`, `build`, `deploy`
32
+
33
+ ## Flags
34
+
35
+ ```
36
+ -t, --template <id> lumen | lumen-baas | node
37
+ --host <domain> deploy host (a domain on your Roxyon subscription)
38
+ --folder <path> sub-path under the host
39
+ --no-install skip npm install
40
+ -y, --yes accept defaults, no prompts
41
+ ```
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import '../dist/index.js';
package/dist/index.js ADDED
@@ -0,0 +1,471 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { basename, resolve as resolve2 } from "path";
5
+ import * as p from "@clack/prompts";
6
+ import pc from "picocolors";
7
+
8
+ // src/scaffold.ts
9
+ import { spawn } from "child_process";
10
+ import { access, mkdir, readFile, readdir, writeFile } from "fs/promises";
11
+ import { dirname, join, resolve } from "path";
12
+ import { buildProjectConfig, saveProjectConfig } from "@roxyon/deploy-core";
13
+
14
+ // src/agent-files.ts
15
+ function agentFiles(config, opts) {
16
+ const isLumen = config.runtime === "lumen";
17
+ const dev = isLumen ? "npm run dev" : `(your app: ${config.start ?? "npm run start"})`;
18
+ const build = config.build ? config.build : "(no build step)";
19
+ const agents = `# AGENTS.md
20
+
21
+ Working notes for AI assistants and humans. Also read as \`CLAUDE.md\`,
22
+ \`GEMINI.md\`, \`.cursor/rules/roxyon.mdc\`, \`.github/copilot-instructions.md\`.
23
+
24
+ ## This project
25
+
26
+ - **${config.name}** \u2014 a ${config.runtime} project deployed on **Roxyon**.
27
+ - Config: \`roxyon.json\` (host \`${config.host}\`${config.folder ? `, folder \`${config.folder}\`` : ""}).
28
+ - Local dev: \`${dev}\`
29
+ - Build: \`${build}\`
30
+
31
+ ${isLumen ? `## LumenJS (V1) \u2014 the rules that bite
32
+
33
+ Vanilla JS/HTML/CSS, **no build step** for the framework. \`.view\` files under
34
+ \`src/views/\` are one per route. Full reference: https://lumenjs.com/llms/lumenjs.md
35
+
36
+ - State is \`var\` (never \`let\`/\`const\`) in a \`<script>\` in the view; reactive
37
+ only when named in \`bind="name"\` on an ancestor element.
38
+ - **Never nest a \`bind\` inside another \`bind\`** \u2014 the inner subtree stops
39
+ rendering, interpolations come out empty.
40
+ - \`bind\` renders its descendants, not the element's own attributes \u2014 put it on
41
+ the parent of whatever interpolates.
42
+ - \`:for\` does not resolve the loop variable in nested loops or in an element's
43
+ own plain attributes. Build that markup as a JS string and inject with
44
+ \`.html()\`. Backtick the moustache on a \`:for\` element's own attribute.
45
+ - A \`{{ fn() }}\` moustache runs once and goes stale \u2014 compute into a \`var\`.
46
+ - Forms: \`o-sub\` / \`b-sub\` on the \`<form>\`. There is no \`v-model\`.
47
+ ` : `## Runtime
48
+
49
+ ${config.runtime} app. \`PORT\` and \`HOST\` are set by the platform \u2014 read them
50
+ from the environment, never hard-code or override them. The start command is
51
+ \`${config.start ?? "npm run start"}\` (see \`roxyon.json\`).
52
+ `}${opts.baas ? `
53
+ ## Roxyon BaaS
54
+
55
+ Configured in \`src/roxyon-baas.js\`. Reference: https://roxyon.com/llms/baas.md
56
+
57
+ - **A failed write returns HTTP 200 with an \`error\` field** \u2014 check for it
58
+ before treating any create/update/delete as successful.
59
+ - The \`in\` operator takes a comma-joined string, not an array.
60
+ - Auth: \`rx.login({Email,Password})\`, \`rx.me()\`, \`rx.logout()\`.
61
+ ` : ""}
62
+ ## Deploy
63
+
64
+ Do **not** SFTP files or edit them on the server. Deploy with:
65
+
66
+ \`\`\`bash
67
+ roxyon deploy # or the roxyon_deploy MCP tool (dry run, then confirm:true)
68
+ \`\`\`
69
+
70
+ - Env vars live on the platform: \`roxyon env set KEY=value\`, then redeploy.
71
+ - Git push-to-deploy: \`roxyon link <repo-url>\`.
72
+ - First deploy creates the app automatically.
73
+ `;
74
+ const llmsTxt = `# ${config.name}
75
+
76
+ > A ${config.runtime} project on Roxyon. Deploy with \`roxyon deploy\`.
77
+
78
+ ## Docs
79
+
80
+ - [How to build & deploy](AGENTS.md)
81
+ ${isLumen ? "- [LumenJS reference](https://lumenjs.com/llms/lumenjs.md)\n" : ""}${opts.baas ? "- [Roxyon BaaS reference](https://roxyon.com/llms/baas.md)\n" : ""}- [Deploying to Roxyon](https://roxyon.com/llms/deploy.md)
82
+ `;
83
+ const cursorRule = `---
84
+ description: How to build and deploy this Roxyon project
85
+ alwaysApply: true
86
+ ---
87
+
88
+ ${agents}`;
89
+ return [
90
+ { path: "AGENTS.md", content: agents },
91
+ { path: "CLAUDE.md", content: "See [AGENTS.md](AGENTS.md).\n" },
92
+ { path: "GEMINI.md", content: "See [AGENTS.md](AGENTS.md).\n" },
93
+ { path: "llms.txt", content: llmsTxt },
94
+ { path: ".cursor/rules/roxyon.mdc", content: cursorRule },
95
+ { path: ".github/copilot-instructions.md", content: agents },
96
+ {
97
+ path: ".roxyonignore",
98
+ content: ["node_modules", ".git", ".next", ".venv", "dist/.cache", ".DS_Store", ""].join(
99
+ "\n"
100
+ )
101
+ }
102
+ ];
103
+ }
104
+
105
+ // src/templates.ts
106
+ var TEMPLATES = {
107
+ lumen: { id: "lumen", label: "LumenJS SPA", runtime: "lumen", baas: false, viaLmjs: true },
108
+ "lumen-baas": {
109
+ id: "lumen-baas",
110
+ label: "LumenJS SPA + Roxyon BaaS (auth + data example)",
111
+ runtime: "lumen",
112
+ baas: true,
113
+ viaLmjs: true
114
+ },
115
+ node: {
116
+ id: "node",
117
+ label: "Node.js app (minimal HTTP server)",
118
+ runtime: "node",
119
+ baas: false,
120
+ viaLmjs: false
121
+ }
122
+ };
123
+ function nodeTemplateFiles(name) {
124
+ return [
125
+ {
126
+ path: "package.json",
127
+ content: `${JSON.stringify(
128
+ {
129
+ name,
130
+ version: "0.1.0",
131
+ private: true,
132
+ type: "module",
133
+ scripts: {
134
+ start: "node src/server.js",
135
+ dev: "node --watch src/server.js"
136
+ },
137
+ engines: { node: ">=20" }
138
+ },
139
+ null,
140
+ 2
141
+ )}
142
+ `
143
+ },
144
+ {
145
+ path: "src/server.js",
146
+ content: `import { createServer } from 'node:http';
147
+
148
+ // The platform sets PORT and HOST. Never hard-code or override them.
149
+ const port = Number(process.env.PORT) || 3000;
150
+ const host = process.env.HOST || '0.0.0.0';
151
+
152
+ const server = createServer((req, res) => {
153
+ if (req.url === '/health') {
154
+ res.writeHead(200, { 'content-type': 'text/plain' });
155
+ res.end('ok');
156
+ return;
157
+ }
158
+ res.writeHead(200, { 'content-type': 'application/json' });
159
+ res.end(JSON.stringify({ hello: 'from ${name} on Roxyon', url: req.url }));
160
+ });
161
+
162
+ server.listen(port, host, () => {
163
+ console.log(\`listening on http://\${host}:\${port}\`);
164
+ });
165
+ `
166
+ },
167
+ {
168
+ path: "README.md",
169
+ content: `# ${name}
170
+
171
+ A Node.js app on Roxyon.
172
+
173
+ \`\`\`bash
174
+ npm install
175
+ npm run dev # local, http://localhost:3000
176
+ roxyon deploy # to Roxyon (first run creates the app)
177
+ \`\`\`
178
+
179
+ See [AGENTS.md](AGENTS.md).
180
+ `
181
+ }
182
+ ];
183
+ }
184
+ function baasOverlayFiles() {
185
+ return [
186
+ {
187
+ path: "src/roxyon-baas.js",
188
+ content: `// Roxyon BaaS client. The RX SDK is loaded in index.html from the CDN.
189
+ //
190
+ // A FAILED WRITE RESOLVES with an { error } field (top-level or inside
191
+ // results[]) \u2014 it does NOT reject. Always run rxError() on a write response.
192
+ window.rx = new RX({
193
+ 'Application-ID': 'YOUR_APPLICATION_ID',
194
+ 'JavaScript-KEY': 'YOUR_JAVASCRIPT_KEY',
195
+ });
196
+
197
+ window.rxError = function (r) {
198
+ if (!r) return 'No response from the server.';
199
+ if (r.error) return r.error;
200
+ var rows = r.results;
201
+ if (Object.prototype.toString.call(rows) === '[object Array]') {
202
+ for (var i = 0; i < rows.length; i++) if (rows[i] && rows[i].error) return rows[i].error;
203
+ }
204
+ return '';
205
+ };
206
+ `
207
+ },
208
+ {
209
+ path: "src/views/items.view",
210
+ content: `<div class="ph50 pv30" bind="items">
211
+ <div class="fs20 b mb20">Items ({{items.length}})</div>
212
+ <form o-sub="addItem" class="mb20">
213
+ <input name="Title" placeholder="New item" required class="p10" />
214
+ <button type="submit" class="p10">Add</button>
215
+ </form>
216
+ <div :if="msg != ''" class="cr mb10">{{msg}}</div>
217
+ <div :for="items as it">
218
+ <div class="pv5">{{it.Title}}</div>
219
+ </div>
220
+ </div>
221
+
222
+ <script>
223
+ // 'var' \u2014 not let/const. Reactive because it's named in bind="items".
224
+ var items = [];
225
+ var msg = '';
226
+
227
+ function loadItems() {
228
+ rx.get('/Items', { fields: 'objectId,Title', order: '-createdAt', limit: 50 }).then(function (r) {
229
+ var err = rxError(r);
230
+ if (err) { msg = err; return; }
231
+ items = r.results || [];
232
+ });
233
+ }
234
+
235
+ function addItem(f, d) {
236
+ msg = '';
237
+ rx.post('/Items', { Title: d.Title }).then(function (r) {
238
+ var err = rxError(r); // a failed write still lands here
239
+ if (err) { msg = err; return; }
240
+ f.find('[name="Title"]').val('');
241
+ loadItems();
242
+ });
243
+ }
244
+
245
+ loadItems();
246
+ </script>
247
+
248
+ <settings> { hasFooter: false, hasHeader: false, hasNav: false } </settings>
249
+ `
250
+ }
251
+ ];
252
+ }
253
+
254
+ // src/scaffold.ts
255
+ function run(cmd, args, cwd) {
256
+ return new Promise((res) => {
257
+ const child = spawn(cmd, args, { cwd, stdio: "inherit", shell: process.platform === "win32" });
258
+ child.on("close", (code) => res(code ?? 1));
259
+ child.on("error", () => res(127));
260
+ });
261
+ }
262
+ async function writeFileOut(dir, f) {
263
+ const path = join(dir, f.path);
264
+ await mkdir(dirname(path), { recursive: true });
265
+ await writeFile(path, f.content);
266
+ }
267
+ async function exists(path) {
268
+ try {
269
+ await access(path);
270
+ return true;
271
+ } catch {
272
+ return false;
273
+ }
274
+ }
275
+ async function isEmptyish(dir) {
276
+ try {
277
+ const entries = await readdir(dir);
278
+ return entries.filter((e) => e !== ".git" && e !== ".DS_Store").length === 0;
279
+ } catch {
280
+ return true;
281
+ }
282
+ }
283
+ async function patchPackageJson(dir, name, patch) {
284
+ const path = join(dir, "package.json");
285
+ let pkg = {};
286
+ try {
287
+ pkg = JSON.parse(await readFile(path, "utf8"));
288
+ } catch {
289
+ pkg = { name, version: "0.1.0", private: true };
290
+ }
291
+ pkg.scripts = { ...pkg.scripts, ...patch.scripts };
292
+ if (patch.devDependencies) {
293
+ pkg.devDependencies = { ...pkg.devDependencies, ...patch.devDependencies };
294
+ }
295
+ await writeFile(path, `${JSON.stringify(pkg, null, 2)}
296
+ `);
297
+ }
298
+ async function scaffold(input) {
299
+ const dir = resolve(input.dir);
300
+ if (!await isEmptyish(dir)) {
301
+ throw new Error(`${dir} is not empty.`);
302
+ }
303
+ await mkdir(dir, { recursive: true });
304
+ const config = buildProjectConfig({
305
+ cwd: dir,
306
+ name: input.name,
307
+ runtime: input.template.runtime,
308
+ host: input.host,
309
+ folder: input.folder
310
+ });
311
+ if (input.template.viaLmjs) {
312
+ const code = await run("npx", ["--yes", "@lmjs/cli", "create", input.name], dir);
313
+ if (code !== 0 || !await exists(join(dir, "src", "index.html"))) {
314
+ throw new Error(
315
+ "Could not scaffold the LumenJS skeleton (npx @lmjs/cli create failed). Is npm online?"
316
+ );
317
+ }
318
+ await patchPackageJson(dir, input.name, {
319
+ scripts: {
320
+ dev: "lm serve",
321
+ build: "lm build --serverless",
322
+ deploy: "lm build --serverless && roxyon deploy"
323
+ },
324
+ devDependencies: { "@lmjs/cli": "^1.0.51" }
325
+ });
326
+ if (input.template.baas) {
327
+ for (const f of baasOverlayFiles()) await writeFileOut(dir, f);
328
+ const htmlPath = join(dir, "src", "index.html");
329
+ try {
330
+ const html = await readFile(htmlPath, "utf8");
331
+ if (!html.includes("rxjs")) {
332
+ await writeFile(
333
+ htmlPath,
334
+ html.replace(
335
+ '<script src="/js/bea.js"',
336
+ '<script src="https://cdn.roxyon.com/libs/rxjs/1.0.0/rx.js"></script>\n <script src="/roxyon-baas.js"></script>\n <script src="/js/bea.js"'
337
+ )
338
+ );
339
+ }
340
+ } catch {
341
+ }
342
+ }
343
+ } else {
344
+ for (const f of nodeTemplateFiles(input.name)) await writeFileOut(dir, f);
345
+ }
346
+ for (const f of agentFiles(config, { baas: input.template.baas })) {
347
+ await writeFileOut(dir, f);
348
+ }
349
+ await saveProjectConfig(config, dir);
350
+ const gitignore = join(dir, ".gitignore");
351
+ const existing = await readFile(gitignore, "utf8").catch(() => "");
352
+ if (!existing.includes("node_modules")) {
353
+ await writeFile(gitignore, `${existing}${existing ? "\n" : ""}node_modules
354
+ dist
355
+ .DS_Store
356
+ `);
357
+ }
358
+ let installed = false;
359
+ if (input.install) {
360
+ const code = await run("npm", ["install"], dir);
361
+ installed = code === 0;
362
+ }
363
+ return { dir, config, installed };
364
+ }
365
+
366
+ // src/index.ts
367
+ function parseArgs(argv) {
368
+ const a = { install: true, yes: false };
369
+ for (let i = 0; i < argv.length; i++) {
370
+ const arg = argv[i];
371
+ if (arg === "--no-install") a.install = false;
372
+ else if (arg === "-y" || arg === "--yes") a.yes = true;
373
+ else if (arg === "--template" || arg === "-t") a.template = argv[++i];
374
+ else if (arg === "--host") a.host = argv[++i];
375
+ else if (arg === "--folder") a.folder = argv[++i];
376
+ else if (arg === "--help" || arg === "-h") a.dir = "--help";
377
+ else if (!arg.startsWith("-") && !a.dir) a.dir = arg;
378
+ }
379
+ return a;
380
+ }
381
+ var HELP = `${pc.bold("create-roxyon-app")} \u2014 scaffold a Roxyon project
382
+
383
+ npm create roxyon-app@latest my-app
384
+ npx create-roxyon-app my-app --template lumen-baas
385
+
386
+ Options:
387
+ -t, --template <id> ${Object.keys(TEMPLATES).join(" | ")}
388
+ --host <domain> deploy host (a domain on your Roxyon subscription)
389
+ --folder <path> sub-path under the host
390
+ --no-install skip npm install
391
+ -y, --yes accept defaults, no prompts
392
+ `;
393
+ async function ask(fn) {
394
+ const v = await fn();
395
+ if (p.isCancel(v)) {
396
+ p.cancel("Cancelled.");
397
+ process.exit(0);
398
+ }
399
+ return v;
400
+ }
401
+ async function main() {
402
+ const args = parseArgs(process.argv.slice(2));
403
+ if (args.dir === "--help") {
404
+ process.stdout.write(HELP);
405
+ return;
406
+ }
407
+ p.intro(pc.bgYellow(pc.black(" create-roxyon-app ")));
408
+ const dirInput = args.dir ?? (args.yes ? "roxyon-app" : await ask(
409
+ () => p.text({ message: "Project directory", placeholder: "my-app", defaultValue: "my-app" })
410
+ ));
411
+ const dir = resolve2(dirInput);
412
+ const name = basename(dir);
413
+ const templateId = args.template ?? (args.yes ? "lumen" : await ask(
414
+ () => p.select({
415
+ message: "Template",
416
+ initialValue: "lumen",
417
+ options: Object.values(TEMPLATES).map((t) => ({ value: t.id, label: t.label }))
418
+ })
419
+ ));
420
+ const template = TEMPLATES[templateId];
421
+ if (!template) {
422
+ p.cancel(`Unknown template "${templateId}". Choose: ${Object.keys(TEMPLATES).join(", ")}`);
423
+ process.exit(1);
424
+ }
425
+ const host = args.host ?? (args.yes ? "" : await ask(
426
+ () => p.text({
427
+ message: "Deploy host (a domain on your Roxyon subscription \u2014 leave blank to set later)",
428
+ placeholder: "example.com",
429
+ defaultValue: ""
430
+ })
431
+ ));
432
+ const folder = args.folder ?? (args.yes || template.runtime === "lumen" ? "" : await ask(
433
+ () => p.text({ message: "Folder under the host", placeholder: name, defaultValue: name })
434
+ ));
435
+ const s = p.spinner();
436
+ s.start("Scaffolding");
437
+ try {
438
+ const result = await scaffold({
439
+ dir,
440
+ name,
441
+ template,
442
+ host: host || "example.com",
443
+ folder,
444
+ install: args.install
445
+ });
446
+ s.stop("Scaffolded.");
447
+ const rel = dir.startsWith(process.cwd()) ? `.${dir.slice(process.cwd().length)}` : dir;
448
+ const steps = [
449
+ `cd ${rel}`,
450
+ ...result.installed ? [] : ["npm install"],
451
+ template.runtime === "lumen" ? "npm run dev" : "npm run dev",
452
+ ...host ? [] : [pc.dim('# set "host" in roxyon.json (or run `roxyon init`)')],
453
+ "roxyon login",
454
+ "roxyon deploy"
455
+ ];
456
+ p.note(steps.join("\n"), "Next");
457
+ p.outro(
458
+ `${pc.green("Ready.")} ${template.baas ? "Set your BaaS keys in src/roxyon-baas.js. " : ""}AGENTS.md tells any AI assistant how to build & deploy this.`
459
+ );
460
+ } catch (err) {
461
+ s.stop("Failed.");
462
+ p.cancel(err instanceof Error ? err.message : String(err));
463
+ process.exit(1);
464
+ }
465
+ }
466
+ main().catch((err) => {
467
+ process.stderr.write(`${err instanceof Error ? err.stack : String(err)}
468
+ `);
469
+ process.exit(1);
470
+ });
471
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/scaffold.ts","../src/agent-files.ts","../src/templates.ts"],"sourcesContent":["import { basename, resolve } from 'node:path';\nimport * as p from '@clack/prompts';\nimport pc from 'picocolors';\nimport { scaffold } from './scaffold.js';\nimport { TEMPLATES, type TemplateId } from './templates.js';\n\ninterface Args {\n dir?: string;\n template?: TemplateId;\n host?: string;\n folder?: string;\n install: boolean;\n yes: boolean;\n}\n\nfunction parseArgs(argv: string[]): Args {\n const a: Args = { install: true, yes: false };\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i]!;\n if (arg === '--no-install') a.install = false;\n else if (arg === '-y' || arg === '--yes') a.yes = true;\n else if (arg === '--template' || arg === '-t') a.template = argv[++i] as TemplateId;\n else if (arg === '--host') a.host = argv[++i];\n else if (arg === '--folder') a.folder = argv[++i];\n else if (arg === '--help' || arg === '-h') a.dir = '--help';\n else if (!arg.startsWith('-') && !a.dir) a.dir = arg;\n }\n return a;\n}\n\nconst HELP = `${pc.bold('create-roxyon-app')} — scaffold a Roxyon project\n\n npm create roxyon-app@latest my-app\n npx create-roxyon-app my-app --template lumen-baas\n\nOptions:\n -t, --template <id> ${Object.keys(TEMPLATES).join(' | ')}\n --host <domain> deploy host (a domain on your Roxyon subscription)\n --folder <path> sub-path under the host\n --no-install skip npm install\n -y, --yes accept defaults, no prompts\n`;\n\nasync function ask<T>(fn: () => Promise<T | symbol>): Promise<T> {\n const v = await fn();\n if (p.isCancel(v)) {\n p.cancel('Cancelled.');\n process.exit(0);\n }\n return v as T;\n}\n\nasync function main(): Promise<void> {\n const args = parseArgs(process.argv.slice(2));\n if (args.dir === '--help') {\n process.stdout.write(HELP);\n return;\n }\n\n p.intro(pc.bgYellow(pc.black(' create-roxyon-app ')));\n\n const dirInput =\n args.dir ??\n (args.yes\n ? 'roxyon-app'\n : await ask<string>(() =>\n p.text({ message: 'Project directory', placeholder: 'my-app', defaultValue: 'my-app' }),\n ));\n const dir = resolve(dirInput);\n const name = basename(dir);\n\n const templateId: TemplateId =\n args.template ??\n (args.yes\n ? 'lumen'\n : await ask<TemplateId>(() =>\n p.select({\n message: 'Template',\n initialValue: 'lumen' as TemplateId,\n options: Object.values(TEMPLATES).map((t) => ({ value: t.id, label: t.label })),\n }),\n ));\n const template = TEMPLATES[templateId];\n if (!template) {\n p.cancel(`Unknown template \"${templateId}\". Choose: ${Object.keys(TEMPLATES).join(', ')}`);\n process.exit(1);\n }\n\n const host =\n args.host ??\n (args.yes\n ? ''\n : await ask<string>(() =>\n p.text({\n message:\n 'Deploy host (a domain on your Roxyon subscription — leave blank to set later)',\n placeholder: 'example.com',\n defaultValue: '',\n }),\n ));\n\n const folder =\n args.folder ??\n (args.yes || template.runtime === 'lumen'\n ? ''\n : await ask<string>(() =>\n p.text({ message: 'Folder under the host', placeholder: name, defaultValue: name }),\n ));\n\n const s = p.spinner();\n s.start('Scaffolding');\n try {\n const result = await scaffold({\n dir,\n name,\n template,\n host: host || 'example.com',\n folder,\n install: args.install,\n });\n s.stop('Scaffolded.');\n\n const rel = dir.startsWith(process.cwd()) ? `.${dir.slice(process.cwd().length)}` : dir;\n const steps = [\n `cd ${rel}`,\n ...(result.installed ? [] : ['npm install']),\n template.runtime === 'lumen' ? 'npm run dev' : 'npm run dev',\n ...(host ? [] : [pc.dim('# set \"host\" in roxyon.json (or run `roxyon init`)')]),\n 'roxyon login',\n 'roxyon deploy',\n ];\n p.note(steps.join('\\n'), 'Next');\n p.outro(\n `${pc.green('Ready.')} ${template.baas ? 'Set your BaaS keys in src/roxyon-baas.js. ' : ''}AGENTS.md tells any AI assistant how to build & deploy this.`,\n );\n } catch (err) {\n s.stop('Failed.');\n p.cancel(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n}\n\nmain().catch((err) => {\n process.stderr.write(`${err instanceof Error ? err.stack : String(err)}\\n`);\n process.exit(1);\n});\n","import { spawn } from 'node:child_process';\nimport { access, mkdir, readFile, readdir, writeFile } from 'node:fs/promises';\nimport { dirname, join, resolve } from 'node:path';\nimport { type ProjectConfig, buildProjectConfig, saveProjectConfig } from '@roxyon/deploy-core';\nimport { type FileOut, agentFiles } from './agent-files.js';\nimport { type Template, baasOverlayFiles, nodeTemplateFiles } from './templates.js';\n\nexport interface ScaffoldInput {\n dir: string;\n name: string;\n template: Template;\n host: string;\n folder: string;\n install: boolean;\n}\n\nexport interface ScaffoldResult {\n dir: string;\n config: ProjectConfig;\n installed: boolean;\n}\n\nfunction run(cmd: string, args: string[], cwd: string): Promise<number> {\n return new Promise((res) => {\n const child = spawn(cmd, args, { cwd, stdio: 'inherit', shell: process.platform === 'win32' });\n child.on('close', (code) => res(code ?? 1));\n child.on('error', () => res(127));\n });\n}\n\nasync function writeFileOut(dir: string, f: FileOut): Promise<void> {\n const path = join(dir, f.path);\n await mkdir(dirname(path), { recursive: true });\n await writeFile(path, f.content);\n}\n\nasync function exists(path: string): Promise<boolean> {\n try {\n await access(path);\n return true;\n } catch {\n return false;\n }\n}\n\nasync function isEmptyish(dir: string): Promise<boolean> {\n try {\n const entries = await readdir(dir);\n return entries.filter((e) => e !== '.git' && e !== '.DS_Store').length === 0;\n } catch {\n return true; // does not exist yet\n }\n}\n\n/** Merge `scripts` and `devDependencies` into a package.json, seeding one if absent. */\nasync function patchPackageJson(\n dir: string,\n name: string,\n patch: { scripts?: Record<string, string>; devDependencies?: Record<string, string> },\n): Promise<void> {\n const path = join(dir, 'package.json');\n let pkg: Record<string, unknown> = {};\n try {\n pkg = JSON.parse(await readFile(path, 'utf8'));\n } catch {\n pkg = { name, version: '0.1.0', private: true };\n }\n pkg.scripts = { ...(pkg.scripts as object), ...patch.scripts };\n if (patch.devDependencies) {\n pkg.devDependencies = { ...(pkg.devDependencies as object), ...patch.devDependencies };\n }\n await writeFile(path, `${JSON.stringify(pkg, null, 2)}\\n`);\n}\n\nexport async function scaffold(input: ScaffoldInput): Promise<ScaffoldResult> {\n const dir = resolve(input.dir);\n if (!(await isEmptyish(dir))) {\n throw new Error(`${dir} is not empty.`);\n }\n await mkdir(dir, { recursive: true });\n\n const config = buildProjectConfig({\n cwd: dir,\n name: input.name,\n runtime: input.template.runtime,\n host: input.host,\n folder: input.folder,\n });\n\n // ---- framework skeleton ----\n if (input.template.viaLmjs) {\n // `lm create <name>` fills the CURRENT directory (the name is only used for\n // [ProjectName] substitution, not as a subdirectory).\n const code = await run('npx', ['--yes', '@lmjs/cli', 'create', input.name], dir);\n if (code !== 0 || !(await exists(join(dir, 'src', 'index.html')))) {\n throw new Error(\n 'Could not scaffold the LumenJS skeleton (npx @lmjs/cli create failed). Is npm online?',\n );\n }\n\n await patchPackageJson(dir, input.name, {\n scripts: {\n dev: 'lm serve',\n build: 'lm build --serverless',\n deploy: 'lm build --serverless && roxyon deploy',\n },\n devDependencies: { '@lmjs/cli': '^1.0.51' },\n });\n\n if (input.template.baas) {\n for (const f of baasOverlayFiles()) await writeFileOut(dir, f);\n // Load the RX SDK + our client before index.js.\n const htmlPath = join(dir, 'src', 'index.html');\n try {\n const html = await readFile(htmlPath, 'utf8');\n if (!html.includes('rxjs')) {\n await writeFile(\n htmlPath,\n html.replace(\n '<script src=\"/js/bea.js\"',\n '<script src=\"https://cdn.roxyon.com/libs/rxjs/1.0.0/rx.js\"></script>\\n ' +\n '<script src=\"/roxyon-baas.js\"></script>\\n <script src=\"/js/bea.js\"',\n ),\n );\n }\n } catch {\n /* template shape changed upstream — the file is still copied, just not linked */\n }\n }\n } else {\n for (const f of nodeTemplateFiles(input.name)) await writeFileOut(dir, f);\n }\n\n // ---- Roxyon overlay (every template) ----\n for (const f of agentFiles(config, { baas: input.template.baas })) {\n await writeFileOut(dir, f);\n }\n await saveProjectConfig(config, dir);\n\n // ---- .gitignore ----\n const gitignore = join(dir, '.gitignore');\n const existing = await readFile(gitignore, 'utf8').catch(() => '');\n if (!existing.includes('node_modules')) {\n await writeFile(gitignore, `${existing}${existing ? '\\n' : ''}node_modules\\ndist\\n.DS_Store\\n`);\n }\n\n // ---- install ----\n let installed = false;\n if (input.install) {\n const code = await run('npm', ['install'], dir);\n installed = code === 0;\n }\n\n return { dir, config, installed };\n}\n","import type { ProjectConfig } from '@roxyon/deploy-core';\n\nexport interface FileOut {\n path: string;\n content: string;\n}\n\n/**\n * The files every scaffolded project gets on top of its framework skeleton, so\n * any AI assistant — and any human — knows how to build and deploy it.\n */\nexport function agentFiles(config: ProjectConfig, opts: { baas: boolean }): FileOut[] {\n const isLumen = config.runtime === 'lumen';\n const dev = isLumen ? 'npm run dev' : `(your app: ${config.start ?? 'npm run start'})`;\n const build = config.build ? config.build : '(no build step)';\n\n const agents = `# AGENTS.md\n\nWorking notes for AI assistants and humans. Also read as \\`CLAUDE.md\\`,\n\\`GEMINI.md\\`, \\`.cursor/rules/roxyon.mdc\\`, \\`.github/copilot-instructions.md\\`.\n\n## This project\n\n- **${config.name}** — a ${config.runtime} project deployed on **Roxyon**.\n- Config: \\`roxyon.json\\` (host \\`${config.host}\\`${config.folder ? `, folder \\`${config.folder}\\`` : ''}).\n- Local dev: \\`${dev}\\`\n- Build: \\`${build}\\`\n\n${\n isLumen\n ? `## LumenJS (V1) — the rules that bite\n\nVanilla JS/HTML/CSS, **no build step** for the framework. \\`.view\\` files under\n\\`src/views/\\` are one per route. Full reference: https://lumenjs.com/llms/lumenjs.md\n\n- State is \\`var\\` (never \\`let\\`/\\`const\\`) in a \\`<script>\\` in the view; reactive\n only when named in \\`bind=\"name\"\\` on an ancestor element.\n- **Never nest a \\`bind\\` inside another \\`bind\\`** — the inner subtree stops\n rendering, interpolations come out empty.\n- \\`bind\\` renders its descendants, not the element's own attributes — put it on\n the parent of whatever interpolates.\n- \\`:for\\` does not resolve the loop variable in nested loops or in an element's\n own plain attributes. Build that markup as a JS string and inject with\n \\`.html()\\`. Backtick the moustache on a \\`:for\\` element's own attribute.\n- A \\`{{ fn() }}\\` moustache runs once and goes stale — compute into a \\`var\\`.\n- Forms: \\`o-sub\\` / \\`b-sub\\` on the \\`<form>\\`. There is no \\`v-model\\`.\n`\n : `## Runtime\n\n${config.runtime} app. \\`PORT\\` and \\`HOST\\` are set by the platform — read them\nfrom the environment, never hard-code or override them. The start command is\n\\`${config.start ?? 'npm run start'}\\` (see \\`roxyon.json\\`).\n`\n}${\n opts.baas\n ? `\n## Roxyon BaaS\n\nConfigured in \\`src/roxyon-baas.js\\`. Reference: https://roxyon.com/llms/baas.md\n\n- **A failed write returns HTTP 200 with an \\`error\\` field** — check for it\n before treating any create/update/delete as successful.\n- The \\`in\\` operator takes a comma-joined string, not an array.\n- Auth: \\`rx.login({Email,Password})\\`, \\`rx.me()\\`, \\`rx.logout()\\`.\n`\n : ''\n}\n## Deploy\n\nDo **not** SFTP files or edit them on the server. Deploy with:\n\n\\`\\`\\`bash\nroxyon deploy # or the roxyon_deploy MCP tool (dry run, then confirm:true)\n\\`\\`\\`\n\n- Env vars live on the platform: \\`roxyon env set KEY=value\\`, then redeploy.\n- Git push-to-deploy: \\`roxyon link <repo-url>\\`.\n- First deploy creates the app automatically.\n`;\n\n const llmsTxt = `# ${config.name}\n\n> A ${config.runtime} project on Roxyon. Deploy with \\`roxyon deploy\\`.\n\n## Docs\n\n- [How to build & deploy](AGENTS.md)\n${isLumen ? '- [LumenJS reference](https://lumenjs.com/llms/lumenjs.md)\\n' : ''}${opts.baas ? '- [Roxyon BaaS reference](https://roxyon.com/llms/baas.md)\\n' : ''}- [Deploying to Roxyon](https://roxyon.com/llms/deploy.md)\n`;\n\n const cursorRule = `---\ndescription: How to build and deploy this Roxyon project\nalwaysApply: true\n---\n\n${agents}`;\n\n return [\n { path: 'AGENTS.md', content: agents },\n { path: 'CLAUDE.md', content: 'See [AGENTS.md](AGENTS.md).\\n' },\n { path: 'GEMINI.md', content: 'See [AGENTS.md](AGENTS.md).\\n' },\n { path: 'llms.txt', content: llmsTxt },\n { path: '.cursor/rules/roxyon.mdc', content: cursorRule },\n { path: '.github/copilot-instructions.md', content: agents },\n {\n path: '.roxyonignore',\n content: ['node_modules', '.git', '.next', '.venv', 'dist/.cache', '.DS_Store', ''].join(\n '\\n',\n ),\n },\n ];\n}\n","import type { FileOut } from './agent-files.js';\n\nexport type TemplateId = 'lumen' | 'lumen-baas' | 'node';\n\nexport interface Template {\n id: TemplateId;\n label: string;\n runtime: 'lumen' | 'node';\n baas: boolean;\n /** LumenJS templates are scaffolded by @lmjs/cli, then overlaid. */\n viaLmjs: boolean;\n}\n\nexport const TEMPLATES: Record<TemplateId, Template> = {\n lumen: { id: 'lumen', label: 'LumenJS SPA', runtime: 'lumen', baas: false, viaLmjs: true },\n 'lumen-baas': {\n id: 'lumen-baas',\n label: 'LumenJS SPA + Roxyon BaaS (auth + data example)',\n runtime: 'lumen',\n baas: true,\n viaLmjs: true,\n },\n node: {\n id: 'node',\n label: 'Node.js app (minimal HTTP server)',\n runtime: 'node',\n baas: false,\n viaLmjs: false,\n },\n};\n\n// ---------------------------------------------------------------------------\n// Node template — bundled inline (no framework, no deps).\n// ---------------------------------------------------------------------------\n\nexport function nodeTemplateFiles(name: string): FileOut[] {\n return [\n {\n path: 'package.json',\n content: `${JSON.stringify(\n {\n name,\n version: '0.1.0',\n private: true,\n type: 'module',\n scripts: {\n start: 'node src/server.js',\n dev: 'node --watch src/server.js',\n },\n engines: { node: '>=20' },\n },\n null,\n 2,\n )}\\n`,\n },\n {\n path: 'src/server.js',\n content: `import { createServer } from 'node:http';\n\n// The platform sets PORT and HOST. Never hard-code or override them.\nconst port = Number(process.env.PORT) || 3000;\nconst host = process.env.HOST || '0.0.0.0';\n\nconst server = createServer((req, res) => {\n if (req.url === '/health') {\n res.writeHead(200, { 'content-type': 'text/plain' });\n res.end('ok');\n return;\n }\n res.writeHead(200, { 'content-type': 'application/json' });\n res.end(JSON.stringify({ hello: 'from ${name} on Roxyon', url: req.url }));\n});\n\nserver.listen(port, host, () => {\n console.log(\\`listening on http://\\${host}:\\${port}\\`);\n});\n`,\n },\n {\n path: 'README.md',\n content: `# ${name}\n\nA Node.js app on Roxyon.\n\n\\`\\`\\`bash\nnpm install\nnpm run dev # local, http://localhost:3000\nroxyon deploy # to Roxyon (first run creates the app)\n\\`\\`\\`\n\nSee [AGENTS.md](AGENTS.md).\n`,\n },\n ];\n}\n\n// ---------------------------------------------------------------------------\n// LumenJS BaaS overlay — added on top of the @lmjs/cli skeleton.\n// ---------------------------------------------------------------------------\n\nexport function baasOverlayFiles(): FileOut[] {\n return [\n {\n path: 'src/roxyon-baas.js',\n content: `// Roxyon BaaS client. The RX SDK is loaded in index.html from the CDN.\n//\n// A FAILED WRITE RESOLVES with an { error } field (top-level or inside\n// results[]) — it does NOT reject. Always run rxError() on a write response.\nwindow.rx = new RX({\n 'Application-ID': 'YOUR_APPLICATION_ID',\n 'JavaScript-KEY': 'YOUR_JAVASCRIPT_KEY',\n});\n\nwindow.rxError = function (r) {\n if (!r) return 'No response from the server.';\n if (r.error) return r.error;\n var rows = r.results;\n if (Object.prototype.toString.call(rows) === '[object Array]') {\n for (var i = 0; i < rows.length; i++) if (rows[i] && rows[i].error) return rows[i].error;\n }\n return '';\n};\n`,\n },\n {\n path: 'src/views/items.view',\n content: `<div class=\"ph50 pv30\" bind=\"items\">\n <div class=\"fs20 b mb20\">Items ({{items.length}})</div>\n <form o-sub=\"addItem\" class=\"mb20\">\n <input name=\"Title\" placeholder=\"New item\" required class=\"p10\" />\n <button type=\"submit\" class=\"p10\">Add</button>\n </form>\n <div :if=\"msg != ''\" class=\"cr mb10\">{{msg}}</div>\n <div :for=\"items as it\">\n <div class=\"pv5\">{{it.Title}}</div>\n </div>\n</div>\n\n<script>\n // 'var' — not let/const. Reactive because it's named in bind=\"items\".\n var items = [];\n var msg = '';\n\n function loadItems() {\n rx.get('/Items', { fields: 'objectId,Title', order: '-createdAt', limit: 50 }).then(function (r) {\n var err = rxError(r);\n if (err) { msg = err; return; }\n items = r.results || [];\n });\n }\n\n function addItem(f, d) {\n msg = '';\n rx.post('/Items', { Title: d.Title }).then(function (r) {\n var err = rxError(r); // a failed write still lands here\n if (err) { msg = err; return; }\n f.find('[name=\"Title\"]').val('');\n loadItems();\n });\n }\n\n loadItems();\n</script>\n\n<settings> { hasFooter: false, hasHeader: false, hasNav: false } </settings>\n`,\n },\n ];\n}\n"],"mappings":"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","names":["resolve","resolve"]}
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "create-roxyon-app",
3
+ "version": "0.1.0",
4
+ "description": "Scaffold a Roxyon project — LumenJS SPA or a Node app — wired for `roxyon deploy`, with AGENTS.md / llms.txt / editor rules so any AI assistant gets it right.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "bin": {
8
+ "create-roxyon-app": "./bin/create-roxyon-app.js"
9
+ },
10
+ "files": [
11
+ "dist",
12
+ "bin",
13
+ "LICENSE"
14
+ ],
15
+ "engines": {
16
+ "node": ">=20"
17
+ },
18
+ "keywords": [
19
+ "roxyon",
20
+ "lumenjs",
21
+ "scaffold",
22
+ "create",
23
+ "starter"
24
+ ],
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "git+https://github.com/bea-technology/roxyon-devtools.git",
28
+ "directory": "packages/create-roxyon-app"
29
+ },
30
+ "dependencies": {
31
+ "@clack/prompts": "^0.7.0",
32
+ "picocolors": "^1.1.1",
33
+ "@roxyon/deploy-core": "0.1.0"
34
+ },
35
+ "devDependencies": {
36
+ "@types/node": "^20.16.11",
37
+ "tsup": "^8.3.0",
38
+ "typescript": "^5.6.3",
39
+ "vitest": "^2.1.3"
40
+ },
41
+ "scripts": {
42
+ "build": "tsup",
43
+ "typecheck": "tsc --noEmit",
44
+ "test": "vitest run"
45
+ }
46
+ }