@deshlo/plugin-github 0.1.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.
Files changed (46) hide show
  1. package/build/src/__tests__/plugin.test.d.ts +2 -0
  2. package/build/src/__tests__/plugin.test.d.ts.map +1 -0
  3. package/build/src/__tests__/plugin.test.js +19 -0
  4. package/build/src/index.d.ts +3 -0
  5. package/build/src/index.d.ts.map +1 -0
  6. package/build/src/index.js +5 -0
  7. package/build/src/lib/__tests__/githubProvider.test.d.ts +2 -0
  8. package/build/src/lib/__tests__/githubProvider.test.d.ts.map +1 -0
  9. package/build/src/lib/__tests__/githubProvider.test.js +54 -0
  10. package/build/src/lib/__tests__/hostConfig.test.d.ts +2 -0
  11. package/build/src/lib/__tests__/hostConfig.test.d.ts.map +1 -0
  12. package/build/src/lib/__tests__/hostConfig.test.js +35 -0
  13. package/build/src/lib/__tests__/jsxTextEdit.test.d.ts +2 -0
  14. package/build/src/lib/__tests__/jsxTextEdit.test.d.ts.map +1 -0
  15. package/build/src/lib/__tests__/jsxTextEdit.test.js +79 -0
  16. package/build/src/lib/__tests__/sourceLoc.test.d.ts +2 -0
  17. package/build/src/lib/__tests__/sourceLoc.test.d.ts.map +1 -0
  18. package/build/src/lib/__tests__/sourceLoc.test.js +26 -0
  19. package/build/src/lib/__tests__/workflow.test.d.ts +2 -0
  20. package/build/src/lib/__tests__/workflow.test.d.ts.map +1 -0
  21. package/build/src/lib/__tests__/workflow.test.js +90 -0
  22. package/build/src/lib/errors.d.ts +8 -0
  23. package/build/src/lib/errors.d.ts.map +1 -0
  24. package/build/src/lib/errors.js +33 -0
  25. package/build/src/lib/githubProvider.d.ts +35 -0
  26. package/build/src/lib/githubProvider.d.ts.map +1 -0
  27. package/build/src/lib/githubProvider.js +167 -0
  28. package/build/src/lib/hostConfig.d.ts +13 -0
  29. package/build/src/lib/hostConfig.d.ts.map +1 -0
  30. package/build/src/lib/hostConfig.js +68 -0
  31. package/build/src/lib/jsxTextEdit.d.ts +15 -0
  32. package/build/src/lib/jsxTextEdit.d.ts.map +1 -0
  33. package/build/src/lib/jsxTextEdit.js +162 -0
  34. package/build/src/lib/sourceLoc.d.ts +4 -0
  35. package/build/src/lib/sourceLoc.d.ts.map +1 -0
  36. package/build/src/lib/sourceLoc.js +27 -0
  37. package/build/src/lib/types.d.ts +44 -0
  38. package/build/src/lib/types.d.ts.map +1 -0
  39. package/build/src/lib/types.js +2 -0
  40. package/build/src/lib/workflow.d.ts +19 -0
  41. package/build/src/lib/workflow.d.ts.map +1 -0
  42. package/build/src/lib/workflow.js +174 -0
  43. package/build/src/plugin.d.ts +9 -0
  44. package/build/src/plugin.d.ts.map +1 -0
  45. package/build/src/plugin.js +83 -0
  46. package/package.json +42 -0
