@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,155 @@
|
|
|
1
|
+
// src/lib/templates.ts
|
|
2
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, unlinkSync, writeFileSync } from "fs";
|
|
3
|
+
import { homedir, tmpdir } from "os";
|
|
4
|
+
import { join } from "path";
|
|
5
|
+
import { spawnSync } from "child_process";
|
|
6
|
+
var GITHUB_OWNER = "lumerahq";
|
|
7
|
+
var GITHUB_REPO = "app-templates";
|
|
8
|
+
var GITHUB_REF = "main";
|
|
9
|
+
function getCacheDir() {
|
|
10
|
+
return join(homedir(), ".lumera", "templates");
|
|
11
|
+
}
|
|
12
|
+
function readCacheMeta() {
|
|
13
|
+
const metaPath = join(getCacheDir(), ".cache-meta.json");
|
|
14
|
+
if (!existsSync(metaPath)) return null;
|
|
15
|
+
try {
|
|
16
|
+
return JSON.parse(readFileSync(metaPath, "utf-8"));
|
|
17
|
+
} catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
async function fetchLatestSha() {
|
|
22
|
+
const url = `https://api.github.com/repos/${GITHUB_OWNER}/${GITHUB_REPO}/commits/${GITHUB_REF}`;
|
|
23
|
+
const res = await fetch(url, {
|
|
24
|
+
headers: { Accept: "application/vnd.github.sha" }
|
|
25
|
+
});
|
|
26
|
+
if (!res.ok) throw new Error(`GitHub API error: ${res.status}`);
|
|
27
|
+
return (await res.text()).trim();
|
|
28
|
+
}
|
|
29
|
+
async function downloadAndExtract(commitSha) {
|
|
30
|
+
const cacheDir = getCacheDir();
|
|
31
|
+
const tarballUrl = `https://github.com/${GITHUB_OWNER}/${GITHUB_REPO}/archive/refs/heads/${GITHUB_REF}.tar.gz`;
|
|
32
|
+
const res = await fetch(tarballUrl);
|
|
33
|
+
if (!res.ok) throw new Error(`Failed to download templates: ${res.status}`);
|
|
34
|
+
const tmpFile = join(tmpdir(), `lumera-templates-${Date.now()}.tar.gz`);
|
|
35
|
+
const buffer = Buffer.from(await res.arrayBuffer());
|
|
36
|
+
writeFileSync(tmpFile, buffer);
|
|
37
|
+
mkdirSync(cacheDir, { recursive: true });
|
|
38
|
+
for (const entry of readdirSync(cacheDir)) {
|
|
39
|
+
if (entry === ".cache-meta.json" || entry === ".refs") continue;
|
|
40
|
+
rmSync(join(cacheDir, entry), { recursive: true, force: true });
|
|
41
|
+
}
|
|
42
|
+
const result = spawnSync("tar", ["xzf", tmpFile, "-C", cacheDir, "--strip-components=1"], { stdio: "ignore" });
|
|
43
|
+
if (result.status !== 0) throw new Error("Failed to extract template archive");
|
|
44
|
+
unlinkSync(tmpFile);
|
|
45
|
+
const meta = { commitSha, fetchedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
46
|
+
writeFileSync(join(cacheDir, ".cache-meta.json"), JSON.stringify(meta));
|
|
47
|
+
return cacheDir;
|
|
48
|
+
}
|
|
49
|
+
async function ensureRemoteCache() {
|
|
50
|
+
const cacheDir = getCacheDir();
|
|
51
|
+
const cached = readCacheMeta();
|
|
52
|
+
const latestSha = await fetchLatestSha();
|
|
53
|
+
if (cached?.commitSha === latestSha && existsSync(cacheDir)) {
|
|
54
|
+
return cacheDir;
|
|
55
|
+
}
|
|
56
|
+
console.log(" Fetching latest templates...");
|
|
57
|
+
return downloadAndExtract(latestSha);
|
|
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
|
+
}
|
|
104
|
+
function listTemplates(baseDir) {
|
|
105
|
+
if (!existsSync(baseDir)) return [];
|
|
106
|
+
const templates = [];
|
|
107
|
+
for (const entry of readdirSync(baseDir, { withFileTypes: true })) {
|
|
108
|
+
if (!entry.isDirectory()) continue;
|
|
109
|
+
const metaPath = join(baseDir, entry.name, "template.json");
|
|
110
|
+
if (!existsSync(metaPath)) continue;
|
|
111
|
+
try {
|
|
112
|
+
const raw = JSON.parse(readFileSync(metaPath, "utf-8"));
|
|
113
|
+
if (!raw.name || !raw.title || !raw.description || !raw.category) {
|
|
114
|
+
console.warn(`Warning: Skipping template "${entry.name}" \u2014 template.json is missing required fields (name, title, description, category)`);
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
templates.push(raw);
|
|
118
|
+
} catch {
|
|
119
|
+
console.warn(`Warning: Skipping template "${entry.name}" \u2014 invalid template.json`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return templates.sort((a, b) => {
|
|
123
|
+
if (a.name === "default") return -1;
|
|
124
|
+
if (b.name === "default") return 1;
|
|
125
|
+
return a.title.localeCompare(b.title);
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
async function resolveTemplate(nameWithRef) {
|
|
129
|
+
const { name, ref } = parseTemplateRef(nameWithRef);
|
|
130
|
+
const cacheDir = ref ? await ensureRemoteCacheForRef(ref) : await ensureRemoteCache();
|
|
131
|
+
const dir = join(cacheDir, name);
|
|
132
|
+
if (!existsSync(dir) || !existsSync(join(dir, "template.json"))) {
|
|
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
|
+
}
|
|
139
|
+
throw new Error(
|
|
140
|
+
`Template "${name}" not found. Available: ${available || "none"}
|
|
141
|
+
Cache location: ${cacheDir}
|
|
142
|
+
Try deleting the cache and retrying: rm -rf ${cacheDir}`
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
return dir;
|
|
146
|
+
}
|
|
147
|
+
async function listAllTemplates() {
|
|
148
|
+
const cacheDir = await ensureRemoteCache();
|
|
149
|
+
return listTemplates(cacheDir);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export {
|
|
153
|
+
resolveTemplate,
|
|
154
|
+
listAllTemplates
|
|
155
|
+
};
|
|
@@ -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,6 +143,8 @@ ${pc.dim("Development:")}
|
|
|
32
143
|
|
|
33
144
|
${pc.dim("Project:")}
|
|
34
145
|
${pc.cyan("init")} [name] Scaffold a new project
|
|
146
|
+
${pc.cyan("templates list")} List available templates
|
|
147
|
+
${pc.cyan("templates validate")} Validate a template directory
|
|
35
148
|
${pc.cyan("status")} Show project info
|
|
36
149
|
${pc.cyan("migrate")} Upgrade legacy project
|
|
37
150
|
|
|
@@ -48,6 +161,7 @@ ${pc.dim("Auth:")}
|
|
|
48
161
|
${pc.dim("Options:")}
|
|
49
162
|
--help, -h Show help
|
|
50
163
|
--version, -v Show version
|
|
164
|
+
--json Output as JSON (where supported)
|
|
51
165
|
|
|
52
166
|
${pc.dim("Resource Types:")}
|
|
53
167
|
collections Data collections (schemas)
|
|
@@ -88,37 +202,42 @@ async function main() {
|
|
|
88
202
|
process.exit(1);
|
|
89
203
|
}
|
|
90
204
|
}
|
|
205
|
+
const startTime = performance.now();
|
|
206
|
+
const updateCheck = checkForUpdate(VERSION);
|
|
91
207
|
try {
|
|
92
208
|
switch (command) {
|
|
93
209
|
// Resource commands
|
|
94
210
|
case "plan":
|
|
95
|
-
await import("./resources-
|
|
211
|
+
await import("./resources-GTG3QMVV.js").then((m) => m.plan(args.slice(1)));
|
|
96
212
|
break;
|
|
97
213
|
case "apply":
|
|
98
|
-
await import("./resources-
|
|
214
|
+
await import("./resources-GTG3QMVV.js").then((m) => m.apply(args.slice(1)));
|
|
99
215
|
break;
|
|
100
216
|
case "pull":
|
|
101
|
-
await import("./resources-
|
|
217
|
+
await import("./resources-GTG3QMVV.js").then((m) => m.pull(args.slice(1)));
|
|
102
218
|
break;
|
|
103
219
|
case "destroy":
|
|
104
|
-
await import("./resources-
|
|
220
|
+
await import("./resources-GTG3QMVV.js").then((m) => m.destroy(args.slice(1)));
|
|
105
221
|
break;
|
|
106
222
|
case "list":
|
|
107
|
-
await import("./resources-
|
|
223
|
+
await import("./resources-GTG3QMVV.js").then((m) => m.list(args.slice(1)));
|
|
108
224
|
break;
|
|
109
225
|
case "show":
|
|
110
|
-
await import("./resources-
|
|
226
|
+
await import("./resources-GTG3QMVV.js").then((m) => m.show(args.slice(1)));
|
|
111
227
|
break;
|
|
112
228
|
// Development
|
|
113
229
|
case "dev":
|
|
114
|
-
await import("./dev-
|
|
230
|
+
await import("./dev-2HVDP3NX.js").then((m) => m.dev(args.slice(1)));
|
|
115
231
|
break;
|
|
116
232
|
case "run":
|
|
117
|
-
await import("./run-
|
|
233
|
+
await import("./run-47GF5VVS.js").then((m) => m.run(args.slice(1)));
|
|
118
234
|
break;
|
|
119
235
|
// Project
|
|
120
236
|
case "init":
|
|
121
|
-
await import("./init-
|
|
237
|
+
await import("./init-VNJNSU4Q.js").then((m) => m.init(args.slice(1)));
|
|
238
|
+
break;
|
|
239
|
+
case "templates":
|
|
240
|
+
await import("./templates-6KMZWOYH.js").then((m) => m.templates(subcommand, args.slice(2)));
|
|
122
241
|
break;
|
|
123
242
|
case "status":
|
|
124
243
|
await import("./status-E4IHEUKO.js").then((m) => m.status(args.slice(1)));
|
|
@@ -140,10 +259,43 @@ async function main() {
|
|
|
140
259
|
case "whoami":
|
|
141
260
|
await import("./auth-7RGL7GXU.js").then((m) => m.whoami());
|
|
142
261
|
break;
|
|
143
|
-
|
|
262
|
+
// Convenience aliases
|
|
263
|
+
case "help":
|
|
264
|
+
showHelp();
|
|
265
|
+
break;
|
|
266
|
+
case "version":
|
|
267
|
+
showVersion();
|
|
268
|
+
break;
|
|
269
|
+
default: {
|
|
270
|
+
const suggestion = suggestCommand(command);
|
|
144
271
|
console.error(pc.red(`Unknown command: ${command}`));
|
|
272
|
+
if (suggestion) {
|
|
273
|
+
console.error(`Did you mean ${pc.cyan(suggestion)}?`);
|
|
274
|
+
}
|
|
145
275
|
console.error(`Run ${pc.cyan("lumera --help")} for usage.`);
|
|
146
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
|
+
}
|
|
147
299
|
}
|
|
148
300
|
} catch (error) {
|
|
149
301
|
if (error instanceof Error) {
|