Skip to content

Commit 92a776a

Browse files
Copilotkhanaffan
andauthored
Merge remote-tracking branch 'origin/master' into affan.khan/v2-checkpoint-as-briefcase
# Conflicts: # docs/changehistory/NextVersion.md Co-authored-by: khanaffan <29756942+khanaffan@users.noreply.github.com>
2 parents 18966cd + f7e4c30 commit 92a776a

302 files changed

Lines changed: 15049 additions & 1810 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/agents/electron-version-bump.agent.md

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,9 @@ grep -r '"electron":' --include='package.json' -l .
115115

116116
Compare the results with the list above and include any additional files found.
117117

118-
### Step 3: Review and apply breaking changes
118+
### Step 3: Review breaking changes and get approval
119+
120+
**This step requires explicit invoker approval before proceeding to file modifications.**
119121

120122
Fetch the Electron breaking changes documentation. Use the raw URL to avoid GitHub rate limiting:
121123

@@ -144,10 +146,7 @@ grep -r "require(\"electron\")" --include='*.ts' --include='*.js' -l .
144146
For each breaking change listed in the Electron docs for the new major version:
145147

146148
1. **Identify** whether the breaking change affects any API used in this codebase.
147-
2. **If affected**, apply the necessary code fix:
148-
- Search the codebase for all usages of the affected API.
149-
- Update the code to use the new API or pattern as prescribed by the Electron breaking changes doc.
150-
- Ensure backward compatibility with the minimum supported Electron version (currently ^35.0.0) — use feature detection or version checks when the old API is removed and a polyfill is needed.
149+
2. **If affected**, describe the required code fix and which files need modification.
151150
3. **If not affected**, note it and move on.
152151

153152
**Common breaking change categories to watch for:**
@@ -159,6 +158,24 @@ For each breaking change listed in the Electron docs for the new major version:
159158
- New required permissions or security policy changes
160159
- Deprecated APIs that are now removed
161160

161+
#### Present findings and wait for approval
162+
163+
After completing the analysis, present a summary table to the invoker:
164+
165+
| Breaking change | Affected? | Proposed fix |
166+
| --- | --- | --- |
167+
| (change description) | Yes / No | (fix description or "N/A") |
168+
169+
**STOP here and wait for the invoker's explicit go-ahead before making any file changes.** If the invoker requests modifications to the proposed approach, incorporate their feedback before proceeding.
170+
171+
#### Apply breaking change fixes (after approval)
172+
173+
Once approved, for each affected breaking change:
174+
175+
- Search the codebase for all usages of the affected API.
176+
- Update the code to use the new API or pattern as prescribed by the Electron breaking changes doc.
177+
- Ensure backward compatibility with the minimum supported Electron version documented in docs/learning/SupportedPlatforms.md — use feature detection or version checks when the old API is removed and a polyfill is needed.
178+
162179
If a breaking change requires a non-trivial migration (e.g., architectural changes), document the issue in the report. If triggered from a GitHub issue, add a comment to the issue describing the blocker and wait for guidance. Otherwise, ask the invoker before proceeding with the fix.
163180

