feat: insert direct edit constraint solver stage
Adds resolveDirectEditConstraints between compileRoadModel and compileGeometry, per the parent design's data flow. The stage is an identity transform until the individual constraint kinds land: it returns empty road profiles, junction plans and handle manifest, so compileGeometry's geometry code is untouched and output stays byte-identical. Constraint planning is separated from solving up front. Only `exact` and `recheck` constraints may reach the solver; `disabled`, `pending`, `conflicted` and `stale` are reported as unapplied with a reason. SOLVED_KINDS starts empty and grows one entry per kind, so a partially delivered solver reports what it skipped instead of publishing half-solved geometry. diagnostic() moves to src/compile/diagnostics.js so the solver can share the compiler's diagnostic shape without depending on fs, which would break the pure-function boundary that lets preview, the full compile and the CLI export share one implementation. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3,7 +3,7 @@
|
|||||||
"name": "direct-edit-solver-api",
|
"name": "direct-edit-solver-api",
|
||||||
"title": "约束求解器与编辑 API",
|
"title": "约束求解器与编辑 API",
|
||||||
"description": "resolveDirectEditConstraints 纯函数边界,以及预览/保存/revision/rebase API",
|
"description": "resolveDirectEditConstraints 纯函数边界,以及预览/保存/revision/rebase API",
|
||||||
"status": "planning",
|
"status": "in_progress",
|
||||||
"dev_type": null,
|
"dev_type": null,
|
||||||
"scope": null,
|
"scope": null,
|
||||||
"package": null,
|
"package": null,
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
"road-compiler": "bin/road-compiler.js"
|
"road-compiler": "bin/road-compiler.js"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "node test/index.js && node test/native-road-edits.js && node test/road-revisions.js && node test/fixtures.js",
|
"test": "node test/index.js && node test/native-road-edits.js && node test/road-revisions.js && node test/direct-edit-solver.js && node test/fixtures.js",
|
||||||
"test:fixtures:update-baseline": "node test/update-fixture-baselines.js",
|
"test:fixtures:update-baseline": "node test/update-fixture-baselines.js",
|
||||||
"road:workbench": "node bin/road-workbench.js",
|
"road:workbench": "node bin/road-workbench.js",
|
||||||
"road:export": "node bin/road-compiler.js",
|
"road:export": "node bin/road-compiler.js",
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ const {
|
|||||||
const { LAYER_REGISTRY, manifestForArea, validatePublishedLayers } = require('./layer-manifest');
|
const { LAYER_REGISTRY, manifestForArea, validatePublishedLayers } = require('./layer-manifest');
|
||||||
const { loadOrGenerate, runtime } = require('../native-traffic-signals');
|
const { loadOrGenerate, runtime } = require('../native-traffic-signals');
|
||||||
const { readAreaConfigSnapshot } = require('./road-revisions');
|
const { readAreaConfigSnapshot } = require('./road-revisions');
|
||||||
|
const { loadEditDocument } = require('./native-road-edits');
|
||||||
|
const { resolveDirectEditConstraints } = require('./direct-edit-solver');
|
||||||
|
|
||||||
function compileInput(input) {
|
function compileInput(input) {
|
||||||
validateInput(input);
|
validateInput(input);
|
||||||
@@ -40,10 +42,17 @@ function compileInput(input) {
|
|||||||
for (const item of validated.stale)
|
for (const item of validated.stale)
|
||||||
console.warn(`[warning] 忽略失效的 override(目标已不存在):${item.id} -> ${item.target}`);
|
console.warn(`[warning] 忽略失效的 override(目标已不存在):${item.id} -> ${item.target}`);
|
||||||
fs.mkdirSync(area.outputs.pipelineDir, { recursive: true });
|
fs.mkdirSync(area.outputs.pipelineDir, { recursive: true });
|
||||||
|
// Direct edits resolve against the model *after* v1 overrides, then feed
|
||||||
|
// compileGeometry. An area with no v2 document resolves to an empty result,
|
||||||
|
// which leaves the geometry byte-identical to a compile without this stage.
|
||||||
|
const directEdits = input.editsFile ? loadEditDocument(input.editsFile) : null;
|
||||||
|
const directEdit = resolveDirectEditConstraints(model, directEdits);
|
||||||
const compiled = compileGeometry(model, overrides, {
|
const compiled = compileGeometry(model, overrides, {
|
||||||
edgeLines: area.nativeRoad.edgeLines,
|
edgeLines: area.nativeRoad.edgeLines,
|
||||||
junctionTemplates: area.nativeRoad.junctionTemplates,
|
junctionTemplates: area.nativeRoad.junctionTemplates,
|
||||||
|
directEdit,
|
||||||
});
|
});
|
||||||
|
compiled.diagnostics.push(...directEdit.diagnostics);
|
||||||
compiled.diagnostics.push(
|
compiled.diagnostics.push(
|
||||||
...validated.stale.map((item) => ({
|
...validated.stale.map((item) => ({
|
||||||
id: `diagnostic:stale-override:${item.id}`,
|
id: `diagnostic:stale-override:${item.id}`,
|
||||||
@@ -169,6 +178,8 @@ function validateInput(input) {
|
|||||||
(typeof input.areaConfigSnapshotFile !== 'string' || !input.areaConfigSnapshotFile)
|
(typeof input.areaConfigSnapshotFile !== 'string' || !input.areaConfigSnapshotFile)
|
||||||
)
|
)
|
||||||
throw new Error('RoadCompilerInput.areaConfigSnapshotFile must be a non-empty string when present');
|
throw new Error('RoadCompilerInput.areaConfigSnapshotFile must be a non-empty string when present');
|
||||||
|
if (input.editsFile !== undefined && (typeof input.editsFile !== 'string' || !input.editsFile))
|
||||||
|
throw new Error('RoadCompilerInput.editsFile must be a non-empty string when present');
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateOptions(options) {
|
function validateOptions(options) {
|
||||||
|
|||||||
19
src/compile/diagnostics.js
Normal file
19
src/compile/diagnostics.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
// Shared so the geometry compiler and the constraint solver report problems in
|
||||||
|
// one shape. The solver must stay free of `fs`, which rules out reaching into
|
||||||
|
// native-road.js for this, and re-declaring the shape there would let the two
|
||||||
|
// drift apart field by field.
|
||||||
|
function diagnostic(severity, subjectId, sourceIds, rule, message, coordinate) {
|
||||||
|
return {
|
||||||
|
id: `diagnostic:${rule}:${subjectId}`,
|
||||||
|
severity,
|
||||||
|
subjectId,
|
||||||
|
sourceIds,
|
||||||
|
rule,
|
||||||
|
message,
|
||||||
|
geometry: coordinate ? { type: 'Point', coordinates: coordinate } : null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { diagnostic };
|
||||||
85
src/compile/direct-edit-solver.js
Normal file
85
src/compile/direct-edit-solver.js
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const { diagnostic } = require('./diagnostics');
|
||||||
|
|
||||||
|
const RESOLUTION_SCHEMA = 'road-edit-resolution/v1';
|
||||||
|
const HANDLE_MANIFEST_SCHEMA = 'road-edit-handles/v1';
|
||||||
|
|
||||||
|
// Only these two statuses may reshape geometry. `pending`, `conflicted` and
|
||||||
|
// `stale` are reported and skipped on purpose: applying a constraint whose
|
||||||
|
// anchor no longer resolves would silently reshape the wrong road, which is the
|
||||||
|
// exact failure the replay rules exist to prevent. Matching decides the status;
|
||||||
|
// this module only obeys it.
|
||||||
|
const ACTIVE_STATUSES = new Set(['exact', 'recheck']);
|
||||||
|
|
||||||
|
// Grows one entry per kind as the solver learns to apply it. A kind absent here
|
||||||
|
// is reported as unapplied rather than approximated, so a partially delivered
|
||||||
|
// solver never publishes half-solved geometry.
|
||||||
|
const SOLVED_KINDS = new Set();
|
||||||
|
|
||||||
|
function emptyHandleManifest(context) {
|
||||||
|
return {
|
||||||
|
schema: HANDLE_MANIFEST_SCHEMA,
|
||||||
|
revisionId: typeof context.revisionId === 'string' ? context.revisionId : null,
|
||||||
|
previewSeq: Number.isInteger(context.previewSeq) ? context.previewSeq : 0,
|
||||||
|
handles: [],
|
||||||
|
reserves: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function constraintState(constraint, applied, reason) {
|
||||||
|
return { constraintId: constraint.id, kind: constraint.kind, status: constraint.status, applied, reason };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Phase one: decide which constraints are allowed to touch geometry and record
|
||||||
|
// why the rest are not. Keeping this separate from solving is what stops a
|
||||||
|
// diagnostic and a geometry decision from being derived from each other.
|
||||||
|
function planConstraints(constraints, diagnostics) {
|
||||||
|
return constraints.map((constraint) => {
|
||||||
|
if (!constraint.enabled) return constraintState(constraint, false, 'disabled');
|
||||||
|
if (!ACTIVE_STATUSES.has(constraint.status))
|
||||||
|
return constraintState(constraint, false, `status-${constraint.status}`);
|
||||||
|
if (!SOLVED_KINDS.has(constraint.kind)) {
|
||||||
|
diagnostics.push(
|
||||||
|
diagnostic(
|
||||||
|
'info',
|
||||||
|
`constraint:${constraint.id}`,
|
||||||
|
[],
|
||||||
|
'direct-edit-kind-not-solved',
|
||||||
|
`直接编辑约束 ${constraint.kind} 尚未接入求解器,本次编译按基线几何输出。`,
|
||||||
|
null,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return constraintState(constraint, false, 'kind-not-solved');
|
||||||
|
}
|
||||||
|
return constraintState(constraint, true, null);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pure: no file or network access, so preview, the full compile and the CLI
|
||||||
|
// export can share one implementation and unit fixtures can drive it directly.
|
||||||
|
// `model` is the road model after v1 overrides are applied; `editDocument` is a
|
||||||
|
// validated `native-road-edits/v2` document, or null when an area has no edits.
|
||||||
|
function resolveDirectEditConstraints(model, editDocument = null, context = {}) {
|
||||||
|
const diagnostics = [];
|
||||||
|
const constraints = Array.isArray(editDocument && editDocument.constraints) ? editDocument.constraints : [];
|
||||||
|
return {
|
||||||
|
schema: RESOLUTION_SCHEMA,
|
||||||
|
// Empty means "use the baseline cross-section / junction geometry". Later
|
||||||
|
// steps populate these per road and per node; an empty resolution is an
|
||||||
|
// identity transform over compileGeometry by construction.
|
||||||
|
roadProfiles: new Map(),
|
||||||
|
junctionPlans: new Map(),
|
||||||
|
handles: emptyHandleManifest(context),
|
||||||
|
constraintStates: planConstraints(constraints, diagnostics),
|
||||||
|
diagnostics,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
RESOLUTION_SCHEMA,
|
||||||
|
HANDLE_MANIFEST_SCHEMA,
|
||||||
|
ACTIVE_STATUSES,
|
||||||
|
SOLVED_KINDS,
|
||||||
|
resolveDirectEditConstraints,
|
||||||
|
};
|
||||||
@@ -4,6 +4,7 @@ const fs = require('fs');
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const { arrowRingsAt, normalizeManeuver } = require('./turn-lane-arrows');
|
const { arrowRingsAt, normalizeManeuver } = require('./turn-lane-arrows');
|
||||||
const { buildComplexJunctionGeometry, complexJunctionMetrics } = require('./complex-junction');
|
const { buildComplexJunctionGeometry, complexJunctionMetrics } = require('./complex-junction');
|
||||||
|
const { diagnostic } = require('./diagnostics');
|
||||||
|
|
||||||
const OVERRIDE_SCHEMA = 'native-road-overrides/v1';
|
const OVERRIDE_SCHEMA = 'native-road-overrides/v1';
|
||||||
const MOTOR_HIGHWAYS = new Set([
|
const MOTOR_HIGHWAYS = new Set([
|
||||||
@@ -3089,17 +3090,6 @@ function unproject(point, origin) {
|
|||||||
const scale = 111320;
|
const scale = 111320;
|
||||||
return [point[0] / (scale * Math.cos((origin[1] * Math.PI) / 180)) + origin[0], point[1] / scale + origin[1]];
|
return [point[0] / (scale * Math.cos((origin[1] * Math.PI) / 180)) + origin[0], point[1] / scale + origin[1]];
|
||||||
}
|
}
|
||||||
function diagnostic(severity, subjectId, sourceIds, rule, message, coordinate) {
|
|
||||||
return {
|
|
||||||
id: `diagnostic:${rule}:${subjectId}`,
|
|
||||||
severity,
|
|
||||||
subjectId,
|
|
||||||
sourceIds,
|
|
||||||
rule,
|
|
||||||
message,
|
|
||||||
geometry: coordinate ? { type: 'Point', coordinates: coordinate } : null,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
function xmlAttrs(text) {
|
function xmlAttrs(text) {
|
||||||
const attrs = {};
|
const attrs = {};
|
||||||
for (const match of text.matchAll(/([:\w-]+)\s*=\s*(?:"([^"]*)"|'([^']*)')/g)) attrs[match[1]] = match[2] ?? match[3];
|
for (const match of text.matchAll(/([:\w-]+)\s*=\s*(?:"([^"]*)"|'([^']*)')/g)) attrs[match[1]] = match[2] ?? match[3];
|
||||||
|
|||||||
116
test/direct-edit-solver.js
Normal file
116
test/direct-edit-solver.js
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const assert = require('assert/strict');
|
||||||
|
const { resolveDirectEditConstraints, SOLVED_KINDS } = require('../src/compile/direct-edit-solver');
|
||||||
|
|
||||||
|
const model = { roads: [], endpoints: [], connections: [], diagnostics: [] };
|
||||||
|
|
||||||
|
function constraint(overrides = {}) {
|
||||||
|
return {
|
||||||
|
id: 'constraint-1',
|
||||||
|
kind: 'road-edge-offset',
|
||||||
|
anchor: { type: 'road-interval', roadId: 'road:way/1:forward', startStation: 0.3, endStation: 0.7 },
|
||||||
|
anchorSnapshot: {
|
||||||
|
coordinate: [113.1, 30.2],
|
||||||
|
tangentAzimuth: 45,
|
||||||
|
roadLengthMeters: 120,
|
||||||
|
osmNodeIds: ['1', '2'],
|
||||||
|
},
|
||||||
|
value: { offsetMeters: 1.5, transition: 'smoothstep' },
|
||||||
|
enabled: true,
|
||||||
|
status: 'exact',
|
||||||
|
provenance: { operationId: 'operation-1', createdAt: '2026-08-27T00:00:00.000Z' },
|
||||||
|
...overrides,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function document(constraints) {
|
||||||
|
return { schema: 'native-road-edits/v2', documentVersion: 1, base: {}, constraints, operations: [] };
|
||||||
|
}
|
||||||
|
|
||||||
|
// A missing or empty document must resolve to the identity: this is what keeps
|
||||||
|
// the stage insertable ahead of compileGeometry without moving any geometry.
|
||||||
|
for (const empty of [null, undefined, document([])]) {
|
||||||
|
const resolution = resolveDirectEditConstraints(model, empty);
|
||||||
|
assert.equal(resolution.schema, 'road-edit-resolution/v1');
|
||||||
|
assert.equal(resolution.roadProfiles.size, 0);
|
||||||
|
assert.equal(resolution.junctionPlans.size, 0);
|
||||||
|
assert.deepEqual(resolution.constraintStates, []);
|
||||||
|
assert.deepEqual(resolution.diagnostics, []);
|
||||||
|
assert.equal(resolution.handles.schema, 'road-edit-handles/v1');
|
||||||
|
assert.deepEqual(resolution.handles.handles, []);
|
||||||
|
assert.deepEqual(resolution.handles.reserves, []);
|
||||||
|
assert.equal(resolution.handles.revisionId, null);
|
||||||
|
assert.equal(resolution.handles.previewSeq, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle manifest context is echoed so a preview response can be matched to the
|
||||||
|
// request that asked for it.
|
||||||
|
const withContext = resolveDirectEditConstraints(model, null, { revisionId: 'rev-0007', previewSeq: 42 });
|
||||||
|
assert.equal(withContext.handles.revisionId, 'rev-0007');
|
||||||
|
assert.equal(withContext.handles.previewSeq, 42);
|
||||||
|
|
||||||
|
// A disabled constraint is reported, never applied, and produces no diagnostic:
|
||||||
|
// the user turned it off deliberately, so it is not a problem to report.
|
||||||
|
const disabled = resolveDirectEditConstraints(model, document([constraint({ enabled: false })]));
|
||||||
|
assert.deepEqual(disabled.constraintStates, [
|
||||||
|
{ constraintId: 'constraint-1', kind: 'road-edge-offset', status: 'exact', applied: false, reason: 'disabled' },
|
||||||
|
]);
|
||||||
|
assert.deepEqual(disabled.diagnostics, []);
|
||||||
|
|
||||||
|
// Only `exact` and `recheck` may reach the solver. The other three statuses mean
|
||||||
|
// the anchor is not trustworthy, so they are skipped with the status as reason.
|
||||||
|
for (const status of ['pending', 'conflicted', 'stale']) {
|
||||||
|
const resolution = resolveDirectEditConstraints(model, document([constraint({ status })]));
|
||||||
|
assert.deepEqual(resolution.constraintStates, [
|
||||||
|
{ constraintId: 'constraint-1', kind: 'road-edge-offset', status, applied: false, reason: `status-${status}` },
|
||||||
|
]);
|
||||||
|
assert.deepEqual(resolution.diagnostics, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Until a kind is wired into the solver its constraints are reported as
|
||||||
|
// unapplied with an explicit diagnostic, rather than approximated or ignored.
|
||||||
|
for (const status of ['exact', 'recheck']) {
|
||||||
|
const resolution = resolveDirectEditConstraints(model, document([constraint({ status })]));
|
||||||
|
const expected = SOLVED_KINDS.has('road-edge-offset')
|
||||||
|
? { applied: true, reason: null, diagnostics: 0 }
|
||||||
|
: { applied: false, reason: 'kind-not-solved', diagnostics: 1 };
|
||||||
|
assert.deepEqual(resolution.constraintStates, [
|
||||||
|
{
|
||||||
|
constraintId: 'constraint-1',
|
||||||
|
kind: 'road-edge-offset',
|
||||||
|
status,
|
||||||
|
applied: expected.applied,
|
||||||
|
reason: expected.reason,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
assert.equal(resolution.diagnostics.length, expected.diagnostics);
|
||||||
|
for (const item of resolution.diagnostics) {
|
||||||
|
assert.equal(item.severity, 'info');
|
||||||
|
assert.equal(item.rule, 'direct-edit-kind-not-solved');
|
||||||
|
assert.equal(item.subjectId, 'constraint:constraint-1');
|
||||||
|
assert.equal(item.geometry, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Every constraint gets exactly one state, in document order, so a caller can
|
||||||
|
// report per-row status without re-deriving the mapping.
|
||||||
|
const many = resolveDirectEditConstraints(
|
||||||
|
model,
|
||||||
|
document([
|
||||||
|
constraint({ id: 'a', enabled: false }),
|
||||||
|
constraint({ id: 'b', status: 'stale' }),
|
||||||
|
constraint({ id: 'c' }),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
assert.deepEqual(
|
||||||
|
many.constraintStates.map((state) => state.constraintId),
|
||||||
|
['a', 'b', 'c'],
|
||||||
|
);
|
||||||
|
|
||||||
|
// The solver must be free of file and network access so preview can share it.
|
||||||
|
const source = require('fs').readFileSync(require.resolve('../src/compile/direct-edit-solver'), 'utf8');
|
||||||
|
for (const forbidden of ["require('fs')", "require('path')", "require('http')", "require('https')"])
|
||||||
|
assert.equal(source.includes(forbidden), false, `direct-edit-solver must not ${forbidden}`);
|
||||||
|
|
||||||
|
console.log('direct edit solver tests passed');
|
||||||
@@ -7,6 +7,7 @@ const os = require('os');
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const { nativeRoadEdits, roadRevisions } = require('../src');
|
const { nativeRoadEdits, roadRevisions } = require('../src');
|
||||||
const { compileInput } = require('../src/compile/compiler');
|
const { compileInput } = require('../src/compile/compiler');
|
||||||
|
const { snapshot: outputSnapshot } = require('./fixture-baseline');
|
||||||
|
|
||||||
const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'road-revisions-'));
|
const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'road-revisions-'));
|
||||||
const source = '<osm version="0.6"><node id="1" lon="114.1" lat="22.5"/></osm>\n';
|
const source = '<osm version="0.6"><node id="1" lon="114.1" lat="22.5"/></osm>\n';
|
||||||
@@ -127,5 +128,39 @@ assert.throws(
|
|||||||
/Area config snapshot is missing/,
|
/Area config snapshot is missing/,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// The workbench compile is the one caller that passes editsFile. With no v2
|
||||||
|
// constraints the direct-edit solver stage must be an identity transform, so
|
||||||
|
// both compiles have to publish byte-identical outputs.
|
||||||
|
const activeEditsFile = path.join(directory, 'active', 'native-road-edits.json');
|
||||||
|
assert.ok(fs.existsSync(activeEditsFile));
|
||||||
|
assert.deepEqual(nativeRoadEdits.loadEditDocument(activeEditsFile).constraints, []);
|
||||||
|
const identityBase = { ...compileBase, areaId: 'edits-identity-test' };
|
||||||
|
const withoutEdits = {
|
||||||
|
...identityBase,
|
||||||
|
outDir: path.join(directory, 'no-edits-output'),
|
||||||
|
stagingDir: path.join(directory, 'no-edits-pipeline'),
|
||||||
|
};
|
||||||
|
const withEdits = {
|
||||||
|
...identityBase,
|
||||||
|
outDir: path.join(directory, 'with-edits-output'),
|
||||||
|
stagingDir: path.join(directory, 'with-edits-pipeline'),
|
||||||
|
editsFile: activeEditsFile,
|
||||||
|
};
|
||||||
|
compileInput(withoutEdits);
|
||||||
|
compileInput(withEdits);
|
||||||
|
assert.deepEqual(
|
||||||
|
outputSnapshot(withEdits).files,
|
||||||
|
outputSnapshot(withoutEdits).files,
|
||||||
|
'an empty v2 document must not change compiled output',
|
||||||
|
);
|
||||||
|
assert.throws(
|
||||||
|
() => compileInput({ ...identityBase, editsFile: '' }),
|
||||||
|
/editsFile must be a non-empty string when present/,
|
||||||
|
);
|
||||||
|
assert.throws(
|
||||||
|
() => compileInput({ ...identityBase, editsFile: '' }),
|
||||||
|
/editsFile must be a non-empty string when present/,
|
||||||
|
);
|
||||||
|
|
||||||
fs.rmSync(directory, { recursive: true, force: true });
|
fs.rmSync(directory, { recursive: true, force: true });
|
||||||
console.log('road revision storage tests passed');
|
console.log('road revision storage tests passed');
|
||||||
|
|||||||
@@ -324,7 +324,11 @@ function readUpload(request, session) {
|
|||||||
function compileWorkbenchInput(input) {
|
function compileWorkbenchInput(input) {
|
||||||
const workspace = path.dirname(input.osmFile);
|
const workspace = path.dirname(input.osmFile);
|
||||||
const initialized = ensureRevisionStore(workspace, input.options);
|
const initialized = ensureRevisionStore(workspace, input.options);
|
||||||
return compileInput({ ...input, areaConfigSnapshotFile: initialized.paths.activeAreaConfig });
|
return compileInput({
|
||||||
|
...input,
|
||||||
|
areaConfigSnapshotFile: initialized.paths.activeAreaConfig,
|
||||||
|
editsFile: initialized.paths.activeEdits,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
function readMultipart(request, limit) {
|
function readMultipart(request, limit) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user