Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 35 additions & 34 deletions .github/renovate.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"ignorePaths": ["**/node_modules/**"],
"schedule": ["on friday"],
"vulnerabilityAlerts": {
"enabled": true
"enabled": true,
},
"osvVulnerabilityAlerts": true,
"updateInternalDeps": true,
Expand All @@ -13,7 +13,7 @@
"enabled": true,
"schedule": ["* 10 * * 5"], // Every Friday at 10:00 AM
"automerge": true,
"automergeType": "pr"
"automergeType": "pr",
},
"automerge": false,
"automergeType": "pr",
Expand All @@ -24,35 +24,55 @@
// Do not edit peer dependency ranges in package manifests.
{
"matchDepTypes": ["peerDependencies"],
"enabled": false
"enabled": false,
},

// By SemVer
{
"matchUpdateTypes": ["patch"],
"minimumReleaseAge": "2 days"
"minimumReleaseAge": "2 days",
},
{
"matchUpdateTypes": ["minor"],
"minimumReleaseAge": "7 days"
"minimumReleaseAge": "7 days",
},
{
"matchUpdateTypes": ["major"],
"minimumReleaseAge": "14 days"
"minimumReleaseAge": "14 days",
},
// By Package Type (App or Package)
{
// Note: Do not include examples in this group,
// they are supposed to be able to run independently
// and its conventions.
"matchFileNames": ["apps/**"],
"extends": ["config:js-app"]
"extends": ["config:js-app"],
},
{
"matchFileNames": ["packages/**", "tooling/**"],
"extends": ["config:js-lib"]
"extends": ["config:js-lib"],
},
// By Package Manager
{
// Generated from apps/playgrounds/*/package.json arkenvExamples metadata.
// Hand-maintained examples, including StackBlitz, remain Renovate-managed.
"description": "Disable sync-generated example apps",
"matchFileNames": [
// BEGIN GENERATED SYNC EXAMPLES
"examples/basic/**",
"examples/basic-js/**",
"examples/with-bun/**",
"examples/with-bun-react/**",
"examples/with-nextjs/**",
"examples/with-nextjs-strict/**",
"examples/with-nuxt/**",
"examples/with-solid-start/**",
"examples/with-standard-schema/**",
"examples/with-vite-react/**",
// END GENERATED SYNC EXAMPLES
],
"enabled": false,
},
{
// Group pnpm workspace updates (minor/patch) across selected dirs
"groupName": "the pnpm group",
Expand All @@ -64,58 +84,39 @@
"package.json", // Root package.json,
"pnpm-workspace.yaml", // Catalog
"examples/with-zod/**", // Hand-maintained (not sync-generated)
"examples/with-valibot/**" // Hand-maintained (not sync-generated)
"examples/with-valibot/**", // Hand-maintained (not sync-generated)
],
"matchUpdateTypes": ["minor", "patch"],
"matchCurrentVersion": ">=1.0.0",
"automerge": true,
"automergeType": "pr"
"automergeType": "pr",
},
{
"groupName": "the Bun group",
"matchManagers": ["bun"],
"matchFileNames": [
".github/actions/size-limit/package.json", // Bun script
"apps/playgrounds/**/*bun*/**",
"examples/**/*bun*/**"
"examples/**/*bun*/**",
],
"matchUpdateTypes": ["minor", "patch"],
"matchCurrentVersion": ">=1.0.0",
"automerge": true,
"automergeType": "pr"
"automergeType": "pr",
},
{
"groupName": "the GitHub Actions group",
"matchManagers": ["github-actions"],
"matchUpdateTypes": ["minor", "patch"],
"automerge": true,
"automergeType": "pr"
"automergeType": "pr",
},
{
"groupName": "arkenv packages",
"matchPackageNames": ["arkenv"],
"matchPackagePatterns": ["^@arkenv/"],
"automerge": true,
"automergeType": "pr"
"automergeType": "pr",
},
{
// Sync-generated examples only — packageManager comes from catalog + transform.js.
// Hand-maintained examples (with-zod, with-valibot) stay Renovate-updatable via the pnpm group.
"description": "Disable packageManager bumps for sync-generated examples; versions come from the catalog + transform.js",
"matchPackageNames": ["npm", "pnpm", "bun"],
"matchFileNames": [
"examples/basic/**",
"examples/basic-js/**",
"examples/with-nextjs/**",
"examples/with-nextjs-strict/**",
"examples/with-nuxt/**",
"examples/with-solid-start/**",
"examples/with-standard-schema/**",
"examples/with-vite-react/**",
"examples/with-bun/**",
"examples/with-bun-react/**"
],
"enabled": false
}
]
],
}
4 changes: 2 additions & 2 deletions .github/workflows/sync-examples.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,8 @@ jobs:
run: |
git config --global user.email "237618717+arkenv-bot[bot]@users.noreply.github.com"
git config --global user.name "arkenv-bot[bot]"
if [ -n "$(git status --porcelain examples)" ]; then
git add examples
if [ -n "$(git status --porcelain examples .github/renovate.json)" ]; then
git add examples .github/renovate.json
git commit -m "chore(examples): sync generated examples"
git push origin HEAD || {
echo "❌ Failed to push example sync changes. Please re-run the workflow."
Expand Down
11 changes: 10 additions & 1 deletion biome.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@
},
"json": {
"parser": {
"allowComments": true
"allowComments": true,
"allowTrailingCommas": true
}
},
"assist": { "actions": { "source": { "organizeImports": "on" } } },
Expand Down Expand Up @@ -98,6 +99,14 @@
}
},
"overrides": [
{
"includes": [".github/renovate.json"],
"json": {
"formatter": {
"trailingCommas": "all"
}
}
},
{
"includes": ["**/examples/**/*", "**/playgrounds/**/*"],
"linter": {
Expand Down
10 changes: 10 additions & 0 deletions scripts/sync-examples.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { existsSync, readdirSync, readFileSync } from "node:fs";
import { basename, join } from "node:path";
import { argv } from "node:process";
import { PLAYGROUNDS_DIR } from "./sync-lib/constants.js";
import { syncRenovateConfig } from "./sync-lib/renovate.js";
import { syncPlayground } from "./sync-lib/sync.js";
import { parseCatalog } from "./sync-lib/workspace.js";

Expand All @@ -39,6 +40,7 @@ function main() {
.map((d) => join(PLAYGROUNDS_DIR, d.name));

let hasChanges = false;
const generatedExampleNames = [];

for (const playgroundPath of playgroundDirs) {
const pkgPath = join(playgroundPath, "package.json");
Expand All @@ -65,6 +67,7 @@ function main() {
continue;
}

generatedExampleNames.push(exampleConfig.name);
const changes = syncPlayground(
playgroundPath,
exampleConfig,
Expand All @@ -82,6 +85,13 @@ function main() {
}
}

if (syncRenovateConfig(generatedExampleNames, checkOnly)) {
hasChanges = true;
console.log(
"\n ✗ Renovate config is out of sync with playground metadata.",
);
}

if (checkOnly) {
if (hasChanges) {
console.log("\n❌ Examples are out of sync with playgrounds.");
Expand Down
55 changes: 55 additions & 0 deletions scripts/sync-lib/renovate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { readFileSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { ROOT_DIR } from "./constants.js";

const RENOVATE_CONFIG_PATH = join(ROOT_DIR, ".github", "renovate.json");
const START_MARKER = "// BEGIN GENERATED SYNC EXAMPLES";
const END_MARKER = "// END GENERATED SYNC EXAMPLES";

/**
* Emit every generated `matchFileNames` entry with a trailing comma so the
* marker region stays self-contained JSONC whether or not hand-written entries
* follow the END marker before the closing `]`.
*
* @param {string[]} exampleNames
*/
export function generatedMatchFileNames(exampleNames) {
return [
`\t\t\t\t${START_MARKER}`,
...exampleNames.map((name) => `\t\t\t\t"examples/${name}/**",`),
Comment thread
yamcodes marked this conversation as resolved.
`\t\t\t\t${END_MARKER}`,
].join("\n");
}

/**
* Keep Renovate's generated-example exclusion in sync with playground metadata.
*/
export function syncRenovateConfig(exampleNames, checkOnly = false) {
const content = readFileSync(RENOVATE_CONFIG_PATH, "utf8");
const markerStart = content.indexOf(START_MARKER);
const start = content.lastIndexOf("\n", markerStart) + 1;
const end = content.indexOf(END_MARKER);

if (markerStart === -1 || end === -1 || end < start) {
throw new Error(
`Renovate config must contain ${START_MARKER} and ${END_MARKER}`,
);
}

const generated = generatedMatchFileNames([...new Set(exampleNames)].sort());
const current = content.slice(start, end + END_MARKER.length);
const updated =
content.slice(0, start) +
generated +
content.slice(end + END_MARKER.length);

if (checkOnly) {
return current !== generated;
}

if (current !== generated) {
writeFileSync(RENOVATE_CONFIG_PATH, updated);
}

return false;
}
23 changes: 23 additions & 0 deletions scripts/sync-lib/renovate.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { describe, expect, it } from "vitest";
import { generatedMatchFileNames } from "./renovate.js";

describe("generatedMatchFileNames", () => {
it("always trailing-commas generated entries so the region is self-contained", () => {
const block = generatedMatchFileNames(["basic", "with-vite-react"]);
const entries = block
.split("\n")
.map((line) => line.trim())
.filter((line) => line.startsWith('"examples/'));

expect(entries).toEqual([
'"examples/basic/**",',
'"examples/with-vite-react/**",',
]);
});

it("trailing-commas a single generated entry", () => {
const block = generatedMatchFileNames(["basic"]);
expect(block).toContain('"examples/basic/**",');
expect(block).not.toMatch(/"examples\/basic\/\*\*"\n/);
});
});
Loading