@pathmode/mcp-server 1.20.0 → 1.20.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1034 -185
- package/dist/packages/intentspec-format/serializeIntentMd.d.ts +24 -0
- package/dist/packages/intentspec-format/serializeIntentMd.d.ts.map +1 -1
- package/dist/packages/mcp-server/src/adopt.d.ts.map +1 -1
- package/dist/packages/mcp-server/src/api-client.d.ts +24 -0
- package/dist/packages/mcp-server/src/api-client.d.ts.map +1 -1
- package/dist/packages/mcp-server/src/intent-compiler.d.ts +2 -0
- package/dist/packages/mcp-server/src/intent-compiler.d.ts.map +1 -1
- package/dist/packages/mcp-server/src/local-reader.d.ts +5 -1
- package/dist/packages/mcp-server/src/local-reader.d.ts.map +1 -1
- package/dist/packages/mcp-server/src/measurement-schema.d.ts +267 -28
- package/dist/packages/mcp-server/src/measurement-schema.d.ts.map +1 -1
- package/dist/packages/mcp-server/src/project-config.d.ts +0 -1
- package/dist/packages/mcp-server/src/project-config.d.ts.map +1 -1
- package/manifest.json +1 -1
- package/package.json +9 -1
package/dist/index.js
CHANGED
|
@@ -35783,6 +35783,7 @@ function State(input, options) {
|
|
|
35783
35783
|
this.legacy = options['legacy'] || false;
|
|
35784
35784
|
this.json = options['json'] || false;
|
|
35785
35785
|
this.listener = options['listener'] || null;
|
|
35786
|
+
this.maxTotalMergeKeys = typeof options['maxTotalMergeKeys'] === 'number' ? options['maxTotalMergeKeys'] : 10000;
|
|
35786
35787
|
|
|
35787
35788
|
this.implicitTypes = this.schema.compiledImplicit;
|
|
35788
35789
|
this.typeMap = this.schema.compiledTypeMap;
|
|
@@ -35792,6 +35793,7 @@ function State(input, options) {
|
|
|
35792
35793
|
this.line = 0;
|
|
35793
35794
|
this.lineStart = 0;
|
|
35794
35795
|
this.lineIndent = 0;
|
|
35796
|
+
this.totalMergeKeys = 0;
|
|
35795
35797
|
|
|
35796
35798
|
this.documents = [];
|
|
35797
35799
|
|
|
@@ -35910,6 +35912,14 @@ function captureSegment(state, start, end, checkJson) {
|
|
|
35910
35912
|
}
|
|
35911
35913
|
}
|
|
35912
35914
|
|
|
35915
|
+
function chargeMergeWork(state) {
|
|
35916
|
+
state.totalMergeKeys += 1;
|
|
35917
|
+
|
|
35918
|
+
if (state.maxTotalMergeKeys !== -1 && state.totalMergeKeys > state.maxTotalMergeKeys) {
|
|
35919
|
+
throwError(state, 'merge keys exceeded maxTotalMergeKeys (' + state.maxTotalMergeKeys + ')');
|
|
35920
|
+
}
|
|
35921
|
+
}
|
|
35922
|
+
|
|
35913
35923
|
function mergeMappings(state, destination, source, overridableKeys) {
|
|
35914
35924
|
var sourceKeys, key, index, quantity;
|
|
35915
35925
|
|
|
@@ -35917,11 +35927,16 @@ function mergeMappings(state, destination, source, overridableKeys) {
|
|
|
35917
35927
|
throwError(state, 'cannot merge mappings; the provided source object is unacceptable');
|
|
35918
35928
|
}
|
|
35919
35929
|
|
|
35930
|
+
// Count the source mapping itself to bound sequences of empty mappings.
|
|
35931
|
+
chargeMergeWork(state);
|
|
35932
|
+
|
|
35920
35933
|
sourceKeys = Object.keys(source);
|
|
35921
35934
|
|
|
35922
35935
|
for (index = 0, quantity = sourceKeys.length; index < quantity; index += 1) {
|
|
35923
35936
|
key = sourceKeys[index];
|
|
35924
35937
|
|
|
35938
|
+
chargeMergeWork(state);
|
|
35939
|
+
|
|
35925
35940
|
if (!_hasOwnProperty.call(destination, key)) {
|
|
35926
35941
|
setProperty(destination, key, source[key]);
|
|
35927
35942
|
overridableKeys[key] = true;
|
|
@@ -35965,6 +35980,10 @@ function storeMappingPair(state, _result, overridableKeys, keyTag, keyNode, valu
|
|
|
35965
35980
|
|
|
35966
35981
|
if (keyTag === 'tag:yaml.org,2002:merge') {
|
|
35967
35982
|
if (Array.isArray(valueNode)) {
|
|
35983
|
+
if (valueNode.length > 100) {
|
|
35984
|
+
throwError(state, 'abnormal merge sequence size');
|
|
35985
|
+
}
|
|
35986
|
+
|
|
35968
35987
|
for (index = 0, quantity = valueNode.length; index < quantity; index += 1) {
|
|
35969
35988
|
mergeMappings(state, _result, valueNode[index], overridableKeys);
|
|
35970
35989
|
}
|
|
@@ -38504,7 +38523,7 @@ var _toString = Object.prototype.toString;
|
|
|
38504
38523
|
function resolveYamlOmap(data) {
|
|
38505
38524
|
if (data === null) return true;
|
|
38506
38525
|
|
|
38507
|
-
var objectKeys =
|
|
38526
|
+
var objectKeys = {}, index, length, pair, pairKey, pairHasKey,
|
|
38508
38527
|
object = data;
|
|
38509
38528
|
|
|
38510
38529
|
for (index = 0, length = object.length; index < length; index += 1) {
|
|
@@ -38522,8 +38541,8 @@ function resolveYamlOmap(data) {
|
|
|
38522
38541
|
|
|
38523
38542
|
if (!pairHasKey) return false;
|
|
38524
38543
|
|
|
38525
|
-
if (
|
|
38526
|
-
|
|
38544
|
+
if (_hasOwnProperty.call(objectKeys, pairKey)) return false;
|
|
38545
|
+
Object.defineProperty(objectKeys, pairKey, { value: true });
|
|
38527
38546
|
}
|
|
38528
38547
|
|
|
38529
38548
|
return true;
|
|
@@ -39199,6 +39218,7 @@ module.exports = function(str) {
|
|
|
39199
39218
|
* the results in as plain data.
|
|
39200
39219
|
*/
|
|
39201
39220
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
39221
|
+
exports.renderOutcomeMeasurementDefinition = renderOutcomeMeasurementDefinition;
|
|
39202
39222
|
exports.toSerializableChecks = toSerializableChecks;
|
|
39203
39223
|
exports.renderAuthorizationGateText = renderAuthorizationGateText;
|
|
39204
39224
|
exports.renderConfirmationsBody = renderConfirmationsBody;
|
|
@@ -39226,6 +39246,24 @@ function yamlScalar(value) {
|
|
|
39226
39246
|
function outcomeOf(o) {
|
|
39227
39247
|
return typeof o === 'string' ? { text: o } : o;
|
|
39228
39248
|
}
|
|
39249
|
+
/** Compact, provider-neutral wording shared by every agent-facing export. */
|
|
39250
|
+
function renderOutcomeMeasurementDefinition(measurement) {
|
|
39251
|
+
if (!measurement)
|
|
39252
|
+
return '';
|
|
39253
|
+
const { source, expectation, window } = measurement;
|
|
39254
|
+
const target = `${expectation.operator} ${expectation.target}${expectation.unit ? ` ${expectation.unit}` : ''}`;
|
|
39255
|
+
const windowValue = window.kind === 'rolling'
|
|
39256
|
+
? `${window.kind} ${window.duration}`
|
|
39257
|
+
: window.kind === 'fixed'
|
|
39258
|
+
? `${window.kind} ${window.start}..${window.end}`
|
|
39259
|
+
: window.kind;
|
|
39260
|
+
return [
|
|
39261
|
+
`measure via ${source.provider} ${source.queryRef.kind}:${source.queryRef.id}`,
|
|
39262
|
+
measurement.resultSelector ? `select ${measurement.resultSelector}` : '',
|
|
39263
|
+
`target ${target}`,
|
|
39264
|
+
`window ${windowValue}`,
|
|
39265
|
+
].filter(Boolean).join('; ');
|
|
39266
|
+
}
|
|
39229
39267
|
function constraintOf(c) {
|
|
39230
39268
|
return typeof c === 'string' ? { text: c } : c;
|
|
39231
39269
|
}
|
|
@@ -39337,9 +39375,14 @@ function renderConfirmationsBody(records) {
|
|
|
39337
39375
|
*/
|
|
39338
39376
|
const FRONTMATTER_ORDER = [
|
|
39339
39377
|
'id', 'version', 'status', 'readiness', 'source', 'specVersion',
|
|
39340
|
-
'origin', 'authorization', 'authorizationNote', '
|
|
39378
|
+
'origin', 'authorization', 'authorizationNote', 'outcomeMeasurements',
|
|
39379
|
+
'evidence', 'space', 'severity', 'created', 'updated',
|
|
39341
39380
|
];
|
|
39342
39381
|
function serializeIntentMd(spec, opts = {}) {
|
|
39382
|
+
const outcomeMeasurements = (spec.outcomes || [])
|
|
39383
|
+
.map(outcomeOf)
|
|
39384
|
+
.map((outcome, index) => outcome.measurement ? { outcome: index, ...outcome.measurement } : null)
|
|
39385
|
+
.filter((entry) => entry !== null);
|
|
39343
39386
|
const values = {
|
|
39344
39387
|
id: spec.id,
|
|
39345
39388
|
version: opts.version && opts.version >= 1 ? opts.version : 1,
|
|
@@ -39358,6 +39401,9 @@ function serializeIntentMd(spec, opts = {}) {
|
|
|
39358
39401
|
authorizationNote: opts.origin === 'agent' && nonEmpty(opts.authorizationNote)
|
|
39359
39402
|
? opts.authorizationNote.replace(/\s+/g, ' ').trim()
|
|
39360
39403
|
: undefined,
|
|
39404
|
+
// Body outcomes stay plain, interoperable list items. The optional recipe is a generic
|
|
39405
|
+
// extension in frontmatter, keyed by outcome position so old readers simply ignore it.
|
|
39406
|
+
outcomeMeasurements: outcomeMeasurements.length ? outcomeMeasurements : undefined,
|
|
39361
39407
|
evidence: opts.evidence?.length || undefined,
|
|
39362
39408
|
space: opts.space?.name,
|
|
39363
39409
|
severity: opts.severity,
|
|
@@ -39369,7 +39415,10 @@ function serializeIntentMd(spec, opts = {}) {
|
|
|
39369
39415
|
// crash rather than an omitted key.
|
|
39370
39416
|
const yamlLines = FRONTMATTER_ORDER
|
|
39371
39417
|
.filter(key => values[key] !== undefined && values[key] !== null && values[key] !== '')
|
|
39372
|
-
.map(key =>
|
|
39418
|
+
.map(key => {
|
|
39419
|
+
const value = values[key];
|
|
39420
|
+
return `${key}: ${typeof value === 'object' ? JSON.stringify(value) : yamlScalar(value)}`;
|
|
39421
|
+
})
|
|
39373
39422
|
.join('\n');
|
|
39374
39423
|
const sections = ['---', yamlLines, '---', ''];
|
|
39375
39424
|
// Directly under the frontmatter: the `authorization` key is machine-readable, but the body
|
|
@@ -39615,6 +39664,9 @@ async function exchangeCode(code, origin, existing) {
|
|
|
39615
39664
|
}
|
|
39616
39665
|
/** Add cloud identity to the existing frontmatter without reformatting the user's spec body. */
|
|
39617
39666
|
function patchAdoptedIntentFrontmatter(filePath, values) {
|
|
39667
|
+
if (fs_1.default.lstatSync(filePath).isSymbolicLink()) {
|
|
39668
|
+
throw new Error('Refusing to update a symbolic-link intent.md. Use a regular file inside the repository.');
|
|
39669
|
+
}
|
|
39618
39670
|
const raw = fs_1.default.readFileSync(filePath, 'utf8');
|
|
39619
39671
|
const newline = raw.includes('\r\n') ? '\r\n' : '\n';
|
|
39620
39672
|
if (!raw.startsWith(`---${newline}`)) {
|
|
@@ -39646,6 +39698,9 @@ async function runAdopt(argv = process.argv) {
|
|
|
39646
39698
|
if (!code)
|
|
39647
39699
|
throw new Error('Usage: npx @pathmode/mcp-server@latest adopt pm_adopt_…');
|
|
39648
39700
|
const filePath = path_1.default.join(process.cwd(), 'intent.md');
|
|
39701
|
+
if (fs_1.default.existsSync(filePath) && fs_1.default.lstatSync(filePath).isSymbolicLink()) {
|
|
39702
|
+
throw new Error('Refusing to adopt a symbolic-link intent.md. Use a regular file inside the repository.');
|
|
39703
|
+
}
|
|
39649
39704
|
const intent = (0, local_reader_1.readIntentFile)(filePath);
|
|
39650
39705
|
const meta = (0, local_reader_1.readIntentMeta)(filePath);
|
|
39651
39706
|
if (!intent || !meta?.id) {
|
|
@@ -39654,7 +39709,7 @@ async function runAdopt(argv = process.argv) {
|
|
|
39654
39709
|
console.log('');
|
|
39655
39710
|
console.log('Pathmode repo adoption');
|
|
39656
39711
|
console.log('──────────────────────');
|
|
39657
|
-
console.log(`
|
|
39712
|
+
console.log(` ✓ Read intent.md — ${intent.title}`);
|
|
39658
39713
|
process.stdout.write(' Connecting…');
|
|
39659
39714
|
const origin = apiOrigin(argv);
|
|
39660
39715
|
const current = (0, api_client_1.loadConfig)();
|
|
@@ -39674,6 +39729,8 @@ async function runAdopt(argv = process.argv) {
|
|
|
39674
39729
|
throw error;
|
|
39675
39730
|
}
|
|
39676
39731
|
}
|
|
39732
|
+
console.log(' ✓');
|
|
39733
|
+
console.log(` ✓ Destination confirmed — ${exchange.workspaceName} / ${exchange.productName}`);
|
|
39677
39734
|
const apiKey = exchange.apiKey || current?.apiKey;
|
|
39678
39735
|
if (!apiKey)
|
|
39679
39736
|
throw new Error('No repository credential is available after adoption exchange.');
|
|
@@ -39686,7 +39743,10 @@ async function runAdopt(argv = process.argv) {
|
|
|
39686
39743
|
// credential remains recoverable from this repo without minting a duplicate key.
|
|
39687
39744
|
(0, project_config_1.saveProjectConnection)(config);
|
|
39688
39745
|
(0, project_config_1.ensureProjectMcpConfig)();
|
|
39746
|
+
console.log(' ✓ Credential stored outside the repository');
|
|
39747
|
+
console.log(' ✓ Pathmode project entries added without the credential');
|
|
39689
39748
|
const client = new api_client_1.PathmodeClient(config);
|
|
39749
|
+
console.log(' • Creating and linking the hosted intent…');
|
|
39690
39750
|
const pushed = await (0, push_spec_1.pushSpec)({
|
|
39691
39751
|
client,
|
|
39692
39752
|
id: meta.id,
|
|
@@ -39717,11 +39777,17 @@ async function runAdopt(argv = process.argv) {
|
|
|
39717
39777
|
if (!pushed.ok)
|
|
39718
39778
|
throw new Error(pushed.error);
|
|
39719
39779
|
await client.completeRepoOnboarding(exchange.sessionId, pushed.canonicalId);
|
|
39720
|
-
console.log('
|
|
39721
|
-
console.log(` Adopted into ${exchange.workspaceName} / ${exchange.productName}`);
|
|
39780
|
+
console.log(' ✓ Hosted intent linked');
|
|
39722
39781
|
console.log(` ${pushed.sourceUrl}`);
|
|
39723
39782
|
console.log('');
|
|
39724
|
-
console.log('
|
|
39783
|
+
console.log('What changed');
|
|
39784
|
+
console.log(' • intent.md remains authoritative; only its cloud identity frontmatter changed');
|
|
39785
|
+
console.log(' • Project connection files contain no Pathmode credential');
|
|
39786
|
+
console.log(' • The hosted record is ready for human review and authorization');
|
|
39787
|
+
console.log('');
|
|
39788
|
+
console.log('Next');
|
|
39789
|
+
console.log(' Keep the browser page open; it finishes automatically.');
|
|
39790
|
+
console.log(' Restart your coding tool once so it reloads .mcp.json and discovers Pathmode.');
|
|
39725
39791
|
console.log('');
|
|
39726
39792
|
}
|
|
39727
39793
|
|
|
@@ -40428,6 +40494,9 @@ function getPriorityLabel(o) {
|
|
|
40428
40494
|
return '';
|
|
40429
40495
|
return `[${o.priority.toUpperCase()}] `;
|
|
40430
40496
|
}
|
|
40497
|
+
function getOutcomeMeasurement(o) {
|
|
40498
|
+
return typeof o === 'string' ? '' : (0, serializeIntentMd_1.renderOutcomeMeasurementDefinition)(o.measurement);
|
|
40499
|
+
}
|
|
40431
40500
|
function getLiveQualityCheckPromptBlock() {
|
|
40432
40501
|
return `LIVE QUALITY CHECKS:
|
|
40433
40502
|
- After EVERY meaningful user answer, silently update your current draft and run a quality pass before asking the next question.
|
|
@@ -40614,6 +40683,9 @@ function formatCursorRules(spec) {
|
|
|
40614
40683
|
sections.push('Your implementation MUST satisfy ALL of these:');
|
|
40615
40684
|
for (const outcome of spec.outcomes) {
|
|
40616
40685
|
sections.push(`- ${getPriorityLabel(outcome)}${getOutcomeText(outcome)}`);
|
|
40686
|
+
const measurement = getOutcomeMeasurement(outcome);
|
|
40687
|
+
if (measurement)
|
|
40688
|
+
sections.push(` - measurement: ${measurement}`);
|
|
40617
40689
|
}
|
|
40618
40690
|
}
|
|
40619
40691
|
if (spec.scope?.inScope?.length || spec.scope?.outOfScope?.length) {
|
|
@@ -40694,7 +40766,10 @@ function formatClaudeMdSection(spec) {
|
|
|
40694
40766
|
sections.push(...decisionLines(spec.decisions, '**Decisions**:'));
|
|
40695
40767
|
if (spec.outcomes?.length) {
|
|
40696
40768
|
sections.push('**Outcomes**:');
|
|
40697
|
-
sections.push(spec.outcomes.map(o =>
|
|
40769
|
+
sections.push(spec.outcomes.map(o => {
|
|
40770
|
+
const measurement = getOutcomeMeasurement(o);
|
|
40771
|
+
return `- [ ] ${getPriorityLabel(o)}${getOutcomeText(o)}${measurement ? `\n - measurement: ${measurement}` : ''}`;
|
|
40772
|
+
}).join('\n'));
|
|
40698
40773
|
}
|
|
40699
40774
|
if (spec.scope?.inScope?.length || spec.scope?.outOfScope?.length) {
|
|
40700
40775
|
const scopeParts = ['**Scope**:'];
|
|
@@ -40849,6 +40924,9 @@ function buildGraderRubric(spec, opts = {}) {
|
|
|
40849
40924
|
for (const o of outcomes) {
|
|
40850
40925
|
const bar = priorityToBar(getOutcomePriority(o));
|
|
40851
40926
|
sections.push(`${i}. [${bar}] ${getOutcomeText(o)}`);
|
|
40927
|
+
const measurement = getOutcomeMeasurement(o);
|
|
40928
|
+
if (measurement)
|
|
40929
|
+
sections.push(` - Production signal: ${measurement}`);
|
|
40852
40930
|
sections.push(' - Evidence: point to the specific value, state, or test result in the artifact that proves this. If you cannot, mark FAIL.');
|
|
40853
40931
|
i++;
|
|
40854
40932
|
}
|
|
@@ -41054,6 +41132,7 @@ exports.stripHtmlComments = stripHtmlComments;
|
|
|
41054
41132
|
const fs_1 = __importDefault(__nccwpck_require__(9896));
|
|
41055
41133
|
const path_1 = __importDefault(__nccwpck_require__(6928));
|
|
41056
41134
|
const gray_matter_1 = __importDefault(__nccwpck_require__(9599));
|
|
41135
|
+
const measurement_schema_1 = __nccwpck_require__(1635);
|
|
41057
41136
|
const CONFIRMATION_HEADER_RE = /^\*\*(objective|outcomes)\*\*\s*[—–-]\s*(confirmed|waived)\s+by\s+(agent|human)\s*$/i;
|
|
41058
41137
|
const CONFIRMATION_FIELD_RE = /^\s*[-*]\s+(actor|problem|outcome|observable|reason|anchor|at)\s*:\s*(.+)$/i;
|
|
41059
41138
|
/**
|
|
@@ -41181,6 +41260,22 @@ function preferSection(fromSection, fromFrontmatter) {
|
|
|
41181
41260
|
.map((v) => v.trim())
|
|
41182
41261
|
.filter(Boolean);
|
|
41183
41262
|
}
|
|
41263
|
+
function attachOutcomeMeasurements(outcomes, rawEntries) {
|
|
41264
|
+
if (!Array.isArray(rawEntries))
|
|
41265
|
+
return outcomes;
|
|
41266
|
+
const definitions = new Map();
|
|
41267
|
+
for (const raw of rawEntries) {
|
|
41268
|
+
const parsed = measurement_schema_1.outcomeMeasurementEntryShape.safeParse(raw);
|
|
41269
|
+
if (!parsed.success || parsed.data.outcome >= outcomes.length)
|
|
41270
|
+
continue;
|
|
41271
|
+
const { outcome, ...definition } = parsed.data;
|
|
41272
|
+
definitions.set(outcome, definition);
|
|
41273
|
+
}
|
|
41274
|
+
return outcomes.map((text, index) => {
|
|
41275
|
+
const measurement = definitions.get(index);
|
|
41276
|
+
return measurement ? { text, measurement } : text;
|
|
41277
|
+
});
|
|
41278
|
+
}
|
|
41184
41279
|
/** Coerce a structured frontmatter entry ({text}, {description}) to its text, else ''. */
|
|
41185
41280
|
function coerceItemText(v) {
|
|
41186
41281
|
if (!v || typeof v !== 'object')
|
|
@@ -41233,7 +41328,7 @@ function parseIntentMarkdown(content, fallbackId = 'intent') {
|
|
|
41233
41328
|
title: data.title || data.userGoal || extractTitle(body) || 'Untitled Intent',
|
|
41234
41329
|
stageName: data.stage || undefined,
|
|
41235
41330
|
severity: data.severity || undefined,
|
|
41236
|
-
outcomes: preferSection(extractListSection(sections, 'Outcomes'), data.outcomes),
|
|
41331
|
+
outcomes: attachOutcomeMeasurements(preferSection(extractListSection(sections, 'Outcomes'), data.outcomes), data.outcomeMeasurements),
|
|
41237
41332
|
decisions: extractDecisions(sections),
|
|
41238
41333
|
constraints: preferSection(extractListSection(sections, 'Constraints'), data.constraints),
|
|
41239
41334
|
edgeCases: (() => { const fromBody = extractEdgeCases(sections); return fromBody.length ? fromBody : frontmatterEdgeCases(data.edgeCases); })(),
|
|
@@ -41490,9 +41585,11 @@ function extractVerification(sections) {
|
|
|
41490
41585
|
const result = {};
|
|
41491
41586
|
let currentKind = null;
|
|
41492
41587
|
let currentLegacy = null;
|
|
41588
|
+
let sawLabel = false;
|
|
41493
41589
|
for (const line of lines) {
|
|
41494
41590
|
const label = parseVerificationLabel(line);
|
|
41495
41591
|
if (label) {
|
|
41592
|
+
sawLabel = true;
|
|
41496
41593
|
const kind = VERIFICATION_LABEL_TO_KIND[label.toLowerCase()];
|
|
41497
41594
|
if (kind) {
|
|
41498
41595
|
currentKind = kind;
|
|
@@ -41531,6 +41628,11 @@ function extractVerification(sections) {
|
|
|
41531
41628
|
else if (currentLegacy) {
|
|
41532
41629
|
result[currentLegacy].push(text);
|
|
41533
41630
|
}
|
|
41631
|
+
else if (!sawLabel) {
|
|
41632
|
+
// IntentSpec labels are optional. Treat an ungrouped list as manual checks so a valid
|
|
41633
|
+
// flat `## Verification` section survives adoption instead of silently disappearing.
|
|
41634
|
+
(result.manualChecks ||= []).push(text);
|
|
41635
|
+
}
|
|
41534
41636
|
}
|
|
41535
41637
|
return result;
|
|
41536
41638
|
}
|
|
@@ -41565,7 +41667,7 @@ function hasVerificationContent(v) {
|
|
|
41565
41667
|
* units and source labels.
|
|
41566
41668
|
*/
|
|
41567
41669
|
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
|
41568
|
-
exports.recordOutcomeMeasurementInputSchema = exports.provenanceShape = exports.expectationShape = exports.windowShape = exports.queryRefShape = exports.windowKindEnum = exports.comparatorEnum = void 0;
|
|
41670
|
+
exports.recordOutcomeMeasurementInputSchema = exports.provenanceShape = exports.outcomeMeasurementEntryShape = exports.outcomeMeasurementDefinitionShape = exports.expectationShape = exports.windowShape = exports.queryRefShape = exports.windowKindEnum = exports.comparatorEnum = void 0;
|
|
41569
41671
|
exports.outcomeSelectorError = outcomeSelectorError;
|
|
41570
41672
|
const zod_1 = __nccwpck_require__(924);
|
|
41571
41673
|
const MAX_TEXT = 500;
|
|
@@ -41602,6 +41704,19 @@ exports.expectationShape = zod_1.z.object({
|
|
|
41602
41704
|
target: zod_1.z.number().finite(),
|
|
41603
41705
|
unit: zod_1.z.string().trim().max(MAX_UNIT).optional(),
|
|
41604
41706
|
});
|
|
41707
|
+
/** Mirrors the optional query recipe stored on a StructuredOutcome. */
|
|
41708
|
+
exports.outcomeMeasurementDefinitionShape = zod_1.z.object({
|
|
41709
|
+
source: zod_1.z.object({
|
|
41710
|
+
provider: zod_1.z.string().trim().min(1).max(MAX_SHORT).describe('System that owns the saved query, e.g. "posthog"'),
|
|
41711
|
+
queryRef: exports.queryRefShape,
|
|
41712
|
+
}),
|
|
41713
|
+
resultSelector: zod_1.z.string().trim().min(1).max(MAX_TEXT).optional().describe('Optional path into a compound provider response'),
|
|
41714
|
+
expectation: exports.expectationShape,
|
|
41715
|
+
window: exports.windowShape,
|
|
41716
|
+
});
|
|
41717
|
+
exports.outcomeMeasurementEntryShape = exports.outcomeMeasurementDefinitionShape.extend({
|
|
41718
|
+
outcome: zod_1.z.number().int().nonnegative(),
|
|
41719
|
+
});
|
|
41605
41720
|
exports.provenanceShape = zod_1.z.object({
|
|
41606
41721
|
provider: zod_1.z.string().trim().min(1).max(MAX_SHORT).describe('System the number came from, e.g. "posthog"'),
|
|
41607
41722
|
queryRef: exports.queryRefShape,
|
|
@@ -42216,6 +42331,7 @@ exports.ensureProjectMcpConfig = ensureProjectMcpConfig;
|
|
|
42216
42331
|
const fs_1 = __importDefault(__nccwpck_require__(9896));
|
|
42217
42332
|
const os_1 = __importDefault(__nccwpck_require__(857));
|
|
42218
42333
|
const path_1 = __importDefault(__nccwpck_require__(6928));
|
|
42334
|
+
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
42219
42335
|
const homeWorkspaceFile = (workspaceId, homeDir = os_1.default.homedir()) => path_1.default.join(homeDir, '.pathmode', 'workspaces', `${workspaceId}.json`);
|
|
42220
42336
|
function projectBindingFile(cwd = process.cwd()) {
|
|
42221
42337
|
return path_1.default.join(cwd, '.pathmode', 'config.json');
|
|
@@ -42228,32 +42344,73 @@ function readJson(filePath) {
|
|
|
42228
42344
|
return null;
|
|
42229
42345
|
}
|
|
42230
42346
|
}
|
|
42347
|
+
function normalizeApiUrl(raw) {
|
|
42348
|
+
if (typeof raw !== 'string' || !raw.trim())
|
|
42349
|
+
return null;
|
|
42350
|
+
try {
|
|
42351
|
+
const url = new URL(raw.trim());
|
|
42352
|
+
if (url.protocol !== 'https:' && url.protocol !== 'http:')
|
|
42353
|
+
return null;
|
|
42354
|
+
return url.origin;
|
|
42355
|
+
}
|
|
42356
|
+
catch {
|
|
42357
|
+
return null;
|
|
42358
|
+
}
|
|
42359
|
+
}
|
|
42360
|
+
/** Repository files are attacker-controlled when a project or branch is first opened. */
|
|
42361
|
+
function assertSafeProjectWrite(cwd, filePath) {
|
|
42362
|
+
const root = path_1.default.resolve(cwd);
|
|
42363
|
+
const target = path_1.default.resolve(filePath);
|
|
42364
|
+
const relative = path_1.default.relative(root, target);
|
|
42365
|
+
if (!relative || relative.startsWith('..') || path_1.default.isAbsolute(relative)) {
|
|
42366
|
+
throw new Error(`Refusing to write outside the repository: ${filePath}`);
|
|
42367
|
+
}
|
|
42368
|
+
let current = root;
|
|
42369
|
+
for (const segment of relative.split(path_1.default.sep)) {
|
|
42370
|
+
current = path_1.default.join(current, segment);
|
|
42371
|
+
if (fs_1.default.existsSync(current) && fs_1.default.lstatSync(current).isSymbolicLink()) {
|
|
42372
|
+
throw new Error(`Refusing to write through a symbolic link: ${current}`);
|
|
42373
|
+
}
|
|
42374
|
+
}
|
|
42375
|
+
}
|
|
42376
|
+
function writeProjectFile(cwd, filePath, contents) {
|
|
42377
|
+
assertSafeProjectWrite(cwd, filePath);
|
|
42378
|
+
fs_1.default.mkdirSync(path_1.default.dirname(filePath), { recursive: true });
|
|
42379
|
+
// Re-check after creating a missing parent so a checked-in symlink is never followed.
|
|
42380
|
+
assertSafeProjectWrite(cwd, filePath);
|
|
42381
|
+
fs_1.default.writeFileSync(filePath, contents, 'utf8');
|
|
42382
|
+
}
|
|
42231
42383
|
function loadProjectConnection(cwd = process.cwd(), homeDir = os_1.default.homedir()) {
|
|
42232
42384
|
const binding = readJson(projectBindingFile(cwd));
|
|
42233
42385
|
const workspaceId = typeof binding?.workspaceId === 'string' ? binding.workspaceId.trim() : '';
|
|
42234
|
-
if (!workspaceId)
|
|
42386
|
+
if (!UUID_RE.test(workspaceId))
|
|
42235
42387
|
return null;
|
|
42236
42388
|
const secret = readJson(homeWorkspaceFile(workspaceId, homeDir));
|
|
42237
42389
|
const apiKey = typeof secret?.apiKey === 'string' ? secret.apiKey.trim() : '';
|
|
42238
42390
|
if (!apiKey)
|
|
42239
42391
|
return null;
|
|
42240
|
-
|
|
42241
|
-
|
|
42242
|
-
|
|
42392
|
+
// The repository chooses only which workspace to use. The protected home secret
|
|
42393
|
+
// chooses where that credential may be sent, so a malicious branch cannot redirect it.
|
|
42394
|
+
const apiUrl = normalizeApiUrl(secret?.apiUrl);
|
|
42395
|
+
if (!apiUrl)
|
|
42396
|
+
return null;
|
|
42243
42397
|
return { apiKey, apiUrl, workspaceId };
|
|
42244
42398
|
}
|
|
42245
42399
|
function saveProjectConnection(config, cwd = process.cwd(), homeDir = os_1.default.homedir()) {
|
|
42400
|
+
if (!UUID_RE.test(config.workspaceId))
|
|
42401
|
+
throw new Error('Cannot save a project connection with an invalid workspace id.');
|
|
42402
|
+
const apiUrl = normalizeApiUrl(config.apiUrl);
|
|
42403
|
+
if (!apiUrl)
|
|
42404
|
+
throw new Error('Cannot save a project connection with an invalid API URL.');
|
|
42246
42405
|
const bindingPath = projectBindingFile(cwd);
|
|
42247
|
-
|
|
42248
|
-
fs_1.default.writeFileSync(bindingPath, JSON.stringify({
|
|
42406
|
+
writeProjectFile(cwd, bindingPath, JSON.stringify({
|
|
42249
42407
|
workspaceId: config.workspaceId,
|
|
42250
|
-
|
|
42251
|
-
}, null, 2) + '\n', 'utf8');
|
|
42408
|
+
}, null, 2) + '\n');
|
|
42252
42409
|
const secretPath = homeWorkspaceFile(config.workspaceId, homeDir);
|
|
42253
42410
|
fs_1.default.mkdirSync(path_1.default.dirname(secretPath), { recursive: true });
|
|
42254
42411
|
fs_1.default.writeFileSync(secretPath, JSON.stringify({
|
|
42255
42412
|
apiKey: config.apiKey,
|
|
42256
|
-
apiUrl
|
|
42413
|
+
apiUrl,
|
|
42257
42414
|
}, null, 2) + '\n', { encoding: 'utf8', mode: 0o600 });
|
|
42258
42415
|
try {
|
|
42259
42416
|
fs_1.default.chmodSync(secretPath, 0o600);
|
|
@@ -42264,6 +42421,7 @@ function saveProjectConnection(config, cwd = process.cwd(), homeDir = os_1.defau
|
|
|
42264
42421
|
* from the project binding at runtime and is never written into a committable file. */
|
|
42265
42422
|
function ensureProjectMcpConfig(cwd = process.cwd()) {
|
|
42266
42423
|
const configPath = path_1.default.join(cwd, '.mcp.json');
|
|
42424
|
+
assertSafeProjectWrite(cwd, configPath);
|
|
42267
42425
|
const existing = fs_1.default.existsSync(configPath) ? readJson(configPath) : {};
|
|
42268
42426
|
if (!existing)
|
|
42269
42427
|
throw new Error(`${configPath} is not valid JSON; fix it before adopting.`);
|
|
@@ -42271,7 +42429,7 @@ function ensureProjectMcpConfig(cwd = process.cwd()) {
|
|
|
42271
42429
|
? existing.mcpServers
|
|
42272
42430
|
: {};
|
|
42273
42431
|
servers.pathmode = { command: 'npx', args: ['@pathmode/mcp-server'] };
|
|
42274
|
-
|
|
42432
|
+
writeProjectFile(cwd, configPath, JSON.stringify({ ...existing, mcpServers: servers }, null, 2) + '\n');
|
|
42275
42433
|
}
|
|
42276
42434
|
|
|
42277
42435
|
|
|
@@ -50043,9 +50201,24 @@ exports.AjvJsonSchemaValidator = AjvJsonSchemaValidator;
|
|
|
50043
50201
|
"use strict";
|
|
50044
50202
|
|
|
50045
50203
|
|
|
50046
|
-
const { normalizeIPv6, removeDotSegments, recomposeAuthority,
|
|
50204
|
+
const { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, serializePathEncoding, normalizeQueryFragmentEncoding, encodeQuery, encodeFragment, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = __nccwpck_require__(5077)
|
|
50047
50205
|
const { SCHEMES, getSchemeHandler } = __nccwpck_require__(5300)
|
|
50048
50206
|
|
|
50207
|
+
const VALID_SCHEME = /^[A-Za-z][A-Za-z0-9+.-]*$/u
|
|
50208
|
+
const MALFORMED_SCHEME_ERROR = 'URI scheme is malformed.'
|
|
50209
|
+
|
|
50210
|
+
/**
|
|
50211
|
+
* @param {string} scheme
|
|
50212
|
+
* @returns {string}
|
|
50213
|
+
*/
|
|
50214
|
+
function decodeValidScheme (scheme) {
|
|
50215
|
+
const decodedScheme = unescape(String(scheme))
|
|
50216
|
+
if (!VALID_SCHEME.test(decodedScheme)) {
|
|
50217
|
+
throw new TypeError(MALFORMED_SCHEME_ERROR)
|
|
50218
|
+
}
|
|
50219
|
+
return decodedScheme
|
|
50220
|
+
}
|
|
50221
|
+
|
|
50049
50222
|
/**
|
|
50050
50223
|
* @template {import('./types/index').URIComponent|string} T
|
|
50051
50224
|
* @param {T} uri
|
|
@@ -50054,7 +50227,7 @@ const { SCHEMES, getSchemeHandler } = __nccwpck_require__(5300)
|
|
|
50054
50227
|
*/
|
|
50055
50228
|
function normalize (uri, options) {
|
|
50056
50229
|
if (typeof uri === 'string') {
|
|
50057
|
-
uri = /** @type {T} */ (
|
|
50230
|
+
uri = /** @type {T} */ (normalizeString(uri, options))
|
|
50058
50231
|
} else if (typeof uri === 'object') {
|
|
50059
50232
|
uri = /** @type {T} */ (parse(serialize(uri, options), options))
|
|
50060
50233
|
}
|
|
@@ -50069,7 +50242,50 @@ function normalize (uri, options) {
|
|
|
50069
50242
|
*/
|
|
50070
50243
|
function resolve (baseURI, relativeURI, options) {
|
|
50071
50244
|
const schemelessOptions = options ? Object.assign({ scheme: 'null' }, options) : { scheme: 'null' }
|
|
50072
|
-
const
|
|
50245
|
+
const {
|
|
50246
|
+
parsed: baseParsed,
|
|
50247
|
+
malformedAuthorityOrPort: baseMalformed,
|
|
50248
|
+
malformedPercentEncoding: baseMalformedPercentEncoding,
|
|
50249
|
+
malformedSchemeSpecific: baseMalformedSchemeSpecific,
|
|
50250
|
+
malformedHost: baseMalformedHost,
|
|
50251
|
+
malformedScheme: baseMalformedScheme
|
|
50252
|
+
} = parseWithStatus(baseURI, schemelessOptions)
|
|
50253
|
+
const {
|
|
50254
|
+
parsed: relativeParsed,
|
|
50255
|
+
malformedAuthorityOrPort: relativeMalformed,
|
|
50256
|
+
malformedPercentEncoding: relativeMalformedPercentEncoding,
|
|
50257
|
+
malformedSchemeSpecific: relativeMalformedSchemeSpecific,
|
|
50258
|
+
malformedHost: relativeMalformedHost,
|
|
50259
|
+
malformedScheme: relativeMalformedScheme
|
|
50260
|
+
} = parseWithStatus(relativeURI, schemelessOptions)
|
|
50261
|
+
if (
|
|
50262
|
+
baseMalformed ||
|
|
50263
|
+
relativeMalformed ||
|
|
50264
|
+
baseMalformedPercentEncoding ||
|
|
50265
|
+
relativeMalformedPercentEncoding ||
|
|
50266
|
+
baseMalformedSchemeSpecific ||
|
|
50267
|
+
relativeMalformedSchemeSpecific ||
|
|
50268
|
+
baseMalformedHost ||
|
|
50269
|
+
relativeMalformedHost ||
|
|
50270
|
+
baseMalformedScheme ||
|
|
50271
|
+
relativeMalformedScheme
|
|
50272
|
+
) {
|
|
50273
|
+
throw new Error(baseParsed.error || relativeParsed.error || 'URI is malformed.')
|
|
50274
|
+
}
|
|
50275
|
+
const resolved = resolveComponent(baseParsed, relativeParsed, schemelessOptions, true)
|
|
50276
|
+
const resolvedSchemeHandler = getSchemeHandler((options && options.scheme) || resolved.scheme)
|
|
50277
|
+
const resolvedHost = resolved.host
|
|
50278
|
+
const resolvedHostIsIP = resolvedHost !== undefined && resolvedHost !== '' &&
|
|
50279
|
+
(isIPv4(resolvedHost) || normalizeIPv6(resolvedHost).isIPV6)
|
|
50280
|
+
canonicalizeHost(resolved, options || {}, resolvedSchemeHandler, resolvedHostIsIP)
|
|
50281
|
+
// Percent escapes in an ASCII reg-name are encoded data. The WHATWG hostname
|
|
50282
|
+
// parser can reject them even though fast-uri preserves them safely as RFC
|
|
50283
|
+
// 3986 data. A raw non-ASCII host must still fail closed if conversion fails.
|
|
50284
|
+
const encodedASCIIHost = resolvedHost && resolvedHost.indexOf('%') !== -1 &&
|
|
50285
|
+
!/\P{ASCII}/u.test(resolvedHost)
|
|
50286
|
+
if (resolved.error && !encodedASCIIHost) {
|
|
50287
|
+
throw new Error(resolved.error)
|
|
50288
|
+
}
|
|
50073
50289
|
schemelessOptions.skipEscape = true
|
|
50074
50290
|
return serialize(resolved, schemelessOptions)
|
|
50075
50291
|
}
|
|
@@ -50149,21 +50365,10 @@ function resolveComponent (base, relative, options, skipNormalization) {
|
|
|
50149
50365
|
* @returns {boolean}
|
|
50150
50366
|
*/
|
|
50151
50367
|
function equal (uriA, uriB, options) {
|
|
50152
|
-
|
|
50153
|
-
|
|
50154
|
-
uriA = serialize(normalizeComponentEncoding(parse(uriA, options), true), { ...options, skipEscape: true })
|
|
50155
|
-
} else if (typeof uriA === 'object') {
|
|
50156
|
-
uriA = serialize(normalizeComponentEncoding(uriA, true), { ...options, skipEscape: true })
|
|
50157
|
-
}
|
|
50368
|
+
const normalizedA = normalizeComparableURI(uriA, options)
|
|
50369
|
+
const normalizedB = normalizeComparableURI(uriB, options)
|
|
50158
50370
|
|
|
50159
|
-
|
|
50160
|
-
uriB = unescape(uriB)
|
|
50161
|
-
uriB = serialize(normalizeComponentEncoding(parse(uriB, options), true), { ...options, skipEscape: true })
|
|
50162
|
-
} else if (typeof uriB === 'object') {
|
|
50163
|
-
uriB = serialize(normalizeComponentEncoding(uriB, true), { ...options, skipEscape: true })
|
|
50164
|
-
}
|
|
50165
|
-
|
|
50166
|
-
return uriA.toLowerCase() === uriB.toLowerCase()
|
|
50371
|
+
return normalizedA !== undefined && normalizedB !== undefined && normalizedA === normalizedB
|
|
50167
50372
|
}
|
|
50168
50373
|
|
|
50169
50374
|
/**
|
|
@@ -50191,25 +50396,30 @@ function serialize (cmpts, opts) {
|
|
|
50191
50396
|
const options = Object.assign({}, opts)
|
|
50192
50397
|
const uriTokens = []
|
|
50193
50398
|
|
|
50399
|
+
if (component.scheme) {
|
|
50400
|
+
component.scheme = decodeValidScheme(component.scheme)
|
|
50401
|
+
}
|
|
50402
|
+
|
|
50194
50403
|
// find scheme handler
|
|
50195
50404
|
const schemeHandler = getSchemeHandler(options.scheme || component.scheme)
|
|
50196
50405
|
|
|
50197
50406
|
// perform scheme specific serialization
|
|
50198
50407
|
if (schemeHandler && schemeHandler.serialize) schemeHandler.serialize(component, options)
|
|
50199
50408
|
|
|
50409
|
+
const hasAuthority = component.userinfo !== undefined || component.host !== undefined || component.port !== undefined
|
|
50410
|
+
const pathNoScheme = !options.skipEscape && component.scheme === undefined && !hasAuthority
|
|
50411
|
+
|
|
50200
50412
|
if (component.path !== undefined) {
|
|
50201
50413
|
if (!options.skipEscape) {
|
|
50202
|
-
component.path =
|
|
50203
|
-
|
|
50204
|
-
if (component.scheme !== undefined) {
|
|
50205
|
-
component.path = component.path.split('%3A').join(':')
|
|
50206
|
-
}
|
|
50414
|
+
component.path = serializePathEncoding(component.path, pathNoScheme)
|
|
50207
50415
|
} else {
|
|
50208
|
-
component.path =
|
|
50416
|
+
component.path = normalizePercentEncoding(component.path)
|
|
50209
50417
|
}
|
|
50210
50418
|
}
|
|
50211
50419
|
|
|
50212
50420
|
if (options.reference !== 'suffix' && component.scheme) {
|
|
50421
|
+
// Scheme handlers may replace the scheme during serialization.
|
|
50422
|
+
component.scheme = decodeValidScheme(component.scheme)
|
|
50213
50423
|
uriTokens.push(component.scheme, ':')
|
|
50214
50424
|
}
|
|
50215
50425
|
|
|
@@ -50232,6 +50442,13 @@ function serialize (cmpts, opts) {
|
|
|
50232
50442
|
s = removeDotSegments(s)
|
|
50233
50443
|
}
|
|
50234
50444
|
|
|
50445
|
+
// Dot-segment removal can expose a colon that was not originally in the
|
|
50446
|
+
// first segment (for example, "./a:b"). Reapply path-noscheme encoding so
|
|
50447
|
+
// the serialized relative reference cannot be reparsed as a URI scheme.
|
|
50448
|
+
if (pathNoScheme) {
|
|
50449
|
+
s = serializePathEncoding(s, true)
|
|
50450
|
+
}
|
|
50451
|
+
|
|
50235
50452
|
if (
|
|
50236
50453
|
authority === undefined &&
|
|
50237
50454
|
s[0] === '/' &&
|
|
@@ -50245,23 +50462,117 @@ function serialize (cmpts, opts) {
|
|
|
50245
50462
|
}
|
|
50246
50463
|
|
|
50247
50464
|
if (component.query !== undefined) {
|
|
50248
|
-
uriTokens.push('?', component.query)
|
|
50465
|
+
uriTokens.push('?', encodeQuery(component.query))
|
|
50249
50466
|
}
|
|
50250
50467
|
|
|
50251
50468
|
if (component.fragment !== undefined) {
|
|
50252
|
-
uriTokens.push('#', component.fragment)
|
|
50469
|
+
uriTokens.push('#', encodeFragment(component.fragment))
|
|
50253
50470
|
}
|
|
50254
50471
|
return uriTokens.join('')
|
|
50255
50472
|
}
|
|
50256
50473
|
|
|
50257
50474
|
const URI_PARSE = /^(?:([^#/:?]+):)?(?:\/\/((?:([^#/?@]*)@)?(\[[^#/?\]]+\]|[^#/:?]*)(?::(\d*))?))?([^#?]*)(?:\?([^#]*))?(?:#((?:.|[\n\r])*))?/u
|
|
50258
50475
|
|
|
50476
|
+
// Captures the authority component (between "//" and the next "/", "?" or "#"),
|
|
50477
|
+
// with or without a scheme prefix, for the literal-backslash rejection below.
|
|
50478
|
+
const AUTHORITY_PREFIX = /^(?:[^#/:?]+:)?\/\/([^/?#]*)/
|
|
50479
|
+
|
|
50480
|
+
// Captures the leading authority-introducer region after an optional scheme: a
|
|
50481
|
+
// run of forward slashes, backslashes, and the characters the WHATWG URL parser
|
|
50482
|
+
// removes before parsing (TAB U+0009, LF U+000A, CR U+000D). A valid introducer
|
|
50483
|
+
// is exactly "//". Node treats "\" as "/" on special schemes and strips those
|
|
50484
|
+
// characters first, so forms like "\\", "/\", "\/", "/<TAB>/", or a leading
|
|
50485
|
+
// "<TAB>//" reach an authority in Node while fast-uri's URI_PARSE folds them into
|
|
50486
|
+
// the path group (host confusion / SSRF / redirect bypass).
|
|
50487
|
+
const AUTHORITY_INTRODUCER_REGION = /^(?:[^#/:?]+:)?([/\\\t\n\r]*)/
|
|
50488
|
+
|
|
50489
|
+
/**
|
|
50490
|
+
* @param {import('./types/index').URIComponent} parsed
|
|
50491
|
+
* @param {RegExpMatchArray} matches
|
|
50492
|
+
* @returns {string|undefined}
|
|
50493
|
+
*/
|
|
50494
|
+
function getParseError (parsed, matches) {
|
|
50495
|
+
if (matches[2] !== undefined && parsed.path && parsed.path[0] !== '/') {
|
|
50496
|
+
return 'URI path must start with "/" when authority is present.'
|
|
50497
|
+
}
|
|
50498
|
+
|
|
50499
|
+
if (typeof parsed.port === 'number' && (parsed.port < 0 || parsed.port > 65535)) {
|
|
50500
|
+
return 'URI port is malformed.'
|
|
50501
|
+
}
|
|
50502
|
+
|
|
50503
|
+
return undefined
|
|
50504
|
+
}
|
|
50505
|
+
|
|
50506
|
+
/**
|
|
50507
|
+
* Checks percent syntax without decoding the represented octets. RFC 3986
|
|
50508
|
+
* percent-encoding is byte-oriented, so sequences such as `%FF` are valid even
|
|
50509
|
+
* though they are not independently valid UTF-8.
|
|
50510
|
+
*
|
|
50511
|
+
* @param {string|undefined} component
|
|
50512
|
+
* @returns {boolean}
|
|
50513
|
+
*/
|
|
50514
|
+
function hasMalformedPercentEncoding (component) {
|
|
50515
|
+
if (component === undefined) return false
|
|
50516
|
+
|
|
50517
|
+
let percent = component.indexOf('%')
|
|
50518
|
+
while (percent !== -1) {
|
|
50519
|
+
if (percent + 2 >= component.length || !/^[\da-f]{2}$/iu.test(component.slice(percent + 1, percent + 3))) {
|
|
50520
|
+
return true
|
|
50521
|
+
}
|
|
50522
|
+
percent = component.indexOf('%', percent + 3)
|
|
50523
|
+
}
|
|
50524
|
+
|
|
50525
|
+
return false
|
|
50526
|
+
}
|
|
50527
|
+
|
|
50528
|
+
/**
|
|
50529
|
+
* @param {RegExpMatchArray} matches
|
|
50530
|
+
* @returns {boolean}
|
|
50531
|
+
*/
|
|
50532
|
+
function hasMalformedComponentPercentEncoding (matches) {
|
|
50533
|
+
// Bracketed IP literals use a raw "%" as the zone separator for historical
|
|
50534
|
+
// compatibility. Their parsing is intentionally left to normalizeIPv6.
|
|
50535
|
+
const host = matches[4]
|
|
50536
|
+
return hasMalformedPercentEncoding(matches[3]) ||
|
|
50537
|
+
(host !== undefined && !(host[0] === '[' && host[host.length - 1] === ']') && hasMalformedPercentEncoding(host)) ||
|
|
50538
|
+
hasMalformedPercentEncoding(matches[6]) ||
|
|
50539
|
+
hasMalformedPercentEncoding(matches[7]) ||
|
|
50540
|
+
hasMalformedPercentEncoding(matches[8])
|
|
50541
|
+
}
|
|
50542
|
+
|
|
50543
|
+
/**
|
|
50544
|
+
* @param {import('./types/index').URIComponent} parsed
|
|
50545
|
+
* @param {import('./types/index').Options} options
|
|
50546
|
+
* @param {{ domainHost?: boolean, unicodeSupport?: boolean }|undefined} schemeHandler
|
|
50547
|
+
* @param {boolean} isIP
|
|
50548
|
+
* @returns {boolean} whether host conversion failed
|
|
50549
|
+
*/
|
|
50550
|
+
function canonicalizeHost (parsed, options, schemeHandler, isIP) {
|
|
50551
|
+
if (
|
|
50552
|
+
!options.unicodeSupport &&
|
|
50553
|
+
(!schemeHandler || !schemeHandler.unicodeSupport) &&
|
|
50554
|
+
parsed.host &&
|
|
50555
|
+
parsed.host[0] !== '[' &&
|
|
50556
|
+
(options.domainHost || (schemeHandler && schemeHandler.domainHost)) &&
|
|
50557
|
+
isIP === false &&
|
|
50558
|
+
nonSimpleDomain(parsed.host)
|
|
50559
|
+
) {
|
|
50560
|
+
try {
|
|
50561
|
+
parsed.host = new URL('http://' + parsed.host).hostname
|
|
50562
|
+
} catch (e) {
|
|
50563
|
+
parsed.error = parsed.error || "Host's domain name can not be converted to ASCII: " + e
|
|
50564
|
+
return true
|
|
50565
|
+
}
|
|
50566
|
+
}
|
|
50567
|
+
return false
|
|
50568
|
+
}
|
|
50569
|
+
|
|
50259
50570
|
/**
|
|
50260
50571
|
* @param {string} uri
|
|
50261
50572
|
* @param {import('./types/index').Options} [opts]
|
|
50262
|
-
* @returns
|
|
50573
|
+
* @returns {{ parsed: import('./types/index').URIComponent, malformedAuthorityOrPort: boolean, malformedPercentEncoding: boolean, malformedSchemeSpecific: boolean, malformedHost: boolean, malformedScheme: boolean }}
|
|
50263
50574
|
*/
|
|
50264
|
-
function
|
|
50575
|
+
function parseWithStatus (uri, opts) {
|
|
50265
50576
|
const options = Object.assign({}, opts)
|
|
50266
50577
|
/** @type {import('./types/index').URIComponent} */
|
|
50267
50578
|
const parsed = {
|
|
@@ -50274,6 +50585,13 @@ function parse (uri, opts) {
|
|
|
50274
50585
|
fragment: undefined
|
|
50275
50586
|
}
|
|
50276
50587
|
|
|
50588
|
+
let malformedAuthorityOrPort = false
|
|
50589
|
+
let malformedPercentEncoding = false
|
|
50590
|
+
let malformedSchemeSpecific = false
|
|
50591
|
+
let malformedHost = false
|
|
50592
|
+
let malformedIPLiteral = false
|
|
50593
|
+
let malformedScheme = false
|
|
50594
|
+
|
|
50277
50595
|
let isIP = false
|
|
50278
50596
|
if (options.reference === 'suffix') {
|
|
50279
50597
|
if (options.scheme) {
|
|
@@ -50283,6 +50601,41 @@ function parse (uri, opts) {
|
|
|
50283
50601
|
}
|
|
50284
50602
|
}
|
|
50285
50603
|
|
|
50604
|
+
// A literal backslash (U+005C) is not a valid RFC 3986 URI character and is
|
|
50605
|
+
// not an authority delimiter. Reject it in the authority rather than
|
|
50606
|
+
// rewriting it: normalizing "\" -> "/" (WHATWG error recovery) could silently
|
|
50607
|
+
// change the resource identified by an otherwise-invalid input, and lets "\"
|
|
50608
|
+
// act as a host delimiter here while Node's native URL parses a different
|
|
50609
|
+
// host (SSRF / redirect / origin-allowlist bypass). Percent-encoded %5C is
|
|
50610
|
+
// untouched and remains valid encoded data.
|
|
50611
|
+
const authorityMatch = uri.match(AUTHORITY_PREFIX)
|
|
50612
|
+
if (authorityMatch !== null && authorityMatch[1].indexOf('\\') !== -1) {
|
|
50613
|
+
parsed.error = 'URI authority must not contain a literal backslash.'
|
|
50614
|
+
malformedAuthorityOrPort = true
|
|
50615
|
+
}
|
|
50616
|
+
|
|
50617
|
+
// Reject a malformed or whitespace-smuggled authority introducer. fast-uri
|
|
50618
|
+
// only recognizes a literal "//"; anything else in the leading separator run
|
|
50619
|
+
// (a backslash, or a "//" that appears only after removing the TAB/LF/CR that
|
|
50620
|
+
// Node strips) means the authority fast-uri parses differs from the one Node's
|
|
50621
|
+
// URL resolves. Reject rather than rewrite, mirroring the literal-backslash
|
|
50622
|
+
// guard above. Percent-encoded forms (%5C, %09) are untouched, valid data.
|
|
50623
|
+
const introducerMatch = uri.match(AUTHORITY_INTRODUCER_REGION)
|
|
50624
|
+
if (introducerMatch !== null) {
|
|
50625
|
+
const region = introducerMatch[1]
|
|
50626
|
+
const normalizedRegion = region.replace(/[\t\n\r]/g, '')
|
|
50627
|
+
// Two or more leading separators introduce an authority.
|
|
50628
|
+
if (normalizedRegion.length >= 2) {
|
|
50629
|
+
if (normalizedRegion.slice(0, 2) !== '//') {
|
|
50630
|
+
parsed.error = parsed.error || 'URI authority must not contain a literal backslash.'
|
|
50631
|
+
malformedAuthorityOrPort = true
|
|
50632
|
+
} else if (region.length !== normalizedRegion.length) {
|
|
50633
|
+
parsed.error = parsed.error || 'URI authority introducer must not contain whitespace.'
|
|
50634
|
+
malformedAuthorityOrPort = true
|
|
50635
|
+
}
|
|
50636
|
+
}
|
|
50637
|
+
}
|
|
50638
|
+
|
|
50286
50639
|
const matches = uri.match(URI_PARSE)
|
|
50287
50640
|
|
|
50288
50641
|
if (matches) {
|
|
@@ -50295,16 +50648,45 @@ function parse (uri, opts) {
|
|
|
50295
50648
|
parsed.query = matches[7]
|
|
50296
50649
|
parsed.fragment = matches[8]
|
|
50297
50650
|
|
|
50651
|
+
if (parsed.scheme !== undefined) {
|
|
50652
|
+
const decodedScheme = unescape(parsed.scheme)
|
|
50653
|
+
if (VALID_SCHEME.test(decodedScheme)) {
|
|
50654
|
+
parsed.scheme = decodedScheme.toLowerCase()
|
|
50655
|
+
} else {
|
|
50656
|
+
parsed.error = parsed.error || MALFORMED_SCHEME_ERROR
|
|
50657
|
+
malformedScheme = true
|
|
50658
|
+
}
|
|
50659
|
+
}
|
|
50660
|
+
|
|
50661
|
+
malformedPercentEncoding = hasMalformedComponentPercentEncoding(matches)
|
|
50662
|
+
if (malformedPercentEncoding) {
|
|
50663
|
+
parsed.error = parsed.error || 'URI contains malformed percent-encoding.'
|
|
50664
|
+
}
|
|
50665
|
+
|
|
50298
50666
|
// fix port number
|
|
50299
50667
|
if (isNaN(parsed.port)) {
|
|
50300
50668
|
parsed.port = matches[5]
|
|
50301
50669
|
}
|
|
50670
|
+
|
|
50671
|
+
const parseError = getParseError(parsed, matches)
|
|
50672
|
+
if (parseError !== undefined) {
|
|
50673
|
+
parsed.error = parsed.error || parseError
|
|
50674
|
+
malformedAuthorityOrPort = true
|
|
50675
|
+
}
|
|
50676
|
+
|
|
50302
50677
|
if (parsed.host) {
|
|
50303
50678
|
const ipv4result = isIPv4(parsed.host)
|
|
50304
50679
|
if (ipv4result === false) {
|
|
50680
|
+
const bracketedIPLiteral = parsed.host[0] === '[' && parsed.host[parsed.host.length - 1] === ']'
|
|
50305
50681
|
const ipv6result = normalizeIPv6(parsed.host)
|
|
50306
|
-
|
|
50307
|
-
|
|
50682
|
+
isIP = ipv6result.isIPV6 || ipv6result.isIPVFuture === true
|
|
50683
|
+
malformedIPLiteral = bracketedIPLiteral && ipv6result.error === true
|
|
50684
|
+
parsed.host = isIP ? ipv6result.host : ipv6result.host.toLowerCase()
|
|
50685
|
+
|
|
50686
|
+
if (malformedIPLiteral) {
|
|
50687
|
+
parsed.error = parsed.error || 'URI host is malformed.'
|
|
50688
|
+
malformedAuthorityOrPort = true
|
|
50689
|
+
}
|
|
50308
50690
|
} else {
|
|
50309
50691
|
isIP = true
|
|
50310
50692
|
}
|
|
@@ -50327,45 +50709,93 @@ function parse (uri, opts) {
|
|
|
50327
50709
|
// find scheme handler
|
|
50328
50710
|
const schemeHandler = getSchemeHandler(options.scheme || parsed.scheme)
|
|
50329
50711
|
|
|
50330
|
-
//
|
|
50331
|
-
|
|
50332
|
-
// if host component is a domain name
|
|
50333
|
-
if (parsed.host && (options.domainHost || (schemeHandler && schemeHandler.domainHost)) && isIP === false && nonSimpleDomain(parsed.host)) {
|
|
50334
|
-
// convert Unicode IDN -> ASCII IDN
|
|
50335
|
-
try {
|
|
50336
|
-
parsed.host = URL.domainToASCII(parsed.host.toLowerCase())
|
|
50337
|
-
} catch (e) {
|
|
50338
|
-
parsed.error = parsed.error || "Host's domain name can not be converted to ASCII: " + e
|
|
50339
|
-
}
|
|
50340
|
-
}
|
|
50341
|
-
// convert IRI -> URI
|
|
50342
|
-
}
|
|
50712
|
+
// convert Unicode IDN -> ASCII IDN when the effective scheme uses domain hosts
|
|
50713
|
+
malformedHost = canonicalizeHost(parsed, options, schemeHandler, isIP)
|
|
50343
50714
|
|
|
50344
50715
|
if (!schemeHandler || (schemeHandler && !schemeHandler.skipNormalize)) {
|
|
50345
50716
|
if (uri.indexOf('%') !== -1) {
|
|
50346
|
-
if (parsed.
|
|
50347
|
-
parsed.
|
|
50348
|
-
|
|
50349
|
-
if (parsed.host !== undefined) {
|
|
50350
|
-
parsed.host = unescape(parsed.host)
|
|
50717
|
+
if (parsed.host !== undefined && !malformedIPLiteral) {
|
|
50718
|
+
const host = isIP ? parsed.host : normalizePercentEncoding(parsed.host, true)
|
|
50719
|
+
parsed.host = reescapeHostDelimiters(host, isIP)
|
|
50351
50720
|
}
|
|
50352
50721
|
}
|
|
50353
50722
|
if (parsed.path) {
|
|
50354
|
-
parsed.path =
|
|
50723
|
+
parsed.path = normalizePathEncoding(parsed.path)
|
|
50724
|
+
}
|
|
50725
|
+
if (parsed.query) {
|
|
50726
|
+
parsed.query = normalizeQueryFragmentEncoding(parsed.query)
|
|
50355
50727
|
}
|
|
50356
50728
|
if (parsed.fragment) {
|
|
50357
|
-
parsed.fragment =
|
|
50729
|
+
parsed.fragment = normalizeQueryFragmentEncoding(parsed.fragment)
|
|
50358
50730
|
}
|
|
50359
50731
|
}
|
|
50360
50732
|
|
|
50361
50733
|
// perform scheme specific parsing
|
|
50362
50734
|
if (schemeHandler && schemeHandler.parse) {
|
|
50363
50735
|
schemeHandler.parse(parsed, options)
|
|
50736
|
+
if (schemeHandler === SCHEMES.urn && parsed.nid === undefined) {
|
|
50737
|
+
malformedSchemeSpecific = true
|
|
50738
|
+
}
|
|
50364
50739
|
}
|
|
50365
50740
|
} else {
|
|
50366
50741
|
parsed.error = parsed.error || 'URI can not be parsed.'
|
|
50367
50742
|
}
|
|
50368
|
-
return parsed
|
|
50743
|
+
return { parsed, malformedAuthorityOrPort, malformedPercentEncoding, malformedSchemeSpecific, malformedHost, malformedScheme }
|
|
50744
|
+
}
|
|
50745
|
+
|
|
50746
|
+
/**
|
|
50747
|
+
* @param {string} uri
|
|
50748
|
+
* @param {import('./types/index').Options} [opts]
|
|
50749
|
+
* @returns
|
|
50750
|
+
*/
|
|
50751
|
+
function parse (uri, opts) {
|
|
50752
|
+
return parseWithStatus(uri, opts).parsed
|
|
50753
|
+
}
|
|
50754
|
+
|
|
50755
|
+
/**
|
|
50756
|
+
* @param {string} uri
|
|
50757
|
+
* @param {import('./types/index').Options} [opts]
|
|
50758
|
+
* @returns {string}
|
|
50759
|
+
*/
|
|
50760
|
+
function normalizeString (uri, opts) {
|
|
50761
|
+
return normalizeStringWithStatus(uri, opts).normalized
|
|
50762
|
+
}
|
|
50763
|
+
|
|
50764
|
+
/**
|
|
50765
|
+
* @param {string} uri
|
|
50766
|
+
* @param {import('./types/index').Options} [opts]
|
|
50767
|
+
* @returns {{ normalized: string, malformedAuthorityOrPort: boolean, malformedPercentEncoding: boolean, malformedSchemeSpecific: boolean, malformedHost: boolean, malformedScheme: boolean }}
|
|
50768
|
+
*/
|
|
50769
|
+
function normalizeStringWithStatus (uri, opts) {
|
|
50770
|
+
const { parsed, malformedAuthorityOrPort, malformedPercentEncoding, malformedSchemeSpecific, malformedHost, malformedScheme } = parseWithStatus(uri, opts)
|
|
50771
|
+
return {
|
|
50772
|
+
normalized: malformedAuthorityOrPort || malformedPercentEncoding || malformedSchemeSpecific || malformedHost || malformedScheme ? uri : serialize(parsed, opts),
|
|
50773
|
+
malformedAuthorityOrPort,
|
|
50774
|
+
malformedPercentEncoding,
|
|
50775
|
+
malformedSchemeSpecific,
|
|
50776
|
+
malformedHost,
|
|
50777
|
+
malformedScheme
|
|
50778
|
+
}
|
|
50779
|
+
}
|
|
50780
|
+
|
|
50781
|
+
/**
|
|
50782
|
+
* @param {import ('./types/index').URIComponent|string} uri
|
|
50783
|
+
* @param {import('./types/index').Options} [opts]
|
|
50784
|
+
* @returns {string|undefined}
|
|
50785
|
+
*/
|
|
50786
|
+
function normalizeComparableURI (uri, opts) {
|
|
50787
|
+
if (typeof uri !== 'string' && typeof uri !== 'object') {
|
|
50788
|
+
return undefined
|
|
50789
|
+
}
|
|
50790
|
+
|
|
50791
|
+
let value
|
|
50792
|
+
try {
|
|
50793
|
+
value = typeof uri === 'string' ? uri : serialize(uri, opts)
|
|
50794
|
+
} catch {
|
|
50795
|
+
return undefined
|
|
50796
|
+
}
|
|
50797
|
+
const { normalized, malformedAuthorityOrPort, malformedPercentEncoding, malformedSchemeSpecific, malformedHost, malformedScheme } = normalizeStringWithStatus(value, opts)
|
|
50798
|
+
return malformedAuthorityOrPort || malformedPercentEncoding || malformedSchemeSpecific || malformedHost || malformedScheme ? undefined : normalized
|
|
50369
50799
|
}
|
|
50370
50800
|
|
|
50371
50801
|
const fastUri = {
|
|
@@ -50392,7 +50822,7 @@ module.exports.fastUri = fastUri
|
|
|
50392
50822
|
|
|
50393
50823
|
|
|
50394
50824
|
const { isUUID } = __nccwpck_require__(5077)
|
|
50395
|
-
const URN_REG =
|
|
50825
|
+
const URN_REG = /^([\da-z][\d\-a-z]{0,31}):((?:[\w!$'()*+,\-./:;=@]|%[\da-f]{2})+)$/iu
|
|
50396
50826
|
|
|
50397
50827
|
const supportedSchemeNames = /** @type {const} */ (['http', 'https', 'ws',
|
|
50398
50828
|
'wss', 'urn', 'urn:uuid'])
|
|
@@ -50504,9 +50934,14 @@ function wsSerialize (wsComponent) {
|
|
|
50504
50934
|
|
|
50505
50935
|
// reconstruct path from resource name
|
|
50506
50936
|
if (wsComponent.resourceName) {
|
|
50507
|
-
const
|
|
50937
|
+
const queryIndex = wsComponent.resourceName.indexOf('?')
|
|
50938
|
+
const path = queryIndex === -1
|
|
50939
|
+
? wsComponent.resourceName
|
|
50940
|
+
: wsComponent.resourceName.slice(0, queryIndex)
|
|
50508
50941
|
wsComponent.path = (path && path !== '/' ? path : undefined)
|
|
50509
|
-
wsComponent.query =
|
|
50942
|
+
wsComponent.query = queryIndex === -1
|
|
50943
|
+
? undefined
|
|
50944
|
+
: wsComponent.resourceName.slice(queryIndex + 1)
|
|
50510
50945
|
wsComponent.resourceName = undefined
|
|
50511
50946
|
}
|
|
50512
50947
|
|
|
@@ -50523,7 +50958,7 @@ function urnParse (urnComponent, options) {
|
|
|
50523
50958
|
return urnComponent
|
|
50524
50959
|
}
|
|
50525
50960
|
const matches = urnComponent.path.match(URN_REG)
|
|
50526
|
-
if (matches) {
|
|
50961
|
+
if (matches && matches[0] === urnComponent.path) {
|
|
50527
50962
|
const scheme = options.scheme || urnComponent.scheme || 'urn'
|
|
50528
50963
|
urnComponent.nid = matches[1].toLowerCase()
|
|
50529
50964
|
urnComponent.nss = matches[2]
|
|
@@ -50672,6 +51107,44 @@ const isUUID = RegExp.prototype.test.bind(/^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\d
|
|
|
50672
51107
|
/** @type {(value: string) => boolean} */
|
|
50673
51108
|
const isIPv4 = RegExp.prototype.test.bind(/^(?:(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)$/u)
|
|
50674
51109
|
|
|
51110
|
+
/** @type {(value: string) => boolean} */
|
|
51111
|
+
const isHexPair = RegExp.prototype.test.bind(/^[\da-f]{2}$/iu)
|
|
51112
|
+
|
|
51113
|
+
/** @type {(value: string) => boolean} */
|
|
51114
|
+
const isUnreserved = RegExp.prototype.test.bind(/^[\da-z\-._~]$/iu)
|
|
51115
|
+
|
|
51116
|
+
/** @type {(value: string) => boolean} */
|
|
51117
|
+
const isPathCharacter = RegExp.prototype.test.bind(/^[A-Za-z0-9\-._~!$&'()*+,;=:@/]$/u)
|
|
51118
|
+
|
|
51119
|
+
/** @type {(value: string) => boolean} */
|
|
51120
|
+
const isQueryFragmentCharacter = RegExp.prototype.test.bind(/^[A-Za-z0-9\-._~!$&'()*+,;=:@/?]$/u)
|
|
51121
|
+
|
|
51122
|
+
/** @type {(value: string) => boolean} */
|
|
51123
|
+
const isUserinfoCharacter = RegExp.prototype.test.bind(/^[A-Za-z0-9\-._~!$&'()*+,;=:]$/u)
|
|
51124
|
+
|
|
51125
|
+
const BYTE_HEX = new Array(256)
|
|
51126
|
+
{
|
|
51127
|
+
const HEX_DIGITS = '0123456789ABCDEF'
|
|
51128
|
+
for (let i = 0; i < 256; i++) {
|
|
51129
|
+
BYTE_HEX[i] = '%' + HEX_DIGITS[i >> 4] + HEX_DIGITS[i & 0xF]
|
|
51130
|
+
}
|
|
51131
|
+
}
|
|
51132
|
+
function percentEncodeNonAscii (cp) {
|
|
51133
|
+
if (cp < 0x800) {
|
|
51134
|
+
return BYTE_HEX[0xC0 | (cp >> 6)] +
|
|
51135
|
+
BYTE_HEX[0x80 | (cp & 0x3F)]
|
|
51136
|
+
}
|
|
51137
|
+
if (cp < 0x10000) {
|
|
51138
|
+
return BYTE_HEX[0xE0 | (cp >> 12)] +
|
|
51139
|
+
BYTE_HEX[0x80 | ((cp >> 6) & 0x3F)] +
|
|
51140
|
+
BYTE_HEX[0x80 | (cp & 0x3F)]
|
|
51141
|
+
}
|
|
51142
|
+
return BYTE_HEX[0xF0 | (cp >> 18)] +
|
|
51143
|
+
BYTE_HEX[0x80 | ((cp >> 12) & 0x3F)] +
|
|
51144
|
+
BYTE_HEX[0x80 | ((cp >> 6) & 0x3F)] +
|
|
51145
|
+
BYTE_HEX[0x80 | (cp & 0x3F)]
|
|
51146
|
+
}
|
|
51147
|
+
|
|
50675
51148
|
/**
|
|
50676
51149
|
* @param {Array<string>} input
|
|
50677
51150
|
* @returns {string}
|
|
@@ -50703,12 +51176,14 @@ function stringArrayToHexStripped (input) {
|
|
|
50703
51176
|
return acc
|
|
50704
51177
|
}
|
|
50705
51178
|
|
|
50706
|
-
/**
|
|
50707
|
-
|
|
50708
|
-
|
|
50709
|
-
|
|
50710
|
-
|
|
50711
|
-
|
|
51179
|
+
/** @type {(value: string) => boolean} */
|
|
51180
|
+
const isHextet = RegExp.prototype.test.bind(/^[\dA-Fa-f]{1,4}$/)
|
|
51181
|
+
|
|
51182
|
+
/** @type {(value: string) => boolean} */
|
|
51183
|
+
const isIPvFuture = RegExp.prototype.test.bind(/^[vV][\dA-Fa-f]+\.[A-Za-z\d\-._~!$&'()*+,;=:]+$/)
|
|
51184
|
+
|
|
51185
|
+
/** @type {(value: string) => boolean} */
|
|
51186
|
+
const isZoneCharacter = RegExp.prototype.test.bind(/^[A-Za-z\d\-._~]$/)
|
|
50712
51187
|
|
|
50713
51188
|
/**
|
|
50714
51189
|
* @param {string} value
|
|
@@ -50717,88 +51192,104 @@ function stringArrayToHexStripped (input) {
|
|
|
50717
51192
|
const nonSimpleDomain = RegExp.prototype.test.bind(/[^!"$&'()*+,\-.;=_`a-z{}~]/u)
|
|
50718
51193
|
|
|
50719
51194
|
/**
|
|
50720
|
-
* @param {
|
|
51195
|
+
* @param {string} zone
|
|
50721
51196
|
* @returns {boolean}
|
|
50722
51197
|
*/
|
|
50723
|
-
function
|
|
50724
|
-
|
|
51198
|
+
function isZoneIdentifier (zone) {
|
|
51199
|
+
if (zone.length === 0) return false
|
|
51200
|
+
|
|
51201
|
+
for (let i = 0; i < zone.length; i++) {
|
|
51202
|
+
if (isZoneCharacter(zone[i])) continue
|
|
51203
|
+
if (zone[i] === '%' && i + 2 < zone.length && isHexPair(zone.slice(i + 1, i + 3))) {
|
|
51204
|
+
i += 2
|
|
51205
|
+
continue
|
|
51206
|
+
}
|
|
51207
|
+
return false
|
|
51208
|
+
}
|
|
51209
|
+
|
|
50725
51210
|
return true
|
|
50726
51211
|
}
|
|
50727
51212
|
|
|
50728
51213
|
/**
|
|
50729
|
-
*
|
|
50730
|
-
*
|
|
50731
|
-
*
|
|
50732
|
-
* @
|
|
51214
|
+
* Compresses the longest run of zero hextets to "::" per RFC 5952. A run of a
|
|
51215
|
+
* single zero hextet is left uncompressed. On ties the leftmost run wins.
|
|
51216
|
+
*
|
|
51217
|
+
* @param {string[]} hextets
|
|
51218
|
+
* @returns {string}
|
|
50733
51219
|
*/
|
|
50734
|
-
function
|
|
50735
|
-
|
|
50736
|
-
|
|
50737
|
-
|
|
50738
|
-
|
|
51220
|
+
function compressIPv6ZeroRun (hextets) {
|
|
51221
|
+
let bestStart = -1
|
|
51222
|
+
let bestLength = 0
|
|
51223
|
+
let runStart = -1
|
|
51224
|
+
let runLength = 0
|
|
51225
|
+
for (let i = 0; i < hextets.length; i++) {
|
|
51226
|
+
if (hextets[i] === '0') {
|
|
51227
|
+
if (runStart === -1) runStart = i
|
|
51228
|
+
runLength++
|
|
51229
|
+
if (runLength > bestLength) {
|
|
51230
|
+
bestLength = runLength
|
|
51231
|
+
bestStart = runStart
|
|
51232
|
+
}
|
|
50739
51233
|
} else {
|
|
50740
|
-
|
|
50741
|
-
|
|
51234
|
+
runStart = -1
|
|
51235
|
+
runLength = 0
|
|
50742
51236
|
}
|
|
50743
|
-
buffer.length = 0
|
|
50744
51237
|
}
|
|
50745
|
-
|
|
51238
|
+
|
|
51239
|
+
if (bestLength < 2) return hextets.join(':')
|
|
51240
|
+
|
|
51241
|
+
const head = hextets.slice(0, bestStart).join(':')
|
|
51242
|
+
const tail = hextets.slice(bestStart + bestLength).join(':')
|
|
51243
|
+
return head + '::' + tail
|
|
50746
51244
|
}
|
|
50747
51245
|
|
|
50748
51246
|
/**
|
|
51247
|
+
* Validates an IPv6 address against the alternatives in RFC 3986 section
|
|
51248
|
+
* 3.2.2 and returns the same address with leading hextet zeroes removed.
|
|
51249
|
+
* An embedded IPv4 address counts as two hextets and is only valid at the end.
|
|
51250
|
+
*
|
|
50749
51251
|
* @param {string} input
|
|
50750
|
-
* @returns {
|
|
51252
|
+
* @returns {string|undefined}
|
|
50751
51253
|
*/
|
|
50752
|
-
function
|
|
50753
|
-
|
|
50754
|
-
|
|
50755
|
-
|
|
50756
|
-
const
|
|
50757
|
-
|
|
50758
|
-
|
|
50759
|
-
|
|
50760
|
-
|
|
50761
|
-
|
|
50762
|
-
|
|
50763
|
-
|
|
50764
|
-
|
|
50765
|
-
|
|
50766
|
-
|
|
50767
|
-
if (
|
|
50768
|
-
|
|
50769
|
-
|
|
50770
|
-
|
|
50771
|
-
|
|
50772
|
-
if (++tokenCount > 7) {
|
|
50773
|
-
// not valid
|
|
50774
|
-
output.error = true
|
|
50775
|
-
break
|
|
50776
|
-
}
|
|
50777
|
-
if (i > 0 && input[i - 1] === ':') {
|
|
50778
|
-
endipv6Encountered = true
|
|
50779
|
-
}
|
|
50780
|
-
address.push(':')
|
|
50781
|
-
continue
|
|
50782
|
-
} else if (cursor === '%') {
|
|
50783
|
-
if (!consume(buffer, address, output)) { break }
|
|
50784
|
-
// switch to zone detection
|
|
50785
|
-
consume = consumeIsZone
|
|
50786
|
-
} else {
|
|
50787
|
-
buffer.push(cursor)
|
|
51254
|
+
function normalizeIPv6Address (input) {
|
|
51255
|
+
const compression = input.indexOf('::')
|
|
51256
|
+
if (compression !== -1 && input.indexOf('::', compression + 1) !== -1) return undefined
|
|
51257
|
+
|
|
51258
|
+
const left = compression === -1 ? input.split(':') : input.slice(0, compression).split(':')
|
|
51259
|
+
const right = compression === -1 ? [] : input.slice(compression + 2).split(':')
|
|
51260
|
+
if (compression !== -1) {
|
|
51261
|
+
if (left.length === 1 && left[0] === '') left.length = 0
|
|
51262
|
+
if (right.length === 1 && right[0] === '') right.length = 0
|
|
51263
|
+
}
|
|
51264
|
+
|
|
51265
|
+
const parts = left.concat(right)
|
|
51266
|
+
let hextetCount = 0
|
|
51267
|
+
for (let i = 0; i < parts.length; i++) {
|
|
51268
|
+
const part = parts[i]
|
|
51269
|
+
if (part === '') return undefined
|
|
51270
|
+
|
|
51271
|
+
if (part.indexOf('.') !== -1) {
|
|
51272
|
+
if (i !== parts.length - 1 || (compression !== -1 && right.length === 0) || !isIPv4(part)) return undefined
|
|
51273
|
+
hextetCount += 2
|
|
50788
51274
|
continue
|
|
50789
51275
|
}
|
|
51276
|
+
|
|
51277
|
+
if (!isHextet(part)) return undefined
|
|
51278
|
+
parts[i] = parseInt(part, 16).toString(16)
|
|
51279
|
+
hextetCount++
|
|
50790
51280
|
}
|
|
50791
|
-
|
|
50792
|
-
|
|
50793
|
-
|
|
50794
|
-
|
|
50795
|
-
address.push(buffer.join(''))
|
|
50796
|
-
} else {
|
|
50797
|
-
address.push(stringArrayToHexStripped(buffer))
|
|
50798
|
-
}
|
|
51281
|
+
|
|
51282
|
+
if (compression === -1) {
|
|
51283
|
+
if (hextetCount !== 8) return undefined
|
|
51284
|
+
return compressIPv6ZeroRun(parts)
|
|
50799
51285
|
}
|
|
50800
|
-
|
|
50801
|
-
|
|
51286
|
+
if (hextetCount >= 8) return undefined
|
|
51287
|
+
|
|
51288
|
+
// expand "::" then re-compress the longest run for a canonical result
|
|
51289
|
+
const expanded = parts.slice(0, left.length)
|
|
51290
|
+
for (let i = hextetCount; i < 8; i++) expanded.push('0')
|
|
51291
|
+
for (let i = left.length; i < parts.length; i++) expanded.push(parts[i])
|
|
51292
|
+
return compressIPv6ZeroRun(expanded)
|
|
50802
51293
|
}
|
|
50803
51294
|
|
|
50804
51295
|
/**
|
|
@@ -50806,26 +51297,49 @@ function getIPV6 (input) {
|
|
|
50806
51297
|
* @property {string} host - The normalized host.
|
|
50807
51298
|
* @property {string} [escapedHost] - The escaped host.
|
|
50808
51299
|
* @property {boolean} isIPV6 - Indicates if the host is an IPv6 address.
|
|
51300
|
+
* @property {boolean} [isIPVFuture] - Indicates if the host is an IPvFuture literal.
|
|
51301
|
+
* @property {boolean} [error] - Indicates if a bracketed IP literal is malformed.
|
|
50809
51302
|
*/
|
|
50810
51303
|
|
|
50811
51304
|
/**
|
|
51305
|
+
* Validates and normalizes a bracketed IP literal. Raw zone separators remain
|
|
51306
|
+
* accepted for backwards compatibility, while encoded separators and zone
|
|
51307
|
+
* contents follow RFC 6874.
|
|
51308
|
+
*
|
|
50812
51309
|
* @param {string} host
|
|
50813
51310
|
* @returns {NormalizeIPv6Result}
|
|
50814
51311
|
*/
|
|
50815
51312
|
function normalizeIPv6 (host) {
|
|
50816
|
-
|
|
50817
|
-
const
|
|
51313
|
+
const bracketed = host[0] === '[' && host[host.length - 1] === ']'
|
|
51314
|
+
const hasBracket = host[0] === '[' || host[host.length - 1] === ']'
|
|
51315
|
+
if (hasBracket && !bracketed) return { host, isIPV6: false, error: true }
|
|
50818
51316
|
|
|
50819
|
-
|
|
50820
|
-
|
|
50821
|
-
|
|
50822
|
-
|
|
50823
|
-
|
|
50824
|
-
|
|
50825
|
-
|
|
50826
|
-
return { host
|
|
50827
|
-
}
|
|
50828
|
-
|
|
51317
|
+
let input = bracketed ? host.slice(1, -1) : host
|
|
51318
|
+
if (bracketed && isIPvFuture(input)) {
|
|
51319
|
+
input = input.toLowerCase()
|
|
51320
|
+
return { host: `[${input}]`, escapedHost: input, isIPV6: false, isIPVFuture: true }
|
|
51321
|
+
}
|
|
51322
|
+
|
|
51323
|
+
if (findToken(input, ':') < 2) {
|
|
51324
|
+
return { host, isIPV6: false, error: bracketed }
|
|
51325
|
+
}
|
|
51326
|
+
|
|
51327
|
+
let zoneIdentifier = ''
|
|
51328
|
+
const zoneSeparator = input.indexOf('%')
|
|
51329
|
+
if (zoneSeparator !== -1) {
|
|
51330
|
+
const separatorLength = input.slice(zoneSeparator, zoneSeparator + 3).toLowerCase() === '%25' ? 3 : 1
|
|
51331
|
+
zoneIdentifier = input.slice(zoneSeparator + separatorLength)
|
|
51332
|
+
if (!isZoneIdentifier(zoneIdentifier)) return { host, isIPV6: false, error: true }
|
|
51333
|
+
input = input.slice(0, zoneSeparator)
|
|
51334
|
+
}
|
|
51335
|
+
|
|
51336
|
+
const address = normalizeIPv6Address(input)
|
|
51337
|
+
if (address === undefined) return { host, isIPV6: false, error: true }
|
|
51338
|
+
|
|
51339
|
+
return {
|
|
51340
|
+
host: address + (zoneIdentifier ? '%' + zoneIdentifier : ''),
|
|
51341
|
+
escapedHost: address + (zoneIdentifier ? '%25' + zoneIdentifier : ''),
|
|
51342
|
+
isIPV6: true
|
|
50829
51343
|
}
|
|
50830
51344
|
}
|
|
50831
51345
|
|
|
@@ -50930,31 +51444,342 @@ function removeDotSegments (path) {
|
|
|
50930
51444
|
}
|
|
50931
51445
|
|
|
50932
51446
|
/**
|
|
50933
|
-
*
|
|
50934
|
-
*
|
|
50935
|
-
*
|
|
51447
|
+
* Re-escape RFC 3986 gen-delims that must not appear literally in the host.
|
|
51448
|
+
* After the URI regex parses, these characters cannot be literal in the host
|
|
51449
|
+
* field, so any that appear after decoding came from percent-encoding and
|
|
51450
|
+
* must be restored to prevent authority structure changes.
|
|
51451
|
+
*
|
|
51452
|
+
* @param {string} host
|
|
51453
|
+
* @param {boolean} isIP - true for IPv4/IPv6 hosts (skip colon re-escaping)
|
|
51454
|
+
* @returns {string}
|
|
50936
51455
|
*/
|
|
50937
|
-
|
|
50938
|
-
|
|
50939
|
-
|
|
50940
|
-
|
|
51456
|
+
const HOST_DELIMS = { '@': '%40', '/': '%2F', '?': '%3F', '#': '%23', ':': '%3A' }
|
|
51457
|
+
const HOST_DELIM_RE = /[@/?#:]/g
|
|
51458
|
+
const HOST_DELIM_NO_COLON_RE = /[@/?#]/g
|
|
51459
|
+
|
|
51460
|
+
function reescapeHostDelimiters (host, isIP) {
|
|
51461
|
+
const re = isIP ? HOST_DELIM_NO_COLON_RE : HOST_DELIM_RE
|
|
51462
|
+
re.lastIndex = 0
|
|
51463
|
+
return host.replace(re, (ch) => HOST_DELIMS[ch])
|
|
51464
|
+
}
|
|
51465
|
+
|
|
51466
|
+
/**
|
|
51467
|
+
* Normalizes percent escapes and optionally decodes only unreserved ASCII bytes.
|
|
51468
|
+
* Reserved delimiters such as `%2F` stay escaped; `%2E` is unreserved.
|
|
51469
|
+
*
|
|
51470
|
+
* @param {string} input
|
|
51471
|
+
* @param {boolean} [decodeUnreserved=false]
|
|
51472
|
+
* @returns {string}
|
|
51473
|
+
*/
|
|
51474
|
+
function normalizePercentEncoding (input, decodeUnreserved = false) {
|
|
51475
|
+
if (input.indexOf('%') === -1) {
|
|
51476
|
+
return input
|
|
50941
51477
|
}
|
|
50942
|
-
|
|
50943
|
-
|
|
51478
|
+
|
|
51479
|
+
let output = ''
|
|
51480
|
+
|
|
51481
|
+
for (let i = 0; i < input.length; i++) {
|
|
51482
|
+
if (input[i] === '%' && i + 2 < input.length) {
|
|
51483
|
+
const hex = input.slice(i + 1, i + 3)
|
|
51484
|
+
if (isHexPair(hex)) {
|
|
51485
|
+
const normalizedHex = hex.toUpperCase()
|
|
51486
|
+
const decoded = String.fromCharCode(parseInt(normalizedHex, 16))
|
|
51487
|
+
|
|
51488
|
+
if (decodeUnreserved && isUnreserved(decoded)) {
|
|
51489
|
+
output += decoded
|
|
51490
|
+
} else {
|
|
51491
|
+
output += '%' + normalizedHex
|
|
51492
|
+
}
|
|
51493
|
+
|
|
51494
|
+
i += 2
|
|
51495
|
+
continue
|
|
51496
|
+
}
|
|
51497
|
+
}
|
|
51498
|
+
|
|
51499
|
+
output += input[i]
|
|
50944
51500
|
}
|
|
50945
|
-
|
|
50946
|
-
|
|
51501
|
+
|
|
51502
|
+
return output
|
|
51503
|
+
}
|
|
51504
|
+
|
|
51505
|
+
/**
|
|
51506
|
+
* Normalizes path data without turning reserved escapes into live path syntax.
|
|
51507
|
+
* Valid escapes are uppercased, raw unsafe characters are escaped, and only
|
|
51508
|
+
* unreserved bytes that are not `.` are decoded.
|
|
51509
|
+
*
|
|
51510
|
+
* @param {string} input
|
|
51511
|
+
* @returns {string}
|
|
51512
|
+
*/
|
|
51513
|
+
function normalizePathEncoding (input) {
|
|
51514
|
+
let output = ''
|
|
51515
|
+
|
|
51516
|
+
for (let i = 0; i < input.length; i++) {
|
|
51517
|
+
const ch = input[i]
|
|
51518
|
+
if (ch === '%' && i + 2 < input.length) {
|
|
51519
|
+
const hex = input.slice(i + 1, i + 3)
|
|
51520
|
+
if (isHexPair(hex)) {
|
|
51521
|
+
const normalizedHex = hex.toUpperCase()
|
|
51522
|
+
const decoded = String.fromCharCode(parseInt(normalizedHex, 16))
|
|
51523
|
+
|
|
51524
|
+
if (decoded !== '.' && isUnreserved(decoded)) {
|
|
51525
|
+
output += decoded
|
|
51526
|
+
} else {
|
|
51527
|
+
output += '%' + normalizedHex
|
|
51528
|
+
}
|
|
51529
|
+
|
|
51530
|
+
i += 2
|
|
51531
|
+
continue
|
|
51532
|
+
}
|
|
51533
|
+
}
|
|
51534
|
+
|
|
51535
|
+
if (isPathCharacter(ch)) {
|
|
51536
|
+
output += ch
|
|
51537
|
+
} else {
|
|
51538
|
+
const code = input.charCodeAt(i)
|
|
51539
|
+
if (code < 0x80) {
|
|
51540
|
+
output += isEscapeSafe(code) ? ch : BYTE_HEX[code]
|
|
51541
|
+
} else if (code < 0xD800 || code > 0xDFFF) {
|
|
51542
|
+
output += percentEncodeNonAscii(code)
|
|
51543
|
+
} else if (code <= 0xDBFF && i + 1 < input.length) {
|
|
51544
|
+
const low = input.charCodeAt(i + 1)
|
|
51545
|
+
if (low >= 0xDC00 && low <= 0xDFFF) {
|
|
51546
|
+
output += percentEncodeNonAscii(0x10000 + ((code - 0xD800) << 10) + (low - 0xDC00))
|
|
51547
|
+
i++
|
|
51548
|
+
} else {
|
|
51549
|
+
output += percentEncodeNonAscii(0xFFFD)
|
|
51550
|
+
}
|
|
51551
|
+
} else {
|
|
51552
|
+
output += percentEncodeNonAscii(0xFFFD)
|
|
51553
|
+
}
|
|
51554
|
+
}
|
|
50947
51555
|
}
|
|
50948
|
-
|
|
50949
|
-
|
|
51556
|
+
|
|
51557
|
+
return output
|
|
51558
|
+
}
|
|
51559
|
+
|
|
51560
|
+
/**
|
|
51561
|
+
* Serializes a path without rewriting reserved data. Raw RFC 3986 path
|
|
51562
|
+
* characters remain literal, valid escapes are preserved and uppercased, and
|
|
51563
|
+
* everything else is UTF-8 percent-encoded. In a path-noscheme, a colon in the
|
|
51564
|
+
* first segment must be escaped so the result cannot be parsed as a scheme.
|
|
51565
|
+
*
|
|
51566
|
+
* @param {string} input
|
|
51567
|
+
* @param {boolean} [pathNoScheme=false]
|
|
51568
|
+
* @returns {string}
|
|
51569
|
+
*/
|
|
51570
|
+
function serializePathEncoding (input, pathNoScheme = false) {
|
|
51571
|
+
let output = ''
|
|
51572
|
+
let firstSegment = pathNoScheme && input[0] !== '/'
|
|
51573
|
+
|
|
51574
|
+
for (let i = 0; i < input.length; i++) {
|
|
51575
|
+
const ch = input[i]
|
|
51576
|
+
if (ch === '%' && i + 2 < input.length) {
|
|
51577
|
+
const hex = input.slice(i + 1, i + 3)
|
|
51578
|
+
if (isHexPair(hex)) {
|
|
51579
|
+
output += '%' + hex.toUpperCase()
|
|
51580
|
+
i += 2
|
|
51581
|
+
continue
|
|
51582
|
+
}
|
|
51583
|
+
}
|
|
51584
|
+
|
|
51585
|
+
if (ch === '/') {
|
|
51586
|
+
firstSegment = false
|
|
51587
|
+
}
|
|
51588
|
+
|
|
51589
|
+
if (isPathCharacter(ch) && (ch !== ':' || !firstSegment)) {
|
|
51590
|
+
output += ch
|
|
51591
|
+
} else {
|
|
51592
|
+
const code = input.charCodeAt(i)
|
|
51593
|
+
if (code < 0x80) {
|
|
51594
|
+
output += BYTE_HEX[code]
|
|
51595
|
+
} else if (code < 0xD800 || code > 0xDFFF) {
|
|
51596
|
+
output += percentEncodeNonAscii(code)
|
|
51597
|
+
} else if (code <= 0xDBFF && i + 1 < input.length) {
|
|
51598
|
+
const low = input.charCodeAt(i + 1)
|
|
51599
|
+
if (low >= 0xDC00 && low <= 0xDFFF) {
|
|
51600
|
+
output += percentEncodeNonAscii(0x10000 + ((code - 0xD800) << 10) + (low - 0xDC00))
|
|
51601
|
+
i++
|
|
51602
|
+
} else {
|
|
51603
|
+
output += percentEncodeNonAscii(0xFFFD)
|
|
51604
|
+
}
|
|
51605
|
+
} else {
|
|
51606
|
+
output += percentEncodeNonAscii(0xFFFD)
|
|
51607
|
+
}
|
|
51608
|
+
}
|
|
50950
51609
|
}
|
|
50951
|
-
|
|
50952
|
-
|
|
51610
|
+
|
|
51611
|
+
return output
|
|
51612
|
+
}
|
|
51613
|
+
|
|
51614
|
+
/**
|
|
51615
|
+
* Percent-encodes a URI component using its RFC 3986 literal character set.
|
|
51616
|
+
* Existing valid escapes are preserved and normalized to uppercase hex.
|
|
51617
|
+
*
|
|
51618
|
+
* @param {string} input
|
|
51619
|
+
* @param {(value: string) => boolean} isAllowed
|
|
51620
|
+
* @returns {string}
|
|
51621
|
+
*/
|
|
51622
|
+
function encodeComponent (input, isAllowed) {
|
|
51623
|
+
let output = ''
|
|
51624
|
+
|
|
51625
|
+
for (let i = 0; i < input.length; i++) {
|
|
51626
|
+
const ch = input[i]
|
|
51627
|
+
if (ch === '%' && i + 2 < input.length) {
|
|
51628
|
+
const hex = input.slice(i + 1, i + 3)
|
|
51629
|
+
if (isHexPair(hex)) {
|
|
51630
|
+
output += '%' + hex.toUpperCase()
|
|
51631
|
+
i += 2
|
|
51632
|
+
continue
|
|
51633
|
+
}
|
|
51634
|
+
}
|
|
51635
|
+
|
|
51636
|
+
if (isAllowed(ch)) {
|
|
51637
|
+
output += ch
|
|
51638
|
+
} else {
|
|
51639
|
+
const code = input.charCodeAt(i)
|
|
51640
|
+
if (code < 0x80) {
|
|
51641
|
+
output += BYTE_HEX[code]
|
|
51642
|
+
} else if (code < 0xD800 || code > 0xDFFF) {
|
|
51643
|
+
output += percentEncodeNonAscii(code)
|
|
51644
|
+
} else if (code <= 0xDBFF && i + 1 < input.length) {
|
|
51645
|
+
const low = input.charCodeAt(i + 1)
|
|
51646
|
+
if (low >= 0xDC00 && low <= 0xDFFF) {
|
|
51647
|
+
output += percentEncodeNonAscii(0x10000 + ((code - 0xD800) << 10) + (low - 0xDC00))
|
|
51648
|
+
i++
|
|
51649
|
+
} else {
|
|
51650
|
+
output += percentEncodeNonAscii(0xFFFD)
|
|
51651
|
+
}
|
|
51652
|
+
} else {
|
|
51653
|
+
output += percentEncodeNonAscii(0xFFFD)
|
|
51654
|
+
}
|
|
51655
|
+
}
|
|
50953
51656
|
}
|
|
50954
|
-
|
|
50955
|
-
|
|
51657
|
+
|
|
51658
|
+
return output
|
|
51659
|
+
}
|
|
51660
|
+
|
|
51661
|
+
/**
|
|
51662
|
+
* Encodes userinfo while preserving its RFC 3986 §3.2.1 literal characters.
|
|
51663
|
+
* In particular, authority delimiters such as `@`, `/`, `?`, and `#` are data.
|
|
51664
|
+
*
|
|
51665
|
+
* @param {string} input
|
|
51666
|
+
* @returns {string}
|
|
51667
|
+
*/
|
|
51668
|
+
function encodeUserinfo (input) {
|
|
51669
|
+
return encodeComponent(input, isUserinfoCharacter)
|
|
51670
|
+
}
|
|
51671
|
+
|
|
51672
|
+
/**
|
|
51673
|
+
* Encodes query data using the RFC 3986 §3.4 grammar. A literal `#` must be
|
|
51674
|
+
* escaped because it would otherwise begin the fragment component.
|
|
51675
|
+
*
|
|
51676
|
+
* @param {string} input
|
|
51677
|
+
* @returns {string}
|
|
51678
|
+
*/
|
|
51679
|
+
function encodeQuery (input) {
|
|
51680
|
+
return encodeComponent(input, isQueryFragmentCharacter)
|
|
51681
|
+
}
|
|
51682
|
+
|
|
51683
|
+
/**
|
|
51684
|
+
* Encodes fragment data using the RFC 3986 §3.5 grammar.
|
|
51685
|
+
*
|
|
51686
|
+
* @param {string} input
|
|
51687
|
+
* @returns {string}
|
|
51688
|
+
*/
|
|
51689
|
+
function encodeFragment (input) {
|
|
51690
|
+
return encodeComponent(input, isQueryFragmentCharacter)
|
|
51691
|
+
}
|
|
51692
|
+
|
|
51693
|
+
function isEscapeSafe (cp) {
|
|
51694
|
+
return (
|
|
51695
|
+
(cp >= 0x30 && cp <= 0x39) ||
|
|
51696
|
+
(cp >= 0x41 && cp <= 0x5A) ||
|
|
51697
|
+
(cp >= 0x61 && cp <= 0x7A) ||
|
|
51698
|
+
cp === 0x2A || cp === 0x2B || cp === 0x2D || cp === 0x2E ||
|
|
51699
|
+
cp === 0x2F || cp === 0x40 || cp === 0x5F
|
|
51700
|
+
)
|
|
51701
|
+
}
|
|
51702
|
+
|
|
51703
|
+
/**
|
|
51704
|
+
* Normalizes the percent-encoding of a query or fragment component.
|
|
51705
|
+
*
|
|
51706
|
+
* Like `normalizePathEncoding`, but uses the query/fragment character set
|
|
51707
|
+
* (which additionally allows `?`) and decodes `.` since it has no dot-segment
|
|
51708
|
+
* meaning outside of a path.
|
|
51709
|
+
*
|
|
51710
|
+
* @param {string} input
|
|
51711
|
+
* @returns {string}
|
|
51712
|
+
*/
|
|
51713
|
+
function normalizeQueryFragmentEncoding (input) {
|
|
51714
|
+
let output = ''
|
|
51715
|
+
|
|
51716
|
+
for (let i = 0; i < input.length; i++) {
|
|
51717
|
+
const ch = input[i]
|
|
51718
|
+
if (ch === '%' && i + 2 < input.length) {
|
|
51719
|
+
const hex = input.slice(i + 1, i + 3)
|
|
51720
|
+
if (isHexPair(hex)) {
|
|
51721
|
+
const normalizedHex = hex.toUpperCase()
|
|
51722
|
+
const decoded = String.fromCharCode(parseInt(normalizedHex, 16))
|
|
51723
|
+
|
|
51724
|
+
if (isUnreserved(decoded)) {
|
|
51725
|
+
output += decoded
|
|
51726
|
+
} else {
|
|
51727
|
+
output += '%' + normalizedHex
|
|
51728
|
+
}
|
|
51729
|
+
|
|
51730
|
+
i += 2
|
|
51731
|
+
continue
|
|
51732
|
+
}
|
|
51733
|
+
}
|
|
51734
|
+
|
|
51735
|
+
if (isQueryFragmentCharacter(ch)) {
|
|
51736
|
+
output += ch
|
|
51737
|
+
} else {
|
|
51738
|
+
const code = input.charCodeAt(i)
|
|
51739
|
+
if (code < 0x80) {
|
|
51740
|
+
output += isEscapeSafe(code) ? ch : BYTE_HEX[code]
|
|
51741
|
+
} else if (code < 0xD800 || code > 0xDFFF) {
|
|
51742
|
+
output += percentEncodeNonAscii(code)
|
|
51743
|
+
} else if (code <= 0xDBFF && i + 1 < input.length) {
|
|
51744
|
+
const low = input.charCodeAt(i + 1)
|
|
51745
|
+
if (low >= 0xDC00 && low <= 0xDFFF) {
|
|
51746
|
+
output += percentEncodeNonAscii(0x10000 + ((code - 0xD800) << 10) + (low - 0xDC00))
|
|
51747
|
+
i++
|
|
51748
|
+
} else {
|
|
51749
|
+
output += percentEncodeNonAscii(0xFFFD)
|
|
51750
|
+
}
|
|
51751
|
+
} else {
|
|
51752
|
+
output += percentEncodeNonAscii(0xFFFD)
|
|
51753
|
+
}
|
|
51754
|
+
}
|
|
50956
51755
|
}
|
|
50957
|
-
|
|
51756
|
+
|
|
51757
|
+
return output
|
|
51758
|
+
}
|
|
51759
|
+
|
|
51760
|
+
/**
|
|
51761
|
+
* Escapes a component while preserving existing valid percent escapes.
|
|
51762
|
+
*
|
|
51763
|
+
* @param {string} input
|
|
51764
|
+
* @returns {string}
|
|
51765
|
+
*/
|
|
51766
|
+
function escapePreservingEscapes (input) {
|
|
51767
|
+
let output = ''
|
|
51768
|
+
|
|
51769
|
+
for (let i = 0; i < input.length; i++) {
|
|
51770
|
+
if (input[i] === '%' && i + 2 < input.length) {
|
|
51771
|
+
const hex = input.slice(i + 1, i + 3)
|
|
51772
|
+
if (isHexPair(hex)) {
|
|
51773
|
+
output += '%' + hex.toUpperCase()
|
|
51774
|
+
i += 2
|
|
51775
|
+
continue
|
|
51776
|
+
}
|
|
51777
|
+
}
|
|
51778
|
+
|
|
51779
|
+
output += escape(input[i])
|
|
51780
|
+
}
|
|
51781
|
+
|
|
51782
|
+
return output
|
|
50958
51783
|
}
|
|
50959
51784
|
|
|
50960
51785
|
/**
|
|
@@ -50965,18 +51790,24 @@ function recomposeAuthority (component) {
|
|
|
50965
51790
|
const uriTokens = []
|
|
50966
51791
|
|
|
50967
51792
|
if (component.userinfo !== undefined) {
|
|
50968
|
-
uriTokens.push(component.userinfo)
|
|
51793
|
+
uriTokens.push(encodeUserinfo(component.userinfo))
|
|
50969
51794
|
uriTokens.push('@')
|
|
50970
51795
|
}
|
|
50971
51796
|
|
|
50972
51797
|
if (component.host !== undefined) {
|
|
50973
|
-
let host =
|
|
51798
|
+
let host = component.host
|
|
50974
51799
|
if (!isIPv4(host)) {
|
|
50975
|
-
|
|
50976
|
-
if (ipV6res.isIPV6
|
|
51800
|
+
let ipV6res = normalizeIPv6(host)
|
|
51801
|
+
if (ipV6res.isIPV6 !== true && ipV6res.isIPVFuture !== true) {
|
|
51802
|
+
// Decode only unreserved bytes, once. In particular, keep %25 encoded
|
|
51803
|
+
// so it cannot introduce a second escape during recomposition.
|
|
51804
|
+
host = normalizePercentEncoding(host, true)
|
|
51805
|
+
ipV6res = normalizeIPv6(host)
|
|
51806
|
+
}
|
|
51807
|
+
if (ipV6res.isIPV6 === true || ipV6res.isIPVFuture === true) {
|
|
50977
51808
|
host = `[${ipV6res.escapedHost}]`
|
|
50978
51809
|
} else {
|
|
50979
|
-
host =
|
|
51810
|
+
host = reescapeHostDelimiters(host, false)
|
|
50980
51811
|
}
|
|
50981
51812
|
}
|
|
50982
51813
|
uriTokens.push(host)
|
|
@@ -50993,7 +51824,15 @@ function recomposeAuthority (component) {
|
|
|
50993
51824
|
module.exports = {
|
|
50994
51825
|
nonSimpleDomain,
|
|
50995
51826
|
recomposeAuthority,
|
|
50996
|
-
|
|
51827
|
+
reescapeHostDelimiters,
|
|
51828
|
+
normalizePercentEncoding,
|
|
51829
|
+
normalizePathEncoding,
|
|
51830
|
+
serializePathEncoding,
|
|
51831
|
+
normalizeQueryFragmentEncoding,
|
|
51832
|
+
encodeUserinfo,
|
|
51833
|
+
encodeQuery,
|
|
51834
|
+
encodeFragment,
|
|
51835
|
+
escapePreservingEscapes,
|
|
50997
51836
|
removeDotSegments,
|
|
50998
51837
|
isIPv4,
|
|
50999
51838
|
isUUID,
|
|
@@ -71946,7 +72785,7 @@ module.exports = /*#__PURE__*/JSON.parse('{"$schema":"http://json-schema.org/dra
|
|
|
71946
72785
|
/***/ ((module) => {
|
|
71947
72786
|
|
|
71948
72787
|
"use strict";
|
|
71949
|
-
module.exports = /*#__PURE__*/JSON.parse('{"name":"@pathmode/mcp-server","version":"1.20.
|
|
72788
|
+
module.exports = /*#__PURE__*/JSON.parse('{"name":"@pathmode/mcp-server","version":"1.20.2","publishConfig":{"access":"public"},"mcpName":"io.github.pathmodeio/mcp-server","description":"Deterministic intent preflight before your agent builds: six calibrated gates, keyless, no model call. Draft and sharpen specs in conversation, or connect a Pathmode workspace to sync intent and evidence across a team.","main":"dist/index.js","bin":{"pathmode-mcp":"dist/index.js"},"files":["dist/","manifest.json","icon.svg","README.md","skills/"],"scripts":{"build":"rm -rf dist && ncc build src/index.ts -o dist","dev":"ts-node src/index.ts","prepublishOnly":"npm run build"},"keywords":["pathmode","mcp","model-context-protocol","claude-code","claude-code-skills","agent-skills","cursor","windsurf","intent-engineering","intent-compiler","ai-agents","product-development","dependency-graph","strategic-planning"],"author":"Pathmode","license":"MIT","type":"commonjs","engines":{"node":">=18.0.0"},"homepage":"https://pathmode.io","dependencies":{"@modelcontextprotocol/sdk":"^1.12.1","gray-matter":"^4.0.3","zod":"^3.24.0"},"overrides":{"@hono/node-server":"^1.19.17","body-parser":"^2.3.0","fast-uri":"^3.1.6","hono":"^4.13.5","ip-address":"^10.7.0","js-yaml":"^3.15.2"},"devDependencies":{"@types/node":"^25.1.0","@vercel/ncc":"^0.38.4","ts-node":"^10.9.2","typescript":"^5.9.3"}}');
|
|
71950
72789
|
|
|
71951
72790
|
/***/ })
|
|
71952
72791
|
|
|
@@ -72406,6 +73245,16 @@ function startMcpServer() {
|
|
|
72406
73245
|
return { content: [{ type: 'text', text: `Failed to fetch intent: ${e.message}` }] };
|
|
72407
73246
|
}
|
|
72408
73247
|
});
|
|
73248
|
+
const outcomeInputShape = zod_1.z.union([
|
|
73249
|
+
zod_1.z.string().trim().min(1),
|
|
73250
|
+
zod_1.z.object({
|
|
73251
|
+
id: zod_1.z.string().trim().min(1).optional(),
|
|
73252
|
+
text: zod_1.z.string().trim().min(1),
|
|
73253
|
+
priority: zod_1.z.enum(['must', 'should', 'could']).optional(),
|
|
73254
|
+
/** Null is meaningful on update: deliberately remove the stored recipe. */
|
|
73255
|
+
measurement: measurement_schema_1.outcomeMeasurementDefinitionShape.nullable().optional(),
|
|
73256
|
+
}),
|
|
73257
|
+
]);
|
|
72409
73258
|
cloudOnly.registerTool('get_intent_relations', {
|
|
72410
73259
|
title: 'Get Intent Relations',
|
|
72411
73260
|
description: 'Get the dependency graph for a specific intent. Shows what it depends on, enables, or blocks.',
|
|
@@ -72974,7 +73823,7 @@ function startMcpServer() {
|
|
|
72974
73823
|
objective: zod_1.z.string().describe('Why this matters — the problem and who has it'),
|
|
72975
73824
|
currentState: zod_1.z.string().optional().describe('How this works today, before the change: existing behavior, the workaround users rely on, what is broken. Tells implementing agents what must not regress. Omit for genuinely net-new capability.'),
|
|
72976
73825
|
productId: zod_1.z.string().optional().describe('Product (Space) ID this intent belongs to. Omit it and the server resolves it when the workspace has exactly one real product. If it cannot, you get code PRODUCT_REQUIRED with the candidate list: ASK THE USER which product, then retry with an explicit id. Do not guess — a GitHub binding hangs off the product, so the product decides which repository the PR stamp and merge verification apply to, and a wrong guess surfaces much later as a merge that silently fails to verify.'),
|
|
72977
|
-
outcomes: zod_1.z.array(
|
|
73826
|
+
outcomes: zod_1.z.array(outcomeInputShape).optional().describe('Observable, testable state changes. Use the structured form to attach a stable saved-query recipe; for PostHog, source.queryRef is typically kind "saved_insight" plus the insight id.'),
|
|
72978
73827
|
constraints: zod_1.z.array(zod_1.z.string()).optional().describe('Hard limits the implementation must respect'),
|
|
72979
73828
|
healthMetrics: zod_1.z.array(zod_1.z.string()).optional().describe('What to monitor after shipping'),
|
|
72980
73829
|
edgeCases: zod_1.z.array(zod_1.z.object({
|
|
@@ -73015,7 +73864,7 @@ function startMcpServer() {
|
|
|
73015
73864
|
title: zod_1.z.string().optional().describe('New title'),
|
|
73016
73865
|
objective: zod_1.z.string().optional().describe('Updated objective'),
|
|
73017
73866
|
currentState: zod_1.z.string().optional().describe('Updated as-is behavior (how it works today). Pass an empty string to clear it.'),
|
|
73018
|
-
outcomes: zod_1.z.array(
|
|
73867
|
+
outcomes: zod_1.z.array(outcomeInputShape).optional().describe('Replace all outcomes. Omit measurement on an outcome to preserve its stored recipe; send measurement: null to clear it.'),
|
|
73019
73868
|
constraints: zod_1.z.array(zod_1.z.string()).optional().describe('Replace all constraints'),
|
|
73020
73869
|
healthMetrics: zod_1.z.array(zod_1.z.string()).optional().describe('Replace all health metrics'),
|
|
73021
73870
|
edgeCases: zod_1.z.array(zod_1.z.object({
|
|
@@ -73246,7 +74095,7 @@ function startMcpServer() {
|
|
|
73246
74095
|
currentState: zod_1.z.string().optional().describe('How this works today, before the change — existing behavior and workarounds, so the implementation knows what must not regress. Omit for net-new capability.'),
|
|
73247
74096
|
implementationContext: zod_1.z.string().optional().describe('What the repo actually looks like where this change lands: the files and modules involved, what already exists, what it would touch, how to verify it. You are in the working tree — go read it. Markdown, sub-headings welcome. Advisory: it never changes the readiness verdict, it just stops the implementing agent from rediscovering the codebase.'),
|
|
73248
74097
|
productId: zod_1.z.string().optional().describe('Which product this intent belongs to (cloud mode only). Omit it and the server resolves it when the workspace has exactly one real product; if it cannot, the save fails with PRODUCT_REQUIRED and the candidate list, and you should ask the user which one and retry with an explicit id rather than guessing — a GitHub binding hangs off the product, so it decides which repository merge verification applies to.'),
|
|
73249
|
-
outcomes: zod_1.z.array(
|
|
74098
|
+
outcomes: zod_1.z.array(outcomeInputShape).describe('Observable, testable state changes. The structured form may carry an optional provider-neutral saved-query measurement recipe.'),
|
|
73250
74099
|
decisions: zod_1.z.array(zod_1.z.object({
|
|
73251
74100
|
choice: zod_1.z.string(),
|
|
73252
74101
|
ruledOut: zod_1.z.string().optional(),
|