Skip to content

Commit 596cf3e

Browse files
authored
Merge branch 'main' into dependabot/npm_and_yarn/js-yaml-5.0.0
2 parents a1e2f59 + 075545c commit 596cf3e

14 files changed

Lines changed: 781 additions & 46 deletions

File tree

.claude/commands/review-code.md

Lines changed: 523 additions & 0 deletions
Large diffs are not rendered by default.

.github/workflows/pr-reviewer.yml

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
name: PR Reviewer
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
pr_number:
7+
description: 'PR number to review'
8+
required: true
9+
type: string
10+
model:
11+
description: 'Claude model to use'
12+
required: false
13+
default: 'claude-sonnet-4-6'
14+
type: string
15+
16+
permissions:
17+
contents: read
18+
pull-requests: write
19+
issues: write
20+
21+
jobs:
22+
review:
23+
name: 'Review PR #${{ inputs.pr_number }}'
24+
runs-on: ubuntu-latest
25+
timeout-minutes: 15
26+
steps:
27+
- name: Validate PR number
28+
env:
29+
PR_NUMBER: ${{ inputs.pr_number }}
30+
run: |
31+
echo "$PR_NUMBER" | grep -qE '^[1-9][0-9]*$' || {
32+
echo "ERROR: pr_number must be a positive integer, got: $PR_NUMBER"
33+
exit 1
34+
}
35+
36+
- name: Get GitHub App token
37+
id: app_token
38+
uses: actions/create-github-app-token@v3.2.0
39+
with:
40+
client-id: ${{ vars.AGENT_GITHUB_APP_ID }}
41+
private-key: ${{ secrets.AGENT_GITHUB_APP_PEM }}
42+
owner: ${{ github.repository_owner }}
43+
44+
- name: Checkout repository
45+
uses: actions/checkout@v7.0.0
46+
with:
47+
token: ${{ steps.app_token.outputs.token }}
48+
# Full history is required for `git merge-base` and the
49+
# `origin/<base>...HEAD` diff used by the review command. A shallow
50+
# clone leaves the merge base out of history and the diff comes back
51+
# empty or wrong.
52+
fetch-depth: 0
53+
54+
- name: Render prompt
55+
id: prompt
56+
env:
57+
PR_NUMBER: ${{ inputs.pr_number }}
58+
run: |
59+
python3 - <<'EOF'
60+
import os, re
61+
with open('.claude/commands/review-code.md') as f:
62+
content = f.read()
63+
content = re.sub(r'^---\n.*?\n---\n', '', content, flags=re.DOTALL)
64+
pr_number = os.environ['PR_NUMBER']
65+
content = content.replace('$ARGUMENTS', pr_number)
66+
content = content.replace('{PR_NUMBER}', pr_number)
67+
with open(os.environ['GITHUB_OUTPUT'], 'a') as out:
68+
out.write('content<<__EOF__\n')
69+
out.write(content.strip())
70+
out.write('\n__EOF__\n')
71+
EOF
72+
73+
- name: Run PR review
74+
id: review
75+
env:
76+
# The review command shells out to `gh` (pr checkout, summary-comment
77+
# upsert). Those calls need an authenticated token in the Bash env —
78+
# the MCP github tools are authenticated separately and do not cover
79+
# raw `gh` invocations.
80+
GH_TOKEN: ${{ steps.app_token.outputs.token }}
81+
uses: anthropics/claude-code-action@80b31826338489861333dc17217865dfe8085cdc # v1
82+
with:
83+
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
84+
github_token: ${{ steps.app_token.outputs.token }}
85+
claude_args: >-
86+
--allowedTools "Bash,Read,Glob,Grep,Write,Agent"
87+
--model "${{ inputs.model }}"
88+
prompt: ${{ steps.prompt.outputs.content }}
89+
90+
- name: Notify on failure
91+
if: failure()
92+
env:
93+
GH_TOKEN: ${{ steps.app_token.outputs.token }}
94+
PR_NUMBER: ${{ inputs.pr_number }}
95+
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
96+
run: |
97+
gh pr comment "$PR_NUMBER" --body "<!-- claude-pr-review-error -->
98+
**Claude PR Review failed.** The review could not be completed — [check the Actions log]($RUN_URL) for details."

THIRD_PARTY_LICENSES.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30738,6 +30738,7 @@ The following npm packages may be included in this product:
3073830738
- ansi-styles@4.3.0
3073930739
- callsites@3.1.0
3074030740
- chalk@2.4.2
30741+
- chalk@4.1.2
3074130742
- env-paths@2.2.1
3074230743
- file-type@6.2.0
3074330744
- has-flag@3.0.0
@@ -30761,6 +30762,7 @@ The following npm packages may be included in this product:
3076130762
- string-width@4.2.3
3076230763
- strip-ansi@6.0.1
3076330764
- supports-color@5.5.0
30765+
- supports-color@7.2.0
3076430766
- yn@3.1.1
3076530767

