@stage5/lumine 0.2.37 → 0.2.39
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/README.md +9 -0
- package/lib/api.js +10 -0
- package/lib/commands.js +101 -12
- package/lib/constants.js +8 -7
- package/lib/workspace.js +35 -21
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,6 +11,7 @@ npx @stage5/lumine@latest rename "My New Build Title"
|
|
|
11
11
|
npx @stage5/lumine@latest rename "New Title" --target 123
|
|
12
12
|
npx @stage5/lumine@latest describe "A welcoming place to build together"
|
|
13
13
|
npx @stage5/lumine@latest describe --no-description --target 123
|
|
14
|
+
npx @stage5/lumine@latest upgrade https://www.twin-kle.com/app/123
|
|
14
15
|
npx @stage5/lumine@latest projects
|
|
15
16
|
npx @stage5/lumine@latest branches 884
|
|
16
17
|
npx @stage5/lumine@latest suggestions 884
|
|
@@ -40,6 +41,14 @@ website. Both commands target the current workspace or selected Build; pass
|
|
|
40
41
|
`--target <build-url-or-id>` to update another Build you own. Run
|
|
41
42
|
`lumine describe --no-description` to clear a description explicitly.
|
|
42
43
|
|
|
44
|
+
The configured project-limit reviewer can run
|
|
45
|
+
`lumine upgrade <build-url-or-id>` to grant Main—and therefore every branch—the
|
|
46
|
+
full approved project room of 500 files and 5 MB. The server verifies reviewer
|
|
47
|
+
authority, resolves branch URLs back to their canonical Main project, and
|
|
48
|
+
returns the confirmed canonical limits after commit. Running the command again
|
|
49
|
+
is a safe no-op that reports the already-active canonical limits. Omit the
|
|
50
|
+
target to use the current workspace or selected project.
|
|
51
|
+
|
|
43
52
|
For team projects, Lumine mirrors the website workspace flow: choosing or
|
|
44
53
|
pulling the owner's main project creates or reuses your contribution branch and
|
|
45
54
|
checks out that branch locally. Saves go to your branch, so the project owner
|
package/lib/api.js
CHANGED
|
@@ -147,6 +147,16 @@ export async function createBuild({ options, auth, title, description }) {
|
|
|
147
147
|
});
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
+
export async function upgradeBuildProjectLimits({ options, auth, buildId }) {
|
|
151
|
+
return await requestJson({
|
|
152
|
+
method: "PUT",
|
|
153
|
+
url: `${options.apiUrl}/build/${buildId}/project-limit-upgrade`,
|
|
154
|
+
authToken: auth.token,
|
|
155
|
+
body: {},
|
|
156
|
+
timeoutMs: options.timeoutMs,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
150
160
|
export async function saveProjectFiles({
|
|
151
161
|
options,
|
|
152
162
|
auth,
|
package/lib/commands.js
CHANGED
|
@@ -46,6 +46,7 @@ import {
|
|
|
46
46
|
saveProjectFiles,
|
|
47
47
|
suggestBuildThumbnailToOwner,
|
|
48
48
|
updateBuildMetadata,
|
|
49
|
+
upgradeBuildProjectLimits,
|
|
49
50
|
} from "./api.js";
|
|
50
51
|
import { assetsCommand, confirmPrompt, writeAssetsManifest } from "./assets.js";
|
|
51
52
|
import { thumbnailCommand } from "./thumbnail.js";
|
|
@@ -67,6 +68,7 @@ import {
|
|
|
67
68
|
defaultReferenceDir,
|
|
68
69
|
defaultVersionCheckoutDir,
|
|
69
70
|
defaultWorkspaceDir,
|
|
71
|
+
formatBytes,
|
|
70
72
|
parseBoolean,
|
|
71
73
|
resolveBuildId,
|
|
72
74
|
resolveBuildReference,
|
|
@@ -142,6 +144,10 @@ export async function main() {
|
|
|
142
144
|
await describeBuild(options);
|
|
143
145
|
return;
|
|
144
146
|
}
|
|
147
|
+
if (options.command === "upgrade") {
|
|
148
|
+
await upgradeProject(options);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
145
151
|
if (options.command === "projects") {
|
|
146
152
|
await projects(options);
|
|
147
153
|
return;
|
|
@@ -285,6 +291,61 @@ export async function describeBuild(options) {
|
|
|
285
291
|
);
|
|
286
292
|
}
|
|
287
293
|
|
|
294
|
+
export async function upgradeProject(options) {
|
|
295
|
+
if (options.positional?.length > 1) {
|
|
296
|
+
throw new Error("Usage: lumine upgrade [twinkle-build-url-or-id]");
|
|
297
|
+
}
|
|
298
|
+
const explicitTarget = String(options.target || "").trim();
|
|
299
|
+
const explicitBuildId = explicitTarget
|
|
300
|
+
? resolveRequiredBuildId(explicitTarget)
|
|
301
|
+
: 0;
|
|
302
|
+
if (
|
|
303
|
+
explicitTarget &&
|
|
304
|
+
(!Number.isSafeInteger(explicitBuildId) || explicitBuildId <= 0)
|
|
305
|
+
) {
|
|
306
|
+
throw new Error("Pass a Twinkle build URL or positive integer build id.");
|
|
307
|
+
}
|
|
308
|
+
const auth = await ensureAuth(options);
|
|
309
|
+
await assertAuthScope({ options, auth, scope: "build:write" });
|
|
310
|
+
// A branch URL already contains canonical Main's id. Let the server resolve
|
|
311
|
+
// numeric branch build ids, but do not require private-project read access
|
|
312
|
+
// merely to translate a reviewer-provided branch URL.
|
|
313
|
+
const buildId = explicitTarget
|
|
314
|
+
? explicitBuildId
|
|
315
|
+
: await resolveRequiredBuildIdOrSelected(options, auth);
|
|
316
|
+
if (!Number.isSafeInteger(buildId) || buildId <= 0) {
|
|
317
|
+
throw new Error("Pass a Twinkle build URL or positive integer build id.");
|
|
318
|
+
}
|
|
319
|
+
const result = await upgradeBuildProjectLimits({
|
|
320
|
+
options,
|
|
321
|
+
auth,
|
|
322
|
+
buildId,
|
|
323
|
+
});
|
|
324
|
+
const upgradedBuild = result?.build;
|
|
325
|
+
const canonicalBuildId = Number(upgradedBuild?.id || 0);
|
|
326
|
+
const maxFilesPerProject = Number(
|
|
327
|
+
upgradedBuild?.projectLimits?.maxFilesPerProject || 0,
|
|
328
|
+
);
|
|
329
|
+
const maxProjectBytes = Number(
|
|
330
|
+
upgradedBuild?.projectLimits?.maxProjectBytes || 0,
|
|
331
|
+
);
|
|
332
|
+
if (
|
|
333
|
+
result?.success !== true ||
|
|
334
|
+
canonicalBuildId <= 0 ||
|
|
335
|
+
maxFilesPerProject <= 0 ||
|
|
336
|
+
maxProjectBytes <= 0
|
|
337
|
+
) {
|
|
338
|
+
throw new Error("Twinkle did not return the upgraded project limits.");
|
|
339
|
+
}
|
|
340
|
+
const buildLabel = formatBuildTitle(upgradedBuild);
|
|
341
|
+
const limitsLabel = `${maxFilesPerProject} project files and ${formatBytes(maxProjectBytes)}`;
|
|
342
|
+
console.log(
|
|
343
|
+
result.changed
|
|
344
|
+
? `Upgraded ${buildLabel} to ${limitsLabel}.`
|
|
345
|
+
: `${buildLabel} already has ${limitsLabel}.`,
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
|
|
288
349
|
async function updateOwnedBuildDetails({
|
|
289
350
|
options,
|
|
290
351
|
patch,
|
|
@@ -908,7 +969,7 @@ export async function save(options) {
|
|
|
908
969
|
buildId = Number(build?.id || buildId);
|
|
909
970
|
const dir = resolveProjectDirForSave({ options, localProject });
|
|
910
971
|
const files = await collectProjectFiles(dir);
|
|
911
|
-
assertProjectFilesWithinLimits(files);
|
|
972
|
+
assertProjectFilesWithinLimits(files, build?.projectLimits);
|
|
912
973
|
// Only claim a base snapshot when this workspace was pulled from the same
|
|
913
974
|
// build we are saving to. Missing filesHash used to mean "unguarded save",
|
|
914
975
|
// which let stale checkouts silently rewind newer server versions — refuse
|
|
@@ -975,12 +1036,25 @@ export async function save(options) {
|
|
|
975
1036
|
}
|
|
976
1037
|
throw error;
|
|
977
1038
|
}
|
|
978
|
-
build = result.build
|
|
979
|
-
|
|
980
|
-
|
|
1039
|
+
build = result.build ? { ...build, ...result.build } : build;
|
|
1040
|
+
if (result.copilotPolicy?.limits) {
|
|
1041
|
+
build = {
|
|
1042
|
+
...build,
|
|
1043
|
+
projectLimits: {
|
|
1044
|
+
maxProjectBytes: result.copilotPolicy.limits.maxProjectBytes,
|
|
1045
|
+
maxFilesPerProject: result.copilotPolicy.limits.maxFilesPerProject,
|
|
1046
|
+
maxFileLines: result.copilotPolicy.limits.maxFileLines,
|
|
1047
|
+
},
|
|
1048
|
+
};
|
|
1049
|
+
}
|
|
1050
|
+
if (!build) {
|
|
1051
|
+
build = (await loadBuildMetadata({ options, auth, buildId }).catch(
|
|
1052
|
+
() => null,
|
|
1053
|
+
)) || {
|
|
981
1054
|
id: buildId,
|
|
982
1055
|
title: `Build ${buildId}`,
|
|
983
1056
|
};
|
|
1057
|
+
}
|
|
984
1058
|
await saveSelectedBuild({ options, auth, build });
|
|
985
1059
|
await writeProjectMetadata({
|
|
986
1060
|
dir,
|
|
@@ -1010,9 +1084,10 @@ export async function save(options) {
|
|
|
1010
1084
|
}
|
|
1011
1085
|
|
|
1012
1086
|
export async function check(options) {
|
|
1013
|
-
await reportLocalProjectFindings(options);
|
|
1014
1087
|
const auth = await resolveAuth(options);
|
|
1015
1088
|
const buildId = await resolveRequiredBuildIdOrSelected(options, auth);
|
|
1089
|
+
const canonicalBuild = await loadBuildMetadata({ options, auth, buildId });
|
|
1090
|
+
await reportLocalProjectFindings(options, canonicalBuild?.projectLimits);
|
|
1016
1091
|
const result = await requestJson({
|
|
1017
1092
|
url: `${options.apiUrl}/cli/build/${buildId}/launch-check`,
|
|
1018
1093
|
authToken: auth.token,
|
|
@@ -1022,9 +1097,13 @@ export async function check(options) {
|
|
|
1022
1097
|
if (!result.ok) process.exitCode = 1;
|
|
1023
1098
|
}
|
|
1024
1099
|
|
|
1025
|
-
// Local
|
|
1026
|
-
//
|
|
1027
|
-
|
|
1100
|
+
// Local half of `lumine check`: validate the workspace against the canonical
|
|
1101
|
+
// project limits fetched by check(), so a newly approved allowance works
|
|
1102
|
+
// immediately without requiring another pull just to refresh local metadata.
|
|
1103
|
+
export async function reportLocalProjectFindings(
|
|
1104
|
+
options,
|
|
1105
|
+
canonicalProjectLimits,
|
|
1106
|
+
) {
|
|
1028
1107
|
const localProject = await findLocalProjectMetadata(
|
|
1029
1108
|
path.resolve(options.dir || process.cwd()),
|
|
1030
1109
|
);
|
|
@@ -1037,7 +1116,10 @@ export async function reportLocalProjectFindings(options) {
|
|
|
1037
1116
|
process.exitCode = 1;
|
|
1038
1117
|
return;
|
|
1039
1118
|
}
|
|
1040
|
-
const { errors, warnings } = collectProjectLimitFindings(
|
|
1119
|
+
const { errors, warnings } = collectProjectLimitFindings(
|
|
1120
|
+
files,
|
|
1121
|
+
canonicalProjectLimits || localProject?.metadata?.build?.projectLimits,
|
|
1122
|
+
);
|
|
1041
1123
|
for (const warning of warnings) {
|
|
1042
1124
|
console.error(`Local check warning: ${warning}`);
|
|
1043
1125
|
}
|
|
@@ -1171,10 +1253,14 @@ export async function resolveBuildForSave({
|
|
|
1171
1253
|
const localBuild = localProject?.metadata?.build;
|
|
1172
1254
|
const localBuildId =
|
|
1173
1255
|
Number(localBuild?.id || 0) || Number(localProject?.metadata?.buildId || 0);
|
|
1256
|
+
// Save is already an online operation. Refresh the server-owned Build
|
|
1257
|
+
// metadata before enforcing permissions and project limits so a newly
|
|
1258
|
+
// approved Main allowance (inherited by branches) works without re-pulling.
|
|
1259
|
+
const canonicalBuild = await loadBuildMetadata({ options, auth, buildId });
|
|
1174
1260
|
const build =
|
|
1175
1261
|
localBuild && localBuildId === Number(buildId)
|
|
1176
|
-
? { ...localBuild, id: buildId }
|
|
1177
|
-
:
|
|
1262
|
+
? { ...localBuild, ...canonicalBuild, id: buildId }
|
|
1263
|
+
: canonicalBuild;
|
|
1178
1264
|
return await resolveEditableWorkspaceBuild({
|
|
1179
1265
|
options,
|
|
1180
1266
|
auth,
|
|
@@ -1281,6 +1367,7 @@ export function normalizeContributionBranchBuild({ branch, sourceBuild }) {
|
|
|
1281
1367
|
Number(branch?.contributionContributorId || branch?.userId || 0) || null,
|
|
1282
1368
|
contributionBranchNumber:
|
|
1283
1369
|
Number(branch?.contributionBranchNumber || 0) || null,
|
|
1370
|
+
projectLimits: branch?.projectLimits || sourceBuild?.projectLimits || null,
|
|
1284
1371
|
};
|
|
1285
1372
|
}
|
|
1286
1373
|
|
|
@@ -2478,6 +2565,7 @@ export function printHelp() {
|
|
|
2478
2565
|
lumine new [title]
|
|
2479
2566
|
lumine rename [title] [--target <twinkle-build-url-or-id>]
|
|
2480
2567
|
lumine describe [description] [--target <twinkle-build-url-or-id>]
|
|
2568
|
+
lumine upgrade [twinkle-build-url-or-id]
|
|
2481
2569
|
lumine projects
|
|
2482
2570
|
lumine branches [twinkle-build-url-or-id] [--limit <n>]
|
|
2483
2571
|
lumine suggest branch [message] [--target <twinkle-branch-url>]
|
|
@@ -2554,6 +2642,7 @@ Examples:
|
|
|
2554
2642
|
npx @stage5/lumine@latest rename "New Title" --target 123
|
|
2555
2643
|
npx @stage5/lumine@latest describe "A welcoming place to build together"
|
|
2556
2644
|
npx @stage5/lumine@latest describe --no-description --target 123
|
|
2645
|
+
npx @stage5/lumine@latest upgrade https://www.twin-kle.com/app/123
|
|
2557
2646
|
npx @stage5/lumine@latest new --title "Daily Reflection App" --description "Private journal with streaks"
|
|
2558
2647
|
npx @stage5/lumine@latest branches 884
|
|
2559
2648
|
npx @stage5/lumine@latest suggest branch "Ready for review"
|
|
@@ -2599,7 +2688,7 @@ Options:
|
|
|
2599
2688
|
--auth-file <path> Saved login path
|
|
2600
2689
|
--auth-token <token> Override saved login
|
|
2601
2690
|
--dir <path> Directory for pulled project files
|
|
2602
|
-
--target <build> Explicit Build URL or ID for rename/describe
|
|
2691
|
+
--target <build> Explicit Build URL or ID for rename/describe/upgrade
|
|
2603
2692
|
--main With pull/versions/restore: target the team project's main
|
|
2604
2693
|
--version <n> With pull: read-only checkout of previous save v<n>
|
|
2605
2694
|
--title <text> Build title for new/rename
|
package/lib/constants.js
CHANGED
|
@@ -69,14 +69,12 @@ export const ASSET_MIME_BY_EXTENSION = {
|
|
|
69
69
|
".bin": "application/octet-stream",
|
|
70
70
|
".drc": "application/octet-stream",
|
|
71
71
|
};
|
|
72
|
-
//
|
|
73
|
-
// limits
|
|
74
|
-
// env-overridable server-side, so the CLI only warns on it.
|
|
72
|
+
// Default platform project-file limits. A pulled workspace can carry larger
|
|
73
|
+
// canonical per-project limits approved on Main in lumine-project.json.
|
|
75
74
|
export const PROJECT_MAX_FILES = 100;
|
|
76
75
|
export const PROJECT_MAX_EFFECTIVE_FILE_LINES = 500;
|
|
77
76
|
export const PROJECT_EFFECTIVE_LINE_MAX_COLUMNS = 160;
|
|
78
77
|
export const PROJECT_MAX_TOTAL_BYTES_DEFAULT = 1024 * 1024;
|
|
79
|
-
export const PROJECT_WARN_TOTAL_BYTES = 500 * 1024;
|
|
80
78
|
// Matches asset references in project files: absolute CloudFront URLs and
|
|
81
79
|
// relative /attachments/build-runtime|optimized/... paths.
|
|
82
80
|
export const RUNTIME_ASSET_REFERENCE_PATTERN =
|
|
@@ -233,10 +231,12 @@ lumine save --summary "Describe the change"
|
|
|
233
231
|
|
|
234
232
|
## Project File Limits
|
|
235
233
|
|
|
236
|
-
-
|
|
234
|
+
- Default max ${PROJECT_MAX_FILES} files and ${Math.floor(PROJECT_MAX_TOTAL_BYTES_DEFAULT / 1024)} KB per project. Read
|
|
235
|
+
\`.twinkle/lumine-project.json\` for this project's canonical \`projectLimits\`;
|
|
236
|
+
an approved Main project can allow up to 500 files and 5 MB, inherited by branches.
|
|
237
|
+
- Each file max ${PROJECT_MAX_EFFECTIVE_FILE_LINES} effective lines
|
|
237
238
|
(a physical line counts once per ${PROJECT_EFFECTIVE_LINE_MAX_COLUMNS} characters, so minified one-liners
|
|
238
|
-
blow the limit).
|
|
239
|
-
with a soft warning from ${Math.floor(PROJECT_WARN_TOTAL_BYTES / 1024)} KB.
|
|
239
|
+
blow the limit).
|
|
240
240
|
- Plan file splits BEFORE writing code; \`lumine check\` validates the local
|
|
241
241
|
workspace against these limits without saving, and \`lumine save\` fails fast
|
|
242
242
|
locally listing every violation.
|
|
@@ -338,6 +338,7 @@ export const COMMANDS = new Set([
|
|
|
338
338
|
"new",
|
|
339
339
|
"rename",
|
|
340
340
|
"describe",
|
|
341
|
+
"upgrade",
|
|
341
342
|
"projects",
|
|
342
343
|
"branches",
|
|
343
344
|
"suggest",
|
package/lib/workspace.js
CHANGED
|
@@ -15,7 +15,6 @@ import {
|
|
|
15
15
|
PROJECT_MAX_EFFECTIVE_FILE_LINES,
|
|
16
16
|
PROJECT_MAX_FILES,
|
|
17
17
|
PROJECT_MAX_TOTAL_BYTES_DEFAULT,
|
|
18
|
-
PROJECT_WARN_TOTAL_BYTES,
|
|
19
18
|
PROJECT_METADATA_DIR,
|
|
20
19
|
PROJECT_METADATA_FILE,
|
|
21
20
|
SDK_REFERENCE_FALLBACK,
|
|
@@ -342,16 +341,30 @@ export function countEffectiveLines(value) {
|
|
|
342
341
|
);
|
|
343
342
|
}
|
|
344
343
|
|
|
345
|
-
// Local mirror of the
|
|
346
|
-
// the full list (the server stops at the first).
|
|
347
|
-
|
|
348
|
-
// is env-overridable server-side -> warning only, the server stays the gate.
|
|
349
|
-
export function collectProjectLimitFindings(files) {
|
|
344
|
+
// Local mirror of the canonical per-project limits returned by the server so
|
|
345
|
+
// violations fail fast with the full list (the server stops at the first).
|
|
346
|
+
export function collectProjectLimitFindings(files, projectLimits = {}) {
|
|
350
347
|
const errors = [];
|
|
351
348
|
const warnings = [];
|
|
352
|
-
|
|
349
|
+
const canonicalMaxFiles = Math.floor(
|
|
350
|
+
Number(projectLimits?.maxFilesPerProject || 0),
|
|
351
|
+
);
|
|
352
|
+
const canonicalMaxProjectBytes = Math.floor(
|
|
353
|
+
Number(projectLimits?.maxProjectBytes || 0),
|
|
354
|
+
);
|
|
355
|
+
const maxFilesPerProject =
|
|
356
|
+
canonicalMaxFiles > 0 ? canonicalMaxFiles : PROJECT_MAX_FILES;
|
|
357
|
+
const maxProjectBytes =
|
|
358
|
+
canonicalMaxProjectBytes > 0
|
|
359
|
+
? canonicalMaxProjectBytes
|
|
360
|
+
: PROJECT_MAX_TOTAL_BYTES_DEFAULT;
|
|
361
|
+
const warnProjectBytes = Math.min(
|
|
362
|
+
Math.floor(maxProjectBytes * 0.8),
|
|
363
|
+
maxProjectBytes - 1,
|
|
364
|
+
);
|
|
365
|
+
if (files.length > maxFilesPerProject) {
|
|
353
366
|
errors.push(
|
|
354
|
-
`Project exceeds the ${
|
|
367
|
+
`Project exceeds the ${maxFilesPerProject}-file limit (${files.length} files). Split or consolidate files before saving.`,
|
|
355
368
|
);
|
|
356
369
|
}
|
|
357
370
|
for (const file of files) {
|
|
@@ -369,26 +382,23 @@ export function collectProjectLimitFindings(files) {
|
|
|
369
382
|
total + Buffer.byteLength(String(file.content || ""), "utf8"),
|
|
370
383
|
0,
|
|
371
384
|
);
|
|
372
|
-
if (totalBytes >
|
|
373
|
-
|
|
374
|
-
`Project files total ${formatBytes(totalBytes)}, above
|
|
375
|
-
PROJECT_MAX_TOTAL_BYTES_DEFAULT / 1024,
|
|
376
|
-
)} KB project limit — the server may reject this save.`,
|
|
385
|
+
if (totalBytes > maxProjectBytes) {
|
|
386
|
+
errors.push(
|
|
387
|
+
`Project files total ${formatBytes(totalBytes)}, above this project's ${formatBytes(maxProjectBytes)} limit.`,
|
|
377
388
|
);
|
|
378
|
-
} else if (totalBytes >
|
|
389
|
+
} else if (totalBytes > warnProjectBytes) {
|
|
379
390
|
warnings.push(
|
|
380
|
-
`Project files total ${formatBytes(totalBytes)} — approaching
|
|
381
|
-
PROJECT_MAX_TOTAL_BYTES_DEFAULT / 1024,
|
|
382
|
-
)} KB project cap (soft warning from ${Math.floor(
|
|
383
|
-
PROJECT_WARN_TOTAL_BYTES / 1024,
|
|
384
|
-
)} KB). Keep files lean where structure allows.`,
|
|
391
|
+
`Project files total ${formatBytes(totalBytes)} — approaching this project's ${formatBytes(maxProjectBytes)} cap. Keep files lean where structure allows.`,
|
|
385
392
|
);
|
|
386
393
|
}
|
|
387
394
|
return { errors, warnings };
|
|
388
395
|
}
|
|
389
396
|
|
|
390
|
-
export function assertProjectFilesWithinLimits(files) {
|
|
391
|
-
const { errors, warnings } = collectProjectLimitFindings(
|
|
397
|
+
export function assertProjectFilesWithinLimits(files, projectLimits = {}) {
|
|
398
|
+
const { errors, warnings } = collectProjectLimitFindings(
|
|
399
|
+
files,
|
|
400
|
+
projectLimits,
|
|
401
|
+
);
|
|
392
402
|
for (const warning of warnings) {
|
|
393
403
|
console.error(`lumine: warning — ${warning}`);
|
|
394
404
|
}
|
|
@@ -587,6 +597,10 @@ export async function writeProjectMetadata({
|
|
|
587
597
|
build.canPublish !== undefined
|
|
588
598
|
? Boolean(build.canPublish)
|
|
589
599
|
: build.role === "owner",
|
|
600
|
+
projectLimits:
|
|
601
|
+
build.projectLimits && typeof build.projectLimits === "object"
|
|
602
|
+
? build.projectLimits
|
|
603
|
+
: null,
|
|
590
604
|
}
|
|
591
605
|
: null,
|
|
592
606
|
apiUrl: options.apiUrl,
|