Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
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
81 changes: 81 additions & 0 deletions .github/workflows/factory-sync.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
name: Factory sync

on:
schedule:
- cron: "17 3 * * *"
workflow_dispatch:

permissions: {}

concurrency:
group: factory-sync

# This is the fork's own workflow. It merges upstream's newest nightly tag into
# main through a pull request; factory/scripts/sync.sh says how. Upstream's code
# (install scripts, the route generator, the checks) runs only in `prepare`,
# which has no write token. `publish` holds the token and runs git and gh only.
jobs:
prepare:
name: Merge and check
runs-on: ubuntu-latest
timeout-minutes: 60
permissions:
contents: read
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0
persist-credentials: false

- name: Setup Vite+
uses: voidzero-dev/setup-vp@v1
with:
node-version-file: package.json
# No cache: this job runs upstream's install scripts, and a cache it
# saves would be restored by the fork's other workflows.
cache: false

- name: Merge and check
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
factory/scripts/sync.sh prepare "$RUNNER_TEMP/sync"

- name: Upload the prepared sync
uses: actions/upload-artifact@v7
with:
name: sync
path: ${{ runner.temp }}/sync
retention-days: 7
if-no-files-found: error

publish:
name: Push, land or file
needs: prepare
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: write
pull-requests: write
issues: write
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0
# git push uses gh's credential helper instead.
persist-credentials: false

- name: Download the prepared sync
uses: actions/download-artifact@v8
with:
name: sync
path: ${{ runner.temp }}/sync

- name: Publish
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh auth setup-git
factory/scripts/sync.sh publish "$RUNNER_TEMP/sync"
83 changes: 83 additions & 0 deletions apps/server/src/factory/regenerate-routes.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// @effect-diagnostics nodeBuiltinImport:off
import * as NodeChildProcess from "node:child_process";
import * as NodeFS from "node:fs";
import * as NodeOS from "node:os";
import * as NodePath from "node:path";

import { afterEach, beforeEach, describe, expect, it } from "vite-plus/test";

const repoRoot = NodePath.resolve(import.meta.dirname, "../../../..");
const PLUGIN_CALL = "tanstackRouter({ autoCodeSplitting: true })";

let dir: string;

const write = (file: string, content: string, mode = 0o644) => {
const full = NodePath.join(dir, file);
NodeFS.mkdirSync(NodePath.dirname(full), { recursive: true });
NodeFS.writeFileSync(full, content, { mode });
};

// A copy of the script in a tree with one route, linked to the web app's
// installed router plugin, so the real generator runs.
const makeTree = (pluginCall: string) => {
write(
"factory/scripts/regenerate-routes.sh",
NodeFS.readFileSync(NodePath.join(repoRoot, "factory/scripts/regenerate-routes.sh"), "utf8"),
0o755,
);
write(
"apps/web/package.json",
NodeFS.readFileSync(NodePath.join(repoRoot, "apps/web/package.json"), "utf8"),
);
NodeFS.symlinkSync(
NodePath.join(repoRoot, "apps/web/node_modules"),
NodePath.join(dir, "apps/web/node_modules"),
);
write("apps/web/vite.config.ts", `export default { plugins: [${pluginCall}] };\n`);
write(
"apps/web/src/routes/__root.tsx",
'import { createRootRoute } from "@tanstack/react-router";\n\nexport const Route = createRootRoute();\n',
);
write(
"apps/web/src/routes/pair.tsx",
'import { createFileRoute } from "@tanstack/react-router";\n\nexport const Route = createFileRoute("/pair")({});\n',
);
};

const run = () =>
NodeChildProcess.spawnSync("bash", ["factory/scripts/regenerate-routes.sh"], {
cwd: dir,
encoding: "utf8",
});

const routeTree = () => NodePath.join(dir, "apps/web/src/routeTree.gen.ts");

describe("regenerate-routes.sh", () => {
beforeEach(() => {
dir = NodeFS.mkdtempSync(NodePath.join(NodeOS.tmpdir(), "regenerate-routes-"));
});

afterEach(() => {
NodeFS.rmSync(dir, { recursive: true, force: true });
});

it("writes the route tree with the web app's router generator", () => {
makeTree(PLUGIN_CALL);
const { status, stdout, stderr } = run();
expect(stderr).toBe("");
expect(status).toBe(0);
expect(stdout).toContain("regenerated");
const tree = NodeFS.readFileSync(routeTree(), "utf8");
expect(tree).toContain("./routes/pair");
expect(tree).toContain("'/pair'");
});

it("refuses when vite.config.ts passes the router plugin other options", () => {
makeTree("tanstackRouter({ autoCodeSplitting: false })");
const { status, stderr } = run();
expect(status).toBe(1);
expect(stderr).toContain("tanstackRouter({ autoCodeSplitting: false })");
expect(stderr).toContain("make them match");
expect(NodeFS.existsSync(routeTree())).toBe(false);
});
});
Loading