164181
### Step 3.5: Update `@itwin/electron-authorization` if needed
@@ -290,10 +307,13 @@ gh pr create \
290307
--head electron-<NEW_MAJOR> \
291308
--title "Add support for Electron <NEW_MAJOR>" \
292309
--body "$(cat <<'EOF'
293-
Adds support for Electron <NEW_MAJOR>.
294-
295310
### Breaking changes review
296-
<Include the breaking changes assessment here — list each change with "affected" / "not affected" status>
311+
312+
<Include the breaking changes assessment table from Step 3 here>
313+
314+
### Notes
315+
316+
<Include any relevant notes, e.g. unmet peer dependencies>
297317
298318
See [Electron <NEW_MAJOR> release blog](https://www.electronjs.org/blog/electron-<NEW_MAJOR>-0) for details.
299319
EOF
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"version": 1,
3+
"hooks": {
4+
"preToolUse": [
5+
{
6+
"type": "command",
7+
"bash": "node .github/hooks/scripts/itwinjs-core-pre-tool-use.mjs",
8+
"cwd": ".",
9+
"timeoutSec": 30
10+
}
11+
],
12+
"postToolUse": [
13+
{
14+
"type": "command",
15+
"bash": "node .github/hooks/scripts/itwinjs-core-post-tool-use.mjs",
16+
"cwd": ".",
17+
"timeoutSec": 10
18+
}
19+
]
20+
}
21+
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
export async function readHookInput() {
2+
const chunks = [];
3+
for await (const chunk of process.stdin) {
4+
chunks.push(chunk);
5+
}
6+
7+
const input = Buffer.concat(chunks).toString("utf8").trim();
8+
if (!input) {
9+
return {};
10+
}
11+
12+
try {
13+
return JSON.parse(input);
14+
} catch {
15+
return {};
16+
}
17+
}
18+
19+
export function parseToolArgs(toolArgs) {
20+
if (!toolArgs) {
21+
return {};
22+
}
23+
24+
if (typeof toolArgs === "object") {
25+
return toolArgs;
26+
}
27+
28+
if (typeof toolArgs === "string") {
29+
try {
30+
return JSON.parse(toolArgs);
31+
} catch {
32+
return { raw: toolArgs };
33+
}
34+
}
35+
36+
return {};
37+
}
38+
39+
export function getBashCommand(input) {
40+
const args = parseToolArgs(input.toolArgs);
41+
return typeof args.command === "string" ? args.command : "";
42+
}
43+
44+
export function isGitCommitCommand(command) {
45+
return /\bgit\s+(?:-[^\s]+\s+)*commit\b/.test(command);
46+
}
47+
48+
export function getChangedPathText(input) {
49+
const args = parseToolArgs(input.toolArgs);
50+
const directPath = args.file_path ?? args.path ?? "";
51+
const summary = getPathSummary(args);
52+
53+
try {
54+
const summaryText = Object.keys(summary).length > 0 ? JSON.stringify(summary) : "";
55+
return summaryText ? `${directPath}\n${summaryText}` : `${directPath}\n`;
56+
} catch {
57+
return `${directPath}\n`;
58+
}
59+
}
60+
61+
export function getChangedPaths(input) {
62+
const args = parseToolArgs(input.toolArgs);
63+
const paths = [];
64+
for (const value of [args.file_path, args.path, args.old_path, args.new_path, args.paths, args.files]) {
65+
if (typeof value === "string" && value.length > 0) {
66+
paths.push(value);
67+
} else if (Array.isArray(value)) {
68+
paths.push(...value.filter((entry) => typeof entry === "string" && entry.length > 0));
69+
}
70+
}
71+
72+
return paths;
73+
}
74+
75+
export function isPackageJsonPath(path) {
76+
return String(path).split(/[\\/]/).pop() === "package.json";
77+
}
78+
79+
export function isWriteLikeTool(toolName) {
80+
return /^(edit|create|write|apply_patch)$/i.test(String(toolName ?? ""));
81+
}
82+
83+
export function compactJson(value) {
84+
return `${JSON.stringify(value)}\n`;
85+
}
86+
87+
function getPathSummary(args) {
88+
const summary = {};
89+
for (const key of ["file_path", "path", "old_path", "new_path", "paths", "files"]) {
90+
const value = args?.[key];
91+
if (typeof value === "string" && value.length > 0) {
92+
summary[key] = value;
93+
} else if (Array.isArray(value)) {
94+
const paths = value.filter((entry) => typeof entry === "string" && entry.length > 0);
95+
if (paths.length > 0) {
96+
summary[key] = paths;
97+
}
98+
}
99+
}
100+
101+
return summary;
102+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import {
2+
getBashCommand,
3+
getChangedPaths,
4+
getChangedPathText,
5+
isGitCommitCommand,
6+
isPackageJsonPath,
7+
isWriteLikeTool,
8+
readHookInput,
9+
} from "./itwinjs-core-hook-utils.mjs";
10+
11+
const input = await readHookInput();
12+
const messages = [];
13+
const isWriteLike = isWriteLikeTool(input.toolName);
14+
15+
if (isWriteLike) {
16+
const changedPaths = getChangedPaths(input);
17+
18+
if (changedPaths.some(isPackageJsonPath)) {
19+
messages.push("[itwinjs] package.json changed - run rush update to sync pnpm-lock.yaml before committing.");
20+
}
21+
22+
if (changedPaths.some((path) => path.includes("tsconfig")) || /tsconfig/i.test(getChangedPathText(input))) {
23+
messages.push(
24+
"[itwinjs] tsconfig changed - clean and rebuild to avoid stale lib/ artifacts: from the affected package directory run rushx clean && rushx build, or from the repo root use rush clean && rush build.",
25+
);
26+
}
27+
}
28+
29+
if (String(input.toolName ?? "").toLowerCase() === "bash") {
30+
const command = getBashCommand(input);
31+
if (isGitCommitCommand(command)) {
32+
messages.push(
33+
"[itwinjs] Commit checklist: (1) any .ts files staged? -> rush lint from the repo root, or rushx lint from the affected package; (2) any @public/@beta symbol or TSDoc comment changed? -> rush extract-api + commit .api.md; (3) package.json dep changed? -> rush update committed; (4) changed a package with dependents? -> run downstream tests.",
34+
);
35+
}
36+
}
37+
38+
if (messages.length > 0) {
39+
process.stderr.write(`${messages.join("\n")}\n`);
40+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { execFile } from "node:child_process";
2+
import { dirname, resolve } from "node:path";
3+
import { fileURLToPath } from "node:url";
4+
import { compactJson, getBashCommand, isGitCommitCommand, readHookInput } from "./itwinjs-core-hook-utils.mjs";
5+
6+
const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), "../../..");
7+
const rushLauncherPath = resolve(repoRoot, "common/scripts/install-run-rush.js");
8+
9+
function runRushChangeVerify() {
10+
return new Promise((resolve, reject) => {
11+
execFile(process.execPath, [rushLauncherPath, "change", "--verify"], { cwd: repoRoot, timeout: 30_000 }, (error, stdout, stderr) => {
12+
if (error) {
13+
reject(new Error(stderr || stdout || error.message));
14+
return;
15+
}
16+
resolve();
17+
});
18+
});
19+
}
20+
21+
const input = await readHookInput();
22+
23+
if (String(input.toolName ?? "").toLowerCase() !== "bash") {
24+
process.exit(0);
25+
}
26+
27+
const command = getBashCommand(input);
28+
if (!isGitCommitCommand(command)) {
29+
process.exit(0);
30+
}
31+
32+
try {
33+
await runRushChangeVerify();
34+
process.exit(0);
35+
} catch (error) {
36+
process.stdout.write(
37+
compactJson({
38+
permissionDecision: "deny",
39+
permissionDecisionReason: `rush change --verify failed before git commit: ${
40+
error instanceof Error ? error.message.trim() : String(error)
41+
}`,
42+
}),
43+
);
44+
}

.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,14 @@ CLAUDE.local.md
6060
.claude/settings.local.json
6161
.claude/*.local.md
6262

63+
# GitHub Copilot CLI - personal/local overrides (do not commit)
64+
COPILOT.local.md
65+
**/COPILOT.local.md
66+
AGENTS.local.md
67+
**/AGENTS.local.md
68+
.copilot/settings.local.json
69+
.copilot/*.local.md
70+
6371
common/api/summary/summary.exports.csv
6472

6573
*.log

common/api/core-backend.api.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -694,7 +694,7 @@ export class BriefcaseManager {
694694
// @internal (undocumented)
695695
static getChangedElementsPathName(iModelId: GuidString): LocalFileName;
696696
// @internal
697-
static getChangedInstancesDataForTxn(db: BriefcaseDb, txnId: string): InstancePatch[];
697+
static getChangedInstancesDataForTxn(db: BriefcaseDb, txnId: string): AsyncGenerator<InstancePatch>;
698698
// @internal (undocumented)
699699
static getChangeSetsPath(iModelId: GuidString): LocalDirName;
700700
static getFileName(briefcase: BriefcaseProps): LocalFileName;
@@ -731,6 +731,8 @@ export class BriefcaseManager {
731731
// @internal
732732
static semanticRebaseSchemaFolderExists(db: BriefcaseDb, txnId: string): boolean;
733733
// @internal
734+
static storeChangedInstancesForSemanticRebase(db: BriefcaseDb, txnId: string, instancePatches: IterableIterator<ChangeInstance>): void;
735+
// @internal
734736
static storeSchemasForSemanticRebase<T extends LocalFileName[] | string[]>(db: BriefcaseDb, txnId: string, schemaFileNames: T): void;
735737
}
736738

@@ -4542,6 +4544,7 @@ export class IModelJsFs {
45424544
static readdirSync(pathname: string): string[];
45434545
static readFileSync(pathname: string): string | Buffer;
45444546
static readFileWithEncodingSync(pathname: string, encoding: BufferEncoding): string;
4547+
static readLines(pathname: string): AsyncGenerator<string>;
45454548
static recursiveFindSync(rootDir: string, pattern: RegExp): string[];
45464549
static recursiveMkDirSync(dirPath: string): void;
45474550
static removeSync(pathname: string): void;
@@ -4679,15 +4682,9 @@ export interface InstanceChange {
46794682
}
46804683

46814684
// @internal
4682-
export interface InstancePatch {
4683-
// (undocumented)
4684-
isIndirect: boolean;
4685+
export interface InstancePatch extends Omit<ChangeInstance, "$meta"> {
46854686
// (undocumented)
4686-
key: PatchInstanceKey;
4687-
// (undocumented)
4688-
op: "Inserted" | "Updated" | "Deleted";
4689-
// (undocumented)
4690-
props?: ECSqlRow;
4687+
$meta: Pick<ChangeMeta, "op" | "stage" | "isIndirectChange">;
46914688
}
46924689

46934690
// @beta
@@ -5170,6 +5167,7 @@ export class LocalHub {
51705167
queryLocks(): LocksEntry[];
51715168
// (undocumented)
51725169
queryLockStatus(elementId: Id64String): LockStatus;
5170+
queryNearestCheckpoint(changesetIndex: ChangesetIndex): ChangesetIndex;
51735171
queryPreviousCheckpoint(changesetIndex: ChangesetIndex): ChangesetIndex;
51745172
// (undocumented)
51755173
releaseAllLocks(arg: {
@@ -7588,6 +7586,8 @@ export class TxnManager {
75887586
cancelTo(txnId: TxnIdString): IModelStatus;
75897587
// @beta
75907588
cancelToTxnAsync(txnId: TxnIdString, args?: ReverseTxnArgs): Promise<void>;
7589+
// @internal
7590+
protected _captureInstanceChanges(id: TxnIdString): void;
75917591
deleteAllTxns(): void;
75927592
endMultiTxnOperation(): DbResult;
75937593
getChangeTrackingMemoryUsed(): number;

common/api/core-common.api.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8622,7 +8622,7 @@ export function resolveNavProp(navProp: RelatedElementProps | undefined, depreca
86228622
export function resolveNavPropId(navProp: RelatedElementProps | undefined, deprecatedNavPropId: Id64String): Id64String;
86238623

86248624
// @internal (undocumented)
8625-
export class ResponseLike implements Response {
8625+
export class ResponseLike {
86268626
constructor(data: any);
86278627
// (undocumented)
86288628
arrayBuffer(): Promise<ArrayBuffer>;

0 commit comments

Comments
 (0)