@lumerahq/cli 0.35.1 → 0.36.0-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.
- package/dist/{chunk-5TQ3BQ2X.js → chunk-DXHA72QY.js} +1 -1
- package/dist/{chunk-A27HZEU6.js → chunk-MFAEFPHY.js} +1 -1
- package/dist/{chunk-2A2DSLIE.js → chunk-RV7WQAK3.js} +7 -0
- package/dist/{deps-DLJUDFNK.js → deps-OUTYSIZU.js} +2 -2
- package/dist/{dev-E2O6R7WW.js → dev-NEHWUIWB.js} +3 -3
- package/dist/index.js +12 -12
- package/dist/{init-MJEVA5HK.js → init-LZAXKILN.js} +1 -1
- package/dist/{register-RUDM4ITV.js → register-MOO266NL.js} +1 -1
- package/dist/{resources-X7K436TK.js → resources-IW5JT7A3.js} +164 -15
- package/dist/{run-BJ7PXSEH.js → run-TMJPC4PK.js} +1 -1
- package/package.json +1 -1
|
@@ -171,6 +171,13 @@ var ApiClient = class {
|
|
|
171
171
|
async getCollection(id) {
|
|
172
172
|
return this.request(`/api/pb/collections/${id}`);
|
|
173
173
|
}
|
|
174
|
+
/** Runs a read-only SQL query in the project's scope. */
|
|
175
|
+
async querySql(sql) {
|
|
176
|
+
return this.request("/api/pb/sql", {
|
|
177
|
+
method: "POST",
|
|
178
|
+
body: JSON.stringify({ sql })
|
|
179
|
+
});
|
|
180
|
+
}
|
|
174
181
|
async ensureCollection(name, def) {
|
|
175
182
|
return this.request(`/api/pb/collections/${name}`, {
|
|
176
183
|
method: "PUT",
|
|
@@ -2,9 +2,9 @@ import {
|
|
|
2
2
|
deps,
|
|
3
3
|
syncDeps,
|
|
4
4
|
syncResourceShareDeps
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-MFAEFPHY.js";
|
|
6
6
|
import "./chunk-2CR762KB.js";
|
|
7
|
-
import "./chunk-
|
|
7
|
+
import "./chunk-RV7WQAK3.js";
|
|
8
8
|
import "./chunk-JLVVHTBY.js";
|
|
9
9
|
import "./chunk-FJFIWC7G.js";
|
|
10
10
|
import "./chunk-PNKVD2UK.js";
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import {
|
|
2
2
|
dev
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-DXHA72QY.js";
|
|
4
4
|
import "./chunk-UGAIWMQV.js";
|
|
5
5
|
import "./chunk-2EHJH2QU.js";
|
|
6
6
|
import "./chunk-XZ5VCTGY.js";
|
|
7
7
|
import "./chunk-5FGDEZSJ.js";
|
|
8
8
|
import {
|
|
9
9
|
syncDeps
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-MFAEFPHY.js";
|
|
11
11
|
import {
|
|
12
12
|
loadEnv
|
|
13
13
|
} from "./chunk-2CR762KB.js";
|
|
14
14
|
import {
|
|
15
15
|
createApiClient
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-RV7WQAK3.js";
|
|
17
17
|
import {
|
|
18
18
|
findProjectRoot,
|
|
19
19
|
getApiUrl,
|
package/dist/index.js
CHANGED
|
@@ -243,32 +243,32 @@ async function main() {
|
|
|
243
243
|
switch (command) {
|
|
244
244
|
// Resource commands
|
|
245
245
|
case "plan":
|
|
246
|
-
await import("./resources-
|
|
246
|
+
await import("./resources-IW5JT7A3.js").then((m) => m.plan(args.slice(1)));
|
|
247
247
|
break;
|
|
248
248
|
case "apply":
|
|
249
|
-
await import("./resources-
|
|
249
|
+
await import("./resources-IW5JT7A3.js").then((m) => m.apply(args.slice(1)));
|
|
250
250
|
break;
|
|
251
251
|
case "pull":
|
|
252
|
-
await import("./resources-
|
|
252
|
+
await import("./resources-IW5JT7A3.js").then((m) => m.pull(args.slice(1)));
|
|
253
253
|
break;
|
|
254
254
|
case "destroy":
|
|
255
|
-
await import("./resources-
|
|
255
|
+
await import("./resources-IW5JT7A3.js").then((m) => m.destroy(args.slice(1)));
|
|
256
256
|
break;
|
|
257
257
|
case "list":
|
|
258
|
-
await import("./resources-
|
|
258
|
+
await import("./resources-IW5JT7A3.js").then((m) => m.list(args.slice(1)));
|
|
259
259
|
break;
|
|
260
260
|
case "show":
|
|
261
|
-
await import("./resources-
|
|
261
|
+
await import("./resources-IW5JT7A3.js").then((m) => m.show(args.slice(1)));
|
|
262
262
|
break;
|
|
263
263
|
case "diff":
|
|
264
|
-
await import("./resources-
|
|
264
|
+
await import("./resources-IW5JT7A3.js").then((m) => m.diff(args.slice(1)));
|
|
265
265
|
break;
|
|
266
266
|
// Development
|
|
267
267
|
case "dev":
|
|
268
|
-
await import("./dev-
|
|
268
|
+
await import("./dev-NEHWUIWB.js").then((m) => m.dev(args.slice(1)));
|
|
269
269
|
break;
|
|
270
270
|
case "run":
|
|
271
|
-
await import("./run-
|
|
271
|
+
await import("./run-TMJPC4PK.js").then((m) => m.run(args.slice(1)));
|
|
272
272
|
break;
|
|
273
273
|
case "functions":
|
|
274
274
|
await import("./functions-5OOHGQT7.js").then(
|
|
@@ -282,10 +282,10 @@ async function main() {
|
|
|
282
282
|
break;
|
|
283
283
|
// Project
|
|
284
284
|
case "init":
|
|
285
|
-
await import("./init-
|
|
285
|
+
await import("./init-LZAXKILN.js").then((m) => m.init(args.slice(1)));
|
|
286
286
|
break;
|
|
287
287
|
case "register":
|
|
288
|
-
await import("./register-
|
|
288
|
+
await import("./register-MOO266NL.js").then((m) => m.register(args.slice(1)));
|
|
289
289
|
break;
|
|
290
290
|
case "templates":
|
|
291
291
|
await import("./templates-LNUOTNLN.js").then((m) => m.templates(subcommand, args.slice(2)));
|
|
@@ -302,7 +302,7 @@ async function main() {
|
|
|
302
302
|
break;
|
|
303
303
|
// Dependencies
|
|
304
304
|
case "deps":
|
|
305
|
-
await import("./deps-
|
|
305
|
+
await import("./deps-OUTYSIZU.js").then((m) => m.deps(args.slice(1)));
|
|
306
306
|
break;
|
|
307
307
|
// Feature flags (read the in-sandbox snapshot)
|
|
308
308
|
case "flags":
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
2
|
deploy,
|
|
3
3
|
deployAppAPI
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-DXHA72QY.js";
|
|
5
5
|
import "./chunk-UGAIWMQV.js";
|
|
6
6
|
import "./chunk-2EHJH2QU.js";
|
|
7
7
|
import "./chunk-XZ5VCTGY.js";
|
|
8
8
|
import "./chunk-5FGDEZSJ.js";
|
|
9
9
|
import {
|
|
10
10
|
syncDeps
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-MFAEFPHY.js";
|
|
12
12
|
import {
|
|
13
13
|
loadEnv
|
|
14
14
|
} from "./chunk-2CR762KB.js";
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
createApiClient,
|
|
18
18
|
isApiErrorStatus,
|
|
19
19
|
validateCollectionNameLength
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-RV7WQAK3.js";
|
|
21
21
|
import {
|
|
22
22
|
findProjectRoot,
|
|
23
23
|
getApiUrl,
|
|
@@ -382,7 +382,7 @@ var automationMainGuardRule = {
|
|
|
382
382
|
if (entrypoint === null) return [];
|
|
383
383
|
const guard = findTopLevelMainGuard(target.source);
|
|
384
384
|
if (guard === null) return [];
|
|
385
|
-
const
|
|
385
|
+
const warning3 = {
|
|
386
386
|
ruleId: "automation-main-guard",
|
|
387
387
|
target,
|
|
388
388
|
severity: "warning",
|
|
@@ -390,7 +390,7 @@ var automationMainGuardRule = {
|
|
|
390
390
|
snippet: guard.snippet,
|
|
391
391
|
message: `Function automations must not contain a top-level __main__ guard. Lumera invokes "${entrypoint}" automatically; remove the guard.`
|
|
392
392
|
};
|
|
393
|
-
return [
|
|
393
|
+
return [warning3];
|
|
394
394
|
}
|
|
395
395
|
};
|
|
396
396
|
|
|
@@ -407,6 +407,14 @@ function error(target, message, snippet) {
|
|
|
407
407
|
snippet
|
|
408
408
|
};
|
|
409
409
|
}
|
|
410
|
+
function warning(target, message) {
|
|
411
|
+
return {
|
|
412
|
+
ruleId: "collection-schema",
|
|
413
|
+
target,
|
|
414
|
+
severity: "warning",
|
|
415
|
+
message
|
|
416
|
+
};
|
|
417
|
+
}
|
|
410
418
|
var collectionSchemaRule = {
|
|
411
419
|
id: "collection-schema",
|
|
412
420
|
description: "Validates local collection JSON files before planning or applying resources.",
|
|
@@ -466,6 +474,21 @@ var collectionSchemaRule = {
|
|
|
466
474
|
if (typeof field.type !== "string" || field.type.trim() === "") {
|
|
467
475
|
issues.push(error(target, `Field ${typeof field.name === "string" ? `"${field.name}"` : `at index ${i}`} is missing required string field "type".`));
|
|
468
476
|
}
|
|
477
|
+
const legacyFile = field.type === "lumera_file" || field.type === "file";
|
|
478
|
+
if (legacyFile || field.type === "lumera_file_v2" || field.type === "file_v2") {
|
|
479
|
+
const label = typeof field.name === "string" ? `"${field.name}"` : `at index ${i}`;
|
|
480
|
+
const ignored = [
|
|
481
|
+
...isRecord3(field.options) && field.options.maxSelect !== void 0 ? ['"options.maxSelect"'] : [],
|
|
482
|
+
...field.multiple !== void 0 ? ['"multiple"'] : []
|
|
483
|
+
];
|
|
484
|
+
if (ignored.length > 0) {
|
|
485
|
+
const fixed = legacyFile ? " A lumera_file field's maxSelect cannot change after it is first deployed; use lumera_file_v2 if it needs to." : "";
|
|
486
|
+
issues.push(warning(
|
|
487
|
+
target,
|
|
488
|
+
`File field ${label} sets ${ignored.join(" and ")}, which collection files ignore; it deploys with maxSelect ${field.maxSelect ?? 1}. Set top-level "maxSelect" instead.${fixed}`
|
|
489
|
+
));
|
|
490
|
+
}
|
|
491
|
+
}
|
|
469
492
|
}
|
|
470
493
|
}
|
|
471
494
|
if (audit !== void 0 && typeof audit !== "boolean") {
|
|
@@ -817,7 +840,7 @@ function error2(target, message, line) {
|
|
|
817
840
|
...line !== void 0 ? { line } : {}
|
|
818
841
|
};
|
|
819
842
|
}
|
|
820
|
-
function
|
|
843
|
+
function warning2(target, message, line) {
|
|
821
844
|
return {
|
|
822
845
|
ruleId: "hook-verify",
|
|
823
846
|
target,
|
|
@@ -897,7 +920,7 @@ var hookVerifyRule = {
|
|
|
897
920
|
if (errors.length > 0) return errors;
|
|
898
921
|
const advisories = [];
|
|
899
922
|
if (finding.legacyHandlerPattern) {
|
|
900
|
-
advisories.push(
|
|
923
|
+
advisories.push(warning2(target, LEGACY_HANDLER_MIGRATION_HINT));
|
|
901
924
|
}
|
|
902
925
|
let script = finding.scriptBody ?? "";
|
|
903
926
|
if (ctx.appName) {
|
|
@@ -1193,16 +1216,19 @@ async function safeLintPass(label, build) {
|
|
|
1193
1216
|
return null;
|
|
1194
1217
|
}
|
|
1195
1218
|
}
|
|
1196
|
-
async function lintCollectionFiles(projectRoot, platformDir, filterName) {
|
|
1219
|
+
async function lintCollectionFiles(projectRoot, platformDir, filterName, printWarnings = true) {
|
|
1197
1220
|
const result = await safeLintPass(
|
|
1198
1221
|
"collection",
|
|
1199
1222
|
async () => runLint({ projectRoot, targets: buildCollectionTargets(platformDir, filterName) })
|
|
1200
1223
|
);
|
|
1201
|
-
if (!result || result.issues.length === 0) return;
|
|
1202
|
-
|
|
1224
|
+
if (!result || result.issues.length === 0) return [];
|
|
1225
|
+
if (printWarnings || result.errors.length > 0) {
|
|
1226
|
+
safePrintLint(result.issues, projectRoot);
|
|
1227
|
+
}
|
|
1203
1228
|
if (result.errors.length > 0) {
|
|
1204
1229
|
throw new Error(`Found ${result.errors.length} collection lint error(s)`);
|
|
1205
1230
|
}
|
|
1231
|
+
return result.issues;
|
|
1206
1232
|
}
|
|
1207
1233
|
async function lintHookFiles(api, projectRoot, platformDir, appName, filterName) {
|
|
1208
1234
|
const result = await safeLintPass("hook", async () => {
|
|
@@ -1349,6 +1375,7 @@ var FIELD_OPTION_KEYS_BY_TYPE = {
|
|
|
1349
1375
|
relation: ["cascadeDelete", "minSelect", "maxSelect"],
|
|
1350
1376
|
json: ["maxSize"],
|
|
1351
1377
|
lumera_file: ["maxSelect", "maxSize", "mimeTypes", "protected"],
|
|
1378
|
+
lumera_file_v2: ["maxSelect", "maxSize", "mimeTypes", "protected"],
|
|
1352
1379
|
editor: ["maxSize", "convertURLs"],
|
|
1353
1380
|
date: ["min", "max"],
|
|
1354
1381
|
select: ["values", "maxSelect"],
|
|
@@ -1360,6 +1387,7 @@ var FIELD_OPTION_DEFAULTS = {
|
|
|
1360
1387
|
relation: { cascadeDelete: false, minSelect: 0, maxSelect: 1 },
|
|
1361
1388
|
json: { maxSize: 0 },
|
|
1362
1389
|
lumera_file: { maxSelect: 1, maxSize: 0, mimeTypes: [], protected: false },
|
|
1390
|
+
lumera_file_v2: { maxSelect: 1, maxSize: 0, mimeTypes: [], protected: false },
|
|
1363
1391
|
editor: { maxSize: 0, convertURLs: false },
|
|
1364
1392
|
date: { min: "", max: "" },
|
|
1365
1393
|
select: { values: [], maxSelect: 1 },
|
|
@@ -2074,6 +2102,7 @@ function mapFieldType(type) {
|
|
|
2074
2102
|
select: "select",
|
|
2075
2103
|
relation: "relation",
|
|
2076
2104
|
file: "lumera_file",
|
|
2105
|
+
file_v2: "lumera_file_v2",
|
|
2077
2106
|
json: "json",
|
|
2078
2107
|
editor: "editor",
|
|
2079
2108
|
currency: "currency"
|
|
@@ -2111,6 +2140,86 @@ function fieldsDiffer(local, remote) {
|
|
|
2111
2140
|
}
|
|
2112
2141
|
return false;
|
|
2113
2142
|
}
|
|
2143
|
+
function lumeraFileMaxSelectErrors(local, remote) {
|
|
2144
|
+
const remoteFields = new Map(remote.schema.map((f) => [f.name, f]));
|
|
2145
|
+
const errors = [];
|
|
2146
|
+
for (const field of local.fields) {
|
|
2147
|
+
const deployed = remoteFields.get(field.name);
|
|
2148
|
+
if (mapFieldType(field.type) !== "lumera_file" || deployed?.type !== "lumera_file") continue;
|
|
2149
|
+
const deployedMaxSelect = Math.max(Number(normalizeFieldOption("lumera_file", "maxSelect", deployed.options?.maxSelect)), 1);
|
|
2150
|
+
const requestedMaxSelect = Math.max(Number(normalizeFieldOption("lumera_file", "maxSelect", field.maxSelect)), 1);
|
|
2151
|
+
if (deployedMaxSelect === requestedMaxSelect) continue;
|
|
2152
|
+
const requested = field.maxSelect === void 0 ? "omitting maxSelect (defaults to 1)" : `maxSelect ${requestedMaxSelect}`;
|
|
2153
|
+
errors.push(`File field "${field.name}" is deployed with maxSelect ${deployedMaxSelect}; ${requested} would change it, which is not supported. Keep maxSelect ${deployedMaxSelect}. To change the limit, move the field to lumera_file_v2.`);
|
|
2154
|
+
}
|
|
2155
|
+
return errors;
|
|
2156
|
+
}
|
|
2157
|
+
function fieldTypeChangeErrors(local, remote) {
|
|
2158
|
+
const remoteFields = new Map(remote.schema.map((f) => [f.name, f]));
|
|
2159
|
+
const errors = [];
|
|
2160
|
+
for (const field of local.fields) {
|
|
2161
|
+
const deployed = remoteFields.get(field.name);
|
|
2162
|
+
const requestedType = mapFieldType(field.type);
|
|
2163
|
+
if (!deployed || deployed.type === requestedType) continue;
|
|
2164
|
+
errors.push(`Field "${field.name}" is deployed as ${deployed.type}; changing it to ${requestedType} would drop its data, which is not supported. Add a field with a new name instead.`);
|
|
2165
|
+
}
|
|
2166
|
+
return errors;
|
|
2167
|
+
}
|
|
2168
|
+
function legacyFileFieldWarnings(fields) {
|
|
2169
|
+
return fields.filter((field) => mapFieldType(field.type) === "lumera_file").map((field) => `File field "${field.name}" uses lumera_file; use lumera_file_v2 for new file fields so maxSelect can change later.`);
|
|
2170
|
+
}
|
|
2171
|
+
function quoteSqlIdentifier(name) {
|
|
2172
|
+
return `"${name.replaceAll('"', '""')}"`;
|
|
2173
|
+
}
|
|
2174
|
+
async function countRecords(api, sql) {
|
|
2175
|
+
try {
|
|
2176
|
+
const total = Number((await api.querySql(sql)).rows?.[0]?.total);
|
|
2177
|
+
return Number.isFinite(total) ? total : void 0;
|
|
2178
|
+
} catch {
|
|
2179
|
+
return void 0;
|
|
2180
|
+
}
|
|
2181
|
+
}
|
|
2182
|
+
function readOnlyRecords(count, condition, fix) {
|
|
2183
|
+
if (count === void 0) return `Records ${condition} become read-only until ${fix}.`;
|
|
2184
|
+
return `${count} record${count === 1 ? "" : "s"} ${condition} ${count === 1 ? "becomes" : "become"} read-only until ${fix}.`;
|
|
2185
|
+
}
|
|
2186
|
+
async function lumeraFileV2TighteningWarnings(api, local, remote) {
|
|
2187
|
+
const remoteFields = new Map(remote.schema.map((f) => [f.name, f]));
|
|
2188
|
+
const warnings = [];
|
|
2189
|
+
for (const field of local.fields) {
|
|
2190
|
+
const deployed = remoteFields.get(field.name);
|
|
2191
|
+
if (mapFieldType(field.type) !== "lumera_file_v2" || deployed?.type !== "lumera_file_v2") continue;
|
|
2192
|
+
const option = (key, value) => normalizeFieldOption("lumera_file_v2", key, value);
|
|
2193
|
+
const label = `${local.name}.${field.name}`;
|
|
2194
|
+
const from = `FROM ${quoteSqlIdentifier(remote.name)} WHERE`;
|
|
2195
|
+
const column = quoteSqlIdentifier(field.name);
|
|
2196
|
+
const deployedMaxSelect = Math.max(Number(option("maxSelect", deployed.options?.maxSelect)), 1);
|
|
2197
|
+
const requestedMaxSelect = Math.max(Number(option("maxSelect", field.maxSelect)), 1);
|
|
2198
|
+
if (requestedMaxSelect < deployedMaxSelect) {
|
|
2199
|
+
const count = await countRecords(api, `SELECT count(*) AS total ${from} jsonb_array_length(${column}) > ${requestedMaxSelect}`);
|
|
2200
|
+
if (count !== 0) {
|
|
2201
|
+
warnings.push(`${label}: maxSelect ${deployedMaxSelect} \u2192 ${requestedMaxSelect}. ${readOnlyRecords(count, `with more than ${requestedMaxSelect} file${requestedMaxSelect === 1 ? "" : "s"}`, "trimmed or deleted")}`);
|
|
2202
|
+
}
|
|
2203
|
+
}
|
|
2204
|
+
if (field.required && !deployed.required) {
|
|
2205
|
+
const count = await countRecords(api, `SELECT count(*) AS total ${from} jsonb_array_length(${column}) = 0`);
|
|
2206
|
+
if (count !== 0) {
|
|
2207
|
+
warnings.push(`${label}: now required. ${readOnlyRecords(count, "with no files", "a file is added or they are deleted")}`);
|
|
2208
|
+
}
|
|
2209
|
+
}
|
|
2210
|
+
const deployedMaxSize = Number(option("maxSize", deployed.options?.maxSize));
|
|
2211
|
+
const requestedMaxSize = Number(option("maxSize", field.maxSize));
|
|
2212
|
+
if (requestedMaxSize > 0 && (deployedMaxSize === 0 || requestedMaxSize < deployedMaxSize)) {
|
|
2213
|
+
warnings.push(`${label}: maxSize lowered to ${requestedMaxSize} bytes. ${readOnlyRecords(void 0, "with larger files", "fixed or deleted")}`);
|
|
2214
|
+
}
|
|
2215
|
+
const deployedTypes = option("mimeTypes", deployed.options?.mimeTypes);
|
|
2216
|
+
const requestedTypes = option("mimeTypes", field.mimeTypes);
|
|
2217
|
+
if (requestedTypes.length > 0 && (deployedTypes.length === 0 || deployedTypes.some((type) => !requestedTypes.includes(type)))) {
|
|
2218
|
+
warnings.push(`${label}: mimeTypes narrowed. ${readOnlyRecords(void 0, "with other file types", "fixed or deleted")}`);
|
|
2219
|
+
}
|
|
2220
|
+
}
|
|
2221
|
+
return warnings;
|
|
2222
|
+
}
|
|
2114
2223
|
function localIndexesToSql(local, appName) {
|
|
2115
2224
|
if (!local.indexes || local.indexes.length === 0) return [];
|
|
2116
2225
|
return local.indexes.map((idx) => buildIndexSql(local, idx, appName)).sort();
|
|
@@ -2171,6 +2280,7 @@ async function planCollections(api, localCollections, appName, projectId) {
|
|
|
2171
2280
|
}
|
|
2172
2281
|
}
|
|
2173
2282
|
if (!remote) {
|
|
2283
|
+
const createWarnings = legacyFileFieldWarnings(local.fields);
|
|
2174
2284
|
changes.push({
|
|
2175
2285
|
type: "create",
|
|
2176
2286
|
resource: "collection",
|
|
@@ -2185,7 +2295,8 @@ async function planCollections(api, localCollections, appName, projectId) {
|
|
|
2185
2295
|
name: f.name,
|
|
2186
2296
|
type: f.type,
|
|
2187
2297
|
required: f.required
|
|
2188
|
-
}))
|
|
2298
|
+
})),
|
|
2299
|
+
...createWarnings.length > 0 ? { warnings: createWarnings } : {}
|
|
2189
2300
|
});
|
|
2190
2301
|
} else {
|
|
2191
2302
|
const localFieldNames = new Set(local.fields.map((f) => f.name));
|
|
@@ -2224,13 +2335,20 @@ async function planCollections(api, localCollections, appName, projectId) {
|
|
|
2224
2335
|
const f = remoteFieldMap.get(name);
|
|
2225
2336
|
fieldDetails.push({ action: "-", name: f.name, type: f.type, required: f.required });
|
|
2226
2337
|
}
|
|
2338
|
+
const errors = [...fieldTypeChangeErrors(local, remote), ...lumeraFileMaxSelectErrors(local, remote)];
|
|
2339
|
+
const warnings = [
|
|
2340
|
+
...legacyFileFieldWarnings(added.map((name) => localFieldMap.get(name))),
|
|
2341
|
+
...await lumeraFileV2TighteningWarnings(api, local, remote)
|
|
2342
|
+
];
|
|
2227
2343
|
changes.push({
|
|
2228
2344
|
type: "update",
|
|
2229
2345
|
resource: "collection",
|
|
2230
2346
|
id: local.id,
|
|
2231
2347
|
name: local.name,
|
|
2232
2348
|
details: details.join(", "),
|
|
2233
|
-
fieldDetails
|
|
2349
|
+
fieldDetails,
|
|
2350
|
+
...errors.length > 0 ? { errors } : {},
|
|
2351
|
+
...warnings.length > 0 ? { warnings } : {}
|
|
2234
2352
|
});
|
|
2235
2353
|
}
|
|
2236
2354
|
}
|
|
@@ -4422,8 +4540,9 @@ async function plan(args) {
|
|
|
4422
4540
|
console.log(pc2.cyan(pc2.bold(" Plan")));
|
|
4423
4541
|
console.log(pc2.dim(" Comparing local files to remote state..."));
|
|
4424
4542
|
console.log();
|
|
4543
|
+
let collectionLintWarnings = [];
|
|
4425
4544
|
if (!type || type === "collections") {
|
|
4426
|
-
await lintCollectionFiles(projectRoot, platformDir, name || void 0);
|
|
4545
|
+
collectionLintWarnings = await lintCollectionFiles(projectRoot, platformDir, name || void 0, false);
|
|
4427
4546
|
}
|
|
4428
4547
|
if (!type || type === "hooks") {
|
|
4429
4548
|
await lintHookFiles(api, projectRoot, platformDir, appName, name || void 0);
|
|
@@ -4498,6 +4617,7 @@ async function plan(args) {
|
|
|
4498
4617
|
}
|
|
4499
4618
|
}
|
|
4500
4619
|
const lintWarnings = [
|
|
4620
|
+
...collectionLintWarnings,
|
|
4501
4621
|
...agentLintWarnings,
|
|
4502
4622
|
...await safeLint(projectRoot, localAutomations)
|
|
4503
4623
|
];
|
|
@@ -4518,6 +4638,7 @@ async function plan(args) {
|
|
|
4518
4638
|
safePrintLint(lintWarnings, projectRoot);
|
|
4519
4639
|
return;
|
|
4520
4640
|
}
|
|
4641
|
+
const collectionErrorCount = allChanges.reduce((count, change) => count + (change.errors?.length ?? 0), 0);
|
|
4521
4642
|
if (jsonOutput) {
|
|
4522
4643
|
const warnings = [];
|
|
4523
4644
|
for (const change of allChanges) {
|
|
@@ -4527,9 +4648,11 @@ async function plan(args) {
|
|
|
4527
4648
|
warnings.push(`Collection "${change.name}" will have ${removals.length} field(s) removed.`);
|
|
4528
4649
|
}
|
|
4529
4650
|
}
|
|
4651
|
+
warnings.push(...change.warnings ?? []);
|
|
4530
4652
|
}
|
|
4531
|
-
|
|
4532
|
-
|
|
4653
|
+
const validationErrors = planValidationErrors + collectionErrorCount;
|
|
4654
|
+
console.log(JSON.stringify({ changes: allChanges, warnings, validationErrors, lintWarnings: serializeLintWarnings(lintWarnings, projectRoot) }));
|
|
4655
|
+
if (validationErrors > 0) process.exitCode = 1;
|
|
4533
4656
|
return;
|
|
4534
4657
|
}
|
|
4535
4658
|
console.log(pc2.bold(" Changes:"));
|
|
@@ -4547,6 +4670,14 @@ async function plan(args) {
|
|
|
4547
4670
|
}
|
|
4548
4671
|
console.log();
|
|
4549
4672
|
}
|
|
4673
|
+
if (change.errors && change.errors.length > 0) {
|
|
4674
|
+
for (const message of change.errors) console.log(pc2.red(` \u2717 ${message}`));
|
|
4675
|
+
console.log();
|
|
4676
|
+
}
|
|
4677
|
+
if (change.warnings && change.warnings.length > 0) {
|
|
4678
|
+
for (const message of change.warnings) console.log(pc2.yellow(` \u26A0 ${message}`));
|
|
4679
|
+
console.log();
|
|
4680
|
+
}
|
|
4550
4681
|
if (change.textDiffs && change.textDiffs.length > 0) {
|
|
4551
4682
|
const maxDiffLines = 20;
|
|
4552
4683
|
for (const td of change.textDiffs) {
|
|
@@ -4572,6 +4703,11 @@ async function plan(args) {
|
|
|
4572
4703
|
safePrintLint(lintWarnings, projectRoot);
|
|
4573
4704
|
if (planValidationErrors > 0) {
|
|
4574
4705
|
console.log(pc2.red(` \u2717 Fix ${planValidationErrors} agent skill validation error${planValidationErrors === 1 ? "" : "s"} before applying.`));
|
|
4706
|
+
}
|
|
4707
|
+
if (collectionErrorCount > 0) {
|
|
4708
|
+
console.log(pc2.red(` \u2717 Fix ${collectionErrorCount} collection error${collectionErrorCount === 1 ? "" : "s"} before applying.`));
|
|
4709
|
+
}
|
|
4710
|
+
if (planValidationErrors > 0 || collectionErrorCount > 0) {
|
|
4575
4711
|
process.exitCode = 1;
|
|
4576
4712
|
} else {
|
|
4577
4713
|
console.log(pc2.dim(` Run 'lumera apply' to apply these changes.`));
|
|
@@ -4679,6 +4815,15 @@ async function apply(args) {
|
|
|
4679
4815
|
console.log();
|
|
4680
4816
|
process.exit(1);
|
|
4681
4817
|
}
|
|
4818
|
+
const collectionErrors = allChanges.flatMap((change) => (change.errors ?? []).map((message) => `${change.resource} ${change.name}: ${message}`));
|
|
4819
|
+
if (collectionErrors.length > 0) {
|
|
4820
|
+
console.log();
|
|
4821
|
+
for (const message of collectionErrors) console.log(pc2.red(` \u2717 ${message}`));
|
|
4822
|
+
console.log();
|
|
4823
|
+
console.log(pc2.red(` \u2717 Apply aborted: fix ${collectionErrors.length} collection error${collectionErrors.length === 1 ? "" : "s"} first. Nothing was applied.`));
|
|
4824
|
+
console.log();
|
|
4825
|
+
process.exit(1);
|
|
4826
|
+
}
|
|
4682
4827
|
let willDeployApp = false;
|
|
4683
4828
|
if (!type && !skipApp) {
|
|
4684
4829
|
try {
|
|
@@ -4707,6 +4852,7 @@ async function apply(args) {
|
|
|
4707
4852
|
const color = change.type === "create" ? pc2.green : pc2.yellow;
|
|
4708
4853
|
const details = change.details ? ` (${change.details})` : "";
|
|
4709
4854
|
console.log(` ${color(icon)} ${change.resource}: ${change.name}${pc2.dim(details)}`);
|
|
4855
|
+
for (const message of change.warnings ?? []) console.log(pc2.yellow(` \u26A0 ${message}`));
|
|
4710
4856
|
}
|
|
4711
4857
|
if (willDeployApp) console.log(` ${pc2.blue("\u25CF")} app: frontend build + deploy`);
|
|
4712
4858
|
console.log();
|
|
@@ -5292,11 +5438,14 @@ export {
|
|
|
5292
5438
|
convertCollectionToApiFormat,
|
|
5293
5439
|
destroy,
|
|
5294
5440
|
diff,
|
|
5441
|
+
fieldTypeChangeErrors,
|
|
5295
5442
|
lintAgentFiles,
|
|
5296
5443
|
list,
|
|
5297
5444
|
loadLocalCollections,
|
|
5298
5445
|
loadLocalSkills,
|
|
5299
5446
|
localProjectSkillSlug,
|
|
5447
|
+
lumeraFileMaxSelectErrors,
|
|
5448
|
+
lumeraFileV2TighteningWarnings,
|
|
5300
5449
|
plan,
|
|
5301
5450
|
planAgents,
|
|
5302
5451
|
planCollections,
|