@lumerahq/cli 0.7.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 +118 -0
- package/dist/auth-7RGL7GXU.js +311 -0
- package/dist/chunk-2CR762KB.js +18 -0
- package/dist/chunk-AVKPM7C4.js +199 -0
- package/dist/chunk-D2BLSEGR.js +59 -0
- package/dist/chunk-NDLYGKS6.js +77 -0
- package/dist/chunk-V2XXMMEI.js +147 -0
- package/dist/dev-UTZC4ZJ7.js +87 -0
- package/dist/index.js +157 -0
- package/dist/init-OQCIET53.js +363 -0
- package/dist/migrate-2DZ6RQ5K.js +190 -0
- package/dist/resources-PNK3NESI.js +1350 -0
- package/dist/run-4NDI2CN4.js +257 -0
- package/dist/skills-56EUKHGY.js +414 -0
- package/dist/status-BEVUV6RY.js +131 -0
- package/package.json +37 -0
- package/templates/default/CLAUDE.md +245 -0
- package/templates/default/README.md +59 -0
- package/templates/default/biome.json +33 -0
- package/templates/default/index.html +13 -0
- package/templates/default/package.json.hbs +46 -0
- package/templates/default/platform/automations/.gitkeep +0 -0
- package/templates/default/platform/collections/example_items.json +28 -0
- package/templates/default/platform/hooks/.gitkeep +0 -0
- package/templates/default/pyproject.toml.hbs +14 -0
- package/templates/default/scripts/seed-demo.py +35 -0
- package/templates/default/src/components/Sidebar.tsx +84 -0
- package/templates/default/src/components/StatCard.tsx +31 -0
- package/templates/default/src/components/layout.tsx +13 -0
- package/templates/default/src/lib/queries.ts +27 -0
- package/templates/default/src/main.tsx +137 -0
- package/templates/default/src/routes/__root.tsx +10 -0
- package/templates/default/src/routes/index.tsx +90 -0
- package/templates/default/src/routes/settings.tsx +25 -0
- package/templates/default/src/styles.css +40 -0
- package/templates/default/tsconfig.json +23 -0
- package/templates/default/vite.config.ts +27 -0
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getBaseUrl
|
|
3
|
+
} from "./chunk-D2BLSEGR.js";
|
|
4
|
+
|
|
5
|
+
// src/commands/init.ts
|
|
6
|
+
import pc from "picocolors";
|
|
7
|
+
import prompts from "prompts";
|
|
8
|
+
import { execSync } from "child_process";
|
|
9
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, rmSync } from "fs";
|
|
10
|
+
import { join, dirname, resolve } from "path";
|
|
11
|
+
import { fileURLToPath } from "url";
|
|
12
|
+
var __filename = fileURLToPath(import.meta.url);
|
|
13
|
+
var __dirname = dirname(__filename);
|
|
14
|
+
function getTemplatesDir() {
|
|
15
|
+
const candidates = [
|
|
16
|
+
// From dist/index.js or dist/commands/init.js
|
|
17
|
+
resolve(__dirname, "../templates/default"),
|
|
18
|
+
resolve(__dirname, "../../templates/default"),
|
|
19
|
+
// From src during development
|
|
20
|
+
resolve(__dirname, "../../../templates/default")
|
|
21
|
+
];
|
|
22
|
+
for (const candidate of candidates) {
|
|
23
|
+
if (existsSync(candidate)) {
|
|
24
|
+
return candidate;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
throw new Error(`Templates directory not found. Searched: ${candidates.join(", ")}`);
|
|
28
|
+
}
|
|
29
|
+
function toTitleCase(str) {
|
|
30
|
+
return str.split(/[-_]/).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
31
|
+
}
|
|
32
|
+
function processTemplate(content, vars) {
|
|
33
|
+
let result = content;
|
|
34
|
+
for (const [key, value] of Object.entries(vars)) {
|
|
35
|
+
result = result.replace(new RegExp(`\\{\\{${key}\\}\\}`, "g"), value);
|
|
36
|
+
}
|
|
37
|
+
return result;
|
|
38
|
+
}
|
|
39
|
+
function copyDir(src, dest, vars) {
|
|
40
|
+
if (!existsSync(dest)) {
|
|
41
|
+
mkdirSync(dest, { recursive: true });
|
|
42
|
+
}
|
|
43
|
+
for (const entry of readdirSync(src, { withFileTypes: true })) {
|
|
44
|
+
const srcPath = join(src, entry.name);
|
|
45
|
+
let destName = entry.name;
|
|
46
|
+
if (destName.endsWith(".hbs")) {
|
|
47
|
+
destName = destName.slice(0, -4);
|
|
48
|
+
}
|
|
49
|
+
const destPath = join(dest, destName);
|
|
50
|
+
if (entry.isDirectory()) {
|
|
51
|
+
copyDir(srcPath, destPath, vars);
|
|
52
|
+
} else {
|
|
53
|
+
const content = readFileSync(srcPath, "utf-8");
|
|
54
|
+
const processed = processTemplate(content, vars);
|
|
55
|
+
writeFileSync(destPath, processed);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function isGitInstalled() {
|
|
60
|
+
try {
|
|
61
|
+
execSync("git --version", { stdio: "ignore" });
|
|
62
|
+
return true;
|
|
63
|
+
} catch {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function initGitRepo(targetDir, projectName) {
|
|
68
|
+
try {
|
|
69
|
+
execSync("git init", { cwd: targetDir, stdio: "ignore" });
|
|
70
|
+
execSync("git add -A", { cwd: targetDir, stdio: "ignore" });
|
|
71
|
+
execSync(`git commit -m "Initial commit: scaffold ${projectName}"`, {
|
|
72
|
+
cwd: targetDir,
|
|
73
|
+
stdio: "ignore"
|
|
74
|
+
});
|
|
75
|
+
return true;
|
|
76
|
+
} catch {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function isUvInstalled() {
|
|
81
|
+
try {
|
|
82
|
+
execSync("uv --version", { stdio: "ignore" });
|
|
83
|
+
return true;
|
|
84
|
+
} catch {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function installUv() {
|
|
89
|
+
try {
|
|
90
|
+
try {
|
|
91
|
+
execSync("curl -LsSf https://astral.sh/uv/install.sh | sh", {
|
|
92
|
+
stdio: "inherit",
|
|
93
|
+
shell: "/bin/bash"
|
|
94
|
+
});
|
|
95
|
+
return true;
|
|
96
|
+
} catch {
|
|
97
|
+
execSync("wget -qO- https://astral.sh/uv/install.sh | sh", {
|
|
98
|
+
stdio: "inherit",
|
|
99
|
+
shell: "/bin/bash"
|
|
100
|
+
});
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
} catch {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
function createPythonVenv(targetDir) {
|
|
108
|
+
try {
|
|
109
|
+
execSync("uv venv", { cwd: targetDir, stdio: "ignore" });
|
|
110
|
+
execSync("uv pip install lumera", { cwd: targetDir, stdio: "ignore" });
|
|
111
|
+
return true;
|
|
112
|
+
} catch {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
async function installSkills(targetDir) {
|
|
117
|
+
const baseUrl = getBaseUrl();
|
|
118
|
+
const skillsApiUrl = `${baseUrl}/api/public/skills`;
|
|
119
|
+
const listRes = await fetch(skillsApiUrl);
|
|
120
|
+
if (!listRes.ok) {
|
|
121
|
+
throw new Error(`Failed to fetch skills list: ${listRes.status}`);
|
|
122
|
+
}
|
|
123
|
+
const skills = await listRes.json();
|
|
124
|
+
const skillsDir = join(targetDir, ".claude", "skills");
|
|
125
|
+
mkdirSync(skillsDir, { recursive: true });
|
|
126
|
+
for (const skill of skills) {
|
|
127
|
+
const mdRes = await fetch(`${skillsApiUrl}/${skill.slug}.md`);
|
|
128
|
+
if (!mdRes.ok) {
|
|
129
|
+
console.log(pc.yellow(" \u26A0"), pc.dim(`Failed to fetch skill ${skill.slug}`));
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
const content = await mdRes.text();
|
|
133
|
+
const slug = skill.slug.startsWith("lumera-") ? skill.slug : `lumera-${skill.slug}`;
|
|
134
|
+
const filename = `${slug.replace(/-/g, "_")}.md`;
|
|
135
|
+
writeFileSync(join(skillsDir, filename), content);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
function parseArgs(args) {
|
|
139
|
+
const result = {
|
|
140
|
+
projectName: void 0,
|
|
141
|
+
directory: void 0,
|
|
142
|
+
install: false,
|
|
143
|
+
yes: false,
|
|
144
|
+
force: false,
|
|
145
|
+
help: false
|
|
146
|
+
};
|
|
147
|
+
for (let i = 0; i < args.length; i++) {
|
|
148
|
+
const arg = args[i];
|
|
149
|
+
if (arg === "--help" || arg === "-h") {
|
|
150
|
+
result.help = true;
|
|
151
|
+
} else if (arg === "--install" || arg === "-i") {
|
|
152
|
+
result.install = true;
|
|
153
|
+
} else if (arg === "--yes" || arg === "-y") {
|
|
154
|
+
result.yes = true;
|
|
155
|
+
} else if (arg === "--force" || arg === "-f") {
|
|
156
|
+
result.force = true;
|
|
157
|
+
} else if (arg === "--dir" || arg === "-d") {
|
|
158
|
+
result.directory = args[++i];
|
|
159
|
+
} else if (!arg.startsWith("-") && !result.projectName) {
|
|
160
|
+
result.projectName = arg;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return result;
|
|
164
|
+
}
|
|
165
|
+
function showHelp() {
|
|
166
|
+
console.log(`
|
|
167
|
+
${pc.dim("Usage:")}
|
|
168
|
+
lumera init [name] [options]
|
|
169
|
+
|
|
170
|
+
${pc.dim("Description:")}
|
|
171
|
+
Scaffold a new Lumera project.
|
|
172
|
+
|
|
173
|
+
${pc.dim("Options:")}
|
|
174
|
+
--yes, -y Non-interactive mode (requires project name)
|
|
175
|
+
--dir, -d <path> Target directory (defaults to project name)
|
|
176
|
+
--force, -f Overwrite existing directory without prompting
|
|
177
|
+
--install, -i Install dependencies after scaffolding
|
|
178
|
+
--help, -h Show this help
|
|
179
|
+
|
|
180
|
+
${pc.dim("Interactive mode:")}
|
|
181
|
+
lumera init # Prompt for project name and directory
|
|
182
|
+
lumera init my-project # Prompt for directory only
|
|
183
|
+
|
|
184
|
+
${pc.dim("Non-interactive mode:")}
|
|
185
|
+
lumera init my-project -y # Create ./my-project
|
|
186
|
+
lumera init my-project -y -d ./apps # Create ./apps
|
|
187
|
+
lumera init my-project -y -f # Overwrite if exists
|
|
188
|
+
lumera init my-project -y -i # Create and install deps
|
|
189
|
+
|
|
190
|
+
${pc.dim("CI/CD example:")}
|
|
191
|
+
lumera init my-app -y -f -i # Full non-interactive setup
|
|
192
|
+
`);
|
|
193
|
+
}
|
|
194
|
+
async function init(args) {
|
|
195
|
+
const opts = parseArgs(args);
|
|
196
|
+
if (opts.help) {
|
|
197
|
+
showHelp();
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
console.log();
|
|
201
|
+
console.log(pc.cyan(pc.bold(" Create Lumera App")));
|
|
202
|
+
console.log();
|
|
203
|
+
let projectName = opts.projectName;
|
|
204
|
+
let directory = opts.directory;
|
|
205
|
+
const nonInteractive = opts.yes;
|
|
206
|
+
if (nonInteractive && !projectName) {
|
|
207
|
+
console.log(pc.red(" Error: Project name is required in non-interactive mode"));
|
|
208
|
+
console.log(pc.dim(" Usage: lumera init <name> -y"));
|
|
209
|
+
process.exit(1);
|
|
210
|
+
}
|
|
211
|
+
if (!projectName) {
|
|
212
|
+
const response = await prompts({
|
|
213
|
+
type: "text",
|
|
214
|
+
name: "projectName",
|
|
215
|
+
message: "What is your project name?",
|
|
216
|
+
initial: "my-lumera-app",
|
|
217
|
+
validate: (value) => {
|
|
218
|
+
if (!value) return "Project name is required";
|
|
219
|
+
if (!/^[a-z0-9-]+$/.test(value)) {
|
|
220
|
+
return "Use lowercase letters, numbers, and hyphens only";
|
|
221
|
+
}
|
|
222
|
+
return true;
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
if (!response.projectName) {
|
|
226
|
+
console.log(pc.red("Cancelled"));
|
|
227
|
+
process.exit(1);
|
|
228
|
+
}
|
|
229
|
+
projectName = response.projectName;
|
|
230
|
+
}
|
|
231
|
+
if (!/^[a-z0-9-]+$/.test(projectName)) {
|
|
232
|
+
console.log(pc.red(" Error: Project name must use lowercase letters, numbers, and hyphens only"));
|
|
233
|
+
process.exit(1);
|
|
234
|
+
}
|
|
235
|
+
if (!directory) {
|
|
236
|
+
if (nonInteractive) {
|
|
237
|
+
directory = projectName;
|
|
238
|
+
} else {
|
|
239
|
+
const response = await prompts({
|
|
240
|
+
type: "text",
|
|
241
|
+
name: "directory",
|
|
242
|
+
message: "Where should we create the project?",
|
|
243
|
+
initial: projectName
|
|
244
|
+
});
|
|
245
|
+
if (!response.directory) {
|
|
246
|
+
console.log(pc.red("Cancelled"));
|
|
247
|
+
process.exit(1);
|
|
248
|
+
}
|
|
249
|
+
directory = response.directory;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
const projectTitle = toTitleCase(projectName);
|
|
253
|
+
const projectInitial = projectTitle.charAt(0).toUpperCase();
|
|
254
|
+
const targetDir = resolve(process.cwd(), directory);
|
|
255
|
+
if (existsSync(targetDir)) {
|
|
256
|
+
if (nonInteractive) {
|
|
257
|
+
if (opts.force) {
|
|
258
|
+
rmSync(targetDir, { recursive: true });
|
|
259
|
+
} else {
|
|
260
|
+
console.log(pc.red(` Error: Directory ${directory} already exists`));
|
|
261
|
+
console.log(pc.dim(" Use --force (-f) to overwrite"));
|
|
262
|
+
process.exit(1);
|
|
263
|
+
}
|
|
264
|
+
} else {
|
|
265
|
+
const { overwrite } = await prompts({
|
|
266
|
+
type: "confirm",
|
|
267
|
+
name: "overwrite",
|
|
268
|
+
message: `Directory ${directory} already exists. Overwrite?`,
|
|
269
|
+
initial: false
|
|
270
|
+
});
|
|
271
|
+
if (!overwrite) {
|
|
272
|
+
console.log(pc.red("Cancelled"));
|
|
273
|
+
process.exit(1);
|
|
274
|
+
}
|
|
275
|
+
rmSync(targetDir, { recursive: true });
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
mkdirSync(targetDir, { recursive: true });
|
|
279
|
+
console.log();
|
|
280
|
+
console.log(pc.dim(` Creating ${projectName} in ${directory}...`));
|
|
281
|
+
console.log();
|
|
282
|
+
const templatesDir = getTemplatesDir();
|
|
283
|
+
const vars = {
|
|
284
|
+
projectName,
|
|
285
|
+
projectTitle,
|
|
286
|
+
projectInitial
|
|
287
|
+
};
|
|
288
|
+
copyDir(templatesDir, targetDir, vars);
|
|
289
|
+
function listFiles(dir, prefix = "") {
|
|
290
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
291
|
+
const relativePath = prefix + entry.name;
|
|
292
|
+
if (entry.isDirectory()) {
|
|
293
|
+
listFiles(join(dir, entry.name), relativePath + "/");
|
|
294
|
+
} else {
|
|
295
|
+
console.log(pc.green(" \u2713"), pc.dim(relativePath));
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
listFiles(targetDir);
|
|
300
|
+
if (isGitInstalled()) {
|
|
301
|
+
console.log();
|
|
302
|
+
console.log(pc.dim(" Initializing git repository..."));
|
|
303
|
+
if (initGitRepo(targetDir, projectName)) {
|
|
304
|
+
console.log(pc.green(" \u2713"), pc.dim("Git repository initialized with initial commit"));
|
|
305
|
+
} else {
|
|
306
|
+
console.log(pc.yellow(" \u26A0"), pc.dim("Failed to initialize git repository"));
|
|
307
|
+
}
|
|
308
|
+
} else {
|
|
309
|
+
console.log();
|
|
310
|
+
console.log(pc.yellow(" \u26A0"), pc.dim("Git not found - skipping repository initialization"));
|
|
311
|
+
}
|
|
312
|
+
let uvAvailable = isUvInstalled();
|
|
313
|
+
if (!uvAvailable) {
|
|
314
|
+
console.log();
|
|
315
|
+
console.log(pc.dim(" Installing uv (Python package manager)..."));
|
|
316
|
+
if (installUv()) {
|
|
317
|
+
console.log(pc.green(" \u2713"), pc.dim("uv installed successfully"));
|
|
318
|
+
uvAvailable = true;
|
|
319
|
+
} else {
|
|
320
|
+
console.log(pc.yellow(" \u26A0"), pc.dim("Failed to install uv - install manually: https://docs.astral.sh/uv/"));
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
if (uvAvailable) {
|
|
324
|
+
console.log();
|
|
325
|
+
console.log(pc.dim(" Creating Python venv with Lumera SDK..."));
|
|
326
|
+
if (createPythonVenv(targetDir)) {
|
|
327
|
+
console.log(pc.green(" \u2713"), pc.dim("Python venv created (.venv/) with lumera SDK"));
|
|
328
|
+
} else {
|
|
329
|
+
console.log(pc.yellow(" \u26A0"), pc.dim("Failed to create Python venv"));
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
if (opts.install) {
|
|
333
|
+
console.log();
|
|
334
|
+
console.log(pc.dim(" Installing dependencies..."));
|
|
335
|
+
try {
|
|
336
|
+
execSync("pnpm install", { cwd: targetDir, stdio: "inherit" });
|
|
337
|
+
console.log(pc.green(" \u2713"), pc.dim("Dependencies installed"));
|
|
338
|
+
} catch {
|
|
339
|
+
console.log(pc.yellow(" \u26A0"), pc.dim("Failed to install dependencies"));
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
console.log();
|
|
343
|
+
console.log(pc.dim(" Installing Lumera skills for AI agents..."));
|
|
344
|
+
try {
|
|
345
|
+
await installSkills(targetDir);
|
|
346
|
+
console.log(pc.green(" \u2713"), pc.dim("Lumera skills installed"));
|
|
347
|
+
} catch (err) {
|
|
348
|
+
console.log(pc.yellow(" \u26A0"), pc.dim(`Failed to install skills: ${err}`));
|
|
349
|
+
}
|
|
350
|
+
console.log();
|
|
351
|
+
console.log(pc.green(pc.bold(" Done!")), "Next steps:");
|
|
352
|
+
console.log();
|
|
353
|
+
console.log(pc.cyan(` cd ${directory}`));
|
|
354
|
+
if (!opts.install) {
|
|
355
|
+
console.log(pc.cyan(" pnpm install"));
|
|
356
|
+
}
|
|
357
|
+
console.log(pc.cyan(" lumera login"));
|
|
358
|
+
console.log(pc.cyan(" pnpm dev"));
|
|
359
|
+
console.log();
|
|
360
|
+
}
|
|
361
|
+
export {
|
|
362
|
+
init
|
|
363
|
+
};
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import {
|
|
2
|
+
detectProjectVersion,
|
|
3
|
+
findProjectRoot
|
|
4
|
+
} from "./chunk-D2BLSEGR.js";
|
|
5
|
+
|
|
6
|
+
// src/commands/migrate.ts
|
|
7
|
+
import pc from "picocolors";
|
|
8
|
+
import { existsSync, mkdirSync, renameSync, readdirSync, readFileSync, writeFileSync, rmSync } from "fs";
|
|
9
|
+
import { join } from "path";
|
|
10
|
+
function showHelp() {
|
|
11
|
+
console.log(`
|
|
12
|
+
${pc.dim("Usage:")}
|
|
13
|
+
lumera migrate [options]
|
|
14
|
+
|
|
15
|
+
${pc.dim("Description:")}
|
|
16
|
+
Upgrade legacy project structure (v0) to the current structure (v1).
|
|
17
|
+
Moves files from lumera_platform/ to platform/ and updates package.json.
|
|
18
|
+
|
|
19
|
+
${pc.dim("Options:")}
|
|
20
|
+
--dry-run Preview migration changes without applying
|
|
21
|
+
--help, -h Show this help
|
|
22
|
+
|
|
23
|
+
${pc.dim("Examples:")}
|
|
24
|
+
lumera migrate # Migrate to v1 structure
|
|
25
|
+
lumera migrate --dry-run # Preview migration changes
|
|
26
|
+
`);
|
|
27
|
+
}
|
|
28
|
+
async function migrate(args) {
|
|
29
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
30
|
+
showHelp();
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const dryRun = args.includes("--dry-run");
|
|
34
|
+
let projectRoot;
|
|
35
|
+
try {
|
|
36
|
+
projectRoot = findProjectRoot();
|
|
37
|
+
} catch {
|
|
38
|
+
console.error(pc.red("Not in a Lumera project directory."));
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
const version = detectProjectVersion(projectRoot);
|
|
42
|
+
if (version === 1) {
|
|
43
|
+
console.log(pc.green("Project is already using v1 structure."));
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (version !== 0) {
|
|
47
|
+
console.error(pc.red(`Unknown project version: ${version}`));
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
const legacyDir = join(projectRoot, "lumera_platform");
|
|
51
|
+
if (!existsSync(legacyDir)) {
|
|
52
|
+
console.error(pc.red("Legacy lumera_platform/ directory not found."));
|
|
53
|
+
console.error(pc.dim("This project may already be migrated or is not a Lumera project."));
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
console.log(pc.bold("Migration Plan:"));
|
|
57
|
+
console.log();
|
|
58
|
+
const actions = [];
|
|
59
|
+
const newDirs = ["platform/collections", "platform/automations", "platform/hooks", "scripts"];
|
|
60
|
+
for (const dir of newDirs) {
|
|
61
|
+
const fullPath = join(projectRoot, dir);
|
|
62
|
+
if (!existsSync(fullPath)) {
|
|
63
|
+
actions.push({ type: "mkdir", to: dir, desc: `Create ${dir}/` });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const legacyCollections = join(legacyDir, "collections");
|
|
67
|
+
if (existsSync(legacyCollections)) {
|
|
68
|
+
for (const file of readdirSync(legacyCollections)) {
|
|
69
|
+
actions.push({
|
|
70
|
+
type: "move",
|
|
71
|
+
from: `lumera_platform/collections/${file}`,
|
|
72
|
+
to: `platform/collections/${file}`,
|
|
73
|
+
desc: `Move collections/${file}`
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const legacyAutomations = join(legacyDir, "automations");
|
|
78
|
+
if (existsSync(legacyAutomations)) {
|
|
79
|
+
for (const item of readdirSync(legacyAutomations, { withFileTypes: true })) {
|
|
80
|
+
actions.push({
|
|
81
|
+
type: "move",
|
|
82
|
+
from: `lumera_platform/automations/${item.name}`,
|
|
83
|
+
to: `platform/automations/${item.name}`,
|
|
84
|
+
desc: `Move automations/${item.name}`
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
const legacyHooks = join(legacyDir, "hooks");
|
|
89
|
+
if (existsSync(legacyHooks)) {
|
|
90
|
+
for (const file of readdirSync(legacyHooks)) {
|
|
91
|
+
actions.push({
|
|
92
|
+
type: "move",
|
|
93
|
+
from: `lumera_platform/hooks/${file}`,
|
|
94
|
+
to: `platform/hooks/${file}`,
|
|
95
|
+
desc: `Move hooks/${file}`
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
const legacyScripts = join(legacyDir, "scripts");
|
|
100
|
+
const legacySeed = join(legacyDir, "seed");
|
|
101
|
+
for (const dir of [legacyScripts, legacySeed]) {
|
|
102
|
+
if (existsSync(dir)) {
|
|
103
|
+
for (const file of readdirSync(dir)) {
|
|
104
|
+
const fromDir = dir === legacyScripts ? "scripts" : "seed";
|
|
105
|
+
actions.push({
|
|
106
|
+
type: "move",
|
|
107
|
+
from: `lumera_platform/${fromDir}/${file}`,
|
|
108
|
+
to: `scripts/${file}`,
|
|
109
|
+
desc: `Move ${fromDir}/${file} to scripts/`
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
actions.push({
|
|
115
|
+
type: "update",
|
|
116
|
+
to: "package.json",
|
|
117
|
+
desc: "Add lumera.version: 1 to package.json"
|
|
118
|
+
});
|
|
119
|
+
actions.push({
|
|
120
|
+
type: "delete",
|
|
121
|
+
from: "lumera_platform/",
|
|
122
|
+
desc: "Remove legacy lumera_platform/ directory"
|
|
123
|
+
});
|
|
124
|
+
for (const action of actions) {
|
|
125
|
+
switch (action.type) {
|
|
126
|
+
case "mkdir":
|
|
127
|
+
console.log(pc.cyan(` + ${action.to}`));
|
|
128
|
+
break;
|
|
129
|
+
case "move":
|
|
130
|
+
console.log(pc.yellow(` \u2192 ${action.from} \u2192 ${action.to}`));
|
|
131
|
+
break;
|
|
132
|
+
case "update":
|
|
133
|
+
console.log(pc.blue(` \u270E ${action.to}`));
|
|
134
|
+
break;
|
|
135
|
+
case "delete":
|
|
136
|
+
console.log(pc.red(` - ${action.from}`));
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
console.log();
|
|
141
|
+
if (dryRun) {
|
|
142
|
+
console.log(pc.dim("Dry run - no changes made."));
|
|
143
|
+
console.log(pc.dim("Run without --dry-run to apply changes."));
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
console.log(pc.bold("Applying changes..."));
|
|
147
|
+
console.log();
|
|
148
|
+
for (const action of actions) {
|
|
149
|
+
try {
|
|
150
|
+
switch (action.type) {
|
|
151
|
+
case "mkdir":
|
|
152
|
+
mkdirSync(join(projectRoot, action.to), { recursive: true });
|
|
153
|
+
console.log(pc.green(` \u2713 Created ${action.to}`));
|
|
154
|
+
break;
|
|
155
|
+
case "move":
|
|
156
|
+
renameSync(
|
|
157
|
+
join(projectRoot, action.from),
|
|
158
|
+
join(projectRoot, action.to)
|
|
159
|
+
);
|
|
160
|
+
console.log(pc.green(` \u2713 Moved ${action.from} \u2192 ${action.to}`));
|
|
161
|
+
break;
|
|
162
|
+
case "update":
|
|
163
|
+
if (action.to === "package.json") {
|
|
164
|
+
const pkgPath = join(projectRoot, "package.json");
|
|
165
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
166
|
+
pkg.lumera = pkg.lumera || {};
|
|
167
|
+
pkg.lumera.version = 1;
|
|
168
|
+
writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
169
|
+
console.log(pc.green(` \u2713 Updated package.json`));
|
|
170
|
+
}
|
|
171
|
+
break;
|
|
172
|
+
case "delete":
|
|
173
|
+
rmSync(join(projectRoot, action.from), { recursive: true, force: true });
|
|
174
|
+
console.log(pc.green(` \u2713 Removed ${action.from}`));
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
} catch (err) {
|
|
178
|
+
console.error(pc.red(` \u2717 Failed: ${action.desc}`));
|
|
179
|
+
if (err instanceof Error) {
|
|
180
|
+
console.error(pc.dim(` ${err.message}`));
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
console.log();
|
|
185
|
+
console.log(pc.green("Migration complete!"));
|
|
186
|
+
console.log(pc.dim("Your project now uses the v1 structure."));
|
|
187
|
+
}
|
|
188
|
+
export {
|
|
189
|
+
migrate
|
|
190
|
+
};
|