@jpkme/diff-review 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.
- package/LICENSE +21 -0
- package/README.md +99 -0
- package/bin/diff-review.mjs +175 -0
- package/package.json +53 -0
- package/schema.json +158 -0
- package/web/build/client/_app/immutable/assets/0.CLQiq3Ed.css +1 -0
- package/web/build/client/_app/immutable/assets/0.CLQiq3Ed.css.br +0 -0
- package/web/build/client/_app/immutable/assets/0.CLQiq3Ed.css.gz +0 -0
- package/web/build/client/_app/immutable/assets/2.CHpfuCyA.css +1 -0
- package/web/build/client/_app/immutable/assets/2.CHpfuCyA.css.br +0 -0
- package/web/build/client/_app/immutable/assets/2.CHpfuCyA.css.gz +0 -0
- package/web/build/client/_app/immutable/assets/3.BiNtcbA-.css +1 -0
- package/web/build/client/_app/immutable/assets/3.BiNtcbA-.css.br +0 -0
- package/web/build/client/_app/immutable/assets/3.BiNtcbA-.css.gz +0 -0
- package/web/build/client/_app/immutable/assets/ThemeToggle.DcT79vPK.css +1 -0
- package/web/build/client/_app/immutable/assets/ThemeToggle.DcT79vPK.css.br +0 -0
- package/web/build/client/_app/immutable/assets/ThemeToggle.DcT79vPK.css.gz +0 -0
- package/web/build/client/_app/immutable/chunks/BZfQ9INH.js +1 -0
- package/web/build/client/_app/immutable/chunks/BZfQ9INH.js.br +0 -0
- package/web/build/client/_app/immutable/chunks/BZfQ9INH.js.gz +0 -0
- package/web/build/client/_app/immutable/chunks/Bjy-W4x2.js +81 -0
- package/web/build/client/_app/immutable/chunks/Bjy-W4x2.js.br +0 -0
- package/web/build/client/_app/immutable/chunks/Bjy-W4x2.js.gz +0 -0
- package/web/build/client/_app/immutable/chunks/Czsj8nEn.js +114 -0
- package/web/build/client/_app/immutable/chunks/Czsj8nEn.js.br +0 -0
- package/web/build/client/_app/immutable/chunks/Czsj8nEn.js.gz +0 -0
- package/web/build/client/_app/immutable/chunks/DWi-qjc4.js +2 -0
- package/web/build/client/_app/immutable/chunks/DWi-qjc4.js.br +0 -0
- package/web/build/client/_app/immutable/chunks/DWi-qjc4.js.gz +0 -0
- package/web/build/client/_app/immutable/chunks/Dur-vdUf.js +3 -0
- package/web/build/client/_app/immutable/chunks/Dur-vdUf.js.br +0 -0
- package/web/build/client/_app/immutable/chunks/Dur-vdUf.js.gz +0 -0
- package/web/build/client/_app/immutable/chunks/tYeNsyIK.js +1 -0
- package/web/build/client/_app/immutable/chunks/tYeNsyIK.js.br +0 -0
- package/web/build/client/_app/immutable/chunks/tYeNsyIK.js.gz +0 -0
- package/web/build/client/_app/immutable/entry/app.BTcBsRpu.js +2 -0
- package/web/build/client/_app/immutable/entry/app.BTcBsRpu.js.br +0 -0
- package/web/build/client/_app/immutable/entry/app.BTcBsRpu.js.gz +0 -0
- package/web/build/client/_app/immutable/entry/start.kanpokEP.js +1 -0
- package/web/build/client/_app/immutable/entry/start.kanpokEP.js.br +1 -0
- package/web/build/client/_app/immutable/entry/start.kanpokEP.js.gz +0 -0
- package/web/build/client/_app/immutable/nodes/0.s5xmjXoS.js +1 -0
- package/web/build/client/_app/immutable/nodes/0.s5xmjXoS.js.br +1 -0
- package/web/build/client/_app/immutable/nodes/0.s5xmjXoS.js.gz +0 -0
- package/web/build/client/_app/immutable/nodes/1.CgR83Zma.js +1 -0
- package/web/build/client/_app/immutable/nodes/1.CgR83Zma.js.br +0 -0
- package/web/build/client/_app/immutable/nodes/1.CgR83Zma.js.gz +0 -0
- package/web/build/client/_app/immutable/nodes/2.CQ9hcOOu.js +66 -0
- package/web/build/client/_app/immutable/nodes/2.CQ9hcOOu.js.br +0 -0
- package/web/build/client/_app/immutable/nodes/2.CQ9hcOOu.js.gz +0 -0
- package/web/build/client/_app/immutable/nodes/3.DkvQklXV.js +170 -0
- package/web/build/client/_app/immutable/nodes/3.DkvQklXV.js.br +0 -0
- package/web/build/client/_app/immutable/nodes/3.DkvQklXV.js.gz +0 -0
- package/web/build/client/_app/version.json +1 -0
- package/web/build/client/_app/version.json.br +0 -0
- package/web/build/client/_app/version.json.gz +0 -0
- package/web/build/env.js +70 -0
- package/web/build/env.js.map +1 -0
- package/web/build/handler.js +16 -0
- package/web/build/handler.js.map +1 -0
- package/web/build/index.js +139 -0
- package/web/build/index.js.map +1 -0
- package/web/build/server/chunks/0-SseDy__O.js +11 -0
- package/web/build/server/chunks/0-SseDy__O.js.map +1 -0
- package/web/build/server/chunks/1-D4XIiB5g.js +7 -0
- package/web/build/server/chunks/1-D4XIiB5g.js.map +1 -0
- package/web/build/server/chunks/2-VwXkm01F.js +7 -0
- package/web/build/server/chunks/2-VwXkm01F.js.map +1 -0
- package/web/build/server/chunks/3-g6WScD1p.js +7 -0
- package/web/build/server/chunks/3-g6WScD1p.js.map +1 -0
- package/web/build/server/chunks/handler-UGld1DQG.js +1473 -0
- package/web/build/server/chunks/handler-UGld1DQG.js.map +1 -0
- package/web/build/server/chunks/hooks.server-DERFlOe1.js +14 -0
- package/web/build/server/chunks/hooks.server-DERFlOe1.js.map +1 -0
- package/web/build/server/chunks/index.js-A_Q670iW.js +11784 -0
- package/web/build/server/chunks/index.js-A_Q670iW.js.map +1 -0
- package/web/build/server/chunks/manifest.js-CbjlFtox.js +50 -0
- package/web/build/server/chunks/manifest.js-CbjlFtox.js.map +1 -0
- package/web/build/server/chunks/remote-1g4ayag-ipWXvkjB.js +2239 -0
- package/web/build/server/chunks/remote-1g4ayag-ipWXvkjB.js.map +1 -0
- package/web/build/shims.js +33 -0
- package/web/build/shims.js.map +1 -0
|
@@ -0,0 +1,2239 @@
|
|
|
1
|
+
import { e as error, g as get_request_store, b as stringify_remote_arg, M as MUTATIVE_METHODS, w as with_request_store, c as create_remote_key, n as noop, H as HttpError, S as SvelteKitError, h as handle_error_and_jsonify, _ as __exportAll, i as init_remote_functions } from './index.js-A_Q670iW.js';
|
|
2
|
+
import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
3
|
+
import { join, resolve } from 'node:path';
|
|
4
|
+
import { execFileSync, spawnSync } from 'node:child_process';
|
|
5
|
+
import { createHash } from 'node:crypto';
|
|
6
|
+
import { tmpdir, platform } from 'node:os';
|
|
7
|
+
import process from 'node:process';
|
|
8
|
+
|
|
9
|
+
//#region node_modules/@sveltejs/kit/src/runtime/app/server/remote/shared.js
|
|
10
|
+
/** @import { RequestEvent } from '@sveltejs/kit' */
|
|
11
|
+
/** @import { ServerHooks, MaybePromise, RequestState, RemoteInternals, RequestStore, RemoteLiveQueryUserFunctionReturnType } from 'types' */
|
|
12
|
+
/**
|
|
13
|
+
* @param {any} validate_or_fn
|
|
14
|
+
* @param {((arg?: any) => any) | undefined} [maybe_fn]
|
|
15
|
+
* @returns {(arg?: any) => MaybePromise<any>}
|
|
16
|
+
*/
|
|
17
|
+
function create_validator(validate_or_fn, maybe_fn) {
|
|
18
|
+
if (!maybe_fn) return (arg) => {
|
|
19
|
+
if (arg !== void 0) error(400, "Bad Request");
|
|
20
|
+
};
|
|
21
|
+
if (validate_or_fn === "unchecked") return (arg) => arg;
|
|
22
|
+
if ("~standard" in validate_or_fn) return async (arg) => {
|
|
23
|
+
const { event, state } = get_request_store();
|
|
24
|
+
const result = await validate_or_fn["~standard"].validate(arg);
|
|
25
|
+
if (result.issues) error(400, await state.handleValidationError({
|
|
26
|
+
issues: result.issues,
|
|
27
|
+
event
|
|
28
|
+
}));
|
|
29
|
+
return result.value;
|
|
30
|
+
};
|
|
31
|
+
throw new Error("Invalid validator passed to remote function. Expected \"unchecked\" or a Standard Schema (https://standardschema.dev)");
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* In case of a single remote function call, just returns the result.
|
|
35
|
+
*
|
|
36
|
+
* In case of a full page reload, returns the response for a remote function call,
|
|
37
|
+
* either from the cache or by invoking the function.
|
|
38
|
+
* Also saves an uneval'ed version of the result for later HTML inlining for hydration.
|
|
39
|
+
*
|
|
40
|
+
* @template {MaybePromise<any>} T
|
|
41
|
+
* @param {RemoteInternals} internals
|
|
42
|
+
* @param {string} payload — the stringified raw argument (i.e. the cache key the client will use)
|
|
43
|
+
* @param {RequestState} state
|
|
44
|
+
* @param {() => Promise<T>} get_result
|
|
45
|
+
* @returns {Promise<T>}
|
|
46
|
+
*/
|
|
47
|
+
async function get_response(internals, payload, state, get_result) {
|
|
48
|
+
await 0;
|
|
49
|
+
const cache = get_cache(internals, state);
|
|
50
|
+
if (!state.is_in_remote_query) get_implicit_lookup(internals, state)[payload] = get_result;
|
|
51
|
+
return cache[payload] ??= get_result();
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* @param {RequestEvent} event
|
|
55
|
+
* @param {RequestState} state
|
|
56
|
+
* @param {boolean} allow_cookies
|
|
57
|
+
* @returns {RequestStore}
|
|
58
|
+
*/
|
|
59
|
+
function derive_remote_function_event(event, state, allow_cookies) {
|
|
60
|
+
return {
|
|
61
|
+
event: {
|
|
62
|
+
...event,
|
|
63
|
+
setHeaders: () => {
|
|
64
|
+
throw new Error("setHeaders is not allowed in remote functions");
|
|
65
|
+
},
|
|
66
|
+
cookies: {
|
|
67
|
+
...event.cookies,
|
|
68
|
+
set: (name, value, opts) => {
|
|
69
|
+
if (!allow_cookies) throw new Error("Cannot set cookies in `query` or `prerender` functions");
|
|
70
|
+
if (opts.path && !opts.path.startsWith("/")) throw new Error("Cookies set in remote functions must have an absolute path");
|
|
71
|
+
return event.cookies.set(name, value, opts);
|
|
72
|
+
},
|
|
73
|
+
delete: (name, opts) => {
|
|
74
|
+
if (!allow_cookies) throw new Error("Cannot delete cookies in `query` or `prerender` functions");
|
|
75
|
+
if (opts.path && !opts.path.startsWith("/")) throw new Error("Cookies deleted in remote functions must have an absolute path");
|
|
76
|
+
return event.cookies.delete(name, opts);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
state: {
|
|
81
|
+
...state,
|
|
82
|
+
is_in_remote_function: true
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Like `with_event` but removes things from `event` you cannot see/call in remote functions, such as `setHeaders`.
|
|
88
|
+
* @template T
|
|
89
|
+
* @param {RequestEvent} event
|
|
90
|
+
* @param {RequestState} state
|
|
91
|
+
* @param {boolean} allow_cookies
|
|
92
|
+
* @param {() => any} get_input
|
|
93
|
+
* @param {(arg?: any) => T} fn
|
|
94
|
+
*/
|
|
95
|
+
async function run_remote_function(event, state, allow_cookies, get_input, fn) {
|
|
96
|
+
const store = derive_remote_function_event(event, state, allow_cookies);
|
|
97
|
+
const input = await with_request_store(store, get_input);
|
|
98
|
+
return with_request_store(store, () => fn(input));
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Like `with_event` but removes things from `event` you cannot see/call in remote functions, such as `setHeaders`.
|
|
102
|
+
* @template T
|
|
103
|
+
* @param {RequestEvent} event
|
|
104
|
+
* @param {RequestState} state
|
|
105
|
+
* @param {boolean} allow_cookies
|
|
106
|
+
* @param {() => any} get_input
|
|
107
|
+
* @param {(arg?: any) => RemoteLiveQueryUserFunctionReturnType<T>} fn
|
|
108
|
+
* @param {string} name
|
|
109
|
+
*/
|
|
110
|
+
async function* run_remote_generator(event, state, allow_cookies, get_input, fn, name) {
|
|
111
|
+
const store = derive_remote_function_event(event, state, allow_cookies);
|
|
112
|
+
const input = await with_request_store(store, get_input);
|
|
113
|
+
const iterator = to_iterator(await with_request_store(store, () => fn(input)), name);
|
|
114
|
+
let done = false;
|
|
115
|
+
try {
|
|
116
|
+
while (true) {
|
|
117
|
+
const result = await with_request_store(store, () => iterator.next());
|
|
118
|
+
if (result.done) {
|
|
119
|
+
done = true;
|
|
120
|
+
return result.value;
|
|
121
|
+
}
|
|
122
|
+
yield result.value;
|
|
123
|
+
}
|
|
124
|
+
} finally {
|
|
125
|
+
if (!done && typeof iterator.return === "function") await with_request_store(store, () => iterator.return?.(void 0));
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* @template T
|
|
130
|
+
* @param {Awaited<RemoteLiveQueryUserFunctionReturnType<T>>} source
|
|
131
|
+
* @param {string} name
|
|
132
|
+
* @returns {Iterator<T> | AsyncIterator<T>}
|
|
133
|
+
*/
|
|
134
|
+
function to_iterator(source, name) {
|
|
135
|
+
if ("next" in source && typeof source.next === "function") return source;
|
|
136
|
+
if (Symbol.asyncIterator in source && typeof source[Symbol.asyncIterator] === "function") return source[Symbol.asyncIterator]();
|
|
137
|
+
if (Symbol.iterator in source && typeof source[Symbol.iterator] === "function") return source[Symbol.iterator]();
|
|
138
|
+
throw new Error(`query.live '${name}' must return an Iterator, Iterable, AsyncIterator or AsyncIterable`);
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Note that `state` is deliberately not optional: resources that capture the request
|
|
142
|
+
* state at creation must pass it explicitly, because reading it from the request store
|
|
143
|
+
* at call time is only equivalent on runtimes with `AsyncLocalStorage` support.
|
|
144
|
+
* Callers without a captured state (such as the module-level `form` instance getters)
|
|
145
|
+
* should pass `get_request_store().state` themselves.
|
|
146
|
+
* @param {RemoteInternals} internals
|
|
147
|
+
* @param {RequestState} state
|
|
148
|
+
*/
|
|
149
|
+
function get_cache(internals, state) {
|
|
150
|
+
let cache = state.remote.data?.get(internals);
|
|
151
|
+
if (cache === void 0) {
|
|
152
|
+
cache = {};
|
|
153
|
+
(state.remote.data ??= /* @__PURE__ */ new Map()).set(internals, cache);
|
|
154
|
+
}
|
|
155
|
+
return cache;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* @param {RemoteInternals} internals
|
|
159
|
+
* @param {RequestState} state
|
|
160
|
+
*/
|
|
161
|
+
function get_implicit_lookup(internals, state) {
|
|
162
|
+
let cache = state.remote.implicit?.get(internals);
|
|
163
|
+
if (cache === void 0) {
|
|
164
|
+
cache = {};
|
|
165
|
+
(state.remote.implicit ??= /* @__PURE__ */ new Map()).set(internals, cache);
|
|
166
|
+
}
|
|
167
|
+
return cache;
|
|
168
|
+
}
|
|
169
|
+
//#endregion
|
|
170
|
+
//#region node_modules/@sveltejs/kit/src/runtime/app/server/remote/command.js
|
|
171
|
+
/** @import { RemoteCommand } from '@sveltejs/kit' */
|
|
172
|
+
/** @import { MaybePromise, RemoteCommandInternals } from 'types' */
|
|
173
|
+
/** @import { StandardSchemaV1 } from '@standard-schema/spec' */
|
|
174
|
+
/**
|
|
175
|
+
* Creates a remote command. When called from the browser, the function will be invoked on the server via a `fetch` call.
|
|
176
|
+
*
|
|
177
|
+
* See [Remote functions](https://svelte.dev/docs/kit/remote-functions#command) for full documentation.
|
|
178
|
+
*
|
|
179
|
+
* @template Output
|
|
180
|
+
* @overload
|
|
181
|
+
* @param {() => MaybePromise<Output>} fn
|
|
182
|
+
* @returns {RemoteCommand<void, Output>}
|
|
183
|
+
* @since 2.27
|
|
184
|
+
*/
|
|
185
|
+
/**
|
|
186
|
+
* Creates a remote command. When called from the browser, the function will be invoked on the server via a `fetch` call.
|
|
187
|
+
*
|
|
188
|
+
* See [Remote functions](https://svelte.dev/docs/kit/remote-functions#command) for full documentation.
|
|
189
|
+
*
|
|
190
|
+
* @template Input
|
|
191
|
+
* @template Output
|
|
192
|
+
* @overload
|
|
193
|
+
* @param {'unchecked'} validate
|
|
194
|
+
* @param {(arg: Input) => MaybePromise<Output>} fn
|
|
195
|
+
* @returns {RemoteCommand<Input, Output>}
|
|
196
|
+
* @since 2.27
|
|
197
|
+
*/
|
|
198
|
+
/**
|
|
199
|
+
* Creates a remote command. When called from the browser, the function will be invoked on the server via a `fetch` call.
|
|
200
|
+
*
|
|
201
|
+
* See [Remote functions](https://svelte.dev/docs/kit/remote-functions#command) for full documentation.
|
|
202
|
+
*
|
|
203
|
+
* @template {StandardSchemaV1} Schema
|
|
204
|
+
* @template Output
|
|
205
|
+
* @overload
|
|
206
|
+
* @param {Schema} validate
|
|
207
|
+
* @param {(arg: StandardSchemaV1.InferOutput<Schema>) => MaybePromise<Output>} fn
|
|
208
|
+
* @returns {RemoteCommand<StandardSchemaV1.InferInput<Schema>, Output>}
|
|
209
|
+
* @since 2.27
|
|
210
|
+
*/
|
|
211
|
+
/**
|
|
212
|
+
* @template Input
|
|
213
|
+
* @template Output
|
|
214
|
+
* @param {any} validate_or_fn
|
|
215
|
+
* @param {(arg?: Input) => MaybePromise<Output>} [maybe_fn]
|
|
216
|
+
* @returns {RemoteCommand<Input, Output>}
|
|
217
|
+
* @since 2.27
|
|
218
|
+
*/
|
|
219
|
+
/*@__NO_SIDE_EFFECTS__*/
|
|
220
|
+
function command(validate_or_fn, maybe_fn) {
|
|
221
|
+
/** @type {(arg?: Input) => MaybePromise<Output>} */
|
|
222
|
+
const fn = maybe_fn ?? validate_or_fn;
|
|
223
|
+
/** @type {(arg?: any) => MaybePromise<Input>} */
|
|
224
|
+
const validate = create_validator(validate_or_fn, maybe_fn);
|
|
225
|
+
/** @type {RemoteCommandInternals} */
|
|
226
|
+
const __ = {
|
|
227
|
+
type: "command",
|
|
228
|
+
id: "",
|
|
229
|
+
name: ""
|
|
230
|
+
};
|
|
231
|
+
/** @type {RemoteCommand<Input, Output> & { __: RemoteCommandInternals }} */
|
|
232
|
+
const wrapper = (arg) => {
|
|
233
|
+
const { event, state } = get_request_store();
|
|
234
|
+
if (!MUTATIVE_METHODS.includes(event.request.method)) throw new Error(`Cannot call a command (\`${__.name}(${maybe_fn ? "..." : ""})\`) from a ${event.request.method} handler`);
|
|
235
|
+
if (state.is_in_render) throw new Error(`Cannot call a command (\`${__.name}(${maybe_fn ? "..." : ""})\`) during server-side rendering`);
|
|
236
|
+
const promise = Promise.resolve(run_remote_function(event, state, true, () => validate(arg), fn));
|
|
237
|
+
promise.updates = () => {
|
|
238
|
+
throw new Error(`Cannot call '${__.name}(...).updates(...)' on the server`);
|
|
239
|
+
};
|
|
240
|
+
return promise;
|
|
241
|
+
};
|
|
242
|
+
Object.defineProperty(wrapper, "__", { value: __ });
|
|
243
|
+
Object.defineProperty(wrapper, "pending", { get: () => 0 });
|
|
244
|
+
return wrapper;
|
|
245
|
+
}
|
|
246
|
+
//#endregion
|
|
247
|
+
//#region node_modules/@sveltejs/kit/src/utils/shared-iterator.js
|
|
248
|
+
/**
|
|
249
|
+
* A pull-style async iterator that fans out a single stream of values to
|
|
250
|
+
* multiple `for await (...)` consumers. Each subscriber gets its own
|
|
251
|
+
* `AsyncGenerator` whose `.next()` resolves whenever a value is pushed via
|
|
252
|
+
* `push(value)`. Multiple consumers see the same values without each one
|
|
253
|
+
* driving an independent underlying source.
|
|
254
|
+
*
|
|
255
|
+
* Backpressure is **latest-wins**: if values arrive faster than a particular
|
|
256
|
+
* consumer drains its iterator, only the most-recently-pushed value is kept
|
|
257
|
+
* pending for that subscriber. Earlier undrained values are dropped. This is
|
|
258
|
+
* appropriate for live data streams (reactive state replication), not for
|
|
259
|
+
* event logs where every value must be delivered.
|
|
260
|
+
*
|
|
261
|
+
* Lifecycle hooks are exposed via the constructor:
|
|
262
|
+
*
|
|
263
|
+
* - `start()` is called when the subscriber count transitions
|
|
264
|
+
* from 0 to 1 (e.g. to start a pump pulling from a real source).
|
|
265
|
+
* - `stop()` is called when the subscriber count transitions
|
|
266
|
+
* from non-zero back to 0 (e.g. to tear down that pump).
|
|
267
|
+
*
|
|
268
|
+
* Either hook may be omitted.
|
|
269
|
+
*
|
|
270
|
+
* The owner is responsible for calling `push(value)` to broadcast values,
|
|
271
|
+
* `done()` to signal natural completion to all subscribers, and `fail(error)`
|
|
272
|
+
* to broadcast a terminal error. After `done()` or `fail()`, the iterator
|
|
273
|
+
* rejects further `subscribe()` calls with the terminal state appropriately.
|
|
274
|
+
*
|
|
275
|
+
* @template T
|
|
276
|
+
*/
|
|
277
|
+
var SharedIterator = class {
|
|
278
|
+
/**
|
|
279
|
+
* @typedef {object} Subscriber
|
|
280
|
+
* @property {{ value: any } | null} pending
|
|
281
|
+
* @property {{ error: unknown } | null} pending_error
|
|
282
|
+
* @property {boolean} finished
|
|
283
|
+
* @property {((result: IteratorResult<any, void>) => void) | null} waiting_resolve
|
|
284
|
+
* @property {((reason: unknown) => void) | null} waiting_reject
|
|
285
|
+
*/
|
|
286
|
+
/** @type {Set<Subscriber>} */
|
|
287
|
+
#subscribers = /* @__PURE__ */ new Set();
|
|
288
|
+
/** @type {((instance: SharedIterator<T>) => (() => void)) | undefined} */
|
|
289
|
+
#start = void 0;
|
|
290
|
+
/** @type {(() => void) | undefined} */
|
|
291
|
+
#stop = void 0;
|
|
292
|
+
/** Once `done()` or `fail()` has been broadcast, no new values are accepted. */
|
|
293
|
+
#closed = false;
|
|
294
|
+
/** @type {unknown} */
|
|
295
|
+
#terminal_error = void 0;
|
|
296
|
+
/** Whether `done()` or `fail()` has been broadcast. */
|
|
297
|
+
get closed() {
|
|
298
|
+
return this.#closed;
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* @param {(instance: SharedIterator<T>) => (() => void)} [start]
|
|
302
|
+
*/
|
|
303
|
+
constructor(start) {
|
|
304
|
+
this.#start = start;
|
|
305
|
+
}
|
|
306
|
+
/** @param {T} value */
|
|
307
|
+
push(value) {
|
|
308
|
+
if (this.#closed) return;
|
|
309
|
+
for (const subscriber of this.#subscribers) if (subscriber.waiting_resolve) {
|
|
310
|
+
const resolve = subscriber.waiting_resolve;
|
|
311
|
+
subscriber.waiting_resolve = null;
|
|
312
|
+
subscriber.waiting_reject = null;
|
|
313
|
+
resolve({
|
|
314
|
+
value,
|
|
315
|
+
done: false
|
|
316
|
+
});
|
|
317
|
+
} else subscriber.pending = { value };
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Signal natural completion to all current subscribers, and to any future
|
|
321
|
+
* subscriber (which will receive an immediately-done iterator).
|
|
322
|
+
*/
|
|
323
|
+
done() {
|
|
324
|
+
if (this.#closed) return;
|
|
325
|
+
this.#closed = true;
|
|
326
|
+
for (const subscriber of this.#subscribers) {
|
|
327
|
+
subscriber.finished = true;
|
|
328
|
+
if (subscriber.waiting_resolve) {
|
|
329
|
+
const resolve = subscriber.waiting_resolve;
|
|
330
|
+
subscriber.waiting_resolve = null;
|
|
331
|
+
subscriber.waiting_reject = null;
|
|
332
|
+
resolve({
|
|
333
|
+
value: void 0,
|
|
334
|
+
done: true
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
this.#subscribers.clear();
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Broadcast a terminal error. All current subscribers will reject their
|
|
342
|
+
* next `.next()` call with `error`. Future subscribers will also reject
|
|
343
|
+
* their first `.next()`.
|
|
344
|
+
*
|
|
345
|
+
* @param {unknown} error
|
|
346
|
+
*/
|
|
347
|
+
fail(error) {
|
|
348
|
+
if (this.#closed) return;
|
|
349
|
+
this.#closed = true;
|
|
350
|
+
this.#terminal_error = error;
|
|
351
|
+
for (const subscriber of this.#subscribers) {
|
|
352
|
+
subscriber.finished = true;
|
|
353
|
+
if (subscriber.waiting_reject) {
|
|
354
|
+
const reject = subscriber.waiting_reject;
|
|
355
|
+
subscriber.waiting_resolve = null;
|
|
356
|
+
subscriber.waiting_reject = null;
|
|
357
|
+
reject(error);
|
|
358
|
+
} else subscriber.pending_error = { error };
|
|
359
|
+
}
|
|
360
|
+
this.#subscribers.clear();
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Subscribe to the shared stream. Returns an `AsyncGenerator<T>` that
|
|
364
|
+
* yields every value pushed after this call (and, if `initial_value` is
|
|
365
|
+
* provided, that value as the first yield).
|
|
366
|
+
*
|
|
367
|
+
* @param {{ initial_value?: { value: T } }} [options]
|
|
368
|
+
* `initial_value` lets the caller seed the iterator with a synchronously-
|
|
369
|
+
* available current value before any new pushes arrive (e.g. the
|
|
370
|
+
* "last-seen value" of a reactive resource). Pass it wrapped in an
|
|
371
|
+
* object so `undefined` can be distinguished from "no initial value".
|
|
372
|
+
* @returns {AsyncGenerator<T, void, void>}
|
|
373
|
+
*/
|
|
374
|
+
subscribe(options) {
|
|
375
|
+
/** @type {Subscriber} */
|
|
376
|
+
const subscriber = {
|
|
377
|
+
pending: options?.initial_value ? { value: options.initial_value.value } : null,
|
|
378
|
+
pending_error: this.#closed && this.#terminal_error !== void 0 ? { error: this.#terminal_error } : null,
|
|
379
|
+
finished: this.#closed && this.#terminal_error === void 0,
|
|
380
|
+
waiting_resolve: null,
|
|
381
|
+
waiting_reject: null
|
|
382
|
+
};
|
|
383
|
+
if (!subscriber.finished && subscriber.pending_error === null) this.#subscribers.add(subscriber);
|
|
384
|
+
if (!this.#closed) this.#stop ??= this.#start?.(this);
|
|
385
|
+
const unsubscribe = () => {
|
|
386
|
+
subscriber.finished = true;
|
|
387
|
+
if (this.#subscribers.delete(subscriber) && this.#subscribers.size === 0) this.#stop?.();
|
|
388
|
+
};
|
|
389
|
+
/** @type {AsyncGenerator<T, void, void>} */
|
|
390
|
+
const iterator = {
|
|
391
|
+
next() {
|
|
392
|
+
if (subscriber.pending_error) {
|
|
393
|
+
const { error } = subscriber.pending_error;
|
|
394
|
+
subscriber.pending_error = null;
|
|
395
|
+
unsubscribe();
|
|
396
|
+
return Promise.reject(error);
|
|
397
|
+
}
|
|
398
|
+
if (subscriber.pending) {
|
|
399
|
+
const { value } = subscriber.pending;
|
|
400
|
+
subscriber.pending = null;
|
|
401
|
+
return Promise.resolve({
|
|
402
|
+
value,
|
|
403
|
+
done: false
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
if (subscriber.finished) return Promise.resolve({
|
|
407
|
+
value: void 0,
|
|
408
|
+
done: true
|
|
409
|
+
});
|
|
410
|
+
return new Promise((resolve, reject) => {
|
|
411
|
+
subscriber.waiting_resolve = resolve;
|
|
412
|
+
subscriber.waiting_reject = reject;
|
|
413
|
+
});
|
|
414
|
+
},
|
|
415
|
+
return(value) {
|
|
416
|
+
unsubscribe();
|
|
417
|
+
if (subscriber.waiting_resolve) {
|
|
418
|
+
const resolve = subscriber.waiting_resolve;
|
|
419
|
+
subscriber.waiting_resolve = null;
|
|
420
|
+
subscriber.waiting_reject = null;
|
|
421
|
+
resolve({
|
|
422
|
+
value: void 0,
|
|
423
|
+
done: true
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
return Promise.resolve({
|
|
427
|
+
value,
|
|
428
|
+
done: true
|
|
429
|
+
});
|
|
430
|
+
},
|
|
431
|
+
throw(error) {
|
|
432
|
+
unsubscribe();
|
|
433
|
+
if (subscriber.waiting_reject) {
|
|
434
|
+
const reject = subscriber.waiting_reject;
|
|
435
|
+
subscriber.waiting_resolve = null;
|
|
436
|
+
subscriber.waiting_reject = null;
|
|
437
|
+
reject(error);
|
|
438
|
+
}
|
|
439
|
+
return Promise.reject(error);
|
|
440
|
+
},
|
|
441
|
+
[Symbol.asyncIterator]() {
|
|
442
|
+
return iterator;
|
|
443
|
+
}
|
|
444
|
+
};
|
|
445
|
+
return iterator;
|
|
446
|
+
}
|
|
447
|
+
};
|
|
448
|
+
//#endregion
|
|
449
|
+
//#region node_modules/@sveltejs/kit/src/runtime/app/server/remote/query.js
|
|
450
|
+
/** @import { RemoteLiveQuery, RemoteLiveQueryFunction, RemoteQuery, RemoteQueryFunction, RequestEvent } from '@sveltejs/kit' */
|
|
451
|
+
/** @import { RemoteInternals, MaybePromise, RequestState, RemoteQueryLiveInternals, RemoteQueryBatchInternals, RemoteQueryInternals, RemoteLiveQueryUserFunctionReturnType } from 'types' */
|
|
452
|
+
/** @import { StandardSchemaV1 } from '@standard-schema/spec' */
|
|
453
|
+
/**
|
|
454
|
+
* Creates a remote query. When called from the browser, the function will be invoked on the server via a `fetch` call.
|
|
455
|
+
*
|
|
456
|
+
* See [Remote functions](https://svelte.dev/docs/kit/remote-functions#query) for full documentation.
|
|
457
|
+
*
|
|
458
|
+
* @template Output
|
|
459
|
+
* @overload
|
|
460
|
+
* @param {() => MaybePromise<Output>} fn
|
|
461
|
+
* @returns {RemoteQueryFunction<void, Output>}
|
|
462
|
+
* @since 2.27
|
|
463
|
+
*/
|
|
464
|
+
/**
|
|
465
|
+
* Creates a remote query. When called from the browser, the function will be invoked on the server via a `fetch` call.
|
|
466
|
+
*
|
|
467
|
+
* See [Remote functions](https://svelte.dev/docs/kit/remote-functions#query) for full documentation.
|
|
468
|
+
*
|
|
469
|
+
* @template Input
|
|
470
|
+
* @template Output
|
|
471
|
+
* @overload
|
|
472
|
+
* @param {'unchecked'} validate
|
|
473
|
+
* @param {(arg: Input) => MaybePromise<Output>} fn
|
|
474
|
+
* @returns {RemoteQueryFunction<Input, Output>}
|
|
475
|
+
* @since 2.27
|
|
476
|
+
*/
|
|
477
|
+
/**
|
|
478
|
+
* Creates a remote query. When called from the browser, the function will be invoked on the server via a `fetch` call.
|
|
479
|
+
*
|
|
480
|
+
* See [Remote functions](https://svelte.dev/docs/kit/remote-functions#query) for full documentation.
|
|
481
|
+
*
|
|
482
|
+
* @template {StandardSchemaV1} Schema
|
|
483
|
+
* @template Output
|
|
484
|
+
* @overload
|
|
485
|
+
* @param {Schema} schema
|
|
486
|
+
* @param {(arg: StandardSchemaV1.InferOutput<Schema>) => MaybePromise<Output>} fn
|
|
487
|
+
* @returns {RemoteQueryFunction<StandardSchemaV1.InferInput<Schema>, Output, StandardSchemaV1.InferOutput<Schema>>}
|
|
488
|
+
* @since 2.27
|
|
489
|
+
*/
|
|
490
|
+
/**
|
|
491
|
+
* @template Input
|
|
492
|
+
* @template Output
|
|
493
|
+
* @param {any} validate_or_fn
|
|
494
|
+
* @param {(args?: Input) => MaybePromise<Output>} [maybe_fn]
|
|
495
|
+
* @returns {RemoteQueryFunction<Input, Output>}
|
|
496
|
+
* @since 2.27
|
|
497
|
+
*/
|
|
498
|
+
/*@__NO_SIDE_EFFECTS__*/
|
|
499
|
+
function query(validate_or_fn, maybe_fn) {
|
|
500
|
+
/** @type {(arg?: Input) => Output} */
|
|
501
|
+
const fn = maybe_fn ?? validate_or_fn;
|
|
502
|
+
/** @type {(arg?: any) => MaybePromise<Input>} */
|
|
503
|
+
const validate = create_validator(validate_or_fn, maybe_fn);
|
|
504
|
+
/** @type {RemoteQueryInternals} */
|
|
505
|
+
const __ = {
|
|
506
|
+
type: "query",
|
|
507
|
+
id: "",
|
|
508
|
+
name: "",
|
|
509
|
+
validate,
|
|
510
|
+
bind(payload, validated_arg) {
|
|
511
|
+
const { event, state } = get_request_store();
|
|
512
|
+
return create_query_resource(__, payload, event, state, () => run_remote_function(event, {
|
|
513
|
+
...state,
|
|
514
|
+
is_in_remote_query: true
|
|
515
|
+
}, false, () => validated_arg, fn));
|
|
516
|
+
}
|
|
517
|
+
};
|
|
518
|
+
/** @type {RemoteQueryFunction<Input, Output> & { __: RemoteQueryInternals }} */
|
|
519
|
+
const wrapper = (arg) => {
|
|
520
|
+
const { event, state } = get_request_store();
|
|
521
|
+
const payload = stringify_remote_arg(arg, state.transport);
|
|
522
|
+
return create_query_resource(__, payload, event, state, () => run_remote_function(event, {
|
|
523
|
+
...state,
|
|
524
|
+
is_in_remote_query: true
|
|
525
|
+
}, false, () => validate(arg), fn));
|
|
526
|
+
};
|
|
527
|
+
Object.defineProperty(wrapper, "__", { value: __ });
|
|
528
|
+
return wrapper;
|
|
529
|
+
}
|
|
530
|
+
/**
|
|
531
|
+
* Creates a live remote query. When called from the browser, the function will be invoked on the server via a streaming `fetch` call.
|
|
532
|
+
*
|
|
533
|
+
* See [Remote functions](https://svelte.dev/docs/kit/remote-functions#query.live) for full documentation.
|
|
534
|
+
*
|
|
535
|
+
* @template Output
|
|
536
|
+
* @overload
|
|
537
|
+
* @param {(arg: void) => RemoteLiveQueryUserFunctionReturnType<Output>} fn
|
|
538
|
+
* @returns {RemoteLiveQueryFunction<void, Output>}
|
|
539
|
+
*/
|
|
540
|
+
/**
|
|
541
|
+
* @template Input
|
|
542
|
+
* @template Output
|
|
543
|
+
* @overload
|
|
544
|
+
* @param {'unchecked'} validate
|
|
545
|
+
* @param {(arg: Input) => RemoteLiveQueryUserFunctionReturnType<Output>} fn
|
|
546
|
+
* @returns {RemoteLiveQueryFunction<Input, Output>}
|
|
547
|
+
*/
|
|
548
|
+
/**
|
|
549
|
+
* @template {StandardSchemaV1} Schema
|
|
550
|
+
* @template Output
|
|
551
|
+
* @overload
|
|
552
|
+
* @param {Schema} schema
|
|
553
|
+
* @param {(arg: StandardSchemaV1.InferOutput<Schema>) => RemoteLiveQueryUserFunctionReturnType<Output>} fn
|
|
554
|
+
* @returns {RemoteLiveQueryFunction<StandardSchemaV1.InferInput<Schema>, Output, StandardSchemaV1.InferOutput<Schema>>}
|
|
555
|
+
*/
|
|
556
|
+
/**
|
|
557
|
+
* @template Input
|
|
558
|
+
* @template Output
|
|
559
|
+
* @param {any} validate_or_fn
|
|
560
|
+
* @param {(args: Input) => RemoteLiveQueryUserFunctionReturnType<Output>} [maybe_fn]
|
|
561
|
+
* @returns {RemoteLiveQueryFunction<Input, Output>}
|
|
562
|
+
*/
|
|
563
|
+
/*@__NO_SIDE_EFFECTS__*/
|
|
564
|
+
function live(validate_or_fn, maybe_fn) {
|
|
565
|
+
/** @type {(arg: Input) => RemoteLiveQueryUserFunctionReturnType<Output>} */
|
|
566
|
+
const fn = maybe_fn ?? validate_or_fn;
|
|
567
|
+
/** @type {(arg?: any) => MaybePromise<Input>} */
|
|
568
|
+
const validate = create_validator(validate_or_fn, maybe_fn);
|
|
569
|
+
/**
|
|
570
|
+
* @param {any} event
|
|
571
|
+
* @param {any} state
|
|
572
|
+
* @param {any} get_input
|
|
573
|
+
*/
|
|
574
|
+
const run = (event, state, get_input) => run_remote_generator(event, {
|
|
575
|
+
...state,
|
|
576
|
+
is_in_remote_query: true
|
|
577
|
+
}, false, get_input, fn, __.name);
|
|
578
|
+
/** @type {RemoteQueryLiveInternals} */
|
|
579
|
+
const __ = {
|
|
580
|
+
type: "query_live",
|
|
581
|
+
id: "",
|
|
582
|
+
name: "",
|
|
583
|
+
run: (event, state, arg) => run(event, state, () => validate(arg)),
|
|
584
|
+
validate,
|
|
585
|
+
bind(payload, validated_arg) {
|
|
586
|
+
const { event, state } = get_request_store();
|
|
587
|
+
return create_live_query_resource(__, payload, event, state, () => run(event, state, () => validated_arg));
|
|
588
|
+
}
|
|
589
|
+
};
|
|
590
|
+
/** @type {RemoteLiveQueryFunction<Input, Output> & { __: RemoteQueryLiveInternals }} */
|
|
591
|
+
const wrapper = (arg) => {
|
|
592
|
+
const { event, state } = get_request_store();
|
|
593
|
+
const payload = stringify_remote_arg(arg, state.transport);
|
|
594
|
+
return create_live_query_resource(__, payload, event, state, () => run(event, state, () => validate(arg)));
|
|
595
|
+
};
|
|
596
|
+
Object.defineProperty(wrapper, "__", { value: __ });
|
|
597
|
+
return wrapper;
|
|
598
|
+
}
|
|
599
|
+
/**
|
|
600
|
+
* Creates a batch query function that collects multiple calls and executes them in a single request
|
|
601
|
+
*
|
|
602
|
+
* See [Remote functions](https://svelte.dev/docs/kit/remote-functions#query.batch) for full documentation.
|
|
603
|
+
*
|
|
604
|
+
* @template Input
|
|
605
|
+
* @template Output
|
|
606
|
+
* @overload
|
|
607
|
+
* @param {'unchecked'} validate
|
|
608
|
+
* @param {(args: Input[]) => MaybePromise<(arg: Input, idx: number) => Output>} fn
|
|
609
|
+
* @returns {RemoteQueryFunction<Input, Output>}
|
|
610
|
+
* @since 2.35
|
|
611
|
+
*/
|
|
612
|
+
/**
|
|
613
|
+
* Creates a batch query function that collects multiple calls and executes them in a single request
|
|
614
|
+
*
|
|
615
|
+
* See [Remote functions](https://svelte.dev/docs/kit/remote-functions#query.batch) for full documentation.
|
|
616
|
+
*
|
|
617
|
+
* @template {StandardSchemaV1} Schema
|
|
618
|
+
* @template Output
|
|
619
|
+
* @overload
|
|
620
|
+
* @param {Schema} schema
|
|
621
|
+
* @param {(args: StandardSchemaV1.InferOutput<Schema>[]) => MaybePromise<(arg: StandardSchemaV1.InferOutput<Schema>, idx: number) => Output>} fn
|
|
622
|
+
* @returns {RemoteQueryFunction<StandardSchemaV1.InferInput<Schema>, Output, StandardSchemaV1.InferOutput<Schema>>}
|
|
623
|
+
* @since 2.35
|
|
624
|
+
*/
|
|
625
|
+
/**
|
|
626
|
+
* @template Input
|
|
627
|
+
* @template Output
|
|
628
|
+
* @param {any} validate_or_fn
|
|
629
|
+
* @param {(args?: Input[]) => MaybePromise<(arg: Input, idx: number) => Output>} [maybe_fn]
|
|
630
|
+
* @returns {RemoteQueryFunction<Input, Output>}
|
|
631
|
+
* @since 2.35
|
|
632
|
+
*/
|
|
633
|
+
/*@__NO_SIDE_EFFECTS__*/
|
|
634
|
+
function batch(validate_or_fn, maybe_fn) {
|
|
635
|
+
/** @type {(args?: Input[]) => MaybePromise<(arg: Input, idx: number) => Output>} */
|
|
636
|
+
const fn = maybe_fn ?? validate_or_fn;
|
|
637
|
+
/** @type {(arg?: any) => MaybePromise<Input>} */
|
|
638
|
+
const validate = create_validator(validate_or_fn, maybe_fn);
|
|
639
|
+
/**
|
|
640
|
+
* Enqueues a single call into the current batch (creating one if necessary)
|
|
641
|
+
* and returns a promise that resolves with the result for this entry.
|
|
642
|
+
*
|
|
643
|
+
* @param {string} payload — the stringified raw argument (cache key)
|
|
644
|
+
* @param {() => MaybePromise<any>} get_validated — produces the validated argument for this entry
|
|
645
|
+
* @returns {Promise<any>}
|
|
646
|
+
*/
|
|
647
|
+
const enqueue = (payload, get_validated) => {
|
|
648
|
+
const { event, state } = get_request_store();
|
|
649
|
+
return new Promise((resolve, reject) => {
|
|
650
|
+
const batches = state.remote.batches ??= /* @__PURE__ */ new Map();
|
|
651
|
+
let batched = batches.get(__.id);
|
|
652
|
+
if (!batched) {
|
|
653
|
+
batched = /* @__PURE__ */ new Map();
|
|
654
|
+
batches.set(__.id, batched);
|
|
655
|
+
}
|
|
656
|
+
const entry = batched.get(payload);
|
|
657
|
+
if (entry) {
|
|
658
|
+
entry.resolvers.push({
|
|
659
|
+
resolve,
|
|
660
|
+
reject
|
|
661
|
+
});
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
664
|
+
batched.set(payload, {
|
|
665
|
+
get_validated,
|
|
666
|
+
resolvers: [{
|
|
667
|
+
resolve,
|
|
668
|
+
reject
|
|
669
|
+
}]
|
|
670
|
+
});
|
|
671
|
+
if (batched.size > 1) return;
|
|
672
|
+
setTimeout(async () => {
|
|
673
|
+
batches.delete(__.id);
|
|
674
|
+
const entries = Array.from(batched.values());
|
|
675
|
+
try {
|
|
676
|
+
return await run_remote_function(event, {
|
|
677
|
+
...state,
|
|
678
|
+
is_in_remote_query: true
|
|
679
|
+
}, false, async () => Promise.all(entries.map((entry) => entry.get_validated())), async (input) => {
|
|
680
|
+
const get_result = await fn(input);
|
|
681
|
+
for (let i = 0; i < entries.length; i++) try {
|
|
682
|
+
const result = get_result(input[i], i);
|
|
683
|
+
for (const resolver of entries[i].resolvers) resolver.resolve(result);
|
|
684
|
+
} catch (error) {
|
|
685
|
+
for (const resolver of entries[i].resolvers) resolver.reject(error);
|
|
686
|
+
}
|
|
687
|
+
});
|
|
688
|
+
} catch (error) {
|
|
689
|
+
for (const entry of batched.values()) for (const resolver of entry.resolvers) resolver.reject(error);
|
|
690
|
+
}
|
|
691
|
+
}, 0);
|
|
692
|
+
});
|
|
693
|
+
};
|
|
694
|
+
/** @type {RemoteQueryBatchInternals} */
|
|
695
|
+
const __ = {
|
|
696
|
+
type: "query_batch",
|
|
697
|
+
id: "",
|
|
698
|
+
name: "",
|
|
699
|
+
validate,
|
|
700
|
+
run: async (args, options) => {
|
|
701
|
+
const { event, state } = get_request_store();
|
|
702
|
+
return run_remote_function(event, {
|
|
703
|
+
...state,
|
|
704
|
+
is_in_remote_query: true
|
|
705
|
+
}, false, async () => Promise.all(args.map(validate)), async (input) => {
|
|
706
|
+
const get_result = await fn(input);
|
|
707
|
+
return Promise.all(input.map(async (arg, i) => {
|
|
708
|
+
try {
|
|
709
|
+
return {
|
|
710
|
+
type: "result",
|
|
711
|
+
data: get_result(arg, i)
|
|
712
|
+
};
|
|
713
|
+
} catch (error) {
|
|
714
|
+
return {
|
|
715
|
+
type: "error",
|
|
716
|
+
error: await handle_error_and_jsonify(event, state, options, error),
|
|
717
|
+
status: error instanceof HttpError || error instanceof SvelteKitError ? error.status : 500
|
|
718
|
+
};
|
|
719
|
+
}
|
|
720
|
+
}));
|
|
721
|
+
});
|
|
722
|
+
},
|
|
723
|
+
bind(payload, validated_arg) {
|
|
724
|
+
const { event, state } = get_request_store();
|
|
725
|
+
return create_query_resource(__, payload, event, state, () => enqueue(payload, () => validated_arg));
|
|
726
|
+
}
|
|
727
|
+
};
|
|
728
|
+
/** @type {RemoteQueryFunction<Input, Output> & { __: RemoteQueryBatchInternals }} */
|
|
729
|
+
const wrapper = (arg) => {
|
|
730
|
+
const { event, state } = get_request_store();
|
|
731
|
+
const payload = stringify_remote_arg(arg, state.transport);
|
|
732
|
+
return create_query_resource(__, payload, event, state, () => enqueue(payload, () => validate(arg)));
|
|
733
|
+
};
|
|
734
|
+
Object.defineProperty(wrapper, "__", { value: __ });
|
|
735
|
+
return wrapper;
|
|
736
|
+
}
|
|
737
|
+
/**
|
|
738
|
+
* Include this value in the returned payload...
|
|
739
|
+
* @param {RequestEvent} event
|
|
740
|
+
* @param {RequestState} state
|
|
741
|
+
* @param {RemoteInternals} internals
|
|
742
|
+
* @param {string} payload
|
|
743
|
+
* @param {() => Promise<any>} fn
|
|
744
|
+
*/
|
|
745
|
+
function refresh(event, state, internals, payload, fn) {
|
|
746
|
+
if (!internals.id) return;
|
|
747
|
+
if (!event.isRemoteRequest) return;
|
|
748
|
+
const key = create_remote_key(internals.id, payload);
|
|
749
|
+
const promise = fn();
|
|
750
|
+
promise.catch(() => {});
|
|
751
|
+
(state.remote.explicit ??= /* @__PURE__ */ new Map()).set(key, {
|
|
752
|
+
internals,
|
|
753
|
+
promise
|
|
754
|
+
});
|
|
755
|
+
}
|
|
756
|
+
/**
|
|
757
|
+
* @param {RemoteInternals} __
|
|
758
|
+
* @param {string} payload — the stringified raw argument (i.e. the cache key the client will use)
|
|
759
|
+
* @param {RequestEvent} event
|
|
760
|
+
* @param {RequestState} state
|
|
761
|
+
* @param {() => Promise<any>} fn
|
|
762
|
+
* @returns {RemoteQuery<any>}
|
|
763
|
+
*/
|
|
764
|
+
function create_query_resource(__, payload, event, state, fn) {
|
|
765
|
+
/** @type {Promise<any> | null} */
|
|
766
|
+
let promise = null;
|
|
767
|
+
const get_promise = () => {
|
|
768
|
+
return promise ??= get_response(__, payload, state, fn);
|
|
769
|
+
};
|
|
770
|
+
const populate_hydratable = () => {
|
|
771
|
+
if (__.id && state.is_in_render) get_promise().catch(noop);
|
|
772
|
+
};
|
|
773
|
+
return {
|
|
774
|
+
/** @type {Promise<any>['catch']} */
|
|
775
|
+
catch(onrejected) {
|
|
776
|
+
return get_promise().catch(onrejected);
|
|
777
|
+
},
|
|
778
|
+
get current() {
|
|
779
|
+
populate_hydratable();
|
|
780
|
+
},
|
|
781
|
+
get error() {
|
|
782
|
+
populate_hydratable();
|
|
783
|
+
},
|
|
784
|
+
/** @type {Promise<any>['finally']} */
|
|
785
|
+
finally(onfinally) {
|
|
786
|
+
return get_promise().finally(onfinally);
|
|
787
|
+
},
|
|
788
|
+
get loading() {
|
|
789
|
+
populate_hydratable();
|
|
790
|
+
return true;
|
|
791
|
+
},
|
|
792
|
+
get ready() {
|
|
793
|
+
populate_hydratable();
|
|
794
|
+
return false;
|
|
795
|
+
},
|
|
796
|
+
refresh() {
|
|
797
|
+
promise = null;
|
|
798
|
+
delete get_cache(__, state)[payload];
|
|
799
|
+
refresh(event, state, __, payload, get_promise);
|
|
800
|
+
return Promise.resolve();
|
|
801
|
+
},
|
|
802
|
+
/** @param {any} value */
|
|
803
|
+
set(value) {
|
|
804
|
+
const p = promise = Promise.resolve(value);
|
|
805
|
+
get_cache(__, state)[payload] = p;
|
|
806
|
+
refresh(event, state, __, payload, () => p);
|
|
807
|
+
},
|
|
808
|
+
run() {
|
|
809
|
+
throw new Error(`\`myQuery().run()\` has been removed — please replace it with \`myQuery()\`. See https://github.com/sveltejs/kit/pull/15779 for more details`);
|
|
810
|
+
},
|
|
811
|
+
/** @type {Promise<any>['then']} */
|
|
812
|
+
then(onfulfilled, onrejected) {
|
|
813
|
+
return get_promise().then(onfulfilled, onrejected);
|
|
814
|
+
},
|
|
815
|
+
withOverride() {
|
|
816
|
+
throw new Error(`Cannot call '${__.name}.withOverride()' on the server`);
|
|
817
|
+
},
|
|
818
|
+
get [Symbol.toStringTag]() {
|
|
819
|
+
return "QueryResource";
|
|
820
|
+
}
|
|
821
|
+
};
|
|
822
|
+
}
|
|
823
|
+
/**
|
|
824
|
+
* @param {RemoteQueryLiveInternals} __
|
|
825
|
+
* @param {string} payload — the stringified raw argument (i.e. the cache key the client will use)
|
|
826
|
+
* @param {RequestEvent} event
|
|
827
|
+
* @param {RequestState} state
|
|
828
|
+
* @param {() => AsyncGenerator<any, void, void>} get_generator
|
|
829
|
+
* @returns {RemoteLiveQuery<any>}
|
|
830
|
+
*/
|
|
831
|
+
function create_live_query_resource(__, payload, event, state, get_generator) {
|
|
832
|
+
/** @type {Promise<any> | null} */
|
|
833
|
+
let promise = null;
|
|
834
|
+
const get_first_value = async () => {
|
|
835
|
+
for await (const value of get_generator()) return value;
|
|
836
|
+
throw new Error(`query.live '${__.name}' did not yield a value`);
|
|
837
|
+
};
|
|
838
|
+
const get_promise = () => {
|
|
839
|
+
return promise ??= get_response(__, payload, state, get_first_value);
|
|
840
|
+
};
|
|
841
|
+
const populate_hydratable = () => {
|
|
842
|
+
if (__.id && state.is_in_render) get_promise().catch(noop);
|
|
843
|
+
};
|
|
844
|
+
return {
|
|
845
|
+
/** @type {Promise<any>['catch']} */
|
|
846
|
+
catch(onrejected) {
|
|
847
|
+
return get_promise().catch(onrejected);
|
|
848
|
+
},
|
|
849
|
+
get current() {
|
|
850
|
+
populate_hydratable();
|
|
851
|
+
},
|
|
852
|
+
get error() {
|
|
853
|
+
populate_hydratable();
|
|
854
|
+
},
|
|
855
|
+
/** @type {Promise<any>['finally']} */
|
|
856
|
+
finally(onfinally) {
|
|
857
|
+
return get_promise().finally(onfinally);
|
|
858
|
+
},
|
|
859
|
+
get done() {
|
|
860
|
+
populate_hydratable();
|
|
861
|
+
return false;
|
|
862
|
+
},
|
|
863
|
+
get loading() {
|
|
864
|
+
populate_hydratable();
|
|
865
|
+
return true;
|
|
866
|
+
},
|
|
867
|
+
get ready() {
|
|
868
|
+
populate_hydratable();
|
|
869
|
+
return false;
|
|
870
|
+
},
|
|
871
|
+
get connected() {
|
|
872
|
+
populate_hydratable();
|
|
873
|
+
return false;
|
|
874
|
+
},
|
|
875
|
+
reconnect() {
|
|
876
|
+
promise = null;
|
|
877
|
+
delete get_cache(__, state)[payload];
|
|
878
|
+
refresh(event, state, __, payload, get_promise);
|
|
879
|
+
return Promise.resolve();
|
|
880
|
+
},
|
|
881
|
+
/** @ts-expect-error This method no longer exists */
|
|
882
|
+
run() {
|
|
883
|
+
throw new Error("`.run()` has been removed from live queries. Use `for await (const value of liveQuery())` instead.");
|
|
884
|
+
},
|
|
885
|
+
/** @type {Promise<any>['then']} */
|
|
886
|
+
then(onfulfilled, onrejected) {
|
|
887
|
+
return get_promise().then(onfulfilled, onrejected);
|
|
888
|
+
},
|
|
889
|
+
[Symbol.asyncIterator]() {
|
|
890
|
+
const key = create_remote_key(__.id, payload);
|
|
891
|
+
const cache = state.remote.live_iterators ??= /* @__PURE__ */ new Map();
|
|
892
|
+
let cached = cache.get(key);
|
|
893
|
+
if (!cached) {
|
|
894
|
+
cached = create_shared_live_iterator(event.request.signal, get_generator);
|
|
895
|
+
cache.set(key, cached);
|
|
896
|
+
}
|
|
897
|
+
return cached.subscribe();
|
|
898
|
+
},
|
|
899
|
+
get [Symbol.toStringTag]() {
|
|
900
|
+
return "LiveQueryResource";
|
|
901
|
+
}
|
|
902
|
+
};
|
|
903
|
+
}
|
|
904
|
+
/**
|
|
905
|
+
* Wraps a lazily-created live-query generator so that multiple `for await`
|
|
906
|
+
* consumers within the same request share one underlying iteration. The first
|
|
907
|
+
* subscriber starts the generator; values are broadcast to all subscribers
|
|
908
|
+
* via a `SharedIterator`. When the last subscriber unsubscribes, the generator
|
|
909
|
+
* is closed via `generator.return(undefined)`.
|
|
910
|
+
*
|
|
911
|
+
* If `signal` aborts (typically because the client has disconnected), the
|
|
912
|
+
* pump is torn down and any in-flight `next()` calls on consumer iterators
|
|
913
|
+
* resolve with `{ done: true }`, so suspended `for await` loops unwind
|
|
914
|
+
* cleanly rather than leaking.
|
|
915
|
+
*
|
|
916
|
+
* @param {AbortSignal} signal
|
|
917
|
+
* @param {() => AsyncGenerator<any, void, void>} get_generator
|
|
918
|
+
*/
|
|
919
|
+
function create_shared_live_iterator(signal, get_generator) {
|
|
920
|
+
return new SharedIterator((instance) => {
|
|
921
|
+
if (signal.aborted) {
|
|
922
|
+
instance.done();
|
|
923
|
+
return noop;
|
|
924
|
+
}
|
|
925
|
+
const generator = get_generator();
|
|
926
|
+
let aborted = false;
|
|
927
|
+
const close = () => {
|
|
928
|
+
aborted = true;
|
|
929
|
+
generator.return().catch(noop);
|
|
930
|
+
};
|
|
931
|
+
signal.addEventListener("abort", () => (close(), instance.done()), { once: true });
|
|
932
|
+
(async () => {
|
|
933
|
+
try {
|
|
934
|
+
while (true) {
|
|
935
|
+
const result = await generator.next();
|
|
936
|
+
if (result.done) {
|
|
937
|
+
instance.done();
|
|
938
|
+
return;
|
|
939
|
+
}
|
|
940
|
+
instance.push(result.value);
|
|
941
|
+
}
|
|
942
|
+
} catch (error) {
|
|
943
|
+
if (!aborted) instance.fail(error);
|
|
944
|
+
} finally {
|
|
945
|
+
close();
|
|
946
|
+
}
|
|
947
|
+
})();
|
|
948
|
+
return close;
|
|
949
|
+
});
|
|
950
|
+
}
|
|
951
|
+
Object.defineProperty(query, "batch", {
|
|
952
|
+
value: batch,
|
|
953
|
+
enumerable: true
|
|
954
|
+
});
|
|
955
|
+
Object.defineProperty(query, "live", {
|
|
956
|
+
value: live,
|
|
957
|
+
enumerable: true
|
|
958
|
+
});
|
|
959
|
+
|
|
960
|
+
//#region ../lib/config.mjs
|
|
961
|
+
var DEFAULT_IGNORE = [
|
|
962
|
+
"**/package-lock.json",
|
|
963
|
+
"**/pnpm-lock.yaml",
|
|
964
|
+
"**/yarn.lock",
|
|
965
|
+
"**/bun.lock",
|
|
966
|
+
"**/bun.lockb",
|
|
967
|
+
"**/*.g.cs",
|
|
968
|
+
"**/*Snapshot.cs",
|
|
969
|
+
"**/*.Designer.cs",
|
|
970
|
+
"**/node_modules/**",
|
|
971
|
+
"**/.svelte-kit/**",
|
|
972
|
+
"**/dist/**",
|
|
973
|
+
"**/build/**",
|
|
974
|
+
"**/*.min.js",
|
|
975
|
+
"**/*.min.css",
|
|
976
|
+
"**/CODE_REVIEW_CORRECTNESS_FINDINGS.md"
|
|
977
|
+
];
|
|
978
|
+
var STORE_DIFF_MAX = 4e5;
|
|
979
|
+
var GUIDELINE_FILES = [
|
|
980
|
+
"REVIEW.md",
|
|
981
|
+
"CLAUDE.md",
|
|
982
|
+
"AGENTS.md"
|
|
983
|
+
];
|
|
984
|
+
function unquote(s) {
|
|
985
|
+
const t = String(s || "").trim();
|
|
986
|
+
if (t.startsWith("\"") && t.endsWith("\"") || t.startsWith("'") && t.endsWith("'")) return t.slice(1, -1);
|
|
987
|
+
return t;
|
|
988
|
+
}
|
|
989
|
+
/** Minimal YAML: `key: value` and `key:\n - item`. */
|
|
990
|
+
function parseSimpleYaml(text) {
|
|
991
|
+
const result = {};
|
|
992
|
+
let key = null;
|
|
993
|
+
for (const raw of String(text || "").split(/\r?\n/)) {
|
|
994
|
+
if (!raw.trim() || raw.trim().startsWith("#")) continue;
|
|
995
|
+
const list = raw.match(/^\s+-\s+(.*)$/);
|
|
996
|
+
if (list && key) {
|
|
997
|
+
if (!Array.isArray(result[key])) result[key] = [];
|
|
998
|
+
result[key].push(unquote(list[1]));
|
|
999
|
+
continue;
|
|
1000
|
+
}
|
|
1001
|
+
const kv = raw.match(/^([A-Za-z][\w]*)\s*:\s*(.*)$/);
|
|
1002
|
+
if (!kv) continue;
|
|
1003
|
+
key = kv[1];
|
|
1004
|
+
const v = kv[2].trim();
|
|
1005
|
+
result[key] = v === "" ? [] : unquote(v);
|
|
1006
|
+
}
|
|
1007
|
+
return result;
|
|
1008
|
+
}
|
|
1009
|
+
function globToRegExp(glob) {
|
|
1010
|
+
let g = String(glob || "").replace(/\\/g, "/");
|
|
1011
|
+
if (!g.includes("/")) g = `**/${g}`;
|
|
1012
|
+
let s = "";
|
|
1013
|
+
for (let i = 0; i < g.length; i++) {
|
|
1014
|
+
const c = g[i];
|
|
1015
|
+
if (c === "*" && g[i + 1] === "*") if (g[i + 2] === "/") {
|
|
1016
|
+
s += "(?:.*/)?";
|
|
1017
|
+
i += 2;
|
|
1018
|
+
} else {
|
|
1019
|
+
s += ".*";
|
|
1020
|
+
i += 1;
|
|
1021
|
+
}
|
|
1022
|
+
else if (c === "*") s += "[^/]*";
|
|
1023
|
+
else if (c === "?") s += "[^/]";
|
|
1024
|
+
else if ("+()[]{}|.^$".includes(c)) s += `\\${c}`;
|
|
1025
|
+
else s += c;
|
|
1026
|
+
}
|
|
1027
|
+
return new RegExp(`^${s}$`, "i");
|
|
1028
|
+
}
|
|
1029
|
+
function globMatch(filePath, pattern) {
|
|
1030
|
+
const norm = String(filePath || "").replace(/\\/g, "/");
|
|
1031
|
+
return globToRegExp(pattern).test(norm);
|
|
1032
|
+
}
|
|
1033
|
+
function isIgnored(filePath, patterns) {
|
|
1034
|
+
return (patterns || []).some((p) => globMatch(filePath, p));
|
|
1035
|
+
}
|
|
1036
|
+
function isTestPath(filePath) {
|
|
1037
|
+
const p = String(filePath || "").replace(/\\/g, "/");
|
|
1038
|
+
return /(^|\/)(__tests__|tests?|spec)s?(\/|$)/i.test(p) || /\.(test|spec)\.[^.]+$/i.test(p);
|
|
1039
|
+
}
|
|
1040
|
+
function readIfExists(path) {
|
|
1041
|
+
try {
|
|
1042
|
+
return existsSync(path) ? readFileSync(path, "utf8") : null;
|
|
1043
|
+
} catch {
|
|
1044
|
+
return null;
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
function loadReviewConfig(repo) {
|
|
1048
|
+
const jsonText = readIfExists(join(repo, ".diff-review.json"));
|
|
1049
|
+
const yamlText = readIfExists(join(repo, ".diff-review.yml")) || readIfExists(join(repo, ".diff-review.yaml"));
|
|
1050
|
+
let extra = {};
|
|
1051
|
+
if (jsonText) try {
|
|
1052
|
+
extra = JSON.parse(jsonText);
|
|
1053
|
+
} catch {
|
|
1054
|
+
extra = {};
|
|
1055
|
+
}
|
|
1056
|
+
else if (yamlText) extra = parseSimpleYaml(yamlText);
|
|
1057
|
+
const extraIgnore = Array.isArray(extra.ignore) ? extra.ignore.map(String) : [];
|
|
1058
|
+
const rulesFile = extra.rulesFile ? String(extra.rulesFile) : "REVIEW.md";
|
|
1059
|
+
const rules = readIfExists(join(repo, rulesFile)) || "";
|
|
1060
|
+
const model = extra.model && extra.model !== "default" ? String(extra.model) : null;
|
|
1061
|
+
return {
|
|
1062
|
+
ignore: [...DEFAULT_IGNORE, ...extraIgnore],
|
|
1063
|
+
model,
|
|
1064
|
+
rulesFile,
|
|
1065
|
+
rules: rules.trim()
|
|
1066
|
+
};
|
|
1067
|
+
}
|
|
1068
|
+
/** CLAUDE.md / REVIEW.md / AGENTS.md from repo root and immediate parent dirs of touched files. */
|
|
1069
|
+
function collectGuidelines(repo, filePaths, { rulesFile = "REVIEW.md", maxChars = 8e3, maxTotalChars = 12e3, maxDepth = 1, skipNames = [] } = {}) {
|
|
1070
|
+
const skip = new Set(skipNames);
|
|
1071
|
+
const names = [.../* @__PURE__ */ new Set([rulesFile, ...GUIDELINE_FILES])].filter((name) => !skip.has(name));
|
|
1072
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1073
|
+
const out = [];
|
|
1074
|
+
const addRel = (rel) => {
|
|
1075
|
+
const norm = String(rel || "").replace(/\\/g, "/").replace(/^\/+/, "");
|
|
1076
|
+
if (!norm || seen.has(norm)) return;
|
|
1077
|
+
const text = readIfExists(join(repo, ...norm.split("/")));
|
|
1078
|
+
if (!text || !text.trim()) return;
|
|
1079
|
+
seen.add(norm);
|
|
1080
|
+
out.push({
|
|
1081
|
+
path: norm,
|
|
1082
|
+
text: text.trim()
|
|
1083
|
+
});
|
|
1084
|
+
};
|
|
1085
|
+
for (const name of names) addRel(name);
|
|
1086
|
+
const depth = Math.max(1, Number(maxDepth) || 1);
|
|
1087
|
+
for (const filePath of filePaths || []) {
|
|
1088
|
+
const parts = String(filePath || "").replace(/\\/g, "/").split("/").filter(Boolean);
|
|
1089
|
+
const minLen = Math.max(1, parts.length - depth);
|
|
1090
|
+
for (let len = parts.length - 1; len >= minLen; len--) {
|
|
1091
|
+
const dir = parts.slice(0, len).join("/");
|
|
1092
|
+
for (const name of names) addRel(`${dir}/${name}`);
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
let total = 0;
|
|
1096
|
+
const capped = [];
|
|
1097
|
+
for (const item of out) {
|
|
1098
|
+
if (total >= maxTotalChars) break;
|
|
1099
|
+
const room = maxTotalChars - total;
|
|
1100
|
+
const slice = item.text.slice(0, Math.min(maxChars, room));
|
|
1101
|
+
if (!slice) continue;
|
|
1102
|
+
capped.push({
|
|
1103
|
+
path: item.path,
|
|
1104
|
+
text: slice
|
|
1105
|
+
});
|
|
1106
|
+
total += slice.length;
|
|
1107
|
+
}
|
|
1108
|
+
return capped;
|
|
1109
|
+
}
|
|
1110
|
+
function capDiff(diff, max = STORE_DIFF_MAX) {
|
|
1111
|
+
const text = String(diff || "");
|
|
1112
|
+
if (text.length <= max) return {
|
|
1113
|
+
diff: text,
|
|
1114
|
+
diffChars: text.length,
|
|
1115
|
+
truncated: false
|
|
1116
|
+
};
|
|
1117
|
+
return {
|
|
1118
|
+
diff: `${text.slice(0, max)}\n\n… [diff recortado: se guardan ${max} de ${text.length} caracteres]`,
|
|
1119
|
+
diffChars: text.length,
|
|
1120
|
+
truncated: true
|
|
1121
|
+
};
|
|
1122
|
+
}
|
|
1123
|
+
//#endregion
|
|
1124
|
+
//#region ../lib/git.mjs
|
|
1125
|
+
function assertGitRepo(repo) {
|
|
1126
|
+
const raw = String(repo || "").trim();
|
|
1127
|
+
if (!raw) throw new Error("Indicá la ruta absoluta de un repositorio git.");
|
|
1128
|
+
if (!/[\\/]/.test(raw) && !/^[A-Za-z]:/.test(raw)) throw new Error(`"${raw}" no es una ruta. Pegá la ruta absoluta del repo (ej. C:\\tienda-argenta\\tienda-argenta-2).`);
|
|
1129
|
+
const resolved = resolve(raw);
|
|
1130
|
+
if (!resolved || resolved === ".") throw new Error("Indicá la ruta absoluta de un repositorio git.");
|
|
1131
|
+
if (!existsSync(resolved)) throw new Error(`No existe la carpeta: ${resolved}`);
|
|
1132
|
+
if (!existsSync(join(resolved, ".git"))) throw new Error(`No es un repositorio git (falta .git): ${resolved}`);
|
|
1133
|
+
return resolved;
|
|
1134
|
+
}
|
|
1135
|
+
function git(repo, args, { maxBuffer = 64 * 1024 * 1024 } = {}) {
|
|
1136
|
+
return execFileSync("git", args, {
|
|
1137
|
+
cwd: repo,
|
|
1138
|
+
encoding: "utf8",
|
|
1139
|
+
maxBuffer,
|
|
1140
|
+
windowsHide: true
|
|
1141
|
+
});
|
|
1142
|
+
}
|
|
1143
|
+
function commitLog(repo, mergeBase, branch, limit = 25) {
|
|
1144
|
+
try {
|
|
1145
|
+
return git(repo, [
|
|
1146
|
+
"log",
|
|
1147
|
+
"--oneline",
|
|
1148
|
+
"--no-merges",
|
|
1149
|
+
"-n",
|
|
1150
|
+
String(limit),
|
|
1151
|
+
`${mergeBase}..${branch}`
|
|
1152
|
+
]).trim().split("\n").filter(Boolean);
|
|
1153
|
+
} catch {
|
|
1154
|
+
return [];
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
var FILE_LINE_CACHE = /* @__PURE__ */ new Map();
|
|
1158
|
+
var FILE_LINE_CACHE_MAX = 48;
|
|
1159
|
+
function rememberFileLines(key, lines) {
|
|
1160
|
+
if (FILE_LINE_CACHE.has(key)) FILE_LINE_CACHE.delete(key);
|
|
1161
|
+
FILE_LINE_CACHE.set(key, lines);
|
|
1162
|
+
while (FILE_LINE_CACHE.size > FILE_LINE_CACHE_MAX) {
|
|
1163
|
+
const oldest = FILE_LINE_CACHE.keys().next().value;
|
|
1164
|
+
FILE_LINE_CACHE.delete(oldest);
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
function showFileLines(repo, rev, path) {
|
|
1168
|
+
const resolved = assertGitRepo(repo);
|
|
1169
|
+
const safeRev = String(rev || "").trim();
|
|
1170
|
+
const safePath = String(path || "").replace(/\\/g, "/").trim();
|
|
1171
|
+
if (!safeRev || !/^[A-Za-z0-9._/\-]+$/.test(safeRev)) throw new Error("Rev inválido.");
|
|
1172
|
+
if (!safePath || safePath.startsWith("/") || safePath.includes("..") || safePath.includes("\0")) throw new Error("Ruta inválida.");
|
|
1173
|
+
const key = `${resolved}\0${safeRev}\0${safePath}`;
|
|
1174
|
+
if (FILE_LINE_CACHE.has(key)) return FILE_LINE_CACHE.get(key);
|
|
1175
|
+
let text;
|
|
1176
|
+
try {
|
|
1177
|
+
text = git(resolved, ["show", `${safeRev}:${safePath}`]);
|
|
1178
|
+
} catch {
|
|
1179
|
+
rememberFileLines(key, null);
|
|
1180
|
+
return null;
|
|
1181
|
+
}
|
|
1182
|
+
if (text.includes("\0")) {
|
|
1183
|
+
rememberFileLines(key, null);
|
|
1184
|
+
return null;
|
|
1185
|
+
}
|
|
1186
|
+
const raw = text.replace(/\r\n/g, "\n");
|
|
1187
|
+
const lines = raw.endsWith("\n") ? raw.slice(0, -1).split("\n") : raw.split("\n");
|
|
1188
|
+
rememberFileLines(key, lines);
|
|
1189
|
+
return lines;
|
|
1190
|
+
}
|
|
1191
|
+
function listBranches(repo) {
|
|
1192
|
+
const names = git(repo, [
|
|
1193
|
+
"for-each-ref",
|
|
1194
|
+
"--sort=-committerdate",
|
|
1195
|
+
"--format=%(refname:short)",
|
|
1196
|
+
"refs/heads",
|
|
1197
|
+
"refs/remotes"
|
|
1198
|
+
]).split("\n").map((s) => s.trim()).filter(Boolean).map((name) => name.replace(/^origin\//, "")).filter((name) => name !== "HEAD" && !name.endsWith("/HEAD"));
|
|
1199
|
+
const unique = [...new Set(names)];
|
|
1200
|
+
const current = git(repo, [
|
|
1201
|
+
"rev-parse",
|
|
1202
|
+
"--abbrev-ref",
|
|
1203
|
+
"HEAD"
|
|
1204
|
+
]).trim();
|
|
1205
|
+
const bases = unique.filter((n) => n === "develop" || n === "main" || n === "master");
|
|
1206
|
+
return {
|
|
1207
|
+
branches: unique,
|
|
1208
|
+
current,
|
|
1209
|
+
defaultBase: bases.includes("develop") ? "develop" : bases.includes("main") ? "main" : bases[0] || "develop"
|
|
1210
|
+
};
|
|
1211
|
+
}
|
|
1212
|
+
/** URL del remote (origin, o el primero disponible). Vacío si no hay. */
|
|
1213
|
+
function getRemoteUrl(repo, preferred = "origin") {
|
|
1214
|
+
try {
|
|
1215
|
+
const url = git(repo, [
|
|
1216
|
+
"remote",
|
|
1217
|
+
"get-url",
|
|
1218
|
+
preferred
|
|
1219
|
+
]).trim();
|
|
1220
|
+
if (url) return url;
|
|
1221
|
+
} catch {}
|
|
1222
|
+
try {
|
|
1223
|
+
const remotes = git(repo, ["remote"]).split("\n").map((s) => s.trim()).filter(Boolean);
|
|
1224
|
+
if (!remotes.length) return "";
|
|
1225
|
+
return git(repo, [
|
|
1226
|
+
"remote",
|
|
1227
|
+
"get-url",
|
|
1228
|
+
remotes[0]
|
|
1229
|
+
]).trim();
|
|
1230
|
+
} catch {
|
|
1231
|
+
return "";
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
//#endregion
|
|
1235
|
+
//#region ../lib/extract.mjs
|
|
1236
|
+
function extractBranchDiff({ repo, branch, base = "develop" }) {
|
|
1237
|
+
if (!branch) throw new Error("Falta el branch a revisar.");
|
|
1238
|
+
const config = loadReviewConfig(repo);
|
|
1239
|
+
const mergeBase = git(repo, [
|
|
1240
|
+
"merge-base",
|
|
1241
|
+
base,
|
|
1242
|
+
branch
|
|
1243
|
+
]).trim();
|
|
1244
|
+
const branchSha = git(repo, ["rev-parse", branch]).trim();
|
|
1245
|
+
const baseSha = git(repo, ["rev-parse", base]).trim();
|
|
1246
|
+
const nameStatus = git(repo, [
|
|
1247
|
+
"diff",
|
|
1248
|
+
mergeBase,
|
|
1249
|
+
branch,
|
|
1250
|
+
"--name-status"
|
|
1251
|
+
]).trim().split("\n").filter(Boolean).map((line) => {
|
|
1252
|
+
const [status, ...rest] = line.split(" ");
|
|
1253
|
+
const path = rest[rest.length - 1];
|
|
1254
|
+
const oldPath = rest.length > 1 ? rest[0] : null;
|
|
1255
|
+
let changeType = "modified";
|
|
1256
|
+
if (status.startsWith("A")) changeType = "added";
|
|
1257
|
+
else if (status.startsWith("D")) changeType = "deleted";
|
|
1258
|
+
else if (status.startsWith("R")) changeType = "renamed";
|
|
1259
|
+
return {
|
|
1260
|
+
path,
|
|
1261
|
+
oldPath,
|
|
1262
|
+
changeType
|
|
1263
|
+
};
|
|
1264
|
+
});
|
|
1265
|
+
const ignored = [];
|
|
1266
|
+
const included = [];
|
|
1267
|
+
for (const f of nameStatus) if (isIgnored(f.path, config.ignore)) ignored.push(f.path);
|
|
1268
|
+
else included.push(f);
|
|
1269
|
+
const files = included.map((f) => {
|
|
1270
|
+
let raw = "";
|
|
1271
|
+
try {
|
|
1272
|
+
raw = git(repo, [
|
|
1273
|
+
"diff",
|
|
1274
|
+
mergeBase,
|
|
1275
|
+
branch,
|
|
1276
|
+
"--",
|
|
1277
|
+
f.path
|
|
1278
|
+
]);
|
|
1279
|
+
const idx = raw.indexOf("\n@@");
|
|
1280
|
+
if (idx !== -1) raw = raw.slice(idx + 1);
|
|
1281
|
+
} catch {
|
|
1282
|
+
raw = "";
|
|
1283
|
+
}
|
|
1284
|
+
const capped = capDiff(raw);
|
|
1285
|
+
return {
|
|
1286
|
+
path: f.path,
|
|
1287
|
+
oldPath: f.oldPath || void 0,
|
|
1288
|
+
changeType: f.changeType,
|
|
1289
|
+
diff: capped.diff,
|
|
1290
|
+
diffChars: capped.diffChars,
|
|
1291
|
+
truncated: capped.truncated,
|
|
1292
|
+
modelChars: capped.diff.length,
|
|
1293
|
+
group: ""
|
|
1294
|
+
};
|
|
1295
|
+
});
|
|
1296
|
+
const shortstat = git(repo, [
|
|
1297
|
+
"diff",
|
|
1298
|
+
mergeBase,
|
|
1299
|
+
branch,
|
|
1300
|
+
"--shortstat"
|
|
1301
|
+
]).trim();
|
|
1302
|
+
const insMatch = shortstat.match(/(\d+) insertion/);
|
|
1303
|
+
const delMatch = shortstat.match(/(\d+) deletion/);
|
|
1304
|
+
return {
|
|
1305
|
+
version: 2,
|
|
1306
|
+
repo,
|
|
1307
|
+
branch,
|
|
1308
|
+
baseBranch: base,
|
|
1309
|
+
mergeBase,
|
|
1310
|
+
branchSha,
|
|
1311
|
+
baseSha,
|
|
1312
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1313
|
+
intent: "",
|
|
1314
|
+
stats: {
|
|
1315
|
+
filesChanged: files.length,
|
|
1316
|
+
filesIgnored: ignored.length,
|
|
1317
|
+
filesTruncated: files.filter((f) => f.truncated).length,
|
|
1318
|
+
insertions: insMatch ? Number(insMatch[1]) : 0,
|
|
1319
|
+
deletions: delMatch ? Number(delMatch[1]) : 0
|
|
1320
|
+
},
|
|
1321
|
+
context: {
|
|
1322
|
+
commits: commitLog(repo, mergeBase, branch),
|
|
1323
|
+
rules: config.rules,
|
|
1324
|
+
rulesFile: config.rulesFile,
|
|
1325
|
+
ignored: ignored.slice(0, 40),
|
|
1326
|
+
testsTouched: files.filter((f) => isTestPath(f.path)).map((f) => f.path),
|
|
1327
|
+
testsMissing: files.filter((f) => !isTestPath(f.path)).length,
|
|
1328
|
+
guidelines: collectGuidelines(repo, files.map((f) => f.path), {
|
|
1329
|
+
rulesFile: config.rulesFile,
|
|
1330
|
+
skipNames: config.rules.trim() ? [config.rulesFile] : [],
|
|
1331
|
+
maxDepth: 1,
|
|
1332
|
+
maxChars: 8e3,
|
|
1333
|
+
maxTotalChars: 12e3
|
|
1334
|
+
})
|
|
1335
|
+
},
|
|
1336
|
+
files,
|
|
1337
|
+
groups: [],
|
|
1338
|
+
blocks: [],
|
|
1339
|
+
findings: [],
|
|
1340
|
+
skipped: []
|
|
1341
|
+
};
|
|
1342
|
+
}
|
|
1343
|
+
//#endregion
|
|
1344
|
+
//#region ../lib/remote-repo.mjs
|
|
1345
|
+
var CACHE_ROOT = join(tmpdir(), "diff-review-clones");
|
|
1346
|
+
var MAX_DEEPEN_ROUNDS = 8;
|
|
1347
|
+
var DEEPEN_BY = "50";
|
|
1348
|
+
function looksLikeGitUrl(value) {
|
|
1349
|
+
const v = String(value || "").trim();
|
|
1350
|
+
if (!v) return false;
|
|
1351
|
+
if (/^(https?:\/\/|git@|ssh:\/\/)/i.test(v)) return true;
|
|
1352
|
+
if (/\.git$/i.test(v) && !/^[A-Za-z]:/.test(v) && !v.startsWith("/") && !v.startsWith("\\")) return true;
|
|
1353
|
+
return false;
|
|
1354
|
+
}
|
|
1355
|
+
function normalizeGitUrl(url) {
|
|
1356
|
+
const raw = String(url || "").trim();
|
|
1357
|
+
if (!raw) throw new Error("Indicá la URL del repositorio.");
|
|
1358
|
+
if (!looksLikeGitUrl(raw)) throw new Error(`"${raw}" no parece una URL git. Usá https://…, git@… o ssh://…`);
|
|
1359
|
+
if (/^(file:|javascript:|data:)/i.test(raw)) throw new Error("Esquema de URL no permitido.");
|
|
1360
|
+
return raw.replace(/\/$/, "");
|
|
1361
|
+
}
|
|
1362
|
+
function cacheDirFor(url) {
|
|
1363
|
+
return join(CACHE_ROOT, createHash("sha1").update(url).digest("hex").slice(0, 16));
|
|
1364
|
+
}
|
|
1365
|
+
function runGit(args, { cwd, timeout = 300 * 1e3 } = {}) {
|
|
1366
|
+
return execFileSync("git", args, {
|
|
1367
|
+
cwd,
|
|
1368
|
+
encoding: "utf8",
|
|
1369
|
+
windowsHide: true,
|
|
1370
|
+
timeout,
|
|
1371
|
+
maxBuffer: 64 * 1024 * 1024
|
|
1372
|
+
});
|
|
1373
|
+
}
|
|
1374
|
+
/** Lista heads del remote sin clonar. */
|
|
1375
|
+
function listRemoteBranches(url) {
|
|
1376
|
+
const remote = normalizeGitUrl(url);
|
|
1377
|
+
let raw;
|
|
1378
|
+
try {
|
|
1379
|
+
raw = runGit([
|
|
1380
|
+
"ls-remote",
|
|
1381
|
+
"--heads",
|
|
1382
|
+
remote
|
|
1383
|
+
]);
|
|
1384
|
+
} catch (err) {
|
|
1385
|
+
const stderr = String(
|
|
1386
|
+
/** @type {{ stderr?: string }} */
|
|
1387
|
+
err.stderr || err.message || err
|
|
1388
|
+
);
|
|
1389
|
+
throw new Error("No se pudo listar branches del remote: " + stderr.split("\n").find(Boolean));
|
|
1390
|
+
}
|
|
1391
|
+
const branches = [...new Set(raw.split("\n").map((line) => line.trim()).filter(Boolean).map((line) => {
|
|
1392
|
+
return (line.split(" ")[1] || "").replace(/^refs\/heads\//, "");
|
|
1393
|
+
}).filter(Boolean))].sort((a, b) => a.localeCompare(b));
|
|
1394
|
+
if (!branches.length) throw new Error("El remote no expone branches (refs/heads).");
|
|
1395
|
+
return {
|
|
1396
|
+
branches,
|
|
1397
|
+
current: "",
|
|
1398
|
+
defaultBase: branches.includes("develop") ? "develop" : branches.includes("main") ? "main" : branches.includes("master") ? "master" : branches[0]
|
|
1399
|
+
};
|
|
1400
|
+
}
|
|
1401
|
+
function fetchRef(repo, ref) {
|
|
1402
|
+
const name = String(ref || "").trim();
|
|
1403
|
+
if (!name) return;
|
|
1404
|
+
try {
|
|
1405
|
+
runGit([
|
|
1406
|
+
"rev-parse",
|
|
1407
|
+
"--verify",
|
|
1408
|
+
name
|
|
1409
|
+
], { cwd: repo });
|
|
1410
|
+
return;
|
|
1411
|
+
} catch {}
|
|
1412
|
+
runGit([
|
|
1413
|
+
"fetch",
|
|
1414
|
+
"--depth",
|
|
1415
|
+
DEEPEN_BY,
|
|
1416
|
+
"origin",
|
|
1417
|
+
`refs/heads/${name}:refs/heads/${name}`
|
|
1418
|
+
], { cwd: repo });
|
|
1419
|
+
}
|
|
1420
|
+
function deepenUntilMergeBase(repo, base, branch) {
|
|
1421
|
+
for (let i = 0; i < MAX_DEEPEN_ROUNDS; i++) try {
|
|
1422
|
+
git(repo, [
|
|
1423
|
+
"merge-base",
|
|
1424
|
+
base,
|
|
1425
|
+
branch
|
|
1426
|
+
]);
|
|
1427
|
+
return;
|
|
1428
|
+
} catch {
|
|
1429
|
+
runGit([
|
|
1430
|
+
"fetch",
|
|
1431
|
+
"--deepen",
|
|
1432
|
+
DEEPEN_BY,
|
|
1433
|
+
"origin",
|
|
1434
|
+
`refs/heads/${base}:refs/heads/${base}`,
|
|
1435
|
+
`refs/heads/${branch}:refs/heads/${branch}`
|
|
1436
|
+
], { cwd: repo });
|
|
1437
|
+
}
|
|
1438
|
+
throw new Error(`No se encontró merge-base entre "${base}" y "${branch}" (historial shallow insuficiente). Probá con más historia o usá una carpeta local.`);
|
|
1439
|
+
}
|
|
1440
|
+
/**
|
|
1441
|
+
* Asegura un worktree local (cache en tmp) con branch y base disponibles para diff.
|
|
1442
|
+
* @returns {string} ruta absoluta del clone
|
|
1443
|
+
*/
|
|
1444
|
+
function ensureRemoteWorktree(url, branch, base = "develop") {
|
|
1445
|
+
const remote = normalizeGitUrl(url);
|
|
1446
|
+
const b = String(branch || "").trim();
|
|
1447
|
+
const baseRef = String(base || "develop").trim() || "develop";
|
|
1448
|
+
if (!b) throw new Error("Falta el branch a revisar.");
|
|
1449
|
+
mkdirSync(CACHE_ROOT, { recursive: true });
|
|
1450
|
+
const dir = cacheDirFor(remote);
|
|
1451
|
+
if (!existsSync(join(dir, ".git"))) {
|
|
1452
|
+
if (existsSync(dir)) rmSync(dir, {
|
|
1453
|
+
recursive: true,
|
|
1454
|
+
force: true
|
|
1455
|
+
});
|
|
1456
|
+
try {
|
|
1457
|
+
runGit([
|
|
1458
|
+
"clone",
|
|
1459
|
+
"--filter=blob:none",
|
|
1460
|
+
"--depth",
|
|
1461
|
+
DEEPEN_BY,
|
|
1462
|
+
"--branch",
|
|
1463
|
+
b,
|
|
1464
|
+
remote,
|
|
1465
|
+
dir
|
|
1466
|
+
], { timeout: 600 * 1e3 });
|
|
1467
|
+
} catch (err) {
|
|
1468
|
+
if (existsSync(dir)) rmSync(dir, {
|
|
1469
|
+
recursive: true,
|
|
1470
|
+
force: true
|
|
1471
|
+
});
|
|
1472
|
+
const stderr = String(
|
|
1473
|
+
/** @type {{ stderr?: string }} */
|
|
1474
|
+
err.stderr || err.message || err
|
|
1475
|
+
);
|
|
1476
|
+
throw new Error("No se pudo clonar el repo: " + stderr.split("\n").find(Boolean));
|
|
1477
|
+
}
|
|
1478
|
+
} else {
|
|
1479
|
+
try {
|
|
1480
|
+
runGit([
|
|
1481
|
+
"remote",
|
|
1482
|
+
"set-url",
|
|
1483
|
+
"origin",
|
|
1484
|
+
remote
|
|
1485
|
+
], { cwd: dir });
|
|
1486
|
+
} catch {}
|
|
1487
|
+
fetchRef(dir, b);
|
|
1488
|
+
try {
|
|
1489
|
+
runGit([
|
|
1490
|
+
"checkout",
|
|
1491
|
+
"--force",
|
|
1492
|
+
b
|
|
1493
|
+
], { cwd: dir });
|
|
1494
|
+
} catch {}
|
|
1495
|
+
}
|
|
1496
|
+
if (baseRef !== b) fetchRef(dir, baseRef);
|
|
1497
|
+
deepenUntilMergeBase(dir, baseRef, b);
|
|
1498
|
+
return dir;
|
|
1499
|
+
}
|
|
1500
|
+
//#endregion
|
|
1501
|
+
//#region ../lib/validate.mjs
|
|
1502
|
+
var GROUP_KINDS = [
|
|
1503
|
+
"feat",
|
|
1504
|
+
"fix",
|
|
1505
|
+
"refactor",
|
|
1506
|
+
"perf",
|
|
1507
|
+
"test",
|
|
1508
|
+
"chore",
|
|
1509
|
+
"docs",
|
|
1510
|
+
"infra"
|
|
1511
|
+
];
|
|
1512
|
+
var BLOCK_OPS = [
|
|
1513
|
+
"add",
|
|
1514
|
+
"mod",
|
|
1515
|
+
"del",
|
|
1516
|
+
"rename"
|
|
1517
|
+
];
|
|
1518
|
+
var WHY_SOURCES = [
|
|
1519
|
+
"code",
|
|
1520
|
+
"commit",
|
|
1521
|
+
"pr",
|
|
1522
|
+
"issue",
|
|
1523
|
+
"inferred"
|
|
1524
|
+
];
|
|
1525
|
+
var FINDING_CLASSES = ["risk", "quality"];
|
|
1526
|
+
var FINDING_SEVERITIES = [
|
|
1527
|
+
"high",
|
|
1528
|
+
"med",
|
|
1529
|
+
"low",
|
|
1530
|
+
"nit"
|
|
1531
|
+
];
|
|
1532
|
+
var FINDING_KINDS = [
|
|
1533
|
+
"bug",
|
|
1534
|
+
"race",
|
|
1535
|
+
"auth",
|
|
1536
|
+
"security",
|
|
1537
|
+
"api-break",
|
|
1538
|
+
"missing-test",
|
|
1539
|
+
"perf",
|
|
1540
|
+
"maintainability",
|
|
1541
|
+
"docs",
|
|
1542
|
+
"other"
|
|
1543
|
+
];
|
|
1544
|
+
var SKIP_REASONS = [
|
|
1545
|
+
"generated",
|
|
1546
|
+
"lockfile",
|
|
1547
|
+
"format",
|
|
1548
|
+
"vendored",
|
|
1549
|
+
"binary",
|
|
1550
|
+
"trivial",
|
|
1551
|
+
"ignored"
|
|
1552
|
+
];
|
|
1553
|
+
//#endregion
|
|
1554
|
+
//#region ../lib/review.mjs
|
|
1555
|
+
var MAX = {
|
|
1556
|
+
intent: 700,
|
|
1557
|
+
groupTitle: 100,
|
|
1558
|
+
groupIntent: 300,
|
|
1559
|
+
what: 240,
|
|
1560
|
+
why: 240,
|
|
1561
|
+
finding: 360
|
|
1562
|
+
};
|
|
1563
|
+
function text$1(value, max) {
|
|
1564
|
+
const raw = String(value ?? "").replace(/\s+/g, " ").trim();
|
|
1565
|
+
return raw.length > max ? `${raw.slice(0, max - 1).trimEnd()}…` : raw;
|
|
1566
|
+
}
|
|
1567
|
+
function oneOf(value, allowed, fallback) {
|
|
1568
|
+
const raw = String(value ?? "").trim().toLowerCase();
|
|
1569
|
+
return allowed.includes(raw) ? raw : fallback;
|
|
1570
|
+
}
|
|
1571
|
+
/**
|
|
1572
|
+
* Rango de líneas de un bloque. `L40-58` es post-imagen (lo que quedó);
|
|
1573
|
+
* `-L88-95` es pre-imagen, que es la única forma de señalar líneas borradas.
|
|
1574
|
+
*/
|
|
1575
|
+
function parseLineRange(value) {
|
|
1576
|
+
const raw = String(value ?? "").trim();
|
|
1577
|
+
const match = /^(-)?L?(\d+)(?:\s*[-–]\s*L?(\d+))?$/i.exec(raw);
|
|
1578
|
+
if (!match) return null;
|
|
1579
|
+
const start = Number(match[2]);
|
|
1580
|
+
const end = match[3] ? Number(match[3]) : start;
|
|
1581
|
+
return {
|
|
1582
|
+
side: match[1] ? "old" : "new",
|
|
1583
|
+
start: Math.min(start, end),
|
|
1584
|
+
end: Math.max(start, end)
|
|
1585
|
+
};
|
|
1586
|
+
}
|
|
1587
|
+
function formatLineRange(range) {
|
|
1588
|
+
if (!range) return "";
|
|
1589
|
+
const prefix = range.side === "old" ? "-L" : "L";
|
|
1590
|
+
return range.start === range.end ? `${prefix}${range.start}` : `${prefix}${range.start}-${range.end}`;
|
|
1591
|
+
}
|
|
1592
|
+
function countHunks(files) {
|
|
1593
|
+
let total = 0;
|
|
1594
|
+
for (const file of files || []) for (const line of String(file.diff || "").split("\n")) if (line.startsWith("@@")) total += 1;
|
|
1595
|
+
return total;
|
|
1596
|
+
}
|
|
1597
|
+
function normalizeGroups(raw) {
|
|
1598
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1599
|
+
const groups = [];
|
|
1600
|
+
for (const item of raw || []) {
|
|
1601
|
+
const id = String(item?.id || "").trim();
|
|
1602
|
+
if (!id || seen.has(id)) continue;
|
|
1603
|
+
seen.add(id);
|
|
1604
|
+
groups.push({
|
|
1605
|
+
id,
|
|
1606
|
+
kind: oneOf(item.kind, GROUP_KINDS, "chore"),
|
|
1607
|
+
title: text$1(item.title, MAX.groupTitle) || `${oneOf(item.kind, GROUP_KINDS, "chore")}: cambios`,
|
|
1608
|
+
intent: text$1(item.intent, MAX.groupIntent)
|
|
1609
|
+
});
|
|
1610
|
+
}
|
|
1611
|
+
return groups;
|
|
1612
|
+
}
|
|
1613
|
+
function normalizeBlocks(raw, { groupIds, knownPaths }) {
|
|
1614
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1615
|
+
const blocks = [];
|
|
1616
|
+
for (const item of raw || []) {
|
|
1617
|
+
const id = String(item?.id || "").trim();
|
|
1618
|
+
const group = String(item?.group || "").trim();
|
|
1619
|
+
const file = String(item?.file || "").trim();
|
|
1620
|
+
if (!id || seen.has(id)) continue;
|
|
1621
|
+
if (!groupIds.has(group)) continue;
|
|
1622
|
+
if (knownPaths.size && !knownPaths.has(file)) continue;
|
|
1623
|
+
const range = parseLineRange(item.lines);
|
|
1624
|
+
seen.add(id);
|
|
1625
|
+
blocks.push({
|
|
1626
|
+
id,
|
|
1627
|
+
group,
|
|
1628
|
+
file,
|
|
1629
|
+
lines: range ? formatLineRange(range) : text$1(item.lines, 24),
|
|
1630
|
+
side: range?.side || "new",
|
|
1631
|
+
start: range?.start ?? 0,
|
|
1632
|
+
end: range?.end ?? 0,
|
|
1633
|
+
op: oneOf(item.op, BLOCK_OPS, "mod"),
|
|
1634
|
+
what: text$1(item.what, MAX.what),
|
|
1635
|
+
why: text$1(item.why, MAX.why),
|
|
1636
|
+
source: oneOf(item.source, WHY_SOURCES, "inferred")
|
|
1637
|
+
});
|
|
1638
|
+
}
|
|
1639
|
+
blocks.sort((a, b) => a.file.localeCompare(b.file) || a.start - b.start);
|
|
1640
|
+
return blocks;
|
|
1641
|
+
}
|
|
1642
|
+
function findingKey(finding) {
|
|
1643
|
+
return [
|
|
1644
|
+
finding.file,
|
|
1645
|
+
finding.line ?? "",
|
|
1646
|
+
finding.kind,
|
|
1647
|
+
finding.what
|
|
1648
|
+
].join("|");
|
|
1649
|
+
}
|
|
1650
|
+
function normalizeFindings(raw, { blockIds, knownPaths }) {
|
|
1651
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1652
|
+
const findings = [];
|
|
1653
|
+
for (const item of raw || []) {
|
|
1654
|
+
const file = String(item?.file || item?.path || "").trim();
|
|
1655
|
+
const what = text$1(item?.what || item?.title, MAX.finding);
|
|
1656
|
+
if (!file || !what) continue;
|
|
1657
|
+
if (knownPaths.size && !knownPaths.has(file)) continue;
|
|
1658
|
+
const cls = oneOf(item.class, FINDING_CLASSES, "risk");
|
|
1659
|
+
const severity = oneOf(item.severity, FINDING_SEVERITIES, "med");
|
|
1660
|
+
const block = String(item?.block || "").trim();
|
|
1661
|
+
const line = Number.isFinite(Number(item?.line)) ? Number(item.line) : null;
|
|
1662
|
+
const finding = {
|
|
1663
|
+
id: "",
|
|
1664
|
+
class: cls,
|
|
1665
|
+
severity,
|
|
1666
|
+
blocking: cls === "risk" && (item?.blocking === true || severity === "high"),
|
|
1667
|
+
kind: oneOf(item.kind, FINDING_KINDS, "other"),
|
|
1668
|
+
file,
|
|
1669
|
+
line,
|
|
1670
|
+
block: blockIds.has(block) ? block : "",
|
|
1671
|
+
what,
|
|
1672
|
+
fix: text$1(item.fix || item.detail, MAX.finding)
|
|
1673
|
+
};
|
|
1674
|
+
const key = findingKey(finding);
|
|
1675
|
+
if (seen.has(key)) continue;
|
|
1676
|
+
seen.add(key);
|
|
1677
|
+
findings.push(finding);
|
|
1678
|
+
}
|
|
1679
|
+
const rank = {
|
|
1680
|
+
high: 0,
|
|
1681
|
+
med: 1,
|
|
1682
|
+
low: 2,
|
|
1683
|
+
nit: 3
|
|
1684
|
+
};
|
|
1685
|
+
findings.sort((a, b) => Number(b.blocking) - Number(a.blocking) || (a.class === b.class ? 0 : a.class === "risk" ? -1 : 1) || rank[a.severity] - rank[b.severity] || a.file.localeCompare(b.file));
|
|
1686
|
+
findings.forEach((finding, i) => {
|
|
1687
|
+
finding.id = `f${i + 1}`;
|
|
1688
|
+
});
|
|
1689
|
+
return findings;
|
|
1690
|
+
}
|
|
1691
|
+
function normalizeSkipped(raw, ignored) {
|
|
1692
|
+
const byFile = /* @__PURE__ */ new Map();
|
|
1693
|
+
for (const path of ignored || []) byFile.set(String(path), {
|
|
1694
|
+
file: String(path),
|
|
1695
|
+
reason: "ignored"
|
|
1696
|
+
});
|
|
1697
|
+
for (const item of raw || []) {
|
|
1698
|
+
const file = String(item?.file || "").trim();
|
|
1699
|
+
if (!file) continue;
|
|
1700
|
+
byFile.set(file, {
|
|
1701
|
+
file,
|
|
1702
|
+
reason: oneOf(item.reason, SKIP_REASONS, "trivial")
|
|
1703
|
+
});
|
|
1704
|
+
}
|
|
1705
|
+
return [...byFile.values()];
|
|
1706
|
+
}
|
|
1707
|
+
/** Tema donde se dibuja el archivo: el que aporta más bloques, desempatando por orden de tema. */
|
|
1708
|
+
function dominantGroup(blocks, path, groupOrder) {
|
|
1709
|
+
const tally = /* @__PURE__ */ new Map();
|
|
1710
|
+
for (const block of blocks) {
|
|
1711
|
+
if (block.file !== path) continue;
|
|
1712
|
+
tally.set(block.group, (tally.get(block.group) || 0) + 1);
|
|
1713
|
+
}
|
|
1714
|
+
if (!tally.size) return "";
|
|
1715
|
+
return [...tally.entries()].sort((a, b) => b[1] - a[1] || groupOrder.indexOf(a[0]) - groupOrder.indexOf(b[0]))[0][0];
|
|
1716
|
+
}
|
|
1717
|
+
/**
|
|
1718
|
+
* Documento final: lo que dijo el modelo, saneado y cruzado contra los archivos
|
|
1719
|
+
* reales del diff. Todo lo que no cierra se descarta acá en vez de romper la UI.
|
|
1720
|
+
*/
|
|
1721
|
+
function buildReview({ skeleton, payload, notes = [] }) {
|
|
1722
|
+
const files = skeleton.files || [];
|
|
1723
|
+
const knownPaths = new Set(files.map((f) => f.path));
|
|
1724
|
+
const groups = normalizeGroups(payload?.groups);
|
|
1725
|
+
const groupIds = new Set(groups.map((g) => g.id));
|
|
1726
|
+
const blocks = normalizeBlocks(payload?.blocks, {
|
|
1727
|
+
groupIds,
|
|
1728
|
+
knownPaths
|
|
1729
|
+
});
|
|
1730
|
+
const blockIds = new Set(blocks.map((b) => b.id));
|
|
1731
|
+
const findings = normalizeFindings(payload?.findings, {
|
|
1732
|
+
blockIds,
|
|
1733
|
+
knownPaths
|
|
1734
|
+
});
|
|
1735
|
+
const skipped = normalizeSkipped(payload?.skipped, skeleton.context?.ignored);
|
|
1736
|
+
const usedGroups = new Set(blocks.map((b) => b.group));
|
|
1737
|
+
const liveGroups = groups.filter((g) => usedGroups.has(g.id));
|
|
1738
|
+
const groupOrder = liveGroups.map((g) => g.id);
|
|
1739
|
+
const skippedFiles = new Set(skipped.map((s) => s.file));
|
|
1740
|
+
const orphans = files.filter((f) => !blocks.some((b) => b.file === f.path) && !skippedFiles.has(f.path));
|
|
1741
|
+
if (orphans.length) notes = [...notes, `${orphans.length} archivo(s) quedaron sin explicación del modelo.`];
|
|
1742
|
+
const decorated = files.map((file) => ({
|
|
1743
|
+
...file,
|
|
1744
|
+
group: dominantGroup(blocks, file.path, groupOrder)
|
|
1745
|
+
}));
|
|
1746
|
+
return {
|
|
1747
|
+
...skeleton,
|
|
1748
|
+
version: 2,
|
|
1749
|
+
intent: text$1(payload?.intent, MAX.intent),
|
|
1750
|
+
groups: liveGroups,
|
|
1751
|
+
blocks,
|
|
1752
|
+
findings,
|
|
1753
|
+
skipped,
|
|
1754
|
+
files: decorated,
|
|
1755
|
+
stats: {
|
|
1756
|
+
...skeleton.stats,
|
|
1757
|
+
hunks: countHunks(files)
|
|
1758
|
+
},
|
|
1759
|
+
notes,
|
|
1760
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1761
|
+
};
|
|
1762
|
+
}
|
|
1763
|
+
//#endregion
|
|
1764
|
+
//#region src/lib/server/pick-folder.ts
|
|
1765
|
+
/**
|
|
1766
|
+
* Diálogo nativo de carpetas.
|
|
1767
|
+
* Windows: PowerShell DPI-aware (exit 2 = canceló; 1 = error → fallback).
|
|
1768
|
+
* Otros: @bindrs/rfd; si falla, shell (VBS / osascript / zenity).
|
|
1769
|
+
*/
|
|
1770
|
+
async function pickFolder() {
|
|
1771
|
+
if (platform() === "win32") try {
|
|
1772
|
+
return pickWindowsDpiAware();
|
|
1773
|
+
} catch (err) {
|
|
1774
|
+
console.warn("[diff-review] picker DPI-aware falló, probando rfd/fallback:", err instanceof Error ? err.message : err);
|
|
1775
|
+
}
|
|
1776
|
+
try {
|
|
1777
|
+
return pickWithRfdWorker();
|
|
1778
|
+
} catch (err) {
|
|
1779
|
+
console.warn("[diff-review] @bindrs/rfd no disponible, usando fallback:", err instanceof Error ? err.message : err);
|
|
1780
|
+
return pickFolderFallback();
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
function resolveNodeBin() {
|
|
1784
|
+
if (process.execPath && /node(\.exe)?$/i.test(process.execPath)) return process.execPath;
|
|
1785
|
+
return "node";
|
|
1786
|
+
}
|
|
1787
|
+
function writeUtf8Bom(path, content) {
|
|
1788
|
+
writeFileSync(path, `\uFEFF${content}`, "utf8");
|
|
1789
|
+
}
|
|
1790
|
+
/**
|
|
1791
|
+
* IFileOpenDialog nativo (Vista+) + DPI Per-Monitor V2 ANTES de cualquier UI.
|
|
1792
|
+
* Exit: 0 ok, 2 canceló, otro error → fallback.
|
|
1793
|
+
*/
|
|
1794
|
+
function pickWindowsDpiAware() {
|
|
1795
|
+
const dir = mkdtempSync(join(tmpdir(), "diff-review-pick-"));
|
|
1796
|
+
const script = join(dir, "pick.ps1");
|
|
1797
|
+
const outFile = join(dir, "path.txt");
|
|
1798
|
+
try {
|
|
1799
|
+
writeUtf8Bom(script, `
|
|
1800
|
+
$ErrorActionPreference = 'Stop'
|
|
1801
|
+
|
|
1802
|
+
# 1) DPI awareness ANTES de cargar WinForms / crear ventanas.
|
|
1803
|
+
Add-Type @'
|
|
1804
|
+
using System;
|
|
1805
|
+
using System.Runtime.InteropServices;
|
|
1806
|
+
public static class DiffReviewDpi {
|
|
1807
|
+
[DllImport("user32.dll")] public static extern IntPtr SetThreadDpiAwarenessContext(IntPtr dpiContext);
|
|
1808
|
+
[DllImport("shcore.dll")] public static extern int SetProcessDpiAwareness(int value);
|
|
1809
|
+
[DllImport("user32.dll")] public static extern bool SetProcessDPIAware();
|
|
1810
|
+
public static readonly IntPtr PerMonitorV2 = unchecked((IntPtr)(-4));
|
|
1811
|
+
public static void Enable() {
|
|
1812
|
+
try { SetThreadDpiAwarenessContext(PerMonitorV2); return; } catch { }
|
|
1813
|
+
try { SetProcessDpiAwareness(2); return; } catch { }
|
|
1814
|
+
try { SetProcessDPIAware(); } catch { }
|
|
1815
|
+
}
|
|
1816
|
+
}
|
|
1817
|
+
'@
|
|
1818
|
+
[DiffReviewDpi]::Enable()
|
|
1819
|
+
|
|
1820
|
+
# 2) IFileOpenDialog COM (mismo dialogo nitido que el Explorer).
|
|
1821
|
+
Add-Type @'
|
|
1822
|
+
using System;
|
|
1823
|
+
using System.Runtime.InteropServices;
|
|
1824
|
+
|
|
1825
|
+
[ComImport, Guid("DC1C5A9C-E88A-4DDE-A5A1-60F82A20AEF7")]
|
|
1826
|
+
internal class FileOpenDialogRCW { }
|
|
1827
|
+
|
|
1828
|
+
[ComImport, Guid("42F85136-DB7E-439C-85F1-E4075D135FC8"),
|
|
1829
|
+
InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
|
1830
|
+
internal interface IFileDialog {
|
|
1831
|
+
[PreserveSig] int Show(IntPtr parent);
|
|
1832
|
+
void SetFileTypes(uint cFileTypes, IntPtr rgFilterSpec);
|
|
1833
|
+
void SetFileTypeIndex(uint iFileType);
|
|
1834
|
+
void GetFileTypeIndex(out uint piFileType);
|
|
1835
|
+
void Advise(IntPtr pfde, out uint pdwCookie);
|
|
1836
|
+
void Unadvise(uint dwCookie);
|
|
1837
|
+
void SetOptions(uint fos);
|
|
1838
|
+
void GetOptions(out uint pfos);
|
|
1839
|
+
void SetDefaultFolder(IShellItem psi);
|
|
1840
|
+
void SetFolder(IShellItem psi);
|
|
1841
|
+
void GetFolder(out IShellItem ppsi);
|
|
1842
|
+
void GetCurrentSelection(out IShellItem ppsi);
|
|
1843
|
+
void SetFileName([MarshalAs(UnmanagedType.LPWStr)] string pszName);
|
|
1844
|
+
void GetFileName([MarshalAs(UnmanagedType.LPWStr)] out string pszName);
|
|
1845
|
+
void SetTitle([MarshalAs(UnmanagedType.LPWStr)] string pszTitle);
|
|
1846
|
+
void SetOkButtonLabel([MarshalAs(UnmanagedType.LPWStr)] string pszText);
|
|
1847
|
+
void SetFileNameLabel([MarshalAs(UnmanagedType.LPWStr)] string pszLabel);
|
|
1848
|
+
void GetResult(out IShellItem ppsi);
|
|
1849
|
+
void AddPlace(IShellItem psi, int fdap);
|
|
1850
|
+
void SetDefaultExtension([MarshalAs(UnmanagedType.LPWStr)] string pszDefaultExtension);
|
|
1851
|
+
void Close(int hr);
|
|
1852
|
+
void SetClientGuid(ref Guid guid);
|
|
1853
|
+
void ClearClientData();
|
|
1854
|
+
void SetFilter(IntPtr pFilter);
|
|
1855
|
+
}
|
|
1856
|
+
|
|
1857
|
+
[ComImport, Guid("43826D1E-E718-42EE-BC55-A1E261C37BFE"),
|
|
1858
|
+
InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
|
1859
|
+
internal interface IShellItem {
|
|
1860
|
+
void BindToHandler(IntPtr pbc, ref Guid bhid, ref Guid riid, out IntPtr ppv);
|
|
1861
|
+
void GetParent(out IShellItem ppsi);
|
|
1862
|
+
void GetDisplayName(uint sigdnName, out IntPtr ppszName);
|
|
1863
|
+
void GetAttributes(uint sfgaoMask, out uint psfgaoAttribs);
|
|
1864
|
+
void Compare(IShellItem psi, uint hint, out int piOrder);
|
|
1865
|
+
}
|
|
1866
|
+
|
|
1867
|
+
public static class DiffReviewFolderPicker {
|
|
1868
|
+
const uint FOS_PICKFOLDERS = 0x00000020;
|
|
1869
|
+
const uint FOS_FORCEFILESYSTEM = 0x00000040;
|
|
1870
|
+
const uint FOS_PATHMUSTEXIST = 0x00000800;
|
|
1871
|
+
const uint SIGDN_FILESYSPATH = 0x80058000;
|
|
1872
|
+
|
|
1873
|
+
[DllImport("ole32.dll")]
|
|
1874
|
+
static extern void CoTaskMemFree(IntPtr pv);
|
|
1875
|
+
|
|
1876
|
+
public static string Pick(string title) {
|
|
1877
|
+
var dialog = (IFileDialog)new FileOpenDialogRCW();
|
|
1878
|
+
uint options;
|
|
1879
|
+
dialog.GetOptions(out options);
|
|
1880
|
+
dialog.SetOptions(options | FOS_PICKFOLDERS | FOS_FORCEFILESYSTEM | FOS_PATHMUSTEXIST);
|
|
1881
|
+
if (!string.IsNullOrEmpty(title)) dialog.SetTitle(title);
|
|
1882
|
+
int hr = dialog.Show(IntPtr.Zero);
|
|
1883
|
+
if (hr != 0) return null; // cancel / error
|
|
1884
|
+
IShellItem item;
|
|
1885
|
+
dialog.GetResult(out item);
|
|
1886
|
+
IntPtr psz;
|
|
1887
|
+
item.GetDisplayName(SIGDN_FILESYSPATH, out psz);
|
|
1888
|
+
string path = Marshal.PtrToStringUni(psz);
|
|
1889
|
+
CoTaskMemFree(psz);
|
|
1890
|
+
return path;
|
|
1891
|
+
}
|
|
1892
|
+
}
|
|
1893
|
+
'@
|
|
1894
|
+
|
|
1895
|
+
$path = [DiffReviewFolderPicker]::Pick('Elegi el repositorio git')
|
|
1896
|
+
if ([string]::IsNullOrWhiteSpace($path)) { exit 2 }
|
|
1897
|
+
[System.IO.File]::WriteAllText(${JSON.stringify(outFile)}, $path)
|
|
1898
|
+
exit 0
|
|
1899
|
+
`.trim() + "\r\n");
|
|
1900
|
+
const result = spawnSync("powershell.exe", [
|
|
1901
|
+
"-STA",
|
|
1902
|
+
"-NoProfile",
|
|
1903
|
+
"-ExecutionPolicy",
|
|
1904
|
+
"Bypass",
|
|
1905
|
+
"-File",
|
|
1906
|
+
script
|
|
1907
|
+
], {
|
|
1908
|
+
encoding: "utf8",
|
|
1909
|
+
windowsHide: false,
|
|
1910
|
+
stdio: [
|
|
1911
|
+
"ignore",
|
|
1912
|
+
"pipe",
|
|
1913
|
+
"pipe"
|
|
1914
|
+
],
|
|
1915
|
+
timeout: 600 * 1e3
|
|
1916
|
+
});
|
|
1917
|
+
if (result.status === 2) return null;
|
|
1918
|
+
if (result.status !== 0) {
|
|
1919
|
+
const err = (result.stderr || result.stdout || result.error?.message || `exit ${result.status}`).toString().trim();
|
|
1920
|
+
throw new Error(err || "powershell picker fallo");
|
|
1921
|
+
}
|
|
1922
|
+
if (!existsSync(outFile)) throw new Error("powershell picker no escribio la ruta");
|
|
1923
|
+
const path = readFileSync(outFile, "utf8").replace(/^\uFEFF/, "").trim();
|
|
1924
|
+
if (!path) throw new Error("powershell picker devolvio ruta vacia");
|
|
1925
|
+
return path;
|
|
1926
|
+
} finally {
|
|
1927
|
+
try {
|
|
1928
|
+
rmSync(dir, {
|
|
1929
|
+
recursive: true,
|
|
1930
|
+
force: true
|
|
1931
|
+
});
|
|
1932
|
+
} catch {}
|
|
1933
|
+
}
|
|
1934
|
+
}
|
|
1935
|
+
function pickWithRfdWorker() {
|
|
1936
|
+
const dir = mkdtempSync(join(tmpdir(), "diff-review-rfd-"));
|
|
1937
|
+
const worker = join(dir, "pick.mjs");
|
|
1938
|
+
const outFile = join(dir, "path.txt");
|
|
1939
|
+
try {
|
|
1940
|
+
writeFileSync(worker, `import { createRequire } from 'node:module';
|
|
1941
|
+
import { existsSync, writeFileSync } from 'node:fs';
|
|
1942
|
+
import { dirname, join } from 'node:path';
|
|
1943
|
+
|
|
1944
|
+
const require = createRequire(import.meta.url);
|
|
1945
|
+
const outFile = ${JSON.stringify(outFile)};
|
|
1946
|
+
|
|
1947
|
+
function findRfdIndex() {
|
|
1948
|
+
const seen = new Set();
|
|
1949
|
+
let dir = process.cwd();
|
|
1950
|
+
for (let i = 0; i < 16; i++) {
|
|
1951
|
+
if (seen.has(dir)) break;
|
|
1952
|
+
seen.add(dir);
|
|
1953
|
+
const indexJs = join(dir, 'node_modules', '@bindrs', 'rfd', 'index.js');
|
|
1954
|
+
if (existsSync(indexJs)) return indexJs;
|
|
1955
|
+
const parent = dirname(dir);
|
|
1956
|
+
if (parent === dir) break;
|
|
1957
|
+
dir = parent;
|
|
1958
|
+
}
|
|
1959
|
+
throw new Error('No se encontro @bindrs/rfd en node_modules');
|
|
1960
|
+
}
|
|
1961
|
+
|
|
1962
|
+
const { AsyncFileDialog } = require(findRfdIndex());
|
|
1963
|
+
const handle = await new AsyncFileDialog().setTitle('Elegi el repositorio git').pickFolder();
|
|
1964
|
+
if (!handle) process.exit(2);
|
|
1965
|
+
writeFileSync(outFile, handle.path(), 'utf8');
|
|
1966
|
+
`, "utf8");
|
|
1967
|
+
const result = spawnSync(resolveNodeBin(), [worker], {
|
|
1968
|
+
encoding: "utf8",
|
|
1969
|
+
windowsHide: false,
|
|
1970
|
+
stdio: [
|
|
1971
|
+
"ignore",
|
|
1972
|
+
"pipe",
|
|
1973
|
+
"pipe"
|
|
1974
|
+
],
|
|
1975
|
+
timeout: 600 * 1e3,
|
|
1976
|
+
cwd: process.cwd(),
|
|
1977
|
+
env: process.env
|
|
1978
|
+
});
|
|
1979
|
+
if (result.status === 2 || result.status === 1) return null;
|
|
1980
|
+
if (result.status !== 0) {
|
|
1981
|
+
const err = (result.stderr || result.stdout || result.error?.message || `exit ${result.status}`).toString().trim();
|
|
1982
|
+
throw new Error(err || "worker rfd fallo");
|
|
1983
|
+
}
|
|
1984
|
+
if (!existsSync(outFile)) return null;
|
|
1985
|
+
return readFileSync(outFile, "utf8").replace(/^\uFEFF/, "").trim() || null;
|
|
1986
|
+
} finally {
|
|
1987
|
+
try {
|
|
1988
|
+
rmSync(dir, {
|
|
1989
|
+
recursive: true,
|
|
1990
|
+
force: true
|
|
1991
|
+
});
|
|
1992
|
+
} catch {}
|
|
1993
|
+
}
|
|
1994
|
+
}
|
|
1995
|
+
function pickFolderFallback() {
|
|
1996
|
+
const os = platform();
|
|
1997
|
+
if (os === "win32") return pickWindowsVbs();
|
|
1998
|
+
if (os === "darwin") return pickMac();
|
|
1999
|
+
return pickLinux();
|
|
2000
|
+
}
|
|
2001
|
+
function pickWindowsVbs() {
|
|
2002
|
+
const dir = mkdtempSync(join(tmpdir(), "diff-review-pick-"));
|
|
2003
|
+
const vbs = join(dir, "pick.vbs");
|
|
2004
|
+
const outFile = join(dir, "path.txt");
|
|
2005
|
+
try {
|
|
2006
|
+
writeFileSync(vbs, [
|
|
2007
|
+
"Set shell = CreateObject(\"Shell.Application\")",
|
|
2008
|
+
"Set folder = shell.BrowseForFolder(0, \"Elegi el repositorio git\", 0)",
|
|
2009
|
+
"If folder Is Nothing Then",
|
|
2010
|
+
" WScript.Quit 1",
|
|
2011
|
+
"End If",
|
|
2012
|
+
"Set fso = CreateObject(\"Scripting.FileSystemObject\")",
|
|
2013
|
+
`Set file = fso.CreateTextFile(${JSON.stringify(outFile)}, True, True)`,
|
|
2014
|
+
"file.Write folder.Self.Path",
|
|
2015
|
+
"file.Close"
|
|
2016
|
+
].join("\r\n"), "utf8");
|
|
2017
|
+
const result = spawnSync("cscript.exe", [
|
|
2018
|
+
"//nologo",
|
|
2019
|
+
"//E:VBScript",
|
|
2020
|
+
vbs
|
|
2021
|
+
], {
|
|
2022
|
+
encoding: "utf8",
|
|
2023
|
+
windowsHide: false,
|
|
2024
|
+
stdio: [
|
|
2025
|
+
"ignore",
|
|
2026
|
+
"ignore",
|
|
2027
|
+
"pipe"
|
|
2028
|
+
],
|
|
2029
|
+
timeout: 600 * 1e3
|
|
2030
|
+
});
|
|
2031
|
+
if (result.status === 1) return null;
|
|
2032
|
+
if (result.status !== 0) {
|
|
2033
|
+
const err = (result.stderr || result.error?.message || `exit ${result.status}`).toString().trim();
|
|
2034
|
+
throw new Error(err || "cscript fallo");
|
|
2035
|
+
}
|
|
2036
|
+
if (!existsSync(outFile)) return null;
|
|
2037
|
+
const buf = readFileSync(outFile);
|
|
2038
|
+
return (buf.length >= 2 && buf[0] === 255 && buf[1] === 254 ? buf.subarray(2).toString("utf16le").trim() : buf.toString("utf8").replace(/^\uFEFF/, "").trim()) || null;
|
|
2039
|
+
} catch (err) {
|
|
2040
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2041
|
+
if (/canceled|cancelled|abort/i.test(msg)) return null;
|
|
2042
|
+
throw new Error("No se pudo abrir el dialogo de carpetas: " + msg);
|
|
2043
|
+
} finally {
|
|
2044
|
+
try {
|
|
2045
|
+
rmSync(dir, {
|
|
2046
|
+
recursive: true,
|
|
2047
|
+
force: true
|
|
2048
|
+
});
|
|
2049
|
+
} catch {}
|
|
2050
|
+
}
|
|
2051
|
+
}
|
|
2052
|
+
function pickMac() {
|
|
2053
|
+
try {
|
|
2054
|
+
const out = execFileSync("osascript", ["-e", "POSIX path of (choose folder with prompt \"Elegi el repositorio git\")"], {
|
|
2055
|
+
encoding: "utf8",
|
|
2056
|
+
timeout: 600 * 1e3
|
|
2057
|
+
});
|
|
2058
|
+
return String(out || "").trim().replace(/\/$/, "");
|
|
2059
|
+
} catch (err) {
|
|
2060
|
+
if (err.status === 1 || /User canceled|-128/i.test(String(err))) return null;
|
|
2061
|
+
throw new Error("No se pudo abrir el dialogo de carpetas: " + (err instanceof Error ? err.message : String(err)));
|
|
2062
|
+
}
|
|
2063
|
+
}
|
|
2064
|
+
function pickLinux() {
|
|
2065
|
+
try {
|
|
2066
|
+
const out = execFileSync("zenity", [
|
|
2067
|
+
"--file-selection",
|
|
2068
|
+
"--directory",
|
|
2069
|
+
"--title=Elegi el repositorio git"
|
|
2070
|
+
], {
|
|
2071
|
+
encoding: "utf8",
|
|
2072
|
+
timeout: 600 * 1e3
|
|
2073
|
+
});
|
|
2074
|
+
return String(out || "").trim() || null;
|
|
2075
|
+
} catch (zenityErr) {
|
|
2076
|
+
if (zenityErr.status === 1) return null;
|
|
2077
|
+
try {
|
|
2078
|
+
const out = execFileSync("kdialog", [
|
|
2079
|
+
"--getexistingdirectory",
|
|
2080
|
+
".",
|
|
2081
|
+
"Elegi el repositorio git"
|
|
2082
|
+
], {
|
|
2083
|
+
encoding: "utf8",
|
|
2084
|
+
timeout: 600 * 1e3
|
|
2085
|
+
});
|
|
2086
|
+
return String(out || "").trim() || null;
|
|
2087
|
+
} catch (kErr) {
|
|
2088
|
+
if (kErr.status === 1) return null;
|
|
2089
|
+
throw new Error("No hay dialogo de carpetas disponible. Instala zenity o kdialog, o pega la ruta a mano.");
|
|
2090
|
+
}
|
|
2091
|
+
}
|
|
2092
|
+
}
|
|
2093
|
+
//#endregion
|
|
2094
|
+
//#region src/lib/server/git-review.ts
|
|
2095
|
+
function describeGitError(err) {
|
|
2096
|
+
if (err && typeof err === "object") {
|
|
2097
|
+
const rec = err;
|
|
2098
|
+
const stderr = String(rec.stderr || "").trim().split("\n").find(Boolean);
|
|
2099
|
+
if (stderr) return stderr;
|
|
2100
|
+
if (rec.message) return rec.message;
|
|
2101
|
+
}
|
|
2102
|
+
return err instanceof Error ? err.message : String(err);
|
|
2103
|
+
}
|
|
2104
|
+
function resolveSource(source, repo) {
|
|
2105
|
+
if (source === "local" || source === "url") return source;
|
|
2106
|
+
return looksLikeGitUrl(repo) ? "url" : "local";
|
|
2107
|
+
}
|
|
2108
|
+
function loadRepoInfo(source, repo) {
|
|
2109
|
+
if (resolveSource(source, repo) === "url") {
|
|
2110
|
+
const url = normalizeGitUrl(repo);
|
|
2111
|
+
return {
|
|
2112
|
+
repo: url,
|
|
2113
|
+
source: "url",
|
|
2114
|
+
remoteUrl: url,
|
|
2115
|
+
...listRemoteBranches(url)
|
|
2116
|
+
};
|
|
2117
|
+
}
|
|
2118
|
+
const resolved = assertGitRepo(repo);
|
|
2119
|
+
const listed = listBranches(resolved);
|
|
2120
|
+
return {
|
|
2121
|
+
repo: resolved,
|
|
2122
|
+
source: "local",
|
|
2123
|
+
remoteUrl: getRemoteUrl(resolved) || void 0,
|
|
2124
|
+
...listed
|
|
2125
|
+
};
|
|
2126
|
+
}
|
|
2127
|
+
async function pickLocalFolder$1() {
|
|
2128
|
+
return pickFolder();
|
|
2129
|
+
}
|
|
2130
|
+
function resolveWorktree(source, repo, branch, base) {
|
|
2131
|
+
if (source === "url") return ensureRemoteWorktree(repo, branch, base);
|
|
2132
|
+
return assertGitRepo(repo);
|
|
2133
|
+
}
|
|
2134
|
+
function toReviewFile(file) {
|
|
2135
|
+
const changeType = file.changeType;
|
|
2136
|
+
const typed = changeType === "added" || changeType === "deleted" || changeType === "renamed" || changeType === "modified" ? changeType : "modified";
|
|
2137
|
+
return {
|
|
2138
|
+
path: file.path,
|
|
2139
|
+
oldPath: file.oldPath || void 0,
|
|
2140
|
+
changeType: typed,
|
|
2141
|
+
diff: String(file.diff ?? "").replaceAll("\0", ""),
|
|
2142
|
+
group: file.group ?? ""
|
|
2143
|
+
};
|
|
2144
|
+
}
|
|
2145
|
+
/**
|
|
2146
|
+
* Contenido del archivo en la punta del branch, para rellenar en el viewer las líneas
|
|
2147
|
+
* que el diff no trae. Devuelve null (en vez de reventar) cuando el repo no está a mano
|
|
2148
|
+
* o el archivo es binario: ahí el viewer apaga los botones de expandir.
|
|
2149
|
+
*/
|
|
2150
|
+
function loadFileLines(input) {
|
|
2151
|
+
try {
|
|
2152
|
+
return showFileLines(resolveWorktree(resolveSource(input.source, input.repo), input.repo, input.branch, input.base || "develop"), input.branch, input.path);
|
|
2153
|
+
} catch (err) {
|
|
2154
|
+
console.error("[diff-review git] no se pudo leer", input.path, describeGitError(err));
|
|
2155
|
+
return null;
|
|
2156
|
+
}
|
|
2157
|
+
}
|
|
2158
|
+
function hydrateReviewPayload(input) {
|
|
2159
|
+
const skeleton = extractBranchDiff({
|
|
2160
|
+
repo: resolveWorktree(resolveSource(input.source, input.repo), input.repo, input.branch, input.base || "develop"),
|
|
2161
|
+
branch: input.branch,
|
|
2162
|
+
base: input.base || "develop"
|
|
2163
|
+
});
|
|
2164
|
+
const extraNotes = Array.isArray(input.payload?.notes) ? input.payload.notes.filter((n) => typeof n === "string") : [];
|
|
2165
|
+
const built = buildReview({
|
|
2166
|
+
skeleton,
|
|
2167
|
+
payload: input.payload,
|
|
2168
|
+
notes: extraNotes
|
|
2169
|
+
});
|
|
2170
|
+
const doc = {
|
|
2171
|
+
intent: built.intent,
|
|
2172
|
+
groups: built.groups,
|
|
2173
|
+
blocks: built.blocks,
|
|
2174
|
+
findings: built.findings,
|
|
2175
|
+
skipped: built.skipped,
|
|
2176
|
+
files: (built.files || []).map(toReviewFile),
|
|
2177
|
+
notes: built.notes || []
|
|
2178
|
+
};
|
|
2179
|
+
return JSON.parse(JSON.stringify(doc));
|
|
2180
|
+
}
|
|
2181
|
+
//#endregion
|
|
2182
|
+
//#region src/lib/report/git.remote.ts
|
|
2183
|
+
var git_remote_exports = /* @__PURE__ */ __exportAll({
|
|
2184
|
+
fileLines: () => fileLines,
|
|
2185
|
+
hydrateFromGit: () => hydrateFromGit,
|
|
2186
|
+
loadRepo: () => loadRepo,
|
|
2187
|
+
pickLocalFolder: () => pickLocalFolder
|
|
2188
|
+
});
|
|
2189
|
+
function fail(err) {
|
|
2190
|
+
const message = describeGitError(err) || "No se pudo hablar con git.";
|
|
2191
|
+
console.error("[diff-review git]", message, err);
|
|
2192
|
+
error(400, message);
|
|
2193
|
+
}
|
|
2194
|
+
var loadRepo = /* @__PURE__ */ query("unchecked", async (input) => {
|
|
2195
|
+
try {
|
|
2196
|
+
return loadRepoInfo(input?.source, String(input?.repo || "").trim());
|
|
2197
|
+
} catch (err) {
|
|
2198
|
+
fail(err);
|
|
2199
|
+
}
|
|
2200
|
+
});
|
|
2201
|
+
var pickLocalFolder = /* @__PURE__ */ command("unchecked", async (_input) => {
|
|
2202
|
+
try {
|
|
2203
|
+
return await pickLocalFolder$1();
|
|
2204
|
+
} catch (err) {
|
|
2205
|
+
fail(err);
|
|
2206
|
+
}
|
|
2207
|
+
});
|
|
2208
|
+
/** Líneas del archivo en el branch revisado, para expandir el contexto oculto de un diff. */
|
|
2209
|
+
var fileLines = /* @__PURE__ */ query("unchecked", async (input) => loadFileLines({
|
|
2210
|
+
repo: String(input?.repo || "").trim(),
|
|
2211
|
+
branch: String(input?.branch || "").trim(),
|
|
2212
|
+
base: String(input?.base || "develop").trim(),
|
|
2213
|
+
source: input?.source,
|
|
2214
|
+
path: String(input?.path || "").trim()
|
|
2215
|
+
}));
|
|
2216
|
+
var hydrateFromGit = /* @__PURE__ */ command("unchecked", async (input) => {
|
|
2217
|
+
try {
|
|
2218
|
+
return hydrateReviewPayload({
|
|
2219
|
+
repo: String(input?.repo || "").trim(),
|
|
2220
|
+
branch: String(input?.branch || "").trim(),
|
|
2221
|
+
base: String(input?.base || "develop").trim(),
|
|
2222
|
+
source: input?.source,
|
|
2223
|
+
payload: input?.payload
|
|
2224
|
+
});
|
|
2225
|
+
} catch (err) {
|
|
2226
|
+
fail(err);
|
|
2227
|
+
}
|
|
2228
|
+
});
|
|
2229
|
+
init_remote_functions(git_remote_exports, "src/lib/report/git.remote.ts", "1g4ayag");
|
|
2230
|
+
for (const [name, fn] of Object.entries(git_remote_exports)) {
|
|
2231
|
+
fn.__.id = "1g4ayag/" + name;
|
|
2232
|
+
fn.__.name = name;
|
|
2233
|
+
}
|
|
2234
|
+
|
|
2235
|
+
//#region \0sveltekit-remote:1g4ayag
|
|
2236
|
+
var _sveltekit_remote_1g4ayag_default = git_remote_exports;
|
|
2237
|
+
|
|
2238
|
+
export { _sveltekit_remote_1g4ayag_default as default };
|
|
2239
|
+
//# sourceMappingURL=remote-1g4ayag-ipWXvkjB.js.map
|