@sverka/playground 0.2.9 → 0.2.11
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.d.mts +22 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +108 -1
- package/dist/index.mjs.map +1 -1
- package/index.html +6 -1
- package/package.json +3 -2
package/dist/index.d.mts
CHANGED
|
@@ -123,5 +123,26 @@ export declare class Entry {
|
|
|
123
123
|
*/
|
|
124
124
|
export declare function runPipeline(project: Project): Promise<PipelineResult>;
|
|
125
125
|
//#endregion
|
|
126
|
-
|
|
126
|
+
//#region src/share.d.ts
|
|
127
|
+
interface ShareableRun {
|
|
128
|
+
readonly schema: "sverka.playground/v1";
|
|
129
|
+
readonly code: string;
|
|
130
|
+
readonly findings?: readonly Finding[];
|
|
131
|
+
}
|
|
132
|
+
type PlaygroundErrorCode = "TRANSPILE_FAILED" | "DECODE_FAILED" | "PAYLOAD_TOO_LARGE";
|
|
133
|
+
export declare class PlaygroundError extends Error {
|
|
134
|
+
readonly code: PlaygroundErrorCode;
|
|
135
|
+
constructor(code: PlaygroundErrorCode, message: string);
|
|
136
|
+
}
|
|
137
|
+
/** Fragment lengths are browser-dependent; warn past 32 KB of payload. */
|
|
138
|
+
export declare const SHARE_PAYLOAD_WARN_BYTES: number;
|
|
139
|
+
/** Encode a run into a share link. `base` is the page URL the link targets
|
|
140
|
+
* (`location.origin + location.pathname` at runtime). */
|
|
141
|
+
export declare function encodeShareLink(run: ShareableRun, base?: string): string;
|
|
142
|
+
/** Decode a `#c=` fragment back into a ShareableRun. Corrupt, oversized, or
|
|
143
|
+
* shape-invalid payloads throw PlaygroundError — callers fall back to the
|
|
144
|
+
* default template. */
|
|
145
|
+
export declare function decodeShareLink(hash: string): ShareableRun;
|
|
146
|
+
//#endregion
|
|
147
|
+
export type { Construct, Finding, FindingSource, PipelineResult, PlaygroundErrorCode, PlaygroundFinding, Severity, ShareableRun, Step, StepResult };
|
|
127
148
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/types.ts","../src/pipeline.ts","../src/runner.ts"],"mappings":";;KAGY;;UAGK;EACf;EACA;EACA;EACA;EACA;;;UAIe;EACf;EACA;EACA;EACA,UAAU;EACV;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,QAAQ;EACR;;;;UAKe;EACf;EACA;EACA;EACA,UAAU;EACV;EACA;EACA;;;UAIe;EACf;EACA;EACA,UAAU;EACV;EACA;;;UAIe;EACf,OAAO;EACP,UAAU;EACV;EACA;;;;;uBCrDoB;WACX;WACA,OAAO;EAEJ,YAAA,OAAO,kBAAkB;;MAMjC;;;qBASO,gBAAgB;WAClB,WAAW;EAER,YAAA;;EAKZ,YAAY,UAAU;;;qBAMX,iBAAiB;WACnB,OAAO;WACP,SAAS;EAEN,YAAA,SAAS,SAAS;;EAM9B,QAAQ,MAAM;;EAKd,SAAS,OAAO;;;uBAMI,aAAa;WACxB;EAEG,YAAA,UAAU,UAAU,YAAY;;WAOnC,WAAW,QAAQ,uBAAuB;;;qBAIxC,qBAAqB;WACvB,UAAU,QAAQ,uBAAuB;EAGhD,YAAA,UAAU,UACV,YACA;IACE,UAAU,QAAQ,uBAAuB;IACzC;;EAOJ,WAAW,QAAQ,uBAAuB;;;qBAM/B;WACF,UAAU;WACV;WACA;IAAW;;WACX;EAGP,YAAA,UAAU,UACV,YACA;IAAW;MAAW;;IAAgB;;;;;;;;;;;;;wBCtBpB,YAAY,SAAS,UAAU,QAAQ"}
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/types.ts","../src/pipeline.ts","../src/runner.ts","../src/share.ts"],"mappings":";;KAGY;;UAGK;EACf;EACA;EACA;EACA;EACA;;;UAIe;EACf;EACA;EACA;EACA,UAAU;EACV;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,QAAQ;EACR;;;;UAKe;EACf;EACA;EACA;EACA,UAAU;EACV;EACA;EACA;;;UAIe;EACf;EACA;EACA,UAAU;EACV;EACA;;;UAIe;EACf,OAAO;EACP,UAAU;EACV;EACA;;;;;uBCrDoB;WACX;WACA,OAAO;EAEJ,YAAA,OAAO,kBAAkB;;MAMjC;;;qBASO,gBAAgB;WAClB,WAAW;EAER,YAAA;;EAKZ,YAAY,UAAU;;;qBAMX,iBAAiB;WACnB,OAAO;WACP,SAAS;EAEN,YAAA,SAAS,SAAS;;EAM9B,QAAQ,MAAM;;EAKd,SAAS,OAAO;;;uBAMI,aAAa;WACxB;EAEG,YAAA,UAAU,UAAU,YAAY;;WAOnC,WAAW,QAAQ,uBAAuB;;;qBAIxC,qBAAqB;WACvB,UAAU,QAAQ,uBAAuB;EAGhD,YAAA,UAAU,UACV,YACA;IACE,UAAU,QAAQ,uBAAuB;IACzC;;EAOJ,WAAW,QAAQ,uBAAuB;;;qBAM/B;WACF,UAAU;WACV;WACA;IAAW;;WACX;EAGP,YAAA,UAAU,UACV,YACA;IAAW;MAAW;;IAAgB;;;;;;;;;;;;;wBCtBpB,YAAY,SAAS,UAAU,QAAQ;;;UC3E5C;WACN;WACA;WACA,oBAAoB;;KAGnB;qBAGC,wBAAwB;WAC1B,MAAM;EACH,YAAA,MAAM,qBAAqB;;;qBAQ5B;;;wBAkFG,gBAAgB,KAAK,cAAc;;;;wBAkCnC,gBAAgB,eAAe"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Inflate, deflateSync } from "fflate";
|
|
1
2
|
//#region src/pipeline.ts
|
|
2
3
|
/** Base class for all constructs. */
|
|
3
4
|
var Construct = class {
|
|
@@ -162,6 +163,112 @@ async function runPipeline(project) {
|
|
|
162
163
|
};
|
|
163
164
|
}
|
|
164
165
|
//#endregion
|
|
165
|
-
|
|
166
|
+
//#region src/share.ts
|
|
167
|
+
var PlaygroundError = class extends Error {
|
|
168
|
+
code;
|
|
169
|
+
constructor(code, message) {
|
|
170
|
+
super(message);
|
|
171
|
+
this.name = "PlaygroundError";
|
|
172
|
+
this.code = code;
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
/** Fragment lengths are browser-dependent; warn past 32 KB of payload. */
|
|
176
|
+
const SHARE_PAYLOAD_WARN_BYTES = 32768;
|
|
177
|
+
/** Hard cap on inflated share payloads — a compressed fragment is cheap to
|
|
178
|
+
* craft into a deflate bomb, so decoding refuses past 1 MB of JSON. */
|
|
179
|
+
const SHARE_MAX_INFLATED_BYTES = 1048576;
|
|
180
|
+
const SHARE_PREFIX = "#c=";
|
|
181
|
+
function toBase64Url(bytes) {
|
|
182
|
+
let bin = "";
|
|
183
|
+
for (const b of bytes) bin += String.fromCharCode(b);
|
|
184
|
+
return btoa(bin).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
185
|
+
}
|
|
186
|
+
function fromBase64Url(text) {
|
|
187
|
+
const b64 = text.replace(/-/g, "+").replace(/_/g, "/");
|
|
188
|
+
const padded = b64 + "=".repeat((4 - b64.length % 4) % 4);
|
|
189
|
+
const bin = atob(padded);
|
|
190
|
+
const bytes = new Uint8Array(bin.length);
|
|
191
|
+
for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
|
|
192
|
+
return bytes;
|
|
193
|
+
}
|
|
194
|
+
/** Inflate with a hard output cap — bounded memory on hostile payloads. */
|
|
195
|
+
function inflateBounded(bytes) {
|
|
196
|
+
const chunks = [];
|
|
197
|
+
let total = 0;
|
|
198
|
+
let overflow = false;
|
|
199
|
+
new Inflate((chunk) => {
|
|
200
|
+
total += chunk.length;
|
|
201
|
+
if (total > SHARE_MAX_INFLATED_BYTES) {
|
|
202
|
+
overflow = true;
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
chunks.push(chunk);
|
|
206
|
+
}).push(bytes, true);
|
|
207
|
+
if (overflow) throw new PlaygroundError("PAYLOAD_TOO_LARGE", `share link inflates past ${SHARE_MAX_INFLATED_BYTES} bytes`);
|
|
208
|
+
const out = new Uint8Array(total);
|
|
209
|
+
let offset = 0;
|
|
210
|
+
for (const chunk of chunks) {
|
|
211
|
+
out.set(chunk, offset);
|
|
212
|
+
offset += chunk.length;
|
|
213
|
+
}
|
|
214
|
+
return out;
|
|
215
|
+
}
|
|
216
|
+
const SEVERITIES = [
|
|
217
|
+
"info",
|
|
218
|
+
"low",
|
|
219
|
+
"medium",
|
|
220
|
+
"high",
|
|
221
|
+
"critical"
|
|
222
|
+
];
|
|
223
|
+
const FINDING_STRING_FIELDS = [
|
|
224
|
+
"rule",
|
|
225
|
+
"file",
|
|
226
|
+
"message"
|
|
227
|
+
];
|
|
228
|
+
const FINDING_NUMBER_FIELDS = ["startLine", "endLine"];
|
|
229
|
+
/** Structural check — a shared finding reaches the HTML report, so every
|
|
230
|
+
* field that gets rendered must be the expected scalar. */
|
|
231
|
+
function isFindingShape(v) {
|
|
232
|
+
if (typeof v !== "object" || v === null) return false;
|
|
233
|
+
const f = v;
|
|
234
|
+
if (!FINDING_STRING_FIELDS.every((k) => typeof f[k] === "string")) return false;
|
|
235
|
+
if (!SEVERITIES.includes(f.severity)) return false;
|
|
236
|
+
return FINDING_NUMBER_FIELDS.every((k) => typeof f[k] === "number" && Number.isFinite(f[k]));
|
|
237
|
+
}
|
|
238
|
+
/** Encode a run into a share link. `base` is the page URL the link targets
|
|
239
|
+
* (`location.origin + location.pathname` at runtime). */
|
|
240
|
+
function encodeShareLink(run, base = "") {
|
|
241
|
+
const payload = toBase64Url(deflateSync(new TextEncoder().encode(JSON.stringify(run))));
|
|
242
|
+
return `${base}${SHARE_PREFIX}${payload}`;
|
|
243
|
+
}
|
|
244
|
+
/** Extract the payload tail from a `#c=` fragment or a bare payload. */
|
|
245
|
+
function shareFragment(hash) {
|
|
246
|
+
const marker = hash.indexOf(SHARE_PREFIX);
|
|
247
|
+
if (marker >= 0) return hash.slice(marker + 3);
|
|
248
|
+
return hash.startsWith("#") ? hash.slice(1) : hash;
|
|
249
|
+
}
|
|
250
|
+
/** Shape assertion — anything else is a corrupt or hostile payload. */
|
|
251
|
+
function assertRunShape(parsed) {
|
|
252
|
+
if (typeof parsed !== "object" || parsed === null || parsed.schema !== "sverka.playground/v1" || typeof parsed.code !== "string") throw new Error("payload is not a sverka.playground/v1 run");
|
|
253
|
+
const findings = parsed.findings;
|
|
254
|
+
if (findings === void 0) return;
|
|
255
|
+
if (!Array.isArray(findings) || !findings.every(isFindingShape)) throw new Error("payload findings failed shape validation");
|
|
256
|
+
}
|
|
257
|
+
/** Decode a `#c=` fragment back into a ShareableRun. Corrupt, oversized, or
|
|
258
|
+
* shape-invalid payloads throw PlaygroundError — callers fall back to the
|
|
259
|
+
* default template. */
|
|
260
|
+
function decodeShareLink(hash) {
|
|
261
|
+
try {
|
|
262
|
+
const json = new TextDecoder().decode(inflateBounded(fromBase64Url(shareFragment(hash))));
|
|
263
|
+
const parsed = JSON.parse(json);
|
|
264
|
+
assertRunShape(parsed);
|
|
265
|
+
return parsed;
|
|
266
|
+
} catch (e) {
|
|
267
|
+
if (e instanceof PlaygroundError) throw e;
|
|
268
|
+
throw new PlaygroundError("DECODE_FAILED", `share link could not be decoded: ${e instanceof Error ? e.message : String(e)}`);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
//#endregion
|
|
272
|
+
export { Entry, FunctionStep, Pipeline, PlaygroundError, Project, SHARE_PAYLOAD_WARN_BYTES, decodeShareLink, encodeShareLink, runPipeline };
|
|
166
273
|
|
|
167
274
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../src/pipeline.ts","../src/runner.ts"],"sourcesContent":["// @sverka/playground — minimal pipeline model. Browser-safe.\n// Mirrors @sverka/workflow Construct API but simplified for browser use.\n\nimport type { PlaygroundFinding } from \"./types.js\";\n\n/** Base class for all constructs. */\nexport abstract class Construct {\n readonly id: string;\n readonly scope: Construct | null;\n\n constructor(scope: Construct | null, id: string) {\n this.id = id;\n this.scope = scope;\n }\n\n /** Full path from root to this construct. */\n get path(): string {\n if (this.scope && this.scope.id !== \"root\") {\n return `${this.scope.path}/${this.id}`;\n }\n return this.id;\n }\n}\n\n/** Root construct representing a project. */\nexport class Project extends Construct {\n readonly pipelines: Pipeline[] = [];\n\n constructor(id: string) {\n super(null, id);\n }\n\n /** Add a pipeline to this project. */\n addPipeline(pipeline: Pipeline): void {\n this.pipelines.push(pipeline);\n }\n}\n\n/** A pipeline containing steps. */\nexport class Pipeline extends Construct {\n readonly steps: Step[] = [];\n readonly entries: Entry[] = [];\n\n constructor(project: Project, id: string) {\n super(project, id);\n project.addPipeline(this);\n }\n\n /** Add a step to this pipeline. */\n addStep(step: Step): void {\n this.steps.push(step);\n }\n\n /** Add an entry to this pipeline. */\n addEntry(entry: Entry): void {\n this.entries.push(entry);\n }\n}\n\n/** Base class for all step types. */\nexport abstract class Step extends Construct {\n readonly dependencies: string[];\n\n constructor(pipeline: Pipeline, id: string, dependencies: string[] = []) {\n super(pipeline, id);\n this.dependencies = dependencies;\n pipeline.addStep(this);\n }\n\n /** Execute this step and return findings. */\n abstract execute(): Promise<PlaygroundFinding[]> | PlaygroundFinding[];\n}\n\n/** A step that runs a JavaScript function in the browser. */\nexport class FunctionStep extends Step {\n readonly fn: () => Promise<PlaygroundFinding[]> | PlaygroundFinding[];\n\n constructor(\n pipeline: Pipeline,\n id: string,\n options: {\n fn: () => Promise<PlaygroundFinding[]> | PlaygroundFinding[];\n dependencies?: string[];\n },\n ) {\n super(pipeline, id, options.dependencies ?? []);\n this.fn = options.fn;\n }\n\n execute(): Promise<PlaygroundFinding[]> | PlaygroundFinding[] {\n return this.fn();\n }\n}\n\n/** An entry point that triggers a pipeline. */\nexport class Entry {\n readonly pipeline: Pipeline;\n readonly id: string;\n readonly trigger: { kind: string };\n readonly roots: string[];\n\n constructor(\n pipeline: Pipeline,\n id: string,\n options: { trigger: { kind: string }; roots: string[] },\n ) {\n this.pipeline = pipeline;\n this.id = id;\n this.trigger = options.trigger;\n this.roots = options.roots;\n pipeline.addEntry(this);\n }\n}\n","// @sverka/playground — pipeline runner. Browser-safe.\n// Executes FunctionSteps and collects findings.\n\nimport type { Project } from \"./pipeline.js\";\nimport type { Pipeline } from \"./pipeline.js\";\nimport type { Step } from \"./pipeline.js\";\nimport type {\n Finding,\n PlaygroundFinding,\n PipelineResult,\n StepResult,\n} from \"./types.js\";\n\n/** Simple deterministic hash for fingerprint generation. Browser-safe. */\nfunction simpleHash(input: string): string {\n let hash = 5381;\n for (let i = 0; i < input.length; i++) {\n hash = ((hash << 5) + hash + input.charCodeAt(i)) | 0;\n }\n return (hash >>> 0).toString(16).padStart(8, \"0\");\n}\n\n/** Convert a PlaygroundFinding to a full Finding with fingerprint and id. */\nfunction toFinding(pf: PlaygroundFinding, stepId: string): Finding {\n // Include checkId (stepId) in fingerprint so identical findings from\n // different checks get distinct fingerprints.\n const fingerprintInput = `${stepId}:${pf.rule}:${pf.file}:${pf.line}`;\n const fingerprint = simpleHash(fingerprintInput);\n const id = `${stepId}:${fingerprint}`;\n return {\n id,\n fingerprint,\n checkId: stepId,\n severity: pf.severity,\n confidence: 0.8,\n message: pf.message,\n rule: pf.rule,\n file: pf.file,\n startLine: pf.line,\n endLine: pf.line,\n source: {\n tool: pf.tool ?? stepId,\n version: null,\n format: \"custom\",\n originalRuleId: pf.rule,\n originalSeverity: null,\n },\n ...(pf.snippet ? { snippet: pf.snippet } : {}),\n };\n}\n\n/** Execute a single step and return its result. */\nasync function executeStep(step: Step): Promise<StepResult> {\n const start = Date.now();\n try {\n const playgroundFindings = await step.execute();\n const findings = playgroundFindings.map((pf) => toFinding(pf, step.id));\n return {\n stepId: step.id,\n status: \"success\",\n findings,\n durationMs: Date.now() - start,\n };\n } catch (e) {\n return {\n stepId: step.id,\n status: \"failure\",\n findings: [],\n durationMs: Date.now() - start,\n error: e instanceof Error ? e.message : String(e),\n };\n }\n}\n\n/**\n * Run all FunctionSteps in a project's pipelines and collect findings.\n * Browser-safe — no Node.js APIs, no child_process, no fs.\n *\n * If a pipeline has entries with `roots`, only steps whose id matches a\n * root are executed. If no entries exist or all entries have empty roots,\n * all steps run.\n */\nexport async function runPipeline(project: Project): Promise<PipelineResult> {\n const start = Date.now();\n const stepResults: StepResult[] = [];\n const allFindings: Finding[] = [];\n\n for (const pipeline of project.pipelines) {\n // Determine which steps to run based on entry roots.\n let stepsToRun = pipeline.steps;\n const entriesWithRoots = pipeline.entries.filter((e) => e.roots.length > 0);\n if (entriesWithRoots.length > 0) {\n const rootSet = new Set<string>();\n for (const entry of entriesWithRoots) {\n for (const root of entry.roots) rootSet.add(root);\n }\n stepsToRun = pipeline.steps.filter((s) => rootSet.has(s.id));\n }\n\n // Execute all selected steps in parallel — dependencies are for\n // ordering, not data flow in the playground.\n const results = await Promise.all(\n stepsToRun.map((step) => executeStep(step)),\n );\n\n for (const result of results) {\n stepResults.push(result);\n allFindings.push(...result.findings);\n }\n }\n\n return {\n steps: stepResults,\n findings: allFindings,\n totalDurationMs: Date.now() - start,\n success: stepResults.every((s) => s.status === \"success\"),\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/pipeline.ts","../src/runner.ts","../src/share.ts"],"sourcesContent":["// @sverka/playground — minimal pipeline model. Browser-safe.\n// Mirrors @sverka/workflow Construct API but simplified for browser use.\n\nimport type { PlaygroundFinding } from \"./types.js\";\n\n/** Base class for all constructs. */\nexport abstract class Construct {\n readonly id: string;\n readonly scope: Construct | null;\n\n constructor(scope: Construct | null, id: string) {\n this.id = id;\n this.scope = scope;\n }\n\n /** Full path from root to this construct. */\n get path(): string {\n if (this.scope && this.scope.id !== \"root\") {\n return `${this.scope.path}/${this.id}`;\n }\n return this.id;\n }\n}\n\n/** Root construct representing a project. */\nexport class Project extends Construct {\n readonly pipelines: Pipeline[] = [];\n\n constructor(id: string) {\n super(null, id);\n }\n\n /** Add a pipeline to this project. */\n addPipeline(pipeline: Pipeline): void {\n this.pipelines.push(pipeline);\n }\n}\n\n/** A pipeline containing steps. */\nexport class Pipeline extends Construct {\n readonly steps: Step[] = [];\n readonly entries: Entry[] = [];\n\n constructor(project: Project, id: string) {\n super(project, id);\n project.addPipeline(this);\n }\n\n /** Add a step to this pipeline. */\n addStep(step: Step): void {\n this.steps.push(step);\n }\n\n /** Add an entry to this pipeline. */\n addEntry(entry: Entry): void {\n this.entries.push(entry);\n }\n}\n\n/** Base class for all step types. */\nexport abstract class Step extends Construct {\n readonly dependencies: string[];\n\n constructor(pipeline: Pipeline, id: string, dependencies: string[] = []) {\n super(pipeline, id);\n this.dependencies = dependencies;\n pipeline.addStep(this);\n }\n\n /** Execute this step and return findings. */\n abstract execute(): Promise<PlaygroundFinding[]> | PlaygroundFinding[];\n}\n\n/** A step that runs a JavaScript function in the browser. */\nexport class FunctionStep extends Step {\n readonly fn: () => Promise<PlaygroundFinding[]> | PlaygroundFinding[];\n\n constructor(\n pipeline: Pipeline,\n id: string,\n options: {\n fn: () => Promise<PlaygroundFinding[]> | PlaygroundFinding[];\n dependencies?: string[];\n },\n ) {\n super(pipeline, id, options.dependencies ?? []);\n this.fn = options.fn;\n }\n\n execute(): Promise<PlaygroundFinding[]> | PlaygroundFinding[] {\n return this.fn();\n }\n}\n\n/** An entry point that triggers a pipeline. */\nexport class Entry {\n readonly pipeline: Pipeline;\n readonly id: string;\n readonly trigger: { kind: string };\n readonly roots: string[];\n\n constructor(\n pipeline: Pipeline,\n id: string,\n options: { trigger: { kind: string }; roots: string[] },\n ) {\n this.pipeline = pipeline;\n this.id = id;\n this.trigger = options.trigger;\n this.roots = options.roots;\n pipeline.addEntry(this);\n }\n}\n","// @sverka/playground — pipeline runner. Browser-safe.\n// Executes FunctionSteps and collects findings.\n\nimport type { Project } from \"./pipeline.js\";\nimport type { Pipeline } from \"./pipeline.js\";\nimport type { Step } from \"./pipeline.js\";\nimport type {\n Finding,\n PlaygroundFinding,\n PipelineResult,\n StepResult,\n} from \"./types.js\";\n\n/** Simple deterministic hash for fingerprint generation. Browser-safe. */\nfunction simpleHash(input: string): string {\n let hash = 5381;\n for (let i = 0; i < input.length; i++) {\n hash = ((hash << 5) + hash + input.charCodeAt(i)) | 0;\n }\n return (hash >>> 0).toString(16).padStart(8, \"0\");\n}\n\n/** Convert a PlaygroundFinding to a full Finding with fingerprint and id. */\nfunction toFinding(pf: PlaygroundFinding, stepId: string): Finding {\n // Include checkId (stepId) in fingerprint so identical findings from\n // different checks get distinct fingerprints.\n const fingerprintInput = `${stepId}:${pf.rule}:${pf.file}:${pf.line}`;\n const fingerprint = simpleHash(fingerprintInput);\n const id = `${stepId}:${fingerprint}`;\n return {\n id,\n fingerprint,\n checkId: stepId,\n severity: pf.severity,\n confidence: 0.8,\n message: pf.message,\n rule: pf.rule,\n file: pf.file,\n startLine: pf.line,\n endLine: pf.line,\n source: {\n tool: pf.tool ?? stepId,\n version: null,\n format: \"custom\",\n originalRuleId: pf.rule,\n originalSeverity: null,\n },\n ...(pf.snippet ? { snippet: pf.snippet } : {}),\n };\n}\n\n/** Execute a single step and return its result. */\nasync function executeStep(step: Step): Promise<StepResult> {\n const start = Date.now();\n try {\n const playgroundFindings = await step.execute();\n const findings = playgroundFindings.map((pf) => toFinding(pf, step.id));\n return {\n stepId: step.id,\n status: \"success\",\n findings,\n durationMs: Date.now() - start,\n };\n } catch (e) {\n return {\n stepId: step.id,\n status: \"failure\",\n findings: [],\n durationMs: Date.now() - start,\n error: e instanceof Error ? e.message : String(e),\n };\n }\n}\n\n/**\n * Run all FunctionSteps in a project's pipelines and collect findings.\n * Browser-safe — no Node.js APIs, no child_process, no fs.\n *\n * If a pipeline has entries with `roots`, only steps whose id matches a\n * root are executed. If no entries exist or all entries have empty roots,\n * all steps run.\n */\nexport async function runPipeline(project: Project): Promise<PipelineResult> {\n const start = Date.now();\n const stepResults: StepResult[] = [];\n const allFindings: Finding[] = [];\n\n for (const pipeline of project.pipelines) {\n // Determine which steps to run based on entry roots.\n let stepsToRun = pipeline.steps;\n const entriesWithRoots = pipeline.entries.filter((e) => e.roots.length > 0);\n if (entriesWithRoots.length > 0) {\n const rootSet = new Set<string>();\n for (const entry of entriesWithRoots) {\n for (const root of entry.roots) rootSet.add(root);\n }\n stepsToRun = pipeline.steps.filter((s) => rootSet.has(s.id));\n }\n\n // Execute all selected steps in parallel — dependencies are for\n // ordering, not data flow in the playground.\n const results = await Promise.all(\n stepsToRun.map((step) => executeStep(step)),\n );\n\n for (const result of results) {\n stepResults.push(result);\n allFindings.push(...result.findings);\n }\n }\n\n return {\n steps: stepResults,\n findings: allFindings,\n totalDurationMs: Date.now() - start,\n success: stepResults.every((s) => s.status === \"success\"),\n };\n}\n","// Share links (Spec 53): a ShareableRun serializes into the URL fragment —\n// `#c=` keeps payloads out of server logs and needs zero backend.\n// Encoding: JSON → deflate-raw → base64url.\n\nimport { deflateSync, Inflate } from \"fflate\";\nimport type { Finding, Severity } from \"./types.js\";\n\nexport interface ShareableRun {\n readonly schema: \"sverka.playground/v1\";\n readonly code: string;\n readonly findings?: readonly Finding[];\n}\n\nexport type PlaygroundErrorCode =\n \"TRANSPILE_FAILED\" | \"DECODE_FAILED\" | \"PAYLOAD_TOO_LARGE\";\n\nexport class PlaygroundError extends Error {\n readonly code: PlaygroundErrorCode;\n constructor(code: PlaygroundErrorCode, message: string) {\n super(message);\n this.name = \"PlaygroundError\";\n this.code = code;\n }\n}\n\n/** Fragment lengths are browser-dependent; warn past 32 KB of payload. */\nexport const SHARE_PAYLOAD_WARN_BYTES = 32 * 1024;\n\n/** Hard cap on inflated share payloads — a compressed fragment is cheap to\n * craft into a deflate bomb, so decoding refuses past 1 MB of JSON. */\nconst SHARE_MAX_INFLATED_BYTES = 1024 * 1024;\n\nconst SHARE_PREFIX = \"#c=\";\n\nfunction toBase64Url(bytes: Uint8Array): string {\n let bin = \"\";\n for (const b of bytes) bin += String.fromCharCode(b);\n return btoa(bin).replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\");\n}\n\nfunction fromBase64Url(text: string): Uint8Array {\n const b64 = text.replace(/-/g, \"+\").replace(/_/g, \"/\");\n // Browser atob() is strict about padding — restore what the encoder\n // stripped. (Node's atob tolerates missing padding, which is why the\n // round-trip test alone did not catch this.)\n const padded = b64 + \"=\".repeat((4 - (b64.length % 4)) % 4);\n const bin = atob(padded);\n const bytes = new Uint8Array(bin.length);\n for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);\n return bytes;\n}\n\n/** Inflate with a hard output cap — bounded memory on hostile payloads. */\nfunction inflateBounded(bytes: Uint8Array): Uint8Array {\n const chunks: Uint8Array[] = [];\n let total = 0;\n let overflow = false;\n const inflate = new Inflate((chunk) => {\n total += chunk.length;\n if (total > SHARE_MAX_INFLATED_BYTES) {\n overflow = true;\n return;\n }\n chunks.push(chunk);\n });\n inflate.push(bytes, true);\n if (overflow) {\n throw new PlaygroundError(\n \"PAYLOAD_TOO_LARGE\",\n `share link inflates past ${SHARE_MAX_INFLATED_BYTES} bytes`,\n );\n }\n const out = new Uint8Array(total);\n let offset = 0;\n for (const chunk of chunks) {\n out.set(chunk, offset);\n offset += chunk.length;\n }\n return out;\n}\n\nconst SEVERITIES: readonly string[] = [\n \"info\",\n \"low\",\n \"medium\",\n \"high\",\n \"critical\",\n];\n\nconst FINDING_STRING_FIELDS = [\"rule\", \"file\", \"message\"] as const;\nconst FINDING_NUMBER_FIELDS = [\"startLine\", \"endLine\"] as const;\n\n/** Structural check — a shared finding reaches the HTML report, so every\n * field that gets rendered must be the expected scalar. */\nfunction isFindingShape(v: unknown): v is Finding {\n if (typeof v !== \"object\" || v === null) return false;\n const f = v as Record<string, unknown>;\n if (!FINDING_STRING_FIELDS.every((k) => typeof f[k] === \"string\")) {\n return false;\n }\n if (!SEVERITIES.includes(f.severity as Severity)) return false;\n return FINDING_NUMBER_FIELDS.every(\n (k) => typeof f[k] === \"number\" && Number.isFinite(f[k]),\n );\n}\n\n/** Encode a run into a share link. `base` is the page URL the link targets\n * (`location.origin + location.pathname` at runtime). */\nexport function encodeShareLink(run: ShareableRun, base = \"\"): string {\n const payload = toBase64Url(\n deflateSync(new TextEncoder().encode(JSON.stringify(run))),\n );\n return `${base}${SHARE_PREFIX}${payload}`;\n}\n\n/** Extract the payload tail from a `#c=` fragment or a bare payload. */\nfunction shareFragment(hash: string): string {\n const marker = hash.indexOf(SHARE_PREFIX);\n if (marker >= 0) return hash.slice(marker + SHARE_PREFIX.length);\n return hash.startsWith(\"#\") ? hash.slice(1) : hash;\n}\n\n/** Shape assertion — anything else is a corrupt or hostile payload. */\nfunction assertRunShape(parsed: unknown): asserts parsed is ShareableRun {\n if (\n typeof parsed !== \"object\" ||\n parsed === null ||\n (parsed as ShareableRun).schema !== \"sverka.playground/v1\" ||\n typeof (parsed as ShareableRun).code !== \"string\"\n ) {\n throw new Error(\"payload is not a sverka.playground/v1 run\");\n }\n const findings = (parsed as ShareableRun).findings;\n if (findings === undefined) return;\n if (!Array.isArray(findings) || !findings.every(isFindingShape)) {\n throw new Error(\"payload findings failed shape validation\");\n }\n}\n\n/** Decode a `#c=` fragment back into a ShareableRun. Corrupt, oversized, or\n * shape-invalid payloads throw PlaygroundError — callers fall back to the\n * default template. */\nexport function decodeShareLink(hash: string): ShareableRun {\n try {\n const json = new TextDecoder().decode(\n inflateBounded(fromBase64Url(shareFragment(hash))),\n );\n const parsed: unknown = JSON.parse(json);\n assertRunShape(parsed);\n return parsed;\n } catch (e) {\n if (e instanceof PlaygroundError) throw e;\n throw new PlaygroundError(\n \"DECODE_FAILED\",\n `share link could not be decoded: ${e instanceof Error ? e.message : String(e)}`,\n );\n }\n}\n"],"mappings":"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"}
|
package/index.html
CHANGED
|
@@ -124,6 +124,7 @@
|
|
|
124
124
|
<h1>Sverka Playground</h1>
|
|
125
125
|
<div class="actions">
|
|
126
126
|
<span class="status" id="status">Ready</span>
|
|
127
|
+
<button id="share-btn" title="Copy share link">Share</button>
|
|
127
128
|
<button id="run-btn">Run</button>
|
|
128
129
|
</div>
|
|
129
130
|
</header>
|
|
@@ -135,7 +136,11 @@
|
|
|
135
136
|
<div class="splitter" id="splitter"></div>
|
|
136
137
|
<div class="findings-panel">
|
|
137
138
|
<div class="panel-header">Findings</div>
|
|
138
|
-
<iframe
|
|
139
|
+
<iframe
|
|
140
|
+
id="findings-frame"
|
|
141
|
+
title="Findings"
|
|
142
|
+
sandbox="allow-scripts"
|
|
143
|
+
></iframe>
|
|
139
144
|
</div>
|
|
140
145
|
</div>
|
|
141
146
|
<!-- Monaco editor loaded from CDN at runtime (see app.ts).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sverka/playground",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.11",
|
|
4
4
|
"description": "Sverka Playground — browser sandbox for building and running check pipelines",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -37,7 +37,8 @@
|
|
|
37
37
|
"typecheck": "tsc --noEmit"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@sverka/sarif-viewer-web": "^0.2.
|
|
40
|
+
"@sverka/sarif-viewer-web": "^0.2.11",
|
|
41
|
+
"fflate": "0.8.3"
|
|
41
42
|
},
|
|
42
43
|
"devDependencies": {
|
|
43
44
|
"tsdown": "^0.23.0",
|