@fleox-agent-crm/agentcrm 0.0.0-stage → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +143 -0
- package/README.md +223 -2
- package/changelog.json +57 -0
- package/dist/admin/admin-files.json +26 -0
- package/dist/admin/assets/chartsImpl-CE2akJat.js +91 -0
- package/dist/admin/assets/firebase-BHSUggRo.js +2 -0
- package/dist/admin/assets/gitJson-BulGh2KV.js +3 -0
- package/dist/admin/assets/index-DidVVK7L.js +1181 -0
- package/dist/admin/assets/index-hMXwHSvN.css +1 -0
- package/dist/admin/assets/index.esm-BFc7vwnl.js +400 -0
- package/dist/admin/assets/index.esm-CtEQZRb1.js +1651 -0
- package/dist/admin/assets/index.esm-Dum0J07z.js +16 -0
- package/dist/admin/assets/index.esm2017-EOzKbpUS.js +390 -0
- package/dist/admin/assets/lucide-react.min-DEDlHCPj.js +6 -0
- package/dist/admin/assets/sdk-CnrLnlRo.js +1717 -0
- package/dist/admin/config.js +3 -0
- package/dist/admin/favicon.svg +1 -0
- package/dist/admin/index.html +139 -0
- package/dist/admin/runtime/agentcrm-runtime.js +104 -0
- package/dist/admin/runtime/lucide-react.js +791 -0
- package/dist/admin/runtime/react-dom-client.js +17 -0
- package/dist/admin/runtime/react-dom.js +20 -0
- package/dist/admin/runtime/react-i18next.js +19 -0
- package/dist/admin/runtime/react-jsx-runtime.js +17 -0
- package/dist/admin/runtime/react.js +22 -0
- package/dist/agent/agentcrm.mjs +3932 -0
- package/dist/agent/chunks/_commonjsHelpers-ClciMIcB.mjs +14 -0
- package/dist/agent/chunks/activities-Cj_SIOay.mjs +227 -0
- package/dist/agent/chunks/api-B6OqNVWp.mjs +97 -0
- package/dist/agent/chunks/appearance-BaShelzt.mjs +174 -0
- package/dist/agent/chunks/automations-BWrvLqP6.mjs +698 -0
- package/dist/agent/chunks/backup-ClSmxmFZ.mjs +103 -0
- package/dist/agent/chunks/batch-CVETiJj5.mjs +142 -0
- package/dist/agent/chunks/compact-CsFPaAAq.mjs +69 -0
- package/dist/agent/chunks/context-CxFfqS4D.mjs +65 -0
- package/dist/agent/chunks/core-Dy7vY9H6.mjs +255 -0
- package/dist/agent/chunks/dedupe-BepmsaHv.mjs +123 -0
- package/dist/agent/chunks/encoding-C9MzdP95.mjs +36 -0
- package/dist/agent/chunks/exportCsv-B5hRvkNm.mjs +95 -0
- package/dist/agent/chunks/extCheck--7lmVvl7.mjs +193 -0
- package/dist/agent/chunks/extPackage-BGl5Oqs8.mjs +115 -0
- package/dist/agent/chunks/extScaffold-DqxI7R1e.mjs +320 -0
- package/dist/agent/chunks/extensionActions-D2HtpQz4.mjs +48 -0
- package/dist/agent/chunks/extensions-BP1k60md.mjs +151 -0
- package/dist/agent/chunks/featureRegistry-C9nbVShp.mjs +149 -0
- package/dist/agent/chunks/fields-B0bitgmr.mjs +476 -0
- package/dist/agent/chunks/files-CjsPJSR6.mjs +158 -0
- package/dist/agent/chunks/files-q8Xu5bft.mjs +367 -0
- package/dist/agent/chunks/firebase-DxGVQV9E.mjs +454 -0
- package/dist/agent/chunks/firebaseRules-iori-s7U.mjs +221 -0
- package/dist/agent/chunks/forms-BKU5F9WF.mjs +847 -0
- package/dist/agent/chunks/gitJson-fjr8mcHe.mjs +528 -0
- package/dist/agent/chunks/hosting-Bme7LxPc.mjs +144 -0
- package/dist/agent/chunks/http-Bk9ICb7I.mjs +127 -0
- package/dist/agent/chunks/ids-CvnmrMJX.mjs +59 -0
- package/dist/agent/chunks/ids-u-noYcy5.mjs +14 -0
- package/dist/agent/chunks/importCsv-DmE_sBch.mjs +334 -0
- package/dist/agent/chunks/index-B-4W0-P4.mjs +535 -0
- package/dist/agent/chunks/index-BM8qeGOb.mjs +2489 -0
- package/dist/agent/chunks/index-Bnmav-Oh.mjs +311 -0
- package/dist/agent/chunks/index-CRkw3_iF.mjs +147 -0
- package/dist/agent/chunks/index-CWrrF1wl.mjs +111 -0
- package/dist/agent/chunks/index-DUSdTBkz.mjs +2988 -0
- package/dist/agent/chunks/index-RCmo-uld.mjs +26 -0
- package/dist/agent/chunks/index-w03ReRs4.mjs +9780 -0
- package/dist/agent/chunks/install-CpHoUFtL.mjs +188 -0
- package/dist/agent/chunks/jsx-runtime-Ca2uwGDr.mjs +76 -0
- package/dist/agent/chunks/loader-C9nm3iwx.mjs +184 -0
- package/dist/agent/chunks/lucide-react.min-Bt3gtepY.mjs +64 -0
- package/dist/agent/chunks/memory-WNR3VHUJ.mjs +56 -0
- package/dist/agent/chunks/migrate-CsClnnN8.mjs +182 -0
- package/dist/agent/chunks/modules-CqdVQVJb.mjs +177 -0
- package/dist/agent/chunks/nodeImport-BPCt5uMu.mjs +38 -0
- package/dist/agent/chunks/package-DWbn3t2R.mjs +194 -0
- package/dist/agent/chunks/papaparse-DjBYnQM2.mjs +1285 -0
- package/dist/agent/chunks/pipelines-DCdRBNBV.mjs +234 -0
- package/dist/agent/chunks/plan-C0TBrU92.mjs +254 -0
- package/dist/agent/chunks/pluginRegistry-DyHtlkcr.mjs +83 -0
- package/dist/agent/chunks/query-C5nJB_3C.mjs +192 -0
- package/dist/agent/chunks/records-DoqiT9xR.mjs +2191 -0
- package/dist/agent/chunks/registry-Ceexawxl.mjs +182 -0
- package/dist/agent/chunks/registry-DijAKZ24.mjs +431 -0
- package/dist/agent/chunks/relations-B9TynBmA.mjs +191 -0
- package/dist/agent/chunks/reports-BB59N6ke.mjs +189 -0
- package/dist/agent/chunks/runtime-DHo7ZEEO.mjs +14852 -0
- package/dist/agent/chunks/runtimeConfig-CCOFg1RM.mjs +250 -0
- package/dist/agent/chunks/s3-Dz50-NuA.mjs +903 -0
- package/dist/agent/chunks/schema-7-4-qtSS.mjs +203 -0
- package/dist/agent/chunks/schema-DlHt2PxI.mjs +367 -0
- package/dist/agent/chunks/sdk-whvfg786.mjs +43644 -0
- package/dist/agent/chunks/seed-CugPGYZL.mjs +523 -0
- package/dist/agent/chunks/server-ISwlDRCN.mjs +242 -0
- package/dist/agent/chunks/settings-BtvR_jdm.mjs +76 -0
- package/dist/agent/chunks/standardObjects-CheqfDqa.mjs +243 -0
- package/dist/agent/chunks/stats-CfeJssSS.mjs +51 -0
- package/dist/agent/chunks/store-5gqRSKXg.mjs +122 -0
- package/dist/agent/chunks/themes-DPZFv3yc.mjs +43 -0
- package/dist/agent/chunks/tools-Bew8lxKx.mjs +844 -0
- package/dist/agent/chunks/types-BOy9PUl7.mjs +44 -0
- package/dist/agent/chunks/users-DI1BvS0f.mjs +107 -0
- package/dist/agent/chunks/utils-DmI8FtXP.mjs +83 -0
- package/dist/agent/chunks/views-fO_BM5U_.mjs +109 -0
- package/dist/version.json +7 -0
- package/package.json +97 -4
- package/plugin/.claude-plugin/plugin.json +17 -0
- package/plugin/skills/agentcrm-admin/SKILL.md +191 -0
- package/plugin/skills/agentcrm-admin/references/appearance.md +26 -0
- package/plugin/skills/agentcrm-admin/references/import.md +16 -0
- package/plugin/skills/agentcrm-admin/references/records.md +30 -0
- package/plugin/skills/agentcrm-admin/references/schema.md +59 -0
- package/plugin/skills/agentcrm-extensions/SKILL.md +93 -0
- package/plugin/skills/agentcrm-extensions/references/module-recipes.md +213 -0
- package/plugin/skills/agentcrm-extensions/references/runtime-api.md +41 -0
- package/plugin/skills/agentcrm-extensions/references/testing.md +76 -0
- package/plugin/skills/agentcrm-extensions/references/theme-recipes.md +65 -0
- package/plugin/skills/agentcrm-install/SKILL.md +116 -0
- package/plugin/skills/agentcrm-install/references/firebase.md +18 -0
- package/plugin/skills/agentcrm-install/references/github.md +20 -0
- package/plugin/skills/agentcrm-install/references/mcp.md +17 -0
- package/plugin/skills/agentcrm-install/references/plans.md +45 -0
- package/plugin/skills/agentcrm-install/references/s3.md +24 -0
- package/plugin/skills/agentcrm-manage/SKILL.md +108 -0
- package/plugin/skills/agentcrm-manage/references/cron.md +11 -0
- package/plugin/skills/agentcrm-manage/references/doctor.md +18 -0
- package/plugin/skills/agentcrm-sales/SKILL.md +92 -0
- package/plugin/skills/agentcrm-sales/references/enrichment.md +13 -0
- package/plugin/skills/agentcrm-sales/references/playbooks.md +29 -0
- package/plugin/skills/agentcrm-sales/references/weekly-report.md +27 -0
- package/types/agentcrm-runtime.d.ts +894 -0
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
import { createRequire as __acrmRequire } from "node:module";
|
|
2
|
+
import { fileURLToPath as __acrmPath } from "node:url";
|
|
3
|
+
import { dirname as __acrmDirname } from "node:path";
|
|
4
|
+
const require2 = __acrmRequire(import.meta.url);
|
|
5
|
+
const __filename = __acrmPath(import.meta.url);
|
|
6
|
+
const __dirname = __acrmDirname(__filename);
|
|
7
|
+
import { existsSync, mkdirSync, writeFileSync, copyFileSync } from "node:fs";
|
|
8
|
+
import { join, dirname } from "node:path";
|
|
9
|
+
import { E as EXTENSION_API_VERSION, R as RUNTIME_SPECIFIERS } from "./api-B6OqNVWp.mjs";
|
|
10
|
+
import { EXTENSION_ID_RE, RESERVED_EXTENSION_IDS } from "./package-DWbn3t2R.mjs";
|
|
11
|
+
import { C as CliError, r as readVersionInfo, p as packageRoot, o as ownPackageRoot, P as PACKAGE_NAME } from "../agentcrm.mjs";
|
|
12
|
+
import "./index-w03ReRs4.mjs";
|
|
13
|
+
import "./_commonjsHelpers-ClciMIcB.mjs";
|
|
14
|
+
import "stream";
|
|
15
|
+
import "events";
|
|
16
|
+
import "buffer";
|
|
17
|
+
import "util";
|
|
18
|
+
import "node:util";
|
|
19
|
+
import "node:url";
|
|
20
|
+
import "node:crypto";
|
|
21
|
+
import "node:os";
|
|
22
|
+
import "node:child_process";
|
|
23
|
+
import "node:http";
|
|
24
|
+
const IMPORT = "import";
|
|
25
|
+
const DEV_DEPENDENCIES = {
|
|
26
|
+
"@types/react": "^18.3.31",
|
|
27
|
+
i18next: "^23.16.8",
|
|
28
|
+
"lucide-react": "^0.460.0",
|
|
29
|
+
react: "^18.3.1",
|
|
30
|
+
"react-i18next": "^15.7.4",
|
|
31
|
+
typescript: "^5.9.3",
|
|
32
|
+
vite: "^6.4.5"
|
|
33
|
+
};
|
|
34
|
+
function titleOf(id) {
|
|
35
|
+
return id.split("-").filter(Boolean).map((w) => w[0].toUpperCase() + w.slice(1)).join(" ");
|
|
36
|
+
}
|
|
37
|
+
function toolNameOf(id) {
|
|
38
|
+
return `${id.replace(/-/g, "_")}_summary`.slice(0, 64);
|
|
39
|
+
}
|
|
40
|
+
function viteConfig(entry) {
|
|
41
|
+
return `// Vite config of an AgentCRM extension. What matters:
|
|
42
|
+
// 1. Library mode, ONE ESM file dist/bundle.js default-exporting
|
|
43
|
+
// defineModule({...}) / defineTheme({...}).
|
|
44
|
+
// 2. The runtime specifiers stay external: the admin maps them to its own
|
|
45
|
+
// React, router, i18n and UI kit at runtime. Never bundle react.
|
|
46
|
+
// 3. inlineDynamicImports: the admin only loads bundle.js (chunks 404).
|
|
47
|
+
// 4. Production JSX (the admin doesn't provide react/jsx-dev-runtime).
|
|
48
|
+
${IMPORT} { defineConfig } from "vite";
|
|
49
|
+
|
|
50
|
+
export const RUNTIME_EXTERNALS = ${JSON.stringify(RUNTIME_SPECIFIERS)};
|
|
51
|
+
|
|
52
|
+
export default defineConfig({
|
|
53
|
+
esbuild: { jsx: "automatic", jsxDev: false },
|
|
54
|
+
define: { "process.env.NODE_ENV": JSON.stringify("production") },
|
|
55
|
+
logLevel: "warn",
|
|
56
|
+
build: {
|
|
57
|
+
lib: { entry: ${JSON.stringify(entry)}, formats: ["es"], fileName: () => "bundle.js" },
|
|
58
|
+
outDir: "dist",
|
|
59
|
+
emptyOutDir: true,
|
|
60
|
+
sourcemap: false,
|
|
61
|
+
minify: true,
|
|
62
|
+
rollupOptions: { external: RUNTIME_EXTERNALS, output: { inlineDynamicImports: true } },
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
`;
|
|
66
|
+
}
|
|
67
|
+
const TSCONFIG = {
|
|
68
|
+
compilerOptions: {
|
|
69
|
+
target: "ES2022",
|
|
70
|
+
lib: ["ES2022", "DOM", "DOM.Iterable"],
|
|
71
|
+
module: "ESNext",
|
|
72
|
+
moduleResolution: "Bundler",
|
|
73
|
+
jsx: "react-jsx",
|
|
74
|
+
strict: true,
|
|
75
|
+
noUnusedLocals: true,
|
|
76
|
+
noUnusedParameters: true,
|
|
77
|
+
noEmit: true,
|
|
78
|
+
isolatedModules: true,
|
|
79
|
+
skipLibCheck: true,
|
|
80
|
+
resolveJsonModule: true,
|
|
81
|
+
types: [],
|
|
82
|
+
// The public typings of the runtime API, copied from the agentcrm CLI.
|
|
83
|
+
paths: { "@agent-crm/runtime": ["./types/agentcrm-runtime.d.ts"] }
|
|
84
|
+
},
|
|
85
|
+
include: ["src", "types"]
|
|
86
|
+
};
|
|
87
|
+
function moduleSource(id, name) {
|
|
88
|
+
const tool = toolNameOf(id);
|
|
89
|
+
return `// ${name} — an AgentCRM module extension.
|
|
90
|
+
// - a page at /${id} with a navigation entry,
|
|
91
|
+
// - a tab on every record page,
|
|
92
|
+
// - an AI tool \`${tool}\` (MCP, in-admin assistant, \`agentcrm tools\`),
|
|
93
|
+
// - English + French strings (namespace "${id}": t("${id}:key")).
|
|
94
|
+
// Everything comes from \`@agent-crm/runtime\` (the admin's own instances).
|
|
95
|
+
// Recipes (custom objects, dashboard widgets, automation actions, settings
|
|
96
|
+
// pages with module config): docs/creating-a-module.md.
|
|
97
|
+
${IMPORT} { Puzzle } from "lucide-react";
|
|
98
|
+
${IMPORT} { useTranslation } from "react-i18next";
|
|
99
|
+
${IMPORT} { Card, PageHeader, defineModule, formatDateTime, intlLocale, ops, recordLabel, useActivities, useCurrentUser, useObjects, useRecords, type RecordSlotProps } from "@agent-crm/runtime";
|
|
100
|
+
|
|
101
|
+
const ID = ${JSON.stringify(id)};
|
|
102
|
+
|
|
103
|
+
const en = {
|
|
104
|
+
nav: ${JSON.stringify(name)},
|
|
105
|
+
title: ${JSON.stringify(name)},
|
|
106
|
+
intro: "Hi {{name}}! This page comes from the {{id}} extension.",
|
|
107
|
+
counts: "Records in this workspace",
|
|
108
|
+
tab: ${JSON.stringify(name)},
|
|
109
|
+
tabBody: "“{{label}}” was created {{date}} and has {{count}} activities.",
|
|
110
|
+
};
|
|
111
|
+
const fr: typeof en = {
|
|
112
|
+
nav: ${JSON.stringify(name)},
|
|
113
|
+
title: ${JSON.stringify(name)},
|
|
114
|
+
intro: "Bonjour {{name}} ! Cette page vient de l'extension {{id}}.",
|
|
115
|
+
counts: "Fiches de cet espace",
|
|
116
|
+
tab: ${JSON.stringify(name)},
|
|
117
|
+
tabBody: "« {{label}} » a été créée le {{date}} et a {{count}} activités.",
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
function MainPage() {
|
|
121
|
+
const { t } = useTranslation(ID);
|
|
122
|
+
const me = useCurrentUser();
|
|
123
|
+
const objects = useObjects();
|
|
124
|
+
const records = useRecords(undefined);
|
|
125
|
+
return (
|
|
126
|
+
<div>
|
|
127
|
+
<PageHeader title={t("title")} icon={<Puzzle />} />
|
|
128
|
+
<div className="space-y-4 p-6">
|
|
129
|
+
<p className="text-sm text-surface-600 dark:text-surface-300">{t("intro", { name: me.name, id: ID })}</p>
|
|
130
|
+
<Card title={t("counts")}>
|
|
131
|
+
<ul className="space-y-1 text-sm">
|
|
132
|
+
{objects.map((o) => (
|
|
133
|
+
<li key={o.id} className="flex justify-between">
|
|
134
|
+
<span>{o.labelPlural}</span>
|
|
135
|
+
<span className="tabular-nums text-surface-500">{records.filter((r) => r.object === o.id).length}</span>
|
|
136
|
+
</li>
|
|
137
|
+
))}
|
|
138
|
+
</ul>
|
|
139
|
+
</Card>
|
|
140
|
+
</div>
|
|
141
|
+
</div>
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function RecordTab({ record, object }: RecordSlotProps) {
|
|
146
|
+
const { t } = useTranslation(ID);
|
|
147
|
+
const activities = useActivities(record.id);
|
|
148
|
+
return <p className="p-4 text-sm text-surface-600 dark:text-surface-300">{t("tabBody", { label: recordLabel(object, record), date: formatDateTime(record.createdAt, intlLocale()), count: activities.length })}</p>;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export default defineModule({
|
|
152
|
+
id: ID,
|
|
153
|
+
apiVersion: ${JSON.stringify(EXTENSION_API_VERSION)},
|
|
154
|
+
feature: {
|
|
155
|
+
routes: [{ path: "/${id}", element: <MainPage /> }],
|
|
156
|
+
navItems: [{ id: ID, label: \`\${ID}:nav\`, icon: Puzzle, to: "/${id}", section: "workspace", order: 60 }],
|
|
157
|
+
i18n: { en, fr },
|
|
158
|
+
slots: { recordDetailTabs: [{ id: ID, label: \`\${ID}:tab\`, icon: Puzzle, order: 90, component: RecordTab }] },
|
|
159
|
+
},
|
|
160
|
+
aiTools: [
|
|
161
|
+
{
|
|
162
|
+
name: ${JSON.stringify(tool)},
|
|
163
|
+
description: "Example tool of the ${id} extension: how many records each object holds (optionally only one object).",
|
|
164
|
+
inputSchema: { type: "object", properties: { object: { type: "string", description: "Only this object id (e.g. companies)." } } },
|
|
165
|
+
mutating: false,
|
|
166
|
+
handler: (ctx, input) => {
|
|
167
|
+
const objects = ops.listObjects(ctx).filter((o) => !input.object || o.id === input.object);
|
|
168
|
+
return { counts: Object.fromEntries(objects.map((o) => [o.id, ops.listRecords(ctx, o.id, { limit: 1 }).total])) };
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
],
|
|
172
|
+
});
|
|
173
|
+
`;
|
|
174
|
+
}
|
|
175
|
+
function themeSource(id, name, description) {
|
|
176
|
+
return `// ${name} — an AgentCRM theme extension. A theme is a set of appearance
|
|
177
|
+
// values (any subset of Settings → Appearance) plus optional CSS
|
|
178
|
+
// (src/style.css, packed as style.css, injected while the theme is
|
|
179
|
+
// selected). Users can still override any value in Settings → Appearance.
|
|
180
|
+
// Values and CSS variables: docs/creating-a-theme.md, docs/appearance.md.
|
|
181
|
+
${IMPORT} { defineTheme } from "@agent-crm/runtime";
|
|
182
|
+
|
|
183
|
+
export default defineTheme({
|
|
184
|
+
id: ${JSON.stringify(id)},
|
|
185
|
+
apiVersion: ${JSON.stringify(EXTENSION_API_VERSION)},
|
|
186
|
+
name: ${JSON.stringify(name)},
|
|
187
|
+
description: ${JSON.stringify(description)},
|
|
188
|
+
appearance: {
|
|
189
|
+
accent: "#0f766e",
|
|
190
|
+
neutral: "stone",
|
|
191
|
+
radius: "lg",
|
|
192
|
+
density: "comfortable",
|
|
193
|
+
font: "system",
|
|
194
|
+
sidebar: "dark",
|
|
195
|
+
buttons: "accent",
|
|
196
|
+
contrast: "normal",
|
|
197
|
+
defaultMode: "system",
|
|
198
|
+
},
|
|
199
|
+
});
|
|
200
|
+
`;
|
|
201
|
+
}
|
|
202
|
+
const THEME_CSS = `/* Extra CSS of the theme (≤ 256 KB), injected while it is selected.
|
|
203
|
+
Prefer the appearance values in src/index.ts; use this for details.
|
|
204
|
+
Colors are "r g b" triplets: rgb(var(--acc-500)). Variables:
|
|
205
|
+
--acc-50…950, --srf-50…950 (+850), --primary, --radius-scale,
|
|
206
|
+
--font-sans, --row-h, --ctl-h, --card-pad… (docs/appearance.md). */
|
|
207
|
+
|
|
208
|
+
/* Slightly stronger page headers. */
|
|
209
|
+
html:root h1 {
|
|
210
|
+
letter-spacing: -0.01em;
|
|
211
|
+
}
|
|
212
|
+
`;
|
|
213
|
+
function readme(kind, id, name, cliVersion) {
|
|
214
|
+
const cli = `npx -y ${PACKAGE_NAME}@${cliVersion}`;
|
|
215
|
+
return `# ${name}
|
|
216
|
+
|
|
217
|
+
An AgentCRM ${kind} extension (\`${id}\`).
|
|
218
|
+
|
|
219
|
+
## Develop
|
|
220
|
+
|
|
221
|
+
\`\`\`bash
|
|
222
|
+
npm install
|
|
223
|
+
npm run typecheck # against types/agentcrm-runtime.d.ts (the runtime API)
|
|
224
|
+
npm run build # → dist/bundle.js
|
|
225
|
+
npm run check # validates and loads the bundle like the admin does
|
|
226
|
+
npm run pack # → ${id}-<version>.zip
|
|
227
|
+
\`\`\`
|
|
228
|
+
|
|
229
|
+
Install it on a CRM (shows a plan first — installing runs this code with the
|
|
230
|
+
rights of every admin who opens the CRM):
|
|
231
|
+
|
|
232
|
+
\`\`\`bash
|
|
233
|
+
${cli} ext install . --site <alias>
|
|
234
|
+
${cli} ext install . --site <alias> --yes
|
|
235
|
+
\`\`\`
|
|
236
|
+
|
|
237
|
+
Or upload the ZIP in the admin: Settings → Modules → Installed extensions.
|
|
238
|
+
|
|
239
|
+
## Files
|
|
240
|
+
|
|
241
|
+
- \`manifest.json\` — id, kind, name, version (bump it for every release), apiVersion, description${kind === "module" ? ", icon, category, dependsOn" : ", style"}.
|
|
242
|
+
- \`src/index.${kind === "module" ? "tsx" : "ts"}\` — the default export (\`${kind === "module" ? "defineModule" : "defineTheme"}({...})\`).${kind === "theme" ? "\n- `src/style.css` — extra CSS packed as style.css." : ""}
|
|
243
|
+
- \`types/agentcrm-runtime.d.ts\` — the runtime API typings (copied from the CLI; refresh with \`${cli} ext new ${kind} ${id} --dir /tmp/x\` and copy the file).
|
|
244
|
+
- \`vite.config.ts\` — keeps react, react-i18next, lucide-react and @agent-crm/runtime external (the admin provides them).
|
|
245
|
+
|
|
246
|
+
Docs: https://github.com/fleox/agentcrm/blob/main/docs/${kind === "module" ? "creating-a-module.md" : "creating-a-theme.md"}
|
|
247
|
+
`;
|
|
248
|
+
}
|
|
249
|
+
function scaffoldExtension(opts) {
|
|
250
|
+
const { kind, id, dir } = opts;
|
|
251
|
+
if (!EXTENSION_ID_RE.test(id)) throw new CliError("usage", `Invalid id "${id}".`, "3–50 characters: lowercase letters, digits and dashes, starting with a letter (e.g. my-module).", 2);
|
|
252
|
+
if (RESERVED_EXTENSION_IDS.includes(id)) throw new CliError("usage", `"${id}" is reserved (built-in module or reserved word).`, "Pick another id, e.g. acme-" + id + ".", 2);
|
|
253
|
+
const name = opts.name?.trim() || titleOf(id);
|
|
254
|
+
const description = opts.description?.trim() || (kind === "module" ? `${name}: a page, a record tab and an AI tool for AgentCRM.` : `${name}: an AgentCRM theme.`);
|
|
255
|
+
const cliVersion = readVersionInfo().version;
|
|
256
|
+
const typings = [join(packageRoot(), "types", "agentcrm-runtime.d.ts"), join(ownPackageRoot(), "types", "agentcrm-runtime.d.ts")].find((p) => existsSync(p)) ?? join(ownPackageRoot(), "types", "agentcrm-runtime.d.ts");
|
|
257
|
+
if (!existsSync(typings)) throw new CliError("internal", `types/agentcrm-runtime.d.ts not found next to the CLI (${typings}).`);
|
|
258
|
+
const manifest = {
|
|
259
|
+
id,
|
|
260
|
+
kind,
|
|
261
|
+
name,
|
|
262
|
+
version: "0.1.0",
|
|
263
|
+
apiVersion: EXTENSION_API_VERSION,
|
|
264
|
+
description,
|
|
265
|
+
...opts.author ? { author: opts.author } : {},
|
|
266
|
+
entry: "bundle.js",
|
|
267
|
+
...kind === "module" ? { icon: "Puzzle", category: "customisation" } : { style: "style.css" }
|
|
268
|
+
};
|
|
269
|
+
const cli = `npx -y ${PACKAGE_NAME}@${cliVersion}`;
|
|
270
|
+
const pkg = {
|
|
271
|
+
name: id,
|
|
272
|
+
version: "0.1.0",
|
|
273
|
+
private: true,
|
|
274
|
+
type: "module",
|
|
275
|
+
description,
|
|
276
|
+
scripts: {
|
|
277
|
+
build: "vite build",
|
|
278
|
+
typecheck: "tsc --noEmit",
|
|
279
|
+
check: `${cli} ext check .`,
|
|
280
|
+
pack: `${cli} ext pack .`
|
|
281
|
+
},
|
|
282
|
+
devDependencies: DEV_DEPENDENCIES
|
|
283
|
+
};
|
|
284
|
+
const entry = kind === "module" ? "src/index.tsx" : "src/index.ts";
|
|
285
|
+
const files = {
|
|
286
|
+
"package.json": JSON.stringify(pkg, null, 2) + "\n",
|
|
287
|
+
"manifest.json": JSON.stringify(manifest, null, 2) + "\n",
|
|
288
|
+
"vite.config.ts": viteConfig(entry),
|
|
289
|
+
"tsconfig.json": JSON.stringify(TSCONFIG, null, 2) + "\n",
|
|
290
|
+
"README.md": readme(kind, id, name, cliVersion),
|
|
291
|
+
".gitignore": "node_modules/\ndist/\n*.zip\n",
|
|
292
|
+
[entry]: kind === "module" ? moduleSource(id, name) : themeSource(id, name, description),
|
|
293
|
+
...kind === "theme" ? { "src/style.css": THEME_CSS } : {}
|
|
294
|
+
};
|
|
295
|
+
for (const [rel, content] of Object.entries(files)) {
|
|
296
|
+
const abs = join(dir, rel);
|
|
297
|
+
mkdirSync(dirname(abs), { recursive: true });
|
|
298
|
+
writeFileSync(abs, content);
|
|
299
|
+
}
|
|
300
|
+
mkdirSync(join(dir, "types"), { recursive: true });
|
|
301
|
+
copyFileSync(typings, join(dir, "types", "agentcrm-runtime.d.ts"));
|
|
302
|
+
return {
|
|
303
|
+
kind,
|
|
304
|
+
id,
|
|
305
|
+
dir,
|
|
306
|
+
files: [...Object.keys(files), "types/agentcrm-runtime.d.ts"].sort(),
|
|
307
|
+
next: [
|
|
308
|
+
`cd ${dir}`,
|
|
309
|
+
"npm install",
|
|
310
|
+
"npm run build",
|
|
311
|
+
`${cli} ext check .`,
|
|
312
|
+
`${cli} ext install . --site <alias> (plan; add --yes once the user agrees)`
|
|
313
|
+
]
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
export {
|
|
317
|
+
scaffoldExtension,
|
|
318
|
+
titleOf,
|
|
319
|
+
toolNameOf
|
|
320
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { createRequire as __acrmRequire } from "node:module";
|
|
2
|
+
import { fileURLToPath as __acrmPath } from "node:url";
|
|
3
|
+
import { dirname as __acrmDirname } from "node:path";
|
|
4
|
+
const require2 = __acrmRequire(import.meta.url);
|
|
5
|
+
const __filename = __acrmPath(import.meta.url);
|
|
6
|
+
const __dirname = __acrmDirname(__filename);
|
|
7
|
+
const ACTION_ID_RE = /^[a-z][a-z0-9-]*\.[A-Za-z][A-Za-z0-9_-]*$/;
|
|
8
|
+
const actions = /* @__PURE__ */ new Map();
|
|
9
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
10
|
+
function notify() {
|
|
11
|
+
for (const l of [...listeners]) l();
|
|
12
|
+
}
|
|
13
|
+
function registerAutomationAction(def) {
|
|
14
|
+
if (!def || typeof def.id !== "string" || !ACTION_ID_RE.test(def.id)) {
|
|
15
|
+
throw new Error(`Invalid automation action id "${String(def?.id)}": use "<moduleId>.<action>".`);
|
|
16
|
+
}
|
|
17
|
+
if (typeof def.run !== "function") throw new Error(`Automation action "${def.id}" needs a run() function.`);
|
|
18
|
+
if (actions.has(def.id)) throw new Error(`Automation action "${def.id}" is already registered.`);
|
|
19
|
+
actions.set(def.id, def);
|
|
20
|
+
notify();
|
|
21
|
+
return () => {
|
|
22
|
+
if (actions.get(def.id) === def) {
|
|
23
|
+
actions.delete(def.id);
|
|
24
|
+
notify();
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function getAutomationAction(id) {
|
|
29
|
+
return actions.get(id);
|
|
30
|
+
}
|
|
31
|
+
function listAutomationActions() {
|
|
32
|
+
return [...actions.values()];
|
|
33
|
+
}
|
|
34
|
+
function subscribeAutomationActions(listener) {
|
|
35
|
+
listeners.add(listener);
|
|
36
|
+
return () => listeners.delete(listener);
|
|
37
|
+
}
|
|
38
|
+
function resetAutomationActions() {
|
|
39
|
+
actions.clear();
|
|
40
|
+
notify();
|
|
41
|
+
}
|
|
42
|
+
export {
|
|
43
|
+
getAutomationAction,
|
|
44
|
+
listAutomationActions,
|
|
45
|
+
registerAutomationAction,
|
|
46
|
+
resetAutomationActions,
|
|
47
|
+
subscribeAutomationActions
|
|
48
|
+
};
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { createRequire as __acrmRequire } from "node:module";
|
|
2
|
+
import { fileURLToPath as __acrmPath } from "node:url";
|
|
3
|
+
import { dirname as __acrmDirname } from "node:path";
|
|
4
|
+
const require2 = __acrmRequire(import.meta.url);
|
|
5
|
+
const __filename = __acrmPath(import.meta.url);
|
|
6
|
+
const __dirname = __acrmDirname(__filename);
|
|
7
|
+
import { a as EXTENSION_LIMITS, i as isCompatibleApiVersion } from "./api-B6OqNVWp.mjs";
|
|
8
|
+
import { validatePackage, ExtensionPackageError } from "./package-DWbn3t2R.mjs";
|
|
9
|
+
import { assertCan, OpsError } from "./context-CxFfqS4D.mjs";
|
|
10
|
+
import { moduleStates, setModuleEnabled } from "./modules-CqdVQVJb.mjs";
|
|
11
|
+
import { extensionDocOf, asDoc, extensionDocsOf } from "./store-5gqRSKXg.mjs";
|
|
12
|
+
import "./index-w03ReRs4.mjs";
|
|
13
|
+
import "./_commonjsHelpers-ClciMIcB.mjs";
|
|
14
|
+
import "stream";
|
|
15
|
+
import "events";
|
|
16
|
+
import "buffer";
|
|
17
|
+
import "util";
|
|
18
|
+
import "./pluginRegistry-DyHtlkcr.mjs";
|
|
19
|
+
import "./settings-BtvR_jdm.mjs";
|
|
20
|
+
import "./runtimeConfig-CCOFg1RM.mjs";
|
|
21
|
+
const bytes = (s) => s ? new TextEncoder().encode(s).length : 0;
|
|
22
|
+
function summarizeExtension(doc) {
|
|
23
|
+
const m = doc.manifest;
|
|
24
|
+
return {
|
|
25
|
+
id: doc.id,
|
|
26
|
+
kind: doc.kind,
|
|
27
|
+
name: m.name,
|
|
28
|
+
version: m.version,
|
|
29
|
+
apiVersion: m.apiVersion,
|
|
30
|
+
description: m.description,
|
|
31
|
+
...m.author ? { author: m.author } : {},
|
|
32
|
+
...m.homepage ? { homepage: m.homepage } : {},
|
|
33
|
+
enabled: !!doc.enabled,
|
|
34
|
+
compatible: isCompatibleApiVersion(m.apiVersion),
|
|
35
|
+
installedAt: doc.installedAt,
|
|
36
|
+
...doc.installedBy ? { installedBy: doc.installedBy } : {},
|
|
37
|
+
updatedAt: doc.updatedAt,
|
|
38
|
+
...doc.previousVersion ? { previousVersion: doc.previousVersion } : {},
|
|
39
|
+
...m.dependsOn?.length ? { dependsOn: m.dependsOn } : {},
|
|
40
|
+
...m.category ? { category: m.category } : {},
|
|
41
|
+
...m.icon ? { icon: m.icon } : {},
|
|
42
|
+
size: { bundle: bytes(doc.files?.["bundle.js"]), style: bytes(doc.files?.["style.css"]) }
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function listExtensions(ctx, opts = {}) {
|
|
46
|
+
assertCan(ctx, "settings.read");
|
|
47
|
+
return extensionDocsOf(ctx.store).filter((d) => !opts.kind || d.kind === opts.kind).map(summarizeExtension).sort((a, b) => a.name.localeCompare(b.name));
|
|
48
|
+
}
|
|
49
|
+
function requireExtension(ctx, id) {
|
|
50
|
+
const doc = extensionDocOf(ctx.store, id);
|
|
51
|
+
if (!doc) {
|
|
52
|
+
const known = extensionDocsOf(ctx.store).map((d) => d.id);
|
|
53
|
+
throw new OpsError("notFound", `Extension "${id}" is not installed.${known.length ? ` Installed: ${known.join(", ")}.` : ""}`);
|
|
54
|
+
}
|
|
55
|
+
return doc;
|
|
56
|
+
}
|
|
57
|
+
function getExtension(ctx, id) {
|
|
58
|
+
assertCan(ctx, "settings.read");
|
|
59
|
+
return summarizeExtension(requireExtension(ctx, id));
|
|
60
|
+
}
|
|
61
|
+
function getExtensionPackage(ctx, id) {
|
|
62
|
+
assertCan(ctx, "settings.read");
|
|
63
|
+
const doc = requireExtension(ctx, id);
|
|
64
|
+
return { manifest: doc.manifest, files: { ...doc.files } };
|
|
65
|
+
}
|
|
66
|
+
async function installExtension(ctx, pkg, opts = {}) {
|
|
67
|
+
assertCan(ctx, "extensions.write");
|
|
68
|
+
let valid;
|
|
69
|
+
try {
|
|
70
|
+
valid = validatePackage(pkg);
|
|
71
|
+
} catch (err) {
|
|
72
|
+
if (err instanceof ExtensionPackageError) throw new OpsError(err.code === "incompatibleApi" ? "invalid" : "validation", err.message, { code: err.code, details: err.details });
|
|
73
|
+
throw err;
|
|
74
|
+
}
|
|
75
|
+
const { manifest, files } = valid;
|
|
76
|
+
const total = bytes(files["bundle.js"]) + bytes(files["style.css"]);
|
|
77
|
+
if (total > EXTENSION_LIMITS.bundle + EXTENSION_LIMITS.style) throw new OpsError("validation", "The package is too large.");
|
|
78
|
+
const existing = extensionDocOf(ctx.store, manifest.id);
|
|
79
|
+
if (existing && existing.kind !== manifest.kind) {
|
|
80
|
+
throw new OpsError("conflict", `"${manifest.id}" is installed as a ${existing.kind}; this package is a ${manifest.kind}. Uninstall it first.`);
|
|
81
|
+
}
|
|
82
|
+
const warnings = [];
|
|
83
|
+
let enabled = existing ? existing.enabled : opts.enabled ?? true;
|
|
84
|
+
if (opts.enabled !== void 0) enabled = opts.enabled;
|
|
85
|
+
if (enabled && manifest.kind === "module" && manifest.dependsOn?.length) {
|
|
86
|
+
const states = new Map(moduleStates(ctx.store).map((m) => [m.id, m]));
|
|
87
|
+
const missing = manifest.dependsOn.filter((d) => !states.get(d)?.enabled);
|
|
88
|
+
if (missing.length) {
|
|
89
|
+
enabled = false;
|
|
90
|
+
warnings.push(`Installed disabled: it needs ${missing.join(", ")} (${missing.map((d) => states.has(d) ? "disabled" : "not installed").join(", ")}).`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
const now = ctx.now();
|
|
94
|
+
const doc = {
|
|
95
|
+
id: manifest.id,
|
|
96
|
+
kind: manifest.kind,
|
|
97
|
+
manifest,
|
|
98
|
+
enabled,
|
|
99
|
+
files,
|
|
100
|
+
installedAt: existing?.installedAt ?? now,
|
|
101
|
+
...ctx.user?.id ? { installedBy: ctx.user.id } : existing?.installedBy ? { installedBy: existing.installedBy } : {},
|
|
102
|
+
updatedAt: now,
|
|
103
|
+
...existing ? { previousVersion: existing.manifest.version } : {}
|
|
104
|
+
};
|
|
105
|
+
await ctx.store.write([{ op: "set", collection: "extensions", doc: asDoc(doc) }], { message: `${existing ? "Upgrade" : "Install"} extension ${manifest.id} ${manifest.version}` });
|
|
106
|
+
ctx.log?.(`extension ${manifest.id} ${existing ? `upgraded ${existing.manifest.version} → ${manifest.version}` : `installed (${manifest.version})`}`);
|
|
107
|
+
return {
|
|
108
|
+
id: manifest.id,
|
|
109
|
+
kind: manifest.kind,
|
|
110
|
+
version: manifest.version,
|
|
111
|
+
mode: existing ? "upgrade" : "install",
|
|
112
|
+
...existing ? { previousVersion: existing.manifest.version } : {},
|
|
113
|
+
enabled,
|
|
114
|
+
warnings
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
async function setExtensionEnabled(ctx, id, enabled, opts = {}) {
|
|
118
|
+
assertCan(ctx, "extensions.write");
|
|
119
|
+
const doc = requireExtension(ctx, id);
|
|
120
|
+
if (enabled && !isCompatibleApiVersion(doc.manifest.apiVersion)) throw new OpsError("invalid", `"${doc.manifest.name}" targets an unsupported API version (${doc.manifest.apiVersion}).`);
|
|
121
|
+
if (doc.kind === "module") {
|
|
122
|
+
await setModuleEnabled(ctx, id, enabled, opts);
|
|
123
|
+
} else if (doc.enabled !== enabled) {
|
|
124
|
+
const mutations = [{ op: "patch", collection: "extensions", id, fields: { enabled, updatedAt: ctx.now() } }];
|
|
125
|
+
if (!enabled && ctx.store.get("config", "appearance")?.themeId === id) mutations.push({ op: "patch", collection: "config", id: "appearance", fields: { themeId: void 0 } });
|
|
126
|
+
await ctx.store.write(mutations, { message: `${enabled ? "Enable" : "Disable"} theme ${id}` });
|
|
127
|
+
}
|
|
128
|
+
return summarizeExtension(requireExtension(ctx, id));
|
|
129
|
+
}
|
|
130
|
+
async function uninstallExtension(ctx, id, opts = {}) {
|
|
131
|
+
assertCan(ctx, "extensions.write");
|
|
132
|
+
const doc = requireExtension(ctx, id);
|
|
133
|
+
if (doc.kind === "module" && !opts.force) {
|
|
134
|
+
const dependents = moduleStates(ctx.store).filter((m) => m.enabled && m.dependsOn.includes(id));
|
|
135
|
+
if (dependents.length) throw new OpsError("conflict", `${dependents.map((d) => `"${d.name}"`).join(", ")} depend${dependents.length > 1 ? "" : "s"} on "${doc.manifest.name}". Disable or uninstall ${dependents.length > 1 ? "them" : "it"} first.`, { dependents: dependents.map((d) => d.id) });
|
|
136
|
+
}
|
|
137
|
+
const mutations = [{ op: "delete", collection: "extensions", id }];
|
|
138
|
+
if (ctx.store.get("config", "appearance")?.themeId === id) mutations.push({ op: "patch", collection: "config", id: "appearance", fields: { themeId: void 0 } });
|
|
139
|
+
await ctx.store.write(mutations, { message: `Uninstall extension ${id}` });
|
|
140
|
+
ctx.log?.(`extension ${id} uninstalled`);
|
|
141
|
+
return { id, removed: true };
|
|
142
|
+
}
|
|
143
|
+
export {
|
|
144
|
+
getExtension,
|
|
145
|
+
getExtensionPackage,
|
|
146
|
+
installExtension,
|
|
147
|
+
listExtensions,
|
|
148
|
+
setExtensionEnabled,
|
|
149
|
+
summarizeExtension,
|
|
150
|
+
uninstallExtension
|
|
151
|
+
};
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { createRequire as __acrmRequire } from "node:module";
|
|
2
|
+
import { fileURLToPath as __acrmPath } from "node:url";
|
|
3
|
+
import { dirname as __acrmDirname } from "node:path";
|
|
4
|
+
const require2 = __acrmRequire(import.meta.url);
|
|
5
|
+
const __filename = __acrmPath(import.meta.url);
|
|
6
|
+
const __dirname = __acrmDirname(__filename);
|
|
7
|
+
import { r as reactExports } from "./index-Bnmav-Oh.mjs";
|
|
8
|
+
import { r as registerI18nNamespace } from "./index-BM8qeGOb.mjs";
|
|
9
|
+
import { moduleStates } from "./modules-CqdVQVJb.mjs";
|
|
10
|
+
import "./_commonjsHelpers-ClciMIcB.mjs";
|
|
11
|
+
import "./api-B6OqNVWp.mjs";
|
|
12
|
+
import "./context-CxFfqS4D.mjs";
|
|
13
|
+
import "./pluginRegistry-DyHtlkcr.mjs";
|
|
14
|
+
import "./settings-BtvR_jdm.mjs";
|
|
15
|
+
import "./store-5gqRSKXg.mjs";
|
|
16
|
+
import "./runtimeConfig-CCOFg1RM.mjs";
|
|
17
|
+
let builtins = [];
|
|
18
|
+
const externals = /* @__PURE__ */ new Map();
|
|
19
|
+
let disabled = /* @__PURE__ */ new Set();
|
|
20
|
+
let modules = [];
|
|
21
|
+
let version = 0;
|
|
22
|
+
let cache = null;
|
|
23
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
24
|
+
function notify() {
|
|
25
|
+
version++;
|
|
26
|
+
cache = null;
|
|
27
|
+
for (const l of [...listeners]) l();
|
|
28
|
+
}
|
|
29
|
+
function registerFeatures(list) {
|
|
30
|
+
builtins = list;
|
|
31
|
+
for (const f of list) registerI18nNamespace(f.id, f.i18n);
|
|
32
|
+
notify();
|
|
33
|
+
}
|
|
34
|
+
function registerExternalFeature(feature) {
|
|
35
|
+
if (builtins.some((f) => f.id === feature.id)) throw new Error(`Feature id "${feature.id}" is taken by a built-in feature.`);
|
|
36
|
+
externals.set(feature.id, feature);
|
|
37
|
+
registerI18nNamespace(feature.id, feature.i18n);
|
|
38
|
+
notify();
|
|
39
|
+
return () => {
|
|
40
|
+
if (externals.get(feature.id) === feature) {
|
|
41
|
+
externals.delete(feature.id);
|
|
42
|
+
notify();
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function getAllFeatures() {
|
|
47
|
+
return [...builtins, ...externals.values()];
|
|
48
|
+
}
|
|
49
|
+
function getFeatures() {
|
|
50
|
+
if (cache && cache.version === version) return cache.enabled;
|
|
51
|
+
const enabled = getAllFeatures().filter((f) => !f.module || f.module.required || !disabled.has(f.id));
|
|
52
|
+
cache = { version, enabled };
|
|
53
|
+
return enabled;
|
|
54
|
+
}
|
|
55
|
+
function getFeature(id) {
|
|
56
|
+
return getFeatures().find((f) => f.id === id);
|
|
57
|
+
}
|
|
58
|
+
function isModuleEnabled(id) {
|
|
59
|
+
return !disabled.has(id);
|
|
60
|
+
}
|
|
61
|
+
function getModuleStates() {
|
|
62
|
+
return modules;
|
|
63
|
+
}
|
|
64
|
+
function getDisabledModules() {
|
|
65
|
+
return disabled;
|
|
66
|
+
}
|
|
67
|
+
function readModules(store) {
|
|
68
|
+
const next = moduleStates(store);
|
|
69
|
+
const nextDisabled = new Set(next.filter((m) => !m.enabled).map((m) => m.id));
|
|
70
|
+
const changed = JSON.stringify(next) !== JSON.stringify(modules);
|
|
71
|
+
modules = next;
|
|
72
|
+
if (changed || [...nextDisabled].join() !== [...disabled].join()) {
|
|
73
|
+
disabled = nextDisabled;
|
|
74
|
+
notify();
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
let unsubscribeStore = null;
|
|
78
|
+
function connectModuleState(store) {
|
|
79
|
+
unsubscribeStore?.();
|
|
80
|
+
readModules(store);
|
|
81
|
+
const off = store.subscribe((changed) => {
|
|
82
|
+
if (changed.has("config") || changed.has("extensions")) readModules(store);
|
|
83
|
+
});
|
|
84
|
+
unsubscribeStore = () => {
|
|
85
|
+
off();
|
|
86
|
+
unsubscribeStore = null;
|
|
87
|
+
};
|
|
88
|
+
return unsubscribeStore;
|
|
89
|
+
}
|
|
90
|
+
function setDisabledModules(ids) {
|
|
91
|
+
disabled = new Set(ids);
|
|
92
|
+
notify();
|
|
93
|
+
}
|
|
94
|
+
function subscribeFeatures(listener) {
|
|
95
|
+
listeners.add(listener);
|
|
96
|
+
return () => listeners.delete(listener);
|
|
97
|
+
}
|
|
98
|
+
function featureRegistryVersion() {
|
|
99
|
+
return version;
|
|
100
|
+
}
|
|
101
|
+
function getSlot(name) {
|
|
102
|
+
const out = [];
|
|
103
|
+
for (const f of getFeatures()) {
|
|
104
|
+
const v = f.slots?.[name];
|
|
105
|
+
if (v === void 0) continue;
|
|
106
|
+
if (Array.isArray(v)) out.push(...v);
|
|
107
|
+
else out.push(v);
|
|
108
|
+
}
|
|
109
|
+
return out.sort((a, b) => (a.order ?? 100) - (b.order ?? 100));
|
|
110
|
+
}
|
|
111
|
+
function useFeatureRegistryVersion() {
|
|
112
|
+
return reactExports.useSyncExternalStore(subscribeFeatures, featureRegistryVersion, featureRegistryVersion);
|
|
113
|
+
}
|
|
114
|
+
function useFeatures() {
|
|
115
|
+
useFeatureRegistryVersion();
|
|
116
|
+
return getFeatures();
|
|
117
|
+
}
|
|
118
|
+
function useSlot(name) {
|
|
119
|
+
useFeatureRegistryVersion();
|
|
120
|
+
return getSlot(name);
|
|
121
|
+
}
|
|
122
|
+
function useModuleEnabled(id) {
|
|
123
|
+
useFeatureRegistryVersion();
|
|
124
|
+
return isModuleEnabled(id);
|
|
125
|
+
}
|
|
126
|
+
function useModuleStates() {
|
|
127
|
+
useFeatureRegistryVersion();
|
|
128
|
+
return modules;
|
|
129
|
+
}
|
|
130
|
+
export {
|
|
131
|
+
connectModuleState,
|
|
132
|
+
featureRegistryVersion,
|
|
133
|
+
getAllFeatures,
|
|
134
|
+
getDisabledModules,
|
|
135
|
+
getFeature,
|
|
136
|
+
getFeatures,
|
|
137
|
+
getModuleStates,
|
|
138
|
+
getSlot,
|
|
139
|
+
isModuleEnabled,
|
|
140
|
+
registerExternalFeature,
|
|
141
|
+
registerFeatures,
|
|
142
|
+
setDisabledModules,
|
|
143
|
+
subscribeFeatures,
|
|
144
|
+
useFeatureRegistryVersion,
|
|
145
|
+
useFeatures,
|
|
146
|
+
useModuleEnabled,
|
|
147
|
+
useModuleStates,
|
|
148
|
+
useSlot
|
|
149
|
+
};
|