@lumerahq/cli 0.10.0 → 0.11.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/dist/chunk-CHRKCAIZ.js +155 -0
- package/dist/{chunk-WRAZC6SJ.js → chunk-HIYM7EM2.js} +17 -1
- package/dist/dev-2HVDP3NX.js +155 -0
- package/dist/index.js +166 -14
- package/dist/init-VNJNSU4Q.js +440 -0
- package/dist/{resources-PGBVCS2K.js → resources-GTG3QMVV.js} +2 -4
- package/dist/{run-WIRQDYYX.js → run-47GF5VVS.js} +2 -4
- package/dist/templates-6KMZWOYH.js +194 -0
- package/package.json +1 -1
- package/dist/chunk-2CR762KB.js +0 -18
- package/dist/dev-BHBF4ECH.js +0 -87
- package/dist/init-EDSRR3YM.js +0 -360
- package/templates/default/ARCHITECTURE.md +0 -80
- package/templates/default/CLAUDE.md +0 -238
- package/templates/default/README.md +0 -59
- package/templates/default/biome.json +0 -38
- package/templates/default/gitignore +0 -9
- package/templates/default/index.html +0 -13
- package/templates/default/package.json.hbs +0 -47
- package/templates/default/platform/automations/.gitkeep +0 -0
- package/templates/default/platform/collections/example_items.json +0 -26
- package/templates/default/platform/hooks/.gitkeep +0 -0
- package/templates/default/pyproject.toml.hbs +0 -14
- package/templates/default/scripts/seed-demo.py +0 -35
- package/templates/default/src/components/Sidebar.tsx +0 -82
- package/templates/default/src/components/StatCard.tsx +0 -25
- package/templates/default/src/components/layout.tsx +0 -13
- package/templates/default/src/lib/queries.ts +0 -27
- package/templates/default/src/main.tsx +0 -131
- package/templates/default/src/routes/__root.tsx +0 -10
- package/templates/default/src/routes/index.tsx +0 -88
- package/templates/default/src/routes/settings.tsx +0 -21
- package/templates/default/src/styles.css +0 -44
- package/templates/default/tsconfig.json +0 -23
- package/templates/default/vite.config.ts +0 -28
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
import {
|
|
2
|
+
installAllSkills,
|
|
3
|
+
syncClaudeMd
|
|
4
|
+
} from "./chunk-UP3GV4HN.js";
|
|
5
|
+
import {
|
|
6
|
+
listAllTemplates,
|
|
7
|
+
resolveTemplate
|
|
8
|
+
} from "./chunk-CHRKCAIZ.js";
|
|
9
|
+
import "./chunk-D2BLSEGR.js";
|
|
10
|
+
|
|
11
|
+
// src/commands/init.ts
|
|
12
|
+
import pc2 from "picocolors";
|
|
13
|
+
import prompts from "prompts";
|
|
14
|
+
import { execSync } from "child_process";
|
|
15
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, rmSync } from "fs";
|
|
16
|
+
import { join, resolve } from "path";
|
|
17
|
+
|
|
18
|
+
// src/lib/spinner.ts
|
|
19
|
+
import pc from "picocolors";
|
|
20
|
+
var frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
21
|
+
function spinner(message) {
|
|
22
|
+
if (!process.stdout.isTTY) {
|
|
23
|
+
process.stdout.write(` ${message}
|
|
24
|
+
`);
|
|
25
|
+
return (doneMessage) => {
|
|
26
|
+
if (doneMessage) {
|
|
27
|
+
process.stdout.write(` ${doneMessage}
|
|
28
|
+
`);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
let i = 0;
|
|
33
|
+
const interval = setInterval(() => {
|
|
34
|
+
const frame = frames[i % frames.length];
|
|
35
|
+
process.stdout.write(`\r ${pc.cyan(frame)} ${message}`);
|
|
36
|
+
i++;
|
|
37
|
+
}, 80);
|
|
38
|
+
return (doneMessage) => {
|
|
39
|
+
clearInterval(interval);
|
|
40
|
+
process.stdout.write(`\r${" ".repeat(message.length + 10)}\r`);
|
|
41
|
+
if (doneMessage) {
|
|
42
|
+
process.stdout.write(` ${doneMessage}
|
|
43
|
+
`);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// src/commands/init.ts
|
|
49
|
+
function toTitleCase(str) {
|
|
50
|
+
return str.split(/[-_]/).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
51
|
+
}
|
|
52
|
+
var TEMPLATE_DEFAULTS = {
|
|
53
|
+
projectName: "my-lumera-app",
|
|
54
|
+
projectTitle: "My Lumera App"
|
|
55
|
+
};
|
|
56
|
+
function processTemplate(content, vars) {
|
|
57
|
+
let result = content;
|
|
58
|
+
for (const [key, value] of Object.entries(vars)) {
|
|
59
|
+
const defaultValue = TEMPLATE_DEFAULTS[key];
|
|
60
|
+
if (defaultValue && defaultValue !== value) {
|
|
61
|
+
result = result.replaceAll(defaultValue, value);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return result;
|
|
65
|
+
}
|
|
66
|
+
var TEMPLATE_EXCLUDE = /* @__PURE__ */ new Set(["template.json"]);
|
|
67
|
+
function copyDir(src, dest, vars, isRoot = true) {
|
|
68
|
+
if (!existsSync(dest)) {
|
|
69
|
+
mkdirSync(dest, { recursive: true });
|
|
70
|
+
}
|
|
71
|
+
for (const entry of readdirSync(src, { withFileTypes: true })) {
|
|
72
|
+
if (isRoot && TEMPLATE_EXCLUDE.has(entry.name)) continue;
|
|
73
|
+
const srcPath = join(src, entry.name);
|
|
74
|
+
const destPath = join(dest, entry.name);
|
|
75
|
+
if (entry.isDirectory()) {
|
|
76
|
+
copyDir(srcPath, destPath, vars, false);
|
|
77
|
+
} else {
|
|
78
|
+
const content = readFileSync(srcPath, "utf-8");
|
|
79
|
+
const processed = processTemplate(content, vars);
|
|
80
|
+
writeFileSync(destPath, processed);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function isGitInstalled() {
|
|
85
|
+
try {
|
|
86
|
+
execSync("git --version", { stdio: "ignore" });
|
|
87
|
+
return true;
|
|
88
|
+
} catch {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function initGitRepo(targetDir, projectName) {
|
|
93
|
+
try {
|
|
94
|
+
execSync("git init", { cwd: targetDir, stdio: "ignore" });
|
|
95
|
+
execSync("git add -A", { cwd: targetDir, stdio: "ignore" });
|
|
96
|
+
execSync(`git commit -m "Initial commit: scaffold ${projectName}"`, {
|
|
97
|
+
cwd: targetDir,
|
|
98
|
+
stdio: "ignore"
|
|
99
|
+
});
|
|
100
|
+
return true;
|
|
101
|
+
} catch {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function isUvInstalled() {
|
|
106
|
+
try {
|
|
107
|
+
execSync("uv --version", { stdio: "ignore" });
|
|
108
|
+
return true;
|
|
109
|
+
} catch {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function installUv() {
|
|
114
|
+
try {
|
|
115
|
+
try {
|
|
116
|
+
execSync("curl -LsSf https://astral.sh/uv/install.sh | sh", {
|
|
117
|
+
stdio: "ignore",
|
|
118
|
+
shell: "/bin/bash"
|
|
119
|
+
});
|
|
120
|
+
return true;
|
|
121
|
+
} catch {
|
|
122
|
+
execSync("wget -qO- https://astral.sh/uv/install.sh | sh", {
|
|
123
|
+
stdio: "ignore",
|
|
124
|
+
shell: "/bin/bash"
|
|
125
|
+
});
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
} catch {
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
function createPythonVenv(targetDir) {
|
|
133
|
+
try {
|
|
134
|
+
execSync("uv venv", { cwd: targetDir, stdio: "ignore" });
|
|
135
|
+
execSync("uv pip install lumera", { cwd: targetDir, stdio: "ignore" });
|
|
136
|
+
return true;
|
|
137
|
+
} catch {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
function detectEditor() {
|
|
142
|
+
const envEditor = process.env.VISUAL || process.env.EDITOR;
|
|
143
|
+
if (envEditor) return envEditor;
|
|
144
|
+
const editors = ["cursor", "code", "zed", "subl"];
|
|
145
|
+
for (const editor of editors) {
|
|
146
|
+
try {
|
|
147
|
+
execSync(`which ${editor}`, { stdio: "ignore" });
|
|
148
|
+
return editor;
|
|
149
|
+
} catch {
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
function openInEditor(targetDir) {
|
|
156
|
+
const editor = detectEditor();
|
|
157
|
+
if (!editor) {
|
|
158
|
+
console.log(pc2.yellow(" \u26A0"), pc2.dim("No editor detected. Set VISUAL or EDITOR env var."));
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
try {
|
|
162
|
+
execSync(`${editor} "${targetDir}"`, { stdio: "ignore" });
|
|
163
|
+
console.log(pc2.green(" \u2713"), pc2.dim(`Opened in ${editor}`));
|
|
164
|
+
} catch {
|
|
165
|
+
console.log(pc2.yellow(" \u26A0"), pc2.dim(`Failed to open in ${editor}`));
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
function parseArgs(args) {
|
|
169
|
+
const result = {
|
|
170
|
+
projectName: void 0,
|
|
171
|
+
directory: void 0,
|
|
172
|
+
template: void 0,
|
|
173
|
+
install: true,
|
|
174
|
+
open: false,
|
|
175
|
+
yes: false,
|
|
176
|
+
force: false,
|
|
177
|
+
help: false
|
|
178
|
+
};
|
|
179
|
+
for (let i = 0; i < args.length; i++) {
|
|
180
|
+
const arg = args[i];
|
|
181
|
+
if (arg === "--help" || arg === "-h") {
|
|
182
|
+
result.help = true;
|
|
183
|
+
} else if (arg === "--no-install") {
|
|
184
|
+
result.install = false;
|
|
185
|
+
} else if (arg === "--open" || arg === "-o") {
|
|
186
|
+
result.open = true;
|
|
187
|
+
} else if (arg === "--yes" || arg === "-y") {
|
|
188
|
+
result.yes = true;
|
|
189
|
+
} else if (arg === "--force" || arg === "-f") {
|
|
190
|
+
result.force = true;
|
|
191
|
+
} else if (arg === "--dir" || arg === "-d") {
|
|
192
|
+
result.directory = args[++i];
|
|
193
|
+
} else if (arg === "--template" || arg === "-t") {
|
|
194
|
+
result.template = args[++i];
|
|
195
|
+
} else if (!arg.startsWith("-") && !result.projectName) {
|
|
196
|
+
result.projectName = arg;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return result;
|
|
200
|
+
}
|
|
201
|
+
function showHelp() {
|
|
202
|
+
console.log(`
|
|
203
|
+
${pc2.dim("Usage:")}
|
|
204
|
+
lumera init [name] [options]
|
|
205
|
+
|
|
206
|
+
${pc2.dim("Description:")}
|
|
207
|
+
Scaffold a new Lumera project from a template.
|
|
208
|
+
|
|
209
|
+
${pc2.dim("Options:")}
|
|
210
|
+
--template, -t <name> Template to use (run ${pc2.cyan("lumera templates")} to see options)
|
|
211
|
+
--yes, -y Non-interactive mode (requires project name)
|
|
212
|
+
--dir, -d <path> Target directory (defaults to project name)
|
|
213
|
+
--force, -f Overwrite existing directory without prompting
|
|
214
|
+
--no-install Skip dependency installation
|
|
215
|
+
--open, -o Open project in editor after scaffolding
|
|
216
|
+
--help, -h Show this help
|
|
217
|
+
|
|
218
|
+
${pc2.dim("Examples:")}
|
|
219
|
+
lumera init my-app # Interactive mode
|
|
220
|
+
lumera init my-app -t invoice-processing # Use a specific template
|
|
221
|
+
lumera init my-app -y # Non-interactive (default template)
|
|
222
|
+
lumera init my-app -t invoice-processing -y -o # Full non-interactive + open editor
|
|
223
|
+
`);
|
|
224
|
+
}
|
|
225
|
+
async function init(args) {
|
|
226
|
+
const opts = parseArgs(args);
|
|
227
|
+
if (opts.help) {
|
|
228
|
+
showHelp();
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
console.log();
|
|
232
|
+
console.log(pc2.cyan(pc2.bold(" Create Lumera App")));
|
|
233
|
+
console.log();
|
|
234
|
+
let projectName = opts.projectName;
|
|
235
|
+
let directory = opts.directory;
|
|
236
|
+
const nonInteractive = opts.yes;
|
|
237
|
+
if (nonInteractive && !projectName) {
|
|
238
|
+
console.log(pc2.red(" Error: Project name is required in non-interactive mode"));
|
|
239
|
+
console.log(pc2.dim(" Usage: lumera init <name> -y"));
|
|
240
|
+
process.exit(1);
|
|
241
|
+
}
|
|
242
|
+
let templateName = opts.template;
|
|
243
|
+
if (!templateName && !nonInteractive) {
|
|
244
|
+
try {
|
|
245
|
+
const stop = spinner("Fetching templates...");
|
|
246
|
+
const available = await listAllTemplates();
|
|
247
|
+
stop();
|
|
248
|
+
if (available.length > 1) {
|
|
249
|
+
const response = await prompts({
|
|
250
|
+
type: "select",
|
|
251
|
+
name: "template",
|
|
252
|
+
message: "Choose a template",
|
|
253
|
+
choices: available.map((t) => ({
|
|
254
|
+
title: `${t.title} ${pc2.dim(`(${t.name})`)}`,
|
|
255
|
+
description: t.description,
|
|
256
|
+
value: t.name
|
|
257
|
+
})),
|
|
258
|
+
initial: 0
|
|
259
|
+
});
|
|
260
|
+
if (!response.template) {
|
|
261
|
+
console.log(pc2.red("Cancelled"));
|
|
262
|
+
process.exit(1);
|
|
263
|
+
}
|
|
264
|
+
templateName = response.template;
|
|
265
|
+
}
|
|
266
|
+
} catch {
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
if (!templateName) {
|
|
270
|
+
templateName = "default";
|
|
271
|
+
}
|
|
272
|
+
const stopResolve = spinner("Resolving template...");
|
|
273
|
+
const templateDir = await resolveTemplate(templateName);
|
|
274
|
+
stopResolve();
|
|
275
|
+
if (!projectName) {
|
|
276
|
+
const response = await prompts({
|
|
277
|
+
type: "text",
|
|
278
|
+
name: "projectName",
|
|
279
|
+
message: "What is your project name?",
|
|
280
|
+
initial: "my-lumera-app",
|
|
281
|
+
validate: (value) => {
|
|
282
|
+
if (!value) return "Project name is required";
|
|
283
|
+
if (!/^[a-z0-9-]+$/.test(value)) {
|
|
284
|
+
return "Use lowercase letters, numbers, and hyphens only";
|
|
285
|
+
}
|
|
286
|
+
return true;
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
if (!response.projectName) {
|
|
290
|
+
console.log(pc2.red("Cancelled"));
|
|
291
|
+
process.exit(1);
|
|
292
|
+
}
|
|
293
|
+
projectName = response.projectName;
|
|
294
|
+
}
|
|
295
|
+
if (!/^[a-z0-9-]+$/.test(projectName)) {
|
|
296
|
+
console.log(pc2.red(" Error: Project name must use lowercase letters, numbers, and hyphens only"));
|
|
297
|
+
process.exit(1);
|
|
298
|
+
}
|
|
299
|
+
if (!directory) {
|
|
300
|
+
if (nonInteractive) {
|
|
301
|
+
directory = projectName;
|
|
302
|
+
} else {
|
|
303
|
+
const response = await prompts({
|
|
304
|
+
type: "text",
|
|
305
|
+
name: "directory",
|
|
306
|
+
message: "Where should we create the project?",
|
|
307
|
+
initial: projectName
|
|
308
|
+
});
|
|
309
|
+
if (!response.directory) {
|
|
310
|
+
console.log(pc2.red("Cancelled"));
|
|
311
|
+
process.exit(1);
|
|
312
|
+
}
|
|
313
|
+
directory = response.directory;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
const projectTitle = toTitleCase(projectName);
|
|
317
|
+
const targetDir = resolve(process.cwd(), directory);
|
|
318
|
+
if (existsSync(targetDir)) {
|
|
319
|
+
if (nonInteractive) {
|
|
320
|
+
if (opts.force) {
|
|
321
|
+
rmSync(targetDir, { recursive: true });
|
|
322
|
+
} else {
|
|
323
|
+
console.log(pc2.red(` Error: Directory ${directory} already exists`));
|
|
324
|
+
console.log(pc2.dim(" Use --force (-f) to overwrite"));
|
|
325
|
+
process.exit(1);
|
|
326
|
+
}
|
|
327
|
+
} else {
|
|
328
|
+
const { overwrite } = await prompts({
|
|
329
|
+
type: "confirm",
|
|
330
|
+
name: "overwrite",
|
|
331
|
+
message: `Directory ${directory} already exists. Overwrite?`,
|
|
332
|
+
initial: false
|
|
333
|
+
});
|
|
334
|
+
if (!overwrite) {
|
|
335
|
+
console.log(pc2.red("Cancelled"));
|
|
336
|
+
process.exit(1);
|
|
337
|
+
}
|
|
338
|
+
rmSync(targetDir, { recursive: true });
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
mkdirSync(targetDir, { recursive: true });
|
|
342
|
+
console.log();
|
|
343
|
+
if (templateName !== "default") {
|
|
344
|
+
console.log(pc2.dim(` Creating ${projectName} from template ${pc2.cyan(templateName)}...`));
|
|
345
|
+
} else {
|
|
346
|
+
console.log(pc2.dim(` Creating ${projectName}...`));
|
|
347
|
+
}
|
|
348
|
+
console.log();
|
|
349
|
+
const vars = {
|
|
350
|
+
projectName,
|
|
351
|
+
projectTitle
|
|
352
|
+
};
|
|
353
|
+
copyDir(templateDir, targetDir, vars);
|
|
354
|
+
function listFiles(dir, prefix = "") {
|
|
355
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
356
|
+
const relativePath = prefix + entry.name;
|
|
357
|
+
if (entry.isDirectory()) {
|
|
358
|
+
listFiles(join(dir, entry.name), relativePath + "/");
|
|
359
|
+
} else {
|
|
360
|
+
console.log(pc2.green(" \u2713"), pc2.dim(relativePath));
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
listFiles(targetDir);
|
|
365
|
+
if (isGitInstalled()) {
|
|
366
|
+
const stopGit = spinner("Initializing git repository...");
|
|
367
|
+
if (initGitRepo(targetDir, projectName)) {
|
|
368
|
+
stopGit(pc2.green("\u2713") + pc2.dim(" Git repository initialized with initial commit"));
|
|
369
|
+
} else {
|
|
370
|
+
stopGit(pc2.yellow("\u26A0") + pc2.dim(" Failed to initialize git repository"));
|
|
371
|
+
}
|
|
372
|
+
} else {
|
|
373
|
+
console.log(pc2.yellow(" \u26A0"), pc2.dim("Git not found \u2014 skipping repository initialization"));
|
|
374
|
+
}
|
|
375
|
+
let uvAvailable = isUvInstalled();
|
|
376
|
+
if (!uvAvailable) {
|
|
377
|
+
const stopUv = spinner("Installing uv (Python package manager)...");
|
|
378
|
+
if (installUv()) {
|
|
379
|
+
stopUv(pc2.green("\u2713") + pc2.dim(" uv installed successfully"));
|
|
380
|
+
uvAvailable = true;
|
|
381
|
+
} else {
|
|
382
|
+
stopUv(pc2.yellow("\u26A0") + pc2.dim(" Failed to install uv \u2014 install manually: https://docs.astral.sh/uv/"));
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
if (uvAvailable) {
|
|
386
|
+
const stopVenv = spinner("Creating Python venv with Lumera SDK...");
|
|
387
|
+
if (createPythonVenv(targetDir)) {
|
|
388
|
+
stopVenv(pc2.green("\u2713") + pc2.dim(" Python venv created (.venv/) with lumera SDK"));
|
|
389
|
+
} else {
|
|
390
|
+
stopVenv(pc2.yellow("\u26A0") + pc2.dim(" Failed to create Python venv"));
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
if (opts.install) {
|
|
394
|
+
const stopInstall = spinner("Installing dependencies...");
|
|
395
|
+
try {
|
|
396
|
+
execSync("pnpm install", { cwd: targetDir, stdio: "ignore" });
|
|
397
|
+
stopInstall(pc2.green("\u2713") + pc2.dim(" Dependencies installed"));
|
|
398
|
+
} catch {
|
|
399
|
+
stopInstall(pc2.yellow("\u26A0") + pc2.dim(" Failed to install dependencies"));
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
const stopSkills = spinner("Installing Lumera skills for AI agents...");
|
|
403
|
+
try {
|
|
404
|
+
const { installed, failed } = await installAllSkills(targetDir);
|
|
405
|
+
if (failed > 0) {
|
|
406
|
+
stopSkills(pc2.yellow("\u26A0") + pc2.dim(` Installed ${installed} skills (${failed} failed)`));
|
|
407
|
+
} else {
|
|
408
|
+
stopSkills(pc2.green("\u2713") + pc2.dim(` ${installed} Lumera skills installed`));
|
|
409
|
+
}
|
|
410
|
+
syncClaudeMd(targetDir);
|
|
411
|
+
if (isGitInstalled()) {
|
|
412
|
+
try {
|
|
413
|
+
execSync('git add -A && git commit -m "chore: install lumera skills"', {
|
|
414
|
+
cwd: targetDir,
|
|
415
|
+
stdio: "ignore"
|
|
416
|
+
});
|
|
417
|
+
} catch {
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
} catch (err) {
|
|
421
|
+
stopSkills(pc2.yellow("\u26A0") + pc2.dim(` Failed to install skills: ${err}`));
|
|
422
|
+
}
|
|
423
|
+
console.log();
|
|
424
|
+
console.log(pc2.green(pc2.bold(" Done!")), "Next steps:");
|
|
425
|
+
console.log();
|
|
426
|
+
console.log(pc2.cyan(` cd ${directory}`));
|
|
427
|
+
if (!opts.install) {
|
|
428
|
+
console.log(pc2.cyan(" pnpm install"));
|
|
429
|
+
}
|
|
430
|
+
console.log(pc2.cyan(" lumera login"));
|
|
431
|
+
console.log(pc2.cyan(" lumera dev"));
|
|
432
|
+
console.log();
|
|
433
|
+
if (opts.open) {
|
|
434
|
+
openInEditor(targetDir);
|
|
435
|
+
console.log();
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
export {
|
|
439
|
+
init
|
|
440
|
+
};
|
|
@@ -2,11 +2,9 @@ import {
|
|
|
2
2
|
deploy
|
|
3
3
|
} from "./chunk-CDZZ3JYU.js";
|
|
4
4
|
import {
|
|
5
|
-
createApiClient
|
|
6
|
-
} from "./chunk-WRAZC6SJ.js";
|
|
7
|
-
import {
|
|
5
|
+
createApiClient,
|
|
8
6
|
loadEnv
|
|
9
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-HIYM7EM2.js";
|
|
10
8
|
import {
|
|
11
9
|
getToken
|
|
12
10
|
} from "./chunk-NDLYGKS6.js";
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import {
|
|
2
|
+
listAllTemplates
|
|
3
|
+
} from "./chunk-CHRKCAIZ.js";
|
|
4
|
+
|
|
5
|
+
// src/commands/templates.ts
|
|
6
|
+
import pc from "picocolors";
|
|
7
|
+
import { existsSync, readdirSync, readFileSync } from "fs";
|
|
8
|
+
import { join, resolve, extname } from "path";
|
|
9
|
+
function showHelp() {
|
|
10
|
+
console.log(`
|
|
11
|
+
${pc.dim("Usage:")}
|
|
12
|
+
lumera templates <command> [options]
|
|
13
|
+
|
|
14
|
+
${pc.dim("Commands:")}
|
|
15
|
+
list, ls List available templates (default)
|
|
16
|
+
validate [dir] Validate a template directory
|
|
17
|
+
|
|
18
|
+
${pc.dim("Options:")}
|
|
19
|
+
--verbose, -v Show full descriptions (list)
|
|
20
|
+
--help, -h Show this help
|
|
21
|
+
|
|
22
|
+
${pc.dim("Examples:")}
|
|
23
|
+
lumera templates # List available templates
|
|
24
|
+
lumera templates list -v # Show with descriptions
|
|
25
|
+
lumera templates validate ./my-template # Validate a template dir
|
|
26
|
+
lumera init my-app -t invoice-processing # Use a template
|
|
27
|
+
`);
|
|
28
|
+
}
|
|
29
|
+
async function list(args) {
|
|
30
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
31
|
+
showHelp();
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const verbose = args.includes("--verbose") || args.includes("-v");
|
|
35
|
+
try {
|
|
36
|
+
const allTemplates = await listAllTemplates();
|
|
37
|
+
if (process.env.LUMERA_JSON) {
|
|
38
|
+
console.log(JSON.stringify(allTemplates, null, 2));
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
console.log();
|
|
42
|
+
console.log(pc.cyan(pc.bold(" Available Templates")));
|
|
43
|
+
console.log();
|
|
44
|
+
if (allTemplates.length === 0) {
|
|
45
|
+
console.log(pc.dim(" No templates available."));
|
|
46
|
+
console.log();
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const byCategory = /* @__PURE__ */ new Map();
|
|
50
|
+
for (const t of allTemplates) {
|
|
51
|
+
const cat = t.category || "General";
|
|
52
|
+
if (!byCategory.has(cat)) byCategory.set(cat, []);
|
|
53
|
+
byCategory.get(cat).push(t);
|
|
54
|
+
}
|
|
55
|
+
for (const [category, items] of byCategory) {
|
|
56
|
+
console.log(pc.bold(` ${category}`));
|
|
57
|
+
console.log();
|
|
58
|
+
for (const t of items) {
|
|
59
|
+
console.log(` ${pc.green(t.title)} ${pc.dim(`(${t.name})`)}`);
|
|
60
|
+
if (verbose) {
|
|
61
|
+
console.log(` ${pc.dim(t.description)}`);
|
|
62
|
+
console.log();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (!verbose) console.log();
|
|
66
|
+
}
|
|
67
|
+
console.log(pc.dim(` ${allTemplates.length} template${allTemplates.length === 1 ? "" : "s"} available.`));
|
|
68
|
+
console.log();
|
|
69
|
+
console.log(pc.dim(" Usage:"), pc.cyan("lumera init <name> --template <template-name>"));
|
|
70
|
+
console.log();
|
|
71
|
+
} catch (err) {
|
|
72
|
+
console.error(pc.red(` Error: ${err}`));
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", ".venv", "dist", "__pycache__", ".tanstack"]);
|
|
77
|
+
var TEXT_EXTS = /* @__PURE__ */ new Set([".json", ".ts", ".tsx", ".js", ".jsx", ".py", ".md", ".html", ".css", ".yaml", ".yml", ".toml"]);
|
|
78
|
+
function scanForPlaceholders(dir, prefix = "") {
|
|
79
|
+
const results = [];
|
|
80
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
81
|
+
if (SKIP_DIRS.has(entry.name)) continue;
|
|
82
|
+
const fullPath = join(dir, entry.name);
|
|
83
|
+
const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
84
|
+
if (entry.isDirectory()) {
|
|
85
|
+
results.push(...scanForPlaceholders(fullPath, relativePath));
|
|
86
|
+
} else {
|
|
87
|
+
const ext = extname(entry.name);
|
|
88
|
+
if (!TEXT_EXTS.has(ext)) continue;
|
|
89
|
+
if (entry.name === "template.json") continue;
|
|
90
|
+
try {
|
|
91
|
+
const content = readFileSync(fullPath, "utf-8");
|
|
92
|
+
const lines = content.split("\n");
|
|
93
|
+
for (let i = 0; i < lines.length; i++) {
|
|
94
|
+
const matches = lines[i].match(/\{\{[^}]+\}\}/g);
|
|
95
|
+
if (matches) {
|
|
96
|
+
for (const match of matches) {
|
|
97
|
+
if (match.includes(":")) continue;
|
|
98
|
+
results.push({ file: relativePath, line: i + 1, text: match });
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
} catch {
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return results;
|
|
107
|
+
}
|
|
108
|
+
async function validate(args) {
|
|
109
|
+
const dir = args.find((a) => !a.startsWith("-")) || ".";
|
|
110
|
+
const targetDir = resolve(process.cwd(), dir);
|
|
111
|
+
console.log();
|
|
112
|
+
console.log(pc.cyan(pc.bold(" Validate Template")));
|
|
113
|
+
console.log(pc.dim(` Directory: ${targetDir}`));
|
|
114
|
+
console.log();
|
|
115
|
+
const issues = [];
|
|
116
|
+
const warnings = [];
|
|
117
|
+
const templateJsonPath = join(targetDir, "template.json");
|
|
118
|
+
if (!existsSync(templateJsonPath)) {
|
|
119
|
+
issues.push("Missing template.json");
|
|
120
|
+
} else {
|
|
121
|
+
try {
|
|
122
|
+
const meta = JSON.parse(readFileSync(templateJsonPath, "utf-8"));
|
|
123
|
+
const required = ["name", "title", "description", "category"];
|
|
124
|
+
for (const field of required) {
|
|
125
|
+
if (!meta[field]) {
|
|
126
|
+
issues.push(`template.json: missing required field "${field}"`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
} catch {
|
|
130
|
+
issues.push("template.json: invalid JSON");
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (!existsSync(join(targetDir, "package.json"))) {
|
|
134
|
+
issues.push("Missing package.json");
|
|
135
|
+
}
|
|
136
|
+
if (!existsSync(join(targetDir, "platform"))) {
|
|
137
|
+
warnings.push("Missing platform/ directory");
|
|
138
|
+
} else if (!existsSync(join(targetDir, "platform", "collections"))) {
|
|
139
|
+
warnings.push("platform/ has no collections/ subdirectory");
|
|
140
|
+
}
|
|
141
|
+
const stalePatterns = scanForPlaceholders(targetDir);
|
|
142
|
+
for (const match of stalePatterns) {
|
|
143
|
+
warnings.push(`Stale placeholder: ${match.file}:${match.line} \u2014 ${match.text}`);
|
|
144
|
+
}
|
|
145
|
+
if (issues.length === 0 && warnings.length === 0) {
|
|
146
|
+
console.log(pc.green(" \u2713 Template is valid"));
|
|
147
|
+
console.log();
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
if (issues.length > 0) {
|
|
151
|
+
console.log(pc.red(` ${issues.length} error${issues.length > 1 ? "s" : ""}:`));
|
|
152
|
+
for (const issue of issues) {
|
|
153
|
+
console.log(pc.red(` \u2717 ${issue}`));
|
|
154
|
+
}
|
|
155
|
+
console.log();
|
|
156
|
+
}
|
|
157
|
+
if (warnings.length > 0) {
|
|
158
|
+
console.log(pc.yellow(` ${warnings.length} warning${warnings.length > 1 ? "s" : ""}:`));
|
|
159
|
+
for (const warning of warnings) {
|
|
160
|
+
console.log(pc.yellow(` \u26A0 ${warning}`));
|
|
161
|
+
}
|
|
162
|
+
console.log();
|
|
163
|
+
}
|
|
164
|
+
if (issues.length > 0) {
|
|
165
|
+
process.exit(1);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
async function templates(subcommand, args) {
|
|
169
|
+
if (subcommand === "--help" || subcommand === "-h") {
|
|
170
|
+
showHelp();
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
const cmd = subcommand || "list";
|
|
174
|
+
switch (cmd) {
|
|
175
|
+
case "list":
|
|
176
|
+
case "ls":
|
|
177
|
+
await list(args);
|
|
178
|
+
break;
|
|
179
|
+
case "validate":
|
|
180
|
+
await validate(args);
|
|
181
|
+
break;
|
|
182
|
+
default:
|
|
183
|
+
if (cmd.startsWith("-")) {
|
|
184
|
+
await list([cmd, ...args]);
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
console.error(pc.red(`Unknown templates command: ${cmd}`));
|
|
188
|
+
console.error(`Run ${pc.cyan("lumera templates --help")} for usage.`);
|
|
189
|
+
process.exit(1);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
export {
|
|
193
|
+
templates
|
|
194
|
+
};
|
package/package.json
CHANGED
package/dist/chunk-2CR762KB.js
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
// src/lib/env.ts
|
|
2
|
-
import { config } from "dotenv";
|
|
3
|
-
import { existsSync } from "fs";
|
|
4
|
-
import { resolve } from "path";
|
|
5
|
-
function loadEnv(cwd = process.cwd()) {
|
|
6
|
-
const envPath = resolve(cwd, ".env");
|
|
7
|
-
const envLocalPath = resolve(cwd, ".env.local");
|
|
8
|
-
if (existsSync(envPath)) {
|
|
9
|
-
config({ path: envPath });
|
|
10
|
-
}
|
|
11
|
-
if (existsSync(envLocalPath)) {
|
|
12
|
-
config({ path: envLocalPath, override: true });
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export {
|
|
17
|
-
loadEnv
|
|
18
|
-
};
|