deepline 0.3.6 → 0.3.8
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/bundling-sources/sdk/src/play.ts +4 -0
- package/dist/bundling-sources/sdk/src/release.ts +1 -1
- package/dist/bundling-sources/shared_libs/play-runtime/context.ts +144 -37
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona-lifecycle.ts +1 -1
- package/dist/bundling-sources/shared_libs/play-runtime/secret-capability.ts +163 -14
- package/dist/bundling-sources/shared_libs/play-runtime/secret-redaction.ts +44 -2
- package/dist/bundling-sources/shared_libs/plays/authoring-contract.ts +44 -15
- package/dist/bundling-sources/shared_libs/plays/docflow.ts +68 -103
- package/dist/bundling-sources/shared_libs/plays/secret-guardrails.ts +137 -1
- package/dist/bundling-sources/shared_libs/security/safe-fetch.ts +25 -7
- package/dist/cli/index.js +153 -9
- package/dist/cli/index.mjs +153 -9
- package/dist/{compiler-manifest-CdgJeOr2.d.mts → compiler-manifest-YIjoJo8y.d.mts} +27 -16
- package/dist/{compiler-manifest-CdgJeOr2.d.ts → compiler-manifest-YIjoJo8y.d.ts} +27 -16
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +30 -3
- package/dist/index.mjs +30 -3
- package/dist/install-integrity.json +2 -2
- package/dist/plays/bundle-play-file.d.mts +2 -2
- package/dist/plays/bundle-play-file.d.ts +2 -2
- package/dist/plays/bundle-play-file.mjs +169 -78
- package/package.json +1 -1
|
@@ -75,6 +75,13 @@ export function redactSecretLikeString(value: string): string {
|
|
|
75
75
|
|
|
76
76
|
export type SecretRedactionContext = {
|
|
77
77
|
register(value: string): void;
|
|
78
|
+
containsRegisteredSecret(
|
|
79
|
+
value: string,
|
|
80
|
+
options?: { includeEncoded?: boolean; minimumLength?: number },
|
|
81
|
+
): boolean;
|
|
82
|
+
matchesRegisteredSecret(value: string): boolean;
|
|
83
|
+
/** Redacts only values registered by the secret resolver, preserving safe diagnostics. */
|
|
84
|
+
redactRegisteredSecrets(value: string): string;
|
|
78
85
|
redactString(value: string): string;
|
|
79
86
|
redactKnownSecrets<T>(value: T): T;
|
|
80
87
|
redact<T>(value: T): T;
|
|
@@ -86,12 +93,39 @@ export function createSecretRedactionContext(
|
|
|
86
93
|
const exactSecrets = new Set<string>();
|
|
87
94
|
|
|
88
95
|
function register(value: string): void {
|
|
89
|
-
if (value.length
|
|
96
|
+
if (value.length > 0) exactSecrets.add(value);
|
|
90
97
|
}
|
|
91
98
|
|
|
92
99
|
for (const value of initialValues) register(value);
|
|
93
100
|
|
|
94
|
-
function
|
|
101
|
+
function containsRegisteredSecret(
|
|
102
|
+
value: string,
|
|
103
|
+
{
|
|
104
|
+
includeEncoded = false,
|
|
105
|
+
minimumLength = 1,
|
|
106
|
+
}: { includeEncoded?: boolean; minimumLength?: number } = {},
|
|
107
|
+
): boolean {
|
|
108
|
+
for (const secret of exactSecrets) {
|
|
109
|
+
if (secret.length < minimumLength) continue;
|
|
110
|
+
if (value.includes(secret)) return true;
|
|
111
|
+
if (includeEncoded) {
|
|
112
|
+
try {
|
|
113
|
+
const encoded = encodeURIComponent(secret);
|
|
114
|
+
if (encoded !== secret && value.includes(encoded)) return true;
|
|
115
|
+
} catch {
|
|
116
|
+
// Malformed surrogate pairs cannot be encoded. The raw comparison is
|
|
117
|
+
// still safe and handles every other secret value.
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function matchesRegisteredSecret(value: string): boolean {
|
|
125
|
+
return exactSecrets.has(value);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function redactRegisteredSecrets(value: string): string {
|
|
95
129
|
let output = value;
|
|
96
130
|
for (const secret of exactSecrets) {
|
|
97
131
|
output = output.replace(
|
|
@@ -111,6 +145,11 @@ export function createSecretRedactionContext(
|
|
|
111
145
|
// redaction above still applies in that rare case.
|
|
112
146
|
}
|
|
113
147
|
}
|
|
148
|
+
return output;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function redactString(value: string): string {
|
|
152
|
+
const output = redactRegisteredSecrets(value);
|
|
114
153
|
return redactSecretLikeString(output);
|
|
115
154
|
}
|
|
116
155
|
|
|
@@ -148,6 +187,9 @@ export function createSecretRedactionContext(
|
|
|
148
187
|
|
|
149
188
|
return {
|
|
150
189
|
register,
|
|
190
|
+
containsRegisteredSecret,
|
|
191
|
+
matchesRegisteredSecret,
|
|
192
|
+
redactRegisteredSecrets,
|
|
151
193
|
redactString,
|
|
152
194
|
redactKnownSecrets,
|
|
153
195
|
redact,
|
|
@@ -7,9 +7,9 @@ import type { ToolExecutionErrorSchemaVersion } from '../tool-execution-error';
|
|
|
7
7
|
import type { PlayDataset, PlayDatasetInput, PlayDatasetRow } from './dataset';
|
|
8
8
|
|
|
9
9
|
export const LEGACY_PLAY_AUTHORING_CONTRACT_EDITION = 1 as const;
|
|
10
|
-
export const PLAY_AUTHORING_CONTRACT_EDITION =
|
|
10
|
+
export const PLAY_AUTHORING_CONTRACT_EDITION = 4 as const;
|
|
11
11
|
export const PLAY_AUTHORING_INPUT_SCHEMA_SNAPSHOT_EDITION = 3 as const;
|
|
12
|
-
export const SUPPORTED_PLAY_AUTHORING_CONTRACT_EDITIONS = [1, 2, 3] as const;
|
|
12
|
+
export const SUPPORTED_PLAY_AUTHORING_CONTRACT_EDITIONS = [1, 2, 3, 4] as const;
|
|
13
13
|
|
|
14
14
|
export type PlayAuthoringContractEdition =
|
|
15
15
|
(typeof SUPPORTED_PLAY_AUTHORING_CONTRACT_EDITIONS)[number];
|
|
@@ -41,6 +41,14 @@ export const PLAY_AUTHORING_CONTRACT_CHANGELOG = [
|
|
|
41
41
|
newWritesEnd: '2026-08-19',
|
|
42
42
|
readerRemoval: null,
|
|
43
43
|
},
|
|
44
|
+
{
|
|
45
|
+
edition: 4,
|
|
46
|
+
changed:
|
|
47
|
+
'ctx.secrets.get(name) resolves an allowed secret to a plaintext string for ordinary Play code. Editions 1–3 retain opaque secret handles.',
|
|
48
|
+
compatibilityOwner: 'Plays Runtime',
|
|
49
|
+
newWritesEnd: null,
|
|
50
|
+
readerRemoval: null,
|
|
51
|
+
},
|
|
44
52
|
] as const;
|
|
45
53
|
|
|
46
54
|
export const PLAY_AUTHORING_CONTRACT_ISSUE_CODES = [
|
|
@@ -58,6 +66,7 @@ export const PLAY_AUTHORING_CONTRACT_ISSUE_CODES = [
|
|
|
58
66
|
'play_authoring_webhook_hmac_invalid',
|
|
59
67
|
'play_authoring_standard_webhooks_invalid',
|
|
60
68
|
'play_authoring_secret_invalid',
|
|
69
|
+
'play_authoring_secret_undeclared',
|
|
61
70
|
'play_authoring_cron_timezone_invalid',
|
|
62
71
|
'play_authoring_tool_request_invalid',
|
|
63
72
|
'play_authoring_durable_policy_invalid',
|
|
@@ -74,6 +83,7 @@ export const PLAY_AUTHORING_CONTRACT_ISSUE_CODES = [
|
|
|
74
83
|
'tool_response_raw_access',
|
|
75
84
|
'check_error',
|
|
76
85
|
'docflow_compile_error',
|
|
86
|
+
'docflow_visualization_missing',
|
|
77
87
|
'docflow_binding_drift',
|
|
78
88
|
'docflow_branch_labels_required',
|
|
79
89
|
'docflow_branch_requires_decision',
|
|
@@ -390,8 +400,10 @@ export type PlaySqlQuery = {
|
|
|
390
400
|
};
|
|
391
401
|
|
|
392
402
|
declare const PLAY_SECRET_HANDLE_BRAND: unique symbol;
|
|
403
|
+
declare const PLAY_SECRET_PROMISE_BRAND: unique symbol;
|
|
393
404
|
/**
|
|
394
|
-
* An opaque reference to a workspace secret
|
|
405
|
+
* An opaque reference to a workspace secret used by legacy authoring-contract
|
|
406
|
+
* editions. New Plays receive plaintext strings from `ctx.secrets.get`.
|
|
395
407
|
*
|
|
396
408
|
* @sdkReference runtime 176 SecretHandle
|
|
397
409
|
*/
|
|
@@ -404,6 +416,12 @@ export type PlaySecretHandle = {
|
|
|
404
416
|
/** Always throws. A secret handle is deliberately not serializable. */
|
|
405
417
|
toJSON(): never;
|
|
406
418
|
};
|
|
419
|
+
/** A secret-reading promise returned only by `ctx.secrets.get`. */
|
|
420
|
+
export type PlaySecretPromise = Promise<string> & {
|
|
421
|
+
readonly [PLAY_SECRET_PROMISE_BRAND]: never;
|
|
422
|
+
};
|
|
423
|
+
/** An opaque legacy secret handle retained for earlier Play artifacts. */
|
|
424
|
+
export type PlaySecretValue = PlaySecretHandle;
|
|
407
425
|
/**
|
|
408
426
|
* One resolved authentication scheme, built by `ctx.secrets.bearer` or `ctx.secrets.header` and attached to a request through `init.auth`.
|
|
409
427
|
*
|
|
@@ -412,8 +430,8 @@ export type PlaySecretHandle = {
|
|
|
412
430
|
export type PlaySecretAuth = {
|
|
413
431
|
/** `bearer` sends `Authorization: Bearer <value>`; `header` sends a named header. */
|
|
414
432
|
readonly kind: 'bearer' | 'header';
|
|
415
|
-
/** The
|
|
416
|
-
readonly secret:
|
|
433
|
+
/** The value whose bytes the runtime attaches. */
|
|
434
|
+
readonly secret: string | PlaySecretPromise | PlaySecretValue;
|
|
417
435
|
/** Header name, set only when `kind` is `header`. */
|
|
418
436
|
readonly header?: string;
|
|
419
437
|
};
|
|
@@ -425,10 +443,10 @@ export type PlaySecretAuthInput = PlaySecretAuth | readonly PlaySecretAuth[];
|
|
|
425
443
|
* @sdkReference runtime 174 SecretAwareRequestInit
|
|
426
444
|
*/
|
|
427
445
|
export type PlaySecretAwareRequestInit = Omit<RequestInit, 'headers'> & {
|
|
428
|
-
/** Ordinary request headers, recorded in the durable receipt
|
|
446
|
+
/** Ordinary request headers, recorded in the durable receipt with any resolved Play secret value redacted. Prefer `auth` for credentials: it enforces HTTPS and keeps the auth header out of the receipt. */
|
|
429
447
|
headers?: HeadersInit;
|
|
430
448
|
/**
|
|
431
|
-
* One or more
|
|
449
|
+
* One or more credentialed headers for this request. Pass a single `ctx.secrets` auth for the common case, or an array when an API requires multiple credentialed headers — for example, Supabase with both `apikey` and `Authorization`. Auth-helper requests require HTTPS and omit the credential from the durable receipt. Each auth entry must target a distinct header.
|
|
432
450
|
*/
|
|
433
451
|
auth?: PlaySecretAuthInput;
|
|
434
452
|
};
|
|
@@ -997,23 +1015,31 @@ export interface PlayAuthoringRuntimeContext {
|
|
|
997
1015
|
|
|
998
1016
|
secrets: {
|
|
999
1017
|
/**
|
|
1000
|
-
*
|
|
1018
|
+
* Read an allowed workspace secret inside the running Play; do not log or return it. Declare uppercase names in top-level `secrets`.
|
|
1001
1019
|
*
|
|
1002
1020
|
* @sdkReference runtime 171 ctx.secrets.get(name)
|
|
1003
1021
|
*/
|
|
1004
|
-
get(name: string):
|
|
1022
|
+
get(name: string): PlaySecretPromise;
|
|
1005
1023
|
/**
|
|
1006
|
-
* Send
|
|
1024
|
+
* Send a credential as `Authorization: Bearer <value>`. Await `get` first;
|
|
1025
|
+
* its direct promise remains accepted for source compatibility, while other
|
|
1026
|
+
* promises are rejected.
|
|
1007
1027
|
*
|
|
1008
1028
|
* @sdkReference runtime 172 ctx.secrets.bearer(secret)
|
|
1009
1029
|
*/
|
|
1010
|
-
bearer(
|
|
1030
|
+
bearer(
|
|
1031
|
+
secret: string | PlaySecretPromise | PlaySecretHandle,
|
|
1032
|
+
): PlaySecretAuth;
|
|
1011
1033
|
/**
|
|
1012
|
-
* Send
|
|
1034
|
+
* Send a credential as a named header, for APIs that do not use bearer
|
|
1035
|
+
* tokens — `x-api-key`, `apikey`, `private-token`, and similar.
|
|
1013
1036
|
*
|
|
1014
1037
|
* @sdkReference runtime 173 ctx.secrets.header(header, secret)
|
|
1015
1038
|
*/
|
|
1016
|
-
header(
|
|
1039
|
+
header(
|
|
1040
|
+
header: string,
|
|
1041
|
+
secret: string | PlaySecretPromise | PlaySecretHandle,
|
|
1042
|
+
): PlaySecretAuth;
|
|
1017
1043
|
};
|
|
1018
1044
|
|
|
1019
1045
|
/**
|
|
@@ -2472,8 +2498,11 @@ export const PLAY_AUTHORING_CLOUD_TYPE_DECLARATIONS = [
|
|
|
2472
2498
|
` secrets?: readonly ${cloudReferenceType('bindings.secrets[]')}[];`,
|
|
2473
2499
|
'};',
|
|
2474
2500
|
'declare const SECRET_HANDLE_BRAND: unique symbol;',
|
|
2501
|
+
'declare const SECRET_PROMISE_BRAND: unique symbol;',
|
|
2475
2502
|
'export type SecretHandle = { readonly [SECRET_HANDLE_BRAND]: never; readonly name: string; toString(): string; toJSON(): never };',
|
|
2476
|
-
|
|
2503
|
+
'export type SecretPromise = Promise<string> & { readonly [SECRET_PROMISE_BRAND]: never };',
|
|
2504
|
+
'export type SecretValue = SecretHandle;',
|
|
2505
|
+
"export type SecretAuth = { readonly kind: 'bearer' | 'header'; readonly secret: string | SecretPromise | SecretHandle; readonly header?: string };",
|
|
2477
2506
|
'export type SecretAuthInput = SecretAuth | readonly SecretAuth[];',
|
|
2478
2507
|
'export type PlayInputContract<TInput> = { readonly schema: Record<string, unknown>; readonly __inputType?: TInput };',
|
|
2479
2508
|
'export type PlayReturnObject = Record<string, unknown> & { readonly _metadata?: never };',
|
|
@@ -2524,7 +2553,7 @@ export const PLAY_AUTHORING_CLOUD_TYPE_DECLARATIONS = [
|
|
|
2524
2553
|
` tool<K extends string>(key: ${cloudReferenceType('ctx.tool.key')}, toolId: K, input: ${cloudReferenceType('ctx.tool.input')}, options?: { description?: ${cloudReferenceType('ctx.tool.options.description')} }): Promise<ToolExecutionOutput<K>>;`,
|
|
2525
2554
|
' step<T>(id: string, run: () => T | Promise<T>, options?: RuntimeStepOptions): Promise<T>;',
|
|
2526
2555
|
" fetch(key: string, url: string | URL, init?: RequestInit & { headers?: HeadersInit & { 'Idempotency-Key'?: string; 'X-Idempotency-Key'?: string }; auth?: SecretAuthInput }, options?: FetchOptions): Promise<PlayFetchResponse>;",
|
|
2527
|
-
' secrets: { get(name: string):
|
|
2556
|
+
' secrets: { get(name: string): SecretPromise; bearer(secret: string | SecretPromise | SecretHandle): SecretAuth; header(header: string, secret: string | SecretPromise | SecretHandle): SecretAuth };',
|
|
2528
2557
|
` runPlay<TOutput = unknown>(key: string, playRef: ${cloudReferenceType('ctx.runPlay.playRef')}, input: ${cloudReferenceType('ctx.runPlay.input')}, options: PlayCallOptions): Promise<TOutput>;`,
|
|
2529
2558
|
' log(message: string): void;',
|
|
2530
2559
|
` sleep(ms: ${cloudReferenceType('ctx.sleep.ms')}): Promise<void>;`,
|
|
@@ -128,13 +128,13 @@ export type PlayDocflow = {
|
|
|
128
128
|
nodes: PlayDocflowNode[];
|
|
129
129
|
edges: PlayDocflowEdge[];
|
|
130
130
|
bindings: PlayDocflowBinding[];
|
|
131
|
-
/** Authoring syntax used by the source file.
|
|
132
|
-
syntax?: '
|
|
131
|
+
/** Authoring syntax used by the source file. */
|
|
132
|
+
syntax?: 'mermaid';
|
|
133
133
|
/** Normalized Mermaid text, ready to pass directly to a Mermaid renderer. */
|
|
134
134
|
mermaidSource?: string;
|
|
135
135
|
/**
|
|
136
|
-
* Mermaid `subgraph` loop regions. Optional
|
|
137
|
-
*
|
|
136
|
+
* Mermaid `subgraph` loop regions. Optional for persisted graphs that have
|
|
137
|
+
* no authored regions.
|
|
138
138
|
*/
|
|
139
139
|
subgraphs?: PlayDocflowSubgraph[];
|
|
140
140
|
/** Mermaid directives accepted by the parser but not applied by React Flow. */
|
|
@@ -232,11 +232,9 @@ export function docflowLabelCountFragment(label: string): string | null {
|
|
|
232
232
|
}
|
|
233
233
|
|
|
234
234
|
const FLOW_START = /^\s*(?:flowchart|graph)\s+(LR|RL|TB|TD|BT)\s*$/i;
|
|
235
|
-
const PUT = /^\s*\/\/\s*put\s+(.+)$/;
|
|
236
235
|
const MERMAID_NODE = /^\s*\/\/\s*@mermaid-node\s+([A-Za-z][\w-]*)(?:\s+(.*))?$/;
|
|
237
236
|
const ATTRIBUTE = /([A-Za-z][\w-]*)\s*:\s*"([^"\n]*)"/y;
|
|
238
237
|
const MERMAID_NODE_ATTRIBUTES = ['label', 'type', 'in', 'out', 'arm'] as const;
|
|
239
|
-
const LEGACY_DOCFLOW_ATTRIBUTES = ['id', ...MERMAID_NODE_ATTRIBUTES] as const;
|
|
240
238
|
const MERMAID_NODE_ID = /^[A-Za-z][\w-]*/;
|
|
241
239
|
/**
|
|
242
240
|
* `class a,b,c sketch` — the author declaring that no statement runs these boxes.
|
|
@@ -292,7 +290,6 @@ const MERMAID_NODE_SHAPES = [
|
|
|
292
290
|
// label bracket is optional; when present the quotes are stripped by the caller.
|
|
293
291
|
const SUBGRAPH_OPEN = /^\s*subgraph\s+([A-Za-z][\w-]*)\s*(?:\[(.*)\])?\s*$/i;
|
|
294
292
|
const SUBGRAPH_END = /^\s*end\s*$/i;
|
|
295
|
-
const DOCFLOW_EDGE_CONNECTOR = /^\s*-->(?:\|([^|]+)\|)?/;
|
|
296
293
|
const MERMAID_EDGE_CONNECTOR = /^\s*(?:-->|-\.->|==>|---)(?:\|([^|]+)\|)?/;
|
|
297
294
|
const DOCFLOW_PATH = /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*$/;
|
|
298
295
|
const IDENTIFIER = /[A-Za-z_$][\w$]*/g;
|
|
@@ -314,17 +311,11 @@ const JS_WORDS = new Set([
|
|
|
314
311
|
|
|
315
312
|
function parseAttributes(
|
|
316
313
|
source: string,
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
annotation: '@mermaid-node' | 'put';
|
|
320
|
-
errors: string[];
|
|
321
|
-
},
|
|
314
|
+
line: number,
|
|
315
|
+
errors: string[],
|
|
322
316
|
): Record<string, string> | null {
|
|
323
317
|
const attributes: Record<string, string> = {};
|
|
324
|
-
const validAttributes =
|
|
325
|
-
input.annotation === '@mermaid-node'
|
|
326
|
-
? MERMAID_NODE_ATTRIBUTES
|
|
327
|
-
: LEGACY_DOCFLOW_ATTRIBUTES;
|
|
318
|
+
const validAttributes = MERMAID_NODE_ATTRIBUTES;
|
|
328
319
|
let cursor = 0;
|
|
329
320
|
while (cursor < source.length) {
|
|
330
321
|
cursor += /^\s*/.exec(source.slice(cursor))?.[0].length ?? 0;
|
|
@@ -334,21 +325,21 @@ function parseAttributes(
|
|
|
334
325
|
if (!match) {
|
|
335
326
|
const fragment =
|
|
336
327
|
source.slice(cursor).split(/\s+/)[0] ?? source.slice(cursor);
|
|
337
|
-
|
|
338
|
-
`Docflow annotation on line ${
|
|
328
|
+
errors.push(
|
|
329
|
+
`Docflow \`// @mermaid-node\` annotation on line ${line} has malformed attribute ${JSON.stringify(fragment)}. Use key:"value" pairs. Valid attributes: ${validAttributes.map((name) => `"${name}"`).join(', ')}.`,
|
|
339
330
|
);
|
|
340
331
|
return null;
|
|
341
332
|
}
|
|
342
333
|
const name = match[1]!;
|
|
343
334
|
if (!(validAttributes as readonly string[]).includes(name)) {
|
|
344
|
-
|
|
345
|
-
`Docflow annotation on line ${
|
|
335
|
+
errors.push(
|
|
336
|
+
`Docflow \`// @mermaid-node\` annotation on line ${line} has unknown attribute "${name}". Valid attributes: ${validAttributes.map((option) => `"${option}"`).join(', ')}.`,
|
|
346
337
|
);
|
|
347
338
|
return null;
|
|
348
339
|
}
|
|
349
340
|
if (Object.prototype.hasOwnProperty.call(attributes, name)) {
|
|
350
|
-
|
|
351
|
-
`Docflow annotation on line ${
|
|
341
|
+
errors.push(
|
|
342
|
+
`Docflow \`// @mermaid-node\` annotation on line ${line} repeats attribute "${name}". Write each attribute once.`,
|
|
352
343
|
);
|
|
353
344
|
return null;
|
|
354
345
|
}
|
|
@@ -508,7 +499,6 @@ function parseShapedMermaidNodes(fragment: string): ParsedMermaidNode[] {
|
|
|
508
499
|
|
|
509
500
|
function parseEdgeChain(
|
|
510
501
|
line: string,
|
|
511
|
-
syntax: 'docflow' | 'mermaid',
|
|
512
502
|
): { nodes: ParsedEdgeNode[]; labels: Array<string | undefined> } | null {
|
|
513
503
|
let remaining = line;
|
|
514
504
|
const first = parseMermaidNodeAtStart(remaining);
|
|
@@ -517,9 +507,7 @@ function parseEdgeChain(
|
|
|
517
507
|
const labels: Array<string | undefined> = [];
|
|
518
508
|
remaining = remaining.slice(first.length);
|
|
519
509
|
while (remaining.trim()) {
|
|
520
|
-
const connector = (
|
|
521
|
-
syntax === 'mermaid' ? MERMAID_EDGE_CONNECTOR : DOCFLOW_EDGE_CONNECTOR
|
|
522
|
-
).exec(remaining);
|
|
510
|
+
const connector = MERMAID_EDGE_CONNECTOR.exec(remaining);
|
|
523
511
|
if (!connector) return null;
|
|
524
512
|
remaining = remaining.slice(connector[0].length);
|
|
525
513
|
const next = parseMermaidNodeAtStart(remaining);
|
|
@@ -630,7 +618,6 @@ function levenshtein(left: string, right: string): number {
|
|
|
630
618
|
}
|
|
631
619
|
|
|
632
620
|
const MERMAID_BLOCK = /\/\*\*\s*@mermaid(?:\s|\r?\n)([\s\S]*?)\*\//g;
|
|
633
|
-
const LEGACY_BLOCK = /\/\*\*\s*@docflow(?:\s|\r?\n)([\s\S]*?)\*\//;
|
|
634
621
|
/**
|
|
635
622
|
* What a block header may name: an export name, or the play's own kebab-case
|
|
636
623
|
* name. Hyphens are in the set for the second — `@mermaid name-to-linkedin-url-
|
|
@@ -702,12 +689,10 @@ type ParsedDocflowBlock = ParsedDocflowBlockGraph & {
|
|
|
702
689
|
};
|
|
703
690
|
|
|
704
691
|
/** How a block is named in a diagnostic. */
|
|
705
|
-
function blockLabel(block: {
|
|
706
|
-
authoredExportName
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
const tag = block.syntax === 'mermaid' ? '@mermaid' : '@docflow';
|
|
710
|
-
return block.authoredExportName ? `${tag} ${block.authoredExportName}` : tag;
|
|
692
|
+
function blockLabel(block: { authoredExportName: string | null }): string {
|
|
693
|
+
return block.authoredExportName
|
|
694
|
+
? `@mermaid ${block.authoredExportName}`
|
|
695
|
+
: '@mermaid';
|
|
711
696
|
}
|
|
712
697
|
|
|
713
698
|
/**
|
|
@@ -717,7 +702,6 @@ function blockLabel(block: {
|
|
|
717
702
|
*/
|
|
718
703
|
function parseDocflowBlockGraph(
|
|
719
704
|
mermaidSource: string,
|
|
720
|
-
syntax: NonNullable<PlayDocflow['syntax']>,
|
|
721
705
|
errors: string[],
|
|
722
706
|
): ParsedDocflowBlockGraph | null {
|
|
723
707
|
const docLines = mermaidSource
|
|
@@ -746,11 +730,9 @@ function parseDocflowBlockGraph(
|
|
|
746
730
|
const sketchIds = new Set<string>();
|
|
747
731
|
const subgraphStack: PlayDocflowSubgraph[] = [];
|
|
748
732
|
const subgraphIds = new Set<string>();
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
if (opened) subgraphIds.add(opened[1]!);
|
|
753
|
-
}
|
|
733
|
+
for (const line of docLines.slice(1)) {
|
|
734
|
+
const opened = SUBGRAPH_OPEN.exec(line);
|
|
735
|
+
if (opened) subgraphIds.add(opened[1]!);
|
|
754
736
|
}
|
|
755
737
|
// Ids that were DECLARED with a shape somewhere, as opposed to merely named
|
|
756
738
|
// as an edge endpoint. A node nothing ever declares reaches the canvas
|
|
@@ -805,40 +787,38 @@ function parseDocflowBlockGraph(
|
|
|
805
787
|
return sawDeclaration;
|
|
806
788
|
};
|
|
807
789
|
for (const line of docLines.slice(1)) {
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
(
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
if (
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
);
|
|
833
|
-
}
|
|
834
|
-
subgraphStack.pop();
|
|
835
|
-
continue;
|
|
790
|
+
const opened = SUBGRAPH_OPEN.exec(line);
|
|
791
|
+
if (opened) {
|
|
792
|
+
const id = opened[1]!;
|
|
793
|
+
const rawLabel = opened[2]?.trim() ?? '';
|
|
794
|
+
const label =
|
|
795
|
+
rawLabel.length >= 2 &&
|
|
796
|
+
((rawLabel.startsWith('"') && rawLabel.endsWith('"')) ||
|
|
797
|
+
(rawLabel.startsWith("'") && rawLabel.endsWith("'")))
|
|
798
|
+
? rawLabel.slice(1, -1)
|
|
799
|
+
: rawLabel;
|
|
800
|
+
const subgraph: PlayDocflowSubgraph = {
|
|
801
|
+
id,
|
|
802
|
+
label: label || id,
|
|
803
|
+
memberIds: [],
|
|
804
|
+
};
|
|
805
|
+
subgraphs.set(id, subgraph);
|
|
806
|
+
subgraphStack.push(subgraph);
|
|
807
|
+
continue;
|
|
808
|
+
}
|
|
809
|
+
if (SUBGRAPH_END.test(line)) {
|
|
810
|
+
if (subgraphStack.length === 0) {
|
|
811
|
+
errors.push(
|
|
812
|
+
'Docflow has an `end` with no open `subgraph` above it. Every `end` closes exactly one `subgraph`.',
|
|
813
|
+
);
|
|
836
814
|
}
|
|
815
|
+
subgraphStack.pop();
|
|
816
|
+
continue;
|
|
837
817
|
}
|
|
838
818
|
let declaredOnThisLine = false;
|
|
839
819
|
const isDirective =
|
|
840
820
|
/^\s*(?:direction|classDef|class|style|linkStyle|click)\b/i.test(line);
|
|
841
|
-
if (
|
|
821
|
+
if (isDirective) {
|
|
842
822
|
const sketch = SKETCH_CLASS.exec(line);
|
|
843
823
|
if (sketch) {
|
|
844
824
|
for (const id of sketch[1]!.split(',')) {
|
|
@@ -849,14 +829,12 @@ function parseDocflowBlockGraph(
|
|
|
849
829
|
ignoredDirectives.push(line.trim());
|
|
850
830
|
}
|
|
851
831
|
}
|
|
852
|
-
if (
|
|
832
|
+
if (!isDirective) {
|
|
853
833
|
declaredOnThisLine = addNodes(line, line);
|
|
854
834
|
}
|
|
855
|
-
const chain = parseEdgeChain(line
|
|
835
|
+
const chain = parseEdgeChain(line);
|
|
856
836
|
if (!chain) {
|
|
857
|
-
if (
|
|
858
|
-
errors.push(`Unsupported docflow line: ${line.trim()}`);
|
|
859
|
-
} else if (!isDirective && !declaredOnThisLine) {
|
|
837
|
+
if (!isDirective && !declaredOnThisLine) {
|
|
860
838
|
// Mermaid the parser could not consume at all: no edge, no node
|
|
861
839
|
// declaration, not a directive it knowingly ignores. It used to be
|
|
862
840
|
// skipped in silence, so a typo'd arrow or a stray token simply removed
|
|
@@ -867,7 +845,7 @@ function parseDocflowBlockGraph(
|
|
|
867
845
|
}
|
|
868
846
|
continue;
|
|
869
847
|
}
|
|
870
|
-
|
|
848
|
+
addNodes(line, line);
|
|
871
849
|
for (const node of chain.nodes) {
|
|
872
850
|
// An edge endpoint may name a subgraph id; keep the edge but do not
|
|
873
851
|
// materialize a regular node for the region.
|
|
@@ -953,12 +931,6 @@ export function parsePlayDocflowFile(
|
|
|
953
931
|
sourceCode: string,
|
|
954
932
|
): PlayDocflowFileParseResult {
|
|
955
933
|
const errors: string[] = [];
|
|
956
|
-
// Ids whose annotation was written but refused. They are NOT unbound boxes:
|
|
957
|
-
// the author already has one actionable error about them, and telling them to
|
|
958
|
-
// "bind it" on the next line is telling them to do what they just did.
|
|
959
|
-
const rejectedNodeIds = new Set<string>();
|
|
960
|
-
const bindings = parseDocflowBindings(sourceCode, errors, rejectedNodeIds);
|
|
961
|
-
|
|
962
934
|
const rawBlocks: Array<{
|
|
963
935
|
syntax: NonNullable<PlayDocflow['syntax']>;
|
|
964
936
|
body: string;
|
|
@@ -967,18 +939,18 @@ export function parsePlayDocflowFile(
|
|
|
967
939
|
for (const match of sourceCode.matchAll(MERMAID_BLOCK)) {
|
|
968
940
|
rawBlocks.push({ syntax: 'mermaid', body: cleanBlockBody(match[1]!) });
|
|
969
941
|
}
|
|
970
|
-
if (rawBlocks.length === 0) {
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
942
|
+
if (rawBlocks.length === 0) return { blocks: [], bindings: [], errors };
|
|
943
|
+
|
|
944
|
+
// Ids whose annotation was written but refused. They are NOT unbound boxes:
|
|
945
|
+
// the author already has one actionable error about them, and telling them to
|
|
946
|
+
// "bind it" on the next line is telling them to do what they just did.
|
|
947
|
+
const rejectedNodeIds = new Set<string>();
|
|
948
|
+
const bindings = parseDocflowBindings(sourceCode, errors, rejectedNodeIds);
|
|
977
949
|
|
|
978
950
|
const parsedBlocks: ParsedDocflowBlock[] = [];
|
|
979
951
|
for (const raw of rawBlocks) {
|
|
980
952
|
const { exportName, diagram } = splitBlockExportHeader(raw.body);
|
|
981
|
-
const graph = parseDocflowBlockGraph(diagram,
|
|
953
|
+
const graph = parseDocflowBlockGraph(diagram, errors);
|
|
982
954
|
if (!graph) continue;
|
|
983
955
|
parsedBlocks.push({
|
|
984
956
|
...graph,
|
|
@@ -1324,10 +1296,8 @@ function attachBindingsToBlocks(
|
|
|
1324
1296
|
)
|
|
1325
1297
|
continue;
|
|
1326
1298
|
errors.push(
|
|
1327
|
-
block.
|
|
1328
|
-
|
|
1329
|
-
: `Docflow box "${node.id}" in \`${blockLabel(block)}\` points at nothing, so the canvas can only draw it and say nothing about it. ` +
|
|
1330
|
-
`Either put \`// @mermaid-node ${node.id}\` above the statement it runs, or — if this play has no such statement, because the work happens in another module or inside a loop — add it to a \`class ${node.id} sketch\` line in the block to declare it a sketch.`,
|
|
1299
|
+
`Docflow box "${node.id}" in \`${blockLabel(block)}\` points at nothing, so the canvas can only draw it and say nothing about it. ` +
|
|
1300
|
+
`Either put \`// @mermaid-node ${node.id}\` above the statement it runs, or — if this play has no such statement, because the work happens in another module or inside a loop — add it to a \`class ${node.id} sketch\` line in the block to declare it a sketch.`,
|
|
1331
1301
|
);
|
|
1332
1302
|
}
|
|
1333
1303
|
}
|
|
@@ -1389,23 +1359,18 @@ function parseDocflowBindings(
|
|
|
1389
1359
|
const bindings: PlayDocflowBinding[] = [];
|
|
1390
1360
|
|
|
1391
1361
|
for (let index = 0; index < lines.length; index += 1) {
|
|
1392
|
-
PUT.lastIndex = 0;
|
|
1393
1362
|
MERMAID_NODE.lastIndex = 0;
|
|
1394
|
-
const
|
|
1395
|
-
|
|
1396
|
-
if (!legacyMatch && !mermaidMatch) continue;
|
|
1363
|
+
const mermaidMatch = MERMAID_NODE.exec(lines[index]!) ?? null;
|
|
1364
|
+
if (!mermaidMatch) continue;
|
|
1397
1365
|
// Whatever this annotation names, the author has now written it down. Every
|
|
1398
1366
|
// `continue` below is a rejection, and a rejected id must not come back as
|
|
1399
1367
|
// an unbound box — see `rejectedNodeIds` in `parsePlayDocflowFile`.
|
|
1400
|
-
const annotatedId = mermaidMatch
|
|
1368
|
+
const annotatedId = mermaidMatch[1]?.trim();
|
|
1401
1369
|
if (annotatedId) rejectedNodeIds.add(annotatedId);
|
|
1402
1370
|
const attributes = parseAttributes(
|
|
1403
|
-
mermaidMatch
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
annotation: mermaidMatch ? '@mermaid-node' : 'put',
|
|
1407
|
-
errors,
|
|
1408
|
-
},
|
|
1371
|
+
mermaidMatch[2] ?? '',
|
|
1372
|
+
index + 1,
|
|
1373
|
+
errors,
|
|
1409
1374
|
);
|
|
1410
1375
|
if (!attributes) continue;
|
|
1411
1376
|
const id = (annotatedId ?? attributes.id)?.trim();
|