@agentxm/extension-model 0.34.0 → 0.36.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/dist/src/unstable/agent-capabilities/derive.js +1 -3
- package/dist/src/unstable/agent-capabilities/identity.d.ts +2 -0
- package/dist/src/unstable/agent-capabilities/identity.js +3 -0
- package/dist/src/unstable/agent-capabilities/index.d.ts +1 -1
- package/dist/src/unstable/agent-capabilities/index.js +1 -1
- package/dist/src/unstable/agents/registry.d.ts +3 -17
- package/dist/src/unstable/agents/registry.js +2 -17
- package/dist/src/unstable/agents/types.d.ts +5 -7
- package/dist/src/unstable/agents/types.js +2 -4
- package/dist/src/unstable/extensions/common.d.ts +20 -8
- package/dist/src/unstable/extensions/common.js +29 -32
- package/dist/src/unstable/extensions/deprecation.d.ts +24 -3
- package/dist/src/unstable/extensions/deprecation.js +22 -3
- package/dist/src/unstable/extensions/index.d.ts +1 -1
- package/dist/src/unstable/extensions/index.js +1 -1
- package/dist/src/unstable/extensions/name-patterns.d.ts +5 -3
- package/dist/src/unstable/extensions/name-patterns.js +31 -32
- package/dist/src/unstable/extensions/refs/extension-ref.d.ts +9 -0
- package/dist/src/unstable/extensions/refs/extension-ref.js +25 -1
- package/dist/src/unstable/extensions/registry-source.d.ts +12 -2
- package/dist/src/unstable/extensions/registry-source.js +17 -32
- package/dist/src/unstable/path-types.d.ts +6 -0
- package/dist/src/unstable/path-types.js +20 -0
- package/dist/src/unstable/sources/forge-grammar.d.ts +46 -10
- package/dist/src/unstable/sources/forge-grammar.js +153 -33
- package/dist/src/unstable/sources/parser.d.ts +9 -7
- package/dist/src/unstable/sources/parser.js +14 -31
- package/dist/src/unstable/sources/printer.js +10 -35
- package/dist/src/unstable/sources/source-host-provider.d.ts +10 -6
- package/dist/src/unstable/sources/types.d.ts +1 -93
- package/dist/src/unstable/sources/types.js +1 -48
- package/dist/src/unstable/version-constraints/version-constraints.d.ts +9 -11
- package/dist/src/unstable/version-constraints/version-constraints.js +14 -43
- package/package.json +3 -3
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import * as Effect from "effect/Effect";
|
|
2
|
+
import * as Option from "effect/Option";
|
|
2
3
|
import * as Result from "effect/Result";
|
|
3
4
|
import * as Schema from "effect/Schema";
|
|
4
5
|
import * as SchemaIssue from "effect/SchemaIssue";
|
|
5
6
|
import * as SchemaTransformation from "effect/SchemaTransformation";
|
|
6
|
-
import { ExtensionNameSchema, ExtensionTypePluralSchema } from "./common.js";
|
|
7
|
+
import { ExtensionNameSchema, ExtensionTypePluralSchema, splitExtensionReference, } from "./common.js";
|
|
7
8
|
import { HandleSchema } from "./handle.js";
|
|
8
9
|
import { VersionRangeSchema } from "../version-constraints/version-constraints.js";
|
|
9
10
|
const invalidRegistrySourcePattern = (input) => `Expected registry source pattern in @handle, @handle/<plural-type>, or @handle/<plural-type>/<name>@<constraint> form, got: ${input}`;
|
|
@@ -44,26 +45,12 @@ export const RegistrySourceRefPartsSchema = Schema.Struct({
|
|
|
44
45
|
});
|
|
45
46
|
const decodeRegistrySourcePatternParts = Schema.decodeUnknownResult(RegistrySourcePatternPartsSchema);
|
|
46
47
|
const decodeRegistrySourceRefParts = Schema.decodeUnknownResult(RegistrySourceRefPartsSchema);
|
|
47
|
-
const parseNameAndConstraintSegment = (segment) => {
|
|
48
|
-
const atIndex = segment.indexOf("@");
|
|
49
|
-
if (atIndex === 0) {
|
|
50
|
-
return undefined;
|
|
51
|
-
}
|
|
52
|
-
if (atIndex === -1) {
|
|
53
|
-
return { name: segment };
|
|
54
|
-
}
|
|
55
|
-
const name = segment.slice(0, atIndex);
|
|
56
|
-
const versionRange = segment.slice(atIndex + 1);
|
|
57
|
-
if (name.length === 0 || versionRange.length === 0) {
|
|
58
|
-
return undefined;
|
|
59
|
-
}
|
|
60
|
-
return { name, versionRange };
|
|
61
|
-
};
|
|
62
48
|
export const parseRegistrySourcePatternParts = (input) => {
|
|
63
49
|
if (!input.startsWith("@")) {
|
|
64
50
|
return undefined;
|
|
65
51
|
}
|
|
66
|
-
const
|
|
52
|
+
const { name: reference, constraint } = splitExtensionReference(input);
|
|
53
|
+
const segments = reference.split("/");
|
|
67
54
|
if (segments.length < 1 || segments.length > 3) {
|
|
68
55
|
return undefined;
|
|
69
56
|
}
|
|
@@ -83,17 +70,9 @@ export const parseRegistrySourcePatternParts = (input) => {
|
|
|
83
70
|
}
|
|
84
71
|
else {
|
|
85
72
|
const type = segments[1];
|
|
86
|
-
const
|
|
87
|
-
if (type !== undefined &&
|
|
88
|
-
|
|
89
|
-
if (parsedName !== undefined) {
|
|
90
|
-
candidate = {
|
|
91
|
-
owner,
|
|
92
|
-
type,
|
|
93
|
-
name: parsedName.name,
|
|
94
|
-
versionRange: parsedName.versionRange,
|
|
95
|
-
};
|
|
96
|
-
}
|
|
73
|
+
const name = segments[2];
|
|
74
|
+
if (type !== undefined && name !== undefined) {
|
|
75
|
+
candidate = { owner, type, name, versionRange: constraint };
|
|
97
76
|
}
|
|
98
77
|
}
|
|
99
78
|
if (candidate === undefined) {
|
|
@@ -102,25 +81,31 @@ export const parseRegistrySourcePatternParts = (input) => {
|
|
|
102
81
|
const result = decodeRegistrySourcePatternParts(candidate);
|
|
103
82
|
return Result.isSuccess(result) ? result.success : undefined;
|
|
104
83
|
};
|
|
105
|
-
/**
|
|
84
|
+
/**
|
|
85
|
+
* Parse a Registry locator, keeping the configured source name it spells
|
|
86
|
+
* (`<source>:@owner/...`) and leaving an unqualified locator unbound.
|
|
87
|
+
*/
|
|
106
88
|
export const parseSourceQualifiedRegistrySourcePatternParts = (input) => {
|
|
107
89
|
const unqualified = parseRegistrySourcePatternParts(input);
|
|
108
90
|
if (unqualified !== undefined) {
|
|
109
|
-
return { ...unqualified, sourceName:
|
|
91
|
+
return { ...unqualified, sourceName: Option.none() };
|
|
110
92
|
}
|
|
111
93
|
const separator = input.indexOf(":");
|
|
112
94
|
if (separator <= 0)
|
|
113
95
|
return undefined;
|
|
114
96
|
const sourceName = input.slice(0, separator);
|
|
115
97
|
const qualified = parseRegistrySourcePatternParts(input.slice(separator + 1));
|
|
116
|
-
return qualified === undefined
|
|
98
|
+
return qualified === undefined
|
|
99
|
+
? undefined
|
|
100
|
+
: { ...qualified, sourceName: Option.some(sourceName) };
|
|
117
101
|
};
|
|
118
102
|
export const parseRegistrySourceRef = (input) => {
|
|
119
103
|
const parsed = parseSourceQualifiedRegistrySourcePatternParts(input);
|
|
120
104
|
if (parsed?.type === undefined || parsed.name === undefined) {
|
|
121
105
|
return undefined;
|
|
122
106
|
}
|
|
123
|
-
const
|
|
107
|
+
const { sourceName: _sourceName, ...parts } = parsed;
|
|
108
|
+
const result = decodeRegistrySourceRefParts(parts);
|
|
124
109
|
return Result.isSuccess(result) ? result.success : undefined;
|
|
125
110
|
};
|
|
126
111
|
export const formatRegistrySourcePatternParts = (value) => {
|
|
@@ -26,4 +26,10 @@ export declare const makeWorkspaceRelativeSourcePath: (path: Path.Path, workspac
|
|
|
26
26
|
* prefix is not mistaken for a child.
|
|
27
27
|
*/
|
|
28
28
|
export declare const isPathSafe: (path: Path.Path, base: string, target: string) => boolean;
|
|
29
|
+
/** Resolve a child against its base and reject paths that escape it. */
|
|
30
|
+
export declare const safeChildPath: (path: Path.Path, base: string, target: string) => Option.Option<AbsolutePath>;
|
|
31
|
+
/** Relative-path containment, including the parent itself. */
|
|
32
|
+
export declare const isWithinOrEqual: (path: Path.Path, parent: string, child: string) => boolean;
|
|
33
|
+
/** Relative-path containment that excludes the parent itself. */
|
|
34
|
+
export declare const isStrictlyWithin: (path: Path.Path, parent: string, child: string) => boolean;
|
|
29
35
|
//# sourceMappingURL=path-types.d.ts.map
|
|
@@ -56,4 +56,24 @@ export const isPathSafe = (path, base, target) => {
|
|
|
56
56
|
const resolvedTarget = path.resolve(target);
|
|
57
57
|
return resolvedTarget === resolvedBase || resolvedTarget.startsWith(resolvedBase + path.sep);
|
|
58
58
|
};
|
|
59
|
+
/** Resolve a child against its base and reject paths that escape it. */
|
|
60
|
+
export const safeChildPath = (path, base, target) => {
|
|
61
|
+
const resolvedBase = path.resolve(base);
|
|
62
|
+
const resolvedTarget = path.isAbsolute(target)
|
|
63
|
+
? path.resolve(target)
|
|
64
|
+
: path.resolve(resolvedBase, target);
|
|
65
|
+
return isPathSafe(path, resolvedBase, resolvedTarget)
|
|
66
|
+
? Option.some(makeAbsolutePath(path, resolvedTarget))
|
|
67
|
+
: Option.none();
|
|
68
|
+
};
|
|
69
|
+
/** Relative-path containment, including the parent itself. */
|
|
70
|
+
export const isWithinOrEqual = (path, parent, child) => {
|
|
71
|
+
const relative = path.relative(path.resolve(parent), path.resolve(child));
|
|
72
|
+
return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
|
|
73
|
+
};
|
|
74
|
+
/** Relative-path containment that excludes the parent itself. */
|
|
75
|
+
export const isStrictlyWithin = (path, parent, child) => {
|
|
76
|
+
const relative = path.relative(path.resolve(parent), path.resolve(child));
|
|
77
|
+
return relative !== "" && !relative.startsWith("..") && !path.isAbsolute(relative);
|
|
78
|
+
};
|
|
59
79
|
//# sourceMappingURL=path-types.js.map
|
|
@@ -1,22 +1,58 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Forge coordinate, clone URL, and browser URL grammar.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* only the string grammar lives here.
|
|
4
|
+
* Probing and acquisition stay with source resolution; the model owns the
|
|
5
|
+
* strings and URLs that identify each forge repository.
|
|
7
6
|
*
|
|
8
7
|
* @experimental This API is unstable and may change without notice.
|
|
9
8
|
* @packageDocumentation
|
|
10
9
|
*/
|
|
11
|
-
import
|
|
10
|
+
import * as Option from "effect/Option";
|
|
11
|
+
import * as Result from "effect/Result";
|
|
12
12
|
/** @experimental */
|
|
13
|
-
export declare const
|
|
13
|
+
export declare const FORGE_PREFIXES: readonly ["github", "gitlab", "bitbucket", "azurerepos"];
|
|
14
14
|
/** @experimental */
|
|
15
|
-
export
|
|
15
|
+
export type ForgePrefix = (typeof FORGE_PREFIXES)[number];
|
|
16
16
|
/** @experimental */
|
|
17
|
-
export declare const
|
|
17
|
+
export declare const isForgePrefix: (value: string) => value is ForgePrefix;
|
|
18
18
|
/** @experimental */
|
|
19
|
-
export
|
|
19
|
+
export interface ForgeCoordinate {
|
|
20
|
+
readonly forge: ForgePrefix;
|
|
21
|
+
readonly repository: string;
|
|
22
|
+
readonly ref: Option.Option<string>;
|
|
23
|
+
readonly subPath: Option.Option<string>;
|
|
24
|
+
}
|
|
25
|
+
interface BrowserPath {
|
|
26
|
+
readonly repository: string;
|
|
27
|
+
readonly ref?: string;
|
|
28
|
+
readonly subPath?: string;
|
|
29
|
+
}
|
|
20
30
|
/** @experimental */
|
|
21
|
-
export
|
|
31
|
+
export interface Forge {
|
|
32
|
+
readonly prefix: ForgePrefix;
|
|
33
|
+
readonly hostname: string;
|
|
34
|
+
readonly repositorySegments: (count: number) => boolean;
|
|
35
|
+
readonly cloneUrl: (repository: string) => URL;
|
|
36
|
+
readonly repositoryFromCloneUrl: (pathname: string) => string | undefined;
|
|
37
|
+
readonly parseBrowserPath: (pathname: string) => BrowserPath | undefined;
|
|
38
|
+
}
|
|
39
|
+
/** @experimental */
|
|
40
|
+
export declare const FORGES: Record<ForgePrefix, Forge>;
|
|
41
|
+
/** @experimental */
|
|
42
|
+
export declare const forgeForHostname: (hostname: string) => Forge | undefined;
|
|
43
|
+
/** @experimental */
|
|
44
|
+
export declare const parseForgeCoordinate: (forge: ForgePrefix, body: string) => Result.Result<ForgeCoordinate, {
|
|
45
|
+
readonly reason: string;
|
|
46
|
+
}>;
|
|
47
|
+
/** @experimental */
|
|
48
|
+
export declare const printForgeCoordinateBody: (coordinate: ForgeCoordinate) => string;
|
|
49
|
+
/** @experimental */
|
|
50
|
+
export declare const printForgeCoordinate: (coordinate: ForgeCoordinate) => string;
|
|
51
|
+
/** @experimental */
|
|
52
|
+
export declare const forgeCloneUrl: (coordinate: ForgeCoordinate) => URL;
|
|
53
|
+
/** @experimental */
|
|
54
|
+
export declare const forgeCoordinateFromGitUrl: (url: URL, ref: Option.Option<string>, subPath: Option.Option<string>) => Option.Option<ForgeCoordinate>;
|
|
55
|
+
/** @experimental */
|
|
56
|
+
export declare const parseForgeBrowserUrl: (url: URL) => Option.Option<ForgeCoordinate>;
|
|
57
|
+
export {};
|
|
22
58
|
//# sourceMappingURL=forge-grammar.d.ts.map
|
|
@@ -1,50 +1,170 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Forge coordinate, clone URL, and browser URL grammar.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* only the string grammar lives here.
|
|
4
|
+
* Probing and acquisition stay with source resolution; the model owns the
|
|
5
|
+
* strings and URLs that identify each forge repository.
|
|
7
6
|
*
|
|
8
7
|
* @experimental This API is unstable and may change without notice.
|
|
9
8
|
* @packageDocumentation
|
|
10
9
|
*/
|
|
11
10
|
import * as Option from "effect/Option";
|
|
11
|
+
import * as Result from "effect/Result";
|
|
12
|
+
import * as Schema from "effect/Schema";
|
|
13
|
+
import { SourceNamespaceSchema, SourceRefSchema, SourceSubPathSchema } from "./types.js";
|
|
12
14
|
/** @experimental */
|
|
13
|
-
export const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
export const FORGE_PREFIXES = ["github", "gitlab", "bitbucket", "azurerepos"];
|
|
16
|
+
/** @experimental */
|
|
17
|
+
export const isForgePrefix = (value) => FORGE_PREFIXES.some((prefix) => prefix === value);
|
|
18
|
+
const segments = (pathname) => pathname.split("/").filter((segment) => segment.length > 0);
|
|
19
|
+
const withoutGit = (name) => (name.endsWith(".git") ? name.slice(0, -4) : name);
|
|
20
|
+
const hostedRepositoryFromCloneUrl = (pathname) => {
|
|
21
|
+
const parts = segments(pathname);
|
|
22
|
+
const last = parts.at(-1);
|
|
23
|
+
return last === undefined || parts.length < 2
|
|
24
|
+
? undefined
|
|
25
|
+
: [...parts.slice(0, -1), withoutGit(last)].join("/");
|
|
26
|
+
};
|
|
27
|
+
const hostedBrowserPath = (pathname, marker) => {
|
|
28
|
+
const pattern = marker === "tree"
|
|
29
|
+
? /^\/([^/]+)\/([^/]+?)(?:\.git)?(?:\/tree\/([^/]+)(?:\/(.+))?)?$/u
|
|
30
|
+
: /^\/([^/]+)\/([^/]+?)(?:\.git)?(?:\/src\/([^/]+)(?:\/(.+))?)?$/u;
|
|
31
|
+
const match = pattern.exec(pathname);
|
|
32
|
+
if (match?.[1] === undefined || match[2] === undefined)
|
|
33
|
+
return undefined;
|
|
34
|
+
return {
|
|
35
|
+
repository: `${match[1]}/${match[2]}`,
|
|
36
|
+
...(match[3] === undefined ? {} : { ref: match[3] }),
|
|
37
|
+
...(match[4] === undefined ? {} : { subPath: match[4] }),
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
const gitlabBrowserPath = (pathname) => {
|
|
41
|
+
const marker = "/-/tree/";
|
|
42
|
+
const markerIndex = pathname.indexOf(marker);
|
|
43
|
+
const repositoryParts = segments(markerIndex < 0 ? pathname : pathname.slice(0, markerIndex));
|
|
44
|
+
const last = repositoryParts.at(-1);
|
|
45
|
+
if (last === undefined || repositoryParts.length < 2)
|
|
46
|
+
return undefined;
|
|
47
|
+
const treePath = markerIndex < 0 ? [] : pathname.slice(markerIndex + marker.length).split("/");
|
|
48
|
+
const subPath = treePath.slice(1).join("/");
|
|
49
|
+
return {
|
|
50
|
+
repository: [...repositoryParts.slice(0, -1), withoutGit(last)].join("/"),
|
|
51
|
+
...(treePath[0] === undefined ? {} : { ref: treePath[0] }),
|
|
52
|
+
...(subPath.length === 0 ? {} : { subPath }),
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
const azureRepositoryFromCloneUrl = (pathname) => {
|
|
56
|
+
const parts = segments(pathname);
|
|
57
|
+
return parts.length === 4 && parts[2] === "_git"
|
|
58
|
+
? `${parts[0]}/${parts[1]}/${withoutGit(parts[3] ?? "")}`
|
|
59
|
+
: undefined;
|
|
60
|
+
};
|
|
61
|
+
const azureBrowserPath = (pathname) => {
|
|
62
|
+
const match = /^\/([^/]+)\/([^/]+)\/_git\/([^/]+?)(?:\.git)?$/u.exec(pathname);
|
|
63
|
+
return match?.[1] === undefined || match[2] === undefined || match[3] === undefined
|
|
64
|
+
? undefined
|
|
65
|
+
: { repository: `${match[1]}/${match[2]}/${match[3]}` };
|
|
66
|
+
};
|
|
67
|
+
const hostedForge = (prefix, hostname, parseBrowserPath) => ({
|
|
68
|
+
prefix,
|
|
69
|
+
hostname,
|
|
70
|
+
repositorySegments: (count) => count >= 2,
|
|
71
|
+
cloneUrl: (repository) => new URL(`https://${hostname}/${repository}.git`),
|
|
72
|
+
repositoryFromCloneUrl: hostedRepositoryFromCloneUrl,
|
|
73
|
+
parseBrowserPath,
|
|
74
|
+
});
|
|
75
|
+
/** @experimental */
|
|
76
|
+
export const FORGES = {
|
|
77
|
+
github: hostedForge("github", "github.com", (pathname) => hostedBrowserPath(pathname, "tree")),
|
|
78
|
+
gitlab: hostedForge("gitlab", "gitlab.com", gitlabBrowserPath),
|
|
79
|
+
bitbucket: hostedForge("bitbucket", "bitbucket.org", (pathname) => hostedBrowserPath(pathname, "src")),
|
|
80
|
+
azurerepos: {
|
|
81
|
+
prefix: "azurerepos",
|
|
82
|
+
hostname: "dev.azure.com",
|
|
83
|
+
repositorySegments: (count) => count === 3,
|
|
84
|
+
cloneUrl: (repository) => {
|
|
85
|
+
const [organization, project, repo] = repository.split("/");
|
|
86
|
+
return new URL(`https://dev.azure.com/${organization}/${project}/_git/${repo}`);
|
|
87
|
+
},
|
|
88
|
+
repositoryFromCloneUrl: azureRepositoryFromCloneUrl,
|
|
89
|
+
parseBrowserPath: azureBrowserPath,
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
/** @experimental */
|
|
93
|
+
export const forgeForHostname = (hostname) => Object.values(FORGES).find((forge) => forge.hostname === hostname);
|
|
94
|
+
const decodeNamespace = Schema.decodeUnknownResult(SourceNamespaceSchema);
|
|
95
|
+
const decodeSubPath = Schema.decodeUnknownResult(SourceSubPathSchema);
|
|
96
|
+
const decodeRef = Schema.decodeUnknownResult(SourceRefSchema);
|
|
97
|
+
const validCoordinate = (coordinate) => FORGES[coordinate.forge].repositorySegments(coordinate.repository.split("/").length) &&
|
|
98
|
+
Result.isSuccess(decodeNamespace(coordinate.repository)) &&
|
|
99
|
+
(Option.isNone(coordinate.subPath) ||
|
|
100
|
+
Result.isSuccess(decodeSubPath(coordinate.subPath.value))) &&
|
|
101
|
+
(Option.isNone(coordinate.ref) || Result.isSuccess(decodeRef(coordinate.ref.value)));
|
|
102
|
+
const splitRef = (input) => {
|
|
103
|
+
const refIndex = input.lastIndexOf("@");
|
|
104
|
+
if (refIndex <= 0)
|
|
105
|
+
return { coordinate: input };
|
|
106
|
+
const ref = input.slice(refIndex + 1);
|
|
107
|
+
const subPathMarker = input.indexOf("//");
|
|
108
|
+
const isAtPathSegment = subPathMarker !== -1 &&
|
|
109
|
+
refIndex > subPathMarker + 1 &&
|
|
110
|
+
input[refIndex - 1] === "/" &&
|
|
111
|
+
ref.includes("/");
|
|
112
|
+
return isAtPathSegment ? { coordinate: input } : { coordinate: input.slice(0, refIndex), ref };
|
|
20
113
|
};
|
|
21
114
|
/** @experimental */
|
|
22
|
-
export const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
115
|
+
export const parseForgeCoordinate = (forge, body) => {
|
|
116
|
+
const { coordinate, ref } = splitRef(body);
|
|
117
|
+
const marker = coordinate.indexOf("//");
|
|
118
|
+
const repository = marker < 0 ? coordinate : coordinate.slice(0, marker);
|
|
119
|
+
const subPath = marker < 0 ? undefined : coordinate.slice(marker + 2);
|
|
120
|
+
const parsed = {
|
|
121
|
+
forge,
|
|
122
|
+
repository,
|
|
123
|
+
ref: Option.fromUndefinedOr(ref),
|
|
124
|
+
subPath: Option.fromUndefinedOr(subPath),
|
|
125
|
+
};
|
|
126
|
+
return validCoordinate(parsed)
|
|
127
|
+
? Result.succeed(parsed)
|
|
128
|
+
: Result.fail({ reason: `Expected valid ${forge} repository[//subpath][@ref]` });
|
|
29
129
|
};
|
|
30
130
|
/** @experimental */
|
|
31
|
-
export const
|
|
32
|
-
let
|
|
33
|
-
if (Option.isSome(
|
|
34
|
-
|
|
35
|
-
if (Option.isSome(
|
|
36
|
-
|
|
37
|
-
return
|
|
131
|
+
export const printForgeCoordinateBody = (coordinate) => {
|
|
132
|
+
let body = coordinate.repository;
|
|
133
|
+
if (Option.isSome(coordinate.subPath))
|
|
134
|
+
body += `//${coordinate.subPath.value}`;
|
|
135
|
+
if (Option.isSome(coordinate.ref))
|
|
136
|
+
body += `@${coordinate.ref.value}`;
|
|
137
|
+
return body;
|
|
38
138
|
};
|
|
39
139
|
/** @experimental */
|
|
40
|
-
export const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
140
|
+
export const printForgeCoordinate = (coordinate) => `${coordinate.forge}:${printForgeCoordinateBody(coordinate)}`;
|
|
141
|
+
/** @experimental */
|
|
142
|
+
export const forgeCloneUrl = (coordinate) => FORGES[coordinate.forge].cloneUrl(coordinate.repository);
|
|
143
|
+
/** @experimental */
|
|
144
|
+
export const forgeCoordinateFromGitUrl = (url, ref, subPath) => {
|
|
145
|
+
const forge = forgeForHostname(url.hostname);
|
|
146
|
+
if (forge === undefined)
|
|
147
|
+
return Option.none();
|
|
148
|
+
const repository = forge.repositoryFromCloneUrl(url.pathname);
|
|
149
|
+
if (repository === undefined)
|
|
150
|
+
return Option.none();
|
|
151
|
+
const coordinate = { forge: forge.prefix, repository, ref, subPath };
|
|
152
|
+
return validCoordinate(coordinate) ? Option.some(coordinate) : Option.none();
|
|
47
153
|
};
|
|
48
154
|
/** @experimental */
|
|
49
|
-
export const
|
|
155
|
+
export const parseForgeBrowserUrl = (url) => {
|
|
156
|
+
const forge = forgeForHostname(url.hostname);
|
|
157
|
+
if (forge === undefined)
|
|
158
|
+
return Option.none();
|
|
159
|
+
const parsed = forge.parseBrowserPath(url.pathname);
|
|
160
|
+
if (parsed === undefined)
|
|
161
|
+
return Option.none();
|
|
162
|
+
const coordinate = {
|
|
163
|
+
forge: forge.prefix,
|
|
164
|
+
repository: parsed.repository,
|
|
165
|
+
ref: Option.fromUndefinedOr(parsed.ref),
|
|
166
|
+
subPath: Option.fromUndefinedOr(parsed.subPath),
|
|
167
|
+
};
|
|
168
|
+
return validCoordinate(coordinate) ? Option.some(coordinate) : Option.none();
|
|
169
|
+
};
|
|
50
170
|
//# sourceMappingURL=forge-grammar.js.map
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* @packageDocumentation
|
|
10
10
|
*/
|
|
11
11
|
import * as Option from "effect/Option";
|
|
12
|
+
import { type ForgeCoordinate, type ForgePrefix } from "./forge-grammar.js";
|
|
12
13
|
import type { VersionRange } from "../version-constraints/version-constraints.js";
|
|
13
14
|
import type { ExtensionName, ExtensionType, ExtensionTypePlural } from "../extensions/common.js";
|
|
14
15
|
import type { Handle } from "../extensions/handle.js";
|
|
@@ -17,10 +18,14 @@ type NameInput = {
|
|
|
17
18
|
readonly pattern: "name-input";
|
|
18
19
|
readonly name: string;
|
|
19
20
|
};
|
|
20
|
-
/**
|
|
21
|
+
/**
|
|
22
|
+
* A namespaced registry source: `[source-name:]@owner/<plural-type>/name`.
|
|
23
|
+
* `sourceName` is the configured source the input spells; an unqualified
|
|
24
|
+
* input leaves it unbound for the workspace to assign.
|
|
25
|
+
*/
|
|
21
26
|
type RegistryPatternInput = {
|
|
22
27
|
readonly pattern: "registry-pattern-input";
|
|
23
|
-
readonly sourceName: string
|
|
28
|
+
readonly sourceName: Option.Option<string>;
|
|
24
29
|
readonly type: Option.Option<ExtensionTypePlural>;
|
|
25
30
|
readonly owner: Handle;
|
|
26
31
|
readonly name: Option.Option<ExtensionName>;
|
|
@@ -41,10 +46,7 @@ type GitScpAddress = {
|
|
|
41
46
|
/** An `owner/repo[//path][@ref]` style pattern containing `/` (not a URL or file path). */
|
|
42
47
|
type SlashPattern = {
|
|
43
48
|
readonly pattern: "slash-pattern";
|
|
44
|
-
readonly
|
|
45
|
-
readonly second: string;
|
|
46
|
-
readonly third: Option.Option<string>;
|
|
47
|
-
readonly ref: Option.Option<string>;
|
|
49
|
+
readonly coordinate: ForgeCoordinate;
|
|
48
50
|
};
|
|
49
51
|
/** A local filesystem path matching `LOCAL_PATH_PATTERN`. */
|
|
50
52
|
type FilePathPattern = {
|
|
@@ -54,7 +56,7 @@ type FilePathPattern = {
|
|
|
54
56
|
/** A shorthand prefixed input: `<prefix>:...` where prefix is a known source provider. */
|
|
55
57
|
export type ShorthandInput = {
|
|
56
58
|
readonly pattern: "shorthand-input";
|
|
57
|
-
readonly prefix:
|
|
59
|
+
readonly prefix: ForgePrefix;
|
|
58
60
|
readonly remainingInput: string;
|
|
59
61
|
};
|
|
60
62
|
/** A glob pattern containing `*` wildcards (e.g. `effect-*`). */
|
|
@@ -10,14 +10,13 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import * as Option from "effect/Option";
|
|
12
12
|
import * as Result from "effect/Result";
|
|
13
|
+
import { isForgePrefix, parseForgeCoordinate, } from "./forge-grammar.js";
|
|
13
14
|
import { parseFqn } from "../extensions/fqn.js";
|
|
14
15
|
import { parseSourceQualifiedRegistrySourcePatternParts } from "../extensions/registry-source.js";
|
|
15
16
|
/** Matches: ./path, ../path, /path, ~/path, ~\path, or Windows paths like C:\path */
|
|
16
17
|
const LOCAL_PATH_PATTERN = /^(?:\.\.?\/|\/|~\/|~\\|[A-Za-z]:[\\/])/;
|
|
17
18
|
/** SCP-style: `user@host:path` — no `://` scheme. */
|
|
18
19
|
const SCP_PATTERN = /^([^@]+)@([^:]+):(.+)$/;
|
|
19
|
-
/** Known shorthand prefixes. */
|
|
20
|
-
const SHORTHAND_PREFIXES = new Set(["github", "gitlab", "bitbucket", "azurerepos"]);
|
|
21
20
|
/** Simple name: alphanumeric with hyphens, no leading/trailing hyphen. */
|
|
22
21
|
const NAME_PATTERN = /^[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?$/;
|
|
23
22
|
/**
|
|
@@ -66,10 +65,11 @@ export const parseInputPattern = (input) => {
|
|
|
66
65
|
}
|
|
67
66
|
// 4. Shorthand prefix (github:..., gitlab:..., etc.) — must check before URL
|
|
68
67
|
const colonIndex = input.indexOf(":");
|
|
69
|
-
|
|
68
|
+
const prefix = input.slice(0, colonIndex);
|
|
69
|
+
if (colonIndex > 0 && isForgePrefix(prefix)) {
|
|
70
70
|
return Option.some(wrap({
|
|
71
71
|
pattern: "shorthand-input",
|
|
72
|
-
prefix
|
|
72
|
+
prefix,
|
|
73
73
|
remainingInput: input.slice(colonIndex + 1),
|
|
74
74
|
}));
|
|
75
75
|
}
|
|
@@ -90,34 +90,17 @@ export const parseInputPattern = (input) => {
|
|
|
90
90
|
}
|
|
91
91
|
// 7. Slash pattern (`owner/repo[//path][@ref]`)
|
|
92
92
|
if (input.includes("/")) {
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
const second = segments.at(1);
|
|
103
|
-
if (first !== undefined &&
|
|
104
|
-
second !== undefined &&
|
|
105
|
-
segments.length === 2 &&
|
|
106
|
-
NAME_PATTERN.test(first) &&
|
|
107
|
-
NAME_PATTERN.test(second) &&
|
|
108
|
-
(ref === undefined || (ref.length > 0 && !ref.includes("/"))) &&
|
|
109
|
-
(subPath === undefined ||
|
|
110
|
-
(subPath.length > 0 &&
|
|
111
|
-
subPath.split("/").every((segment) => segment.length > 0 && segment !== "..")))) {
|
|
112
|
-
return Option.some(wrap({
|
|
113
|
-
pattern: "slash-pattern",
|
|
114
|
-
first,
|
|
115
|
-
second,
|
|
116
|
-
third: Option.fromUndefinedOr(subPath),
|
|
117
|
-
ref: Option.fromUndefinedOr(ref),
|
|
118
|
-
}));
|
|
93
|
+
const parsed = parseForgeCoordinate("github", input);
|
|
94
|
+
if (Result.isFailure(parsed))
|
|
95
|
+
return Option.none();
|
|
96
|
+
const coordinate = parsed.success;
|
|
97
|
+
const segments = coordinate.repository.split("/");
|
|
98
|
+
if (segments.length !== 2 ||
|
|
99
|
+
!segments.every((segment) => NAME_PATTERN.test(segment)) ||
|
|
100
|
+
Option.exists(coordinate.ref, (ref) => ref.includes("/"))) {
|
|
101
|
+
return Option.none();
|
|
119
102
|
}
|
|
120
|
-
return Option.
|
|
103
|
+
return Option.some(wrap({ pattern: "slash-pattern", coordinate }));
|
|
121
104
|
}
|
|
122
105
|
// 8. Glob pattern (contains `*` wildcard)
|
|
123
106
|
if (input.includes("*")) {
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import * as Option from "effect/Option";
|
|
8
8
|
import { formatFqn } from "../extensions/fqn.js";
|
|
9
|
-
import {
|
|
9
|
+
import { forgeCoordinateFromGitUrl, printForgeCoordinate } from "./forge-grammar.js";
|
|
10
|
+
const printLocalSource = (source) => source.path.startsWith("/") || source.path.startsWith(".") ? source.path : `./${source.path}`;
|
|
10
11
|
/**
|
|
11
12
|
* Print source params as their canonical shorthand string.
|
|
12
13
|
*
|
|
@@ -17,40 +18,14 @@ export const printSourceParams = (source) => {
|
|
|
17
18
|
case "local":
|
|
18
19
|
return printLocalSource(source);
|
|
19
20
|
case "git": {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
shorthand += `//${source.subPath.value}`;
|
|
29
|
-
if (Option.isSome(source.ref))
|
|
30
|
-
shorthand += `@${source.ref.value}`;
|
|
31
|
-
return shorthand;
|
|
32
|
-
}
|
|
33
|
-
const lastSegment = repositorySegments.at(-1);
|
|
34
|
-
const repo = lastSegment?.endsWith(".git") ? lastSegment.slice(0, -4) : lastSegment;
|
|
35
|
-
const sourcePrefix = source.url.hostname === "github.com"
|
|
36
|
-
? "github"
|
|
37
|
-
: source.url.hostname === "gitlab.com"
|
|
38
|
-
? "gitlab"
|
|
39
|
-
: source.url.hostname === "bitbucket.org"
|
|
40
|
-
? "bitbucket"
|
|
41
|
-
: undefined;
|
|
42
|
-
if (sourcePrefix !== undefined && repo !== undefined && repositorySegments.length >= 2) {
|
|
43
|
-
const owner = repositorySegments.slice(0, -1).join("/");
|
|
44
|
-
let shorthand = `${sourcePrefix}:${owner}/${repo}`;
|
|
45
|
-
if (Option.isSome(source.subPath))
|
|
46
|
-
shorthand += `//${source.subPath.value}`;
|
|
47
|
-
if (Option.isSome(source.ref))
|
|
48
|
-
shorthand += `@${source.ref.value}`;
|
|
49
|
-
return shorthand;
|
|
50
|
-
}
|
|
51
|
-
const url = new URL(source.url.href);
|
|
52
|
-
url.hash = Option.getOrElse(source.ref, () => "");
|
|
53
|
-
return url.href;
|
|
21
|
+
return Option.match(forgeCoordinateFromGitUrl(source.url, source.ref, source.subPath), {
|
|
22
|
+
onSome: printForgeCoordinate,
|
|
23
|
+
onNone: () => {
|
|
24
|
+
const url = new URL(source.url.href);
|
|
25
|
+
url.hash = Option.getOrElse(source.ref, () => "");
|
|
26
|
+
return url.href;
|
|
27
|
+
},
|
|
28
|
+
});
|
|
54
29
|
}
|
|
55
30
|
case "registry": {
|
|
56
31
|
return source.sourceName ?? ("name" in source ? String(source.name) : "agentxm");
|
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
* @experimental This API is unstable and may change without notice.
|
|
8
8
|
* @packageDocumentation
|
|
9
9
|
*/
|
|
10
|
-
import type * as Duration from "effect/Duration";
|
|
11
10
|
import type * as Effect from "effect/Effect";
|
|
12
11
|
import type * as Option from "effect/Option";
|
|
13
12
|
import type { ExtensionType, Handle } from "../extensions/index.js";
|
|
@@ -29,11 +28,6 @@ export interface FindOptions {
|
|
|
29
28
|
/** Registry owner filter (e.g. "@acme"). */
|
|
30
29
|
readonly owner: Option.Option<Handle>;
|
|
31
30
|
readonly versionRange: Option.Option<string>;
|
|
32
|
-
/**
|
|
33
|
-
* Optional minimum age a registry release must have before it is selectable.
|
|
34
|
-
* Omitted for explicit attended installs.
|
|
35
|
-
*/
|
|
36
|
-
readonly minimumReleaseAge?: Option.Option<Duration.Duration>;
|
|
37
31
|
}
|
|
38
32
|
export interface NamedRegistryFindOptions {
|
|
39
33
|
readonly name: string;
|
|
@@ -54,6 +48,8 @@ export type NamedRegistryResolution = {
|
|
|
54
48
|
readonly refType: "registry";
|
|
55
49
|
}>;
|
|
56
50
|
readonly newerHeld?: ReleaseAgeEvidence;
|
|
51
|
+
/** The newest release a range could select, whatever range was requested. */
|
|
52
|
+
readonly newestVisible?: string;
|
|
57
53
|
} | {
|
|
58
54
|
readonly kind: "exempted";
|
|
59
55
|
readonly target: string;
|
|
@@ -62,6 +58,7 @@ export type NamedRegistryResolution = {
|
|
|
62
58
|
}>;
|
|
63
59
|
readonly bypassed: ReleaseAgeEvidence;
|
|
64
60
|
readonly exemption: ReleaseAgeExemption;
|
|
61
|
+
readonly newestVisible?: string;
|
|
65
62
|
} | {
|
|
66
63
|
readonly kind: "not_found";
|
|
67
64
|
readonly target: string;
|
|
@@ -87,11 +84,18 @@ export type NamedRegistryVersionDecision = {
|
|
|
87
84
|
readonly kind: "selected";
|
|
88
85
|
readonly version: string;
|
|
89
86
|
readonly newerHeld?: ReleaseAgeEvidence;
|
|
87
|
+
/**
|
|
88
|
+
* The newest release the index lists that a range could select — not
|
|
89
|
+
* yanked, whatever range was requested — so a caller can say what its
|
|
90
|
+
* constraint held the selection below.
|
|
91
|
+
*/
|
|
92
|
+
readonly newestVisible?: string;
|
|
90
93
|
} | {
|
|
91
94
|
readonly kind: "exempted";
|
|
92
95
|
readonly version: string;
|
|
93
96
|
readonly bypassed: ReleaseAgeEvidence;
|
|
94
97
|
readonly exemption: ReleaseAgeExemption;
|
|
98
|
+
readonly newestVisible?: string;
|
|
95
99
|
} | {
|
|
96
100
|
readonly kind: "not_found";
|
|
97
101
|
} | {
|