feat: bundle git change sets with git: source tokens - #9
Merged
Conversation
A pattern may now name a set of changed files instead of a glob: `git:staged`, `git:unstaged`, `git:untracked`, `git:dirty`, or `git:<rev>`. CLI flags `--staged`, `--dirty` and `--since <rev>` build the same bundle ad hoc, with no config file at all. Globs cannot express "what I'm currently changing", which is the most common reason to hand code to an LLM. Resolving the set lazily inside the pattern array keeps the config plain data — it still works in package.json, composes with `!` exclusions, and concrete paths never get re-matched as globs (a staged `src/[id].tsx` would silently match nothing). See ADR 001. Also raises the Node floor to ^22.18 so cosmiconfig 10 can load `srcpack.config.ts` via Node's built-in type stripping instead of a bundled TypeScript parser, cutting the published CLI from 10.8 MB to 2.4 MB. See ADR 002. Fixes found while reviewing the above: - `outDir: "."` deleted the entire project, then reported success - reruns bundled their own output, nesting it deeper each time - `srcpack web` emptied outDir, destroying bundles it could not rebuild - symlinks were followed, so a tracked link could pull in a file from outside the project and upload it - a quote in a bundle filename could make the Drive query resolve to an unrelated file, which the upload then overwrote - OAuth refresh tokens were written world-readable (0644) - `srcpack init` generated invalid TypeScript for hyphenated bundle names, and dropped backslashes from patterns - failed uploads exited 0 - `--since init` ran the init wizard instead of diffing against `init` Docs now deploy from GitHub Actions rather than the gh-pages branch.
Node derives a `.ts` file's module format from the nearest package.json
`type`, so in a CommonJS project — the `npm init` default — the generated
config's `import { defineConfig }` line is a syntax error. cosmiconfig 9
compiled the file with its bundled TypeScript and hid this; cosmiconfig 10
type-strips it, so 0.2.0 would have shipped broken for those projects.
`srcpack.config.mts` joins searchPlaces and `init` writes it whenever the
project is not `"type": "module"`. `.mts` is unconditionally ESM and loads
either way; `.ts` stays the default for ESM projects since that is the name
the docs use.
The suite runs on Bun, which loads either extension regardless of package
type and so cannot see this failure. CI now installs the packed tarball into
a CommonJS project and runs the CLI under Node.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Bundle patterns can now name git change sets, not just globs. Along the way: the runtime floor moves to Node 22.18, docs publishing moves off the
gh-pagesbranch, and nine pre-existing bugs are fixed — four of them destructive.git:source tokensgit:stagedHEADgit:unstagedgit:untrackedgit:dirtygit:<rev><rev>, via merge baseWhy a token, not a helper. The alternative — an exported
$stagedspread into the array — resolves eagerly, which runs git at config import time even for unrelated commands, rules outpackage.jsonconfig, and drops literal paths into an array that is later matched as globs, so a stagedsrc/[id].tsxwould silently match nothing. Tokens resolve lazily insideresolvePatterns(): no new config shape, no new exports, and!exclusions compose for free. → ADR 001Decisions a reviewer might otherwise question:
.gitignoredoesn't apply — anything git reports is already tracked or filtered.upload.excludecan't name a bundle that exists for one run.Node 22.18 floor
cosmiconfig 10 drops its bundled
typescriptfor Node's built-in type stripping.srcpack.config.tsis the primary config format, so runtime TS loading isn't optional — the floor follows cosmiconfig's own range rather than a looser invented one. A>=20floor would install cleanly, then fail on the first.tsconfig with a parse error instead of an engine warning.Published CLI: 10.8 MB → 2.4 MB. Node 18 and 20 are both EOL. → ADR 002
Two consequences of type stripping:
enumandnamespaceare out..tsfile's module format from the nearestpackage.json, so a.tsconfig'simportline is a syntax error in a CommonJS project — which cosmiconfig 9's bundled compiler used to hide.srcpack.config.mtsjoinssearchPlaces, andinitwrites it when the project isn't"type": "module".The suite runs on Bun, which loads either extension regardless of package type and so can't see that second one. CI now installs the packed tarball into a CommonJS project and runs the CLI under Node.
Fixes
Destructive, each reproduced before fixing:
outDir: "."node_modules— then printedBundled: 1 bundleand exited0. Now refused outright.srcpack weboutDir, destroyingapi.txt— which that run could not rebuild. Emptying now happens only on a full run.notes.txt -> ~/.ssh/id_rsawas read into the bundle and uploaded under an innocuous name. Nowlstat, so symlinks are skipped.Security and correctness (5 more)
'in a bundle filename madefindFileresolve to a different file, which the upload then overwrote. Name andfolderIdare escaped now.~/.config/srcpack/credentials.jsonwas written0644— a refresh token readable by any account on the machine. Now0600, with achmodso files from older versions are repaired.initcodegenmy-app: "…"), and backslashes were silently dropped (src\**\*→src***). All values go throughJSON.stringify.0— CI saw green after a failed upload.--since initinitbranch. Subcommands now match in first position only.Every fix has a regression test, each mutation-tested to confirm the test fails without it.
Docs publishing
Deploys via
actions/deploy-pagesonrelease: published, replacing thegh-pagesbranch and devDependency.docs:buildnow runs in CI, so broken links fail at PR time. ADRs became a linked section of the site instead of orphan pages.Three more bugs fixed here: a live 404 on
/srcpack/favicon.ico(referenced but never present), a shallow checkout that collapsed every page's "Last updated" to the release commit, and prereleases republishing the site.Important
Merge before cutting 0.2.0 —
releaseandworkflow_dispatchonly run workflow files from the default branch, sodocs.ymlmust be onmainor the deploy silently no-ops.The Pages source is still
legacy/gh-pagesand must be switched by hand. The site is unpublished until the first Actions deploy lands (~1–2 min):gh api -X PUT repos/kriasoft/srcpack/pages -f build_type=workflow gh workflow run docs.yml # verify kriasoft.com/srcpack/, then: git push origin --delete gh-pagesThis can't cost the custom domain: the repo's Pages
cnameisnulland/srcpack/CNAME404s —kriasoft.comis inherited from the org site, not set by this repo.