3076630768
These packages each contain the following license:

compose.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ services:
33
tables:
44
container_name: tables
55
restart: unless-stopped
6-
image: public.ecr.aws/openops/openops-tables:0.2.20
6+
image: public.ecr.aws/openops/openops-tables:0.2.21
77
environment:
88
BASEROW_PUBLIC_URL: ${OPS_OPENOPS_TABLES_PUBLIC_URL}
99
BASEROW_PRIVATE_URL: http://localhost:3001

deploy/docker-compose/docker-compose.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ services:
2323
environment:
2424
OPS_COMPONENT: app
2525
OPS_VERSION: ${OPS_VERSION:-latest}
26-
OPS_OPENOPS_TABLES_VERSION: 0.2.20
26+
OPS_OPENOPS_TABLES_VERSION: 0.2.21
2727
OPS_ANALYTICS_VERSION: 0.14.7
2828
OPS_WORKER_URL: http://openops-worker:3000
2929
depends_on:
@@ -50,7 +50,7 @@ services:
5050
depends_on:
5151
- openops-app
5252
openops-tables:
53-
image: public.ecr.aws/openops/openops-tables:0.2.20
53+
image: public.ecr.aws/openops/openops-tables:0.2.21
5454
restart: unless-stopped
5555
environment:
5656
BASEROW_PUBLIC_URL: ${OPS_OPENOPS_TABLES_PUBLIC_URL}

package-lock.json

Lines changed: 1 addition & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@
158158
"axios-retry": "4.5.0",
159159
"bcrypt": "6.0.0",
160160
"bullmq": "5.8.3",
161+
"chalk": "4.1.2",
161162
"chokidar": "3.6.0",
162163
"class-variance-authority": "0.7.1",
163164
"classnames": "2.5.1",
@@ -370,7 +371,6 @@
370371
"autoprefixer": "10.5.0",
371372
"babel-jest": "29.7.0",
372373
"cd": "0.3.3",
373-
"chalk": "4.1.2",
374374
"chromatic": "11.29.0",
375375
"concurrently": "8.2.2",
376376
"esbuild": "0.28.1",

packages/engine/src/lib/code-block/code-builder.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import fs, { rm } from 'node:fs/promises';
1515
import path from 'node:path';
1616
import { rollup } from 'rollup';
1717
import { ExecutionMode } from '../core/code/execution-mode';
18+
import { BASE64_POLYFILL_TYPES } from '../core/code/polyfills/base64-polyfill';
1819
import { CodeArtifact } from './code-artifact';
1920

