@lumerahq/cli 0.10.1 → 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-WTDV3MTG.js → chunk-CHRKCAIZ.js} +54 -3
- package/dist/{chunk-WRAZC6SJ.js → chunk-HIYM7EM2.js} +17 -1
- package/dist/dev-2HVDP3NX.js +155 -0
- package/dist/index.js +164 -16
- package/dist/{init-OH433IPH.js → init-VNJNSU4Q.js} +122 -59
- 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/templates-67O6PVFK.js +0 -70
|
@@ -36,7 +36,7 @@ async function downloadAndExtract(commitSha) {
|
|
|
36
36
|
writeFileSync(tmpFile, buffer);
|
|
37
37
|
mkdirSync(cacheDir, { recursive: true });
|
|
38
38
|
for (const entry of readdirSync(cacheDir)) {
|
|
39
|
-
if (entry === ".cache-meta.json") continue;
|
|
39
|
+
if (entry === ".cache-meta.json" || entry === ".refs") continue;
|
|
40
40
|
rmSync(join(cacheDir, entry), { recursive: true, force: true });
|
|
41
41
|
}
|
|
42
42
|
const result = spawnSync("tar", ["xzf", tmpFile, "-C", cacheDir, "--strip-components=1"], { stdio: "ignore" });
|
|
@@ -56,6 +56,51 @@ async function ensureRemoteCache() {
|
|
|
56
56
|
console.log(" Fetching latest templates...");
|
|
57
57
|
return downloadAndExtract(latestSha);
|
|
58
58
|
}
|
|
59
|
+
function parseTemplateRef(input) {
|
|
60
|
+
const atIndex = input.lastIndexOf("@");
|
|
61
|
+
if (atIndex > 0) {
|
|
62
|
+
return {
|
|
63
|
+
name: input.slice(0, atIndex),
|
|
64
|
+
ref: input.slice(atIndex + 1)
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
return { name: input, ref: null };
|
|
68
|
+
}
|
|
69
|
+
async function ensureRemoteCacheForRef(ref) {
|
|
70
|
+
const refCacheDir = join(getCacheDir(), ".refs", ref);
|
|
71
|
+
if (existsSync(refCacheDir) && existsSync(join(refCacheDir, ".cache-meta.json"))) {
|
|
72
|
+
return refCacheDir;
|
|
73
|
+
}
|
|
74
|
+
console.log(` Fetching templates at ref "${ref}"...`);
|
|
75
|
+
const urls = [
|
|
76
|
+
`https://github.com/${GITHUB_OWNER}/${GITHUB_REPO}/archive/refs/tags/${ref}.tar.gz`,
|
|
77
|
+
`https://github.com/${GITHUB_OWNER}/${GITHUB_REPO}/archive/refs/heads/${ref}.tar.gz`,
|
|
78
|
+
`https://github.com/${GITHUB_OWNER}/${GITHUB_REPO}/archive/${ref}.tar.gz`
|
|
79
|
+
];
|
|
80
|
+
let res = null;
|
|
81
|
+
for (const url of urls) {
|
|
82
|
+
const attempt = await fetch(url);
|
|
83
|
+
if (attempt.ok) {
|
|
84
|
+
res = attempt;
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (!res) {
|
|
89
|
+
throw new Error(
|
|
90
|
+
`Failed to fetch templates at ref "${ref}". Ensure the tag, branch, or SHA exists in ${GITHUB_OWNER}/${GITHUB_REPO}.`
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
const tmpFile = join(tmpdir(), `lumera-templates-${ref}-${Date.now()}.tar.gz`);
|
|
94
|
+
const buffer = Buffer.from(await res.arrayBuffer());
|
|
95
|
+
writeFileSync(tmpFile, buffer);
|
|
96
|
+
mkdirSync(refCacheDir, { recursive: true });
|
|
97
|
+
const result = spawnSync("tar", ["xzf", tmpFile, "-C", refCacheDir, "--strip-components=1"], { stdio: "ignore" });
|
|
98
|
+
if (result.status !== 0) throw new Error("Failed to extract template archive");
|
|
99
|
+
unlinkSync(tmpFile);
|
|
100
|
+
const meta = { commitSha: ref, fetchedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
101
|
+
writeFileSync(join(refCacheDir, ".cache-meta.json"), JSON.stringify(meta));
|
|
102
|
+
return refCacheDir;
|
|
103
|
+
}
|
|
59
104
|
function listTemplates(baseDir) {
|
|
60
105
|
if (!existsSync(baseDir)) return [];
|
|
61
106
|
const templates = [];
|
|
@@ -80,11 +125,17 @@ function listTemplates(baseDir) {
|
|
|
80
125
|
return a.title.localeCompare(b.title);
|
|
81
126
|
});
|
|
82
127
|
}
|
|
83
|
-
async function resolveTemplate(
|
|
84
|
-
const
|
|
128
|
+
async function resolveTemplate(nameWithRef) {
|
|
129
|
+
const { name, ref } = parseTemplateRef(nameWithRef);
|
|
130
|
+
const cacheDir = ref ? await ensureRemoteCacheForRef(ref) : await ensureRemoteCache();
|
|
85
131
|
const dir = join(cacheDir, name);
|
|
86
132
|
if (!existsSync(dir) || !existsSync(join(dir, "template.json"))) {
|
|
87
133
|
const available = listTemplates(cacheDir).map((t) => t.name).join(", ");
|
|
134
|
+
if (ref) {
|
|
135
|
+
throw new Error(
|
|
136
|
+
`Template "${name}" not found at ref "${ref}". Available: ${available || "none"}`
|
|
137
|
+
);
|
|
138
|
+
}
|
|
88
139
|
throw new Error(
|
|
89
140
|
`Template "${name}" not found. Available: ${available || "none"}
|
|
90
141
|
Cache location: ${cacheDir}
|
|
@@ -143,6 +143,22 @@ function createApiClient(token, baseUrl) {
|
|
|
143
143
|
return new ApiClient(token, baseUrl);
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
+
// src/lib/env.ts
|
|
147
|
+
import { config } from "dotenv";
|
|
148
|
+
import { existsSync } from "fs";
|
|
149
|
+
import { resolve } from "path";
|
|
150
|
+
function loadEnv(cwd = process.cwd()) {
|
|
151
|
+
const envPath = resolve(cwd, ".env");
|
|
152
|
+
const envLocalPath = resolve(cwd, ".env.local");
|
|
153
|
+
if (existsSync(envPath)) {
|
|
154
|
+
config({ path: envPath });
|
|
155
|
+
}
|
|
156
|
+
if (existsSync(envLocalPath)) {
|
|
157
|
+
config({ path: envLocalPath, override: true });
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
146
161
|
export {
|
|
147
|
-
createApiClient
|
|
162
|
+
createApiClient,
|
|
163
|
+
loadEnv
|
|
148
164
|
};
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import {
|
|
2
|
+
dev
|
|
3
|
+
} from "./chunk-CDZZ3JYU.js";
|
|
4
|
+
import {
|
|
5
|
+
createApiClient,
|
|
6
|
+
loadEnv
|
|
7
|
+
} from "./chunk-HIYM7EM2.js";
|
|
8
|
+
import {
|
|
9
|
+
getToken
|
|
10
|
+
} from "./chunk-NDLYGKS6.js";
|
|
11
|
+
import {
|
|
12
|
+
findProjectRoot,
|
|
13
|
+
getApiUrl,
|
|
14
|
+
getAppName,
|
|
15
|
+
getAppTitle
|
|
16
|
+
} from "./chunk-D2BLSEGR.js";
|
|
17
|
+
|
|
18
|
+
// src/commands/dev.ts
|
|
19
|
+
import pc from "picocolors";
|
|
20
|
+
import { execFileSync, execSync } from "child_process";
|
|
21
|
+
import { existsSync } from "fs";
|
|
22
|
+
import { join } from "path";
|
|
23
|
+
function parseFlags(args) {
|
|
24
|
+
const result = {};
|
|
25
|
+
for (let i = 0; i < args.length; i++) {
|
|
26
|
+
const arg = args[i];
|
|
27
|
+
if (arg.startsWith("--")) {
|
|
28
|
+
const key = arg.slice(2);
|
|
29
|
+
const next = args[i + 1];
|
|
30
|
+
if (next && !next.startsWith("--")) {
|
|
31
|
+
result[key] = next;
|
|
32
|
+
i++;
|
|
33
|
+
} else {
|
|
34
|
+
result[key] = true;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return result;
|
|
39
|
+
}
|
|
40
|
+
function showHelp() {
|
|
41
|
+
console.log(`
|
|
42
|
+
${pc.dim("Usage:")}
|
|
43
|
+
lumera dev [options]
|
|
44
|
+
|
|
45
|
+
${pc.dim("Description:")}
|
|
46
|
+
Start the development server with Lumera registration.
|
|
47
|
+
Registers your app with Lumera and starts a local dev server.
|
|
48
|
+
On first run, automatically applies resources and seeds demo data.
|
|
49
|
+
|
|
50
|
+
${pc.dim("Options:")}
|
|
51
|
+
--port <number> Dev server port (default: 8080)
|
|
52
|
+
--url <url> App URL for dev mode (default: http://localhost:{port})
|
|
53
|
+
--skip-setup Skip auto-apply on first run
|
|
54
|
+
--help, -h Show this help
|
|
55
|
+
|
|
56
|
+
${pc.dim("Environment variables:")}
|
|
57
|
+
LUMERA_TOKEN API token (overrides \`lumera login\`)
|
|
58
|
+
LUMERA_API_URL API base URL (default: https://app.lumerahq.com/api)
|
|
59
|
+
PORT Dev server port (default: 8080)
|
|
60
|
+
APP_URL App URL for dev mode
|
|
61
|
+
|
|
62
|
+
${pc.dim("Examples:")}
|
|
63
|
+
lumera dev # Start dev server on port 8080
|
|
64
|
+
lumera dev --port 3000 # Start dev server on port 3000
|
|
65
|
+
lumera dev --skip-setup # Skip first-run auto-apply
|
|
66
|
+
`);
|
|
67
|
+
}
|
|
68
|
+
async function isFreshProject(projectRoot) {
|
|
69
|
+
try {
|
|
70
|
+
const api = createApiClient();
|
|
71
|
+
const remoteCollections = await api.listCollections();
|
|
72
|
+
const customCollections = remoteCollections.filter((c) => !c.system && !c.managed);
|
|
73
|
+
const platformDir = existsSync(join(projectRoot, "platform")) ? join(projectRoot, "platform") : join(projectRoot, "lumera_platform");
|
|
74
|
+
const collectionsDir = join(platformDir, "collections");
|
|
75
|
+
const hasLocalCollections = existsSync(collectionsDir);
|
|
76
|
+
return customCollections.length === 0 && hasLocalCollections;
|
|
77
|
+
} catch {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function runApply(projectRoot) {
|
|
82
|
+
try {
|
|
83
|
+
execFileSync(process.execPath, [...process.execArgv, process.argv[1], "apply"], {
|
|
84
|
+
cwd: projectRoot,
|
|
85
|
+
stdio: "inherit",
|
|
86
|
+
env: process.env
|
|
87
|
+
});
|
|
88
|
+
return true;
|
|
89
|
+
} catch {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function runSeed(projectRoot) {
|
|
94
|
+
const seedScript = join(projectRoot, "scripts", "seed-demo.py");
|
|
95
|
+
if (!existsSync(seedScript)) return false;
|
|
96
|
+
try {
|
|
97
|
+
execSync(`uv run "${seedScript}"`, {
|
|
98
|
+
cwd: projectRoot,
|
|
99
|
+
stdio: "inherit",
|
|
100
|
+
env: process.env
|
|
101
|
+
});
|
|
102
|
+
return true;
|
|
103
|
+
} catch {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
async function dev2(args) {
|
|
108
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
109
|
+
showHelp();
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const flags = parseFlags(args);
|
|
113
|
+
const projectRoot = findProjectRoot();
|
|
114
|
+
loadEnv(projectRoot);
|
|
115
|
+
const token = getToken(projectRoot);
|
|
116
|
+
const appName = getAppName(projectRoot);
|
|
117
|
+
const appTitle = getAppTitle(projectRoot);
|
|
118
|
+
const apiUrl = getApiUrl();
|
|
119
|
+
if (!flags["skip-setup"]) {
|
|
120
|
+
const fresh = await isFreshProject(projectRoot);
|
|
121
|
+
if (fresh) {
|
|
122
|
+
console.log();
|
|
123
|
+
console.log(pc.cyan(pc.bold(" First run detected")), pc.dim("\u2014 applying resources and seeding data..."));
|
|
124
|
+
console.log();
|
|
125
|
+
const applyOk = runApply(projectRoot);
|
|
126
|
+
if (!applyOk) {
|
|
127
|
+
console.log(pc.yellow(" \u26A0 Some resources failed to apply (continuing)"));
|
|
128
|
+
}
|
|
129
|
+
const seedScript = join(projectRoot, "scripts", "seed-demo.py");
|
|
130
|
+
if (existsSync(seedScript)) {
|
|
131
|
+
console.log();
|
|
132
|
+
console.log(pc.dim(" Running seed script..."));
|
|
133
|
+
if (runSeed(projectRoot)) {
|
|
134
|
+
console.log(pc.green(" \u2713"), pc.dim("Seed data applied"));
|
|
135
|
+
} else {
|
|
136
|
+
console.log(pc.yellow(" \u26A0"), pc.dim("Seed script failed (continuing)"));
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
console.log();
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
const port = Number(flags.port || process.env.PORT || "8080");
|
|
143
|
+
const appUrl = typeof flags.url === "string" ? flags.url : process.env.APP_URL;
|
|
144
|
+
await dev({
|
|
145
|
+
token,
|
|
146
|
+
appName,
|
|
147
|
+
appTitle,
|
|
148
|
+
port,
|
|
149
|
+
appUrl,
|
|
150
|
+
apiUrl
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
export {
|
|
154
|
+
dev2 as dev
|
|
155
|
+
};
|
package/dist/index.js
CHANGED
|
@@ -1,16 +1,127 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { readFileSync } from "fs";
|
|
5
|
-
import { dirname, join } from "path";
|
|
4
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
5
|
+
import { dirname, join as join2 } from "path";
|
|
6
6
|
import { fileURLToPath } from "url";
|
|
7
7
|
import pc from "picocolors";
|
|
8
|
+
|
|
9
|
+
// src/lib/update-check.ts
|
|
10
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
11
|
+
import { homedir } from "os";
|
|
12
|
+
import { join } from "path";
|
|
13
|
+
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
14
|
+
function getCachePath() {
|
|
15
|
+
return join(homedir(), ".lumera", "update-check.json");
|
|
16
|
+
}
|
|
17
|
+
function readCache() {
|
|
18
|
+
const cachePath = getCachePath();
|
|
19
|
+
if (!existsSync(cachePath)) return null;
|
|
20
|
+
try {
|
|
21
|
+
return JSON.parse(readFileSync(cachePath, "utf-8"));
|
|
22
|
+
} catch {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function writeCache(data) {
|
|
27
|
+
const dir = join(homedir(), ".lumera");
|
|
28
|
+
mkdirSync(dir, { recursive: true });
|
|
29
|
+
writeFileSync(getCachePath(), JSON.stringify(data));
|
|
30
|
+
}
|
|
31
|
+
function isNewer(latest, current) {
|
|
32
|
+
const l = latest.split(".").map(Number);
|
|
33
|
+
const c = current.split(".").map(Number);
|
|
34
|
+
for (let i = 0; i < 3; i++) {
|
|
35
|
+
if ((l[i] || 0) > (c[i] || 0)) return true;
|
|
36
|
+
if ((l[i] || 0) < (c[i] || 0)) return false;
|
|
37
|
+
}
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
async function checkForUpdate(currentVersion) {
|
|
41
|
+
try {
|
|
42
|
+
if (process.env.CI || process.env.LUMERA_NO_UPDATE_CHECK) return null;
|
|
43
|
+
const cache = readCache();
|
|
44
|
+
if (cache) {
|
|
45
|
+
const age = Date.now() - new Date(cache.lastCheck).getTime();
|
|
46
|
+
if (age < CHECK_INTERVAL_MS) {
|
|
47
|
+
if (isNewer(cache.latestVersion, currentVersion)) {
|
|
48
|
+
return { current: currentVersion, latest: cache.latestVersion };
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
const res = await fetch("https://registry.npmjs.org/@lumerahq/cli/latest");
|
|
54
|
+
if (!res.ok) return null;
|
|
55
|
+
const data = await res.json();
|
|
56
|
+
const latest = data.version;
|
|
57
|
+
writeCache({ lastCheck: (/* @__PURE__ */ new Date()).toISOString(), latestVersion: latest });
|
|
58
|
+
if (isNewer(latest, currentVersion)) {
|
|
59
|
+
return { current: currentVersion, latest };
|
|
60
|
+
}
|
|
61
|
+
return null;
|
|
62
|
+
} catch {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// src/index.ts
|
|
8
68
|
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
9
|
-
var pkg = JSON.parse(
|
|
69
|
+
var pkg = JSON.parse(readFileSync2(join2(__dirname, "..", "package.json"), "utf-8"));
|
|
10
70
|
var VERSION = pkg.version;
|
|
11
|
-
var
|
|
71
|
+
var rawArgs = process.argv.slice(2);
|
|
72
|
+
var jsonMode = rawArgs.includes("--json");
|
|
73
|
+
if (jsonMode) process.env.LUMERA_JSON = "1";
|
|
74
|
+
var args = rawArgs.filter((a) => a !== "--json");
|
|
12
75
|
var command = args[0];
|
|
13
76
|
var subcommand = args[1];
|
|
77
|
+
var COMMANDS = [
|
|
78
|
+
"plan",
|
|
79
|
+
"apply",
|
|
80
|
+
"pull",
|
|
81
|
+
"destroy",
|
|
82
|
+
"list",
|
|
83
|
+
"show",
|
|
84
|
+
"dev",
|
|
85
|
+
"run",
|
|
86
|
+
"init",
|
|
87
|
+
"templates",
|
|
88
|
+
"status",
|
|
89
|
+
"migrate",
|
|
90
|
+
"skills",
|
|
91
|
+
"login",
|
|
92
|
+
"logout",
|
|
93
|
+
"whoami"
|
|
94
|
+
];
|
|
95
|
+
function editDistance(a, b) {
|
|
96
|
+
const m = a.length;
|
|
97
|
+
const n = b.length;
|
|
98
|
+
const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
|
|
99
|
+
for (let i = 0; i <= m; i++) dp[i][0] = i;
|
|
100
|
+
for (let j = 0; j <= n; j++) dp[0][j] = j;
|
|
101
|
+
for (let i = 1; i <= m; i++) {
|
|
102
|
+
for (let j = 1; j <= n; j++) {
|
|
103
|
+
dp[i][j] = a[i - 1] === b[j - 1] ? dp[i - 1][j - 1] : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return dp[m][n];
|
|
107
|
+
}
|
|
108
|
+
function suggestCommand(input) {
|
|
109
|
+
let best = "";
|
|
110
|
+
let bestDist = Infinity;
|
|
111
|
+
for (const cmd of COMMANDS) {
|
|
112
|
+
const d = editDistance(input.toLowerCase(), cmd);
|
|
113
|
+
if (d < bestDist) {
|
|
114
|
+
bestDist = d;
|
|
115
|
+
best = cmd;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
const threshold = Math.max(2, Math.ceil(input.length * 0.4));
|
|
119
|
+
return bestDist <= threshold ? best : null;
|
|
120
|
+
}
|
|
121
|
+
function formatElapsed(ms) {
|
|
122
|
+
if (ms < 1e3) return `${Math.round(ms)}ms`;
|
|
123
|
+
return `${(ms / 1e3).toFixed(1)}s`;
|
|
124
|
+
}
|
|
14
125
|
function showHelp() {
|
|
15
126
|
console.log(`
|
|
16
127
|
${pc.bold("Lumera CLI")} - Build and deploy Lumera apps
|
|
@@ -32,7 +143,8 @@ ${pc.dim("Development:")}
|
|
|
32
143
|
|
|
33
144
|
${pc.dim("Project:")}
|
|
34
145
|
${pc.cyan("init")} [name] Scaffold a new project
|
|
35
|
-
${pc.cyan("templates")}
|
|
146
|
+
${pc.cyan("templates list")} List available templates
|
|
147
|
+
${pc.cyan("templates validate")} Validate a template directory
|
|
36
148
|
${pc.cyan("status")} Show project info
|
|
37
149
|
${pc.cyan("migrate")} Upgrade legacy project
|
|
38
150
|
|
|
@@ -49,6 +161,7 @@ ${pc.dim("Auth:")}
|
|
|
49
161
|
${pc.dim("Options:")}
|
|
50
162
|
--help, -h Show help
|
|
51
163
|
--version, -v Show version
|
|
164
|
+
--json Output as JSON (where supported)
|
|
52
165
|
|
|
53
166
|
${pc.dim("Resource Types:")}
|
|
54
167
|
collections Data collections (schemas)
|
|
@@ -89,40 +202,42 @@ async function main() {
|
|
|
89
202
|
process.exit(1);
|
|
90
203
|
}
|
|
91
204
|
}
|
|
205
|
+
const startTime = performance.now();
|
|
206
|
+
const updateCheck = checkForUpdate(VERSION);
|
|
92
207
|
try {
|
|
93
208
|
switch (command) {
|
|
94
209
|
// Resource commands
|
|
95
210
|
case "plan":
|
|
96
|
-
await import("./resources-
|
|
211
|
+
await import("./resources-GTG3QMVV.js").then((m) => m.plan(args.slice(1)));
|
|
97
212
|
break;
|
|
98
213
|
case "apply":
|
|
99
|
-
await import("./resources-
|
|
214
|
+
await import("./resources-GTG3QMVV.js").then((m) => m.apply(args.slice(1)));
|
|
100
215
|
break;
|
|
101
216
|
case "pull":
|
|
102
|
-
await import("./resources-
|
|
217
|
+
await import("./resources-GTG3QMVV.js").then((m) => m.pull(args.slice(1)));
|
|
103
218
|
break;
|
|
104
219
|
case "destroy":
|
|
105
|
-
await import("./resources-
|
|
220
|
+
await import("./resources-GTG3QMVV.js").then((m) => m.destroy(args.slice(1)));
|
|
106
221
|
break;
|
|
107
222
|
case "list":
|
|
108
|
-
await import("./resources-
|
|
223
|
+
await import("./resources-GTG3QMVV.js").then((m) => m.list(args.slice(1)));
|
|
109
224
|
break;
|
|
110
225
|
case "show":
|
|
111
|
-
await import("./resources-
|
|
226
|
+
await import("./resources-GTG3QMVV.js").then((m) => m.show(args.slice(1)));
|
|
112
227
|
break;
|
|
113
228
|
// Development
|
|
114
229
|
case "dev":
|
|
115
|
-
await import("./dev-
|
|
230
|
+
await import("./dev-2HVDP3NX.js").then((m) => m.dev(args.slice(1)));
|
|
116
231
|
break;
|
|
117
232
|
case "run":
|
|
118
|
-
await import("./run-
|
|
233
|
+
await import("./run-47GF5VVS.js").then((m) => m.run(args.slice(1)));
|
|
119
234
|
break;
|
|
120
235
|
// Project
|
|
121
236
|
case "init":
|
|
122
|
-
await import("./init-
|
|
237
|
+
await import("./init-VNJNSU4Q.js").then((m) => m.init(args.slice(1)));
|
|
123
238
|
break;
|
|
124
239
|
case "templates":
|
|
125
|
-
await import("./templates-
|
|
240
|
+
await import("./templates-6KMZWOYH.js").then((m) => m.templates(subcommand, args.slice(2)));
|
|
126
241
|
break;
|
|
127
242
|
case "status":
|
|
128
243
|
await import("./status-E4IHEUKO.js").then((m) => m.status(args.slice(1)));
|
|
@@ -144,10 +259,43 @@ async function main() {
|
|
|
144
259
|
case "whoami":
|
|
145
260
|
await import("./auth-7RGL7GXU.js").then((m) => m.whoami());
|
|
146
261
|
break;
|
|
147
|
-
|
|
262
|
+
// Convenience aliases
|
|
263
|
+
case "help":
|
|
264
|
+
showHelp();
|
|
265
|
+
break;
|
|
266
|
+
case "version":
|
|
267
|
+
showVersion();
|
|
268
|
+
break;
|
|
269
|
+
default: {
|
|
270
|
+
const suggestion = suggestCommand(command);
|
|
148
271
|
console.error(pc.red(`Unknown command: ${command}`));
|
|
272
|
+
if (suggestion) {
|
|
273
|
+
console.error(`Did you mean ${pc.cyan(suggestion)}?`);
|
|
274
|
+
}
|
|
149
275
|
console.error(`Run ${pc.cyan("lumera --help")} for usage.`);
|
|
150
276
|
process.exit(1);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
if (!jsonMode) {
|
|
280
|
+
const elapsed = performance.now() - startTime;
|
|
281
|
+
if (elapsed >= 500) {
|
|
282
|
+
console.log(pc.dim(`
|
|
283
|
+
Done in ${formatElapsed(elapsed)}`));
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
if (!jsonMode) {
|
|
287
|
+
try {
|
|
288
|
+
const update = await Promise.race([
|
|
289
|
+
updateCheck,
|
|
290
|
+
new Promise((resolve) => setTimeout(() => resolve(null), 3e3))
|
|
291
|
+
]);
|
|
292
|
+
if (update) {
|
|
293
|
+
console.log();
|
|
294
|
+
console.log(` ${pc.yellow("Update available:")} ${pc.dim(update.current)} \u2192 ${pc.green(update.latest)}`);
|
|
295
|
+
console.log(` Run ${pc.cyan("npm i -g @lumerahq/cli")} to update`);
|
|
296
|
+
}
|
|
297
|
+
} catch {
|
|
298
|
+
}
|
|
151
299
|
}
|
|
152
300
|
} catch (error) {
|
|
153
301
|
if (error instanceof Error) {
|
|
@@ -5,15 +5,47 @@ import {
|
|
|
5
5
|
import {
|
|
6
6
|
listAllTemplates,
|
|
7
7
|
resolveTemplate
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-CHRKCAIZ.js";
|
|
9
9
|
import "./chunk-D2BLSEGR.js";
|
|
10
10
|
|
|
11
11
|
// src/commands/init.ts
|
|
12
|
-
import
|
|
12
|
+
import pc2 from "picocolors";
|
|
13
13
|
import prompts from "prompts";
|
|
14
14
|
import { execSync } from "child_process";
|
|
15
15
|
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, rmSync } from "fs";
|
|
16
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
|
|
17
49
|
function toTitleCase(str) {
|
|
18
50
|
return str.split(/[-_]/).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
19
51
|
}
|
|
@@ -82,13 +114,13 @@ function installUv() {
|
|
|
82
114
|
try {
|
|
83
115
|
try {
|
|
84
116
|
execSync("curl -LsSf https://astral.sh/uv/install.sh | sh", {
|
|
85
|
-
stdio: "
|
|
117
|
+
stdio: "ignore",
|
|
86
118
|
shell: "/bin/bash"
|
|
87
119
|
});
|
|
88
120
|
return true;
|
|
89
121
|
} catch {
|
|
90
122
|
execSync("wget -qO- https://astral.sh/uv/install.sh | sh", {
|
|
91
|
-
stdio: "
|
|
123
|
+
stdio: "ignore",
|
|
92
124
|
shell: "/bin/bash"
|
|
93
125
|
});
|
|
94
126
|
return true;
|
|
@@ -106,12 +138,40 @@ function createPythonVenv(targetDir) {
|
|
|
106
138
|
return false;
|
|
107
139
|
}
|
|
108
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
|
+
}
|
|
109
168
|
function parseArgs(args) {
|
|
110
169
|
const result = {
|
|
111
170
|
projectName: void 0,
|
|
112
171
|
directory: void 0,
|
|
113
172
|
template: void 0,
|
|
114
|
-
install:
|
|
173
|
+
install: true,
|
|
174
|
+
open: false,
|
|
115
175
|
yes: false,
|
|
116
176
|
force: false,
|
|
117
177
|
help: false
|
|
@@ -120,8 +180,10 @@ function parseArgs(args) {
|
|
|
120
180
|
const arg = args[i];
|
|
121
181
|
if (arg === "--help" || arg === "-h") {
|
|
122
182
|
result.help = true;
|
|
123
|
-
} else if (arg === "--install"
|
|
124
|
-
result.install =
|
|
183
|
+
} else if (arg === "--no-install") {
|
|
184
|
+
result.install = false;
|
|
185
|
+
} else if (arg === "--open" || arg === "-o") {
|
|
186
|
+
result.open = true;
|
|
125
187
|
} else if (arg === "--yes" || arg === "-y") {
|
|
126
188
|
result.yes = true;
|
|
127
189
|
} else if (arg === "--force" || arg === "-f") {
|
|
@@ -138,25 +200,26 @@ function parseArgs(args) {
|
|
|
138
200
|
}
|
|
139
201
|
function showHelp() {
|
|
140
202
|
console.log(`
|
|
141
|
-
${
|
|
203
|
+
${pc2.dim("Usage:")}
|
|
142
204
|
lumera init [name] [options]
|
|
143
205
|
|
|
144
|
-
${
|
|
206
|
+
${pc2.dim("Description:")}
|
|
145
207
|
Scaffold a new Lumera project from a template.
|
|
146
208
|
|
|
147
|
-
${
|
|
148
|
-
--template, -t <name> Template to use (run ${
|
|
209
|
+
${pc2.dim("Options:")}
|
|
210
|
+
--template, -t <name> Template to use (run ${pc2.cyan("lumera templates")} to see options)
|
|
149
211
|
--yes, -y Non-interactive mode (requires project name)
|
|
150
212
|
--dir, -d <path> Target directory (defaults to project name)
|
|
151
213
|
--force, -f Overwrite existing directory without prompting
|
|
152
|
-
--install
|
|
214
|
+
--no-install Skip dependency installation
|
|
215
|
+
--open, -o Open project in editor after scaffolding
|
|
153
216
|
--help, -h Show this help
|
|
154
217
|
|
|
155
|
-
${
|
|
218
|
+
${pc2.dim("Examples:")}
|
|
156
219
|
lumera init my-app # Interactive mode
|
|
157
220
|
lumera init my-app -t invoice-processing # Use a specific template
|
|
158
221
|
lumera init my-app -y # Non-interactive (default template)
|
|
159
|
-
lumera init my-app -t invoice-processing -y -
|
|
222
|
+
lumera init my-app -t invoice-processing -y -o # Full non-interactive + open editor
|
|
160
223
|
`);
|
|
161
224
|
}
|
|
162
225
|
async function init(args) {
|
|
@@ -166,34 +229,36 @@ async function init(args) {
|
|
|
166
229
|
return;
|
|
167
230
|
}
|
|
168
231
|
console.log();
|
|
169
|
-
console.log(
|
|
232
|
+
console.log(pc2.cyan(pc2.bold(" Create Lumera App")));
|
|
170
233
|
console.log();
|
|
171
234
|
let projectName = opts.projectName;
|
|
172
235
|
let directory = opts.directory;
|
|
173
236
|
const nonInteractive = opts.yes;
|
|
174
237
|
if (nonInteractive && !projectName) {
|
|
175
|
-
console.log(
|
|
176
|
-
console.log(
|
|
238
|
+
console.log(pc2.red(" Error: Project name is required in non-interactive mode"));
|
|
239
|
+
console.log(pc2.dim(" Usage: lumera init <name> -y"));
|
|
177
240
|
process.exit(1);
|
|
178
241
|
}
|
|
179
242
|
let templateName = opts.template;
|
|
180
243
|
if (!templateName && !nonInteractive) {
|
|
181
244
|
try {
|
|
245
|
+
const stop = spinner("Fetching templates...");
|
|
182
246
|
const available = await listAllTemplates();
|
|
247
|
+
stop();
|
|
183
248
|
if (available.length > 1) {
|
|
184
249
|
const response = await prompts({
|
|
185
250
|
type: "select",
|
|
186
251
|
name: "template",
|
|
187
252
|
message: "Choose a template",
|
|
188
253
|
choices: available.map((t) => ({
|
|
189
|
-
title: `${t.title} ${
|
|
254
|
+
title: `${t.title} ${pc2.dim(`(${t.name})`)}`,
|
|
190
255
|
description: t.description,
|
|
191
256
|
value: t.name
|
|
192
257
|
})),
|
|
193
258
|
initial: 0
|
|
194
259
|
});
|
|
195
260
|
if (!response.template) {
|
|
196
|
-
console.log(
|
|
261
|
+
console.log(pc2.red("Cancelled"));
|
|
197
262
|
process.exit(1);
|
|
198
263
|
}
|
|
199
264
|
templateName = response.template;
|
|
@@ -204,7 +269,9 @@ async function init(args) {
|
|
|
204
269
|
if (!templateName) {
|
|
205
270
|
templateName = "default";
|
|
206
271
|
}
|
|
272
|
+
const stopResolve = spinner("Resolving template...");
|
|
207
273
|
const templateDir = await resolveTemplate(templateName);
|
|
274
|
+
stopResolve();
|
|
208
275
|
if (!projectName) {
|
|
209
276
|
const response = await prompts({
|
|
210
277
|
type: "text",
|
|
@@ -220,13 +287,13 @@ async function init(args) {
|
|
|
220
287
|
}
|
|
221
288
|
});
|
|
222
289
|
if (!response.projectName) {
|
|
223
|
-
console.log(
|
|
290
|
+
console.log(pc2.red("Cancelled"));
|
|
224
291
|
process.exit(1);
|
|
225
292
|
}
|
|
226
293
|
projectName = response.projectName;
|
|
227
294
|
}
|
|
228
295
|
if (!/^[a-z0-9-]+$/.test(projectName)) {
|
|
229
|
-
console.log(
|
|
296
|
+
console.log(pc2.red(" Error: Project name must use lowercase letters, numbers, and hyphens only"));
|
|
230
297
|
process.exit(1);
|
|
231
298
|
}
|
|
232
299
|
if (!directory) {
|
|
@@ -240,7 +307,7 @@ async function init(args) {
|
|
|
240
307
|
initial: projectName
|
|
241
308
|
});
|
|
242
309
|
if (!response.directory) {
|
|
243
|
-
console.log(
|
|
310
|
+
console.log(pc2.red("Cancelled"));
|
|
244
311
|
process.exit(1);
|
|
245
312
|
}
|
|
246
313
|
directory = response.directory;
|
|
@@ -253,8 +320,8 @@ async function init(args) {
|
|
|
253
320
|
if (opts.force) {
|
|
254
321
|
rmSync(targetDir, { recursive: true });
|
|
255
322
|
} else {
|
|
256
|
-
console.log(
|
|
257
|
-
console.log(
|
|
323
|
+
console.log(pc2.red(` Error: Directory ${directory} already exists`));
|
|
324
|
+
console.log(pc2.dim(" Use --force (-f) to overwrite"));
|
|
258
325
|
process.exit(1);
|
|
259
326
|
}
|
|
260
327
|
} else {
|
|
@@ -265,7 +332,7 @@ async function init(args) {
|
|
|
265
332
|
initial: false
|
|
266
333
|
});
|
|
267
334
|
if (!overwrite) {
|
|
268
|
-
console.log(
|
|
335
|
+
console.log(pc2.red("Cancelled"));
|
|
269
336
|
process.exit(1);
|
|
270
337
|
}
|
|
271
338
|
rmSync(targetDir, { recursive: true });
|
|
@@ -274,9 +341,9 @@ async function init(args) {
|
|
|
274
341
|
mkdirSync(targetDir, { recursive: true });
|
|
275
342
|
console.log();
|
|
276
343
|
if (templateName !== "default") {
|
|
277
|
-
console.log(
|
|
344
|
+
console.log(pc2.dim(` Creating ${projectName} from template ${pc2.cyan(templateName)}...`));
|
|
278
345
|
} else {
|
|
279
|
-
console.log(
|
|
346
|
+
console.log(pc2.dim(` Creating ${projectName}...`));
|
|
280
347
|
}
|
|
281
348
|
console.log();
|
|
282
349
|
const vars = {
|
|
@@ -290,61 +357,55 @@ async function init(args) {
|
|
|
290
357
|
if (entry.isDirectory()) {
|
|
291
358
|
listFiles(join(dir, entry.name), relativePath + "/");
|
|
292
359
|
} else {
|
|
293
|
-
console.log(
|
|
360
|
+
console.log(pc2.green(" \u2713"), pc2.dim(relativePath));
|
|
294
361
|
}
|
|
295
362
|
}
|
|
296
363
|
}
|
|
297
364
|
listFiles(targetDir);
|
|
298
365
|
if (isGitInstalled()) {
|
|
299
|
-
|
|
300
|
-
console.log(pc.dim(" Initializing git repository..."));
|
|
366
|
+
const stopGit = spinner("Initializing git repository...");
|
|
301
367
|
if (initGitRepo(targetDir, projectName)) {
|
|
302
|
-
|
|
368
|
+
stopGit(pc2.green("\u2713") + pc2.dim(" Git repository initialized with initial commit"));
|
|
303
369
|
} else {
|
|
304
|
-
|
|
370
|
+
stopGit(pc2.yellow("\u26A0") + pc2.dim(" Failed to initialize git repository"));
|
|
305
371
|
}
|
|
306
372
|
} else {
|
|
307
|
-
console.log();
|
|
308
|
-
console.log(pc.yellow(" \u26A0"), pc.dim("Git not found - skipping repository initialization"));
|
|
373
|
+
console.log(pc2.yellow(" \u26A0"), pc2.dim("Git not found \u2014 skipping repository initialization"));
|
|
309
374
|
}
|
|
310
375
|
let uvAvailable = isUvInstalled();
|
|
311
376
|
if (!uvAvailable) {
|
|
312
|
-
|
|
313
|
-
console.log(pc.dim(" Installing uv (Python package manager)..."));
|
|
377
|
+
const stopUv = spinner("Installing uv (Python package manager)...");
|
|
314
378
|
if (installUv()) {
|
|
315
|
-
|
|
379
|
+
stopUv(pc2.green("\u2713") + pc2.dim(" uv installed successfully"));
|
|
316
380
|
uvAvailable = true;
|
|
317
381
|
} else {
|
|
318
|
-
|
|
382
|
+
stopUv(pc2.yellow("\u26A0") + pc2.dim(" Failed to install uv \u2014 install manually: https://docs.astral.sh/uv/"));
|
|
319
383
|
}
|
|
320
384
|
}
|
|
321
385
|
if (uvAvailable) {
|
|
322
|
-
|
|
323
|
-
console.log(pc.dim(" Creating Python venv with Lumera SDK..."));
|
|
386
|
+
const stopVenv = spinner("Creating Python venv with Lumera SDK...");
|
|
324
387
|
if (createPythonVenv(targetDir)) {
|
|
325
|
-
|
|
388
|
+
stopVenv(pc2.green("\u2713") + pc2.dim(" Python venv created (.venv/) with lumera SDK"));
|
|
326
389
|
} else {
|
|
327
|
-
|
|
390
|
+
stopVenv(pc2.yellow("\u26A0") + pc2.dim(" Failed to create Python venv"));
|
|
328
391
|
}
|
|
329
392
|
}
|
|
330
393
|
if (opts.install) {
|
|
331
|
-
|
|
332
|
-
console.log(pc.dim(" Installing dependencies..."));
|
|
394
|
+
const stopInstall = spinner("Installing dependencies...");
|
|
333
395
|
try {
|
|
334
|
-
execSync("pnpm install", { cwd: targetDir, stdio: "
|
|
335
|
-
|
|
396
|
+
execSync("pnpm install", { cwd: targetDir, stdio: "ignore" });
|
|
397
|
+
stopInstall(pc2.green("\u2713") + pc2.dim(" Dependencies installed"));
|
|
336
398
|
} catch {
|
|
337
|
-
|
|
399
|
+
stopInstall(pc2.yellow("\u26A0") + pc2.dim(" Failed to install dependencies"));
|
|
338
400
|
}
|
|
339
401
|
}
|
|
340
|
-
|
|
341
|
-
console.log(pc.dim(" Installing Lumera skills for AI agents..."));
|
|
402
|
+
const stopSkills = spinner("Installing Lumera skills for AI agents...");
|
|
342
403
|
try {
|
|
343
404
|
const { installed, failed } = await installAllSkills(targetDir);
|
|
344
405
|
if (failed > 0) {
|
|
345
|
-
|
|
406
|
+
stopSkills(pc2.yellow("\u26A0") + pc2.dim(` Installed ${installed} skills (${failed} failed)`));
|
|
346
407
|
} else {
|
|
347
|
-
|
|
408
|
+
stopSkills(pc2.green("\u2713") + pc2.dim(` ${installed} Lumera skills installed`));
|
|
348
409
|
}
|
|
349
410
|
syncClaudeMd(targetDir);
|
|
350
411
|
if (isGitInstalled()) {
|
|
@@ -357,20 +418,22 @@ async function init(args) {
|
|
|
357
418
|
}
|
|
358
419
|
}
|
|
359
420
|
} catch (err) {
|
|
360
|
-
|
|
421
|
+
stopSkills(pc2.yellow("\u26A0") + pc2.dim(` Failed to install skills: ${err}`));
|
|
361
422
|
}
|
|
362
423
|
console.log();
|
|
363
|
-
console.log(
|
|
424
|
+
console.log(pc2.green(pc2.bold(" Done!")), "Next steps:");
|
|
364
425
|
console.log();
|
|
365
|
-
console.log(
|
|
426
|
+
console.log(pc2.cyan(` cd ${directory}`));
|
|
366
427
|
if (!opts.install) {
|
|
367
|
-
console.log(
|
|
428
|
+
console.log(pc2.cyan(" pnpm install"));
|
|
368
429
|
}
|
|
369
|
-
console.log(
|
|
370
|
-
console.log(
|
|
371
|
-
console.log(pc.cyan(" lumera run scripts/seed-demo.py"));
|
|
372
|
-
console.log(pc.cyan(" lumera dev"));
|
|
430
|
+
console.log(pc2.cyan(" lumera login"));
|
|
431
|
+
console.log(pc2.cyan(" lumera dev"));
|
|
373
432
|
console.log();
|
|
433
|
+
if (opts.open) {
|
|
434
|
+
openInEditor(targetDir);
|
|
435
|
+
console.log();
|
|
436
|
+
}
|
|
374
437
|
}
|
|
375
438
|
export {
|
|
376
439
|
init
|
|
@@ -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
|
-
};
|
package/dist/dev-BHBF4ECH.js
DELETED
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
dev
|
|
3
|
-
} from "./chunk-CDZZ3JYU.js";
|
|
4
|
-
import {
|
|
5
|
-
loadEnv
|
|
6
|
-
} from "./chunk-2CR762KB.js";
|
|
7
|
-
import {
|
|
8
|
-
getToken
|
|
9
|
-
} from "./chunk-NDLYGKS6.js";
|
|
10
|
-
import {
|
|
11
|
-
findProjectRoot,
|
|
12
|
-
getApiUrl,
|
|
13
|
-
getAppName,
|
|
14
|
-
getAppTitle
|
|
15
|
-
} from "./chunk-D2BLSEGR.js";
|
|
16
|
-
|
|
17
|
-
// src/commands/dev.ts
|
|
18
|
-
import pc from "picocolors";
|
|
19
|
-
function parseFlags(args) {
|
|
20
|
-
const result = {};
|
|
21
|
-
for (let i = 0; i < args.length; i++) {
|
|
22
|
-
const arg = args[i];
|
|
23
|
-
if (arg.startsWith("--")) {
|
|
24
|
-
const key = arg.slice(2);
|
|
25
|
-
const next = args[i + 1];
|
|
26
|
-
if (next && !next.startsWith("--")) {
|
|
27
|
-
result[key] = next;
|
|
28
|
-
i++;
|
|
29
|
-
} else {
|
|
30
|
-
result[key] = true;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
return result;
|
|
35
|
-
}
|
|
36
|
-
function showHelp() {
|
|
37
|
-
console.log(`
|
|
38
|
-
${pc.dim("Usage:")}
|
|
39
|
-
lumera dev [options]
|
|
40
|
-
|
|
41
|
-
${pc.dim("Description:")}
|
|
42
|
-
Start the development server with Lumera registration.
|
|
43
|
-
Registers your app with Lumera and starts a local dev server.
|
|
44
|
-
|
|
45
|
-
${pc.dim("Options:")}
|
|
46
|
-
--port <number> Dev server port (default: 8080)
|
|
47
|
-
--url <url> App URL for dev mode (default: http://localhost:{port})
|
|
48
|
-
--help, -h Show this help
|
|
49
|
-
|
|
50
|
-
${pc.dim("Environment variables:")}
|
|
51
|
-
LUMERA_TOKEN API token (overrides \`lumera login\`)
|
|
52
|
-
LUMERA_API_URL API base URL (default: https://app.lumerahq.com/api)
|
|
53
|
-
PORT Dev server port (default: 8080)
|
|
54
|
-
APP_URL App URL for dev mode
|
|
55
|
-
|
|
56
|
-
${pc.dim("Examples:")}
|
|
57
|
-
lumera dev # Start dev server on port 8080
|
|
58
|
-
lumera dev --port 3000 # Start dev server on port 3000
|
|
59
|
-
lumera dev --url http://192.168.1.100:8080 # Custom URL for mobile testing
|
|
60
|
-
`);
|
|
61
|
-
}
|
|
62
|
-
async function dev2(args) {
|
|
63
|
-
if (args.includes("--help") || args.includes("-h")) {
|
|
64
|
-
showHelp();
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
const flags = parseFlags(args);
|
|
68
|
-
const projectRoot = findProjectRoot();
|
|
69
|
-
loadEnv(projectRoot);
|
|
70
|
-
const token = getToken(projectRoot);
|
|
71
|
-
const appName = getAppName(projectRoot);
|
|
72
|
-
const appTitle = getAppTitle(projectRoot);
|
|
73
|
-
const apiUrl = getApiUrl();
|
|
74
|
-
const port = Number(flags.port || process.env.PORT || "8080");
|
|
75
|
-
const appUrl = typeof flags.url === "string" ? flags.url : process.env.APP_URL;
|
|
76
|
-
await dev({
|
|
77
|
-
token,
|
|
78
|
-
appName,
|
|
79
|
-
appTitle,
|
|
80
|
-
port,
|
|
81
|
-
appUrl,
|
|
82
|
-
apiUrl
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
export {
|
|
86
|
-
dev2 as dev
|
|
87
|
-
};
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
listAllTemplates
|
|
3
|
-
} from "./chunk-WTDV3MTG.js";
|
|
4
|
-
|
|
5
|
-
// src/commands/templates.ts
|
|
6
|
-
import pc from "picocolors";
|
|
7
|
-
function showHelp() {
|
|
8
|
-
console.log(`
|
|
9
|
-
${pc.dim("Usage:")}
|
|
10
|
-
lumera templates [options]
|
|
11
|
-
|
|
12
|
-
${pc.dim("Description:")}
|
|
13
|
-
List available project templates.
|
|
14
|
-
|
|
15
|
-
${pc.dim("Options:")}
|
|
16
|
-
--verbose, -v Show full descriptions
|
|
17
|
-
--help, -h Show this help
|
|
18
|
-
|
|
19
|
-
${pc.dim("Examples:")}
|
|
20
|
-
lumera templates # List available templates
|
|
21
|
-
lumera templates -v # Show with descriptions
|
|
22
|
-
lumera init my-app -t invoice-processing # Use a template
|
|
23
|
-
`);
|
|
24
|
-
}
|
|
25
|
-
async function templates(args) {
|
|
26
|
-
if (args.includes("--help") || args.includes("-h")) {
|
|
27
|
-
showHelp();
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
const verbose = args.includes("--verbose") || args.includes("-v");
|
|
31
|
-
console.log();
|
|
32
|
-
console.log(pc.cyan(pc.bold(" Available Templates")));
|
|
33
|
-
console.log();
|
|
34
|
-
try {
|
|
35
|
-
const allTemplates = await listAllTemplates();
|
|
36
|
-
if (allTemplates.length === 0) {
|
|
37
|
-
console.log(pc.dim(" No templates available."));
|
|
38
|
-
console.log();
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
const byCategory = /* @__PURE__ */ new Map();
|
|
42
|
-
for (const t of allTemplates) {
|
|
43
|
-
const cat = t.category || "General";
|
|
44
|
-
if (!byCategory.has(cat)) byCategory.set(cat, []);
|
|
45
|
-
byCategory.get(cat).push(t);
|
|
46
|
-
}
|
|
47
|
-
for (const [category, items] of byCategory) {
|
|
48
|
-
console.log(pc.bold(` ${category}`));
|
|
49
|
-
console.log();
|
|
50
|
-
for (const t of items) {
|
|
51
|
-
console.log(` ${pc.green(t.title)} ${pc.dim(`(${t.name})`)}`);
|
|
52
|
-
if (verbose) {
|
|
53
|
-
console.log(` ${pc.dim(t.description)}`);
|
|
54
|
-
console.log();
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
if (!verbose) console.log();
|
|
58
|
-
}
|
|
59
|
-
console.log(pc.dim(` ${allTemplates.length} template${allTemplates.length === 1 ? "" : "s"} available.`));
|
|
60
|
-
console.log();
|
|
61
|
-
console.log(pc.dim(" Usage:"), pc.cyan("lumera init <name> --template <template-name>"));
|
|
62
|
-
console.log();
|
|
63
|
-
} catch (err) {
|
|
64
|
-
console.error(pc.red(` Error: ${err}`));
|
|
65
|
-
process.exit(1);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
export {
|
|
69
|
-
templates
|
|
70
|
-
};
|