@lumerahq/cli 0.19.3 → 0.19.5
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-AUYOTENF.js → chunk-HU7RYLUF.js} +1 -1
- package/dist/{chunk-P5HFNAVN.js → chunk-RUJRTLHA.js} +12 -3
- package/dist/{chunk-JKXLKK5I.js → chunk-WY6UMJNI.js} +18 -2
- package/dist/{deps-EC3VRNN7.js → deps-73XZXRYP.js} +2 -2
- package/dist/{dev-R43VQCZD.js → dev-LDLUZ2VO.js} +8 -3
- package/dist/index.js +13 -13
- package/dist/{init-TDIQAOG4.js → init-7YWMMTBF.js} +26 -16
- package/dist/{register-JFJADKAJ.js → register-KJMSMMD6.js} +1 -1
- package/dist/{resources-OP7EECKZ.js → resources-UMGSN6QA.js} +290 -103
- package/dist/{run-EJP5WCQU.js → run-C23KZI4Z.js} +1 -1
- package/dist/{skills-TNJHMV4F.js → skills-REOKLNEF.js} +1 -1
- package/package.json +1 -1
|
@@ -31,7 +31,7 @@ async function fetchSkillsList() {
|
|
|
31
31
|
if (!listRes.ok) {
|
|
32
32
|
throw new Error(`Failed to fetch skills list: ${listRes.status}`);
|
|
33
33
|
}
|
|
34
|
-
return listRes.json();
|
|
34
|
+
return await listRes.json();
|
|
35
35
|
}
|
|
36
36
|
async function fetchSkillContent(slug) {
|
|
37
37
|
const baseUrl = getBaseUrl();
|
|
@@ -2,8 +2,9 @@ import {
|
|
|
2
2
|
loadEnv
|
|
3
3
|
} from "./chunk-2CR762KB.js";
|
|
4
4
|
import {
|
|
5
|
-
createApiClient
|
|
6
|
-
|
|
5
|
+
createApiClient,
|
|
6
|
+
isApiErrorStatus
|
|
7
|
+
} from "./chunk-WY6UMJNI.js";
|
|
7
8
|
import {
|
|
8
9
|
findProjectRoot,
|
|
9
10
|
getAppName
|
|
@@ -86,6 +87,10 @@ async function syncResourceShareDeps(projectRoot, api, projectExternalId, opts)
|
|
|
86
87
|
try {
|
|
87
88
|
shares = await listIncomingCollectionShares(api, projectExternalId);
|
|
88
89
|
} catch (e) {
|
|
90
|
+
if (opts.ignorePermissionDenied && isApiErrorStatus(e, 403)) {
|
|
91
|
+
console.log(pc.yellow(" \u26A0"), "Skipping resource-share sync \u2014 current token cannot list project shares (403).");
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
89
94
|
if (!opts.quiet) {
|
|
90
95
|
console.log(pc.red(" \u2717"), `Failed to list incoming resource shares \u2014 ${e}`);
|
|
91
96
|
}
|
|
@@ -170,7 +175,11 @@ async function syncDeps(projectRoot, options = {}) {
|
|
|
170
175
|
const quiet = options.quiet === true;
|
|
171
176
|
const write = options.write === true;
|
|
172
177
|
if (flagOn) {
|
|
173
|
-
return syncResourceShareDeps(root, api, appName, {
|
|
178
|
+
return syncResourceShareDeps(root, api, appName, {
|
|
179
|
+
write,
|
|
180
|
+
quiet,
|
|
181
|
+
ignorePermissionDenied: options.ignorePermissionDenied === true
|
|
182
|
+
});
|
|
174
183
|
}
|
|
175
184
|
if (options.legacyWhenDisabled === false) {
|
|
176
185
|
return true;
|
|
@@ -25,6 +25,19 @@ while (__pkgDir !== "/") {
|
|
|
25
25
|
}
|
|
26
26
|
var pkg = JSON.parse(readFileSync(resolve(__pkgDir, "package.json"), "utf-8"));
|
|
27
27
|
var CLI_USER_AGENT = `lumera-cli/${pkg.version}`;
|
|
28
|
+
var ApiError = class extends Error {
|
|
29
|
+
status;
|
|
30
|
+
body;
|
|
31
|
+
constructor(status, body) {
|
|
32
|
+
super(`API request failed: ${status} ${body}`);
|
|
33
|
+
this.name = "ApiError";
|
|
34
|
+
this.status = status;
|
|
35
|
+
this.body = body;
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
function isApiErrorStatus(error, status) {
|
|
39
|
+
return error instanceof ApiError && error.status === status;
|
|
40
|
+
}
|
|
28
41
|
var ApiClient = class {
|
|
29
42
|
baseUrl;
|
|
30
43
|
token;
|
|
@@ -66,12 +79,12 @@ var ApiClient = class {
|
|
|
66
79
|
});
|
|
67
80
|
if (!response.ok) {
|
|
68
81
|
const text = await response.text();
|
|
69
|
-
throw new
|
|
82
|
+
throw new ApiError(response.status, text);
|
|
70
83
|
}
|
|
71
84
|
if (response.status === 204) {
|
|
72
85
|
return {};
|
|
73
86
|
}
|
|
74
|
-
return response.json();
|
|
87
|
+
return await response.json();
|
|
75
88
|
}
|
|
76
89
|
// Collections
|
|
77
90
|
async getMe() {
|
|
@@ -104,6 +117,7 @@ var ApiClient = class {
|
|
|
104
117
|
const qs = new URLSearchParams();
|
|
105
118
|
if (params?.external_id) qs.set("external_id", params.external_id);
|
|
106
119
|
if (params?.include_code) qs.set("include_code", "true");
|
|
120
|
+
if (params?.project_id) qs.set("project_id", params.project_id);
|
|
107
121
|
qs.set("limit", "100");
|
|
108
122
|
const all = [];
|
|
109
123
|
let offset = 0;
|
|
@@ -166,6 +180,7 @@ var ApiClient = class {
|
|
|
166
180
|
const query = new URLSearchParams();
|
|
167
181
|
if (params?.collection_id) query.set("collection_id", params.collection_id);
|
|
168
182
|
if (params?.external_id) query.set("external_id", params.external_id);
|
|
183
|
+
if (params?.project_id) query.set("project_id", params.project_id);
|
|
169
184
|
const queryString = query.toString();
|
|
170
185
|
const path = queryString ? `/api/pb/hooks?${queryString}` : "/api/pb/hooks";
|
|
171
186
|
const result = await this.request(path);
|
|
@@ -325,5 +340,6 @@ function createApiClient(token, baseUrl, projectExternalId) {
|
|
|
325
340
|
}
|
|
326
341
|
|
|
327
342
|
export {
|
|
343
|
+
isApiErrorStatus,
|
|
328
344
|
createApiClient
|
|
329
345
|
};
|
|
@@ -2,9 +2,9 @@ import {
|
|
|
2
2
|
deps,
|
|
3
3
|
projectResourceDepsEnabled,
|
|
4
4
|
syncDeps
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-RUJRTLHA.js";
|
|
6
6
|
import "./chunk-2CR762KB.js";
|
|
7
|
-
import "./chunk-
|
|
7
|
+
import "./chunk-WY6UMJNI.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-
|
|
7
|
+
} from "./chunk-RUJRTLHA.js";
|
|
8
8
|
import {
|
|
9
9
|
loadEnv
|
|
10
10
|
} from "./chunk-2CR762KB.js";
|
|
11
11
|
import {
|
|
12
12
|
createApiClient
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-WY6UMJNI.js";
|
|
14
14
|
import {
|
|
15
15
|
findProjectRoot,
|
|
16
16
|
getApiUrl,
|
|
@@ -126,7 +126,12 @@ async function dev2(args) {
|
|
|
126
126
|
const appTitle = getAppTitle(projectRoot);
|
|
127
127
|
const apiUrl = getApiUrl();
|
|
128
128
|
if (await projectResourceDepsEnabled(projectRoot)) {
|
|
129
|
-
await syncDeps(projectRoot, {
|
|
129
|
+
await syncDeps(projectRoot, {
|
|
130
|
+
write: true,
|
|
131
|
+
quiet: true,
|
|
132
|
+
legacyWhenDisabled: false,
|
|
133
|
+
ignorePermissionDenied: true
|
|
134
|
+
});
|
|
130
135
|
}
|
|
131
136
|
if (!flags["skip-setup"]) {
|
|
132
137
|
const fresh = await isFreshProject(projectRoot);
|
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-UMGSN6QA.js").then((m) => m.plan(args.slice(1)));
|
|
223
223
|
break;
|
|
224
224
|
case "apply":
|
|
225
|
-
await import("./resources-
|
|
225
|
+
await import("./resources-UMGSN6QA.js").then((m) => m.apply(args.slice(1)));
|
|
226
226
|
break;
|
|
227
227
|
case "pull":
|
|
228
|
-
await import("./resources-
|
|
228
|
+
await import("./resources-UMGSN6QA.js").then((m) => m.pull(args.slice(1)));
|
|
229
229
|
break;
|
|
230
230
|
case "destroy":
|
|
231
|
-
await import("./resources-
|
|
231
|
+
await import("./resources-UMGSN6QA.js").then((m) => m.destroy(args.slice(1)));
|
|
232
232
|
break;
|
|
233
233
|
case "list":
|
|
234
|
-
await import("./resources-
|
|
234
|
+
await import("./resources-UMGSN6QA.js").then((m) => m.list(args.slice(1)));
|
|
235
235
|
break;
|
|
236
236
|
case "show":
|
|
237
|
-
await import("./resources-
|
|
237
|
+
await import("./resources-UMGSN6QA.js").then((m) => m.show(args.slice(1)));
|
|
238
238
|
break;
|
|
239
239
|
case "diff":
|
|
240
|
-
await import("./resources-
|
|
240
|
+
await import("./resources-UMGSN6QA.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-LDLUZ2VO.js").then((m) => m.dev(args.slice(1)));
|
|
245
245
|
break;
|
|
246
246
|
case "run":
|
|
247
|
-
await import("./run-
|
|
247
|
+
await import("./run-C23KZI4Z.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-7YWMMTBF.js").then((m) => m.init(args.slice(1)));
|
|
252
252
|
break;
|
|
253
253
|
case "register":
|
|
254
|
-
await import("./register-
|
|
254
|
+
await import("./register-KJMSMMD6.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)));
|
|
@@ -264,11 +264,11 @@ async function main() {
|
|
|
264
264
|
break;
|
|
265
265
|
// Skills
|
|
266
266
|
case "skills":
|
|
267
|
-
await import("./skills-
|
|
267
|
+
await import("./skills-REOKLNEF.js").then((m) => m.skills(subcommand, args.slice(2)));
|
|
268
268
|
break;
|
|
269
269
|
// Dependencies
|
|
270
270
|
case "deps":
|
|
271
|
-
await import("./deps-
|
|
271
|
+
await import("./deps-73XZXRYP.js").then((m) => m.deps(args.slice(1)));
|
|
272
272
|
break;
|
|
273
273
|
// Auth
|
|
274
274
|
case "login":
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
installAllSkills,
|
|
3
3
|
syncClaudeMd
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-HU7RYLUF.js";
|
|
5
5
|
import {
|
|
6
6
|
spinner
|
|
7
7
|
} from "./chunk-BHYDYR75.js";
|
|
8
8
|
import {
|
|
9
9
|
createApiClient
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-WY6UMJNI.js";
|
|
11
11
|
import {
|
|
12
12
|
getToken,
|
|
13
13
|
init_auth,
|
|
@@ -287,19 +287,24 @@ async function init(args) {
|
|
|
287
287
|
}
|
|
288
288
|
projectName = response.projectName;
|
|
289
289
|
}
|
|
290
|
-
if (
|
|
290
|
+
if (!projectName) {
|
|
291
|
+
console.log(pc.red(" Error: Project name is required"));
|
|
292
|
+
process.exit(1);
|
|
293
|
+
}
|
|
294
|
+
const finalProjectName = projectName;
|
|
295
|
+
if (!/^[a-z0-9-]+$/.test(finalProjectName)) {
|
|
291
296
|
console.log(pc.red(" Error: Project name must use lowercase letters, numbers, and hyphens only"));
|
|
292
297
|
process.exit(1);
|
|
293
298
|
}
|
|
294
299
|
if (!directory) {
|
|
295
300
|
if (nonInteractive) {
|
|
296
|
-
directory =
|
|
301
|
+
directory = finalProjectName;
|
|
297
302
|
} else {
|
|
298
303
|
const response = await prompts({
|
|
299
304
|
type: "text",
|
|
300
305
|
name: "directory",
|
|
301
306
|
message: "Where should we create the project?",
|
|
302
|
-
initial:
|
|
307
|
+
initial: finalProjectName
|
|
303
308
|
});
|
|
304
309
|
if (!response.directory) {
|
|
305
310
|
console.log(pc.red("Cancelled"));
|
|
@@ -308,14 +313,19 @@ async function init(args) {
|
|
|
308
313
|
directory = response.directory;
|
|
309
314
|
}
|
|
310
315
|
}
|
|
311
|
-
|
|
312
|
-
|
|
316
|
+
if (!directory) {
|
|
317
|
+
console.log(pc.red(" Error: Directory is required"));
|
|
318
|
+
process.exit(1);
|
|
319
|
+
}
|
|
320
|
+
const finalDirectory = directory;
|
|
321
|
+
const projectTitle = toTitleCase(finalProjectName);
|
|
322
|
+
const targetDir = resolve(process.cwd(), finalDirectory);
|
|
313
323
|
if (existsSync(targetDir)) {
|
|
314
324
|
if (nonInteractive) {
|
|
315
325
|
if (opts.force) {
|
|
316
326
|
rmSync(targetDir, { recursive: true });
|
|
317
327
|
} else {
|
|
318
|
-
console.log(pc.red(` Error: Directory ${
|
|
328
|
+
console.log(pc.red(` Error: Directory ${finalDirectory} already exists`));
|
|
319
329
|
console.log(pc.dim(" Use --force (-f) to overwrite"));
|
|
320
330
|
process.exit(1);
|
|
321
331
|
}
|
|
@@ -323,7 +333,7 @@ async function init(args) {
|
|
|
323
333
|
const { overwrite } = await prompts({
|
|
324
334
|
type: "confirm",
|
|
325
335
|
name: "overwrite",
|
|
326
|
-
message: `Directory ${
|
|
336
|
+
message: `Directory ${finalDirectory} already exists. Overwrite?`,
|
|
327
337
|
initial: false
|
|
328
338
|
});
|
|
329
339
|
if (!overwrite) {
|
|
@@ -336,9 +346,9 @@ async function init(args) {
|
|
|
336
346
|
mkdirSync(targetDir, { recursive: true });
|
|
337
347
|
console.log();
|
|
338
348
|
if (templateName !== "default") {
|
|
339
|
-
console.log(pc.dim(` Creating ${
|
|
349
|
+
console.log(pc.dim(` Creating ${finalProjectName} from template ${pc.cyan(templateName)}...`));
|
|
340
350
|
} else {
|
|
341
|
-
console.log(pc.dim(` Creating ${
|
|
351
|
+
console.log(pc.dim(` Creating ${finalProjectName}...`));
|
|
342
352
|
}
|
|
343
353
|
console.log();
|
|
344
354
|
const templatePkgPath = join(templateDir, "package.json");
|
|
@@ -346,9 +356,9 @@ async function init(args) {
|
|
|
346
356
|
const sourceName = templatePkg.name || "my-lumera-app";
|
|
347
357
|
const sourceTitle = templatePkg.lumera?.name || toTitleCase(sourceName);
|
|
348
358
|
const replacements = [
|
|
349
|
-
["{{projectName}}",
|
|
359
|
+
["{{projectName}}", finalProjectName],
|
|
350
360
|
["{{projectTitle}}", projectTitle],
|
|
351
|
-
[sourceName,
|
|
361
|
+
[sourceName, finalProjectName],
|
|
352
362
|
[sourceTitle, projectTitle]
|
|
353
363
|
];
|
|
354
364
|
copyDir(templateDir, targetDir, replacements);
|
|
@@ -366,7 +376,7 @@ async function init(args) {
|
|
|
366
376
|
listFiles(targetDir);
|
|
367
377
|
if (isGitInstalled()) {
|
|
368
378
|
const stopGit = spinner("Initializing git repository...");
|
|
369
|
-
if (initGitRepo(targetDir,
|
|
379
|
+
if (initGitRepo(targetDir, finalProjectName)) {
|
|
370
380
|
stopGit(pc.green("\u2713") + pc.dim(" Git repository initialized with initial commit"));
|
|
371
381
|
} else {
|
|
372
382
|
stopGit(pc.yellow("\u26A0") + pc.dim(" Failed to initialize git repository"));
|
|
@@ -428,7 +438,7 @@ async function init(args) {
|
|
|
428
438
|
const api = createApiClient(token);
|
|
429
439
|
const stopRegister = spinner("Registering project on Lumera...");
|
|
430
440
|
try {
|
|
431
|
-
const project = await api.registerProject(
|
|
441
|
+
const project = await api.registerProject(finalProjectName);
|
|
432
442
|
setProjectId(targetDir, project.id);
|
|
433
443
|
stopRegister(pc.green("\u2713") + pc.dim(` Project registered (${project.id})`));
|
|
434
444
|
registered = true;
|
|
@@ -441,7 +451,7 @@ async function init(args) {
|
|
|
441
451
|
console.log();
|
|
442
452
|
console.log(pc.green(pc.bold(" Done!")), "Next steps:");
|
|
443
453
|
console.log();
|
|
444
|
-
console.log(pc.cyan(` cd ${
|
|
454
|
+
console.log(pc.cyan(` cd ${finalDirectory}`));
|
|
445
455
|
if (!opts.install) {
|
|
446
456
|
console.log(pc.cyan(" pnpm install"));
|
|
447
457
|
}
|
|
@@ -4,13 +4,13 @@ import {
|
|
|
4
4
|
import {
|
|
5
5
|
projectResourceDepsEnabled,
|
|
6
6
|
syncDeps
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-RUJRTLHA.js";
|
|
8
8
|
import {
|
|
9
9
|
loadEnv
|
|
10
10
|
} from "./chunk-2CR762KB.js";
|
|
11
11
|
import {
|
|
12
12
|
createApiClient
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-WY6UMJNI.js";
|
|
14
14
|
import {
|
|
15
15
|
findProjectRoot,
|
|
16
16
|
getApiUrl,
|
|
@@ -29,8 +29,95 @@ import "./chunk-PNKVD2UK.js";
|
|
|
29
29
|
import pc2 from "picocolors";
|
|
30
30
|
import prompts from "prompts";
|
|
31
31
|
import { execFileSync, execSync } from "child_process";
|
|
32
|
-
import { existsSync, readdirSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
33
|
-
import { join, resolve } from "path";
|
|
32
|
+
import { existsSync as existsSync2, readdirSync as readdirSync2, readFileSync as readFileSync2, writeFileSync, mkdirSync } from "fs";
|
|
33
|
+
import { join as join2, resolve } from "path";
|
|
34
|
+
|
|
35
|
+
// src/lib/lint/index.ts
|
|
36
|
+
import { existsSync, readFileSync, readdirSync } from "fs";
|
|
37
|
+
import { basename, join } from "path";
|
|
38
|
+
|
|
39
|
+
// src/lib/lint/rules/collection-schema.ts
|
|
40
|
+
function isRecord(value) {
|
|
41
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
42
|
+
}
|
|
43
|
+
function error(target, message, snippet) {
|
|
44
|
+
return {
|
|
45
|
+
ruleId: "collection-schema",
|
|
46
|
+
target,
|
|
47
|
+
severity: "error",
|
|
48
|
+
message,
|
|
49
|
+
snippet
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
var collectionSchemaRule = {
|
|
53
|
+
id: "collection-schema",
|
|
54
|
+
description: "Validates local collection JSON files before planning or applying resources.",
|
|
55
|
+
appliesTo: ["collection"],
|
|
56
|
+
check(target) {
|
|
57
|
+
let parsed;
|
|
58
|
+
try {
|
|
59
|
+
parsed = JSON.parse(target.source);
|
|
60
|
+
} catch (err) {
|
|
61
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
62
|
+
return [error(target, `Invalid JSON: ${message}`)];
|
|
63
|
+
}
|
|
64
|
+
if (!isRecord(parsed)) {
|
|
65
|
+
return [error(target, "Collection file must contain a JSON object.")];
|
|
66
|
+
}
|
|
67
|
+
const issues = [];
|
|
68
|
+
const id = parsed.id;
|
|
69
|
+
const name = parsed.name;
|
|
70
|
+
const fields = parsed.fields;
|
|
71
|
+
const indexes = parsed.indexes;
|
|
72
|
+
if (typeof id !== "string" || id.trim() === "") {
|
|
73
|
+
issues.push(error(target, 'Missing required string field "id".'));
|
|
74
|
+
}
|
|
75
|
+
if (typeof name !== "string" || name.trim() === "") {
|
|
76
|
+
issues.push(error(target, 'Missing required string field "name".'));
|
|
77
|
+
} else {
|
|
78
|
+
if (/\s/.test(name)) {
|
|
79
|
+
issues.push(error(target, `Collection name "${name}" contains spaces; use underscores instead.`));
|
|
80
|
+
}
|
|
81
|
+
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)) {
|
|
82
|
+
issues.push(error(target, `Collection name "${name}" contains invalid characters.`));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (!Array.isArray(fields)) {
|
|
86
|
+
issues.push(error(target, 'Missing required array field "fields".'));
|
|
87
|
+
} else {
|
|
88
|
+
for (let i = 0; i < fields.length; i++) {
|
|
89
|
+
const field = fields[i];
|
|
90
|
+
if (!isRecord(field)) {
|
|
91
|
+
issues.push(error(target, `Field at index ${i} must be an object.`));
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
if (typeof field.name !== "string" || field.name.trim() === "") {
|
|
95
|
+
issues.push(error(target, `Field at index ${i} is missing required string field "name".`));
|
|
96
|
+
}
|
|
97
|
+
if (typeof field.type !== "string" || field.type.trim() === "") {
|
|
98
|
+
issues.push(error(target, `Field ${typeof field.name === "string" ? `"${field.name}"` : `at index ${i}`} is missing required string field "type".`));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (indexes !== void 0) {
|
|
103
|
+
if (!Array.isArray(indexes)) {
|
|
104
|
+
issues.push(error(target, 'Optional field "indexes" must be an array when present.'));
|
|
105
|
+
} else {
|
|
106
|
+
for (let i = 0; i < indexes.length; i++) {
|
|
107
|
+
const index = indexes[i];
|
|
108
|
+
if (!isRecord(index)) {
|
|
109
|
+
issues.push(error(target, `Index at index ${i} must be an object.`));
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
if (!Array.isArray(index.fields) || !index.fields.every((field) => typeof field === "string" && field.trim() !== "")) {
|
|
113
|
+
issues.push(error(target, `Index at index ${i} must include a non-empty string array "fields".`));
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return issues;
|
|
119
|
+
}
|
|
120
|
+
};
|
|
34
121
|
|
|
35
122
|
// src/lib/lint/rules/llm-import.ts
|
|
36
123
|
var FROM_LUMERA_IMPORT_LLM = /^\s*from\s+lumera\s+import\s+\(?\s*(?:[\w\s,]*?\b)?llm\b/;
|
|
@@ -75,25 +162,37 @@ var llmImportRule = {
|
|
|
75
162
|
};
|
|
76
163
|
|
|
77
164
|
// src/lib/lint/registry.ts
|
|
78
|
-
var ALL_RULES = [llmImportRule];
|
|
165
|
+
var ALL_RULES = [collectionSchemaRule, llmImportRule];
|
|
79
166
|
|
|
80
167
|
// src/lib/lint/format.ts
|
|
81
168
|
import { relative } from "path";
|
|
82
169
|
import pc from "picocolors";
|
|
83
170
|
function printLintWarnings(warnings, projectRoot) {
|
|
84
171
|
if (warnings.length === 0) return;
|
|
172
|
+
const errors = warnings.filter((w) => w.severity === "error");
|
|
173
|
+
const advisory = warnings.filter((w) => w.severity !== "error");
|
|
85
174
|
console.log();
|
|
86
|
-
console.log(pc.bold(" Warnings:"));
|
|
175
|
+
console.log(pc.bold(errors.length > 0 ? " Lint issues:" : " Warnings:"));
|
|
87
176
|
for (const w of warnings) {
|
|
88
177
|
const rel = relative(projectRoot, w.target.filePath);
|
|
89
178
|
const loc = w.line ? `${rel}:${w.line}` : rel;
|
|
90
|
-
|
|
179
|
+
const isError = w.severity === "error";
|
|
180
|
+
const icon = isError ? pc.red("\u2717") : pc.yellow("\u26A0");
|
|
181
|
+
console.log(` ${icon} ${loc} ${pc.dim(`[${w.ruleId}]`)}`);
|
|
91
182
|
console.log(` ${w.message}`);
|
|
92
183
|
if (w.snippet) console.log(pc.dim(` > ${w.snippet}`));
|
|
93
184
|
}
|
|
94
185
|
console.log();
|
|
95
|
-
|
|
96
|
-
|
|
186
|
+
if (errors.length > 0) {
|
|
187
|
+
const n = errors.length;
|
|
188
|
+
console.log(pc.red(` ${n} error${n === 1 ? "" : "s"} \u2014 fix these before continuing.`));
|
|
189
|
+
if (advisory.length > 0) {
|
|
190
|
+
console.log(pc.dim(` ${advisory.length} warning${advisory.length === 1 ? "" : "s"} \u2014 advisory.`));
|
|
191
|
+
}
|
|
192
|
+
} else {
|
|
193
|
+
const n = advisory.length;
|
|
194
|
+
console.log(pc.dim(` ${n} warning${n === 1 ? "" : "s"} \u2014 advisory, will not block apply.`));
|
|
195
|
+
}
|
|
97
196
|
console.log();
|
|
98
197
|
}
|
|
99
198
|
function serializeLintWarnings(warnings, projectRoot) {
|
|
@@ -101,6 +200,7 @@ function serializeLintWarnings(warnings, projectRoot) {
|
|
|
101
200
|
ruleId: w.ruleId,
|
|
102
201
|
target: { kind: w.target.kind, name: w.target.name, filePath: relative(projectRoot, w.target.filePath) },
|
|
103
202
|
message: w.message,
|
|
203
|
+
severity: w.severity ?? "warning",
|
|
104
204
|
line: w.line,
|
|
105
205
|
snippet: w.snippet
|
|
106
206
|
}));
|
|
@@ -131,6 +231,22 @@ function buildAutomationTargets(localAutomations) {
|
|
|
131
231
|
source: a.code
|
|
132
232
|
}));
|
|
133
233
|
}
|
|
234
|
+
function buildCollectionTargets(platformDir, _filterName) {
|
|
235
|
+
const collectionsDir = join(platformDir, "collections");
|
|
236
|
+
if (!existsSync(collectionsDir)) return [];
|
|
237
|
+
const targets = [];
|
|
238
|
+
for (const entry of readdirSync(collectionsDir, { withFileTypes: true })) {
|
|
239
|
+
if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
|
|
240
|
+
const filePath = join(collectionsDir, entry.name);
|
|
241
|
+
targets.push({
|
|
242
|
+
kind: "collection",
|
|
243
|
+
name: basename(entry.name, ".json"),
|
|
244
|
+
filePath,
|
|
245
|
+
source: readFileSync(filePath, "utf-8")
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
return targets;
|
|
249
|
+
}
|
|
134
250
|
|
|
135
251
|
// src/commands/resources.ts
|
|
136
252
|
init_auth();
|
|
@@ -149,6 +265,13 @@ function safePrintLint(warnings, projectRoot) {
|
|
|
149
265
|
if (process.env.LUMERA_DEBUG) console.error("[lint] print failed:", err);
|
|
150
266
|
}
|
|
151
267
|
}
|
|
268
|
+
function lintCollectionFiles(projectRoot, platformDir, filterName) {
|
|
269
|
+
const issues = runLint({ projectRoot, targets: buildCollectionTargets(platformDir, filterName) });
|
|
270
|
+
const errors = issues.filter((issue) => issue.severity === "error");
|
|
271
|
+
if (errors.length === 0) return;
|
|
272
|
+
printLintWarnings(issues, projectRoot);
|
|
273
|
+
throw new Error(`Found ${errors.length} collection lint error(s)`);
|
|
274
|
+
}
|
|
152
275
|
function detectPackageManager() {
|
|
153
276
|
for (const pm of ["bun", "pnpm", "yarn", "npm"]) {
|
|
154
277
|
try {
|
|
@@ -171,6 +294,23 @@ function stripNamespacePrefix(name, appName) {
|
|
|
171
294
|
}
|
|
172
295
|
return name;
|
|
173
296
|
}
|
|
297
|
+
function buildCollectionIdMap(collections, appName) {
|
|
298
|
+
const result = /* @__PURE__ */ new Map();
|
|
299
|
+
const add = (key, id) => {
|
|
300
|
+
const trimmed = (key || "").trim();
|
|
301
|
+
if (trimmed) result.set(trimmed, id);
|
|
302
|
+
};
|
|
303
|
+
for (const collection of collections) {
|
|
304
|
+
add(collection.id, collection.id);
|
|
305
|
+
add(collection.name, collection.id);
|
|
306
|
+
const stripped = stripNamespacePrefix(collection.name, appName);
|
|
307
|
+
add(stripped, collection.id);
|
|
308
|
+
if (collection.name.includes(NAMESPACE_SEPARATOR)) {
|
|
309
|
+
add(collection.name.split(NAMESPACE_SEPARATOR).slice(1).join(NAMESPACE_SEPARATOR), collection.id);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
return result;
|
|
313
|
+
}
|
|
174
314
|
function computeLineDiff(oldText, newText) {
|
|
175
315
|
const oldLines = (oldText || "").trimEnd().split("\n");
|
|
176
316
|
const newLines = (newText || "").trimEnd().split("\n");
|
|
@@ -568,29 +708,29 @@ function parseResource(resourcePath) {
|
|
|
568
708
|
return { type, name };
|
|
569
709
|
}
|
|
570
710
|
function getPlatformDir() {
|
|
571
|
-
if (
|
|
572
|
-
return
|
|
711
|
+
if (existsSync2(join2(process.cwd(), "platform"))) {
|
|
712
|
+
return join2(process.cwd(), "platform");
|
|
573
713
|
}
|
|
574
|
-
if (
|
|
575
|
-
return
|
|
714
|
+
if (existsSync2(join2(process.cwd(), "lumera_platform"))) {
|
|
715
|
+
return join2(process.cwd(), "lumera_platform");
|
|
576
716
|
}
|
|
577
|
-
return
|
|
717
|
+
return join2(process.cwd(), "platform");
|
|
578
718
|
}
|
|
579
719
|
function toSafeFilename(name) {
|
|
580
720
|
return name.replace(/\s+/g, "_").replace(/[^a-zA-Z0-9_-]/g, "").toLowerCase();
|
|
581
721
|
}
|
|
582
722
|
function loadLocalCollections(platformDir, filterName) {
|
|
583
|
-
const collectionsDir =
|
|
584
|
-
if (!
|
|
723
|
+
const collectionsDir = join2(platformDir, "collections");
|
|
724
|
+
if (!existsSync2(collectionsDir)) {
|
|
585
725
|
return [];
|
|
586
726
|
}
|
|
587
727
|
const collections = [];
|
|
588
728
|
const errors = [];
|
|
589
|
-
for (const file of
|
|
729
|
+
for (const file of readdirSync2(collectionsDir)) {
|
|
590
730
|
if (!file.endsWith(".json")) continue;
|
|
591
|
-
const filePath =
|
|
731
|
+
const filePath = join2(collectionsDir, file);
|
|
592
732
|
try {
|
|
593
|
-
const content =
|
|
733
|
+
const content = readFileSync2(filePath, "utf-8");
|
|
594
734
|
const collection = JSON.parse(content);
|
|
595
735
|
if (filterName && collection.name !== filterName && collection.id !== filterName) {
|
|
596
736
|
continue;
|
|
@@ -611,6 +751,27 @@ function loadLocalCollections(platformDir, filterName) {
|
|
|
611
751
|
errors.push(`${file}: collection name "${collection.name}" contains invalid characters`);
|
|
612
752
|
continue;
|
|
613
753
|
}
|
|
754
|
+
if (!Array.isArray(collection.fields)) {
|
|
755
|
+
errors.push(`${file}: missing fields array`);
|
|
756
|
+
continue;
|
|
757
|
+
}
|
|
758
|
+
for (let i = 0; i < collection.fields.length; i++) {
|
|
759
|
+
const field = collection.fields[i];
|
|
760
|
+
if (!field || typeof field !== "object") {
|
|
761
|
+
errors.push(`${file}: field at index ${i} must be an object`);
|
|
762
|
+
continue;
|
|
763
|
+
}
|
|
764
|
+
if (typeof field.name !== "string" || field.name.trim() === "") {
|
|
765
|
+
errors.push(`${file}: field at index ${i} is missing name`);
|
|
766
|
+
}
|
|
767
|
+
if (typeof field.type !== "string" || field.type.trim() === "") {
|
|
768
|
+
errors.push(`${file}: field ${typeof field.name === "string" ? `"${field.name}"` : `at index ${i}`} is missing type`);
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
if (collection.indexes !== void 0 && !Array.isArray(collection.indexes)) {
|
|
772
|
+
errors.push(`${file}: indexes must be an array when present`);
|
|
773
|
+
continue;
|
|
774
|
+
}
|
|
614
775
|
const existingById = collections.find((c) => c.id === collection.id);
|
|
615
776
|
if (existingById) {
|
|
616
777
|
errors.push(`${file}: duplicate collection id "${collection.id}" (also defined in another file)`);
|
|
@@ -636,27 +797,27 @@ function loadLocalCollections(platformDir, filterName) {
|
|
|
636
797
|
return collections;
|
|
637
798
|
}
|
|
638
799
|
function loadLocalAutomations(platformDir, filterName, appName) {
|
|
639
|
-
const automationsDir =
|
|
640
|
-
if (!
|
|
800
|
+
const automationsDir = join2(platformDir, "automations");
|
|
801
|
+
if (!existsSync2(automationsDir)) {
|
|
641
802
|
return [];
|
|
642
803
|
}
|
|
643
804
|
const automations = [];
|
|
644
805
|
const errors = [];
|
|
645
|
-
for (const entry of
|
|
806
|
+
for (const entry of readdirSync2(automationsDir, { withFileTypes: true })) {
|
|
646
807
|
if (!entry.isDirectory()) continue;
|
|
647
|
-
const automationDir =
|
|
648
|
-
const configPath =
|
|
649
|
-
const mainPath =
|
|
650
|
-
if (!
|
|
808
|
+
const automationDir = join2(automationsDir, entry.name);
|
|
809
|
+
const configPath = join2(automationDir, "config.json");
|
|
810
|
+
const mainPath = join2(automationDir, "main.py");
|
|
811
|
+
if (!existsSync2(configPath)) {
|
|
651
812
|
errors.push(`${entry.name}: missing config.json`);
|
|
652
813
|
continue;
|
|
653
814
|
}
|
|
654
|
-
if (!
|
|
815
|
+
if (!existsSync2(mainPath)) {
|
|
655
816
|
errors.push(`${entry.name}: missing main.py`);
|
|
656
817
|
continue;
|
|
657
818
|
}
|
|
658
819
|
try {
|
|
659
|
-
const configContent =
|
|
820
|
+
const configContent = readFileSync2(configPath, "utf-8");
|
|
660
821
|
const rawConfig = JSON.parse(configContent);
|
|
661
822
|
if (filterName && rawConfig.external_id !== filterName && rawConfig.name !== filterName && entry.name !== filterName) {
|
|
662
823
|
continue;
|
|
@@ -683,7 +844,7 @@ function loadLocalAutomations(platformDir, filterName, appName) {
|
|
|
683
844
|
errors.push(`${entry.name}: duplicate external_id "${config.external_id}" (also defined in ${existingByExtId.automation.name})`);
|
|
684
845
|
continue;
|
|
685
846
|
}
|
|
686
|
-
let code =
|
|
847
|
+
let code = readFileSync2(mainPath, "utf-8");
|
|
687
848
|
if (appName) {
|
|
688
849
|
code = code.replaceAll("{{app}}", appName);
|
|
689
850
|
}
|
|
@@ -702,15 +863,15 @@ function loadLocalAutomations(platformDir, filterName, appName) {
|
|
|
702
863
|
return automations;
|
|
703
864
|
}
|
|
704
865
|
function loadLocalHooks(platformDir, filterName, appName) {
|
|
705
|
-
const hooksDir =
|
|
706
|
-
if (!
|
|
866
|
+
const hooksDir = join2(platformDir, "hooks");
|
|
867
|
+
if (!existsSync2(hooksDir)) {
|
|
707
868
|
return [];
|
|
708
869
|
}
|
|
709
870
|
const hooks = [];
|
|
710
|
-
for (const file of
|
|
871
|
+
for (const file of readdirSync2(hooksDir)) {
|
|
711
872
|
if (!file.endsWith(".js") && !file.endsWith(".ts")) continue;
|
|
712
|
-
const filePath =
|
|
713
|
-
const content =
|
|
873
|
+
const filePath = join2(hooksDir, file);
|
|
874
|
+
const content = readFileSync2(filePath, "utf-8");
|
|
714
875
|
const config = parseHookConfig(content);
|
|
715
876
|
if (!config) {
|
|
716
877
|
console.log(pc2.yellow(` \u26A0 Skipping ${file}: could not parse config export`));
|
|
@@ -830,6 +991,16 @@ function mapFieldType(type) {
|
|
|
830
991
|
};
|
|
831
992
|
return typeMap[type] || type;
|
|
832
993
|
}
|
|
994
|
+
function normalizeConstraintValue(fieldType, value) {
|
|
995
|
+
if (value === void 0 || value === null) return void 0;
|
|
996
|
+
if (["text", "email", "url", "editor"].includes(fieldType) && value === 0) {
|
|
997
|
+
return void 0;
|
|
998
|
+
}
|
|
999
|
+
if (fieldType === "date" && value === "") {
|
|
1000
|
+
return void 0;
|
|
1001
|
+
}
|
|
1002
|
+
return value;
|
|
1003
|
+
}
|
|
833
1004
|
function fieldsDiffer(local, remote) {
|
|
834
1005
|
if (mapFieldType(local.type) !== remote.type) return true;
|
|
835
1006
|
if ((local.required || false) !== (remote.required || false)) return true;
|
|
@@ -858,11 +1029,11 @@ function fieldsDiffer(local, remote) {
|
|
|
858
1029
|
const remoteDecimalPrecision = opts.decimalPrecision !== void 0 ? opts.decimalPrecision : 2;
|
|
859
1030
|
if (localDecimalPrecision !== remoteDecimalPrecision) return true;
|
|
860
1031
|
}
|
|
861
|
-
const localMin = local.
|
|
862
|
-
const remoteMin =
|
|
1032
|
+
const localMin = normalizeConstraintValue(local.type, local.min);
|
|
1033
|
+
const remoteMin = normalizeConstraintValue(local.type, opts.min);
|
|
863
1034
|
if (localMin !== remoteMin) return true;
|
|
864
|
-
const localMax = local.
|
|
865
|
-
const remoteMax =
|
|
1035
|
+
const localMax = normalizeConstraintValue(local.type, local.max);
|
|
1036
|
+
const remoteMax = normalizeConstraintValue(local.type, opts.max);
|
|
866
1037
|
if (localMax !== remoteMax) return true;
|
|
867
1038
|
return false;
|
|
868
1039
|
}
|
|
@@ -956,9 +1127,12 @@ async function planCollections(api, localCollections) {
|
|
|
956
1127
|
}
|
|
957
1128
|
return changes;
|
|
958
1129
|
}
|
|
959
|
-
async function planAutomations(api, localAutomations) {
|
|
1130
|
+
async function planAutomations(api, localAutomations, projectId) {
|
|
960
1131
|
const changes = [];
|
|
961
|
-
const remoteAutomations = await api.listAutomations({
|
|
1132
|
+
const remoteAutomations = await api.listAutomations({
|
|
1133
|
+
include_code: true,
|
|
1134
|
+
...projectId ? { project_id: projectId } : {}
|
|
1135
|
+
});
|
|
962
1136
|
const remoteByExternalId = new Map(remoteAutomations.filter((a) => a.external_id).map((a) => [a.external_id, a]));
|
|
963
1137
|
const remotePresetCache = /* @__PURE__ */ new Map();
|
|
964
1138
|
const getRemotePresets = (automationId) => {
|
|
@@ -1026,12 +1200,12 @@ async function planAutomations(api, localAutomations) {
|
|
|
1026
1200
|
return changes;
|
|
1027
1201
|
}
|
|
1028
1202
|
function loadLocalMailboxes(platformDir, filterName) {
|
|
1029
|
-
const mailboxesDir =
|
|
1030
|
-
if (!
|
|
1203
|
+
const mailboxesDir = join2(platformDir, "mailboxes");
|
|
1204
|
+
if (!existsSync2(mailboxesDir)) {
|
|
1031
1205
|
return [];
|
|
1032
1206
|
}
|
|
1033
1207
|
const mailboxes = [];
|
|
1034
|
-
for (const file of
|
|
1208
|
+
for (const file of readdirSync2(mailboxesDir)) {
|
|
1035
1209
|
if (!file.endsWith(".json")) continue;
|
|
1036
1210
|
const slug = file.replace(/\.json$/, "").trim();
|
|
1037
1211
|
if (!slug) {
|
|
@@ -1041,10 +1215,10 @@ function loadLocalMailboxes(platformDir, filterName) {
|
|
|
1041
1215
|
if (filterName && slug !== filterName) {
|
|
1042
1216
|
continue;
|
|
1043
1217
|
}
|
|
1044
|
-
const filePath =
|
|
1218
|
+
const filePath = join2(mailboxesDir, file);
|
|
1045
1219
|
let raw;
|
|
1046
1220
|
try {
|
|
1047
|
-
raw = JSON.parse(
|
|
1221
|
+
raw = JSON.parse(readFileSync2(filePath, "utf-8"));
|
|
1048
1222
|
} catch (e) {
|
|
1049
1223
|
console.log(pc2.yellow(` \u26A0 Skipping ${file}: invalid JSON (${e.message})`));
|
|
1050
1224
|
continue;
|
|
@@ -1123,8 +1297,8 @@ async function pullMailboxes(api, platformDir, filterName) {
|
|
|
1123
1297
|
console.log(pc2.dim(" (no mailboxes)"));
|
|
1124
1298
|
return;
|
|
1125
1299
|
}
|
|
1126
|
-
const outDir =
|
|
1127
|
-
if (!
|
|
1300
|
+
const outDir = join2(platformDir, "mailboxes");
|
|
1301
|
+
if (!existsSync2(outDir)) {
|
|
1128
1302
|
mkdirSync(outDir, { recursive: true });
|
|
1129
1303
|
}
|
|
1130
1304
|
let count = 0;
|
|
@@ -1132,7 +1306,7 @@ async function pullMailboxes(api, platformDir, filterName) {
|
|
|
1132
1306
|
if (filterName && mb.slug !== filterName) continue;
|
|
1133
1307
|
const body = {};
|
|
1134
1308
|
if (mb.description) body.description = mb.description;
|
|
1135
|
-
const file =
|
|
1309
|
+
const file = join2(outDir, `${toSafeFilename(mb.slug)}.json`);
|
|
1136
1310
|
writeFileSync(file, JSON.stringify(body, null, 2) + "\n");
|
|
1137
1311
|
console.log(pc2.green(" \u2713"), `pulled mailbox ${mb.slug} ${pc2.dim(`\u2192 ${file}`)}`);
|
|
1138
1312
|
count++;
|
|
@@ -1141,9 +1315,9 @@ async function pullMailboxes(api, platformDir, filterName) {
|
|
|
1141
1315
|
console.log(pc2.yellow(` \u26A0 mailbox '${filterName}' not found on remote`));
|
|
1142
1316
|
}
|
|
1143
1317
|
}
|
|
1144
|
-
async function planHooks(api, localHooks, collections) {
|
|
1318
|
+
async function planHooks(api, localHooks, collections, projectId) {
|
|
1145
1319
|
const changes = [];
|
|
1146
|
-
const remoteHooks = await api.listHooks();
|
|
1320
|
+
const remoteHooks = await api.listHooks(projectId ? { project_id: projectId } : void 0);
|
|
1147
1321
|
const remoteByExternalId = new Map(remoteHooks.filter((h) => h.external_id).map((h) => [h.external_id, h]));
|
|
1148
1322
|
for (const { hook, script, fileName } of localHooks) {
|
|
1149
1323
|
const remote = remoteByExternalId.get(hook.external_id);
|
|
@@ -1231,7 +1405,7 @@ async function applyCollections(api, localCollections) {
|
|
|
1231
1405
|
}
|
|
1232
1406
|
async function applyAutomations(api, localAutomations, projectId) {
|
|
1233
1407
|
let errors = 0;
|
|
1234
|
-
const remoteAutomations = await api.listAutomations();
|
|
1408
|
+
const remoteAutomations = await api.listAutomations(projectId ? { project_id: projectId } : void 0);
|
|
1235
1409
|
const remoteByExternalId = new Map(remoteAutomations.filter((a) => a.external_id).map((a) => [a.external_id, a]));
|
|
1236
1410
|
for (const { automation, code } of localAutomations) {
|
|
1237
1411
|
const remote = remoteByExternalId.get(automation.external_id);
|
|
@@ -1343,7 +1517,7 @@ async function setSchedule(api, automationId, schedule, localPresets) {
|
|
|
1343
1517
|
}
|
|
1344
1518
|
async function applyHooks(api, localHooks, collections, projectId) {
|
|
1345
1519
|
let errors = 0;
|
|
1346
|
-
const remoteHooks = await api.listHooks();
|
|
1520
|
+
const remoteHooks = await api.listHooks(projectId ? { project_id: projectId } : void 0);
|
|
1347
1521
|
const remoteByExternalId = new Map(remoteHooks.filter((h) => h.external_id).map((h) => [h.external_id, h]));
|
|
1348
1522
|
for (const { hook, script, fileName } of localHooks) {
|
|
1349
1523
|
const remote = remoteByExternalId.get(hook.external_id);
|
|
@@ -1401,7 +1575,7 @@ async function applyApp(args) {
|
|
|
1401
1575
|
await deploy({ token, appName, appTitle, distDir, apiUrl });
|
|
1402
1576
|
}
|
|
1403
1577
|
async function pullCollections(api, platformDir, filterName, appName) {
|
|
1404
|
-
const collectionsDir =
|
|
1578
|
+
const collectionsDir = join2(platformDir, "collections");
|
|
1405
1579
|
mkdirSync(collectionsDir, { recursive: true });
|
|
1406
1580
|
const collections = await api.listCollections();
|
|
1407
1581
|
for (const collection of collections) {
|
|
@@ -1445,15 +1619,18 @@ async function pullCollections(api, platformDir, filterName, appName) {
|
|
|
1445
1619
|
}).filter((idx) => idx !== null)
|
|
1446
1620
|
};
|
|
1447
1621
|
const fileName = toSafeFilename(localName);
|
|
1448
|
-
const filePath =
|
|
1622
|
+
const filePath = join2(collectionsDir, `${fileName}.json`);
|
|
1449
1623
|
writeFileSync(filePath, JSON.stringify(localFormat, null, 2) + "\n");
|
|
1450
1624
|
console.log(pc2.green(" \u2713"), `${localName} \u2192 collections/${fileName}.json`);
|
|
1451
1625
|
}
|
|
1452
1626
|
}
|
|
1453
1627
|
async function pullAutomations(api, platformDir, filterName, projectId) {
|
|
1454
|
-
const automationsDir =
|
|
1628
|
+
const automationsDir = join2(platformDir, "automations");
|
|
1455
1629
|
mkdirSync(automationsDir, { recursive: true });
|
|
1456
|
-
const automations = await api.listAutomations({
|
|
1630
|
+
const automations = await api.listAutomations({
|
|
1631
|
+
include_code: true,
|
|
1632
|
+
...projectId ? { project_id: projectId } : {}
|
|
1633
|
+
});
|
|
1457
1634
|
for (const automation of automations) {
|
|
1458
1635
|
if (!automation.external_id || automation.managed) continue;
|
|
1459
1636
|
if (projectId && automation.project_id && automation.project_id !== projectId) continue;
|
|
@@ -1462,7 +1639,7 @@ async function pullAutomations(api, platformDir, filterName, projectId) {
|
|
|
1462
1639
|
continue;
|
|
1463
1640
|
}
|
|
1464
1641
|
const dirName = automation.external_id.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
1465
|
-
const automationDir =
|
|
1642
|
+
const automationDir = join2(automationsDir, dirName);
|
|
1466
1643
|
mkdirSync(automationDir, { recursive: true });
|
|
1467
1644
|
const config = {
|
|
1468
1645
|
external_id: automation.external_id,
|
|
@@ -1495,15 +1672,15 @@ async function pullAutomations(api, platformDir, filterName, projectId) {
|
|
|
1495
1672
|
}
|
|
1496
1673
|
} catch {
|
|
1497
1674
|
}
|
|
1498
|
-
writeFileSync(
|
|
1499
|
-
writeFileSync(
|
|
1675
|
+
writeFileSync(join2(automationDir, "config.json"), JSON.stringify(config, null, 2) + "\n");
|
|
1676
|
+
writeFileSync(join2(automationDir, "main.py"), automation.code || "");
|
|
1500
1677
|
console.log(pc2.green(" \u2713"), `${automation.name} \u2192 automations/${dirName}/`);
|
|
1501
1678
|
}
|
|
1502
1679
|
}
|
|
1503
1680
|
async function pullHooks(api, platformDir, filterName, appName, projectId) {
|
|
1504
|
-
const hooksDir =
|
|
1681
|
+
const hooksDir = join2(platformDir, "hooks");
|
|
1505
1682
|
mkdirSync(hooksDir, { recursive: true });
|
|
1506
|
-
const hooks = await api.listHooks();
|
|
1683
|
+
const hooks = await api.listHooks(projectId ? { project_id: projectId } : void 0);
|
|
1507
1684
|
for (const hook of hooks) {
|
|
1508
1685
|
if (!hook.external_id) continue;
|
|
1509
1686
|
if (projectId && hook.project_id && hook.project_id !== projectId) continue;
|
|
@@ -1523,31 +1700,31 @@ export default async function handler({ record, app, http }) {
|
|
|
1523
1700
|
${hook.script.split("\n").map((line) => " " + line).join("\n")}
|
|
1524
1701
|
}
|
|
1525
1702
|
`;
|
|
1526
|
-
writeFileSync(
|
|
1703
|
+
writeFileSync(join2(hooksDir, fileName), content);
|
|
1527
1704
|
console.log(pc2.green(" \u2713"), `${hook.name} \u2192 hooks/${fileName}`);
|
|
1528
1705
|
}
|
|
1529
1706
|
}
|
|
1530
1707
|
function loadLocalAgents(platformDir, filterName, appName) {
|
|
1531
|
-
const agentsDir =
|
|
1532
|
-
if (!
|
|
1708
|
+
const agentsDir = join2(platformDir, "agents");
|
|
1709
|
+
if (!existsSync2(agentsDir)) return [];
|
|
1533
1710
|
const agents = [];
|
|
1534
1711
|
const errors = [];
|
|
1535
|
-
for (const entry of
|
|
1712
|
+
for (const entry of readdirSync2(agentsDir, { withFileTypes: true })) {
|
|
1536
1713
|
if (!entry.isDirectory()) continue;
|
|
1537
|
-
const agentDir =
|
|
1538
|
-
const configPath =
|
|
1539
|
-
const promptPath =
|
|
1540
|
-
const policyPath =
|
|
1541
|
-
if (!
|
|
1714
|
+
const agentDir = join2(agentsDir, entry.name);
|
|
1715
|
+
const configPath = join2(agentDir, "config.json");
|
|
1716
|
+
const promptPath = join2(agentDir, "system_prompt.md");
|
|
1717
|
+
const policyPath = join2(agentDir, "policy.js");
|
|
1718
|
+
if (!existsSync2(configPath)) {
|
|
1542
1719
|
errors.push(`${entry.name}: missing config.json`);
|
|
1543
1720
|
continue;
|
|
1544
1721
|
}
|
|
1545
|
-
if (!
|
|
1722
|
+
if (!existsSync2(promptPath)) {
|
|
1546
1723
|
errors.push(`${entry.name}: missing system_prompt.md`);
|
|
1547
1724
|
continue;
|
|
1548
1725
|
}
|
|
1549
1726
|
try {
|
|
1550
|
-
const config = JSON.parse(
|
|
1727
|
+
const config = JSON.parse(readFileSync2(configPath, "utf-8"));
|
|
1551
1728
|
if (filterName && config.external_id !== filterName && config.name !== filterName && entry.name !== filterName) {
|
|
1552
1729
|
continue;
|
|
1553
1730
|
}
|
|
@@ -1570,8 +1747,8 @@ function loadLocalAgents(platformDir, filterName, appName) {
|
|
|
1570
1747
|
errors.push(`${entry.name}: missing name in config.json`);
|
|
1571
1748
|
continue;
|
|
1572
1749
|
}
|
|
1573
|
-
let systemPrompt =
|
|
1574
|
-
let policyScript =
|
|
1750
|
+
let systemPrompt = readFileSync2(promptPath, "utf-8");
|
|
1751
|
+
let policyScript = existsSync2(policyPath) ? readFileSync2(policyPath, "utf-8") : "";
|
|
1575
1752
|
if (appName) {
|
|
1576
1753
|
systemPrompt = systemPrompt.replaceAll("{{app}}", appName);
|
|
1577
1754
|
policyScript = policyScript.replaceAll("{{app}}", appName);
|
|
@@ -1701,7 +1878,7 @@ async function applyAgents(api, localAgents, projectId) {
|
|
|
1701
1878
|
return errors;
|
|
1702
1879
|
}
|
|
1703
1880
|
async function pullAgents(api, platformDir, filterName, projectId) {
|
|
1704
|
-
const agentsDir =
|
|
1881
|
+
const agentsDir = join2(platformDir, "agents");
|
|
1705
1882
|
mkdirSync(agentsDir, { recursive: true });
|
|
1706
1883
|
const agents = await api.listAgents(projectId ? { project_id: projectId } : void 0);
|
|
1707
1884
|
let skillIdToSlug = /* @__PURE__ */ new Map();
|
|
@@ -1716,7 +1893,7 @@ async function pullAgents(api, platformDir, filterName, projectId) {
|
|
|
1716
1893
|
continue;
|
|
1717
1894
|
}
|
|
1718
1895
|
const dirName = agent.external_id.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
1719
|
-
const agentDir =
|
|
1896
|
+
const agentDir = join2(agentsDir, dirName);
|
|
1720
1897
|
mkdirSync(agentDir, { recursive: true });
|
|
1721
1898
|
const skillSlugs = [];
|
|
1722
1899
|
if (agent.skill_ids) {
|
|
@@ -1733,10 +1910,10 @@ async function pullAgents(api, platformDir, filterName, projectId) {
|
|
|
1733
1910
|
if (agent.model) config.model = agent.model;
|
|
1734
1911
|
if (skillSlugs.length > 0) config.skills = skillSlugs;
|
|
1735
1912
|
if (agent.policy_enabled) config.policy_enabled = true;
|
|
1736
|
-
writeFileSync(
|
|
1737
|
-
writeFileSync(
|
|
1913
|
+
writeFileSync(join2(agentDir, "config.json"), JSON.stringify(config, null, 2) + "\n");
|
|
1914
|
+
writeFileSync(join2(agentDir, "system_prompt.md"), agent.system_prompt || "");
|
|
1738
1915
|
if (agent.policy_script) {
|
|
1739
|
-
writeFileSync(
|
|
1916
|
+
writeFileSync(join2(agentDir, "policy.js"), agent.policy_script);
|
|
1740
1917
|
}
|
|
1741
1918
|
console.log(pc2.green(" \u2713"), `${agent.name} \u2192 agents/${dirName}/`);
|
|
1742
1919
|
}
|
|
@@ -1781,7 +1958,10 @@ async function listResources(api, platformDir, filterType, appName, projectId) {
|
|
|
1781
1958
|
}
|
|
1782
1959
|
if (!filterType || filterType === "automations") {
|
|
1783
1960
|
const localAutomations = loadLocalAutomations(platformDir, void 0, appName);
|
|
1784
|
-
const remoteAutomations = await api.listAutomations({
|
|
1961
|
+
const remoteAutomations = await api.listAutomations({
|
|
1962
|
+
include_code: true,
|
|
1963
|
+
...projectId ? { project_id: projectId } : {}
|
|
1964
|
+
});
|
|
1785
1965
|
const remoteByExternalId = new Map(remoteAutomations.filter((a) => a.external_id && !a.managed).map((a) => [a.external_id, a]));
|
|
1786
1966
|
const localIds = new Set(localAutomations.map((a) => a.automation.external_id));
|
|
1787
1967
|
for (const { automation, code } of localAutomations) {
|
|
@@ -1807,7 +1987,7 @@ async function listResources(api, platformDir, filterType, appName, projectId) {
|
|
|
1807
1987
|
}
|
|
1808
1988
|
if (!filterType || filterType === "hooks") {
|
|
1809
1989
|
const localHooks = loadLocalHooks(platformDir, void 0, appName);
|
|
1810
|
-
const remoteHooks = await api.listHooks();
|
|
1990
|
+
const remoteHooks = await api.listHooks(projectId ? { project_id: projectId } : void 0);
|
|
1811
1991
|
const remoteByExternalId = new Map(remoteHooks.filter((h) => h.external_id).map((h) => [h.external_id, h]));
|
|
1812
1992
|
const localIds = new Set(localHooks.map((h) => h.hook.external_id));
|
|
1813
1993
|
for (const { hook, script } of localHooks) {
|
|
@@ -1967,7 +2147,7 @@ async function destroyResources(api, platformDir, resourceType, resourceName, sk
|
|
|
1967
2147
|
if (!resourceType || resourceType === "automations") {
|
|
1968
2148
|
try {
|
|
1969
2149
|
const localAutomations = loadLocalAutomations(platformDir, resourceName || void 0, appName);
|
|
1970
|
-
const remoteAutomations = await api.listAutomations();
|
|
2150
|
+
const remoteAutomations = await api.listAutomations(projectId ? { project_id: projectId } : void 0);
|
|
1971
2151
|
const remoteByExternalId = new Map(remoteAutomations.filter((a) => a.external_id).map((a) => [a.external_id, a]));
|
|
1972
2152
|
for (const { automation } of localAutomations) {
|
|
1973
2153
|
const remote = remoteByExternalId.get(automation.external_id);
|
|
@@ -1980,7 +2160,7 @@ async function destroyResources(api, platformDir, resourceType, resourceName, sk
|
|
|
1980
2160
|
}
|
|
1981
2161
|
if (!resourceType || resourceType === "hooks") {
|
|
1982
2162
|
const localHooks = loadLocalHooks(platformDir, resourceName || void 0, appName);
|
|
1983
|
-
const remoteHooks = await api.listHooks();
|
|
2163
|
+
const remoteHooks = await api.listHooks(projectId ? { project_id: projectId } : void 0);
|
|
1984
2164
|
const remoteByExternalId = new Map(remoteHooks.filter((h) => h.external_id).map((h) => [h.external_id, h]));
|
|
1985
2165
|
for (const { hook } of localHooks) {
|
|
1986
2166
|
const remote = remoteByExternalId.get(hook.external_id);
|
|
@@ -2221,7 +2401,7 @@ async function showResource(api, platformDir, resourceType, resourceName, appNam
|
|
|
2221
2401
|
console.log();
|
|
2222
2402
|
} else if (resourceType === "automations") {
|
|
2223
2403
|
const localAutomations = loadLocalAutomations(platformDir, resourceName, appName);
|
|
2224
|
-
const remoteAutomations = await api.listAutomations();
|
|
2404
|
+
const remoteAutomations = await api.listAutomations(projectId ? { project_id: projectId } : void 0);
|
|
2225
2405
|
const local = localAutomations[0];
|
|
2226
2406
|
const remote = remoteAutomations.find((a) => a.external_id === resourceName || a.name === resourceName);
|
|
2227
2407
|
if (!local && !remote) {
|
|
@@ -2244,7 +2424,7 @@ async function showResource(api, platformDir, resourceType, resourceName, appNam
|
|
|
2244
2424
|
console.log();
|
|
2245
2425
|
} else if (resourceType === "hooks") {
|
|
2246
2426
|
const localHooks = loadLocalHooks(platformDir, resourceName, appName);
|
|
2247
|
-
const remoteHooks = await api.listHooks();
|
|
2427
|
+
const remoteHooks = await api.listHooks(projectId ? { project_id: projectId } : void 0);
|
|
2248
2428
|
const local = localHooks[0];
|
|
2249
2429
|
const remote = remoteHooks.find((h) => h.external_id === resourceName);
|
|
2250
2430
|
if (!local && !remote) {
|
|
@@ -2374,12 +2554,15 @@ async function plan(args) {
|
|
|
2374
2554
|
console.log(pc2.cyan(pc2.bold(" Plan")));
|
|
2375
2555
|
console.log(pc2.dim(" Comparing local files to remote state..."));
|
|
2376
2556
|
console.log();
|
|
2377
|
-
|
|
2557
|
+
if (!type || type === "collections") {
|
|
2558
|
+
lintCollectionFiles(projectRoot, platformDir, name || void 0);
|
|
2559
|
+
}
|
|
2560
|
+
await syncDeps(projectRoot, { ignorePermissionDenied: true });
|
|
2378
2561
|
const allChanges = [];
|
|
2379
2562
|
let collections;
|
|
2380
2563
|
try {
|
|
2381
2564
|
const remoteCollections = await api.listCollections();
|
|
2382
|
-
collections =
|
|
2565
|
+
collections = buildCollectionIdMap(remoteCollections, appName);
|
|
2383
2566
|
} catch {
|
|
2384
2567
|
collections = /* @__PURE__ */ new Map();
|
|
2385
2568
|
}
|
|
@@ -2394,14 +2577,14 @@ async function plan(args) {
|
|
|
2394
2577
|
if (!type || type === "automations") {
|
|
2395
2578
|
localAutomations = loadLocalAutomations(platformDir, name || void 0, appName);
|
|
2396
2579
|
if (localAutomations.length > 0) {
|
|
2397
|
-
const changes = await planAutomations(api, localAutomations);
|
|
2580
|
+
const changes = await planAutomations(api, localAutomations, projectId);
|
|
2398
2581
|
allChanges.push(...changes);
|
|
2399
2582
|
}
|
|
2400
2583
|
}
|
|
2401
2584
|
if (!type || type === "hooks") {
|
|
2402
2585
|
const localHooks = loadLocalHooks(platformDir, name || void 0, appName);
|
|
2403
2586
|
if (localHooks.length > 0) {
|
|
2404
|
-
const changes = await planHooks(api, localHooks, collections);
|
|
2587
|
+
const changes = await planHooks(api, localHooks, collections, projectId);
|
|
2405
2588
|
allChanges.push(...changes);
|
|
2406
2589
|
}
|
|
2407
2590
|
}
|
|
@@ -2509,12 +2692,14 @@ async function apply(args) {
|
|
|
2509
2692
|
console.log();
|
|
2510
2693
|
return;
|
|
2511
2694
|
}
|
|
2512
|
-
|
|
2695
|
+
if (!type || type === "collections") {
|
|
2696
|
+
lintCollectionFiles(projectRoot, platformDir, name || void 0);
|
|
2697
|
+
}
|
|
2698
|
+
await syncDeps(projectRoot, { ignorePermissionDenied: true });
|
|
2513
2699
|
let collections;
|
|
2514
2700
|
try {
|
|
2515
2701
|
const remoteCollections = await api.listCollections();
|
|
2516
|
-
collections =
|
|
2517
|
-
for (const c of remoteCollections) collections.set(c.id, c.id);
|
|
2702
|
+
collections = buildCollectionIdMap(remoteCollections, appName);
|
|
2518
2703
|
} catch {
|
|
2519
2704
|
collections = /* @__PURE__ */ new Map();
|
|
2520
2705
|
}
|
|
@@ -2525,8 +2710,8 @@ async function apply(args) {
|
|
|
2525
2710
|
const localAgents = !type || type === "agents" ? loadLocalAgents(platformDir, name || void 0, appName) : [];
|
|
2526
2711
|
const localMailboxes = !type || type === "mailboxes" ? loadLocalMailboxes(platformDir, name || void 0) : [];
|
|
2527
2712
|
if (localCollections.length > 0) allChanges.push(...await planCollections(api, localCollections));
|
|
2528
|
-
if (localAutomations.length > 0) allChanges.push(...await planAutomations(api, localAutomations));
|
|
2529
|
-
if (localHooks.length > 0) allChanges.push(...await planHooks(api, localHooks, collections));
|
|
2713
|
+
if (localAutomations.length > 0) allChanges.push(...await planAutomations(api, localAutomations, projectId));
|
|
2714
|
+
if (localHooks.length > 0) allChanges.push(...await planHooks(api, localHooks, collections, projectId));
|
|
2530
2715
|
if (localAgents.length > 0) allChanges.push(...await planAgents(api, localAgents, projectId));
|
|
2531
2716
|
if (localMailboxes.length > 0) allChanges.push(...await planMailboxes(api, localMailboxes));
|
|
2532
2717
|
if (name) {
|
|
@@ -2540,7 +2725,7 @@ async function apply(args) {
|
|
|
2540
2725
|
let willDeployApp = false;
|
|
2541
2726
|
if (!type) {
|
|
2542
2727
|
try {
|
|
2543
|
-
if (
|
|
2728
|
+
if (existsSync2(join2(projectRoot, "dist")) || existsSync2(join2(projectRoot, "src"))) {
|
|
2544
2729
|
willDeployApp = true;
|
|
2545
2730
|
}
|
|
2546
2731
|
} catch {
|
|
@@ -2604,8 +2789,7 @@ async function apply(args) {
|
|
|
2604
2789
|
}
|
|
2605
2790
|
try {
|
|
2606
2791
|
const remoteCollections = await api.listCollections();
|
|
2607
|
-
collections =
|
|
2608
|
-
for (const c of remoteCollections) collections.set(c.id, c.id);
|
|
2792
|
+
collections = buildCollectionIdMap(remoteCollections, appName);
|
|
2609
2793
|
} catch {
|
|
2610
2794
|
}
|
|
2611
2795
|
if (localAutomations.length > 0) {
|
|
@@ -2666,7 +2850,7 @@ async function pull(args) {
|
|
|
2666
2850
|
let collections;
|
|
2667
2851
|
try {
|
|
2668
2852
|
const remoteCollections = await api.listCollections();
|
|
2669
|
-
collections =
|
|
2853
|
+
collections = buildCollectionIdMap(remoteCollections, appName);
|
|
2670
2854
|
} catch {
|
|
2671
2855
|
collections = /* @__PURE__ */ new Map();
|
|
2672
2856
|
}
|
|
@@ -2682,7 +2866,7 @@ async function pull(args) {
|
|
|
2682
2866
|
if (!type || type === "automations") {
|
|
2683
2867
|
const localAutomations = loadLocalAutomations(platformDir, name || void 0, appName);
|
|
2684
2868
|
if (localAutomations.length > 0) {
|
|
2685
|
-
const changes = await planAutomations(api, localAutomations);
|
|
2869
|
+
const changes = await planAutomations(api, localAutomations, projectId);
|
|
2686
2870
|
for (const c of changes) {
|
|
2687
2871
|
if (c.type === "update") conflicts.push(`automations/${c.name}`);
|
|
2688
2872
|
}
|
|
@@ -2691,7 +2875,7 @@ async function pull(args) {
|
|
|
2691
2875
|
if (!type || type === "hooks") {
|
|
2692
2876
|
const localHooks = loadLocalHooks(platformDir, name || void 0, appName);
|
|
2693
2877
|
if (localHooks.length > 0) {
|
|
2694
|
-
const changes = await planHooks(api, localHooks, collections);
|
|
2878
|
+
const changes = await planHooks(api, localHooks, collections, projectId);
|
|
2695
2879
|
for (const c of changes) {
|
|
2696
2880
|
if (c.type === "update") conflicts.push(`hooks/${c.name}`);
|
|
2697
2881
|
}
|
|
@@ -2720,7 +2904,7 @@ async function pull(args) {
|
|
|
2720
2904
|
console.log();
|
|
2721
2905
|
if (!name && await projectResourceDepsEnabled(projectRoot)) {
|
|
2722
2906
|
console.log(pc2.bold(" Resource shares:"));
|
|
2723
|
-
await syncDeps(projectRoot, { write: true, legacyWhenDisabled: false });
|
|
2907
|
+
await syncDeps(projectRoot, { write: true, legacyWhenDisabled: false, ignorePermissionDenied: true });
|
|
2724
2908
|
console.log();
|
|
2725
2909
|
}
|
|
2726
2910
|
}
|
|
@@ -2970,7 +3154,10 @@ async function diff(args) {
|
|
|
2970
3154
|
}
|
|
2971
3155
|
} else if (type === "automations") {
|
|
2972
3156
|
const localAutomations = loadLocalAutomations(platformDir, name, appName);
|
|
2973
|
-
const remoteAutomations = await api.listAutomations({
|
|
3157
|
+
const remoteAutomations = await api.listAutomations({
|
|
3158
|
+
include_code: true,
|
|
3159
|
+
...projectId ? { project_id: projectId } : {}
|
|
3160
|
+
});
|
|
2974
3161
|
const local = localAutomations[0];
|
|
2975
3162
|
const localExtId = local?.automation.external_id;
|
|
2976
3163
|
const remote = remoteAutomations.find(
|
|
@@ -3000,7 +3187,7 @@ async function diff(args) {
|
|
|
3000
3187
|
}
|
|
3001
3188
|
} else if (type === "hooks") {
|
|
3002
3189
|
const localHooks = loadLocalHooks(platformDir, name, appName);
|
|
3003
|
-
const remoteHooks = await api.listHooks();
|
|
3190
|
+
const remoteHooks = await api.listHooks(projectId ? { project_id: projectId } : void 0);
|
|
3004
3191
|
const local = localHooks[0];
|
|
3005
3192
|
const localExtId = local?.hook.external_id;
|
|
3006
3193
|
const remote = remoteHooks.find(
|