2021
const executionMode = system.get<ExecutionMode>(
@@ -140,6 +141,12 @@ const compileCode = async ({
140141
encoding: 'utf8',
141142
flag: 'w',
142143
});
144+
// Declare the polyfilled globals (btoaUtf8/atobUtf8) so tsc can type-check
145+
// code blocks that use them; the runtime definitions live in BASE64_POLYFILL.
146+
await fs.writeFile(`${path}/openops-globals.d.ts`, BASE64_POLYFILL_TYPES, {
147+
encoding: 'utf8',
148+
flag: 'w',
149+
});
143150
await fs.writeFile(`${path}/index.ts`, code, { encoding: 'utf8', flag: 'w' });
144151

145152
await packageManager.exec({

packages/engine/src/lib/core/code/polyfills/base64-polyfill.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,12 @@ export const BASE64_POLYFILL = `(() => {
3939
};
4040
}
4141
42+
if (typeof globalThis.btoaUtf8 !== 'function') {
43+
globalThis.btoaUtf8 = (str) => {
44+
return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, (_, h) => String.fromCharCode(parseInt(h, 16))));
45+
}
46+
}
47+
4248
if (typeof globalThis.atob !== 'function') {
4349
globalThis.atob = (input) => {
4450
let string = String(input).replace(/[\\t\\n\\f\\r ]+/g, '');
@@ -89,4 +95,21 @@ export const BASE64_POLYFILL = `(() => {
8995
return output;
9096
};
9197
}
98+
99+
if (typeof globalThis.atobUtf8 !== 'function') {
100+
globalThis.atobUtf8 = (str) => {
101+
return decodeURIComponent(Array.prototype.map.call(atob(str), (c) => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)).join(''));
102+
}
103+
}
92104
})();`;
105+
106+
/**
107+
* Ambient declarations for the custom globals the polyfill injects at runtime.
108+
* `btoa`/`atob` are already typed via the `dom` lib, but `btoaUtf8`/`atobUtf8`
109+
* are OpenOps-specific, so without this declaration `tsc` rejects code blocks
110+
* that use them with `TS2304: Cannot find name 'btoaUtf8'`. This is written into
111+
* each code block's compile directory by the code builder so the two stay in sync.
112+
*/
113+
export const BASE64_POLYFILL_TYPES = `declare function btoaUtf8(data: string): string;
114+
declare function atobUtf8(data: string): string;
115+
`;
Lines changed: 60 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,66 @@
11
import { v8IsolateCodeSandbox } from '../../../src/lib/core/code/v8-isolate-code-sandbox'
22

33
const runScript = (script: string): Promise<unknown> =>
4-
v8IsolateCodeSandbox.runScript({ script, scriptContext: {} })
4+
v8IsolateCodeSandbox.runScript({ script, scriptContext: {} })
55

66
describe('v8IsolateCodeSandbox base64 polyfill', () => {
7-
it('exposes btoa that matches Latin1 base64 encoding', async () => {
8-
const result = await runScript("btoa('hello world')")
9-
expect(result).toBe(Buffer.from('hello world', 'latin1').toString('base64'))
10-
})
11-
12-
it('exposes atob that decodes base64 back to the original string', async () => {
13-
const encoded = Buffer.from('hello world', 'latin1').toString('base64')
14-
const result = await runScript(`atob('${encoded}')`)
15-
expect(result).toBe('hello world')
16-
})
17-
18-
it('round-trips a value through btoa and atob', async () => {
19-
const result = await runScript("atob(btoa('ServiceNow ticket #42'))")
20-
expect(result).toBe('ServiceNow ticket #42')
21-
})
22-
23-
it('decodes a base64-encoded ServiceNow batch response body', async () => {
24-
const body = Buffer.from(
25-
JSON.stringify({ result: { sys_id: 'abc123def456' } }),
26-
'latin1',
27-
).toString('base64')
28-
const result = await runScript(
29-
`JSON.parse(atob('${body}')).result.sys_id`,
30-
)
31-
expect(result).toBe('abc123def456')
32-
})
33-
34-
it('throws when btoa receives characters outside the Latin1 range', async () => {
35-
await expect(runScript("btoa('日本語')")).rejects.toThrow(/Latin1 range/)
36-
})
7+
it('exposes btoa that matches Latin1 base64 encoding', async () => {
8+
const result = await runScript("btoa('hello world')")
9+
expect(result).toBe(Buffer.from('hello world', 'latin1').toString('base64'))
10+
})
11+
12+
it('exposes atob that decodes base64 back to the original string', async () => {
13+
const encoded = Buffer.from('hello world', 'latin1').toString('base64')
14+
const result = await runScript(`atob('${encoded}')`)
15+
expect(result).toBe('hello world')
16+
})
17+
18+
it('round-trips a value through btoa and atob', async () => {
19+
const result = await runScript("atob(btoa('ServiceNow ticket #42'))")
20+
expect(result).toBe('ServiceNow ticket #42')
21+
})
22+
23+
it('decodes a base64-encoded ServiceNow batch response body', async () => {
24+
const body = Buffer.from(
25+
JSON.stringify({ result: { sys_id: 'abc123def456' } }),
26+
'latin1',
27+
).toString('base64')
28+
const result = await runScript(
29+
`JSON.parse(atob('${body}')).result.sys_id`,
30+
)
31+
expect(result).toBe('abc123def456')
32+
})
33+
34+
it('throws when btoa receives characters outside the Latin1 range', async () => {
35+
await expect(runScript("btoa('日本語')")).rejects.toThrow(/Latin1 range/)
36+
})
37+
38+
it('exposes btoaUtf8 that encodes characters outside the Latin1 range', async () => {
39+
const result = await runScript("btoaUtf8('日本語')")
40+
expect(result).toBe(Buffer.from('日本語', 'utf8').toString('base64'))
41+
})
42+
43+
it('exposes atobUtf8 that decodes utf8 base64 back to the original string', async () => {
44+
const encoded = Buffer.from('日本語', 'utf8').toString('base64')
45+
const result = await runScript(`atobUtf8('${encoded}')`)
46+
expect(result).toBe('日本語')
47+
})
48+
49+
it('round-trips a unicode value through btoaUtf8 and atobUtf8', async () => {
50+
const result = await runScript(
51+
"atobUtf8(btoaUtf8('Coût € — 日本語 🚀'))",
52+
)
53+
expect(result).toBe('Coût € — 日本語 🚀')
54+
})
55+
56+
it('decodes a utf8 base64-encoded ServiceNow batch response body', async () => {
57+
const body = Buffer.from(
58+
JSON.stringify({ result: { sys_id: 'Coût-中文-🚀' } }),
59+
'utf8',
60+
).toString('base64')
61+
const result = await runScript(
62+
`JSON.parse(atobUtf8('${body}')).result.sys_id`,
63+
)
64+
expect(result).toBe('Coût-中文-🚀')
65+
})
3766
})

0 commit comments

Comments
 (0)