diff --git a/.gitignore b/.gitignore index d7b61c3ae..79b524fbd 100644 --- a/.gitignore +++ b/.gitignore @@ -19,4 +19,5 @@ CLAUDE.local.md coverage example/public/ out -example/tsconfig.e2e.tsbuildinfo \ No newline at end of file +example/tsconfig.e2e.tsbuildinfo +.schema-dumps/ diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index d8fb880af..707e058d1 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -75,6 +75,7 @@ The example app will be available at `http://localhost:3001`. - `npm test` - Run tests - `npm run type-check` - Run TypeScript type checking - `npm run format` - Format code with Prettier +- `npm run dump:schema -- --country=XXX [--form=contract_details] [--version=N|latest]` - Dump a country schema from the sandbox gateway to `.schema-dumps/` - `npm run size` - Analyze bundle size - `npm run size:check` - Check if bundle size is within limits - `npm run size:badge` - Generate badge and history data diff --git a/example/package-lock.json b/example/package-lock.json index d0cff39f3..ed1359d93 100644 --- a/example/package-lock.json +++ b/example/package-lock.json @@ -24,7 +24,7 @@ }, "devDependencies": { "@playwright/test": "1.63.0", - "@remoteoss/remote-json-schema-form-kit": "1.1.2", + "@remoteoss/remote-json-schema-form-kit": "1.1.4", "@types/node": "24.12.4", "@types/react-dom": "19.3.0", "@types/react-syntax-highlighter": "15.5.13", @@ -37,7 +37,7 @@ }, "..": { "name": "@remoteoss/remote-flows", - "version": "1.62.0", + "version": "1.63.0", "dependencies": { "@hookform/resolvers": "5.4.0", "@radix-ui/react-checkbox": "1.3.4", @@ -48,8 +48,8 @@ "@radix-ui/react-scroll-area": "1.2.11", "@radix-ui/react-select": "2.3.0", "@radix-ui/react-tabs": "1.1.14", - "@remoteoss/json-schema-form": "1.2.18", - "@remoteoss/remote-json-schema-form-kit": "1.1.2", + "@remoteoss/json-schema-form": "^1.2.18", + "@remoteoss/remote-json-schema-form-kit": "1.1.4", "@tailwindcss/cli": "4.2.4", "@tailwindcss/postcss": "4.2.4", "@tanstack/react-query": "5.104.1", @@ -94,13 +94,13 @@ "@types/lodash.omitby": "4.6.9", "@types/node": "24.12.4", "@types/react": "19.3.0", - "@vitest/coverage-istanbul": "5.0.1", - "chalk": "6.0.0", + "@vitest/coverage-istanbul": "5.0.3", + "chalk": "6.0.1", "dotenv": "18.0.5", - "filesize": "11.0.24", + "filesize": "11.0.25", "glob": "13.0.6", "gzip-size": "7.0.0", - "jsdom": "30.0.1", + "jsdom": "30.1.2", "lodash": "4.18.1", "msw": "2.15.0", "oxfmt": "0.68.0", @@ -112,7 +112,7 @@ "tsx": "4.23.15", "typescript": "7.0.2", "unrun": "0.3.1", - "vitest": "5.0.1" + "vitest": "5.0.3" }, "engines": { "node": ">=20" @@ -589,9 +589,9 @@ } }, "node_modules/@remoteoss/json-schema-form": { - "version": "1.2.18", - "resolved": "https://registry.npmjs.org/@remoteoss/json-schema-form/-/json-schema-form-1.2.18.tgz", - "integrity": "sha512-Tp8B+krO0VU5nWgEDqBQcS2R2uPpWi2tBX4n3kZOpVhH4kIbogTs/c2EZkvCRrovyqqhKKFDGZu1PB8RThBTtw==", + "version": "1.2.19", + "resolved": "https://registry.npmjs.org/@remoteoss/json-schema-form/-/json-schema-form-1.2.19.tgz", + "integrity": "sha512-j4TnAq8AO2FUGbrhlaigq8Z7AvWJZ5DcTUCrn5qCuM8HXeqSXjWtVTxE8QQc4G9O5JX6m/rGG08CG3RlluZ/jQ==", "dev": true, "license": "MIT", "dependencies": { @@ -623,13 +623,13 @@ "link": true }, "node_modules/@remoteoss/remote-json-schema-form-kit": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@remoteoss/remote-json-schema-form-kit/-/remote-json-schema-form-kit-1.1.2.tgz", - "integrity": "sha512-B5+TmP/RXo6piD0s7oEkK2qJAomMJh1b0HDNEQDx/F4OeC1e9K0UZyKYhU246oY8EZ3e0A9AGTm/O8fCiaG1cw==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@remoteoss/remote-json-schema-form-kit/-/remote-json-schema-form-kit-1.1.4.tgz", + "integrity": "sha512-0FyjIvONWp+ZC8+4aCLzdAgm3KQk0PkC5Dk6z5oDrMwQlfE80NSzEFm40pxFbY8ms8kzNnQEigXZz023K1P1Nw==", "dev": true, "license": "MIT", "dependencies": { - "@remoteoss/json-schema-form": "^1.2.18", + "@remoteoss/json-schema-form": "^1.2.19", "@remoteoss/json-schema-form-v0-deprecated": "npm:@remoteoss/json-schema-form@0.12.5-beta.0", "date-fns": "^4.1.0" }, diff --git a/example/package.json b/example/package.json index fa07df630..5659375f3 100644 --- a/example/package.json +++ b/example/package.json @@ -31,7 +31,7 @@ }, "devDependencies": { "@playwright/test": "1.63.0", - "@remoteoss/remote-json-schema-form-kit": "1.1.2", + "@remoteoss/remote-json-schema-form-kit": "1.1.4", "@types/node": "24.12.4", "@types/react-dom": "19.3.0", "@types/react-syntax-highlighter": "15.5.13", diff --git a/package-lock.json b/package-lock.json index c1b1fa743..a3bd187c6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,8 +17,8 @@ "@radix-ui/react-scroll-area": "1.2.11", "@radix-ui/react-select": "2.3.0", "@radix-ui/react-tabs": "1.1.14", - "@remoteoss/json-schema-form": "^1.2.18", - "@remoteoss/remote-json-schema-form-kit": "1.1.2", + "@remoteoss/json-schema-form": "1.2.19", + "@remoteoss/remote-json-schema-form-kit": "1.1.4", "@tailwindcss/cli": "4.2.4", "@tailwindcss/postcss": "4.2.4", "@tanstack/react-query": "5.104.1", @@ -4103,9 +4103,9 @@ "license": "MIT" }, "node_modules/@remoteoss/json-schema-form": { - "version": "1.2.18", - "resolved": "https://registry.npmjs.org/@remoteoss/json-schema-form/-/json-schema-form-1.2.18.tgz", - "integrity": "sha512-Tp8B+krO0VU5nWgEDqBQcS2R2uPpWi2tBX4n3kZOpVhH4kIbogTs/c2EZkvCRrovyqqhKKFDGZu1PB8RThBTtw==", + "version": "1.2.19", + "resolved": "https://registry.npmjs.org/@remoteoss/json-schema-form/-/json-schema-form-1.2.19.tgz", + "integrity": "sha512-j4TnAq8AO2FUGbrhlaigq8Z7AvWJZ5DcTUCrn5qCuM8HXeqSXjWtVTxE8QQc4G9O5JX6m/rGG08CG3RlluZ/jQ==", "license": "MIT", "dependencies": { "json-logic-js": "^2.0.5" @@ -4147,12 +4147,12 @@ } }, "node_modules/@remoteoss/remote-json-schema-form-kit": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@remoteoss/remote-json-schema-form-kit/-/remote-json-schema-form-kit-1.1.2.tgz", - "integrity": "sha512-B5+TmP/RXo6piD0s7oEkK2qJAomMJh1b0HDNEQDx/F4OeC1e9K0UZyKYhU246oY8EZ3e0A9AGTm/O8fCiaG1cw==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@remoteoss/remote-json-schema-form-kit/-/remote-json-schema-form-kit-1.1.4.tgz", + "integrity": "sha512-0FyjIvONWp+ZC8+4aCLzdAgm3KQk0PkC5Dk6z5oDrMwQlfE80NSzEFm40pxFbY8ms8kzNnQEigXZz023K1P1Nw==", "license": "MIT", "dependencies": { - "@remoteoss/json-schema-form": "^1.2.18", + "@remoteoss/json-schema-form": "^1.2.19", "@remoteoss/json-schema-form-v0-deprecated": "npm:@remoteoss/json-schema-form@0.12.5-beta.0", "date-fns": "^4.1.0" }, diff --git a/package.json b/package.json index 3e6cbe88d..c2d011405 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "release": "tsx scripts/release.ts", "release:fix": "tsx scripts/release-hotfix.ts", "schema-canary": "tsx scripts/schema-canary.ts", + "dump:schema": "tsx scripts/dump-schema.ts", "seed:onboarding": "tsx scripts/seed-onboarding.ts", "size": "tsx scripts/analyze-bundle-size.ts", "size:check": "tsx scripts/check-size-limits.ts", @@ -104,8 +105,8 @@ "@radix-ui/react-scroll-area": "1.2.11", "@radix-ui/react-select": "2.3.0", "@radix-ui/react-tabs": "1.1.14", - "@remoteoss/json-schema-form": "^1.2.18", - "@remoteoss/remote-json-schema-form-kit": "1.1.2", + "@remoteoss/json-schema-form": "1.2.19", + "@remoteoss/remote-json-schema-form-kit": "1.1.4", "@tailwindcss/cli": "4.2.4", "@tailwindcss/postcss": "4.2.4", "@tanstack/react-query": "5.104.1", diff --git a/scripts/check-single-schema-form.ts b/scripts/check-single-schema-form.ts index c78cbc505..6366da92f 100644 --- a/scripts/check-single-schema-form.ts +++ b/scripts/check-single-schema-form.ts @@ -1,9 +1,11 @@ #!/usr/bin/env tsx import { execSync } from 'child_process'; +import { readFileSync } from 'fs'; import chalk from 'chalk'; const PACKAGE_NAME = '@remoteoss/json-schema-form'; +const KIT_NAME = '@remoteoss/remote-json-schema-form-kit'; type TreeNode = { version?: string; @@ -30,6 +32,35 @@ function readTree(): string { } } +type PackageJson = { dependencies?: Record }; + +function readPackageJson(path: string): PackageJson { + return JSON.parse(readFileSync(path, 'utf-8')) as PackageJson; +} + +function checkPinMatchesKit(): boolean { + const declared = readPackageJson('package.json').dependencies?.[PACKAGE_NAME]; + const required = readPackageJson(`node_modules/${KIT_NAME}/package.json`) + .dependencies?.[PACKAGE_NAME]; + const kitFloor = required?.replace(/^[\^~>=\s]+/, ''); + + if (!declared || !kitFloor || declared === kitFloor) { + return true; + } + + console.error( + chalk.red.bold( + `❌ ${PACKAGE_NAME} is declared as ${declared}; it must be pinned to the floor of what ${KIT_NAME} requires (${required})`, + ), + ); + console.error( + chalk.red( + ` Set "${PACKAGE_NAME}": "${kitFloor}" in package.json, then run npm install.`, + ), + ); + return false; +} + function main(): void { const output = readTree(); const versions = new Set(); @@ -49,6 +80,10 @@ function main(): void { process.exit(1); } + if (!checkPinMatchesKit()) { + process.exit(1); + } + console.log( chalk.green(`✅ ${PACKAGE_NAME} resolves to ${[...versions][0]}`), ); diff --git a/scripts/dump-schema.ts b/scripts/dump-schema.ts new file mode 100644 index 000000000..70c11e654 --- /dev/null +++ b/scripts/dump-schema.ts @@ -0,0 +1,83 @@ +#!/usr/bin/env tsx +import { mkdirSync, writeFileSync } from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import dotenv from 'dotenv'; +import { getV1CountriesCountryCodeForm } from '@/src/client'; +import { createSandboxClient } from './schema-canary/auth'; +import { + archiveEmployment, + seedEmploymentForCountry, +} from './schema-canary/seed-employment'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +dotenv.config({ path: path.resolve(__dirname, '..', '.env.sandbox') }); + +const args: Record = {}; +for (const raw of process.argv.slice(2)) { + const match = raw.match(/^--([^=]+)=(.*)$/); + if (match) { + args[match[1]] = match[2]; + } +} +const country = args.country?.toUpperCase(); +const form = args.form ?? 'contract_details'; +const version: number | 'latest' = + args.version === undefined || args.version === 'latest' + ? 'latest' + : Number(args.version); +if (version !== 'latest' && !Number.isInteger(version)) { + throw new Error( + `--version must be an integer or 'latest', got ${args.version}`, + ); +} +const outDir = args.out ?? path.resolve(__dirname, '..', '.schema-dumps'); +if (!country) { + throw new Error('--country=XXX is required'); +} + +const { VITE_CLIENT_ID, VITE_CLIENT_SECRET, VITE_REFRESH_TOKEN } = process.env; +if (!VITE_CLIENT_ID || !VITE_CLIENT_SECRET || !VITE_REFRESH_TOKEN) { + throw new Error( + 'Missing VITE_CLIENT_ID/SECRET/REFRESH_TOKEN in .env.sandbox', + ); +} +const client = createSandboxClient( + VITE_CLIENT_ID, + VITE_CLIENT_SECRET, + VITE_REFRESH_TOKEN, +); + +let employmentId: string | undefined; +try { + if (form === 'contract_details') { + employmentId = await seedEmploymentForCountry(client, country); + } + const response = await getV1CountriesCountryCodeForm({ + client, + headers: { Authorization: '' }, + path: { country_code: country, form }, + query: { + skip_benefits: true, + ...(employmentId ? { employment_id: employmentId } : {}), + json_schema_version: version, + }, + }); + if (response.error || !response.data) { + throw new Error(`Fetch failed: ${JSON.stringify(response.error)}`); + } + mkdirSync(outDir, { recursive: true }); + const file = path.join(outDir, `${country}.${form}.v${version}.json`); + writeFileSync(file, JSON.stringify(response.data.data, null, 2)); + console.log(`Wrote ${file}`); +} finally { + if (employmentId) { + try { + await archiveEmployment(client, employmentId); + } catch (error) { + console.warn( + `Failed to archive employment ${employmentId}: ${error instanceof Error ? error.message : String(error)}`, + ); + } + } +}