@@ -0,0 +1,68 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveRepoConfigForCurrentHost = resolveRepoConfigForCurrentHost;
4
+ const errors_1 = require("./errors");
5
+ const HOST_CONFIG_ENV = "NEXT_PUBLIC_SOURCE_INSPECTOR_HOST_CONFIG";
6
+ function parseHostConfig(raw) {
7
+ let parsed;
8
+ try {
9
+ parsed = JSON.parse(raw);
10
+ }
11
+ catch {
12
+ throw new errors_1.SourceInspectorError("UNMAPPED_HOST", `${HOST_CONFIG_ENV} must be valid JSON.`);
13
+ }
14
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
15
+ throw new errors_1.SourceInspectorError("UNMAPPED_HOST", `${HOST_CONFIG_ENV} must be an object keyed by host.`);
16
+ }
17
+ return parsed;
18
+ }
19
+ function parseHostConfigInput(input) {
20
+ if (input && typeof input === "object" && !Array.isArray(input)) {
21
+ return input;
22
+ }
23
+ const raw = typeof input === "string" ? input : process.env.NEXT_PUBLIC_SOURCE_INSPECTOR_HOST_CONFIG;
24
+ if (!raw) {
25
+ throw new errors_1.SourceInspectorError("UNMAPPED_HOST", `${HOST_CONFIG_ENV} is not configured.`);
26
+ }
27
+ return parseHostConfig(raw);
28
+ }
29
+ function getCurrentHost() {
30
+ if (typeof window === "undefined" || !window.location?.host) {
31
+ throw new errors_1.SourceInspectorError("UNMAPPED_HOST", "Unable to infer current host in this runtime.");
32
+ }
33
+ const host = window.location.host.trim().toLowerCase();
34
+ if (!host) {
35
+ throw new errors_1.SourceInspectorError("UNMAPPED_HOST", "Unable to infer current host.");
36
+ }
37
+ return host;
38
+ }
39
+ function stripPort(host) {
40
+ const portIndex = host.indexOf(":");
41
+ if (portIndex === -1) {
42
+ return host;
43
+ }
44
+ return host.slice(0, portIndex);
45
+ }
46
+ function validateHostConfig(host, config) {
47
+ if (!config || typeof config !== "object") {
48
+ throw new errors_1.SourceInspectorError("UNMAPPED_HOST", `Missing repository configuration for host ${host}.`);
49
+ }
50
+ if (!config.owner || !config.repo) {
51
+ throw new errors_1.SourceInspectorError("UNMAPPED_HOST", `Host config for ${host} must include owner and repo.`);
52
+ }
53
+ return {
54
+ ...config,
55
+ host,
56
+ apiBaseUrl: config.apiBaseUrl?.trim() || "https://api.github.com",
57
+ };
58
+ }
59
+ function resolveRepoConfigForCurrentHost(hostOverride, hostConfigInput) {
60
+ const host = (hostOverride || getCurrentHost()).trim().toLowerCase();
61
+ const withoutPort = stripPort(host);
62
+ const mapping = parseHostConfigInput(hostConfigInput);
63
+ const config = mapping[host] ?? mapping[withoutPort];
64
+ if (!config) {
65
+ throw new errors_1.SourceInspectorError("UNMAPPED_HOST", `No source inspector repo mapping found for host ${host}.`);
66
+ }
67
+ return validateHostConfig(host, config);
68
+ }
@@ -0,0 +1,15 @@
1
+ import type { ParsedSourceLoc } from "./types";
2
+ export interface ApplyTextReplacementInput {
3
+ sourceCode: string;
4
+ sourceLoc: ParsedSourceLoc;
5
+ tagName: string;
6
+ selectedText: string;
7
+ proposedText: string;
8
+ }
9
+ export interface ApplyTextReplacementResult {
10
+ updatedSourceCode: string;
11
+ oldText: string;
12
+ newText: string;
13
+ }
14
+ export declare function applyTextReplacement({ sourceCode, sourceLoc, tagName, selectedText, proposedText, }: ApplyTextReplacementInput): ApplyTextReplacementResult;
15
+ //# sourceMappingURL=jsxTextEdit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jsxTextEdit.d.ts","sourceRoot":"","sources":["../../../src/lib/jsxTextEdit.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE/C,MAAM,WAAW,yBAAyB;IACxC,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,eAAe,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,0BAA0B;IACzC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB;AA6FD,wBAAgB,oBAAoB,CAAC,EACnC,UAAU,EACV,SAAS,EACT,OAAO,EACP,YAAY,EACZ,YAAY,GACb,EAAE,yBAAyB,GAAG,0BAA0B,CAuFxD"}
@@ -0,0 +1,162 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.applyTextReplacement = applyTextReplacement;
40
+ const parser = __importStar(require("@babel/parser"));
41
+ const traverse_1 = __importDefault(require("@babel/traverse"));
42
+ const t = __importStar(require("@babel/types"));
43
+ const errors_1 = require("./errors");
44
+ const sourceLoc_1 = require("./sourceLoc");
45
+ function findMatchedOpeningElement(sourceCode, sourceLoc, tagName) {
46
+ let ast;
47
+ try {
48
+ ast = parser.parse(sourceCode, {
49
+ sourceType: "unambiguous",
50
+ errorRecovery: true,
51
+ plugins: [
52
+ "jsx",
53
+ "typescript",
54
+ "classProperties",
55
+ "classPrivateProperties",
56
+ "classPrivateMethods",
57
+ "dynamicImport",
58
+ "importMeta",
59
+ "topLevelAwait",
60
+ ],
61
+ });
62
+ }
63
+ catch {
64
+ throw new errors_1.SourceInspectorError("INVALID_SOURCE_LOC", "Unable to parse source file.");
65
+ }
66
+ const matches = [];
67
+ (0, traverse_1.default)(ast, {
68
+ JSXOpeningElement(path) {
69
+ const nameNode = path.node.name;
70
+ if (!t.isJSXIdentifier(nameNode)) {
71
+ return;
72
+ }
73
+ if (!path.node.loc || typeof path.node.start !== "number") {
74
+ return;
75
+ }
76
+ const nodeLine = path.node.loc.start.line;
77
+ const nodeColumnOneBased = path.node.loc.start.column + 1;
78
+ if (nodeLine === sourceLoc.line &&
79
+ nodeColumnOneBased === sourceLoc.column &&
80
+ nameNode.name === tagName) {
81
+ matches.push({
82
+ node: path.node,
83
+ start: path.node.start,
84
+ });
85
+ }
86
+ },
87
+ });
88
+ if (matches.length !== 1) {
89
+ throw new errors_1.SourceInspectorError("INVALID_SOURCE_LOC", "Unable to map selected element to a unique JSX tag in source file.");
90
+ }
91
+ return matches[0];
92
+ }
93
+ function replaceTrimmedPortion(rawText, replacement) {
94
+ const trimmed = rawText.trim();
95
+ if (!trimmed) {
96
+ throw new errors_1.SourceInspectorError("NON_TEXT_NODE", "Selected element does not contain editable text.");
97
+ }
98
+ const first = rawText.indexOf(trimmed);
99
+ const last = rawText.lastIndexOf(trimmed);
100
+ if (first === -1 || first !== last) {
101
+ throw new errors_1.SourceInspectorError("NON_TEXT_NODE", "Selected element text is ambiguous and cannot be replaced safely.");
102
+ }
103
+ return `${rawText.slice(0, first)}${replacement}${rawText.slice(first + trimmed.length)}`;
104
+ }
105
+ function applyTextReplacement({ sourceCode, sourceLoc, tagName, selectedText, proposedText, }) {
106
+ const matchedOpening = findMatchedOpeningElement(sourceCode, sourceLoc, tagName);
107
+ let ast;
108
+ try {
109
+ ast = parser.parse(sourceCode, {
110
+ sourceType: "unambiguous",
111
+ errorRecovery: true,
112
+ plugins: ["jsx", "typescript", "classProperties", "classPrivateProperties", "classPrivateMethods"],
113
+ });
114
+ }
115
+ catch {
116
+ throw new errors_1.SourceInspectorError("INVALID_SOURCE_LOC", "Unable to parse source file.");
117
+ }
118
+ let targetChildren = null;
119
+ (0, traverse_1.default)(ast, {
120
+ JSXElement(path) {
121
+ const opening = path.node.openingElement;
122
+ if (typeof opening.start !== "number") {
123
+ return;
124
+ }
125
+ if (opening.start === matchedOpening.start) {
126
+ targetChildren = path.node.children;
127
+ path.stop();
128
+ }
129
+ },
130
+ });
131
+ if (!targetChildren) {
132
+ throw new errors_1.SourceInspectorError("INVALID_SOURCE_LOC", "Unable to locate selected JSX element.");
133
+ }
134
+ const resolvedChildren = targetChildren;
135
+ const unsupportedChild = resolvedChildren.some((child) => !t.isJSXText(child));
136
+ if (unsupportedChild) {
137
+ throw new errors_1.SourceInspectorError("NON_TEXT_NODE", "Selected element contains nested or dynamic children. Select a simple text element.");
138
+ }
139
+ const meaningfulTextNodes = resolvedChildren.filter((child) => t.isJSXText(child) && child.value.trim().length > 0);
140
+ if (meaningfulTextNodes.length !== 1) {
141
+ throw new errors_1.SourceInspectorError("NON_TEXT_NODE", "Selected element must contain exactly one direct text segment.");
142
+ }
143
+ const textNode = meaningfulTextNodes[0];
144
+ if (typeof textNode.start !== "number" || typeof textNode.end !== "number") {
145
+ throw new errors_1.SourceInspectorError("INVALID_SOURCE_LOC", "Unable to map selected text node offsets.");
146
+ }
147
+ const rawText = sourceCode.slice(textNode.start, textNode.end);
148
+ const oldText = rawText.trim();
149
+ if ((0, sourceLoc_1.normalizeTextForComparison)(oldText) !== (0, sourceLoc_1.normalizeTextForComparison)(selectedText)) {
150
+ throw new errors_1.SourceInspectorError("TEXT_MISMATCH", "Selected DOM text no longer matches source. Refresh and try again.");
151
+ }
152
+ if (oldText === proposedText.trim()) {
153
+ throw new errors_1.SourceInspectorError("NO_DIFF", "No effective change detected.");
154
+ }
155
+ const replacementRawText = replaceTrimmedPortion(rawText, proposedText.trim());
156
+ const updatedSourceCode = sourceCode.slice(0, textNode.start) + replacementRawText + sourceCode.slice(textNode.end);
157
+ return {
158
+ updatedSourceCode,
159
+ oldText,
160
+ newText: proposedText.trim(),
161
+ };
162
+ }
@@ -0,0 +1,4 @@
1
+ import type { ParsedSourceLoc } from "./types";
2
+ export declare function parseSourceLoc(raw: string): ParsedSourceLoc;
3
+ export declare function normalizeTextForComparison(value: string): string;
4
+ //# sourceMappingURL=sourceLoc.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sourceLoc.d.ts","sourceRoot":"","sources":["../../../src/lib/sourceLoc.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAI/C,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,CA2B3D;AAED,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEhE"}
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseSourceLoc = parseSourceLoc;
4
+ exports.normalizeTextForComparison = normalizeTextForComparison;
5
+ const errors_1 = require("./errors");
6
+ const SOURCE_LOC_PATTERN = /^(?<filePath>.+):(?<line>\d+):(?<column>\d+)$/;
7
+ function parseSourceLoc(raw) {
8
+ const trimmed = raw.trim();
9
+ const match = SOURCE_LOC_PATTERN.exec(trimmed);
10
+ if (!match || !match.groups) {
11
+ throw new errors_1.SourceInspectorError("INVALID_SOURCE_LOC", "Invalid source location format. Expected path:line:column.");
12
+ }
13
+ const filePath = match.groups.filePath;
14
+ const line = Number(match.groups.line);
15
+ const column = Number(match.groups.column);
16
+ if (!filePath || Number.isNaN(line) || Number.isNaN(column) || line < 1 || column < 1) {
17
+ throw new errors_1.SourceInspectorError("INVALID_SOURCE_LOC", "Invalid source location values. Line and column must be positive integers.");
18
+ }
19
+ return {
20
+ filePath,
21
+ line,
22
+ column,
23
+ };
24
+ }
25
+ function normalizeTextForComparison(value) {
26
+ return value.replace(/\s+/g, " ").trim();
27
+ }
@@ -0,0 +1,44 @@
1
+ export type SourceInspectorErrorCode = "AUTH_REQUIRED" | "UNMAPPED_HOST" | "INVALID_SOURCE_LOC" | "BASE_BRANCH_NOT_FOUND" | "FILE_NOT_FOUND" | "NON_TEXT_NODE" | "TEXT_MISMATCH" | "NO_DIFF" | "PROVIDER_ERROR";
2
+ export interface ProposedChangeInput {
3
+ sourceLoc: string;
4
+ tagName: string;
5
+ selectedText: string;
6
+ proposedText: string;
7
+ baseBranch: string;
8
+ commitSha?: string;
9
+ }
10
+ export interface ParsedSourceLoc {
11
+ filePath: string;
12
+ line: number;
13
+ column: number;
14
+ }
15
+ export interface PreviewChangeSuccess {
16
+ filePath: string;
17
+ oldText: string;
18
+ newText: string;
19
+ line: number;
20
+ column: number;
21
+ tagName: string;
22
+ branchNamePreview: string;
23
+ }
24
+ export interface CreateDraftPrSuccess {
25
+ branchName: string;
26
+ commitSha: string;
27
+ prNumber: number;
28
+ prUrl: string;
29
+ }
30
+ export interface ActionFailure {
31
+ ok: false;
32
+ code: SourceInspectorErrorCode;
33
+ message: string;
34
+ }
35
+ export interface ActionSuccess<T> {
36
+ ok: true;
37
+ data: T;
38
+ }
39
+ export type ActionResult<T> = ActionSuccess<T> | ActionFailure;
40
+ export interface BranchesSuccess {
41
+ branches: string[];
42
+ defaultBaseBranch: string;
43
+ }
44
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/lib/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,wBAAwB,GAChC,eAAe,GACf,eAAe,GACf,oBAAoB,GACpB,uBAAuB,GACvB,gBAAgB,GAChB,eAAe,GACf,eAAe,GACf,SAAS,GACT,gBAAgB,CAAC;AAErB,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,oBAAoB;IACnC,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,KAAK,CAAC;IACV,IAAI,EAAE,wBAAwB,CAAC;IAC/B,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,aAAa,CAAC,CAAC;IAC9B,EAAE,EAAE,IAAI,CAAC;IACT,IAAI,EAAE,CAAC,CAAC;CACT;AAED,MAAM,MAAM,YAAY,CAAC,CAAC,IAAI,aAAa,CAAC,CAAC,CAAC,GAAG,aAAa,CAAC;AAE/D,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,iBAAiB,EAAE,MAAM,CAAC;CAC3B"}
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,19 @@
1
+ import { type RepoProvider } from "./githubProvider";
2
+ import { type HostConfigInput } from "./hostConfig";
3
+ import type { BranchesSuccess, CreateDraftPrSuccess, PreviewChangeSuccess, ProposedChangeInput } from "./types";
4
+ export interface ClientRuntimeContext {
5
+ token: string;
6
+ runtime?: SourceInspectorRuntimeOptions;
7
+ }
8
+ export interface SourceInspectorRuntimeOptions {
9
+ host?: string;
10
+ hostConfig?: HostConfigInput;
11
+ githubPathPrefix?: string;
12
+ branchPrefix?: string;
13
+ }
14
+ export declare function getBranches(context: ClientRuntimeContext): Promise<BranchesSuccess>;
15
+ export declare function previewProposedChangeWithProvider(input: ProposedChangeInput, provider: RepoProvider, runtime?: SourceInspectorRuntimeOptions): Promise<PreviewChangeSuccess>;
16
+ export declare function createDraftPrFromProposedChangeWithProvider(input: ProposedChangeInput, provider: RepoProvider, runtime?: SourceInspectorRuntimeOptions): Promise<CreateDraftPrSuccess>;
17
+ export declare function previewProposedChange(input: ProposedChangeInput, context: ClientRuntimeContext): Promise<PreviewChangeSuccess>;
18
+ export declare function createDraftPrFromProposedChange(input: ProposedChangeInput, context: ClientRuntimeContext): Promise<CreateDraftPrSuccess>;
19
+ //# sourceMappingURL=workflow.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workflow.d.ts","sourceRoot":"","sources":["../../../src/lib/workflow.ts"],"names":[],"mappings":"AACA,OAAO,EAAwB,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAC3E,OAAO,EAEL,KAAK,eAAe,EACrB,MAAM,cAAc,CAAC;AAGtB,OAAO,KAAK,EACV,eAAe,EACf,oBAAoB,EACpB,oBAAoB,EACpB,mBAAmB,EACpB,MAAM,SAAS,CAAC;AA6EjB,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,6BAA6B,CAAC;CACzC;AAED,MAAM,WAAW,6BAA6B;IAC5C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,eAAe,CAAC;IAC7B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAeD,wBAAsB,WAAW,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,eAAe,CAAC,CAkBzF;AAoCD,wBAAsB,iCAAiC,CACrD,KAAK,EAAE,mBAAmB,EAC1B,QAAQ,EAAE,YAAY,EACtB,OAAO,CAAC,EAAE,6BAA6B,GACtC,OAAO,CAAC,oBAAoB,CAAC,CAG/B;AAyBD,wBAAsB,2CAA2C,CAC/D,KAAK,EAAE,mBAAmB,EAC1B,QAAQ,EAAE,YAAY,EACtB,OAAO,CAAC,EAAE,6BAA6B,GACtC,OAAO,CAAC,oBAAoB,CAAC,CAwC/B;AAED,wBAAsB,qBAAqB,CACzC,KAAK,EAAE,mBAAmB,EAC1B,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,oBAAoB,CAAC,CAG/B;AAED,wBAAsB,+BAA+B,CACnD,KAAK,EAAE,mBAAmB,EAC1B,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,oBAAoB,CAAC,CAG/B"}
@@ -0,0 +1,174 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getBranches = getBranches;
4
+ exports.previewProposedChangeWithProvider = previewProposedChangeWithProvider;
5
+ exports.createDraftPrFromProposedChangeWithProvider = createDraftPrFromProposedChangeWithProvider;
6
+ exports.previewProposedChange = previewProposedChange;
7
+ exports.createDraftPrFromProposedChange = createDraftPrFromProposedChange;
8
+ const errors_1 = require("./errors");
9
+ const githubProvider_1 = require("./githubProvider");
10
+ const hostConfig_1 = require("./hostConfig");
11
+ const jsxTextEdit_1 = require("./jsxTextEdit");
12
+ const sourceLoc_1 = require("./sourceLoc");
13
+ function normalizeRepoPath(value) {
14
+ return value
15
+ .replace(/\\/g, "/")
16
+ .replace(/^\/+/, "")
17
+ .replace(/^\.\/+/, "");
18
+ }
19
+ function resolveGitHubFilePath(sourceFilePath, prefixOverride) {
20
+ const normalizedSourcePath = normalizeRepoPath(sourceFilePath);
21
+ const rawPrefix = prefixOverride?.trim() || process.env.NEXT_PUBLIC_SOURCE_INSPECTOR_GITHUB_PATH_PREFIX?.trim() || "";
22
+ const normalizedPrefix = rawPrefix
23
+ .replace(/\\/g, "/")
24
+ .replace(/^\/+/, "")
25
+ .replace(/\/+$/, "");
26
+ if (!normalizedPrefix) {
27
+ return normalizedSourcePath;
28
+ }
29
+ if (normalizedSourcePath === normalizedPrefix ||
30
+ normalizedSourcePath.startsWith(`${normalizedPrefix}/`)) {
31
+ return normalizedSourcePath;
32
+ }
33
+ return `${normalizedPrefix}/${normalizedSourcePath}`;
34
+ }
35
+ function slugifyTag(tagName) {
36
+ return (tagName
37
+ .trim()
38
+ .toLowerCase()
39
+ .replace(/[^a-z0-9]+/g, "-")
40
+ .replace(/^-+|-+$/g, "") || "element");
41
+ }
42
+ function generateBranchName(tagName, prefixOverride) {
43
+ const prefix = prefixOverride?.trim() ||
44
+ process.env.NEXT_PUBLIC_SOURCE_INSPECTOR_BRANCH_PREFIX ||
45
+ "source-inspector";
46
+ const timestamp = Date.now();
47
+ const slug = slugifyTag(tagName);
48
+ return `${prefix}/${slug}-${timestamp}`;
49
+ }
50
+ function assertValidInput(input) {
51
+ if (!input.tagName?.trim()) {
52
+ throw new errors_1.SourceInspectorError("INVALID_SOURCE_LOC", "tagName is required.");
53
+ }
54
+ if (!input.sourceLoc?.trim()) {
55
+ throw new errors_1.SourceInspectorError("INVALID_SOURCE_LOC", "sourceLoc is required.");
56
+ }
57
+ if (!input.baseBranch?.trim()) {
58
+ throw new errors_1.SourceInspectorError("BASE_BRANCH_NOT_FOUND", "baseBranch is required.");
59
+ }
60
+ if (!input.selectedText?.trim()) {
61
+ throw new errors_1.SourceInspectorError("NON_TEXT_NODE", "Selected element does not contain direct text. Select a text element.");
62
+ }
63
+ if (!input.proposedText?.trim()) {
64
+ throw new errors_1.SourceInspectorError("NO_DIFF", "Proposed changes cannot be empty.");
65
+ }
66
+ }
67
+ function createProviderFromContext(context) {
68
+ const token = context.token.trim();
69
+ if (!token) {
70
+ throw new errors_1.SourceInspectorError("AUTH_REQUIRED", "GitHub token is required.");
71
+ }
72
+ const repoConfig = (0, hostConfig_1.resolveRepoConfigForCurrentHost)(context.runtime?.host, context.runtime?.hostConfig);
73
+ return (0, githubProvider_1.createGitHubProvider)(repoConfig, token);
74
+ }
75
+ async function getBranches(context) {
76
+ const repoConfig = (0, hostConfig_1.resolveRepoConfigForCurrentHost)(context.runtime?.host, context.runtime?.hostConfig);
77
+ const provider = (0, githubProvider_1.createGitHubProvider)(repoConfig, context.token);
78
+ const branches = await provider.listBranches();
79
+ if (branches.length === 0) {
80
+ throw new errors_1.SourceInspectorError("BASE_BRANCH_NOT_FOUND", "Repository has no branches.");
81
+ }
82
+ const fallback = branches.includes("main") ? "main" : branches[0];
83
+ return {
84
+ branches,
85
+ defaultBaseBranch: repoConfig.defaultBaseBranch || fallback,
86
+ };
87
+ }
88
+ async function buildPreview(input, provider, runtime) {
89
+ assertValidInput(input);
90
+ const parsedSourceLoc = (0, sourceLoc_1.parseSourceLoc)(input.sourceLoc);
91
+ const repoFilePath = resolveGitHubFilePath(parsedSourceLoc.filePath, runtime?.githubPathPrefix);
92
+ await provider.getBranchHeadSha(input.baseBranch);
93
+ const file = await provider.getFileContent(repoFilePath, input.baseBranch);
94
+ const replacement = (0, jsxTextEdit_1.applyTextReplacement)({
95
+ sourceCode: file.content,
96
+ sourceLoc: parsedSourceLoc,
97
+ tagName: input.tagName,
98
+ selectedText: input.selectedText,
99
+ proposedText: input.proposedText,
100
+ });
101
+ return {
102
+ preview: {
103
+ filePath: repoFilePath,
104
+ oldText: replacement.oldText,
105
+ newText: replacement.newText,
106
+ line: parsedSourceLoc.line,
107
+ column: parsedSourceLoc.column,
108
+ tagName: input.tagName,
109
+ branchNamePreview: generateBranchName(input.tagName, runtime?.branchPrefix),
110
+ },
111
+ updatedSourceCode: replacement.updatedSourceCode,
112
+ };
113
+ }
114
+ async function previewProposedChangeWithProvider(input, provider, runtime) {
115
+ const { preview } = await buildPreview(input, provider, runtime);
116
+ return preview;
117
+ }
118
+ function buildPrBody(params) {
119
+ const lines = [
120
+ "This draft PR was generated by Source Inspector.",
121
+ "",
122
+ `- Source location: \`${params.sourceLoc}\``,
123
+ `- Base branch: \`${params.baseBranch}\``,
124
+ `- Old text: \`${params.oldText}\``,
125
+ `- New text: \`${params.newText}\``,
126
+ ];
127
+ if (params.commitSha && params.commitSha !== "unknown") {
128
+ lines.push(`- Build commit: \`${params.commitSha}\``);
129
+ }
130
+ return lines.join("\n");
131
+ }
132
+ async function createDraftPrFromProposedChangeWithProvider(input, provider, runtime) {
133
+ const { preview, updatedSourceCode } = await buildPreview(input, provider, runtime);
134
+ const branchName = generateBranchName(input.tagName, runtime?.branchPrefix);
135
+ const baseSha = await provider.getBranchHeadSha(input.baseBranch);
136
+ await provider.createBranch(branchName, baseSha);
137
+ const currentFile = await provider.getFileContent(preview.filePath, input.baseBranch);
138
+ const commitMessage = `Source Inspector: update ${preview.tagName} text in ${preview.filePath}`;
139
+ const commit = await provider.updateFile({
140
+ path: preview.filePath,
141
+ branch: branchName,
142
+ sha: currentFile.sha,
143
+ content: updatedSourceCode,
144
+ message: commitMessage,
145
+ });
146
+ const prTitle = `Source Inspector: update ${preview.tagName} text in ${preview.filePath}`;
147
+ const prBody = buildPrBody({
148
+ sourceLoc: input.sourceLoc,
149
+ oldText: preview.oldText,
150
+ newText: preview.newText,
151
+ baseBranch: input.baseBranch,
152
+ commitSha: input.commitSha,
153
+ });
154
+ const pr = await provider.createDraftPullRequest({
155
+ title: prTitle,
156
+ body: prBody,
157
+ head: branchName,
158
+ base: input.baseBranch,
159
+ });
160
+ return {
161
+ branchName,
162
+ commitSha: commit.commitSha,
163
+ prNumber: pr.prNumber,
164
+ prUrl: pr.prUrl,
165
+ };
166
+ }
167
+ async function previewProposedChange(input, context) {
168
+ const provider = createProviderFromContext(context);
169
+ return previewProposedChangeWithProvider(input, provider, context.runtime);
170
+ }
171
+ async function createDraftPrFromProposedChange(input, context) {
172
+ const provider = createProviderFromContext(context);
173
+ return createDraftPrFromProposedChangeWithProvider(input, provider, context.runtime);
174
+ }
@@ -0,0 +1,9 @@
1
+ import type { OverlayPlugin } from "@deshlo/core/overlay-plugin";
2
+ import { type SourceInspectorRuntimeOptions } from "./lib/workflow";
3
+ export interface GitHubBrowserPluginConfig extends SourceInspectorRuntimeOptions {
4
+ token?: string;
5
+ baseBranch?: string;
6
+ }
7
+ export declare function createGitHubBrowserPlugin(config?: GitHubBrowserPluginConfig): OverlayPlugin;
8
+ export type { SourceInspectorRuntimeOptions };
9
+ //# sourceMappingURL=plugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,aAAa,EAId,MAAM,6BAA6B,CAAC;AAGrC,OAAO,EAGL,KAAK,6BAA6B,EACnC,MAAM,gBAAgB,CAAC;AAExB,MAAM,WAAW,yBAA0B,SAAQ,6BAA6B;IAC9E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AA8CD,wBAAgB,yBAAyB,CACvC,MAAM,GAAE,yBAA8B,GACrC,aAAa,CAoDf;AAED,YAAY,EAAE,6BAA6B,EAAE,CAAC"}
@@ -0,0 +1,83 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createGitHubBrowserPlugin = createGitHubBrowserPlugin;
4
+ const errors_1 = require("./lib/errors");
5
+ const workflow_1 = require("./lib/workflow");
6
+ function resolveToken(config) {
7
+ return (config.token?.trim() ||
8
+ process.env.NEXT_PUBLIC_SOURCE_INSPECTOR_GITHUB_TOKEN?.trim() ||
9
+ "");
10
+ }
11
+ function resolveHost(context, config) {
12
+ return config.host || context.host || "unknown";
13
+ }
14
+ function toErrorResult(error) {
15
+ if (error instanceof errors_1.SourceInspectorError) {
16
+ return {
17
+ ok: false,
18
+ message: `${error.code}: ${error.message}`,
19
+ };
20
+ }
21
+ if (error instanceof Error) {
22
+ return {
23
+ ok: false,
24
+ message: `PROVIDER_ERROR: ${error.message}`,
25
+ };
26
+ }
27
+ return {
28
+ ok: false,
29
+ message: "PROVIDER_ERROR: Unexpected provider error.",
30
+ };
31
+ }
32
+ function toProposedChangeInput(input, baseBranch) {
33
+ return {
34
+ sourceLoc: input.sourceLoc,
35
+ tagName: input.tagName,
36
+ selectedText: input.selectedText,
37
+ proposedText: input.proposedText,
38
+ commitSha: input.commitSha,
39
+ baseBranch,
40
+ };
41
+ }
42
+ function createGitHubBrowserPlugin(config = {}) {
43
+ return {
44
+ id: "github-browser",
45
+ async submit(input, context) {
46
+ const token = resolveToken(config);
47
+ if (!token) {
48
+ return {
49
+ ok: false,
50
+ message: "AUTH_REQUIRED: Configure NEXT_PUBLIC_SOURCE_INSPECTOR_GITHUB_TOKEN or pass token.",
51
+ };
52
+ }
53
+ const runtime = {
54
+ ...config,
55
+ host: resolveHost(context, config),
56
+ };
57
+ try {
58
+ const baseBranch = config.baseBranch ||
59
+ (await (0, workflow_1.getBranches)({
60
+ token,
61
+ runtime,
62
+ })).defaultBaseBranch;
63
+ const result = await (0, workflow_1.createDraftPrFromProposedChange)(toProposedChangeInput(input, baseBranch), {
64
+ token,
65
+ runtime,
66
+ });
67
+ return {
68
+ ok: true,
69
+ message: `Draft PR #${result.prNumber} created on branch ${result.branchName}.`,
70
+ links: [
71
+ {
72
+ label: `Open PR #${result.prNumber}`,
73
+ url: result.prUrl,
74
+ },
75
+ ],
76
+ };
77
+ }
78
+ catch (error) {
79
+ return toErrorResult(error);
80
+ }
81
+ },
82
+ };
83
+ }