create-lumiose 0.3.0 → 0.5.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 +28 -0
- package/dist/main.d.ts +8 -1
- package/dist/main.js +24 -5
- package/dist/scaffold.d.ts +12 -0
- package/dist/scaffold.js +63 -13
- package/package.json +5 -4
package/README.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# create-lumiose
|
|
2
|
+
|
|
3
|
+
Creates a [Lumiose](https://itsjavi.com/lumiose/) app.
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
npm create lumiose@latest my-app
|
|
7
|
+
pnpm create lumiose my-app --template ssr
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
| Template | What you get |
|
|
11
|
+
| --------- | ----------------------------------------------------------- |
|
|
12
|
+
| `minimal` | One page |
|
|
13
|
+
| `static` | A layout, pages, a blog with `staticPaths()` and a 404 page |
|
|
14
|
+
| `ssr` | Streaming, a form action, a JSON endpoint and a Node server |
|
|
15
|
+
| `spa` | Pages and a persistent layout rendered in the browser |
|
|
16
|
+
|
|
17
|
+
Every project starts agent-ready: `AGENTS.md`, `CLAUDE.md` and the `lumiose` skill. `--mcp` also
|
|
18
|
+
registers the Lumiose MCP server for Claude Code and Codex. `create-lumiose --help` lists every
|
|
19
|
+
option (`--yes`, `--force`, `--mcp`, `--format json`).
|
|
20
|
+
|
|
21
|
+
Every template sets `lumiose({ csp: "strict" })` in `vite.config.ts`: a strict Content Security
|
|
22
|
+
Policy from the first page ([Security](https://itsjavi.com/lumiose/docs/concepts/security/)).
|
|
23
|
+
|
|
24
|
+
Next: [Getting started](https://itsjavi.com/lumiose/docs/getting-started/).
|
|
25
|
+
|
|
26
|
+
## License
|
|
27
|
+
|
|
28
|
+
[MIT](https://github.com/itsjavi/lumiose/blob/main/LICENSE)
|
package/dist/main.d.ts
CHANGED
|
@@ -7,4 +7,11 @@ export interface CreateIO {
|
|
|
7
7
|
/** `npm_config_user_agent`, for the next-step commands. */
|
|
8
8
|
userAgent?: string;
|
|
9
9
|
}
|
|
10
|
-
export
|
|
10
|
+
export interface CreateOptions {
|
|
11
|
+
/**
|
|
12
|
+
* Offer @lumiose/ui (the `--ui` flag and its question). Default: once the package is on npm
|
|
13
|
+
* (TASK-389); tests turn it on against the workspace package.
|
|
14
|
+
*/
|
|
15
|
+
offerUi?: boolean;
|
|
16
|
+
}
|
|
17
|
+
export declare function main(argv: string[], io?: CreateIO, options?: CreateOptions): Promise<number>;
|
package/dist/main.js
CHANGED
|
@@ -7,8 +7,8 @@ import { relative, resolve } from "node:path";
|
|
|
7
7
|
import { parseArgs } from "node:util";
|
|
8
8
|
import { mcpCommand, mcpNote } from "@lumiose/cli/agents";
|
|
9
9
|
import pkg from "../package.json" with { type: "json" };
|
|
10
|
-
import { DEFAULT_TEMPLATE, scaffold, ScaffoldError, TEMPLATES, } from "./scaffold.js";
|
|
11
|
-
const
|
|
10
|
+
import { DEFAULT_TEMPLATE, scaffold, ScaffoldError, TEMPLATES, UI_PACKAGE, } from "./scaffold.js";
|
|
11
|
+
const usage = (offerUi) => `Usage: create-lumiose [dir] [options]
|
|
12
12
|
|
|
13
13
|
Create a Lumiose app in <dir> (asked for when missing in a terminal; default: lumiose-app).
|
|
14
14
|
|
|
@@ -19,7 +19,11 @@ Options:
|
|
|
19
19
|
--force Write into a non-empty directory (same-name files are overwritten)
|
|
20
20
|
--mcp Register the Lumiose MCP server for Claude Code (.mcp.json) and Codex
|
|
21
21
|
(.codex/config.toml), run with your package manager
|
|
22
|
-
|
|
22
|
+
${offerUi
|
|
23
|
+
? ` --ui Add @lumiose/ui: accessible, unstyled components with the default theme,
|
|
24
|
+
and their agent docs and skill
|
|
25
|
+
`
|
|
26
|
+
: ""} --format <format> human (default) or json: { command: "create", ok, ... } on stdout
|
|
23
27
|
-h, --help Show this help
|
|
24
28
|
-v, --version Show the version
|
|
25
29
|
|
|
@@ -33,7 +37,8 @@ Examples:
|
|
|
33
37
|
create-lumiose my-site -t static --yes --format json
|
|
34
38
|
pnpm create lumiose my-app --mcp
|
|
35
39
|
`;
|
|
36
|
-
export async function main(argv, io = processIO()) {
|
|
40
|
+
export async function main(argv, io = processIO(), options = {}) {
|
|
41
|
+
const offerUi = options.offerUi ?? UI_PACKAGE?.published === true;
|
|
37
42
|
const json = argv.some((arg, index) => arg === "--format=json" || (arg === "--format" && argv[index + 1] === "json"));
|
|
38
43
|
const usageError = (message) => {
|
|
39
44
|
if (json) {
|
|
@@ -54,6 +59,7 @@ export async function main(argv, io = processIO()) {
|
|
|
54
59
|
yes: { type: "boolean", short: "y" },
|
|
55
60
|
force: { type: "boolean" },
|
|
56
61
|
mcp: { type: "boolean" },
|
|
62
|
+
ui: { type: "boolean" },
|
|
57
63
|
format: { type: "string" },
|
|
58
64
|
help: { type: "boolean", short: "h" },
|
|
59
65
|
version: { type: "boolean", short: "v" },
|
|
@@ -68,7 +74,7 @@ export async function main(argv, io = processIO()) {
|
|
|
68
74
|
return usageError(`--format must be "human" or "json", not "${values.format}".`);
|
|
69
75
|
}
|
|
70
76
|
if (values.help) {
|
|
71
|
-
io.write(
|
|
77
|
+
io.write(usage(offerUi));
|
|
72
78
|
return 0;
|
|
73
79
|
}
|
|
74
80
|
if (values.version) {
|
|
@@ -77,6 +83,9 @@ export async function main(argv, io = processIO()) {
|
|
|
77
83
|
}
|
|
78
84
|
if (positionals.length > 1)
|
|
79
85
|
return usageError(`Unexpected argument "${positionals[1]}".`);
|
|
86
|
+
if (values.ui && !offerUi) {
|
|
87
|
+
return usageError("--ui comes with the first release of @lumiose/ui, which is not on npm yet.");
|
|
88
|
+
}
|
|
80
89
|
if (values.template !== undefined && !isTemplate(values.template)) {
|
|
81
90
|
return usageError(`Unknown template "${values.template}". Pick one of: ${TEMPLATES.map((t) => t.name).join(", ")}.`);
|
|
82
91
|
}
|
|
@@ -89,6 +98,11 @@ export async function main(argv, io = processIO()) {
|
|
|
89
98
|
if (template === undefined && ask)
|
|
90
99
|
template = await askTemplate(ask);
|
|
91
100
|
template ??= DEFAULT_TEMPLATE;
|
|
101
|
+
let ui = values.ui ?? false;
|
|
102
|
+
if (values.ui === undefined && offerUi && ask) {
|
|
103
|
+
const answer = await ask("Add @lumiose/ui, accessible UI components with a default theme? (y/N): ");
|
|
104
|
+
ui = /^y(?:es)?$/i.test(answer.trim());
|
|
105
|
+
}
|
|
92
106
|
const pm = packageManager(io.userAgent);
|
|
93
107
|
let result;
|
|
94
108
|
try {
|
|
@@ -98,6 +112,7 @@ export async function main(argv, io = processIO()) {
|
|
|
98
112
|
...(values.name === undefined ? {} : { name: values.name }),
|
|
99
113
|
force: values.force ?? false,
|
|
100
114
|
...(values.mcp ? { mcp: pm } : {}),
|
|
115
|
+
...(ui ? { ui } : {}),
|
|
101
116
|
});
|
|
102
117
|
}
|
|
103
118
|
catch (error) {
|
|
@@ -123,6 +138,7 @@ export async function main(argv, io = processIO()) {
|
|
|
123
138
|
template: result.template.name,
|
|
124
139
|
mode: result.template.mode,
|
|
125
140
|
files: result.files,
|
|
141
|
+
ui,
|
|
126
142
|
next,
|
|
127
143
|
...(values.mcp ? { mcp: { ...mcpCommand(pm), note: mcpNote(pm) } } : {}),
|
|
128
144
|
}));
|
|
@@ -130,6 +146,9 @@ export async function main(argv, io = processIO()) {
|
|
|
130
146
|
else {
|
|
131
147
|
io.write(`\nCreated ${result.name} in ${result.dir} (template: ${result.template.name}, mode: ${result.template.mode}).\n` +
|
|
132
148
|
`Agent-ready: AGENTS.md, CLAUDE.md and the lumiose skill (.claude/skills/lumiose).\n` +
|
|
149
|
+
(ui
|
|
150
|
+
? "UI: @lumiose/ui with its default theme, and the lumiose-ui skill (.claude/skills/lumiose-ui).\n"
|
|
151
|
+
: "") +
|
|
133
152
|
(values.mcp
|
|
134
153
|
? `MCP: .mcp.json (Claude Code) and .codex/config.toml (Codex) run the Lumiose MCP server.\n${mcpNote(pm)}\n`
|
|
135
154
|
: "") +
|
package/dist/scaffold.d.ts
CHANGED
|
@@ -8,6 +8,16 @@ export interface Template {
|
|
|
8
8
|
}
|
|
9
9
|
export declare const TEMPLATES: readonly Template[];
|
|
10
10
|
export declare const DEFAULT_TEMPLATE: TemplateName;
|
|
11
|
+
/**
|
|
12
|
+
* The @lumiose/ui this create-lumiose was released with: it keeps its own versions (decision-54).
|
|
13
|
+
* `published` is false while the package is private (TASK-389): the option stays hidden until then.
|
|
14
|
+
*/
|
|
15
|
+
export declare const UI_PACKAGE: {
|
|
16
|
+
version: string;
|
|
17
|
+
published: boolean;
|
|
18
|
+
} | undefined;
|
|
19
|
+
/** The stylesheets a project with @lumiose/ui imports in its root layout. */
|
|
20
|
+
export declare const UI_STYLESHEETS: string[];
|
|
11
21
|
/** A problem with the target (not empty, invalid name): the CLI exits with 1. */
|
|
12
22
|
export declare class ScaffoldError extends Error {
|
|
13
23
|
name: string;
|
|
@@ -25,6 +35,8 @@ export interface ScaffoldOptions {
|
|
|
25
35
|
* this package manager.
|
|
26
36
|
*/
|
|
27
37
|
mcp?: PackageManager;
|
|
38
|
+
/** Add @lumiose/ui: the dependency, its stylesheet and default theme, and its agent files. */
|
|
39
|
+
ui?: boolean;
|
|
28
40
|
}
|
|
29
41
|
export interface ScaffoldResult {
|
|
30
42
|
/** Absolute target directory. */
|
package/dist/scaffold.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
// Writes a new Lumiose project: a template's `src/` (and extras), the generated package.json,
|
|
2
|
-
// tsconfig.json (extending the generated one, decision-25), vite.config.ts with the template's
|
|
3
|
-
// .gitignore and README, then the agent files (AGENTS.md, CLAUDE.md, the lumiose skill,
|
|
4
|
-
// .gitattributes) from `@lumiose/cli/agents`, shared with `lumiose agents init`.
|
|
2
|
+
// tsconfig.json (extending the generated one, decision-25), vite.config.ts with the template's
|
|
3
|
+
// `lumiose({ mode })` (decision-21) and `csp: "strict"` (decision-56), .gitignore and README, then the agent files (AGENTS.md, CLAUDE.md, the lumiose skill,
|
|
4
|
+
// .gitattributes) from `@lumiose/cli/agents`, shared with `lumiose agents init`. With `ui`, the app
|
|
5
|
+
// also depends on @lumiose/ui, imports its stylesheet and default theme in the root layout, and
|
|
6
|
+
// gets its agent files (decision-64).
|
|
5
7
|
import { existsSync } from "node:fs";
|
|
8
|
+
import { createRequire } from "node:module";
|
|
6
9
|
import { mkdir, readdir, readFile, writeFile } from "node:fs/promises";
|
|
7
10
|
import { basename, dirname, join, relative, resolve, sep } from "node:path";
|
|
8
11
|
import { fileURLToPath } from "node:url";
|
|
@@ -38,6 +41,22 @@ const VERSIONS = {
|
|
|
38
41
|
oxlint: "^1.87.0",
|
|
39
42
|
oxfmt: "^0.72.0",
|
|
40
43
|
};
|
|
44
|
+
/**
|
|
45
|
+
* The @lumiose/ui this create-lumiose was released with: it keeps its own versions (decision-54).
|
|
46
|
+
* `published` is false while the package is private (TASK-389): the option stays hidden until then.
|
|
47
|
+
*/
|
|
48
|
+
export const UI_PACKAGE = readUiPackage();
|
|
49
|
+
function readUiPackage() {
|
|
50
|
+
try {
|
|
51
|
+
const manifest = createRequire(import.meta.url)("@lumiose/ui/package.json");
|
|
52
|
+
return { version: manifest.version, published: manifest.private !== true };
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/** The stylesheets a project with @lumiose/ui imports in its root layout. */
|
|
59
|
+
export const UI_STYLESHEETS = ["@lumiose/ui/styles.css", "@lumiose/ui/themes/default.css"];
|
|
41
60
|
/** A problem with the target (not empty, invalid name): the CLI exits with 1. */
|
|
42
61
|
export class ScaffoldError extends Error {
|
|
43
62
|
name = "ScaffoldError";
|
|
@@ -47,6 +66,9 @@ export async function scaffold(options) {
|
|
|
47
66
|
const template = TEMPLATES.find((candidate) => candidate.name === options.template);
|
|
48
67
|
if (!template)
|
|
49
68
|
throw new ScaffoldError(`Unknown template "${options.template}".`);
|
|
69
|
+
if (options.ui && UI_PACKAGE === undefined) {
|
|
70
|
+
throw new ScaffoldError("@lumiose/ui is not available in this create-lumiose.");
|
|
71
|
+
}
|
|
50
72
|
const name = options.name ?? packageName(basename(dir));
|
|
51
73
|
if (!isValidPackageName(name)) {
|
|
52
74
|
throw new ScaffoldError(`"${name}" is not a valid package name: use lowercase letters, digits, "-", "." and "_" (pass --name).`);
|
|
@@ -60,11 +82,21 @@ export async function scaffold(options) {
|
|
|
60
82
|
const text = await readFile(join(templateDir, file), "utf8");
|
|
61
83
|
files.set(file, text.replaceAll("{{name}}", name));
|
|
62
84
|
}
|
|
63
|
-
|
|
85
|
+
// The root layout imports @lumiose/ui's styles for every page; the minimal template has no
|
|
86
|
+
// layout, so its only page does.
|
|
87
|
+
let uiEntry;
|
|
88
|
+
if (options.ui) {
|
|
89
|
+
uiEntry = files.has("src/routes/layout.stsx")
|
|
90
|
+
? "src/routes/layout.stsx"
|
|
91
|
+
: "src/routes/page.stsx";
|
|
92
|
+
const imports = UI_STYLESHEETS.map((sheet) => `import "${sheet}";\n`).join("");
|
|
93
|
+
files.set(uiEntry, imports + files.get(uiEntry));
|
|
94
|
+
}
|
|
95
|
+
files.set("package.json", packageJson(name, template, options.ui ?? false));
|
|
64
96
|
files.set("tsconfig.json", formatTsconfig());
|
|
65
97
|
files.set("vite.config.ts", viteConfig(template));
|
|
66
98
|
files.set(".gitignore", "node_modules\ndist\n.lumiose\n");
|
|
67
|
-
files.set("README.md", readme(name, template));
|
|
99
|
+
files.set("README.md", readme(name, template, uiEntry));
|
|
68
100
|
for (const [file, text] of files) {
|
|
69
101
|
await mkdir(dirname(join(dir, file)), { recursive: true });
|
|
70
102
|
await writeFile(join(dir, file), text);
|
|
@@ -92,7 +124,7 @@ export function packageName(text) {
|
|
|
92
124
|
export function isValidPackageName(name) {
|
|
93
125
|
return /^(?:@[a-z0-9][a-z0-9._~-]*\/)?[a-z0-9][a-z0-9._~-]*$/.test(name) && name.length <= 214;
|
|
94
126
|
}
|
|
95
|
-
function packageJson(name, template) {
|
|
127
|
+
function packageJson(name, template, ui) {
|
|
96
128
|
const server = template.mode === "server";
|
|
97
129
|
// The adapter serves `lumiose preview` everywhere and is the production server of `ssr`.
|
|
98
130
|
const adapter = { "@lumiose/adapter-node": VERSIONS.lumiose };
|
|
@@ -110,7 +142,11 @@ function packageJson(name, template) {
|
|
|
110
142
|
lint: "lumiose lint",
|
|
111
143
|
fmt: "lumiose fmt",
|
|
112
144
|
},
|
|
113
|
-
dependencies: {
|
|
145
|
+
dependencies: {
|
|
146
|
+
...(server ? adapter : {}),
|
|
147
|
+
...(ui ? { "@lumiose/ui": `^${UI_PACKAGE.version}` } : {}),
|
|
148
|
+
lumiose: VERSIONS.lumiose,
|
|
149
|
+
},
|
|
114
150
|
devDependencies: {
|
|
115
151
|
...(server ? {} : adapter),
|
|
116
152
|
"@lumiose/cli": VERSIONS.lumiose,
|
|
@@ -129,13 +165,21 @@ function viteConfig(template) {
|
|
|
129
165
|
import { defineConfig } from "vite";
|
|
130
166
|
|
|
131
167
|
export default defineConfig({
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
168
|
+
plugins: [
|
|
169
|
+
lumiose({
|
|
170
|
+
// The default mode of every route: "static" (built HTML), "server" (rendered per request) or
|
|
171
|
+
// "spa" (rendered in the browser). A route or layout overrides it with \`export const mode\`.
|
|
172
|
+
mode: "${template.mode}",
|
|
173
|
+
// A strict Content Security Policy: a nonce on pages rendered per request, hashes on
|
|
174
|
+
// prerendered ones. The scripts and styles Lumiose and your JSX write get them.
|
|
175
|
+
csp: "strict",
|
|
176
|
+
}),
|
|
177
|
+
],
|
|
135
178
|
});
|
|
136
179
|
`;
|
|
137
180
|
}
|
|
138
|
-
|
|
181
|
+
/** The project README; `uiEntry` is the file that imports @lumiose/ui's styles, if any. */
|
|
182
|
+
function readme(name, template, uiEntry) {
|
|
139
183
|
const server = template.mode === "server";
|
|
140
184
|
return `# ${name}
|
|
141
185
|
|
|
@@ -153,12 +197,18 @@ ${template.description}. Made with Lumiose (\`create-lumiose --template ${templa
|
|
|
153
197
|
|
|
154
198
|
- \`src/routes/\`: one folder per URL segment, with a \`page.stsx\` page or a \`route.ts\` endpoint;
|
|
155
199
|
\`layout.stsx\` wraps the pages below it. Other files there are colocated modules.
|
|
156
|
-
- \`vite.config.ts\`: \`lumiose({ mode: "${template.mode}" })\` sets the default route mode
|
|
200
|
+
- \`vite.config.ts\`: \`lumiose({ mode: "${template.mode}", csp: "strict" })\` sets the default route mode
|
|
201
|
+
and a strict Content Security Policy.
|
|
157
202
|
- \`.lumiose/\`: generated types and shadow files (git-ignored).
|
|
158
203
|
|
|
159
204
|
\`.stsx\` files are TSX with raw-CSS \`<style>\` blocks. The docs for your Lumiose version are in
|
|
160
205
|
\`node_modules/lumiose/docs\` (\`npx lumiose docs <topic>\`). Coding agents: see \`AGENTS.md\`.
|
|
161
|
-
|
|
206
|
+
${uiEntry === undefined
|
|
207
|
+
? ""
|
|
208
|
+
: `
|
|
209
|
+
UI components come from \`@lumiose/ui\`: accessible and unstyled, with its default theme imported in
|
|
210
|
+
\`${uiEntry}\`. Their docs are in \`node_modules/@lumiose/ui/docs\` (\`npx lumiose docs ui\`).
|
|
211
|
+
`}`;
|
|
162
212
|
}
|
|
163
213
|
async function listFiles(dir, base = dir) {
|
|
164
214
|
const entries = await readdir(dir, { withFileTypes: true });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-lumiose",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Create a Lumiose app: `pnpm create lumiose my-app --template ssr`. Every project starts agent-ready (AGENTS.md, CLAUDE.md, the lumiose skill).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -17,11 +17,12 @@
|
|
|
17
17
|
],
|
|
18
18
|
"type": "module",
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@lumiose/cli": "0.
|
|
21
|
-
"lumiose": "0.
|
|
20
|
+
"@lumiose/cli": "0.5.0",
|
|
21
|
+
"lumiose": "0.5.0"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
|
-
"@lumiose/adapter-node": "0.
|
|
24
|
+
"@lumiose/adapter-node": "0.5.0",
|
|
25
|
+
"@lumiose/ui": "0.0.0",
|
|
25
26
|
"typescript": "^7.0.2",
|
|
26
27
|
"vite": "^8.3.4"
|
|
27
28
|
},
|