@lumerahq/cli 0.19.8-dev.1 → 0.19.9-dev.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.
@@ -195,6 +195,12 @@ var ApiClient = class {
195
195
  body: JSON.stringify(def)
196
196
  });
197
197
  }
198
+ async compileHook(script) {
199
+ await this.request("/api/pb/hooks/compile", {
200
+ method: "POST",
201
+ body: JSON.stringify({ script })
202
+ });
203
+ }
198
204
  async updateHook(id, def) {
199
205
  return this.request(`/api/pb/hooks/${id}`, {
200
206
  method: "PATCH",
@@ -355,6 +361,7 @@ function createApiClient(token, baseUrl, projectExternalId) {
355
361
  }
356
362
 
357
363
  export {
364
+ ApiError,
358
365
  isApiErrorStatus,
359
366
  createApiClient
360
367
  };
@@ -4,7 +4,7 @@ import {
4
4
  import {
5
5
  createApiClient,
6
6
  isApiErrorStatus
7
- } from "./chunk-SMZESQV2.js";
7
+ } from "./chunk-EDRAYUWN.js";
8
8
  import {
9
9
  findProjectRoot,
10
10
  getAppName
@@ -2,9 +2,9 @@ import {
2
2
  deps,
3
3
  projectResourceDepsEnabled,
4
4
  syncDeps
5
- } from "./chunk-2WDZ3QKS.js";
5
+ } from "./chunk-WWEIOMW6.js";
6
6
  import "./chunk-2CR762KB.js";
7
- import "./chunk-SMZESQV2.js";
7
+ import "./chunk-EDRAYUWN.js";
8
8
  import "./chunk-ZH3NVYEQ.js";
9
9
  import "./chunk-FJFIWC7G.js";
10
10
  import "./chunk-PNKVD2UK.js";
@@ -4,13 +4,13 @@ import {
4
4
  import {
5
5
  projectResourceDepsEnabled,
6
6
  syncDeps
7
- } from "./chunk-2WDZ3QKS.js";
7
+ } from "./chunk-WWEIOMW6.js";
8
8
  import {
9
9
  loadEnv
10
10
  } from "./chunk-2CR762KB.js";
11
11
  import {
12
12
  createApiClient
13
- } from "./chunk-SMZESQV2.js";
13
+ } from "./chunk-EDRAYUWN.js";
14
14
  import {
15
15
  findProjectRoot,
16
16
  getApiUrl,
package/dist/index.js CHANGED
@@ -219,39 +219,39 @@ async function main() {
219
219
  switch (command) {
220
220
  // Resource commands
221
221
  case "plan":
222
- await import("./resources-EPCEYCFY.js").then((m) => m.plan(args.slice(1)));
222
+ await import("./resources-VGUMSRND.js").then((m) => m.plan(args.slice(1)));
223
223
  break;
224
224
  case "apply":
225
- await import("./resources-EPCEYCFY.js").then((m) => m.apply(args.slice(1)));
225
+ await import("./resources-VGUMSRND.js").then((m) => m.apply(args.slice(1)));
226
226
  break;
227
227
  case "pull":
228
- await import("./resources-EPCEYCFY.js").then((m) => m.pull(args.slice(1)));
228
+ await import("./resources-VGUMSRND.js").then((m) => m.pull(args.slice(1)));
229
229
  break;
230
230
  case "destroy":
231
- await import("./resources-EPCEYCFY.js").then((m) => m.destroy(args.slice(1)));
231
+ await import("./resources-VGUMSRND.js").then((m) => m.destroy(args.slice(1)));
232
232
  break;
233
233
  case "list":
234
- await import("./resources-EPCEYCFY.js").then((m) => m.list(args.slice(1)));
234
+ await import("./resources-VGUMSRND.js").then((m) => m.list(args.slice(1)));
235
235
  break;
236
236
  case "show":
237
- await import("./resources-EPCEYCFY.js").then((m) => m.show(args.slice(1)));
237
+ await import("./resources-VGUMSRND.js").then((m) => m.show(args.slice(1)));
238
238
  break;
239
239
  case "diff":
240
- await import("./resources-EPCEYCFY.js").then((m) => m.diff(args.slice(1)));
240
+ await import("./resources-VGUMSRND.js").then((m) => m.diff(args.slice(1)));
241
241
  break;
242
242
  // Development
243
243
  case "dev":
244
- await import("./dev-RENAXSGD.js").then((m) => m.dev(args.slice(1)));
244
+ await import("./dev-3SDNIPWA.js").then((m) => m.dev(args.slice(1)));
245
245
  break;
246
246
  case "run":
247
- await import("./run-XWXUBWWH.js").then((m) => m.run(args.slice(1)));
247
+ await import("./run-YUL73K5O.js").then((m) => m.run(args.slice(1)));
248
248
  break;
249
249
  // Project
250
250
  case "init":
251
- await import("./init-J5BNFCSP.js").then((m) => m.init(args.slice(1)));
251
+ await import("./init-FW4RFXLL.js").then((m) => m.init(args.slice(1)));
252
252
  break;
253
253
  case "register":
254
- await import("./register-ZYUFXURK.js").then((m) => m.register(args.slice(1)));
254
+ await import("./register-HR2QQFWX.js").then((m) => m.register(args.slice(1)));
255
255
  break;
256
256
  case "templates":
257
257
  await import("./templates-LNUOTNLN.js").then((m) => m.templates(subcommand, args.slice(2)));
@@ -268,7 +268,7 @@ async function main() {
268
268
  break;
269
269
  // Dependencies
270
270
  case "deps":
271
- await import("./deps-53AOYHH2.js").then((m) => m.deps(args.slice(1)));
271
+ await import("./deps-WSZGH35V.js").then((m) => m.deps(args.slice(1)));
272
272
  break;
273
273
  // Auth
274
274
  case "login":
@@ -7,7 +7,7 @@ import {
7
7
  } from "./chunk-BHYDYR75.js";
8
8
  import {
9
9
  createApiClient
10
- } from "./chunk-SMZESQV2.js";
10
+ } from "./chunk-EDRAYUWN.js";
11
11
  import {
12
12
  getToken,
13
13
  init_auth,
@@ -6,7 +6,7 @@ import {
6
6
  } from "./chunk-BHYDYR75.js";
7
7
  import {
8
8
  createApiClient
9
- } from "./chunk-SMZESQV2.js";
9
+ } from "./chunk-EDRAYUWN.js";
10
10
  import {
11
11
  findProjectRoot,
12
12
  getAppName,
@@ -4,13 +4,14 @@ import {
4
4
  import {
5
5
  projectResourceDepsEnabled,
6
6
  syncDeps
7
- } from "./chunk-2WDZ3QKS.js";
7
+ } from "./chunk-WWEIOMW6.js";
8
8
  import {
9
9
  loadEnv
10
10
  } from "./chunk-2CR762KB.js";
11
11
  import {
12
+ ApiError,
12
13
  createApiClient
13
- } from "./chunk-SMZESQV2.js";
14
+ } from "./chunk-EDRAYUWN.js";
14
15
  import {
15
16
  findProjectRoot,
16
17
  getApiUrl,
@@ -119,6 +120,108 @@ var collectionSchemaRule = {
119
120
  }
120
121
  };
121
122
 
123
+ // src/lib/hooks-parse.ts
124
+ function parseHookConfig(content) {
125
+ const configMatch = content.match(/export\s+const\s+config\s*[=:]\s*(\{[\s\S]*?\});?/);
126
+ if (!configMatch) return null;
127
+ const externalId = configMatch[1].match(/external_id\s*:\s*['"]([^'"]+)['"]/)?.[1];
128
+ const collection = configMatch[1].match(/collection\s*:\s*['"]([^'"]+)['"]/)?.[1];
129
+ const trigger = configMatch[1].match(/trigger\s*:\s*['"]([^'"]+)['"]/)?.[1];
130
+ const enabled = configMatch[1].match(/enabled\s*:\s*(true|false)/)?.[1];
131
+ const name = configMatch[1].match(/name\s*:\s*['"]([^'"]+)['"]/)?.[1];
132
+ if (!collection || !trigger) return null;
133
+ const hook = {
134
+ external_id: externalId || "",
135
+ collection,
136
+ trigger,
137
+ enabled: enabled !== "false"
138
+ };
139
+ if (name) hook.name = name;
140
+ const metadataMatch = configMatch[1].match(/metadata\s*:\s*(\{[^}]*\})/);
141
+ if (metadataMatch) {
142
+ try {
143
+ const metaStr = metadataMatch[1].replace(/'/g, '"').replace(/(\w+)\s*:/g, '"$1":').replace(/,\s*}/g, "}");
144
+ hook.metadata = JSON.parse(metaStr);
145
+ } catch {
146
+ }
147
+ }
148
+ return hook;
149
+ }
150
+ function extractHookScript(content) {
151
+ const handlerMatch = content.match(
152
+ /export\s+default\s+(?:async\s+)?function\s*(?:\w+)?\s*\([^)]*\)\s*\{([\s\S]*)\}[\s\n]*$/
153
+ );
154
+ if (handlerMatch) {
155
+ return handlerMatch[1].trim();
156
+ }
157
+ const simpleMatch = content.match(
158
+ /export\s+default\s+async\s+function[^{]*\{([\s\S]*)\}[\s\n]*$/
159
+ );
160
+ if (simpleMatch) {
161
+ return simpleMatch[1].trim();
162
+ }
163
+ return content.replace(/export\s+const\s+config[\s\S]*?;/, "").trim();
164
+ }
165
+ function hasDefaultHandler(content) {
166
+ return /export\s+default\s+(?:async\s+)?function\b/.test(content);
167
+ }
168
+
169
+ // src/lib/lint/rules/hook-verify.ts
170
+ function error2(target, message) {
171
+ return {
172
+ ruleId: "hook-verify",
173
+ target,
174
+ severity: "error",
175
+ message
176
+ };
177
+ }
178
+ var hookVerifyRule = {
179
+ id: "hook-verify",
180
+ description: "Validates hook file structure (config export + default handler) and verifies the script compiles via the backend.",
181
+ appliesTo: ["hook"],
182
+ async check(target, ctx) {
183
+ const config = parseHookConfig(target.source);
184
+ if (!config) {
185
+ return [
186
+ error2(
187
+ target,
188
+ "Missing or unparseable `export const config = {...}`. Hook files must export a config object with at least `collection` and `trigger`."
189
+ )
190
+ ];
191
+ }
192
+ if (!config.external_id && !ctx.appName) {
193
+ return [
194
+ error2(
195
+ target,
196
+ "Hook config is missing `external_id` and no app name is configured to derive one from the file name."
197
+ )
198
+ ];
199
+ }
200
+ if (!hasDefaultHandler(target.source)) {
201
+ return [
202
+ error2(
203
+ target,
204
+ "Missing default exported handler. Hook files must `export default function(ctx) { ... }` (async allowed)."
205
+ )
206
+ ];
207
+ }
208
+ let script = extractHookScript(target.source);
209
+ if (ctx.appName) {
210
+ script = script.replaceAll("{{app}}", ctx.appName);
211
+ }
212
+ if (!ctx.api) {
213
+ return [];
214
+ }
215
+ try {
216
+ await ctx.api.compileHook(script);
217
+ } catch (err) {
218
+ const detail = err instanceof ApiError ? err.body.trim() : err instanceof Error ? err.message : String(err);
219
+ return [error2(target, `Hook failed to compile on the backend: ${detail}`)];
220
+ }
221
+ return [];
222
+ }
223
+ };
224
+
122
225
  // src/lib/lint/rules/llm-import.ts
123
226
  var FROM_LUMERA_IMPORT_LLM = /^\s*from\s+lumera\s+import\s+\(?\s*(?:[\w\s,]*?\b)?llm\b/;
124
227
  var LUMERA_LLM_SUBMODULE = /^\s*(?:from\s+lumera\.llm\b|import\s+lumera\.llm\b)/;
@@ -162,7 +265,7 @@ var llmImportRule = {
162
265
  };
163
266
 
164
267
  // src/lib/lint/registry.ts
165
- var ALL_RULES = [collectionSchemaRule, llmImportRule];
268
+ var ALL_RULES = [collectionSchemaRule, hookVerifyRule, llmImportRule];
166
269
 
167
270
  // src/lib/lint/format.ts
168
271
  import { relative } from "path";
@@ -207,13 +310,14 @@ function serializeLintWarnings(warnings, projectRoot) {
207
310
  }
208
311
 
209
312
  // src/lib/lint/index.ts
210
- function runLint(ctx) {
313
+ async function runLint(ctx) {
211
314
  const out = [];
212
315
  for (const rule of ALL_RULES) {
213
316
  for (const t of ctx.targets) {
214
317
  if (!rule.appliesTo.includes(t.kind)) continue;
215
318
  try {
216
- out.push(...rule.check(t, ctx));
319
+ const result = await rule.check(t, ctx);
320
+ out.push(...result);
217
321
  } catch (err) {
218
322
  if (process.env.LUMERA_DEBUG) {
219
323
  console.error(`[lint] rule "${rule.id}" threw on ${t.filePath}:`, err);
@@ -247,12 +351,31 @@ function buildCollectionTargets(platformDir, _filterName) {
247
351
  }
248
352
  return targets;
249
353
  }
354
+ function buildHookTargets(platformDir, filterName) {
355
+ const hooksDir = join(platformDir, "hooks");
356
+ if (!existsSync(hooksDir)) return [];
357
+ const targets = [];
358
+ for (const entry of readdirSync(hooksDir, { withFileTypes: true })) {
359
+ if (!entry.isFile()) continue;
360
+ if (!entry.name.endsWith(".js") && !entry.name.endsWith(".ts")) continue;
361
+ const base = entry.name.replace(/\.(js|ts)$/, "");
362
+ if (filterName && base !== filterName) continue;
363
+ const filePath = join(hooksDir, entry.name);
364
+ targets.push({
365
+ kind: "hook",
366
+ name: base,
367
+ filePath,
368
+ source: readFileSync(filePath, "utf-8")
369
+ });
370
+ }
371
+ return targets;
372
+ }
250
373
 
251
374
  // src/commands/resources.ts
252
375
  init_auth();
253
- function safeLint(projectRoot, localAutomations) {
376
+ async function safeLint(projectRoot, localAutomations) {
254
377
  try {
255
- return runLint({ projectRoot, targets: buildAutomationTargets(localAutomations) });
378
+ return await runLint({ projectRoot, targets: buildAutomationTargets(localAutomations) });
256
379
  } catch (err) {
257
380
  if (process.env.LUMERA_DEBUG) console.error("[lint] pass failed:", err);
258
381
  return [];
@@ -265,13 +388,22 @@ function safePrintLint(warnings, projectRoot) {
265
388
  if (process.env.LUMERA_DEBUG) console.error("[lint] print failed:", err);
266
389
  }
267
390
  }
268
- function lintCollectionFiles(projectRoot, platformDir, filterName) {
269
- const issues = runLint({ projectRoot, targets: buildCollectionTargets(platformDir, filterName) });
391
+ async function lintCollectionFiles(projectRoot, platformDir, filterName) {
392
+ const issues = await runLint({ projectRoot, targets: buildCollectionTargets(platformDir, filterName) });
270
393
  const errors = issues.filter((issue) => issue.severity === "error");
271
394
  if (errors.length === 0) return;
272
395
  printLintWarnings(issues, projectRoot);
273
396
  throw new Error(`Found ${errors.length} collection lint error(s)`);
274
397
  }
398
+ async function lintHookFiles(api, projectRoot, platformDir, appName, filterName) {
399
+ const targets = buildHookTargets(platformDir, filterName);
400
+ if (targets.length === 0) return;
401
+ const issues = await runLint({ projectRoot, targets, api, appName });
402
+ const errors = issues.filter((issue) => issue.severity === "error");
403
+ if (errors.length === 0) return;
404
+ printLintWarnings(issues, projectRoot);
405
+ throw new Error(`Found ${errors.length} hook lint error(s)`);
406
+ }
275
407
  var PACKAGE_MANAGERS = ["bun", "pnpm", "yarn", "npm"];
276
408
  var PACKAGE_MANAGER_VALUE_FLAGS = /* @__PURE__ */ new Set(["--package-manager"]);
277
409
  function isPackageManager(value) {
@@ -970,47 +1102,6 @@ function loadLocalHooks(platformDir, filterName, appName) {
970
1102
  }
971
1103
  return hooks;
972
1104
  }
973
- function parseHookConfig(content) {
974
- const configMatch = content.match(/export\s+const\s+config\s*[=:]\s*(\{[\s\S]*?\});?/);
975
- if (!configMatch) return null;
976
- const externalId = configMatch[1].match(/external_id\s*:\s*['"]([^'"]+)['"]/)?.[1];
977
- const collection = configMatch[1].match(/collection\s*:\s*['"]([^'"]+)['"]/)?.[1];
978
- const trigger = configMatch[1].match(/trigger\s*:\s*['"]([^'"]+)['"]/)?.[1];
979
- const enabled = configMatch[1].match(/enabled\s*:\s*(true|false)/)?.[1];
980
- const name = configMatch[1].match(/name\s*:\s*['"]([^'"]+)['"]/)?.[1];
981
- if (!collection || !trigger) return null;
982
- const hook = {
983
- external_id: externalId || "",
984
- collection,
985
- trigger,
986
- enabled: enabled !== "false"
987
- };
988
- if (name) hook.name = name;
989
- const metadataMatch = configMatch[1].match(/metadata\s*:\s*(\{[^}]*\})/);
990
- if (metadataMatch) {
991
- try {
992
- const metaStr = metadataMatch[1].replace(/'/g, '"').replace(/(\w+)\s*:/g, '"$1":').replace(/,\s*}/g, "}");
993
- hook.metadata = JSON.parse(metaStr);
994
- } catch {
995
- }
996
- }
997
- return hook;
998
- }
999
- function extractHookScript(content) {
1000
- const handlerMatch = content.match(
1001
- /export\s+default\s+(?:async\s+)?function\s*(?:\w+)?\s*\([^)]*\)\s*\{([\s\S]*)\}[\s\n]*$/
1002
- );
1003
- if (handlerMatch) {
1004
- return handlerMatch[1].trim();
1005
- }
1006
- const simpleMatch = content.match(
1007
- /export\s+default\s+async\s+function[^{]*\{([\s\S]*)\}[\s\n]*$/
1008
- );
1009
- if (simpleMatch) {
1010
- return simpleMatch[1].trim();
1011
- }
1012
- return content.replace(/export\s+const\s+config[\s\S]*?;/, "").trim();
1013
- }
1014
1105
  function convertCollectionToApiFormat(local) {
1015
1106
  const schema = local.fields.map((field) => {
1016
1107
  const apiField = {
@@ -2686,7 +2777,10 @@ async function plan(args) {
2686
2777
  console.log(pc2.dim(" Comparing local files to remote state..."));
2687
2778
  console.log();
2688
2779
  if (!type || type === "collections") {
2689
- lintCollectionFiles(projectRoot, platformDir, name || void 0);
2780
+ await lintCollectionFiles(projectRoot, platformDir, name || void 0);
2781
+ }
2782
+ if (!type || type === "hooks") {
2783
+ await lintHookFiles(api, projectRoot, platformDir, appName, name || void 0);
2690
2784
  }
2691
2785
  await syncDeps(projectRoot, { ignorePermissionDenied: true });
2692
2786
  const allChanges = [];
@@ -2733,7 +2827,7 @@ async function plan(args) {
2733
2827
  allChanges.push(...changes);
2734
2828
  }
2735
2829
  }
2736
- const lintWarnings = safeLint(projectRoot, localAutomations);
2830
+ const lintWarnings = await safeLint(projectRoot, localAutomations);
2737
2831
  if (allChanges.length === 0) {
2738
2832
  if (jsonOutput) {
2739
2833
  console.log(JSON.stringify({ changes: [], warnings: [], lintWarnings: serializeLintWarnings(lintWarnings, projectRoot) }));
@@ -2828,7 +2922,10 @@ async function apply(args) {
2828
2922
  return;
2829
2923
  }
2830
2924
  if (!type || type === "collections") {
2831
- lintCollectionFiles(projectRoot, platformDir, name || void 0);
2925
+ await lintCollectionFiles(projectRoot, platformDir, name || void 0);
2926
+ }
2927
+ if (!type || type === "hooks") {
2928
+ await lintHookFiles(api, projectRoot, platformDir, appName, name || void 0);
2832
2929
  }
2833
2930
  await syncDeps(projectRoot, { ignorePermissionDenied: true });
2834
2931
  let collections;
@@ -2870,7 +2967,7 @@ async function apply(args) {
2870
2967
  console.log();
2871
2968
  console.log(pc2.green(" \u2713 Nothing to apply \u2014 local and remote are in sync."));
2872
2969
  console.log();
2873
- safePrintLint(safeLint(projectRoot, localAutomations), projectRoot);
2970
+ safePrintLint(await safeLint(projectRoot, localAutomations), projectRoot);
2874
2971
  return;
2875
2972
  }
2876
2973
  console.log();
@@ -2898,7 +2995,7 @@ async function apply(args) {
2898
2995
  console.log(pc2.dim(" No infrastructure changes \u2014 deploying app only."));
2899
2996
  console.log();
2900
2997
  }
2901
- safePrintLint(safeLint(projectRoot, localAutomations), projectRoot);
2998
+ safePrintLint(await safeLint(projectRoot, localAutomations), projectRoot);
2902
2999
  if (!autoConfirm && allChanges.length > 0) {
2903
3000
  const { confirm } = await prompts({
2904
3001
  type: "confirm",
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-2CR762KB.js";
4
4
  import {
5
5
  createApiClient
6
- } from "./chunk-SMZESQV2.js";
6
+ } from "./chunk-EDRAYUWN.js";
7
7
  import {
8
8
  findProjectRoot,
9
9
  getApiUrl,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lumerahq/cli",
3
- "version": "0.19.8-dev.1",
3
+ "version": "0.19.9-dev.0",
4
4
  "description": "CLI for building and deploying Lumera apps",
5
5
  "type": "module",
6
6
  "engines": {