create-mercato-app 0.5.1-develop.2691.d8a0934b37 → 0.5.1-develop.2699.f8b50c8046
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/index.js +243 -102
- package/dist/lib/templates/modules-ts.template +29 -0
- package/package.json +1 -1
- package/template/src/app/page.tsx +5 -3
- package/template/src/modules/example/api/todos/route.ts +26 -19
- package/template/src/modules/example/commands/todos.ts +1 -1
- package/template/src/modules/example/data/entities.ts +1 -1
- package/template/src/modules/example_customers_sync/api/example-customers-sync/mappings/route.ts +18 -13
- package/template/src/modules/example_customers_sync/data/entities.ts +1 -1
- package/template/src/modules/example_customers_sync/lib/mappings.ts +1 -1
- package/template/src/modules/example_customers_sync/lib/sync.ts +17 -14
package/dist/index.js
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
|
|
4
4
|
// src/index.ts
|
|
5
5
|
import { createInterface } from "node:readline";
|
|
6
|
-
import { basename as basename2, dirname as
|
|
7
|
-
import { existsSync as existsSync6, mkdirSync as mkdirSync6, readdirSync as readdirSync3, readFileSync as
|
|
8
|
-
import { fileURLToPath as
|
|
6
|
+
import { basename as basename2, dirname as dirname6, join as join7, resolve } from "node:path";
|
|
7
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync6, readdirSync as readdirSync3, readFileSync as readFileSync6, statSync as statSync2, writeFileSync as writeFileSync7, copyFileSync as copyFileSync5 } from "node:fs";
|
|
8
|
+
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
9
9
|
import pc from "picocolors";
|
|
10
10
|
|
|
11
11
|
// src/lib/ready-apps.ts
|
|
@@ -308,138 +308,262 @@ async function extractTarballSnapshot(archive, targetDir) {
|
|
|
308
308
|
}
|
|
309
309
|
}
|
|
310
310
|
|
|
311
|
+
// src/lib/apply-starter-preset.ts
|
|
312
|
+
import { readFileSync, writeFileSync as writeFileSync2, rmSync as rmSync2 } from "node:fs";
|
|
313
|
+
import { join as join2, dirname } from "node:path";
|
|
314
|
+
import { fileURLToPath } from "node:url";
|
|
315
|
+
|
|
316
|
+
// src/lib/starter-presets.ts
|
|
317
|
+
var CORE = "@open-mercato/core";
|
|
318
|
+
var EMPTY_MODULES = [
|
|
319
|
+
{ id: "auth", from: CORE },
|
|
320
|
+
{ id: "directory", from: CORE },
|
|
321
|
+
{ id: "configs", from: CORE },
|
|
322
|
+
{ id: "entities", from: CORE },
|
|
323
|
+
{ id: "query_index", from: CORE },
|
|
324
|
+
{ id: "api_docs", from: CORE }
|
|
325
|
+
];
|
|
326
|
+
var STARTER_PRESETS = {
|
|
327
|
+
classic: {
|
|
328
|
+
id: "classic",
|
|
329
|
+
label: "Classic",
|
|
330
|
+
description: "Current full starter behavior",
|
|
331
|
+
modules: { mode: "replace", enabled: [] },
|
|
332
|
+
ui: { startPageVariant: "classic", hideDemoLinks: false },
|
|
333
|
+
constraints: { rejectWithReadyApps: false }
|
|
334
|
+
},
|
|
335
|
+
empty: {
|
|
336
|
+
id: "empty",
|
|
337
|
+
label: "Empty",
|
|
338
|
+
description: "Minimal builder-ready baseline",
|
|
339
|
+
modules: { mode: "replace", enabled: EMPTY_MODULES },
|
|
340
|
+
ui: { startPageVariant: "minimal", hideDemoLinks: true },
|
|
341
|
+
files: { remove: ["src/modules/example", "src/modules/example_customers_sync"] },
|
|
342
|
+
constraints: { rejectWithReadyApps: true }
|
|
343
|
+
},
|
|
344
|
+
crm: {
|
|
345
|
+
id: "crm",
|
|
346
|
+
label: "CRM",
|
|
347
|
+
description: "Empty preset plus CRM capabilities",
|
|
348
|
+
extends: "empty",
|
|
349
|
+
modules: {
|
|
350
|
+
mode: "patch",
|
|
351
|
+
add: [
|
|
352
|
+
{ id: "customers", from: CORE },
|
|
353
|
+
{ id: "dictionaries", from: CORE },
|
|
354
|
+
{ id: "feature_toggles", from: CORE }
|
|
355
|
+
]
|
|
356
|
+
},
|
|
357
|
+
ui: { startPageVariant: "crm", hideDemoLinks: true },
|
|
358
|
+
constraints: { rejectWithReadyApps: true }
|
|
359
|
+
}
|
|
360
|
+
};
|
|
361
|
+
var DEFAULT_PRESET_ID = "classic";
|
|
362
|
+
var VALID_PRESET_IDS = Object.keys(STARTER_PRESETS);
|
|
363
|
+
|
|
364
|
+
// src/lib/apply-starter-preset.ts
|
|
365
|
+
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
366
|
+
function resolvePreset(presetId) {
|
|
367
|
+
const preset = STARTER_PRESETS[presetId];
|
|
368
|
+
if (!preset) {
|
|
369
|
+
throw new Error(`Unknown preset "${presetId}". Valid presets: ${VALID_PRESET_IDS.join(", ")}`);
|
|
370
|
+
}
|
|
371
|
+
let modules;
|
|
372
|
+
let parentFilesToRemove = [];
|
|
373
|
+
if (preset.modules.mode === "replace") {
|
|
374
|
+
modules = [...preset.modules.enabled];
|
|
375
|
+
} else {
|
|
376
|
+
if (!preset.extends) {
|
|
377
|
+
throw new Error(`Preset "${presetId}" uses mode "patch" but has no "extends" parent`);
|
|
378
|
+
}
|
|
379
|
+
const parent = STARTER_PRESETS[preset.extends];
|
|
380
|
+
if (!parent) {
|
|
381
|
+
throw new Error(`Preset "${presetId}" extends unknown preset "${preset.extends}"`);
|
|
382
|
+
}
|
|
383
|
+
if (parent.modules.mode !== "replace") {
|
|
384
|
+
throw new Error(`Preset "${presetId}" parent "${preset.extends}" must use mode "replace"`);
|
|
385
|
+
}
|
|
386
|
+
parentFilesToRemove = parent.files?.remove ?? [];
|
|
387
|
+
let base = [...parent.modules.enabled];
|
|
388
|
+
if (preset.modules.add) {
|
|
389
|
+
base = [...base, ...preset.modules.add];
|
|
390
|
+
}
|
|
391
|
+
if (preset.modules.remove) {
|
|
392
|
+
const toRemove = new Set(preset.modules.remove);
|
|
393
|
+
base = base.filter((m) => !toRemove.has(m.id));
|
|
394
|
+
}
|
|
395
|
+
modules = base;
|
|
396
|
+
}
|
|
397
|
+
const ids = modules.map((m) => m.id);
|
|
398
|
+
const seen = /* @__PURE__ */ new Set();
|
|
399
|
+
const duplicates = ids.filter((id) => {
|
|
400
|
+
if (seen.has(id)) return true;
|
|
401
|
+
seen.add(id);
|
|
402
|
+
return false;
|
|
403
|
+
});
|
|
404
|
+
if (duplicates.length > 0) {
|
|
405
|
+
throw new Error(`Preset "${presetId}" has duplicate module IDs: ${duplicates.join(", ")}`);
|
|
406
|
+
}
|
|
407
|
+
const ownFilesToRemove = preset.files?.remove ?? [];
|
|
408
|
+
const filesToRemove = [.../* @__PURE__ */ new Set([...parentFilesToRemove, ...ownFilesToRemove])];
|
|
409
|
+
return {
|
|
410
|
+
id: preset.id,
|
|
411
|
+
modules,
|
|
412
|
+
filesToRemove,
|
|
413
|
+
ui: preset.ui,
|
|
414
|
+
isClassic: presetId === DEFAULT_PRESET_ID
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
function generateModulesTs(modules) {
|
|
418
|
+
const moduleLines = modules.map((m) => ` { id: '${m.id}', from: '${m.from}' },`).join("\n");
|
|
419
|
+
const template = readFileSync(join2(__dirname, "templates", "modules-ts.template"), "utf8");
|
|
420
|
+
return template.replace("{{MODULES}}", moduleLines);
|
|
421
|
+
}
|
|
422
|
+
function applyStarterPreset(presetId, targetDir) {
|
|
423
|
+
const resolved = resolvePreset(presetId);
|
|
424
|
+
if (resolved.isClassic) return;
|
|
425
|
+
writeFileSync2(join2(targetDir, "src", "modules.ts"), generateModulesTs(resolved.modules));
|
|
426
|
+
for (const relativePath of resolved.filesToRemove) {
|
|
427
|
+
rmSync2(join2(targetDir, relativePath), { recursive: true, force: true });
|
|
428
|
+
}
|
|
429
|
+
writeFileSync2(
|
|
430
|
+
join2(targetDir, ".mercato", "starter-preset.json"),
|
|
431
|
+
JSON.stringify({ preset: presetId, generatedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2) + "\n"
|
|
432
|
+
);
|
|
433
|
+
}
|
|
434
|
+
|
|
311
435
|
// src/setup/wizard.ts
|
|
312
436
|
import { basename } from "node:path";
|
|
313
437
|
|
|
314
438
|
// src/setup/tools/shared.ts
|
|
315
|
-
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, writeFileSync as
|
|
316
|
-
import { join as
|
|
317
|
-
import { fileURLToPath } from "node:url";
|
|
318
|
-
var
|
|
319
|
-
var AGENTIC_DIR =
|
|
320
|
-
var GUIDES_DIR =
|
|
439
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync3, copyFileSync, readdirSync as readdirSync2 } from "node:fs";
|
|
440
|
+
import { join as join3, dirname as dirname2 } from "node:path";
|
|
441
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
442
|
+
var __dirname2 = dirname2(fileURLToPath2(import.meta.url));
|
|
443
|
+
var AGENTIC_DIR = join3(__dirname2, "agentic", "shared");
|
|
444
|
+
var GUIDES_DIR = join3(__dirname2, "agentic", "guides");
|
|
321
445
|
function resolvePlaceholders(content, config) {
|
|
322
446
|
return content.replace(/\{\{PROJECT_NAME\}\}/g, config.projectName);
|
|
323
447
|
}
|
|
324
448
|
function ensureDir(filePath) {
|
|
325
|
-
const dir =
|
|
449
|
+
const dir = dirname2(filePath);
|
|
326
450
|
if (!existsSync2(dir)) {
|
|
327
451
|
mkdirSync2(dir, { recursive: true });
|
|
328
452
|
}
|
|
329
453
|
}
|
|
330
454
|
function writeTemplate(srcRelative, destPath, config) {
|
|
331
|
-
const srcPath =
|
|
332
|
-
const content =
|
|
455
|
+
const srcPath = join3(AGENTIC_DIR, srcRelative);
|
|
456
|
+
const content = readFileSync2(srcPath, "utf-8");
|
|
333
457
|
ensureDir(destPath);
|
|
334
|
-
|
|
458
|
+
writeFileSync3(destPath, resolvePlaceholders(content, config));
|
|
335
459
|
}
|
|
336
460
|
function copyFile(srcRelative, destPath) {
|
|
337
|
-
const srcPath =
|
|
461
|
+
const srcPath = join3(AGENTIC_DIR, srcRelative);
|
|
338
462
|
ensureDir(destPath);
|
|
339
463
|
copyFileSync(srcPath, destPath);
|
|
340
464
|
}
|
|
341
465
|
function generateShared(config) {
|
|
342
466
|
const { targetDir } = config;
|
|
343
|
-
writeTemplate("AGENTS.md.template",
|
|
344
|
-
writeTemplate("ai/specs/README.md",
|
|
345
|
-
copyFile("ai/specs/SPEC-000-template.md",
|
|
346
|
-
copyFile("ai/lessons.md",
|
|
467
|
+
writeTemplate("AGENTS.md.template", join3(targetDir, "AGENTS.md"), config);
|
|
468
|
+
writeTemplate("ai/specs/README.md", join3(targetDir, ".ai", "specs", "README.md"), config);
|
|
469
|
+
copyFile("ai/specs/SPEC-000-template.md", join3(targetDir, ".ai", "specs", "SPEC-000-template.md"));
|
|
470
|
+
copyFile("ai/lessons.md", join3(targetDir, ".ai", "lessons.md"));
|
|
347
471
|
writeTemplate(
|
|
348
472
|
"ai/skills/spec-writing/SKILL.md",
|
|
349
|
-
|
|
473
|
+
join3(targetDir, ".ai", "skills", "spec-writing", "SKILL.md"),
|
|
350
474
|
config
|
|
351
475
|
);
|
|
352
476
|
copyFile(
|
|
353
477
|
"ai/skills/spec-writing/references/spec-template.md",
|
|
354
|
-
|
|
478
|
+
join3(targetDir, ".ai", "skills", "spec-writing", "references", "spec-template.md")
|
|
355
479
|
);
|
|
356
480
|
copyFile(
|
|
357
481
|
"ai/skills/spec-writing/references/spec-checklist.md",
|
|
358
|
-
|
|
482
|
+
join3(targetDir, ".ai", "skills", "spec-writing", "references", "spec-checklist.md")
|
|
359
483
|
);
|
|
360
484
|
copyFile(
|
|
361
485
|
"ai/skills/backend-ui-design/SKILL.md",
|
|
362
|
-
|
|
486
|
+
join3(targetDir, ".ai", "skills", "backend-ui-design", "SKILL.md")
|
|
363
487
|
);
|
|
364
488
|
copyFile(
|
|
365
489
|
"ai/skills/backend-ui-design/references/ui-components.md",
|
|
366
|
-
|
|
490
|
+
join3(targetDir, ".ai", "skills", "backend-ui-design", "references", "ui-components.md")
|
|
367
491
|
);
|
|
368
492
|
copyFile(
|
|
369
493
|
"ai/skills/code-review/SKILL.md",
|
|
370
|
-
|
|
494
|
+
join3(targetDir, ".ai", "skills", "code-review", "SKILL.md")
|
|
371
495
|
);
|
|
372
496
|
copyFile(
|
|
373
497
|
"ai/skills/code-review/references/review-checklist.md",
|
|
374
|
-
|
|
498
|
+
join3(targetDir, ".ai", "skills", "code-review", "references", "review-checklist.md")
|
|
375
499
|
);
|
|
376
500
|
copyFile(
|
|
377
501
|
"ai/skills/integration-builder/SKILL.md",
|
|
378
|
-
|
|
502
|
+
join3(targetDir, ".ai", "skills", "integration-builder", "SKILL.md")
|
|
379
503
|
);
|
|
380
504
|
copyFile(
|
|
381
505
|
"ai/skills/integration-builder/references/adapter-contracts.md",
|
|
382
|
-
|
|
506
|
+
join3(targetDir, ".ai", "skills", "integration-builder", "references", "adapter-contracts.md")
|
|
383
507
|
);
|
|
384
508
|
copyFile(
|
|
385
509
|
"ai/skills/system-extension/SKILL.md",
|
|
386
|
-
|
|
510
|
+
join3(targetDir, ".ai", "skills", "system-extension", "SKILL.md")
|
|
387
511
|
);
|
|
388
512
|
copyFile(
|
|
389
513
|
"ai/skills/system-extension/references/extension-contracts.md",
|
|
390
|
-
|
|
514
|
+
join3(targetDir, ".ai", "skills", "system-extension", "references", "extension-contracts.md")
|
|
391
515
|
);
|
|
392
516
|
copyFile(
|
|
393
517
|
"ai/skills/module-scaffold/SKILL.md",
|
|
394
|
-
|
|
518
|
+
join3(targetDir, ".ai", "skills", "module-scaffold", "SKILL.md")
|
|
395
519
|
);
|
|
396
520
|
copyFile(
|
|
397
521
|
"ai/skills/module-scaffold/references/naming-conventions.md",
|
|
398
|
-
|
|
522
|
+
join3(targetDir, ".ai", "skills", "module-scaffold", "references", "naming-conventions.md")
|
|
399
523
|
);
|
|
400
524
|
copyFile(
|
|
401
525
|
"ai/skills/module-scaffold/references/navigation-patterns.md",
|
|
402
|
-
|
|
526
|
+
join3(targetDir, ".ai", "skills", "module-scaffold", "references", "navigation-patterns.md")
|
|
403
527
|
);
|
|
404
528
|
copyFile(
|
|
405
529
|
"ai/skills/troubleshooter/SKILL.md",
|
|
406
|
-
|
|
530
|
+
join3(targetDir, ".ai", "skills", "troubleshooter", "SKILL.md")
|
|
407
531
|
);
|
|
408
532
|
copyFile(
|
|
409
533
|
"ai/skills/troubleshooter/references/diagnostic-commands.md",
|
|
410
|
-
|
|
534
|
+
join3(targetDir, ".ai", "skills", "troubleshooter", "references", "diagnostic-commands.md")
|
|
411
535
|
);
|
|
412
536
|
copyFile(
|
|
413
537
|
"ai/skills/eject-and-customize/SKILL.md",
|
|
414
|
-
|
|
538
|
+
join3(targetDir, ".ai", "skills", "eject-and-customize", "SKILL.md")
|
|
415
539
|
);
|
|
416
540
|
copyFile(
|
|
417
541
|
"ai/skills/data-model-design/SKILL.md",
|
|
418
|
-
|
|
542
|
+
join3(targetDir, ".ai", "skills", "data-model-design", "SKILL.md")
|
|
419
543
|
);
|
|
420
544
|
copyFile(
|
|
421
545
|
"ai/skills/data-model-design/references/mikro-orm-cheatsheet.md",
|
|
422
|
-
|
|
546
|
+
join3(targetDir, ".ai", "skills", "data-model-design", "references", "mikro-orm-cheatsheet.md")
|
|
423
547
|
);
|
|
424
548
|
copyFile(
|
|
425
549
|
"ai/skills/implement-spec/SKILL.md",
|
|
426
|
-
|
|
550
|
+
join3(targetDir, ".ai", "skills", "implement-spec", "SKILL.md")
|
|
427
551
|
);
|
|
428
552
|
copyFile(
|
|
429
553
|
"ai/skills/integration-tests/SKILL.md",
|
|
430
|
-
|
|
554
|
+
join3(targetDir, ".ai", "skills", "integration-tests", "SKILL.md")
|
|
431
555
|
);
|
|
432
556
|
copyFile(
|
|
433
557
|
"ai/skills/auto-upgrade-0.4.10-to-0.5.0/SKILL.md",
|
|
434
|
-
|
|
558
|
+
join3(targetDir, ".ai", "skills", "auto-upgrade-0.4.10-to-0.5.0", "SKILL.md")
|
|
435
559
|
);
|
|
436
|
-
copyFile("ai/qa/tests/playwright.config.ts",
|
|
560
|
+
copyFile("ai/qa/tests/playwright.config.ts", join3(targetDir, ".ai", "qa", "tests", "playwright.config.ts"));
|
|
437
561
|
if (existsSync2(GUIDES_DIR)) {
|
|
438
|
-
const guidesDestDir =
|
|
562
|
+
const guidesDestDir = join3(targetDir, ".ai", "guides");
|
|
439
563
|
for (const file of readdirSync2(GUIDES_DIR)) {
|
|
440
564
|
if (file.endsWith(".md")) {
|
|
441
|
-
const srcPath =
|
|
442
|
-
const destPath =
|
|
565
|
+
const srcPath = join3(GUIDES_DIR, file);
|
|
566
|
+
const destPath = join3(guidesDestDir, file);
|
|
443
567
|
ensureDir(destPath);
|
|
444
568
|
copyFileSync(srcPath, destPath);
|
|
445
569
|
}
|
|
@@ -448,38 +572,38 @@ function generateShared(config) {
|
|
|
448
572
|
}
|
|
449
573
|
|
|
450
574
|
// src/setup/tools/claude-code.ts
|
|
451
|
-
import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as
|
|
452
|
-
import { join as
|
|
453
|
-
import { fileURLToPath as
|
|
454
|
-
var
|
|
455
|
-
var AGENTIC_DIR2 =
|
|
575
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync4, copyFileSync as copyFileSync2, symlinkSync, lstatSync, unlinkSync } from "node:fs";
|
|
576
|
+
import { join as join4, dirname as dirname3 } from "node:path";
|
|
577
|
+
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
578
|
+
var __dirname3 = dirname3(fileURLToPath3(import.meta.url));
|
|
579
|
+
var AGENTIC_DIR2 = join4(__dirname3, "agentic", "claude-code");
|
|
456
580
|
function resolvePlaceholders2(content, config) {
|
|
457
581
|
return content.replace(/\{\{PROJECT_NAME\}\}/g, config.projectName);
|
|
458
582
|
}
|
|
459
583
|
function ensureDir2(filePath) {
|
|
460
|
-
const dir =
|
|
584
|
+
const dir = dirname3(filePath);
|
|
461
585
|
if (!existsSync3(dir)) {
|
|
462
586
|
mkdirSync3(dir, { recursive: true });
|
|
463
587
|
}
|
|
464
588
|
}
|
|
465
589
|
function writeTemplate2(srcRelative, destPath, config) {
|
|
466
|
-
const srcPath =
|
|
467
|
-
const content =
|
|
590
|
+
const srcPath = join4(AGENTIC_DIR2, srcRelative);
|
|
591
|
+
const content = readFileSync3(srcPath, "utf-8");
|
|
468
592
|
ensureDir2(destPath);
|
|
469
|
-
|
|
593
|
+
writeFileSync4(destPath, resolvePlaceholders2(content, config));
|
|
470
594
|
}
|
|
471
595
|
function copyFile2(srcRelative, destPath) {
|
|
472
|
-
const srcPath =
|
|
596
|
+
const srcPath = join4(AGENTIC_DIR2, srcRelative);
|
|
473
597
|
ensureDir2(destPath);
|
|
474
598
|
copyFileSync2(srcPath, destPath);
|
|
475
599
|
}
|
|
476
600
|
function generateClaudeCode(config) {
|
|
477
601
|
const { targetDir } = config;
|
|
478
|
-
writeTemplate2("CLAUDE.md.template",
|
|
479
|
-
copyFile2("settings.json",
|
|
480
|
-
copyFile2("hooks/entity-migration-check.ts",
|
|
481
|
-
copyFile2("mcp.json.example",
|
|
482
|
-
ensureSkillsLink(
|
|
602
|
+
writeTemplate2("CLAUDE.md.template", join4(targetDir, "CLAUDE.md"), config);
|
|
603
|
+
copyFile2("settings.json", join4(targetDir, ".claude", "settings.json"));
|
|
604
|
+
copyFile2("hooks/entity-migration-check.ts", join4(targetDir, ".claude", "hooks", "entity-migration-check.ts"));
|
|
605
|
+
copyFile2("mcp.json.example", join4(targetDir, ".mcp.json.example"));
|
|
606
|
+
ensureSkillsLink(join4(targetDir, ".claude", "skills"), join4("..", ".ai", "skills"));
|
|
483
607
|
}
|
|
484
608
|
function ensureSkillsLink(linkPath, target) {
|
|
485
609
|
ensureDir2(linkPath);
|
|
@@ -493,31 +617,31 @@ function ensureSkillsLink(linkPath, target) {
|
|
|
493
617
|
}
|
|
494
618
|
|
|
495
619
|
// src/setup/tools/codex.ts
|
|
496
|
-
import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as
|
|
497
|
-
import { join as
|
|
498
|
-
import { fileURLToPath as
|
|
499
|
-
var
|
|
500
|
-
var AGENTIC_DIR3 =
|
|
620
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync5, copyFileSync as copyFileSync3, symlinkSync as symlinkSync2, lstatSync as lstatSync2, unlinkSync as unlinkSync2 } from "node:fs";
|
|
621
|
+
import { join as join5, dirname as dirname4 } from "node:path";
|
|
622
|
+
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
623
|
+
var __dirname4 = dirname4(fileURLToPath4(import.meta.url));
|
|
624
|
+
var AGENTIC_DIR3 = join5(__dirname4, "agentic", "codex");
|
|
501
625
|
var MARKER_START = "<!-- CODEX_ENFORCEMENT_RULES_START -->";
|
|
502
626
|
var MARKER_END = "<!-- CODEX_ENFORCEMENT_RULES_END -->";
|
|
503
627
|
function ensureDir3(filePath) {
|
|
504
|
-
const dir =
|
|
628
|
+
const dir = dirname4(filePath);
|
|
505
629
|
if (!existsSync4(dir)) {
|
|
506
630
|
mkdirSync4(dir, { recursive: true });
|
|
507
631
|
}
|
|
508
632
|
}
|
|
509
633
|
function copyFile3(srcRelative, destPath) {
|
|
510
|
-
const srcPath =
|
|
634
|
+
const srcPath = join5(AGENTIC_DIR3, srcRelative);
|
|
511
635
|
ensureDir3(destPath);
|
|
512
636
|
copyFileSync3(srcPath, destPath);
|
|
513
637
|
}
|
|
514
638
|
function generateCodex(config) {
|
|
515
639
|
const { targetDir } = config;
|
|
516
|
-
const agentsPath =
|
|
517
|
-
const rulesPath =
|
|
640
|
+
const agentsPath = join5(targetDir, "AGENTS.md");
|
|
641
|
+
const rulesPath = join5(AGENTIC_DIR3, "enforcement-rules.md");
|
|
518
642
|
if (existsSync4(agentsPath)) {
|
|
519
|
-
let agents =
|
|
520
|
-
const rules =
|
|
643
|
+
let agents = readFileSync4(agentsPath, "utf-8");
|
|
644
|
+
const rules = readFileSync4(rulesPath, "utf-8");
|
|
521
645
|
if (agents.includes(MARKER_START)) {
|
|
522
646
|
const startIdx = agents.indexOf(MARKER_START);
|
|
523
647
|
const endIdx = agents.indexOf(MARKER_END);
|
|
@@ -532,10 +656,10 @@ function generateCodex(config) {
|
|
|
532
656
|
agents = agents + "\n\n" + rules;
|
|
533
657
|
}
|
|
534
658
|
}
|
|
535
|
-
|
|
659
|
+
writeFileSync5(agentsPath, agents);
|
|
536
660
|
}
|
|
537
|
-
copyFile3("mcp.json.example",
|
|
538
|
-
ensureSkillsLink2(
|
|
661
|
+
copyFile3("mcp.json.example", join5(targetDir, ".codex", "mcp.json.example"));
|
|
662
|
+
ensureSkillsLink2(join5(targetDir, ".codex", "skills"), join5("..", ".ai", "skills"));
|
|
539
663
|
}
|
|
540
664
|
function ensureSkillsLink2(linkPath, target) {
|
|
541
665
|
ensureDir3(linkPath);
|
|
@@ -549,40 +673,40 @@ function ensureSkillsLink2(linkPath, target) {
|
|
|
549
673
|
}
|
|
550
674
|
|
|
551
675
|
// src/setup/tools/cursor.ts
|
|
552
|
-
import { existsSync as existsSync5, mkdirSync as mkdirSync5, readFileSync as
|
|
553
|
-
import { join as
|
|
554
|
-
import { fileURLToPath as
|
|
555
|
-
var
|
|
556
|
-
var AGENTIC_DIR4 =
|
|
676
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync6, copyFileSync as copyFileSync4, symlinkSync as symlinkSync3, lstatSync as lstatSync3, unlinkSync as unlinkSync3 } from "node:fs";
|
|
677
|
+
import { join as join6, dirname as dirname5 } from "node:path";
|
|
678
|
+
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
679
|
+
var __dirname5 = dirname5(fileURLToPath5(import.meta.url));
|
|
680
|
+
var AGENTIC_DIR4 = join6(__dirname5, "agentic", "cursor");
|
|
557
681
|
function resolvePlaceholders3(content, config) {
|
|
558
682
|
return content.replace(/\{\{PROJECT_NAME\}\}/g, config.projectName);
|
|
559
683
|
}
|
|
560
684
|
function ensureDir4(filePath) {
|
|
561
|
-
const dir =
|
|
685
|
+
const dir = dirname5(filePath);
|
|
562
686
|
if (!existsSync5(dir)) {
|
|
563
687
|
mkdirSync5(dir, { recursive: true });
|
|
564
688
|
}
|
|
565
689
|
}
|
|
566
690
|
function writeTemplate3(srcRelative, destPath, config) {
|
|
567
|
-
const srcPath =
|
|
568
|
-
const content =
|
|
691
|
+
const srcPath = join6(AGENTIC_DIR4, srcRelative);
|
|
692
|
+
const content = readFileSync5(srcPath, "utf-8");
|
|
569
693
|
ensureDir4(destPath);
|
|
570
|
-
|
|
694
|
+
writeFileSync6(destPath, resolvePlaceholders3(content, config));
|
|
571
695
|
}
|
|
572
696
|
function copyFile4(srcRelative, destPath) {
|
|
573
|
-
const srcPath =
|
|
697
|
+
const srcPath = join6(AGENTIC_DIR4, srcRelative);
|
|
574
698
|
ensureDir4(destPath);
|
|
575
699
|
copyFileSync4(srcPath, destPath);
|
|
576
700
|
}
|
|
577
701
|
function generateCursor(config) {
|
|
578
702
|
const { targetDir } = config;
|
|
579
|
-
writeTemplate3("rules/open-mercato.mdc",
|
|
580
|
-
copyFile4("rules/entity-guard.mdc",
|
|
581
|
-
copyFile4("rules/generated-guard.mdc",
|
|
582
|
-
copyFile4("hooks.json",
|
|
583
|
-
copyFile4("hooks/entity-migration-check.mjs",
|
|
584
|
-
copyFile4("mcp.json.example",
|
|
585
|
-
ensureSkillsLink3(
|
|
703
|
+
writeTemplate3("rules/open-mercato.mdc", join6(targetDir, ".cursor", "rules", "open-mercato.mdc"), config);
|
|
704
|
+
copyFile4("rules/entity-guard.mdc", join6(targetDir, ".cursor", "rules", "entity-guard.mdc"));
|
|
705
|
+
copyFile4("rules/generated-guard.mdc", join6(targetDir, ".cursor", "rules", "generated-guard.mdc"));
|
|
706
|
+
copyFile4("hooks.json", join6(targetDir, ".cursor", "hooks.json"));
|
|
707
|
+
copyFile4("hooks/entity-migration-check.mjs", join6(targetDir, ".cursor", "hooks", "entity-migration-check.mjs"));
|
|
708
|
+
copyFile4("mcp.json.example", join6(targetDir, ".cursor", "mcp.json.example"));
|
|
709
|
+
ensureSkillsLink3(join6(targetDir, ".cursor", "skills"), join6("..", ".ai", "skills"));
|
|
586
710
|
}
|
|
587
711
|
function ensureSkillsLink3(linkPath, target) {
|
|
588
712
|
ensureDir4(linkPath);
|
|
@@ -675,10 +799,10 @@ function printSummary(selectedIds) {
|
|
|
675
799
|
}
|
|
676
800
|
|
|
677
801
|
// src/index.ts
|
|
678
|
-
var
|
|
679
|
-
var packageJson = JSON.parse(
|
|
802
|
+
var __dirname6 = dirname6(fileURLToPath6(import.meta.url));
|
|
803
|
+
var packageJson = JSON.parse(readFileSync6(join7(__dirname6, "..", "package.json"), "utf-8"));
|
|
680
804
|
var PACKAGE_VERSION = packageJson.version;
|
|
681
|
-
var TEMPLATE_DIR =
|
|
805
|
+
var TEMPLATE_DIR = join7(__dirname6, "..", "template");
|
|
682
806
|
function showHelp() {
|
|
683
807
|
console.log(`
|
|
684
808
|
${pc.bold("create-mercato-app")} - Create a new Open Mercato application
|
|
@@ -692,6 +816,7 @@ ${pc.bold("Arguments:")}
|
|
|
692
816
|
${pc.bold("Options:")}
|
|
693
817
|
--app <name> Bootstrap an official ready app from open-mercato/ready-app-<name>
|
|
694
818
|
--app-url <url> Bootstrap a ready app from a GitHub repository URL
|
|
819
|
+
--preset <id> Starter preset: classic (default), empty, or crm
|
|
695
820
|
--skip-agentic-setup Skip the interactive agentic setup wizard
|
|
696
821
|
--registry <url> Custom npm registry URL
|
|
697
822
|
--verdaccio Use local Verdaccio registry (http://localhost:4873)
|
|
@@ -700,6 +825,8 @@ ${pc.bold("Options:")}
|
|
|
700
825
|
|
|
701
826
|
${pc.bold("Examples:")}
|
|
702
827
|
npx create-mercato-app my-store
|
|
828
|
+
npx create-mercato-app my-store --preset empty
|
|
829
|
+
npx create-mercato-app my-store --preset crm
|
|
703
830
|
npx create-mercato-app my-prm --app prm
|
|
704
831
|
npx create-mercato-app my-marketplace --app-url https://github.com/some-agency/ready-app-marketplace
|
|
705
832
|
npx create-mercato-app my-store --verdaccio
|
|
@@ -720,6 +847,7 @@ function parseArgs(args) {
|
|
|
720
847
|
const options = {
|
|
721
848
|
app: void 0,
|
|
722
849
|
appUrl: void 0,
|
|
850
|
+
preset: void 0,
|
|
723
851
|
registry: void 0,
|
|
724
852
|
skipAgenticSetup: false,
|
|
725
853
|
verdaccio: false,
|
|
@@ -746,6 +874,9 @@ function parseArgs(args) {
|
|
|
746
874
|
} else if (arg === "--app-url") {
|
|
747
875
|
options.appUrl = requireOptionValue(args, index, arg);
|
|
748
876
|
index += 1;
|
|
877
|
+
} else if (arg === "--preset") {
|
|
878
|
+
options.preset = requireOptionValue(args, index, arg);
|
|
879
|
+
index += 1;
|
|
749
880
|
} else if (!arg.startsWith("-")) {
|
|
750
881
|
appName = arg;
|
|
751
882
|
}
|
|
@@ -785,32 +916,32 @@ function copyDirRecursive(src, dest, placeholders) {
|
|
|
785
916
|
}
|
|
786
917
|
const entries = readdirSync3(src);
|
|
787
918
|
for (const entry of entries) {
|
|
788
|
-
const srcPath =
|
|
919
|
+
const srcPath = join7(src, entry);
|
|
789
920
|
const destName = FILE_RENAMES[entry] ?? entry;
|
|
790
|
-
let destPath =
|
|
921
|
+
let destPath = join7(dest, destName);
|
|
791
922
|
const stat = statSync2(srcPath);
|
|
792
923
|
if (stat.isDirectory()) {
|
|
793
924
|
if (SKIP_DIRS.has(entry)) continue;
|
|
794
925
|
copyDirRecursive(srcPath, destPath, placeholders);
|
|
795
926
|
} else if (entry.endsWith(".template")) {
|
|
796
927
|
const finalName = entry.replace(".template", "");
|
|
797
|
-
destPath =
|
|
798
|
-
let content =
|
|
928
|
+
destPath = join7(dest, finalName);
|
|
929
|
+
let content = readFileSync6(srcPath, "utf-8");
|
|
799
930
|
for (const [key, value] of Object.entries(placeholders)) {
|
|
800
931
|
content = content.replace(new RegExp(`\\{\\{${key}\\}\\}`, "g"), value);
|
|
801
932
|
}
|
|
802
|
-
|
|
933
|
+
writeFileSync7(destPath, content);
|
|
803
934
|
} else {
|
|
804
935
|
copyFileSync5(srcPath, destPath);
|
|
805
936
|
}
|
|
806
937
|
}
|
|
807
938
|
}
|
|
808
939
|
function ensureGeneratedCssPlaceholder(targetDir) {
|
|
809
|
-
const generatedDir =
|
|
810
|
-
const placeholderPath =
|
|
940
|
+
const generatedDir = join7(targetDir, ".mercato", "generated");
|
|
941
|
+
const placeholderPath = join7(generatedDir, "module-package-sources.css");
|
|
811
942
|
mkdirSync6(generatedDir, { recursive: true });
|
|
812
943
|
if (!existsSync6(placeholderPath)) {
|
|
813
|
-
|
|
944
|
+
writeFileSync7(placeholderPath, "");
|
|
814
945
|
}
|
|
815
946
|
}
|
|
816
947
|
async function scaffoldTemplateApp(targetDir, placeholders) {
|
|
@@ -910,6 +1041,15 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
910
1041
|
throw new Error(`Directory "${appName}" already exists`);
|
|
911
1042
|
}
|
|
912
1043
|
const readyAppSource = resolveReadyAppSource(options, PACKAGE_VERSION);
|
|
1044
|
+
const presetId = options.preset ?? DEFAULT_PRESET_ID;
|
|
1045
|
+
if (!VALID_PRESET_IDS.includes(presetId)) {
|
|
1046
|
+
throw new Error(`Unknown preset "${presetId}". Valid presets: ${VALID_PRESET_IDS.join(", ")}`);
|
|
1047
|
+
}
|
|
1048
|
+
if (presetId !== DEFAULT_PRESET_ID && readyAppSource) {
|
|
1049
|
+
throw new Error(
|
|
1050
|
+
`--preset ${presetId} cannot be combined with --app or --app-url. Presets apply only to the built-in template.`
|
|
1051
|
+
);
|
|
1052
|
+
}
|
|
913
1053
|
const registryConfig = options.verdaccio ? buildRegistryConfig("http://localhost:4873") : options.registry ? buildRegistryConfig(options.registry) : "";
|
|
914
1054
|
console.log("");
|
|
915
1055
|
console.log(pc.bold(`Creating a new Open Mercato app in ${pc.cyan(targetDir)}`));
|
|
@@ -925,6 +1065,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
925
1065
|
await scaffoldImportedReadyApp(targetDir, readyAppSource);
|
|
926
1066
|
} else {
|
|
927
1067
|
await scaffoldTemplateApp(targetDir, placeholders);
|
|
1068
|
+
applyStarterPreset(presetId, targetDir);
|
|
928
1069
|
}
|
|
929
1070
|
console.log(pc.green("Success!") + ` Created ${pc.bold(appName)}`);
|
|
930
1071
|
console.log("");
|
|
@@ -937,7 +1078,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
937
1078
|
console.log(pc.dim("For more information, visit https://github.com/open-mercato/open-mercato"));
|
|
938
1079
|
console.log("");
|
|
939
1080
|
}
|
|
940
|
-
var isEntrypoint = process.argv[1] !== void 0 && resolve(process.argv[1]) ===
|
|
1081
|
+
var isEntrypoint = process.argv[1] !== void 0 && resolve(process.argv[1]) === fileURLToPath6(import.meta.url);
|
|
941
1082
|
if (isEntrypoint) {
|
|
942
1083
|
main().catch((error) => {
|
|
943
1084
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Central place to enable modules and their source.
|
|
2
|
+
// - id: module id (plural snake_case; special cases: 'auth')
|
|
3
|
+
// - from: '@open-mercato/core' | '@app' | custom alias/path in future
|
|
4
|
+
import { parseBooleanWithDefault } from '@open-mercato/shared/lib/boolean'
|
|
5
|
+
|
|
6
|
+
export type ModuleEntry = { id: string; from?: '@open-mercato/core' | '@app' | string }
|
|
7
|
+
|
|
8
|
+
export const enabledModules: ModuleEntry[] = [
|
|
9
|
+
{{MODULES}}
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
const enterpriseModulesEnabled = parseBooleanWithDefault(process.env.OM_ENABLE_ENTERPRISE_MODULES, false)
|
|
13
|
+
const enterpriseSsoEnabled = parseBooleanWithDefault(process.env.OM_ENABLE_ENTERPRISE_MODULES_SSO, false)
|
|
14
|
+
const enterpriseSecurityEnabled = parseBooleanWithDefault(process.env.OM_ENABLE_ENTERPRISE_MODULES_SECURITY, false)
|
|
15
|
+
|
|
16
|
+
if (enterpriseModulesEnabled) {
|
|
17
|
+
enabledModules.push(
|
|
18
|
+
{ id: 'record_locks', from: '@open-mercato/enterprise' },
|
|
19
|
+
{ id: 'system_status_overlays', from: '@open-mercato/enterprise' },
|
|
20
|
+
)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (enterpriseModulesEnabled && enterpriseSsoEnabled) {
|
|
24
|
+
enabledModules.push({ id: 'sso', from: '@open-mercato/enterprise' })
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (enterpriseModulesEnabled && enterpriseSecurityEnabled) {
|
|
28
|
+
enabledModules.push({ id: 'security', from: '@open-mercato/enterprise' })
|
|
29
|
+
}
|
package/package.json
CHANGED
|
@@ -11,6 +11,8 @@ import Link from 'next/link'
|
|
|
11
11
|
import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
|
|
12
12
|
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
13
13
|
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
14
|
+
import { User } from '@open-mercato/core/modules/auth/data/entities'
|
|
15
|
+
import { Tenant, Organization } from '@open-mercato/core/modules/directory/data/entities'
|
|
14
16
|
|
|
15
17
|
function FeatureBadge({ label }: { label: string }) {
|
|
16
18
|
return (
|
|
@@ -60,9 +62,9 @@ export default async function Home() {
|
|
|
60
62
|
try {
|
|
61
63
|
const container = await createRequestContainer()
|
|
62
64
|
const em = container.resolve<EntityManager>('em')
|
|
63
|
-
usersCount = await em.count(
|
|
64
|
-
tenantsCount = await em.count(
|
|
65
|
-
orgsCount = await em.count(
|
|
65
|
+
usersCount = await em.count(User, {})
|
|
66
|
+
tenantsCount = await em.count(Tenant, {})
|
|
67
|
+
orgsCount = await em.count(Organization, {})
|
|
66
68
|
dbStatus = t('app.page.dbStatus.connected', 'Connected')
|
|
67
69
|
} catch (error: unknown) {
|
|
68
70
|
const message = error instanceof Error ? error.message : t('app.page.dbStatus.noConnection', 'no connection')
|
|
@@ -191,25 +191,32 @@ export const { metadata, GET, POST, PUT, DELETE } = makeCrudRoute({
|
|
|
191
191
|
.map((d: any) => d.key)
|
|
192
192
|
// Fallback discovery: keys that have values even if no definition exists
|
|
193
193
|
try {
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
.
|
|
197
|
-
.
|
|
198
|
-
.
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
.
|
|
194
|
+
const db = (em as any).getKysely()
|
|
195
|
+
let cfvQuery = db
|
|
196
|
+
.selectFrom('custom_field_values')
|
|
197
|
+
.select('field_key')
|
|
198
|
+
.distinct()
|
|
199
|
+
.where('entity_id', '=', ENTITY_ID as any)
|
|
200
|
+
.where('deleted_at', 'is', null)
|
|
201
|
+
if (scopedOrgIds === null) {
|
|
202
|
+
// no organization restriction
|
|
203
|
+
} else if (scopedOrgIds.length > 0) {
|
|
204
|
+
cfvQuery = cfvQuery.where((eb: any) => eb.or([
|
|
205
|
+
eb('organization_id', 'in', scopedOrgIds),
|
|
206
|
+
eb('organization_id', 'is', null),
|
|
207
|
+
]))
|
|
208
|
+
} else {
|
|
209
|
+
cfvQuery = cfvQuery.where('organization_id', 'is', null)
|
|
210
|
+
}
|
|
211
|
+
if (ctx.auth!.tenantId != null) {
|
|
212
|
+
cfvQuery = cfvQuery.where((eb: any) => eb.or([
|
|
213
|
+
eb('tenant_id', '=', ctx.auth!.tenantId),
|
|
214
|
+
eb('tenant_id', 'is', null),
|
|
215
|
+
]))
|
|
216
|
+
} else {
|
|
217
|
+
cfvQuery = cfvQuery.where('tenant_id', 'is', null)
|
|
218
|
+
}
|
|
219
|
+
const rows = await cfvQuery.execute()
|
|
213
220
|
const keysFromValues = (rows || []).map((r: any) => String(r.field_key))
|
|
214
221
|
// Merge with code-declared keys and de-dupe
|
|
215
222
|
dynamicCfKeys = Array.from(new Set([ ...cfSel.keys, ...keysFromDefs, ...keysFromValues ]))
|
|
@@ -407,7 +407,7 @@ const deleteTodoCommand: CommandHandler<{ body?: Record<string, unknown>; query?
|
|
|
407
407
|
restored.isDone = before.is_done
|
|
408
408
|
restored.tenantId = before.tenantId ?? scope.tenantId
|
|
409
409
|
restored.organizationId = before.organizationId ?? scope.organizationId
|
|
410
|
-
await em.
|
|
410
|
+
await em.persist(restored).flush()
|
|
411
411
|
} else {
|
|
412
412
|
restored = await de.createOrmEntity({
|
|
413
413
|
entity: Todo,
|
package/template/src/modules/example_customers_sync/api/example-customers-sync/mappings/route.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
2
|
import { z } from 'zod'
|
|
3
3
|
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
4
|
+
import { type Kysely } from 'kysely'
|
|
4
5
|
import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
|
|
5
6
|
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
6
7
|
import { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
@@ -82,9 +83,10 @@ export async function GET(request: Request) {
|
|
|
82
83
|
: []
|
|
83
84
|
|
|
84
85
|
const em = (container.resolve('em') as EntityManager).fork()
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
.
|
|
86
|
+
const db = (em as any).getKysely() as Kysely<any>
|
|
87
|
+
let rowsQuery = db
|
|
88
|
+
.selectFrom('example_customer_interaction_mappings')
|
|
89
|
+
.select([
|
|
88
90
|
'id',
|
|
89
91
|
'interaction_id',
|
|
90
92
|
'todo_id',
|
|
@@ -97,29 +99,32 @@ export async function GET(request: Request) {
|
|
|
97
99
|
'organization_id',
|
|
98
100
|
'tenant_id',
|
|
99
101
|
])
|
|
100
|
-
.where('tenant_id', auth.tenantId)
|
|
102
|
+
.where('tenant_id', '=', auth.tenantId)
|
|
101
103
|
.orderBy('updated_at', 'desc')
|
|
102
104
|
.orderBy('id', 'desc')
|
|
103
105
|
.limit(query.limit + 1)
|
|
104
106
|
|
|
105
107
|
if (organizationIds.length > 0) {
|
|
106
|
-
rowsQuery.
|
|
108
|
+
rowsQuery = rowsQuery.where('organization_id', 'in', organizationIds)
|
|
107
109
|
}
|
|
108
110
|
if (query.interactionId) {
|
|
109
|
-
rowsQuery.
|
|
111
|
+
rowsQuery = rowsQuery.where('interaction_id', '=', query.interactionId)
|
|
110
112
|
}
|
|
111
113
|
if (query.todoId) {
|
|
112
|
-
rowsQuery.
|
|
114
|
+
rowsQuery = rowsQuery.where('todo_id', '=', query.todoId)
|
|
113
115
|
}
|
|
114
116
|
if (cursor) {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
117
|
+
const cursorDate = new Date(cursor.updatedAt)
|
|
118
|
+
rowsQuery = rowsQuery.where(eb => eb.or([
|
|
119
|
+
eb('updated_at', '<', cursorDate),
|
|
120
|
+
eb.and([
|
|
121
|
+
eb('updated_at', '=', cursorDate),
|
|
122
|
+
eb('id', '<', cursor.id),
|
|
123
|
+
]),
|
|
124
|
+
]))
|
|
120
125
|
}
|
|
121
126
|
|
|
122
|
-
const rows = await rowsQuery
|
|
127
|
+
const rows = (await rowsQuery.execute()) as MappingRow[]
|
|
123
128
|
const pageRows = rows.slice(0, query.limit)
|
|
124
129
|
const interactionIds = Array.from(new Set(pageRows.map((row) => row.interaction_id)))
|
|
125
130
|
const interactions = interactionIds.length > 0
|
|
@@ -247,6 +247,6 @@ export async function deleteExampleCustomerInteractionMapping(
|
|
|
247
247
|
mapping: ExampleCustomerInteractionMapping | null | undefined,
|
|
248
248
|
): Promise<boolean> {
|
|
249
249
|
if (!mapping) return false
|
|
250
|
-
await em.
|
|
250
|
+
await em.remove(mapping).flush()
|
|
251
251
|
return true
|
|
252
252
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
2
|
+
import { type Kysely } from 'kysely'
|
|
2
3
|
import type { CommandBus } from '@open-mercato/shared/lib/commands'
|
|
3
4
|
import { loadCustomFieldSnapshot } from '@open-mercato/shared/lib/commands/customFieldSnapshots'
|
|
4
5
|
import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
|
|
@@ -720,34 +721,36 @@ async function loadLegacyExampleTodoLinks(
|
|
|
720
721
|
cursor?: string,
|
|
721
722
|
): Promise<{ rows: LegacyExampleTodoLinkRow[]; nextCursor?: string }> {
|
|
722
723
|
const em = (container.resolve('em') as EntityManager).fork()
|
|
723
|
-
const
|
|
724
|
+
const db = (em as any).getKysely() as Kysely<any>
|
|
724
725
|
const parsedCursor = decodeCursor(cursor)
|
|
725
|
-
|
|
726
|
-
.
|
|
726
|
+
let query = db
|
|
727
|
+
.selectFrom('customer_todo_links')
|
|
728
|
+
.select([
|
|
727
729
|
'id',
|
|
728
730
|
'entity_id as entityId',
|
|
729
731
|
'todo_id as todoId',
|
|
730
732
|
'created_by_user_id as createdByUserId',
|
|
731
733
|
'created_at as createdAt',
|
|
732
734
|
])
|
|
733
|
-
.where(
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
todo_source: 'example:todo',
|
|
737
|
-
})
|
|
735
|
+
.where('tenant_id', '=', scope.tenantId)
|
|
736
|
+
.where('organization_id', '=', scope.organizationId)
|
|
737
|
+
.where('todo_source', '=', 'example:todo')
|
|
738
738
|
.orderBy('created_at', 'asc')
|
|
739
739
|
.orderBy('id', 'asc')
|
|
740
740
|
.limit(limit + 1)
|
|
741
741
|
|
|
742
742
|
if (parsedCursor) {
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
743
|
+
const cursorDate = new Date(parsedCursor.createdAt)
|
|
744
|
+
query = query.where(eb => eb.or([
|
|
745
|
+
eb('created_at', '>', cursorDate),
|
|
746
|
+
eb.and([
|
|
747
|
+
eb('created_at', '=', cursorDate),
|
|
748
|
+
eb('id', '>', parsedCursor.id),
|
|
749
|
+
]),
|
|
750
|
+
]))
|
|
748
751
|
}
|
|
749
752
|
|
|
750
|
-
const rows = await query
|
|
753
|
+
const rows = (await query.execute()) as LegacyExampleTodoLinkRow[]
|
|
751
754
|
const pageRows = rows.slice(0, limit)
|
|
752
755
|
const next = rows.length > limit ? pageRows[pageRows.length - 1] : null
|
|
753
756
|
return {
|