|
|
@@ -58,7 +58,7 @@ type Prepared =
|
|
|
})
|
|
|
|
|
|
interface Target {
|
|
|
- readonly canonical: string
|
|
|
+ readonly absolute: string
|
|
|
readonly resource: string
|
|
|
readonly externalDirectory?: {
|
|
|
readonly directory: string
|
|
|
@@ -76,267 +76,256 @@ export const Plugin = {
|
|
|
|
|
|
yield* ctx.tool
|
|
|
.transform((draft) =>
|
|
|
- draft.add(
|
|
|
- ({
|
|
|
- name,
|
|
|
- options: { codemode: false, permission: "edit" },
|
|
|
- description: DESCRIPTION,
|
|
|
- input: Input,
|
|
|
- output: Output,
|
|
|
- execute: (input, context) => {
|
|
|
- const applied: Array<typeof Applied.Type> = []
|
|
|
- const fail = (operation: string, error: unknown) => {
|
|
|
- const completed = applied.map((item) => item.resource).join(", ")
|
|
|
- return new ToolFailure({
|
|
|
- message: `${operation}: ${errorMessage(error)}${completed ? `. Completed before failure: ${completed}` : ""}`,
|
|
|
- })
|
|
|
- }
|
|
|
- return Effect.gen(function* () {
|
|
|
- const source = {
|
|
|
- type: "tool" as const,
|
|
|
- messageID: context.messageID,
|
|
|
- id: context.id,
|
|
|
+ draft.add({
|
|
|
+ name,
|
|
|
+ options: { codemode: false, permission: "edit" },
|
|
|
+ description: DESCRIPTION,
|
|
|
+ input: Input,
|
|
|
+ output: Output,
|
|
|
+ execute: (input, context) => {
|
|
|
+ const applied: Array<typeof Applied.Type> = []
|
|
|
+ const fail = (operation: string, error: unknown) => {
|
|
|
+ const completed = applied.map((item) => item.resource).join(", ")
|
|
|
+ return new ToolFailure({
|
|
|
+ message: `${operation}: ${errorMessage(error)}${completed ? `. Completed before failure: ${completed}` : ""}`,
|
|
|
+ })
|
|
|
+ }
|
|
|
+ return Effect.gen(function* () {
|
|
|
+ const source = {
|
|
|
+ type: "tool" as const,
|
|
|
+ messageID: context.messageID,
|
|
|
+ id: context.id,
|
|
|
+ }
|
|
|
+ if (!input.patchText) return yield* new ToolFailure({ message: "patchText is required" })
|
|
|
+ const hunks = yield* Effect.fromResult(Patch.parse(input.patchText)).pipe(
|
|
|
+ Effect.mapError((error) => new ToolFailure({ message: `patch verification failed: ${error.message}` })),
|
|
|
+ )
|
|
|
+ if (hunks.length === 0) {
|
|
|
+ return yield* new ToolFailure({ message: "patch rejected: empty patch" })
|
|
|
+ }
|
|
|
+ const prepared: Prepared[] = []
|
|
|
+ const targets: Target[] = []
|
|
|
+ const updates = new Map<string, string>()
|
|
|
+ for (const hunk of hunks) {
|
|
|
+ yield* Effect.gen(function* () {
|
|
|
+ const target = resolveTarget(location, hunk.path)
|
|
|
+ targets.push(target)
|
|
|
+ if (target.externalDirectory) {
|
|
|
+ yield* permission.assert({
|
|
|
+ action: "external_directory",
|
|
|
+ resources: [target.externalDirectory.resource],
|
|
|
+ save: [target.externalDirectory.resource],
|
|
|
+ metadata: {
|
|
|
+ filepath: target.absolute,
|
|
|
+ parentDir: target.externalDirectory.directory,
|
|
|
+ },
|
|
|
+ sessionID: context.sessionID,
|
|
|
+ agent: context.agent,
|
|
|
+ source,
|
|
|
+ })
|
|
|
}
|
|
|
- if (!input.patchText) return yield* new ToolFailure({ message: "patchText is required" })
|
|
|
- const hunks = yield* Effect.fromResult(Patch.parse(input.patchText)).pipe(
|
|
|
- Effect.mapError(
|
|
|
- (error) => new ToolFailure({ message: `patch verification failed: ${error.message}` }),
|
|
|
- ),
|
|
|
- )
|
|
|
- if (hunks.length === 0) {
|
|
|
- return yield* new ToolFailure({ message: "patch rejected: empty patch" })
|
|
|
+ if (hunk.type === "add") {
|
|
|
+ prepared.push({
|
|
|
+ ...hunk,
|
|
|
+ target,
|
|
|
+ before: "",
|
|
|
+ after: Bom.split(
|
|
|
+ hunk.contents.endsWith("\n") || hunk.contents === "" ? hunk.contents : `${hunk.contents}\n`,
|
|
|
+ ).text,
|
|
|
+ })
|
|
|
+ return
|
|
|
}
|
|
|
- const prepared: Prepared[] = []
|
|
|
- const targets: Target[] = []
|
|
|
- const updates = new Map<string, string>()
|
|
|
- for (const hunk of hunks) {
|
|
|
- yield* Effect.gen(function* () {
|
|
|
- const target = resolveTarget(location, hunk.path)
|
|
|
- targets.push(target)
|
|
|
- if (target.externalDirectory) {
|
|
|
- yield* permission.assert({
|
|
|
- action: "external_directory",
|
|
|
- resources: [target.externalDirectory.resource],
|
|
|
- save: [target.externalDirectory.resource],
|
|
|
- metadata: {
|
|
|
- filepath: target.canonical,
|
|
|
- parentDir: target.externalDirectory.directory,
|
|
|
- },
|
|
|
- sessionID: context.sessionID,
|
|
|
- agent: context.agent,
|
|
|
- source,
|
|
|
- })
|
|
|
- }
|
|
|
- if (hunk.type === "add") {
|
|
|
- prepared.push({
|
|
|
- ...hunk,
|
|
|
- target,
|
|
|
- before: "",
|
|
|
- after: Bom.split(
|
|
|
- hunk.contents.endsWith("\n") || hunk.contents === ""
|
|
|
- ? hunk.contents
|
|
|
- : `${hunk.contents}\n`,
|
|
|
- ).text,
|
|
|
- })
|
|
|
- return
|
|
|
- }
|
|
|
- if (hunk.type === "delete") {
|
|
|
- const content = yield* Bom.readFile(fs, target.canonical).pipe(
|
|
|
- Effect.mapError(
|
|
|
- (error) =>
|
|
|
- new ToolFailure({
|
|
|
- message: `patch verification failed: Failed to delete ${target.resource}: ${errorMessage(error)}`,
|
|
|
- }),
|
|
|
- ),
|
|
|
- )
|
|
|
- prepared.push({ ...hunk, target, before: content.text, after: "" })
|
|
|
- return
|
|
|
- }
|
|
|
- const previous = updates.get(target.canonical)
|
|
|
- const original =
|
|
|
- previous ??
|
|
|
- (yield* Effect.gen(function* () {
|
|
|
- const stats = yield* fs.stat(target.canonical).pipe(
|
|
|
- Effect.mapError(
|
|
|
- (error) =>
|
|
|
- new ToolFailure({
|
|
|
- message: `patch verification failed: Failed to read file to update ${target.canonical}: ${errorMessage(error)}`,
|
|
|
- }),
|
|
|
- ),
|
|
|
- )
|
|
|
- if (stats.type === "Directory") {
|
|
|
- return yield* new ToolFailure({
|
|
|
- message: `patch verification failed: Failed to read file to update ${target.canonical}: path is a directory`,
|
|
|
- })
|
|
|
- }
|
|
|
- const content = yield* Bom.readFile(fs, target.canonical).pipe(
|
|
|
- Effect.mapError(
|
|
|
- (error) =>
|
|
|
- new ToolFailure({
|
|
|
- message: `patch verification failed: Failed to read file to update ${target.canonical}: ${errorMessage(error)}`,
|
|
|
- }),
|
|
|
- ),
|
|
|
- )
|
|
|
- return Bom.join(content.text, content.bom)
|
|
|
- }))
|
|
|
- const before = Bom.split(original).text
|
|
|
- const update = yield* Effect.try({
|
|
|
- try: () => Patch.derive(hunk.path, hunk.chunks, original),
|
|
|
- catch: (error) =>
|
|
|
- new ToolFailure({ message: `patch verification failed: ${errorMessage(error)}` }),
|
|
|
- })
|
|
|
- const moveTarget = hunk.movePath ? resolveTarget(location, hunk.movePath) : undefined
|
|
|
- if (moveTarget) targets.push(moveTarget)
|
|
|
- if (moveTarget?.externalDirectory) {
|
|
|
- yield* permission.assert({
|
|
|
- action: "external_directory",
|
|
|
- resources: [moveTarget.externalDirectory.resource],
|
|
|
- save: [moveTarget.externalDirectory.resource],
|
|
|
- metadata: {
|
|
|
- filepath: moveTarget.canonical,
|
|
|
- parentDir: moveTarget.externalDirectory.directory,
|
|
|
- },
|
|
|
- sessionID: context.sessionID,
|
|
|
- agent: context.agent,
|
|
|
- source,
|
|
|
- })
|
|
|
- }
|
|
|
- prepared.push({
|
|
|
- ...hunk,
|
|
|
- target,
|
|
|
- content: Patch.joinBom(update.content, update.bom),
|
|
|
- before,
|
|
|
- after: update.content,
|
|
|
- moveTarget,
|
|
|
- })
|
|
|
- if (!moveTarget) updates.set(target.canonical, Patch.joinBom(update.content, update.bom))
|
|
|
- }).pipe(
|
|
|
- Effect.mapError((error) =>
|
|
|
- error instanceof ToolFailure
|
|
|
- ? error
|
|
|
- : new ToolFailure({ message: `Unable to prepare patch at ${hunk.path}`, error }),
|
|
|
+ if (hunk.type === "delete") {
|
|
|
+ const content = yield* Bom.readFile(fs, target.absolute).pipe(
|
|
|
+ Effect.mapError(
|
|
|
+ (error) =>
|
|
|
+ new ToolFailure({
|
|
|
+ message: `patch verification failed: Failed to delete ${target.resource}: ${errorMessage(error)}`,
|
|
|
+ }),
|
|
|
),
|
|
|
)
|
|
|
+ prepared.push({ ...hunk, target, before: content.text, after: "" })
|
|
|
+ return
|
|
|
}
|
|
|
-
|
|
|
- const patchFiles = prepared.map((change) => patchFile(change))
|
|
|
- yield* permission.assert({
|
|
|
- action: "edit",
|
|
|
- resources: [...new Set(targets.map((target) => target.resource))],
|
|
|
- save: ["*"],
|
|
|
- metadata: {
|
|
|
- filepath: targets.map((target) => target.resource).join(", "),
|
|
|
- diff: patchFiles.map((file) => `${file.patch}\n`).join(""),
|
|
|
- files: patchFiles,
|
|
|
- },
|
|
|
- sessionID: context.sessionID,
|
|
|
- agent: context.agent,
|
|
|
- source,
|
|
|
- })
|
|
|
-
|
|
|
- yield* Effect.forEach(
|
|
|
- prepared,
|
|
|
- (change) =>
|
|
|
- Effect.gen(function* () {
|
|
|
- if (change.type === "add") {
|
|
|
- yield* fs
|
|
|
- .writeWithDirs(
|
|
|
- change.target.canonical,
|
|
|
- change.contents.endsWith("\n") || change.contents === ""
|
|
|
- ? change.contents
|
|
|
- : `${change.contents}\n`,
|
|
|
- )
|
|
|
- .pipe(
|
|
|
- Effect.mapError((error) => fail(`Failed to write ${change.target.resource}`, error)),
|
|
|
- )
|
|
|
- applied.push({
|
|
|
- type: change.type,
|
|
|
- resource: change.target.resource,
|
|
|
- target: change.target.canonical,
|
|
|
- })
|
|
|
- return
|
|
|
- }
|
|
|
- if (change.type === "delete") {
|
|
|
- yield* fs
|
|
|
- .remove(change.target.canonical)
|
|
|
- .pipe(
|
|
|
- Effect.mapError((error) => fail(`Failed to delete ${change.target.resource}`, error)),
|
|
|
- )
|
|
|
- applied.push({
|
|
|
- type: change.type,
|
|
|
- resource: change.target.resource,
|
|
|
- target: change.target.canonical,
|
|
|
- })
|
|
|
- return
|
|
|
- }
|
|
|
- if (change.moveTarget) {
|
|
|
- const moveTarget = change.moveTarget
|
|
|
- yield* fs
|
|
|
- .writeWithDirs(moveTarget.canonical, change.content)
|
|
|
- .pipe(Effect.mapError((error) => fail(`Failed to write ${moveTarget.resource}`, error)))
|
|
|
- yield* fs.remove(change.target.canonical).pipe(
|
|
|
- Effect.mapError((error) =>
|
|
|
- fail(`Wrote ${moveTarget.resource} but failed to remove ${change.target.resource}`, error),
|
|
|
- ),
|
|
|
- )
|
|
|
- applied.push({
|
|
|
- type: change.type,
|
|
|
- resource: change.moveTarget.resource,
|
|
|
- target: change.moveTarget.canonical,
|
|
|
- })
|
|
|
- return
|
|
|
- }
|
|
|
- yield* fs
|
|
|
- .writeWithDirs(change.target.canonical, change.content)
|
|
|
- .pipe(Effect.mapError((error) => fail(`Failed to write ${change.target.resource}`, error)))
|
|
|
- applied.push({
|
|
|
- type: change.type,
|
|
|
- resource: change.target.resource,
|
|
|
- target: change.target.canonical,
|
|
|
+ const previous = updates.get(target.absolute)
|
|
|
+ const original =
|
|
|
+ previous ??
|
|
|
+ (yield* Effect.gen(function* () {
|
|
|
+ const stats = yield* fs.stat(target.absolute).pipe(
|
|
|
+ Effect.mapError(
|
|
|
+ (error) =>
|
|
|
+ new ToolFailure({
|
|
|
+ message: `patch verification failed: Failed to read file to update ${target.absolute}: ${errorMessage(error)}`,
|
|
|
+ }),
|
|
|
+ ),
|
|
|
+ )
|
|
|
+ if (stats.type === "Directory") {
|
|
|
+ return yield* new ToolFailure({
|
|
|
+ message: `patch verification failed: Failed to read file to update ${target.absolute}: path is a directory`,
|
|
|
})
|
|
|
- }),
|
|
|
- { discard: true },
|
|
|
- )
|
|
|
- const formatted = new Map<string, string>()
|
|
|
- yield* Effect.forEach(
|
|
|
- [...new Set(applied.filter((item) => item.type !== "delete").map((item) => item.target))],
|
|
|
- (target) =>
|
|
|
- Effect.gen(function* () {
|
|
|
- const current = yield* Bom.readFile(fs, target).pipe(
|
|
|
- Effect.mapError((error) => fail(`Failed to read ${target}`, error)),
|
|
|
- )
|
|
|
- formatted.set(
|
|
|
- target,
|
|
|
- (yield* formatter.file(target))
|
|
|
- ? yield* Bom.syncFile(fs, target, current.bom).pipe(
|
|
|
- Effect.mapError((error) => fail(`Failed to sync ${target}`, error)),
|
|
|
- )
|
|
|
- : current.text,
|
|
|
- )
|
|
|
- }),
|
|
|
- { discard: true },
|
|
|
- )
|
|
|
- const files = yield* Effect.forEach(prepared, (change) => {
|
|
|
- if (change.type === "delete") return Effect.succeed(patchFile(change))
|
|
|
- const target = change.type === "update" && change.moveTarget ? change.moveTarget : change.target
|
|
|
- return Effect.succeed(patchFile(change, formatted.get(target.canonical)))
|
|
|
+ }
|
|
|
+ const content = yield* Bom.readFile(fs, target.absolute).pipe(
|
|
|
+ Effect.mapError(
|
|
|
+ (error) =>
|
|
|
+ new ToolFailure({
|
|
|
+ message: `patch verification failed: Failed to read file to update ${target.absolute}: ${errorMessage(error)}`,
|
|
|
+ }),
|
|
|
+ ),
|
|
|
+ )
|
|
|
+ return Bom.join(content.text, content.bom)
|
|
|
+ }))
|
|
|
+ const before = Bom.split(original).text
|
|
|
+ const update = yield* Effect.try({
|
|
|
+ try: () => Patch.derive(hunk.path, hunk.chunks, original),
|
|
|
+ catch: (error) => new ToolFailure({ message: `patch verification failed: ${errorMessage(error)}` }),
|
|
|
+ })
|
|
|
+ const moveTarget = hunk.movePath ? resolveTarget(location, hunk.movePath) : undefined
|
|
|
+ if (moveTarget) targets.push(moveTarget)
|
|
|
+ if (moveTarget?.externalDirectory) {
|
|
|
+ yield* permission.assert({
|
|
|
+ action: "external_directory",
|
|
|
+ resources: [moveTarget.externalDirectory.resource],
|
|
|
+ save: [moveTarget.externalDirectory.resource],
|
|
|
+ metadata: {
|
|
|
+ filepath: moveTarget.absolute,
|
|
|
+ parentDir: moveTarget.externalDirectory.directory,
|
|
|
+ },
|
|
|
+ sessionID: context.sessionID,
|
|
|
+ agent: context.agent,
|
|
|
+ source,
|
|
|
+ })
|
|
|
+ }
|
|
|
+ prepared.push({
|
|
|
+ ...hunk,
|
|
|
+ target,
|
|
|
+ content: Patch.joinBom(update.content, update.bom),
|
|
|
+ before,
|
|
|
+ after: update.content,
|
|
|
+ moveTarget,
|
|
|
})
|
|
|
- return { applied, files }
|
|
|
+ if (!moveTarget) updates.set(target.absolute, Patch.joinBom(update.content, update.bom))
|
|
|
}).pipe(
|
|
|
- Effect.map((output) => ({
|
|
|
- output,
|
|
|
- content: toModelOutput(output),
|
|
|
- metadata: { files: output.files },
|
|
|
- })),
|
|
|
Effect.mapError((error) =>
|
|
|
error instanceof ToolFailure
|
|
|
? error
|
|
|
- : new ToolFailure({ message: "Unable to apply patch", error }),
|
|
|
+ : new ToolFailure({ message: `Unable to prepare patch at ${hunk.path}`, error }),
|
|
|
),
|
|
|
)
|
|
|
- },
|
|
|
- }),
|
|
|
- ),
|
|
|
+ }
|
|
|
+
|
|
|
+ const patchFiles = prepared.map((change) => patchFile(change))
|
|
|
+ yield* permission.assert({
|
|
|
+ action: "edit",
|
|
|
+ resources: [...new Set(targets.map((target) => target.resource))],
|
|
|
+ save: ["*"],
|
|
|
+ metadata: {
|
|
|
+ filepath: targets.map((target) => target.resource).join(", "),
|
|
|
+ diff: patchFiles.map((file) => `${file.patch}\n`).join(""),
|
|
|
+ files: patchFiles,
|
|
|
+ },
|
|
|
+ sessionID: context.sessionID,
|
|
|
+ agent: context.agent,
|
|
|
+ source,
|
|
|
+ })
|
|
|
+
|
|
|
+ yield* Effect.forEach(
|
|
|
+ prepared,
|
|
|
+ (change) =>
|
|
|
+ Effect.gen(function* () {
|
|
|
+ if (change.type === "add") {
|
|
|
+ yield* fs
|
|
|
+ .writeWithDirs(
|
|
|
+ change.target.absolute,
|
|
|
+ change.contents.endsWith("\n") || change.contents === ""
|
|
|
+ ? change.contents
|
|
|
+ : `${change.contents}\n`,
|
|
|
+ )
|
|
|
+ .pipe(Effect.mapError((error) => fail(`Failed to write ${change.target.resource}`, error)))
|
|
|
+ applied.push({
|
|
|
+ type: change.type,
|
|
|
+ resource: change.target.resource,
|
|
|
+ target: change.target.absolute,
|
|
|
+ })
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if (change.type === "delete") {
|
|
|
+ yield* fs
|
|
|
+ .remove(change.target.absolute)
|
|
|
+ .pipe(Effect.mapError((error) => fail(`Failed to delete ${change.target.resource}`, error)))
|
|
|
+ applied.push({
|
|
|
+ type: change.type,
|
|
|
+ resource: change.target.resource,
|
|
|
+ target: change.target.absolute,
|
|
|
+ })
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if (change.moveTarget) {
|
|
|
+ const moveTarget = change.moveTarget
|
|
|
+ yield* fs
|
|
|
+ .writeWithDirs(moveTarget.absolute, change.content)
|
|
|
+ .pipe(Effect.mapError((error) => fail(`Failed to write ${moveTarget.resource}`, error)))
|
|
|
+ yield* fs
|
|
|
+ .remove(change.target.absolute)
|
|
|
+ .pipe(
|
|
|
+ Effect.mapError((error) =>
|
|
|
+ fail(`Wrote ${moveTarget.resource} but failed to remove ${change.target.resource}`, error),
|
|
|
+ ),
|
|
|
+ )
|
|
|
+ applied.push({
|
|
|
+ type: change.type,
|
|
|
+ resource: change.moveTarget.resource,
|
|
|
+ target: change.moveTarget.absolute,
|
|
|
+ })
|
|
|
+ return
|
|
|
+ }
|
|
|
+ yield* fs
|
|
|
+ .writeWithDirs(change.target.absolute, change.content)
|
|
|
+ .pipe(Effect.mapError((error) => fail(`Failed to write ${change.target.resource}`, error)))
|
|
|
+ applied.push({
|
|
|
+ type: change.type,
|
|
|
+ resource: change.target.resource,
|
|
|
+ target: change.target.absolute,
|
|
|
+ })
|
|
|
+ }),
|
|
|
+ { discard: true },
|
|
|
+ )
|
|
|
+ const formatted = new Map<string, string>()
|
|
|
+ yield* Effect.forEach(
|
|
|
+ [...new Set(applied.filter((item) => item.type !== "delete").map((item) => item.target))],
|
|
|
+ (target) =>
|
|
|
+ Effect.gen(function* () {
|
|
|
+ const current = yield* Bom.readFile(fs, target).pipe(
|
|
|
+ Effect.mapError((error) => fail(`Failed to read ${target}`, error)),
|
|
|
+ )
|
|
|
+ formatted.set(
|
|
|
+ target,
|
|
|
+ (yield* formatter.file(target))
|
|
|
+ ? yield* Bom.syncFile(fs, target, current.bom).pipe(
|
|
|
+ Effect.mapError((error) => fail(`Failed to sync ${target}`, error)),
|
|
|
+ )
|
|
|
+ : current.text,
|
|
|
+ )
|
|
|
+ }),
|
|
|
+ { discard: true },
|
|
|
+ )
|
|
|
+ const files = yield* Effect.forEach(prepared, (change) => {
|
|
|
+ if (change.type === "delete") return Effect.succeed(patchFile(change))
|
|
|
+ const target = change.type === "update" && change.moveTarget ? change.moveTarget : change.target
|
|
|
+ return Effect.succeed(patchFile(change, formatted.get(target.absolute)))
|
|
|
+ })
|
|
|
+ return { applied, files }
|
|
|
+ }).pipe(
|
|
|
+ Effect.map((output) => ({
|
|
|
+ output,
|
|
|
+ content: toModelOutput(output),
|
|
|
+ metadata: { files: output.files },
|
|
|
+ })),
|
|
|
+ Effect.mapError((error) =>
|
|
|
+ error instanceof ToolFailure ? error : new ToolFailure({ message: "Unable to apply patch", error }),
|
|
|
+ ),
|
|
|
+ )
|
|
|
+ },
|
|
|
+ }),
|
|
|
)
|
|
|
.pipe(Effect.orDie)
|
|
|
|
|
|
@@ -365,9 +354,7 @@ function errorMessage(error: unknown) {
|
|
|
|
|
|
function patchFile(change: Prepared, after = change.after): typeof FileDiff.Info.Type {
|
|
|
const target = (change.type === "update" ? change.moveTarget : undefined)?.resource ?? change.target.resource
|
|
|
- const patch = trimDiff(
|
|
|
- createTwoFilesPatch(change.target.canonical, change.target.canonical, change.before, after),
|
|
|
- )
|
|
|
+ const patch = trimDiff(createTwoFilesPatch(change.target.absolute, change.target.absolute, change.before, after))
|
|
|
const counts =
|
|
|
change.type === "delete"
|
|
|
? { additions: 0, deletions: change.before.split("\n").length }
|
|
|
@@ -416,22 +403,22 @@ function trimDiff(diff: string) {
|
|
|
}
|
|
|
|
|
|
function resolveTarget(location: Location.Interface, value: string): Target {
|
|
|
- const canonical =
|
|
|
+ const absolute =
|
|
|
process.platform === "win32"
|
|
|
? FSUtil.normalizePath(path.resolve(location.directory, value))
|
|
|
: path.resolve(location.directory, value)
|
|
|
const projectRoot = path.parse(location.project.directory).root
|
|
|
const external =
|
|
|
- !FSUtil.contains(location.directory, canonical) &&
|
|
|
- (location.project.directory === projectRoot || !FSUtil.contains(location.project.directory, canonical))
|
|
|
- const directory = path.dirname(canonical)
|
|
|
+ !FSUtil.contains(location.directory, absolute) &&
|
|
|
+ (location.project.directory === projectRoot || !FSUtil.contains(location.project.directory, absolute))
|
|
|
+ const directory = path.dirname(absolute)
|
|
|
const resource =
|
|
|
process.platform === "win32"
|
|
|
? FSUtil.normalizePathPattern(path.join(directory, "*"))
|
|
|
: path.join(directory, "*").replaceAll("\\", "/")
|
|
|
return {
|
|
|
- canonical,
|
|
|
- resource: path.relative(location.project.directory, canonical).replaceAll("\\", "/") || ".",
|
|
|
+ absolute,
|
|
|
+ resource: path.relative(location.project.directory, absolute).replaceAll("\\", "/") || ".",
|
|
|
externalDirectory: external ? { directory, resource } : undefined,
|
|
|
}
|
|
|
}
|