@dev.fast/whiteboard 0.0.0-stage → 0.2.0
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/LICENSE +21 -0
- package/README.md +146 -2
- package/THIRD_PARTY_NOTICES.md +93 -0
- package/dist/account-alias-DOHN1RSH.js +973 -0
- package/dist/agent-cli-ChOq0ZuO.js +117 -0
- package/dist/agent-cli-DzWfZr6G.js +2 -0
- package/dist/agent-client-CPQo7iTI.js +305 -0
- package/dist/authoring-tools-Cr6Kpjsx.js +234 -0
- package/dist/build-info.json +1 -0
- package/dist/cli-hVgHcdsv.js +47 -0
- package/dist/cli-runner-D8y2_luU.js +5095 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +128 -0
- package/dist/client-DFoh50W3.js +950 -0
- package/dist/desktop-discovery-DBO-PM2V.js +164 -0
- package/dist/error-message-OtiDonty.js +6 -0
- package/dist/fs-utils-BMPLt0cr.js +22 -0
- package/dist/fuzzy-match-BoAmcyak.js +30 -0
- package/dist/headless-host-BJxf4dUU.js +94 -0
- package/dist/input-error-OLgB_h31.js +11 -0
- package/dist/local-data-CxLk75rx.js +4491 -0
- package/dist/mcp-QeF8vkdf.js +140 -0
- package/dist/package-paths-B6-zxvIO.js +47 -0
- package/dist/process-error-telemetry-v9e5h6D7.js +3952 -0
- package/dist/profile-4ry2f3AM.js +76 -0
- package/dist/profile-Bh5FIrmf.js +2 -0
- package/dist/request-origin-D_4QIdoN.js +811 -0
- package/dist/review-agent-traces-DNJFYzc8.js +3866 -0
- package/dist/review-home-paths-6zZH-1c9.js +2560 -0
- package/dist/review-telemetry-DEFnQsQ2.js +1195 -0
- package/dist/runtime-CKiWhGrf.js +52 -0
- package/dist/runtime.d.ts +9 -0
- package/dist/runtime.js +2 -0
- package/dist/s3-SzLpeCG6.js +345 -0
- package/dist/s3-config-BgxkSoOy.js +2 -0
- package/dist/s3-config-CAxhO9u_.js +676 -0
- package/dist/server/desktop-host.d.ts +4 -0
- package/dist/server/desktop-host.js +1702 -0
- package/dist/server-discovery-Dbzn3w6Z.js +66 -0
- package/dist/sharing/index.d.ts +1977 -0
- package/dist/sharing/index.js +2 -0
- package/dist/src-CGP5ytbV.js +283 -0
- package/dist/src-CmdiBL20.js +3676 -0
- package/dist/src-DnwdaQ2r.js +865 -0
- package/dist/src-X9phtB2j.js +68 -0
- package/dist/stored-document-migration-DHeMpLMb.js +2671 -0
- package/dist/tool-failure-C8zB73HV.js +98 -0
- package/dist/tutorial-trace-DrBvkaeL.js +43 -0
- package/instructions/authoring.md +36 -0
- package/instructions/file-lenses.md +13 -0
- package/instructions/scratchpad.md +23 -0
- package/instructions/trace-archaeology.md +113 -0
- package/onboarding.md +8 -0
- package/package.json +106 -3
- package/src/agent-selection.ts +100 -0
- package/src/agent-session-ref.ts +80 -0
- package/src/ask/agents.ts +351 -0
- package/src/ask/checkout-files.ts +47 -0
- package/src/ask/file-refs.ts +111 -0
- package/src/ask/pi-mcp.ts +154 -0
- package/src/ask/protocol.ts +244 -0
- package/src/ask/thread-state.ts +350 -0
- package/src/ask/thread.ts +1400 -0
- package/src/ask/threads.ts +219 -0
- package/src/ask/watch.ts +79 -0
- package/src/cli-install.ts +1041 -0
- package/src/cli-runner.ts +1461 -0
- package/src/cli-runtime-info.ts +35 -0
- package/src/cli.ts +222 -0
- package/src/connect-prompts.ts +248 -0
- package/src/cursor-deeplink.ts +11 -0
- package/src/desktop-discovery.ts +355 -0
- package/src/diff-selection-migration.ts +63 -0
- package/src/embedded-posthog-key.ts +6 -0
- package/src/error-telemetry.ts +247 -0
- package/src/evidence.ts +14 -0
- package/src/exception-telemetry.ts +126 -0
- package/src/fixtures/blocks/call_stack_diff.json +27 -0
- package/src/fixtures/blocks/callout.json +11 -0
- package/src/fixtures/blocks/code.json +9 -0
- package/src/fixtures/blocks/code_peek.json +8 -0
- package/src/fixtures/blocks/database_lens.json +51 -0
- package/src/fixtures/blocks/divider.json +1 -0
- package/src/fixtures/blocks/fixtures.ts +33 -0
- package/src/fixtures/blocks/flow_diagram.json +36 -0
- package/src/fixtures/blocks/ids.ts +8 -0
- package/src/fixtures/blocks/image.json +9 -0
- package/src/fixtures/blocks/markdown.json +7 -0
- package/src/fixtures/blocks/section.json +11 -0
- package/src/fixtures/blocks/sequence.json +31 -0
- package/src/fixtures/blocks/software_map.json +7 -0
- package/src/fixtures/blocks/trace_quote.json +9 -0
- package/src/fixtures/blocks/tutorial.json +51 -0
- package/src/fs-utils.ts +32 -0
- package/src/fuzzy-match.ts +94 -0
- package/src/install.ts +29 -0
- package/src/legacy-skills.ts +133 -0
- package/src/lens-selection.ts +230 -0
- package/src/markdown-latex-math.ts +230 -0
- package/src/markdown.ts +81 -0
- package/src/package-paths.ts +53 -0
- package/src/posthog-capture-client.ts +610 -0
- package/src/review-api/README.md +295 -0
- package/src/review-api/activity.ts +340 -0
- package/src/review-api/agent-cli.ts +219 -0
- package/src/review-api/agent-client.ts +188 -0
- package/src/review-api/anchor-quotes.ts +88 -0
- package/src/review-api/ask-history.ts +242 -0
- package/src/review-api/authoring-tools.ts +211 -0
- package/src/review-api/blocks/call_stack_diff.ts +75 -0
- package/src/review-api/blocks/callout.ts +24 -0
- package/src/review-api/blocks/code.ts +9 -0
- package/src/review-api/blocks/code_peek.ts +13 -0
- package/src/review-api/blocks/database_lens.ts +150 -0
- package/src/review-api/blocks/definition.ts +40 -0
- package/src/review-api/blocks/divider.ts +6 -0
- package/src/review-api/blocks/flow_diagram.ts +111 -0
- package/src/review-api/blocks/image.ts +10 -0
- package/src/review-api/blocks/index.ts +90 -0
- package/src/review-api/blocks/markdown.ts +17 -0
- package/src/review-api/blocks/section.ts +24 -0
- package/src/review-api/blocks/sequence.ts +56 -0
- package/src/review-api/blocks/software_map.ts +9 -0
- package/src/review-api/blocks/trace_quote.ts +10 -0
- package/src/review-api/blocks/tutorial.ts +39 -0
- package/src/review-api/checkout-fs.ts +14 -0
- package/src/review-api/client.ts +1 -0
- package/src/review-api/comparison-coverage.ts +304 -0
- package/src/review-api/component-reference.ts +30 -0
- package/src/review-api/diff-lenses.ts +175 -0
- package/src/review-api/document-headings.ts +51 -0
- package/src/review-api/document-text.ts +200 -0
- package/src/review-api/document.ts +906 -0
- package/src/review-api/file-lenses.ts +100 -0
- package/src/review-api/http.ts +1877 -0
- package/src/review-api/image-decode.ts +29 -0
- package/src/review-api/input-error.ts +9 -0
- package/src/review-api/instructions.ts +89 -0
- package/src/review-api/lens-alignment.ts +52 -0
- package/src/review-api/local-data.ts +1837 -0
- package/src/review-api/map-input.ts +154 -0
- package/src/review-api/mcp-client-agent.ts +32 -0
- package/src/review-api/mcp.ts +240 -0
- package/src/review-api/origin.ts +43 -0
- package/src/review-api/profile.ts +146 -0
- package/src/review-api/public-tools.ts +102 -0
- package/src/review-api/pull-request.ts +389 -0
- package/src/review-api/read-schemas.ts +85 -0
- package/src/review-api/recovery.ts +2 -0
- package/src/review-api/request-origin.ts +33 -0
- package/src/review-api/review-progress.ts +382 -0
- package/src/review-api/status-tool.ts +10 -0
- package/src/review-api/store-schema.ts +22 -0
- package/src/review-api/store.ts +1647 -0
- package/src/review-api/tool-failure.ts +64 -0
- package/src/review-api/trace-schema.ts +13 -0
- package/src/review-api/traces.ts +119 -0
- package/src/review-api/unsupported-files.integration.ts +91 -0
- package/src/review-api/workspaces.ts +692 -0
- package/src/review-api/worktree-source.ts +170 -0
- package/src/review-api/worktree-structural.integration.ts +320 -0
- package/src/review-app-launcher.ts +432 -0
- package/src/review-app-picker.ts +168 -0
- package/src/review-app.ts +134 -0
- package/src/review-bundled-tools.ts +249 -0
- package/src/review-checkout-paths.ts +37 -0
- package/src/review-diff-files.ts +89 -0
- package/src/review-head-checkout.ts +300 -0
- package/src/review-home-paths.ts +73 -0
- package/src/review-info.ts +59 -0
- package/src/review-instances.ts +113 -0
- package/src/review-logger.ts +183 -0
- package/src/review-preferences.ts +88 -0
- package/src/review-prepare.ts +291 -0
- package/src/review-stack.ts +112 -0
- package/src/review-telemetry.ts +1066 -0
- package/src/runtime.ts +74 -0
- package/src/server/account-alias.ts +30 -0
- package/src/server/bounded-stream.ts +34 -0
- package/src/server/bug-report.ts +301 -0
- package/src/server/client-error-budget.ts +45 -0
- package/src/server/crash-report.ts +273 -0
- package/src/server/desktop-host-shutdown.ts +64 -0
- package/src/server/desktop-host.ts +190 -0
- package/src/server/desktop-server.ts +703 -0
- package/src/server/diffr-config.ts +509 -0
- package/src/server/diffr-languages.ts +97 -0
- package/src/server/global-verb-relay.ts +190 -0
- package/src/server/headless-host.ts +149 -0
- package/src/server/hono-http.ts +162 -0
- package/src/server/http-json.ts +24 -0
- package/src/server/json-review-reporting.ts +174 -0
- package/src/server/process-error-telemetry.ts +174 -0
- package/src/server/review-api-parsers.ts +90 -0
- package/src/server/review-lifecycle-telemetry.ts +73 -0
- package/src/server/review-open-watchdog.ts +46 -0
- package/src/server/review-server-core.ts +290 -0
- package/src/server/structural-comparisons.ts +153 -0
- package/src/server/structural-diff.ts +186 -0
- package/src/server/tutorial-service.ts +241 -0
- package/src/server/ui-telemetry.ts +198 -0
- package/src/server-discovery.ts +95 -0
- package/src/session-markers.ts +132 -0
- package/src/sharing/auth.ts +41 -0
- package/src/sharing/cli.ts +83 -0
- package/src/sharing/client.ts +334 -0
- package/src/sharing/export.ts +195 -0
- package/src/sharing/host.ts +316 -0
- package/src/sharing/import.ts +787 -0
- package/src/sharing/index.ts +16 -0
- package/src/sharing/repository.ts +145 -0
- package/src/sharing/routes.ts +34 -0
- package/src/slug.ts +23 -0
- package/src/software-map-diff-counts.ts +517 -0
- package/src/software-map-model.ts +1147 -0
- package/src/software-map-topology-diff.ts +260 -0
- package/src/source.ts +92 -0
- package/src/startup-trace.ts +232 -0
- package/src/stored-document-migration.ts +151 -0
- package/src/telemetry-clean-text.ts +257 -0
- package/src/telemetry-config.ts +298 -0
- package/src/telemetry-debug-sink.ts +38 -0
- package/src/telemetry.ts +13 -0
- package/src/trace-cli.ts +156 -0
- package/src/trace-storage-cli.ts +509 -0
- package/src/tutorial-conversation.ts +18 -0
- package/src/ui-telemetry-events.ts +765 -0
- package/src/unified-diff.ts +71 -0
- package/src/viewed-coverage.ts +259 -0
- package/src/windows-cli.ts +125 -0
- package/tutorial/document.json +275 -0
- package/tutorial/runtime-manifest.json +12 -0
- package/tutorial/sample-service/package.json +9 -0
- package/tutorial/sample-service/src/api/checkout-api.ts +17 -0
- package/tutorial/sample-service/src/app.ts +26 -0
- package/tutorial/sample-service/src/database/schema.ts +10 -0
- package/tutorial/sample-service/src/fulfillment/fulfillment-queue.ts +15 -0
- package/tutorial/sample-service/src/fulfillment/fulfillment-worker.ts +24 -0
- package/tutorial/sample-service/src/inventory/inventory-service.ts +11 -0
- package/tutorial/sample-service/src/orders/order-service.ts +34 -0
- package/tutorial/sample-service/src/orders/order.ts +21 -0
- package/tutorial/sample-service/src/orders/orders-repository.ts +24 -0
- package/tutorial/sample-service/src/payments/payment-gateway.ts +12 -0
- package/tutorial/sample-service/src/shipping/shipping-gateway.ts +15 -0
- package/tutorial/sample-service/tsconfig.json +12 -0
- package/tutorial/software-map.json +139 -0
- package/tutorial/trace.json +20 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export { ShareClient } from "./client.js";
|
|
2
|
+
|
|
3
|
+
export {
|
|
4
|
+
exportShare,
|
|
5
|
+
type ShareBundle,
|
|
6
|
+
type ShareExportData,
|
|
7
|
+
type ShareExportStore,
|
|
8
|
+
} from "./export.js";
|
|
9
|
+
|
|
10
|
+
export {
|
|
11
|
+
SharedReviewStore,
|
|
12
|
+
sharedReviewId,
|
|
13
|
+
validateShareBundle,
|
|
14
|
+
} from "./import.js";
|
|
15
|
+
|
|
16
|
+
export { verifyShareRepository } from "./repository.js";
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { mkdir, mkdtemp, rm } from "node:fs/promises";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
import { gitAt } from "@dev.fast/local-vcs";
|
|
6
|
+
import { normalizeGitHubRemote } from "@dev.fast/review-share-protocol";
|
|
7
|
+
import { type Pins, ReviewInputError } from "@review/review-api/document.js";
|
|
8
|
+
|
|
9
|
+
/** Keep credential helpers but prevent the calling shell from redirecting Git's object store. */
|
|
10
|
+
export function sharedGit(cwd: string, args: string[]) {
|
|
11
|
+
const env = Object.fromEntries(
|
|
12
|
+
Object.entries(process.env).filter(
|
|
13
|
+
([key]) =>
|
|
14
|
+
!key.startsWith("GIT_") ||
|
|
15
|
+
[
|
|
16
|
+
"GIT_SSH",
|
|
17
|
+
"GIT_SSH_COMMAND",
|
|
18
|
+
"GIT_ASKPASS",
|
|
19
|
+
"GIT_SSH_VARIANT",
|
|
20
|
+
].includes(key),
|
|
21
|
+
),
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
return gitAt(
|
|
25
|
+
cwd,
|
|
26
|
+
["-c", "core.hooksPath=/dev/null", "-c", "core.fsmonitor=false", ...args],
|
|
27
|
+
{
|
|
28
|
+
env: { ...env, GIT_TERMINAL_PROMPT: "0" },
|
|
29
|
+
timeout: 300_000,
|
|
30
|
+
},
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function fetchError(stderr: string, publishing: boolean): ReviewInputError {
|
|
35
|
+
if (/not our ref|couldn't find remote ref|unadvertised object/i.test(stderr))
|
|
36
|
+
return new ReviewInputError(
|
|
37
|
+
publishing
|
|
38
|
+
? "Push the reviewed commits to GitHub before sharing."
|
|
39
|
+
: "The shared commits are no longer available on GitHub. Ask the sender to restore them, then retry.",
|
|
40
|
+
409,
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
return new ReviewInputError(
|
|
44
|
+
"Could not fetch the GitHub repository. Check your connection and Git credentials for this repository, then retry.",
|
|
45
|
+
409,
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Recipients need complete history; publication only checks that the commits exist. */
|
|
50
|
+
export async function fetchPinnedRepository(
|
|
51
|
+
root: string,
|
|
52
|
+
url: string,
|
|
53
|
+
pins: Pick<Pins, "base" | "head">,
|
|
54
|
+
publishing = false,
|
|
55
|
+
) {
|
|
56
|
+
if (
|
|
57
|
+
![pins.base, pins.head].every((commit) =>
|
|
58
|
+
/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/.test(commit),
|
|
59
|
+
)
|
|
60
|
+
)
|
|
61
|
+
throw new ReviewInputError(
|
|
62
|
+
"Sharing requires immutable base and head commits.",
|
|
63
|
+
);
|
|
64
|
+
await mkdir(root, { recursive: true, mode: 0o700 });
|
|
65
|
+
await sharedGit(root, ["init", "--quiet"]);
|
|
66
|
+
|
|
67
|
+
if (!publishing) {
|
|
68
|
+
await sharedGit(root, ["config", "core.hooksPath", "/dev/null"]);
|
|
69
|
+
await sharedGit(root, ["config", "core.fsmonitor", "false"]);
|
|
70
|
+
await sharedGit(root, ["remote", "add", "origin", url]);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Branch refs make Git reject non-commit objects during the fetch itself.
|
|
74
|
+
const refs = publishing ? "refs/heads/review" : "refs/review";
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
await sharedGit(root, [
|
|
78
|
+
"fetch",
|
|
79
|
+
"--no-tags",
|
|
80
|
+
"--force",
|
|
81
|
+
...(publishing ? ["--depth=1", "--filter=tree:0"] : []),
|
|
82
|
+
publishing ? url : "origin",
|
|
83
|
+
`${pins.base}:${refs}/base`,
|
|
84
|
+
`${pins.head}:${refs}/head`,
|
|
85
|
+
]);
|
|
86
|
+
} catch (error) {
|
|
87
|
+
throw fetchError(
|
|
88
|
+
error instanceof Error && "stderr" in error ? String(error.stderr) : "",
|
|
89
|
+
publishing,
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (!publishing)
|
|
94
|
+
for (const commit of [pins.base, pins.head])
|
|
95
|
+
await sharedGit(root, ["cat-file", "-e", `${commit}^{commit}`]);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export async function repositoryReady(
|
|
99
|
+
root: string,
|
|
100
|
+
pins: Pick<Pins, "base" | "head">,
|
|
101
|
+
): Promise<boolean> {
|
|
102
|
+
try {
|
|
103
|
+
const head = await sharedGit(root, ["rev-parse", "HEAD"]);
|
|
104
|
+
|
|
105
|
+
if (head.stdout.trim() !== pins.head) return false;
|
|
106
|
+
|
|
107
|
+
for (const commit of [pins.base, pins.head])
|
|
108
|
+
await sharedGit(root, ["cat-file", "-e", `${commit}^{commit}`]);
|
|
109
|
+
|
|
110
|
+
return true;
|
|
111
|
+
} catch {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export async function readShareRepository(root: string) {
|
|
117
|
+
let cloneUrl: string;
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
cloneUrl = normalizeGitHubRemote(
|
|
121
|
+
(await sharedGit(root, ["remote", "get-url", "origin"])).stdout,
|
|
122
|
+
);
|
|
123
|
+
} catch {
|
|
124
|
+
throw new ReviewInputError("Sharing requires a GitHub origin remote.");
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return { cloneUrl };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export async function verifyShareRepository(
|
|
131
|
+
root: string,
|
|
132
|
+
pins: Pins,
|
|
133
|
+
repository?: { cloneUrl: string },
|
|
134
|
+
) {
|
|
135
|
+
const { cloneUrl } = repository ?? (await readShareRepository(root));
|
|
136
|
+
const temporary = await mkdtemp(path.join(tmpdir(), "review-share-check-"));
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
await fetchPinnedRepository(temporary, cloneUrl, pins, true);
|
|
140
|
+
} finally {
|
|
141
|
+
await rm(temporary, { recursive: true, force: true });
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return { cloneUrl };
|
|
145
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { ReviewInputError } from "@review/review-api/document.js";
|
|
2
|
+
|
|
3
|
+
import type { SharedReviewStore } from "./import.js";
|
|
4
|
+
|
|
5
|
+
/** Snapshot-backed reads used by the same HTTP routes as local reviews. */
|
|
6
|
+
export class SharedReviewData {
|
|
7
|
+
constructor(private readonly store: SharedReviewStore) {}
|
|
8
|
+
|
|
9
|
+
map(id: string, resourceId: string) {
|
|
10
|
+
const map = this.store.get(id).presentation.maps[resourceId];
|
|
11
|
+
|
|
12
|
+
if (!map)
|
|
13
|
+
throw new ReviewInputError("Map is not included in this share.", 404);
|
|
14
|
+
|
|
15
|
+
return map;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async resource(id: string, resourceId: string) {
|
|
19
|
+
const resource = this.store
|
|
20
|
+
.get(id)
|
|
21
|
+
.manifest.resources.find((item) => item.id === resourceId);
|
|
22
|
+
|
|
23
|
+
if (!resource)
|
|
24
|
+
throw new ReviewInputError(
|
|
25
|
+
"Resource is not included in this share.",
|
|
26
|
+
404,
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
data: await this.store.readObject(id, resource.object),
|
|
31
|
+
mimeType: resource.mimeType,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
}
|
package/src/slug.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** Lowercase ASCII slug: runs of anything but `a-z0-9` become one `-`, outer
|
|
2
|
+
* dashes go. Empty input, or input with no letters or digits, yields `""`;
|
|
3
|
+
* callers choose their own fallback (`"section"`, `"diagram"`, `"database"`).
|
|
4
|
+
* This is the rule sequence and lens ids were already published with, so it
|
|
5
|
+
* must not change. */
|
|
6
|
+
export function slugify(text: string): string {
|
|
7
|
+
return text
|
|
8
|
+
.trim()
|
|
9
|
+
.toLowerCase()
|
|
10
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
11
|
+
.replace(/^-+|-+$/g, "");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** First of `base`, `base-2`, `base-3`, … not present in `used`. */
|
|
15
|
+
export function uniqueId(base: string, used: ReadonlySet<string>): string {
|
|
16
|
+
let candidate = base;
|
|
17
|
+
|
|
18
|
+
for (let suffix = 2; used.has(candidate); suffix += 1) {
|
|
19
|
+
candidate = `${base}-${suffix}`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return candidate;
|
|
23
|
+
}
|