@lumerahq/cli 0.35.4 → 0.36.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-UEW2JIEW.js → chunk-JA2Q4RJU.js} +1 -1
- package/dist/{chunk-2A2DSLIE.js → chunk-RV7WQAK3.js} +7 -0
- package/dist/{chunk-COPJBRRG.js → chunk-Z7GPNMK5.js} +1 -1
- package/dist/{deps-4BPFSUNT.js → deps-SIKW7IW2.js} +2 -2
- package/dist/{dev-CSDJ6UPM.js → dev-ZPSLZARR.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-4GAHJHBT.js → resources-PFFAWVGF.js} +94 -8
- 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-Z7GPNMK5.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-JA2Q4RJU.js";
|
|
4
4
|
import "./chunk-UGAIWMQV.js";
|
|
5
5
|
import "./chunk-CHUCWVFV.js";
|
|
6
6
|
import "./chunk-PIY3XXJI.js";
|
|
7
7
|
import "./chunk-KI57NBGA.js";
|
|
8
8
|
import {
|
|
9
9
|
syncDeps
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-Z7GPNMK5.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-PFFAWVGF.js").then((m) => m.plan(args.slice(1)));
|
|
247
247
|
break;
|
|
248
248
|
case "apply":
|
|
249
|
-
await import("./resources-
|
|
249
|
+
await import("./resources-PFFAWVGF.js").then((m) => m.apply(args.slice(1)));
|
|
250
250
|
break;
|
|
251
251
|
case "pull":
|
|
252
|
-
await import("./resources-
|
|
252
|
+
await import("./resources-PFFAWVGF.js").then((m) => m.pull(args.slice(1)));
|
|
253
253
|
break;
|
|
254
254
|
case "destroy":
|
|
255
|
-
await import("./resources-
|
|
255
|
+
await import("./resources-PFFAWVGF.js").then((m) => m.destroy(args.slice(1)));
|
|
256
256
|
break;
|
|
257
257
|
case "list":
|
|
258
|
-
await import("./resources-
|
|
258
|
+
await import("./resources-PFFAWVGF.js").then((m) => m.list(args.slice(1)));
|
|
259
259
|
break;
|
|
260
260
|
case "show":
|
|
261
|
-
await import("./resources-
|
|
261
|
+
await import("./resources-PFFAWVGF.js").then((m) => m.show(args.slice(1)));
|
|
262
262
|
break;
|
|
263
263
|
case "diff":
|
|
264
|
-
await import("./resources-
|
|
264
|
+
await import("./resources-PFFAWVGF.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-ZPSLZARR.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-C5SGYMZK.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-SIKW7IW2.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-JA2Q4RJU.js";
|
|
5
5
|
import "./chunk-UGAIWMQV.js";
|
|
6
6
|
import "./chunk-CHUCWVFV.js";
|
|
7
7
|
import "./chunk-PIY3XXJI.js";
|
|
8
8
|
import "./chunk-KI57NBGA.js";
|
|
9
9
|
import {
|
|
10
10
|
syncDeps
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-Z7GPNMK5.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,
|
|
@@ -474,16 +474,18 @@ var collectionSchemaRule = {
|
|
|
474
474
|
if (typeof field.type !== "string" || field.type.trim() === "") {
|
|
475
475
|
issues.push(error(target, `Field ${typeof field.name === "string" ? `"${field.name}"` : `at index ${i}`} is missing required string field "type".`));
|
|
476
476
|
}
|
|
477
|
-
|
|
477
|
+
const legacyFile = field.type === "lumera_file" || field.type === "file";
|
|
478
|
+
if (legacyFile || field.type === "lumera_file_v2" || field.type === "file_v2") {
|
|
478
479
|
const label = typeof field.name === "string" ? `"${field.name}"` : `at index ${i}`;
|
|
479
480
|
const ignored = [
|
|
480
481
|
...isRecord3(field.options) && field.options.maxSelect !== void 0 ? ['"options.maxSelect"'] : [],
|
|
481
482
|
...field.multiple !== void 0 ? ['"multiple"'] : []
|
|
482
483
|
];
|
|
483
484
|
if (ignored.length > 0) {
|
|
485
|
+
const fixed = legacyFile ? " A lumera_file field's maxSelect cannot change after it is first deployed." : "";
|
|
484
486
|
issues.push(warning(
|
|
485
487
|
target,
|
|
486
|
-
`File field ${label} sets ${ignored.join(" and ")}, which collection files ignore; it deploys with maxSelect ${field.maxSelect ?? 1}. Set top-level "maxSelect" instead
|
|
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}`
|
|
487
489
|
));
|
|
488
490
|
}
|
|
489
491
|
}
|
|
@@ -1373,6 +1375,7 @@ var FIELD_OPTION_KEYS_BY_TYPE = {
|
|
|
1373
1375
|
relation: ["cascadeDelete", "minSelect", "maxSelect"],
|
|
1374
1376
|
json: ["maxSize"],
|
|
1375
1377
|
lumera_file: ["maxSelect", "maxSize", "mimeTypes", "protected"],
|
|
1378
|
+
lumera_file_v2: ["maxSelect", "maxSize", "mimeTypes", "protected"],
|
|
1376
1379
|
editor: ["maxSize", "convertURLs"],
|
|
1377
1380
|
date: ["min", "max"],
|
|
1378
1381
|
select: ["values", "maxSelect"],
|
|
@@ -1384,6 +1387,7 @@ var FIELD_OPTION_DEFAULTS = {
|
|
|
1384
1387
|
relation: { cascadeDelete: false, minSelect: 0, maxSelect: 1 },
|
|
1385
1388
|
json: { maxSize: 0 },
|
|
1386
1389
|
lumera_file: { maxSelect: 1, maxSize: 0, mimeTypes: [], protected: false },
|
|
1390
|
+
lumera_file_v2: { maxSelect: 1, maxSize: 0, mimeTypes: [], protected: false },
|
|
1387
1391
|
editor: { maxSize: 0, convertURLs: false },
|
|
1388
1392
|
date: { min: "", max: "" },
|
|
1389
1393
|
select: { values: [], maxSelect: 1 },
|
|
@@ -2098,6 +2102,7 @@ function mapFieldType(type) {
|
|
|
2098
2102
|
select: "select",
|
|
2099
2103
|
relation: "relation",
|
|
2100
2104
|
file: "lumera_file",
|
|
2105
|
+
file_v2: "lumera_file_v2",
|
|
2101
2106
|
json: "json",
|
|
2102
2107
|
editor: "editor",
|
|
2103
2108
|
currency: "currency"
|
|
@@ -2149,6 +2154,72 @@ function lumeraFileMaxSelectErrors(local, remote) {
|
|
|
2149
2154
|
}
|
|
2150
2155
|
return errors;
|
|
2151
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
|
+
}
|
|
2152
2223
|
function localIndexesToSql(local, appName) {
|
|
2153
2224
|
if (!local.indexes || local.indexes.length === 0) return [];
|
|
2154
2225
|
return local.indexes.map((idx) => buildIndexSql(local, idx, appName)).sort();
|
|
@@ -2209,6 +2280,7 @@ async function planCollections(api, localCollections, appName, projectId) {
|
|
|
2209
2280
|
}
|
|
2210
2281
|
}
|
|
2211
2282
|
if (!remote) {
|
|
2283
|
+
const createWarnings = legacyFileFieldWarnings(local.fields);
|
|
2212
2284
|
changes.push({
|
|
2213
2285
|
type: "create",
|
|
2214
2286
|
resource: "collection",
|
|
@@ -2223,7 +2295,8 @@ async function planCollections(api, localCollections, appName, projectId) {
|
|
|
2223
2295
|
name: f.name,
|
|
2224
2296
|
type: f.type,
|
|
2225
2297
|
required: f.required
|
|
2226
|
-
}))
|
|
2298
|
+
})),
|
|
2299
|
+
...createWarnings.length > 0 ? { warnings: createWarnings } : {}
|
|
2227
2300
|
});
|
|
2228
2301
|
} else {
|
|
2229
2302
|
const localFieldNames = new Set(local.fields.map((f) => f.name));
|
|
@@ -2262,7 +2335,11 @@ async function planCollections(api, localCollections, appName, projectId) {
|
|
|
2262
2335
|
const f = remoteFieldMap.get(name);
|
|
2263
2336
|
fieldDetails.push({ action: "-", name: f.name, type: f.type, required: f.required });
|
|
2264
2337
|
}
|
|
2265
|
-
const errors = lumeraFileMaxSelectErrors(local, remote);
|
|
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
|
+
];
|
|
2266
2343
|
changes.push({
|
|
2267
2344
|
type: "update",
|
|
2268
2345
|
resource: "collection",
|
|
@@ -2270,7 +2347,8 @@ async function planCollections(api, localCollections, appName, projectId) {
|
|
|
2270
2347
|
name: local.name,
|
|
2271
2348
|
details: details.join(", "),
|
|
2272
2349
|
fieldDetails,
|
|
2273
|
-
...errors.length > 0 ? { errors } : {}
|
|
2350
|
+
...errors.length > 0 ? { errors } : {},
|
|
2351
|
+
...warnings.length > 0 ? { warnings } : {}
|
|
2274
2352
|
});
|
|
2275
2353
|
}
|
|
2276
2354
|
}
|
|
@@ -4570,6 +4648,7 @@ async function plan(args) {
|
|
|
4570
4648
|
warnings.push(`Collection "${change.name}" will have ${removals.length} field(s) removed.`);
|
|
4571
4649
|
}
|
|
4572
4650
|
}
|
|
4651
|
+
warnings.push(...change.warnings ?? []);
|
|
4573
4652
|
}
|
|
4574
4653
|
const validationErrors = planValidationErrors + collectionErrorCount;
|
|
4575
4654
|
console.log(JSON.stringify({ changes: allChanges, warnings, validationErrors, lintWarnings: serializeLintWarnings(lintWarnings, projectRoot) }));
|
|
@@ -4595,6 +4674,10 @@ async function plan(args) {
|
|
|
4595
4674
|
for (const message of change.errors) console.log(pc2.red(` \u2717 ${message}`));
|
|
4596
4675
|
console.log();
|
|
4597
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
|
+
}
|
|
4598
4681
|
if (change.textDiffs && change.textDiffs.length > 0) {
|
|
4599
4682
|
const maxDiffLines = 20;
|
|
4600
4683
|
for (const td of change.textDiffs) {
|
|
@@ -4769,6 +4852,7 @@ async function apply(args) {
|
|
|
4769
4852
|
const color = change.type === "create" ? pc2.green : pc2.yellow;
|
|
4770
4853
|
const details = change.details ? ` (${change.details})` : "";
|
|
4771
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}`));
|
|
4772
4856
|
}
|
|
4773
4857
|
if (willDeployApp) console.log(` ${pc2.blue("\u25CF")} app: frontend build + deploy`);
|
|
4774
4858
|
console.log();
|
|
@@ -5354,12 +5438,14 @@ export {
|
|
|
5354
5438
|
convertCollectionToApiFormat,
|
|
5355
5439
|
destroy,
|
|
5356
5440
|
diff,
|
|
5441
|
+
fieldTypeChangeErrors,
|
|
5357
5442
|
lintAgentFiles,
|
|
5358
5443
|
list,
|
|
5359
5444
|
loadLocalCollections,
|
|
5360
5445
|
loadLocalSkills,
|
|
5361
5446
|
localProjectSkillSlug,
|
|
5362
5447
|
lumeraFileMaxSelectErrors,
|
|
5448
|
+
lumeraFileV2TighteningWarnings,
|
|
5363
5449
|
plan,
|
|
5364
5450
|
planAgents,
|
|
5365
5451
|
planCollections,
|