@dailephd/my-dev-kit-lab 0.6.2 → 0.6.3
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/CHANGELOG.md +18 -0
- package/README.md +9 -8
- package/dist/src/cli/help.js +16 -9
- package/dist/src/commands/runExperimentDescribeCommand.js +5 -2
- package/dist/src/commands/runExperimentRunCommand.js +0 -4
- package/dist/src/evaluation/benchmarkMetadata.js +5 -1
- package/dist/src/evaluation/parseAgentAnswer.js +172 -24
- package/dist/src/evaluation/runMyDevKitRetrieval.js +120 -7
- package/dist/src/experiments/plugins/incrementalChangeStaleness/comparisonV2.js +49 -0
- package/dist/src/experiments/plugins/incrementalChangeStaleness/config.js +5 -4
- package/dist/src/experiments/plugins/incrementalChangeStaleness/disposableTarget.js +20 -2
- package/dist/src/experiments/plugins/incrementalChangeStaleness/execution.js +42 -16
- package/dist/src/experiments/plugins/incrementalChangeStaleness/executionArtifact.js +4 -4
- package/dist/src/experiments/plugins/incrementalChangeStaleness/executionArtifactV2.js +289 -0
- package/dist/src/experiments/plugins/incrementalChangeStaleness/executionV2.js +195 -0
- package/dist/src/experiments/plugins/incrementalChangeStaleness/indexClone.js +76 -0
- package/dist/src/experiments/plugins/incrementalChangeStaleness/lifecycle.js +64 -32
- package/dist/src/experiments/plugins/incrementalChangeStaleness/lifecyclePolicy.js +6 -4
- package/dist/src/experiments/plugins/incrementalChangeStaleness/lifecyclePolicyV2.js +78 -0
- package/dist/src/experiments/plugins/incrementalChangeStaleness/lifecycleV2.js +298 -0
- package/dist/src/experiments/plugins/incrementalChangeStaleness/plugin.js +213 -12
- package/dist/src/experiments/plugins/incrementalChangeStaleness/treatmentSessionV2.js +5 -0
- package/dist/src/experiments/plugins/warmIndexReuse/warmIndexSession.js +3 -1
- package/dist/src/mobile/android/advancedSecurity/candidateEvidence.js +9 -2
- package/dist/src/mobile/android/advancedSecurity/redaction.js +19 -10
- package/dist/src/mobile/android/advancedSecurity/secretCandidates/analyzeSecretCandidates.js +12 -2
- package/dist/src/mobile/android/advancedSecurity/signingConfiguration/analyzeSigningConfiguration.js +1 -1
- package/dist/src/mobile/android/advancedSecurity/signingConfiguration/extractSigningConfigurations.js +7 -7
- package/dist/src/report/experiments/buildIncrementalChangeStalenessPluginReport.js +33 -0
- package/dist/src/report/experiments/buildIncrementalChangeStalenessReport.js +1 -1
- package/dist/src/report/experiments/buildIncrementalChangeStalenessReportV2.js +80 -0
- package/dist/src/report/experiments/buildPluginExperimentReport.js +2 -2
- package/dist/src/report/experiments/incrementalChangeStalenessReportModelV2.js +3 -0
- package/dist/src/report/experiments/incrementalChangeStalenessReportRowsV2.js +146 -0
- package/dist/src/report/experiments/index.js +3 -0
- package/dist/src/report/experiments/renderIncrementalChangeStalenessHtml.js +4 -0
- package/dist/src/report/experiments/renderIncrementalChangeStalenessHtmlV2.js +111 -0
- package/dist/src/report/experiments/renderIncrementalChangeStalenessTextV2.js +96 -0
- package/dist/src/report/experiments/renderPluginExperimentReportText.js +6 -0
- package/dist/src/securityValidation/staticScans/codeql.js +8 -8
- package/docs/METRICS.md +32 -0
- package/examples/tutorial-browser/prepare.mjs +13 -13
- package/examples/tutorial-browser/server.mjs +13 -13
- package/package.json +1 -1
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// v0.6.3 -- V2 text section for the incremental-change-staleness plugin report. Renders only persisted
|
|
2
|
+
// V2 report-model fields (via the shared row builders); no recomputation. Lists are bounded.
|
|
3
|
+
import { COMPARISON_PRESENTATION_ORDER, EVIDENCE_FAMILY_NOTE, SCENARIO_SUMMARY_ROWS, TREATMENT_PRESENTATION_ORDER, affectedNeighborhoodSummary, comparisonBlock, treatmentBlock } from "./incrementalChangeStalenessReportRowsV2.js";
|
|
4
|
+
function renderScenario(lines, scenario, h) {
|
|
5
|
+
h.pushSection(lines, `Scenario: ${scenario.scenarioId}`);
|
|
6
|
+
lines.push(h.fieldLine("Category", scenario.category));
|
|
7
|
+
lines.push(h.fieldLine("Status", scenario.status));
|
|
8
|
+
lines.push(h.fieldLine("Failure Reason", scenario.failureReason));
|
|
9
|
+
lines.push(h.fieldLine("Benchmark Project", scenario.benchmarkProjectId));
|
|
10
|
+
lines.push(h.fieldLine("Base Case", scenario.baseCaseId));
|
|
11
|
+
lines.push(h.fieldLine("Answer Policy", scenario.answerPolicy));
|
|
12
|
+
lines.push(h.fieldLine("Query", scenario.query));
|
|
13
|
+
h.pushBoundedStringList(lines, "Expected Files", scenario.expectedFiles);
|
|
14
|
+
h.pushBoundedStringList(lines, "Expected Symbols", scenario.expectedSymbols);
|
|
15
|
+
h.pushSection(lines, "Lifecycle");
|
|
16
|
+
const lifecycle = scenario.lifecycle;
|
|
17
|
+
if (!lifecycle) {
|
|
18
|
+
lines.push("Not available.");
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
lines.push(h.fieldLine("my-dev-kit Version", lifecycle.myDevKitVersion));
|
|
22
|
+
lines.push(h.fieldLine("Index Invocations (total)", lifecycle.totalIndexInvocationCount));
|
|
23
|
+
lines.push(h.fieldLine("Freshness Assessments (total)", lifecycle.totalFreshnessAssessmentCount));
|
|
24
|
+
for (const [treatmentId, result] of Object.entries(lifecycle.preMutationEquivalence)) {
|
|
25
|
+
lines.push(h.fieldLine(`Pre-Mutation Equivalence (${treatmentId} vs stale-index reference)`, result));
|
|
26
|
+
}
|
|
27
|
+
for (const [treatmentId, result] of Object.entries(lifecycle.postMutationEquivalence)) {
|
|
28
|
+
lines.push(h.fieldLine(`Post-Mutation Equivalence (${treatmentId} vs stale-index reference)`, result));
|
|
29
|
+
}
|
|
30
|
+
h.pushBoundedStringList(lines, "Controlled File Paths", lifecycle.controlledFilePaths);
|
|
31
|
+
h.pushBoundedStringList(lines, "Source Roots", lifecycle.sourceRoots);
|
|
32
|
+
}
|
|
33
|
+
h.pushSection(lines, "Affected-Neighborhood Evidence (changed baseline neighborhood relative to the task)");
|
|
34
|
+
const affected = affectedNeighborhoodSummary(scenario);
|
|
35
|
+
if (affected.state === "none") {
|
|
36
|
+
lines.push("Not available.");
|
|
37
|
+
}
|
|
38
|
+
else if (affected.state === "inconsistent") {
|
|
39
|
+
lines.push("inconsistent persisted evidence");
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
for (const [label, value] of affected.rows)
|
|
43
|
+
lines.push(h.fieldLine(label, value));
|
|
44
|
+
lines.push(h.fieldLine("Reindex Recommendation Explanation", affected.explanation));
|
|
45
|
+
}
|
|
46
|
+
lines.push(EVIDENCE_FAMILY_NOTE);
|
|
47
|
+
if (scenario.treatments.length === 0) {
|
|
48
|
+
h.pushSection(lines, "Treatments");
|
|
49
|
+
lines.push("Not produced: the scenario failed before any treatment evidence existed.");
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
for (const entry of TREATMENT_PRESENTATION_ORDER) {
|
|
53
|
+
const block = treatmentBlock(scenario, entry.id, entry.label);
|
|
54
|
+
h.pushSection(lines, `${block.label} Treatment (${entry.id})`);
|
|
55
|
+
if (!block.available) {
|
|
56
|
+
lines.push("Not available.");
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
for (const [label, value] of block.rows)
|
|
60
|
+
lines.push(h.fieldLine(label, value));
|
|
61
|
+
lines.push("Refresh Execution (upstream evidence of what my-dev-kit actually did):");
|
|
62
|
+
for (const [label, value] of block.refreshRows)
|
|
63
|
+
lines.push(h.fieldLine(label, value));
|
|
64
|
+
if (block.fallbackStatement)
|
|
65
|
+
lines.push(block.fallbackStatement);
|
|
66
|
+
if (block.forcedNeighborSample)
|
|
67
|
+
h.pushBoundedStringList(lines, "Forced-Neighbor Sample", block.forcedNeighborSample);
|
|
68
|
+
h.pushBoundedStringList(lines, "Files Read", block.filesRead);
|
|
69
|
+
h.pushBoundedStringList(lines, "Required Files", block.requiredFiles);
|
|
70
|
+
h.pushBoundedStringList(lines, "Observed Files", block.observedFiles);
|
|
71
|
+
h.pushBoundedStringList(lines, "Missing Files", block.missingFiles);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
h.pushSection(lines, "Reference Comparisons (against the full-refresh reference)");
|
|
75
|
+
for (const entry of COMPARISON_PRESENTATION_ORDER) {
|
|
76
|
+
const block = comparisonBlock(scenario, entry);
|
|
77
|
+
lines.push(block.label);
|
|
78
|
+
if (!block.available) {
|
|
79
|
+
lines.push("Not available.");
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
for (const [label, value] of block.rows)
|
|
83
|
+
lines.push(h.fieldLine(label, value));
|
|
84
|
+
h.pushDashList(lines, block.reasonCodes);
|
|
85
|
+
lines.push(block.explanation);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
export function renderIncrementalChangeStalenessSectionV2(lines, section, h) {
|
|
89
|
+
h.pushSection(lines, "Summary");
|
|
90
|
+
for (const [label, value] of SCENARIO_SUMMARY_ROWS(section.summary))
|
|
91
|
+
lines.push(h.fieldLine(label, value));
|
|
92
|
+
for (const scenario of section.scenarios)
|
|
93
|
+
renderScenario(lines, scenario, h);
|
|
94
|
+
h.pushSection(lines, "Limitations");
|
|
95
|
+
h.pushDashList(lines, section.limitations);
|
|
96
|
+
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { STALE_RISK_CLASSIFICATION_EXPLANATIONS } from "./buildIncrementalChangeStalenessReport.js";
|
|
2
|
+
import { isV2ReportSection } from "./buildIncrementalChangeStalenessReportV2.js";
|
|
3
|
+
import { renderIncrementalChangeStalenessSectionV2 } from "./renderIncrementalChangeStalenessTextV2.js";
|
|
2
4
|
import { REINDEX_RECOMMENDATION_EXPLANATIONS } from "./buildWarmIndexReuseReport.js";
|
|
3
5
|
function sanitizeScalar(value) {
|
|
4
6
|
const text = String(value);
|
|
@@ -612,6 +614,10 @@ function renderIncrementalChangeStalenessSection(lines, section) {
|
|
|
612
614
|
lines.push("Not applicable to this plugin.");
|
|
613
615
|
return;
|
|
614
616
|
}
|
|
617
|
+
if (isV2ReportSection(section)) {
|
|
618
|
+
renderIncrementalChangeStalenessSectionV2(lines, section, { pushSection, fieldLine, pushDashList, pushBoundedStringList });
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
615
621
|
pushSection(lines, "Summary");
|
|
616
622
|
lines.push(fieldLine("Scenarios", section.scenarioCount));
|
|
617
623
|
lines.push(fieldLine("Ready", section.readyScenarioCount));
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import { runSecurityCommand } from "../commandRunner.js";
|
|
2
2
|
import { resolveCommand } from "../../core/resolveCommand.js";
|
|
3
3
|
import { skippedCheck } from "../cliAdversarial/runAdversarialCheck.js";
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
4
|
+
// This local check only verifies CodeQL CLI availability and integration.
|
|
5
|
+
// Full CodeQL analysis is owned by .github/workflows/codeql.yml.
|
|
6
|
+
// Release readiness requires successful GitHub analysis for the exact candidate SHA.
|
|
7
7
|
export async function runCodeqlCheck(options) {
|
|
8
8
|
const { cwd, timeoutMs } = options;
|
|
9
9
|
const resolved = resolveCommand("codeql", { cwd, env: process.env });
|
|
10
10
|
if (resolved.resolutionKind === "unavailable") {
|
|
11
11
|
return skippedCheck({
|
|
12
12
|
id: "codeql-scan",
|
|
13
|
-
name: "CodeQL
|
|
13
|
+
name: "CodeQL CLI availability preflight",
|
|
14
14
|
category: "static-scan",
|
|
15
|
-
reason: "CodeQL CLI
|
|
15
|
+
reason: "Local CodeQL CLI is unavailable, so the local availability preflight is skipped. Full release-readiness coverage requires a successful GitHub CodeQL advanced-setup analysis for the exact candidate SHA.",
|
|
16
16
|
});
|
|
17
17
|
}
|
|
18
18
|
const startedAt = new Date().toISOString();
|
|
@@ -29,7 +29,7 @@ export async function runCodeqlCheck(options) {
|
|
|
29
29
|
if (versionCmd.exitCode !== 0 || versionCmd.exitCode === null) {
|
|
30
30
|
return {
|
|
31
31
|
id: "codeql-scan",
|
|
32
|
-
name: "CodeQL
|
|
32
|
+
name: "CodeQL CLI availability preflight",
|
|
33
33
|
category: "static-scan",
|
|
34
34
|
status: "failed",
|
|
35
35
|
severity: "major",
|
|
@@ -50,10 +50,10 @@ export async function runCodeqlCheck(options) {
|
|
|
50
50
|
command: "codeql version --format terse",
|
|
51
51
|
};
|
|
52
52
|
}
|
|
53
|
-
// CLI is present and functional
|
|
53
|
+
// The CLI is present and functional; this is not repository-wide analysis.
|
|
54
54
|
return {
|
|
55
55
|
id: "codeql-scan",
|
|
56
|
-
name: "CodeQL
|
|
56
|
+
name: "CodeQL CLI availability preflight",
|
|
57
57
|
category: "static-scan",
|
|
58
58
|
status: "passed",
|
|
59
59
|
severity: "informational",
|
package/docs/METRICS.md
CHANGED
|
@@ -629,3 +629,35 @@ The `incremental-change-staleness` plugin reuses the six existing v0.6.1 affecte
|
|
|
629
629
|
`staleRiskClassification` is not a numeric risk score, not a winner, not a ranking, and not an automatic reindex decision. `no-observed-stale-regression` means only that this matched scenario did not observe a stale-specific regression under these two frozen evidence dimensions; it does not establish that stale indexes are generally safe. `reindexRecommendation` (the released v0.6.1 categorical evidence) remains separate observational evidence about the affected-neighborhood analysis; it does not select or trigger either treatment and is not an input to the stale-risk classification above.
|
|
630
630
|
|
|
631
631
|
**Report presentation.** `report.incrementalChangeStaleness` holds `scenarioCount`, `readyScenarioCount`, `failedScenarioCount`, `observedStaleRegressionCount`, `noObservedStaleRegressionCount`, `inconclusiveCount`, per-scenario evidence (lifecycle, both treatments in order, and the comparison), and fixed limitations text. There is no overall score, grade, winner, best-treatment, or safe-to-skip-reindex field anywhere in the report or the persisted execution artifact.
|
|
632
|
+
|
|
633
|
+
### Partial-refresh treatment evidence (v0.6.3, current release)
|
|
634
|
+
|
|
635
|
+
The `incremental-change-staleness` plugin now compares four treatments (`stale-index`, `changed-files-refresh`, `affected-neighborhood-refresh`, `full-refresh`). It defines no new formula. Two evidence families must not be merged:
|
|
636
|
+
|
|
637
|
+
- **Lab task-relationship affected-neighborhood evidence** (released v0.6.1, unchanged): the six numeric metrics `changedFileCount`, `changedSymbolCount`, `affectedNodeCount`, `affectedEdgeCount`, `taskOverlapCount`, `taskOverlapPercent`, plus the categorical `relationship` and `reindexRecommendation`. They describe the baseline graph neighborhood of the controlled change relative to the task. `changedSymbolCount` still means baseline symbol identities belonging to confirmed changed indexed files, not proof that any symbol body changed, and `reindexRecommendation` stays observational: it selects and executes no treatment.
|
|
638
|
+
- **Upstream refresh execution evidence** (new): what my-dev-kit actually did during a partial refresh invocation. It does not depend on the task and is not a task-relevance measure.
|
|
639
|
+
|
|
640
|
+
**Numeric upstream-refresh metrics** (unit `count`; emitted as `ExperimentMetric` entries on the outcome of an incremental treatment, `changed-files-refresh` or `affected-neighborhood-refresh`, and never on `stale-index` or `full-refresh`):
|
|
641
|
+
|
|
642
|
+
| Metric ID | Meaning |
|
|
643
|
+
|---|---|
|
|
644
|
+
| `upstream-refresh-fresh-extraction-file-count` | Files upstream freshly re-extracted during the refresh invocation. |
|
|
645
|
+
| `upstream-refresh-reused-file-count` | Files upstream reused from the previous index. |
|
|
646
|
+
| `upstream-refresh-forced-neighbor-reanalysis-file-count` | Neighboring files upstream forced to re-analyze during the refresh. |
|
|
647
|
+
| `upstream-refresh-seed-file-count` | Seed files upstream selected for the affected-neighborhood refresh. |
|
|
648
|
+
| `upstream-refresh-seed-symbol-count` | Seed symbols upstream selected. |
|
|
649
|
+
| `upstream-refresh-affected-node-count` | Graph nodes upstream treated as affected. |
|
|
650
|
+
| `upstream-refresh-affected-edge-count` | Graph edges upstream treated as affected. |
|
|
651
|
+
|
|
652
|
+
Availability: the first three are always present for a validated incremental treatment. The seed, node, and edge counts appear only when upstream supplied a non-null value; a `null` upstream value is unavailable evidence and the metric is omitted, never emitted as zero. These counts describe upstream execution only: they do not prove a treatment is better, faster, safer, or more accurate, and they are distinct from the Lab `affectedNodeCount`/`affectedEdgeCount` above (different metric IDs).
|
|
653
|
+
|
|
654
|
+
**Categorical refresh evidence** (not numeric metrics, never converted to numbers): `requestedScope` (`changed-files` or `affected-neighborhood`), `appliedScope` (the scope upstream actually applied: `changed-files`, `affected-neighborhood`, `full`, or `none`), `selectionStatus` (`applied`, `fallback-full`, or `not-needed`), `fallbackReason` (an upstream reason or `null`), `forcedNeighborSample` (a bounded sample of path strings, capped by upstream, so its length can be less than the forced-neighbor count), and the Lab `refreshRealization` (`NO_REFRESH`, `APPLIED_PARTIAL`, `FALLBACK_FULL`, `FULL_REFRESH`). Only `changed-files-refresh` and `affected-neighborhood-refresh` can be `APPLIED_PARTIAL` or `FALLBACK_FULL`.
|
|
655
|
+
|
|
656
|
+
**Reference comparisons** (exactly three per ready scenario, each against `full-refresh`):
|
|
657
|
+
|
|
658
|
+
- `stale-index` vs `full-refresh` uses the v0.6.2 stale-risk vocabulary and precedence unchanged (`observed-stale-regression`, `no-observed-stale-regression`, `inconclusive`).
|
|
659
|
+
- `changed-files-refresh` vs `full-refresh` and `affected-neighborhood-refresh` vs `full-refresh` use neutral candidate relations (`candidate-worse`, `same`, `candidate-better`, `unknown`) for deterministic fake-agent correctness and for the required-file status, with the frozen precedence: (1) `not-comparable-as-partial-refresh` if the candidate fell back to a full rebuild (`FALLBACK_FULL`); (2) `observed-regression-relative-to-full` if correctness or required-file presence is `candidate-worse`; (3) `inconclusive` if either relation is `unknown`; (4) otherwise `no-observed-regression-relative-to-full`.
|
|
660
|
+
|
|
661
|
+
`no-observed-regression-relative-to-full` means only that the executed scenario showed no regression under those two evidence dimensions. It does not establish equivalence or safety and does not show that full refresh is unnecessary. `not-comparable-as-partial-refresh` says the run gives no evidence about the requested partial behavior. There is no winner, ranking, composite score, safety score, safe-to-skip-refresh flag, or stale-risk percentage. Ordinary per-treatment correctness `score` remains the existing deterministic fake-agent evidence.
|
|
662
|
+
|
|
663
|
+
**Report presentation.** `report.incrementalChangeStaleness` (schema `my-dev-kit-lab-incremental-change-staleness-report-v2`) holds the summary counts (including per-treatment applied-partial, fallback, and classification counts), per-scenario lifecycle, four treatments and three comparisons identified by explicit treatment IDs, and fixed limitations text. Unavailable evidence is written as `unavailable` or `not applicable`, never zero. Historical V1 reports keep their original two-treatment presentation.
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { fileURLToPath } from "node:url";
|
|
4
|
-
|
|
5
|
-
const sourceRoot = path.dirname(fileURLToPath(import.meta.url));
|
|
6
|
-
const targetRoot = process.argv[2];
|
|
7
|
-
if (!targetRoot) {
|
|
8
|
-
console.error("prepare requires a target root argument");
|
|
9
|
-
process.exit(2);
|
|
10
|
-
}
|
|
11
|
-
fs.mkdirSync(path.join(targetRoot, "app"), { recursive: true });
|
|
12
|
-
fs.mkdirSync(path.join(targetRoot, "out"), { recursive: true });
|
|
13
|
-
for (const file of ["index.html", "server.mjs"]) fs.copyFileSync(path.join(sourceRoot, file), path.join(targetRoot, "app", file));
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
const sourceRoot = path.dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
const targetRoot = process.argv[2];
|
|
7
|
+
if (!targetRoot) {
|
|
8
|
+
console.error("prepare requires a target root argument");
|
|
9
|
+
process.exit(2);
|
|
10
|
+
}
|
|
11
|
+
fs.mkdirSync(path.join(targetRoot, "app"), { recursive: true });
|
|
12
|
+
fs.mkdirSync(path.join(targetRoot, "out"), { recursive: true });
|
|
13
|
+
for (const file of ["index.html", "server.mjs"]) fs.copyFileSync(path.join(sourceRoot, file), path.join(targetRoot, "app", file));
|
|
14
14
|
fs.writeFileSync(path.join(targetRoot, "out", "report.json"), JSON.stringify({ prepared: true, counts: { widgets: 3 } }, null, 2), "utf8");
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import http from "node:http";
|
|
2
|
-
import fs from "node:fs";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import { fileURLToPath } from "node:url";
|
|
5
|
-
|
|
6
|
-
const root = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
-
const port = Number(process.env.TUTORIAL_FIXTURE_PORT || 0);
|
|
8
|
-
const server = http.createServer((req, res) => {
|
|
9
|
-
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
10
|
-
if (url.pathname === "/api/status") { res.writeHead(200, { "content-type": "application/json" }); res.end(JSON.stringify({ ok: true, widgets: 3 })); return; }
|
|
11
|
-
const filePath = url.pathname === "/" ? path.join(root, "index.html") : path.join(root, url.pathname);
|
|
12
|
-
fs.readFile(filePath, (error, contents) => { if (error) { res.writeHead(404); res.end("not found"); return; } res.writeHead(200, { "content-type": filePath.endsWith(".html") ? "text/html" : "application/octet-stream" }); res.end(contents); });
|
|
13
|
-
});
|
|
1
|
+
import http from "node:http";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
|
|
6
|
+
const root = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const port = Number(process.env.TUTORIAL_FIXTURE_PORT || 0);
|
|
8
|
+
const server = http.createServer((req, res) => {
|
|
9
|
+
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
10
|
+
if (url.pathname === "/api/status") { res.writeHead(200, { "content-type": "application/json" }); res.end(JSON.stringify({ ok: true, widgets: 3 })); return; }
|
|
11
|
+
const filePath = url.pathname === "/" ? path.join(root, "index.html") : path.join(root, url.pathname);
|
|
12
|
+
fs.readFile(filePath, (error, contents) => { if (error) { res.writeHead(404); res.end("not found"); return; } res.writeHead(200, { "content-type": filePath.endsWith(".html") ? "text/html" : "application/octet-stream" }); res.end(contents); });
|
|
13
|
+
});
|
|
14
14
|
server.listen(port, "127.0.0.1", () => console.log("tutorial fixture listening on " + server.address().port));
|