@lumerahq/cli 0.19.21 → 0.19.23
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-ML7XB7Z2.js → chunk-JMRD5WIY.js} +56 -0
- package/dist/{chunk-S7FJU4GM.js → chunk-RH4FQF6O.js} +1 -1
- package/dist/{chunk-PY4J2U2M.js → chunk-XT4EGGK7.js} +8 -2
- package/dist/{deps-H4QYLGM7.js → deps-VTLXKCQ7.js} +2 -2
- package/dist/{dev-O6TXHGAK.js → dev-EQK27BUM.js} +3 -3
- package/dist/index.js +12 -12
- package/dist/{init-HUV5ZJAE.js → init-FTDMPFR3.js} +122 -3
- package/dist/{register-OZDOFUPF.js → register-BQCVUPWS.js} +8 -2
- package/dist/{resources-FLFD64X2.js → resources-Z5ZMPWTK.js} +10 -4
- package/dist/{run-NR3725J2.js → run-LHHX7UBN.js} +1 -1
- package/package.json +1 -1
|
@@ -7,6 +7,55 @@ import {
|
|
|
7
7
|
fetchWithRetry
|
|
8
8
|
} from "./chunk-FJFIWC7G.js";
|
|
9
9
|
|
|
10
|
+
// src/lib/name-limits.ts
|
|
11
|
+
var MAX_PROJECT_NAME_LENGTH = 30;
|
|
12
|
+
var MAX_COLLECTION_NAME_LENGTH = 30;
|
|
13
|
+
var MAX_POSTGRES_IDENTIFIER_BYTES = 63;
|
|
14
|
+
var PROJECT_EXTERNAL_ID_REGEX = /^[a-z0-9][a-z0-9-]*[a-z0-9]$|^[a-z0-9]$/;
|
|
15
|
+
var RESERVED_PROJECT_EXTERNAL_IDS = /* @__PURE__ */ new Set([
|
|
16
|
+
"collection",
|
|
17
|
+
"collections",
|
|
18
|
+
"default",
|
|
19
|
+
"admin",
|
|
20
|
+
"api",
|
|
21
|
+
"system",
|
|
22
|
+
"internal",
|
|
23
|
+
"platform",
|
|
24
|
+
"lumera",
|
|
25
|
+
"lm"
|
|
26
|
+
]);
|
|
27
|
+
function lengthOf(value) {
|
|
28
|
+
return Array.from(value).length;
|
|
29
|
+
}
|
|
30
|
+
function validateProjectNameLength(name, label = "App name") {
|
|
31
|
+
if (lengthOf(name.trim()) > MAX_PROJECT_NAME_LENGTH) {
|
|
32
|
+
return `${label} must be ${MAX_PROJECT_NAME_LENGTH} characters or less`;
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
function validateProjectExternalId(externalId) {
|
|
37
|
+
const trimmed = externalId.trim();
|
|
38
|
+
const lengthError = validateProjectNameLength(trimmed, "App ID");
|
|
39
|
+
if (lengthError) return lengthError;
|
|
40
|
+
if (!PROJECT_EXTERNAL_ID_REGEX.test(trimmed)) {
|
|
41
|
+
return "App ID must use lowercase letters, numbers, and hyphens only";
|
|
42
|
+
}
|
|
43
|
+
if (RESERVED_PROJECT_EXTERNAL_IDS.has(trimmed)) {
|
|
44
|
+
return `App ID "${trimmed}" is a reserved name`;
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
function validateCollectionNameLength(name) {
|
|
49
|
+
const trimmed = name.trim();
|
|
50
|
+
if (lengthOf(trimmed) > MAX_COLLECTION_NAME_LENGTH) {
|
|
51
|
+
return `collection name must be ${MAX_COLLECTION_NAME_LENGTH} characters or less`;
|
|
52
|
+
}
|
|
53
|
+
if (new TextEncoder().encode(trimmed).byteLength > MAX_POSTGRES_IDENTIFIER_BYTES) {
|
|
54
|
+
return `collection name must be ${MAX_POSTGRES_IDENTIFIER_BYTES} bytes or less`;
|
|
55
|
+
}
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
|
|
10
59
|
// src/lib/api.ts
|
|
11
60
|
init_auth();
|
|
12
61
|
import { readFileSync } from "fs";
|
|
@@ -282,6 +331,10 @@ var ApiClient = class {
|
|
|
282
331
|
// Projects — register/lookup via PocketBase records API
|
|
283
332
|
async registerProject(name, externalId) {
|
|
284
333
|
const extId = externalId || name;
|
|
334
|
+
const nameError = validateProjectNameLength(name, "App name");
|
|
335
|
+
if (nameError) throw new Error(nameError);
|
|
336
|
+
const externalIdError = validateProjectExternalId(extId);
|
|
337
|
+
if (externalIdError) throw new Error(externalIdError);
|
|
285
338
|
const existing = await this.getProjectByExternalId(extId);
|
|
286
339
|
if (existing) {
|
|
287
340
|
return existing;
|
|
@@ -364,6 +417,9 @@ function createApiClient(token, baseUrl, projectExternalId) {
|
|
|
364
417
|
}
|
|
365
418
|
|
|
366
419
|
export {
|
|
420
|
+
validateProjectNameLength,
|
|
421
|
+
validateProjectExternalId,
|
|
422
|
+
validateCollectionNameLength,
|
|
367
423
|
userContextHeaders,
|
|
368
424
|
ApiError,
|
|
369
425
|
isApiErrorStatus,
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
|
-
userContextHeaders
|
|
3
|
-
|
|
2
|
+
userContextHeaders,
|
|
3
|
+
validateProjectExternalId,
|
|
4
|
+
validateProjectNameLength
|
|
5
|
+
} from "./chunk-JMRD5WIY.js";
|
|
4
6
|
import {
|
|
5
7
|
fetchWithRetry
|
|
6
8
|
} from "./chunk-FJFIWC7G.js";
|
|
@@ -63,6 +65,10 @@ async function createTarball(distDir) {
|
|
|
63
65
|
});
|
|
64
66
|
}
|
|
65
67
|
async function ensureAppRecord(apiBase, token, appName, appTitle) {
|
|
68
|
+
const appIdError = validateProjectExternalId(appName);
|
|
69
|
+
if (appIdError) throw new Error(appIdError);
|
|
70
|
+
const appNameError = validateProjectNameLength(appTitle, "App name");
|
|
71
|
+
if (appNameError) throw new Error(appNameError);
|
|
66
72
|
const filterParam = encodeURIComponent(JSON.stringify({ external_id: appName }));
|
|
67
73
|
const searchRes = await fetchWithRetry(
|
|
68
74
|
`${apiBase}/pb/collections/lm_custom_apps/records?filter=${filterParam}`,
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
deps,
|
|
3
3
|
syncDeps
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-RH4FQF6O.js";
|
|
5
5
|
import "./chunk-2CR762KB.js";
|
|
6
|
-
import "./chunk-
|
|
6
|
+
import "./chunk-JMRD5WIY.js";
|
|
7
7
|
import "./chunk-ZH3NVYEQ.js";
|
|
8
8
|
import "./chunk-FJFIWC7G.js";
|
|
9
9
|
import "./chunk-PNKVD2UK.js";
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
dev
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-XT4EGGK7.js";
|
|
4
4
|
import {
|
|
5
5
|
syncDeps
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-RH4FQF6O.js";
|
|
7
7
|
import {
|
|
8
8
|
loadEnv
|
|
9
9
|
} from "./chunk-2CR762KB.js";
|
|
10
10
|
import {
|
|
11
11
|
createApiClient
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-JMRD5WIY.js";
|
|
13
13
|
import {
|
|
14
14
|
findProjectRoot,
|
|
15
15
|
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-
|
|
222
|
+
await import("./resources-Z5ZMPWTK.js").then((m) => m.plan(args.slice(1)));
|
|
223
223
|
break;
|
|
224
224
|
case "apply":
|
|
225
|
-
await import("./resources-
|
|
225
|
+
await import("./resources-Z5ZMPWTK.js").then((m) => m.apply(args.slice(1)));
|
|
226
226
|
break;
|
|
227
227
|
case "pull":
|
|
228
|
-
await import("./resources-
|
|
228
|
+
await import("./resources-Z5ZMPWTK.js").then((m) => m.pull(args.slice(1)));
|
|
229
229
|
break;
|
|
230
230
|
case "destroy":
|
|
231
|
-
await import("./resources-
|
|
231
|
+
await import("./resources-Z5ZMPWTK.js").then((m) => m.destroy(args.slice(1)));
|
|
232
232
|
break;
|
|
233
233
|
case "list":
|
|
234
|
-
await import("./resources-
|
|
234
|
+
await import("./resources-Z5ZMPWTK.js").then((m) => m.list(args.slice(1)));
|
|
235
235
|
break;
|
|
236
236
|
case "show":
|
|
237
|
-
await import("./resources-
|
|
237
|
+
await import("./resources-Z5ZMPWTK.js").then((m) => m.show(args.slice(1)));
|
|
238
238
|
break;
|
|
239
239
|
case "diff":
|
|
240
|
-
await import("./resources-
|
|
240
|
+
await import("./resources-Z5ZMPWTK.js").then((m) => m.diff(args.slice(1)));
|
|
241
241
|
break;
|
|
242
242
|
// Development
|
|
243
243
|
case "dev":
|
|
244
|
-
await import("./dev-
|
|
244
|
+
await import("./dev-EQK27BUM.js").then((m) => m.dev(args.slice(1)));
|
|
245
245
|
break;
|
|
246
246
|
case "run":
|
|
247
|
-
await import("./run-
|
|
247
|
+
await import("./run-LHHX7UBN.js").then((m) => m.run(args.slice(1)));
|
|
248
248
|
break;
|
|
249
249
|
// Project
|
|
250
250
|
case "init":
|
|
251
|
-
await import("./init-
|
|
251
|
+
await import("./init-FTDMPFR3.js").then((m) => m.init(args.slice(1)));
|
|
252
252
|
break;
|
|
253
253
|
case "register":
|
|
254
|
-
await import("./register-
|
|
254
|
+
await import("./register-BQCVUPWS.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-
|
|
271
|
+
await import("./deps-VTLXKCQ7.js").then((m) => m.deps(args.slice(1)));
|
|
272
272
|
break;
|
|
273
273
|
// Auth
|
|
274
274
|
case "login":
|
|
@@ -6,8 +6,9 @@ import {
|
|
|
6
6
|
spinner
|
|
7
7
|
} from "./chunk-BHYDYR75.js";
|
|
8
8
|
import {
|
|
9
|
-
createApiClient
|
|
10
|
-
|
|
9
|
+
createApiClient,
|
|
10
|
+
validateProjectNameLength
|
|
11
|
+
} from "./chunk-JMRD5WIY.js";
|
|
11
12
|
import {
|
|
12
13
|
getToken,
|
|
13
14
|
init_auth,
|
|
@@ -26,7 +27,7 @@ import pc from "picocolors";
|
|
|
26
27
|
import prompts from "prompts";
|
|
27
28
|
import { execSync } from "child_process";
|
|
28
29
|
import { copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, rmSync, readlinkSync, symlinkSync } from "fs";
|
|
29
|
-
import { join, resolve } from "path";
|
|
30
|
+
import { dirname, join, resolve } from "path";
|
|
30
31
|
function toTitleCase(str) {
|
|
31
32
|
return str.split(/[-_]/).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
32
33
|
}
|
|
@@ -42,6 +43,84 @@ function processTemplate(content, replacements) {
|
|
|
42
43
|
var TEMPLATE_EXCLUDE = /* @__PURE__ */ new Set(["template.json"]);
|
|
43
44
|
var TEMPLATE_RENAMES = /* @__PURE__ */ new Map([["_gitignore", ".gitignore"]]);
|
|
44
45
|
var TEMPLATE_RAW_COPY = /* @__PURE__ */ new Set(["pnpm-lock.yaml"]);
|
|
46
|
+
var STUDIO_PROBLEM_FIRST_FLAG = "studio_problem_first";
|
|
47
|
+
var MEMORY_AGENTS_SECTION = `## Project Memory
|
|
48
|
+
|
|
49
|
+
This project may use repo-backed memory in \`memory/\`.
|
|
50
|
+
|
|
51
|
+
Before substantial work, read \`memory/README.md\` if it exists, then any
|
|
52
|
+
relevant memory files.
|
|
53
|
+
|
|
54
|
+
Update memory when the user confirms a durable project decision, workflow
|
|
55
|
+
rule, data contract, naming convention, recurring assumption, or correction
|
|
56
|
+
that should survive future sessions.
|
|
57
|
+
|
|
58
|
+
Do not store secrets, credentials, tokens, private keys, or raw sensitive
|
|
59
|
+
source files in memory.
|
|
60
|
+
|
|
61
|
+
If a memory item affects app behavior, do not rely only on repo memory.
|
|
62
|
+
Persist it to the app's runtime source of truth, such as collections or config,
|
|
63
|
+
and expose it in the app UI or a clear review/edit path.
|
|
64
|
+
|
|
65
|
+
Summarize memory updates in your final response.
|
|
66
|
+
`;
|
|
67
|
+
var MEMORY_FILES = /* @__PURE__ */ new Map([
|
|
68
|
+
["README.md", `# Project Memory
|
|
69
|
+
|
|
70
|
+
Repo-backed memory keeps durable project context available across agent
|
|
71
|
+
sessions. Keep it concise and reviewable.
|
|
72
|
+
|
|
73
|
+
Use this folder for:
|
|
74
|
+
- confirmed project decisions
|
|
75
|
+
- durable workflow rules and mappings
|
|
76
|
+
- data contracts and source-file assumptions
|
|
77
|
+
- runbooks for recurring work
|
|
78
|
+
- open questions that should carry forward
|
|
79
|
+
|
|
80
|
+
Do not store secrets, credentials, tokens, private keys, or raw sensitive source
|
|
81
|
+
files here.
|
|
82
|
+
|
|
83
|
+
If a memory item affects app behavior, also persist it to the app's runtime
|
|
84
|
+
source of truth, such as collections or config, and expose it in the app UI or a
|
|
85
|
+
clear review/edit path.
|
|
86
|
+
`],
|
|
87
|
+
["project-context.md", `# Project Context
|
|
88
|
+
|
|
89
|
+
Summarize what this app does, who uses it, and the recurring workflow it
|
|
90
|
+
supports.
|
|
91
|
+
`],
|
|
92
|
+
["decisions.md", `# Decisions
|
|
93
|
+
|
|
94
|
+
Record durable decisions that future sessions should preserve.
|
|
95
|
+
|
|
96
|
+
| Date | Decision | Reason |
|
|
97
|
+
| --- | --- | --- |
|
|
98
|
+
`],
|
|
99
|
+
["open-questions.md", `# Open Questions
|
|
100
|
+
|
|
101
|
+
Track unresolved questions that should be answered before durable workflow
|
|
102
|
+
behavior is finalized.
|
|
103
|
+
`],
|
|
104
|
+
["rules/README.md", `# Rules
|
|
105
|
+
|
|
106
|
+
Document durable business rules, validation rules, thresholds, and exception
|
|
107
|
+
handling expectations.
|
|
108
|
+
`],
|
|
109
|
+
["mappings/README.md", `# Mappings
|
|
110
|
+
|
|
111
|
+
Document source-to-target mappings, normalization rules, and user-approved
|
|
112
|
+
corrections.
|
|
113
|
+
`],
|
|
114
|
+
["data-contracts/README.md", `# Data Contracts
|
|
115
|
+
|
|
116
|
+
Document expected source files, required columns, data types, and known source
|
|
117
|
+
system assumptions.
|
|
118
|
+
`],
|
|
119
|
+
["runbooks/README.md", `# Runbooks
|
|
120
|
+
|
|
121
|
+
Document repeatable operating steps for recurring workflows.
|
|
122
|
+
`]
|
|
123
|
+
]);
|
|
45
124
|
function copyDir(src, dest, replacements, isRoot = true) {
|
|
46
125
|
if (!existsSync(dest)) {
|
|
47
126
|
mkdirSync(dest, { recursive: true });
|
|
@@ -81,6 +160,35 @@ function ensureClaudeInstructionsLink(projectRoot) {
|
|
|
81
160
|
writeFileSync(claudeMdPath, readFileSync(agentsMdPath, "utf-8"));
|
|
82
161
|
}
|
|
83
162
|
}
|
|
163
|
+
async function isStudioProblemFirstScaffoldEnabled(targetDir) {
|
|
164
|
+
try {
|
|
165
|
+
const token = getToken(targetDir);
|
|
166
|
+
const api = createApiClient(token);
|
|
167
|
+
const me = await api.getMe();
|
|
168
|
+
return me.user?.feature_flags?.[STUDIO_PROBLEM_FIRST_FLAG] === true;
|
|
169
|
+
} catch {
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
function writeFileIfMissing(path, content) {
|
|
174
|
+
if (existsSync(path)) return;
|
|
175
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
176
|
+
writeFileSync(path, content);
|
|
177
|
+
}
|
|
178
|
+
function applyProblemFirstMemoryScaffold(projectRoot) {
|
|
179
|
+
const agentsMdPath = join(projectRoot, "AGENTS.md");
|
|
180
|
+
if (existsSync(agentsMdPath)) {
|
|
181
|
+
const agentsMd = readFileSync(agentsMdPath, "utf-8");
|
|
182
|
+
if (!agentsMd.includes("## Project Memory")) {
|
|
183
|
+
writeFileSync(agentsMdPath, `${agentsMd.trimEnd()}
|
|
184
|
+
|
|
185
|
+
${MEMORY_AGENTS_SECTION}`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
for (const [relativePath, content] of MEMORY_FILES) {
|
|
189
|
+
writeFileIfMissing(join(projectRoot, "memory", relativePath), content);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
84
192
|
function isGitInstalled() {
|
|
85
193
|
try {
|
|
86
194
|
execSync("git --version", { stdio: "ignore" });
|
|
@@ -280,6 +388,8 @@ async function init(args) {
|
|
|
280
388
|
initial: "my-app",
|
|
281
389
|
validate: (value) => {
|
|
282
390
|
if (!value) return "Project name is required";
|
|
391
|
+
const lengthError = validateProjectNameLength(value, "Project name");
|
|
392
|
+
if (lengthError) return lengthError;
|
|
283
393
|
if (!/^[a-z0-9-]+$/.test(value)) {
|
|
284
394
|
return "Use lowercase letters, numbers, and hyphens only";
|
|
285
395
|
}
|
|
@@ -301,6 +411,11 @@ async function init(args) {
|
|
|
301
411
|
console.log(pc.red(" Error: Project name must use lowercase letters, numbers, and hyphens only"));
|
|
302
412
|
process.exit(1);
|
|
303
413
|
}
|
|
414
|
+
const projectNameLengthError = validateProjectNameLength(finalProjectName, "Project name");
|
|
415
|
+
if (projectNameLengthError) {
|
|
416
|
+
console.log(pc.red(` Error: ${projectNameLengthError}`));
|
|
417
|
+
process.exit(1);
|
|
418
|
+
}
|
|
304
419
|
if (!directory) {
|
|
305
420
|
if (nonInteractive) {
|
|
306
421
|
directory = finalProjectName;
|
|
@@ -366,7 +481,11 @@ async function init(args) {
|
|
|
366
481
|
[sourceName, finalProjectName],
|
|
367
482
|
[sourceTitle, projectTitle]
|
|
368
483
|
];
|
|
484
|
+
const problemFirstScaffold = await isStudioProblemFirstScaffoldEnabled(targetDir);
|
|
369
485
|
copyDir(templateDir, targetDir, replacements);
|
|
486
|
+
if (problemFirstScaffold) {
|
|
487
|
+
applyProblemFirstMemoryScaffold(targetDir);
|
|
488
|
+
}
|
|
370
489
|
ensureClaudeInstructionsLink(targetDir);
|
|
371
490
|
const installCommand = existsSync(join(targetDir, "pnpm-lock.yaml")) ? "pnpm install --frozen-lockfile" : "pnpm install";
|
|
372
491
|
function listFiles(dir, prefix = "") {
|
|
@@ -5,8 +5,9 @@ import {
|
|
|
5
5
|
spinner
|
|
6
6
|
} from "./chunk-BHYDYR75.js";
|
|
7
7
|
import {
|
|
8
|
-
createApiClient
|
|
9
|
-
|
|
8
|
+
createApiClient,
|
|
9
|
+
validateProjectExternalId
|
|
10
|
+
} from "./chunk-JMRD5WIY.js";
|
|
10
11
|
import {
|
|
11
12
|
findProjectRoot,
|
|
12
13
|
getAppName,
|
|
@@ -45,6 +46,11 @@ async function register(args) {
|
|
|
45
46
|
const projectRoot = findProjectRoot();
|
|
46
47
|
loadEnv(projectRoot);
|
|
47
48
|
const appName = getAppName(projectRoot);
|
|
49
|
+
const appNameError = validateProjectExternalId(appName);
|
|
50
|
+
if (appNameError) {
|
|
51
|
+
console.error(pc.red(` ${appNameError}`));
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
48
54
|
const existingId = getProjectId(projectRoot);
|
|
49
55
|
if (existingId && !force) {
|
|
50
56
|
console.log();
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import {
|
|
2
2
|
deploy
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-XT4EGGK7.js";
|
|
4
4
|
import {
|
|
5
5
|
syncDeps
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-RH4FQF6O.js";
|
|
7
7
|
import {
|
|
8
8
|
loadEnv
|
|
9
9
|
} from "./chunk-2CR762KB.js";
|
|
10
10
|
import {
|
|
11
11
|
ApiError,
|
|
12
|
-
createApiClient
|
|
13
|
-
|
|
12
|
+
createApiClient,
|
|
13
|
+
validateCollectionNameLength
|
|
14
|
+
} from "./chunk-JMRD5WIY.js";
|
|
14
15
|
import {
|
|
15
16
|
findProjectRoot,
|
|
16
17
|
getApiUrl,
|
|
@@ -1261,6 +1262,11 @@ function loadLocalCollections(platformDir, filterName) {
|
|
|
1261
1262
|
errors.push(`${file}: missing name field`);
|
|
1262
1263
|
continue;
|
|
1263
1264
|
}
|
|
1265
|
+
const collectionNameError = validateCollectionNameLength(collection.name);
|
|
1266
|
+
if (collectionNameError) {
|
|
1267
|
+
errors.push(`${file}: ${collectionNameError}`);
|
|
1268
|
+
continue;
|
|
1269
|
+
}
|
|
1264
1270
|
if (/\s/.test(collection.name)) {
|
|
1265
1271
|
errors.push(`${file}: collection name "${collection.name}" contains spaces - use underscores instead`);
|
|
1266
1272
|
continue;
|