@alfe.ai/integrations 0.0.3 → 0.0.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/index.d.ts +23 -18
- package/dist/index.js +77 -69
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -8,11 +8,10 @@
|
|
|
8
8
|
interface RegistryEntry {
|
|
9
9
|
/** Human-readable display name (e.g. 'Alfe Voice') */
|
|
10
10
|
name?: string;
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
commit?: string;
|
|
11
|
+
/** GitHub repository URL (HTTPS) for public clone */
|
|
12
|
+
repository: string;
|
|
13
|
+
/** Git commit SHA — installer clones and checks out this exact commit */
|
|
14
|
+
commit: string;
|
|
16
15
|
/** Subdirectory within the repo where the integration lives (for monorepos) */
|
|
17
16
|
subdir?: string;
|
|
18
17
|
versions: string[];
|
|
@@ -103,11 +102,10 @@ declare class Registry {
|
|
|
103
102
|
interface ResolvedIntegration {
|
|
104
103
|
id: string;
|
|
105
104
|
name?: string;
|
|
106
|
-
|
|
105
|
+
repository: string;
|
|
107
106
|
version: string;
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
commit?: string;
|
|
107
|
+
/** Git commit SHA to checkout after clone */
|
|
108
|
+
commit: string;
|
|
111
109
|
/** Subdirectory within the repo where the integration lives (for monorepos) */
|
|
112
110
|
subdir?: string;
|
|
113
111
|
description: string;
|
|
@@ -123,7 +121,7 @@ declare class Resolver {
|
|
|
123
121
|
*
|
|
124
122
|
* @param name - Integration name (e.g. "discord")
|
|
125
123
|
* @param version - Specific version (e.g. "1.0.0") or undefined for latest
|
|
126
|
-
* @returns Resolved integration with repo URL and
|
|
124
|
+
* @returns Resolved integration with repo URL and commit hash
|
|
127
125
|
*/
|
|
128
126
|
resolve(id: string, version?: string): Promise<ResolvedIntegration>;
|
|
129
127
|
/**
|
|
@@ -148,8 +146,6 @@ declare class InstallerError extends Error {
|
|
|
148
146
|
}
|
|
149
147
|
declare class Installer {
|
|
150
148
|
private basePath;
|
|
151
|
-
/** Tracks subdir overrides per integration id */
|
|
152
|
-
private subdirs;
|
|
153
149
|
/**
|
|
154
150
|
* @param basePath - Override the integrations directory (for testing).
|
|
155
151
|
* Defaults to ~/.alfe/integrations/
|
|
@@ -157,20 +153,27 @@ declare class Installer {
|
|
|
157
153
|
constructor(basePath?: string);
|
|
158
154
|
/**
|
|
159
155
|
* Get the path where an integration's content lives.
|
|
160
|
-
* When a subdir is set, returns the subdir within the clone.
|
|
161
156
|
*/
|
|
162
157
|
getInstallPath(name: string): string;
|
|
163
158
|
/**
|
|
164
|
-
*
|
|
159
|
+
* Install an integration by cloning its git repo and checking out the pinned commit.
|
|
160
|
+
* For monorepo integrations (with subdir), extracts only the subdir contents.
|
|
161
|
+
*
|
|
162
|
+
* If the install path already exists (e.g. from a failed previous install),
|
|
163
|
+
* it is cleaned up before retrying.
|
|
165
164
|
*/
|
|
166
|
-
|
|
165
|
+
install(resolved: ResolvedIntegration): Promise<string>;
|
|
167
166
|
/**
|
|
168
|
-
*
|
|
169
|
-
* Returns the effective install path (with subdir if present).
|
|
167
|
+
* Clone repo directly to the install path (no subdir).
|
|
170
168
|
*/
|
|
171
|
-
|
|
169
|
+
private cloneDirect;
|
|
170
|
+
/**
|
|
171
|
+
* Clone monorepo to a temp dir, extract the subdir to the install path, discard the clone.
|
|
172
|
+
*/
|
|
173
|
+
private cloneAndExtractSubdir;
|
|
172
174
|
/**
|
|
173
175
|
* Update an installed integration to a new version.
|
|
176
|
+
* Removes old install and does a fresh clone.
|
|
174
177
|
*/
|
|
175
178
|
update(name: string, resolved: ResolvedIntegration): Promise<string>;
|
|
176
179
|
/**
|
|
@@ -287,6 +290,8 @@ interface IntegrationManifest {
|
|
|
287
290
|
config_schema: ConfigSchemaField[];
|
|
288
291
|
capabilities: string[];
|
|
289
292
|
hooks: IntegrationHooks;
|
|
293
|
+
/** Git repository URL (HTTPS, with .git suffix) — used by installer to clone */
|
|
294
|
+
repository: string;
|
|
290
295
|
/**
|
|
291
296
|
* Agent runtimes this integration supports.
|
|
292
297
|
* If omitted or empty, the integration is considered universal (all runtimes).
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { execFile, spawn } from "node:child_process";
|
|
2
2
|
import { promisify } from "node:util";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
|
-
import { homedir } from "node:os";
|
|
5
|
-
import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { homedir, tmpdir } from "node:os";
|
|
5
|
+
import { cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
|
|
6
6
|
import { buildConfigValidationSchema, parseManifestFile } from "@alfe.ai/integration-manifest";
|
|
7
7
|
import { createLogger } from "@auriclabs/logger";
|
|
8
8
|
//#region src/registry.ts
|
|
@@ -83,7 +83,7 @@ var Resolver = class {
|
|
|
83
83
|
*
|
|
84
84
|
* @param name - Integration name (e.g. "discord")
|
|
85
85
|
* @param version - Specific version (e.g. "1.0.0") or undefined for latest
|
|
86
|
-
* @returns Resolved integration with repo URL and
|
|
86
|
+
* @returns Resolved integration with repo URL and commit hash
|
|
87
87
|
*/
|
|
88
88
|
async resolve(id, version) {
|
|
89
89
|
const entry = await this.registry.get(id);
|
|
@@ -93,9 +93,8 @@ var Resolver = class {
|
|
|
93
93
|
return {
|
|
94
94
|
id,
|
|
95
95
|
name: entry.name,
|
|
96
|
-
|
|
96
|
+
repository: entry.repository,
|
|
97
97
|
version: resolvedVersion,
|
|
98
|
-
tag: `v${resolvedVersion}`,
|
|
99
98
|
commit: entry.commit,
|
|
100
99
|
subdir: entry.subdir,
|
|
101
100
|
description: entry.description
|
|
@@ -119,10 +118,12 @@ var Resolver = class {
|
|
|
119
118
|
//#endregion
|
|
120
119
|
//#region src/installer.ts
|
|
121
120
|
/**
|
|
122
|
-
* Installer — git clone
|
|
121
|
+
* Installer — git clone integrations to ~/.alfe/integrations/{name}/.
|
|
123
122
|
*
|
|
124
123
|
* Manages the local installation directory for integration repos.
|
|
125
|
-
* Each integration is
|
|
124
|
+
* Each integration is cloned and checked out to a pinned commit hash.
|
|
125
|
+
* For monorepo integrations (with subdir), only the subdir contents
|
|
126
|
+
* are extracted — the full clone is discarded.
|
|
126
127
|
*/
|
|
127
128
|
const execFileAsync$1 = promisify(execFile);
|
|
128
129
|
const INTEGRATIONS_DIR = join(homedir(), ".alfe", "integrations");
|
|
@@ -135,8 +136,6 @@ var InstallerError = class extends Error {
|
|
|
135
136
|
};
|
|
136
137
|
var Installer = class {
|
|
137
138
|
basePath;
|
|
138
|
-
/** Tracks subdir overrides per integration id */
|
|
139
|
-
subdirs = /* @__PURE__ */ new Map();
|
|
140
139
|
/**
|
|
141
140
|
* @param basePath - Override the integrations directory (for testing).
|
|
142
141
|
* Defaults to ~/.alfe/integrations/
|
|
@@ -146,100 +145,109 @@ var Installer = class {
|
|
|
146
145
|
}
|
|
147
146
|
/**
|
|
148
147
|
* Get the path where an integration's content lives.
|
|
149
|
-
* When a subdir is set, returns the subdir within the clone.
|
|
150
148
|
*/
|
|
151
149
|
getInstallPath(name) {
|
|
152
|
-
const base = join(this.basePath, name);
|
|
153
|
-
const subdir = this.subdirs.get(name);
|
|
154
|
-
return subdir ? join(base, subdir) : base;
|
|
155
|
-
}
|
|
156
|
-
/**
|
|
157
|
-
* Get the root clone path (without subdir).
|
|
158
|
-
*/
|
|
159
|
-
getClonePath(name) {
|
|
160
150
|
return join(this.basePath, name);
|
|
161
151
|
}
|
|
162
152
|
/**
|
|
163
|
-
* Install an integration by cloning its git repo and checking out the
|
|
164
|
-
*
|
|
153
|
+
* Install an integration by cloning its git repo and checking out the pinned commit.
|
|
154
|
+
* For monorepo integrations (with subdir), extracts only the subdir contents.
|
|
155
|
+
*
|
|
156
|
+
* If the install path already exists (e.g. from a failed previous install),
|
|
157
|
+
* it is cleaned up before retrying.
|
|
165
158
|
*/
|
|
166
159
|
async install(resolved) {
|
|
167
|
-
const
|
|
168
|
-
if (existsSync(
|
|
169
|
-
|
|
160
|
+
const installPath = this.getInstallPath(resolved.id);
|
|
161
|
+
if (existsSync(installPath)) rmSync(installPath, {
|
|
162
|
+
recursive: true,
|
|
163
|
+
force: true
|
|
164
|
+
});
|
|
170
165
|
mkdirSync(this.basePath, { recursive: true });
|
|
166
|
+
if (resolved.subdir) await this.cloneAndExtractSubdir(resolved, installPath);
|
|
167
|
+
else await this.cloneDirect(resolved, installPath);
|
|
168
|
+
return installPath;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Clone repo directly to the install path (no subdir).
|
|
172
|
+
*/
|
|
173
|
+
async cloneDirect(resolved, installPath) {
|
|
171
174
|
try {
|
|
172
|
-
|
|
173
|
-
await execFileAsync$1("git", [
|
|
174
|
-
"clone",
|
|
175
|
-
resolved.repo,
|
|
176
|
-
clonePath
|
|
177
|
-
], { timeout: GIT_TIMEOUT_MS });
|
|
178
|
-
await execFileAsync$1("git", ["checkout", resolved.commit], {
|
|
179
|
-
cwd: clonePath,
|
|
180
|
-
timeout: GIT_TIMEOUT_MS
|
|
181
|
-
});
|
|
182
|
-
} else await execFileAsync$1("git", [
|
|
175
|
+
await execFileAsync$1("git", [
|
|
183
176
|
"clone",
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
"--branch",
|
|
187
|
-
resolved.tag,
|
|
188
|
-
resolved.repo,
|
|
189
|
-
clonePath
|
|
177
|
+
resolved.repository,
|
|
178
|
+
installPath
|
|
190
179
|
], { timeout: GIT_TIMEOUT_MS });
|
|
180
|
+
await execFileAsync$1("git", ["checkout", resolved.commit], {
|
|
181
|
+
cwd: installPath,
|
|
182
|
+
timeout: GIT_TIMEOUT_MS
|
|
183
|
+
});
|
|
191
184
|
} catch (err) {
|
|
192
|
-
if (existsSync(
|
|
185
|
+
if (existsSync(installPath)) rmSync(installPath, {
|
|
193
186
|
recursive: true,
|
|
194
187
|
force: true
|
|
195
188
|
});
|
|
196
|
-
this.subdirs.delete(resolved.id);
|
|
197
189
|
const message = err instanceof Error ? err.message : String(err);
|
|
198
190
|
throw new InstallerError(`Failed to clone integration "${resolved.id}": ${message}`);
|
|
199
191
|
}
|
|
200
|
-
return this.getInstallPath(resolved.id);
|
|
201
192
|
}
|
|
202
193
|
/**
|
|
203
|
-
*
|
|
194
|
+
* Clone monorepo to a temp dir, extract the subdir to the install path, discard the clone.
|
|
204
195
|
*/
|
|
205
|
-
async
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
else this.subdirs.delete(name);
|
|
196
|
+
async cloneAndExtractSubdir(resolved, installPath) {
|
|
197
|
+
if (!resolved.subdir) throw new InstallerError("subdir is required for monorepo extraction");
|
|
198
|
+
const subdir = resolved.subdir;
|
|
199
|
+
const tempDir = mkdtempSync(join(tmpdir(), `alfe-clone-${resolved.id}-`));
|
|
210
200
|
try {
|
|
211
201
|
await execFileAsync$1("git", [
|
|
212
|
-
"
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
], {
|
|
219
|
-
cwd: clonePath,
|
|
220
|
-
timeout: GIT_TIMEOUT_MS
|
|
221
|
-
});
|
|
222
|
-
await execFileAsync$1("git", ["checkout", resolved.tag], {
|
|
223
|
-
cwd: clonePath,
|
|
202
|
+
"clone",
|
|
203
|
+
resolved.repository,
|
|
204
|
+
tempDir
|
|
205
|
+
], { timeout: GIT_TIMEOUT_MS });
|
|
206
|
+
await execFileAsync$1("git", ["checkout", resolved.commit], {
|
|
207
|
+
cwd: tempDir,
|
|
224
208
|
timeout: GIT_TIMEOUT_MS
|
|
225
209
|
});
|
|
210
|
+
const subdirPath = join(tempDir, subdir);
|
|
211
|
+
if (!existsSync(subdirPath)) throw new InstallerError(`Subdir "${subdir}" not found in repo after checkout`);
|
|
212
|
+
cpSync(subdirPath, installPath, { recursive: true });
|
|
226
213
|
} catch (err) {
|
|
214
|
+
if (existsSync(installPath)) rmSync(installPath, {
|
|
215
|
+
recursive: true,
|
|
216
|
+
force: true
|
|
217
|
+
});
|
|
218
|
+
if (err instanceof InstallerError) throw err;
|
|
227
219
|
const message = err instanceof Error ? err.message : String(err);
|
|
228
|
-
throw new InstallerError(`Failed to
|
|
220
|
+
throw new InstallerError(`Failed to clone integration "${resolved.id}": ${message}`);
|
|
221
|
+
} finally {
|
|
222
|
+
rmSync(tempDir, {
|
|
223
|
+
recursive: true,
|
|
224
|
+
force: true
|
|
225
|
+
});
|
|
229
226
|
}
|
|
230
|
-
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Update an installed integration to a new version.
|
|
230
|
+
* Removes old install and does a fresh clone.
|
|
231
|
+
*/
|
|
232
|
+
async update(name, resolved) {
|
|
233
|
+
const installPath = this.getInstallPath(name);
|
|
234
|
+
if (!existsSync(installPath)) throw new InstallerError(`Integration "${name}" is not installed — cannot update`);
|
|
235
|
+
rmSync(installPath, {
|
|
236
|
+
recursive: true,
|
|
237
|
+
force: true
|
|
238
|
+
});
|
|
239
|
+
return this.install(resolved);
|
|
231
240
|
}
|
|
232
241
|
/**
|
|
233
242
|
* Remove an installed integration.
|
|
234
243
|
*/
|
|
235
244
|
remove(name) {
|
|
236
|
-
const
|
|
237
|
-
if (!existsSync(
|
|
238
|
-
rmSync(
|
|
245
|
+
const installPath = this.getInstallPath(name);
|
|
246
|
+
if (!existsSync(installPath)) throw new InstallerError(`Integration "${name}" is not installed at ${installPath}`);
|
|
247
|
+
rmSync(installPath, {
|
|
239
248
|
recursive: true,
|
|
240
249
|
force: true
|
|
241
250
|
});
|
|
242
|
-
this.subdirs.delete(name);
|
|
243
251
|
return Promise.resolve();
|
|
244
252
|
}
|
|
245
253
|
/**
|
|
@@ -276,7 +284,7 @@ var Installer = class {
|
|
|
276
284
|
* Check if an integration is installed.
|
|
277
285
|
*/
|
|
278
286
|
isInstalled(name) {
|
|
279
|
-
return existsSync(this.
|
|
287
|
+
return existsSync(this.getInstallPath(name));
|
|
280
288
|
}
|
|
281
289
|
};
|
|
282
290
|
//#endregion
|
|
@@ -711,7 +719,7 @@ var IntegrationManager = class {
|
|
|
711
719
|
});
|
|
712
720
|
try {
|
|
713
721
|
const resolved = await this.resolver.resolve(name, version);
|
|
714
|
-
this.log.info(`Resolved ${name}@${resolved.version} from ${resolved.
|
|
722
|
+
this.log.info(`Resolved ${name}@${resolved.version} from ${resolved.repository}`);
|
|
715
723
|
const installPath = await this.installer.install(resolved);
|
|
716
724
|
this.log.info(`Cloned to ${installPath}`);
|
|
717
725
|
const manifestPath = join(installPath, "alfe-integration.yaml");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alfe.ai/integrations",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
4
4
|
"description": "Integration lifecycle management for Alfe — registry, resolution, installation, and state",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
15
|
"@auriclabs/logger": "^0.1.1",
|
|
16
|
-
"@alfe.ai/integration-manifest": "^0.0.
|
|
16
|
+
"@alfe.ai/integration-manifest": "^0.0.3"
|
|
17
17
|
},
|
|
18
18
|
"files": [
|
|
19
19
|
"dist"
|