Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e9d04c7f8d | ||
|
|
ece1dffb39 | ||
|
|
71c7fe09cd | ||
|
|
45f3830794 | ||
|
|
b8d0a69d4d | ||
|
|
c5f8ee6005 | ||
|
|
9983f0b8e9 | ||
|
|
0d339e2de0 | ||
|
|
7dcb0d1b3a | ||
|
|
f8658265ae | ||
|
|
c42899c89e | ||
|
|
00fb6ef297 | ||
|
|
dc475afcd1 | ||
|
|
96d2d19046 | ||
|
|
976fc8c1a7 | ||
|
|
30029e5954 | ||
|
|
9edb042e69 | ||
|
|
198b0bb1b0 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,4 +1,5 @@
|
|||||||
.pre-commit-config.yaml
|
.pre-commit-config.yaml
|
||||||
|
lefthook.yml
|
||||||
.direnv
|
.direnv
|
||||||
result
|
result
|
||||||
template/flake.lock
|
template/flake.lock
|
||||||
1
.tools/bun/bin/moon
Symbolic link
1
.tools/bun/bin/moon
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../install/global/node_modules/@moonrepo/cli/moon.js
|
||||||
1
.tools/bun/bin/moonx
Symbolic link
1
.tools/bun/bin/moonx
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../install/global/node_modules/@moonrepo/cli/moonx.js
|
||||||
28
.tools/bun/install/global/bun.lock
Normal file
28
.tools/bun/install/global/bun.lock
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"lockfileVersion": 1,
|
||||||
|
"configVersion": 1,
|
||||||
|
"workspaces": {
|
||||||
|
"": {
|
||||||
|
"dependencies": {
|
||||||
|
"@moonrepo/cli": "^2.0.4",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"packages": {
|
||||||
|
"@moonrepo/cli": ["@moonrepo/cli@2.0.4", "", { "dependencies": { "detect-libc": "^2.1.2" }, "optionalDependencies": { "@moonrepo/core-linux-arm64-gnu": "2.0.4", "@moonrepo/core-linux-arm64-musl": "2.0.4", "@moonrepo/core-linux-x64-gnu": "2.0.4", "@moonrepo/core-linux-x64-musl": "2.0.4", "@moonrepo/core-macos-arm64": "2.0.4", "@moonrepo/core-windows-x64-msvc": "2.0.4" }, "bin": { "moon": "moon.js", "moonx": "moonx.js" } }, "sha512-cP62Fa7hzToEi0I2i3Gx7zhPPdimVCeW8WqbSlE5y6fhjH38g1N77vZg4A6rcauDEUl/cpA5+vWQyxm4KCsqUA=="],
|
||||||
|
|
||||||
|
"@moonrepo/core-linux-arm64-gnu": ["@moonrepo/core-linux-arm64-gnu@2.0.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-pvMgTfYUN8ARvhOgVumR1j1XQY0V+59qz+Bi9BBmgOJsaB/QosGGWkCfdV2dChpzfE9AELnjIBgrazUGcOX9KA=="],
|
||||||
|
|
||||||
|
"@moonrepo/core-linux-arm64-musl": ["@moonrepo/core-linux-arm64-musl@2.0.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-lF+KuN8ymTR+kynI7OU+CN//V6hSGL9eq8+7VUVFY3cRobyzjEulB7xkPgVYH2C81E8TUK2tp1R79Y8nOgEHaA=="],
|
||||||
|
|
||||||
|
"@moonrepo/core-linux-x64-gnu": ["@moonrepo/core-linux-x64-gnu@2.0.4", "", { "os": "linux", "cpu": "x64" }, "sha512-0auEy/jyMm5vjIZy/dLdrIoOJ0hnoxcEk+veBGwZatS65dSKEvXdUYrYSGWTRvykbsXDBTAaXFUtj1enGzIXmg=="],
|
||||||
|
|
||||||
|
"@moonrepo/core-linux-x64-musl": ["@moonrepo/core-linux-x64-musl@2.0.4", "", { "os": "linux", "cpu": "x64" }, "sha512-h7b7uw8GdhTyZg2R7rHA04mqBIvYvcHtgtVBUXwwZQlh6jprFFtxuvehAdK32PN8sxygwRa+AdTfgq3vZHXFmw=="],
|
||||||
|
|
||||||
|
"@moonrepo/core-macos-arm64": ["@moonrepo/core-macos-arm64@2.0.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-gz2FO0xRHUOXQjzBAz97S4pShL0YbTeNWLdCuRyjX8SNU1l82TX+l20e6OKfyt6jThWvCZeSiolx7W02xDT+iA=="],
|
||||||
|
|
||||||
|
"@moonrepo/core-windows-x64-msvc": ["@moonrepo/core-windows-x64-msvc@2.0.4", "", { "os": "win32", "cpu": "x64" }, "sha512-LjwpvjWTeusQjpqLNK7N7tVr/IQSd0W8v0L7fUIKBXNGmuONhDCHwgDqSs3yVzwcQMluiHM2qwu9YSKIEVfTIw=="],
|
||||||
|
|
||||||
|
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
|
||||||
|
}
|
||||||
|
}
|
||||||
18
.tools/bun/install/global/node_modules/@moonrepo/cli/LICENSE
generated
vendored
Normal file
18
.tools/bun/install/global/node_modules/@moonrepo/cli/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2021 moonrepo, Inc., Miles Johnson
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
||||||
|
associated documentation files (the "Software"), to deal in the Software without restriction,
|
||||||
|
including without limitation the rights to use, copy, modify, merge, publish, distribute,
|
||||||
|
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial
|
||||||
|
portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
|
||||||
|
NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
|
||||||
|
OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
104
.tools/bun/install/global/node_modules/@moonrepo/cli/README.md
generated
vendored
Normal file
104
.tools/bun/install/global/node_modules/@moonrepo/cli/README.md
generated
vendored
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
# @moonrepo/cli
|
||||||
|
|
||||||
|
The official CLI for [moon](https://moonrepo.dev), a build system and repo management tool for the
|
||||||
|
web ecosystem, written in Rust! Supports JavaScript, TypeScript, Bash, Rust, Go, and much more!
|
||||||
|
|
||||||
|
- [Documentation](https://moonrepo.dev/docs)
|
||||||
|
- [Getting started](https://moonrepo.dev/docs/install)
|
||||||
|
- [Feature comparison](https://moonrepo.dev/docs/comparison)
|
||||||
|
- [FAQ](https://moonrepo.dev/docs/faq)
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
moon can be installed with bash:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
curl -fsSL https://moonrepo.dev/install/moon.sh | bash
|
||||||
|
```
|
||||||
|
|
||||||
|
Or with npm, pnpm, or yarn.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
yarn add --dev @moonrepo/cli
|
||||||
|
```
|
||||||
|
|
||||||
|
Once installed, initialize moon in your repository.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
moon init
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Once [projects](https://moonrepo.dev/docs/create-project) and
|
||||||
|
[tasks](https://moonrepo.dev/docs/create-task) have been configured, tasks can be ran with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run `lint` in project `app`
|
||||||
|
moon run app:lint
|
||||||
|
|
||||||
|
# Run `lint` in all projects
|
||||||
|
moon run :lint
|
||||||
|
```
|
||||||
|
|
||||||
|
## Why use moon?
|
||||||
|
|
||||||
|
Working in the JavaScript ecosystem can be very involved, especially when it comes to managing a
|
||||||
|
repository effectively. Which package manager to use? Which Node.js version to use? How to import
|
||||||
|
node modules? How to build packages? So on and so forth. moon aims to streamline this entire process
|
||||||
|
and provide a first-class developer experience.
|
||||||
|
|
||||||
|
- **Increased productivity** - With [Rust](https://www.rust-lang.org/) as our foundation, we can
|
||||||
|
ensure robust speeds, high performance, and low memory usage. Instead of long builds blocking you,
|
||||||
|
focus on your work.
|
||||||
|
- **Exceptional developer experience** - As veterans of the JavaScript ecosystem, we're well aware
|
||||||
|
of the pain points and frustrations. Our goal is to mitigate and overcome these obstacles.
|
||||||
|
- **Incremental adoption** - At its core, moon has been designed to be adopted incrementally and is
|
||||||
|
_not_ an "all at once adoption". Migrate project-by-project, or task-by-task, it's up to you!
|
||||||
|
- **Reduced scripts confusion** - `package.json` scripts can become unwieldy, very quickly. No more
|
||||||
|
duplicating the same script into every package, or reverse-engineering which root scripts to use.
|
||||||
|
With moon, all you need to know is the project name, and a task name.
|
||||||
|
- **Ensure correct versions** - Whether it's Node.js or npm, ensure the same version of each tool is
|
||||||
|
the same across _every_ developer's environment. No more wasted hours of debugging.
|
||||||
|
- **Automation built-in** - When applicable, moon will automatically install `node_modules`, or sync
|
||||||
|
package dependencies, or even sync TypeScript project references.
|
||||||
|
- And of course, the amazing list of features below!
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
> Not all features are currently supported, view the documentation for an accurate list!
|
||||||
|
|
||||||
|
#### Management
|
||||||
|
|
||||||
|
- **Smart hashing** - Collects inputs from multiple sources to ensure builds are deterministic and
|
||||||
|
reproducible.
|
||||||
|
- **Remote caching** - Persists builds, hashes, and caches between teammates and CI/CD environments.
|
||||||
|
- **Integrated toolchain** - Automatically downloads and installs explicit versions of Node.js and
|
||||||
|
other tools for consistency across the entire workspace or per project.
|
||||||
|
- **Multi-platform** - Runs on common development platforms: Linux, macOS, and Windows.
|
||||||
|
|
||||||
|
#### Organization
|
||||||
|
|
||||||
|
- **Project graph** - Generates a project graph for dependency and dependent relationships.
|
||||||
|
- **Code generation** - Easily scaffold new applications, libraries, tooling, and more!
|
||||||
|
- **Dependency workspaces** - Works alongside package manager workspaces so that projects have
|
||||||
|
distinct dependency trees.
|
||||||
|
- **Code ownership** - Declare owners, maintainers, support channels, and more. Generate CODEOWNERS.
|
||||||
|
|
||||||
|
#### Orchestration
|
||||||
|
|
||||||
|
- **Dependency graph** - Generates a dependency graph to increase performance and reduce workloads.
|
||||||
|
- **Action pipeline** - Executes actions in parallel and in order using a thread pool and our
|
||||||
|
dependency graph.
|
||||||
|
- **Action distribution** - Distributes actions across multiple machines to increase throughput.
|
||||||
|
- **Incremental builds** - With our smart hashing, only rebuild projects that have been changed
|
||||||
|
since the last build.
|
||||||
|
|
||||||
|
#### Notification
|
||||||
|
|
||||||
|
- **Flakiness detection** - Reduce flaky builds with automatic retries and passthrough settings.
|
||||||
|
- **Webhook events** - Receive a webhook for every event in the pipeline. Useful for metrics
|
||||||
|
gathering and insights.
|
||||||
|
- **Terminal notifications** - Receives notifications in your chosen terminal when builds are
|
||||||
|
successful... or are not.
|
||||||
|
- **Git hooks** - Manage Git hooks to enforce workflows and requirements for contributors.
|
||||||
15
.tools/bun/install/global/node_modules/@moonrepo/cli/moon.js
generated
vendored
Executable file
15
.tools/bun/install/global/node_modules/@moonrepo/cli/moon.js
generated
vendored
Executable file
@@ -0,0 +1,15 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
const cp = require('child_process');
|
||||||
|
const { findMoonExe } = require('./utils');
|
||||||
|
|
||||||
|
const result = cp.spawnSync(findMoonExe(), process.argv.slice(2), {
|
||||||
|
shell: false,
|
||||||
|
stdio: 'inherit',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.error) {
|
||||||
|
throw result.error;
|
||||||
|
}
|
||||||
|
|
||||||
|
process.exitCode = result.status;
|
||||||
15
.tools/bun/install/global/node_modules/@moonrepo/cli/moonx.js
generated
vendored
Executable file
15
.tools/bun/install/global/node_modules/@moonrepo/cli/moonx.js
generated
vendored
Executable file
@@ -0,0 +1,15 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
const cp = require('child_process');
|
||||||
|
const { findMoonxExe } = require('./utils');
|
||||||
|
|
||||||
|
const result = cp.spawnSync(findMoonxExe(), process.argv.slice(2), {
|
||||||
|
shell: false,
|
||||||
|
stdio: 'inherit',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.error) {
|
||||||
|
throw result.error;
|
||||||
|
}
|
||||||
|
|
||||||
|
process.exitCode = result.status;
|
||||||
39
.tools/bun/install/global/node_modules/@moonrepo/cli/package.json
generated
vendored
Normal file
39
.tools/bun/install/global/node_modules/@moonrepo/cli/package.json
generated
vendored
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"name": "@moonrepo/cli",
|
||||||
|
"version": "2.0.4",
|
||||||
|
"type": "commonjs",
|
||||||
|
"description": "moon command line and core system.",
|
||||||
|
"keywords": [
|
||||||
|
"moon",
|
||||||
|
"repo",
|
||||||
|
"cli",
|
||||||
|
"core"
|
||||||
|
],
|
||||||
|
"files": [
|
||||||
|
"moon.js",
|
||||||
|
"moonx.js",
|
||||||
|
"utils.js"
|
||||||
|
],
|
||||||
|
"author": "Miles Johnson",
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"moon": "moon.js",
|
||||||
|
"moonx": "moonx.js"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/moonrepo/moon",
|
||||||
|
"directory": "packages/cli"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"detect-libc": "^2.1.2"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@moonrepo/core-linux-arm64-gnu": "2.0.4",
|
||||||
|
"@moonrepo/core-linux-arm64-musl": "2.0.4",
|
||||||
|
"@moonrepo/core-linux-x64-gnu": "2.0.4",
|
||||||
|
"@moonrepo/core-linux-x64-musl": "2.0.4",
|
||||||
|
"@moonrepo/core-macos-arm64": "2.0.4",
|
||||||
|
"@moonrepo/core-windows-x64-msvc": "2.0.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
51
.tools/bun/install/global/node_modules/@moonrepo/cli/utils.js
generated
vendored
Normal file
51
.tools/bun/install/global/node_modules/@moonrepo/cli/utils.js
generated
vendored
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const isLinux = process.platform === 'linux';
|
||||||
|
const isMacos = process.platform === 'darwin';
|
||||||
|
const isWindows = process.platform === 'win32';
|
||||||
|
|
||||||
|
const platform = isWindows ? 'windows' : isMacos ? 'macos' : process.platform;
|
||||||
|
const arch =
|
||||||
|
process.env['npm_config_user_agent'] && process.env['npm_config_user_agent'].match(/^bun.*arm64$/)
|
||||||
|
? 'arm64'
|
||||||
|
: process.arch; // https://github.com/moonrepo/moon/issues/1103
|
||||||
|
const parts = [platform, arch];
|
||||||
|
|
||||||
|
if (isLinux) {
|
||||||
|
const { familySync } = require('detect-libc');
|
||||||
|
|
||||||
|
if (familySync() === 'musl') {
|
||||||
|
parts.push('musl');
|
||||||
|
// } else if (process.arch === 'arm') {
|
||||||
|
// parts.push('gnueabihf');
|
||||||
|
} else {
|
||||||
|
parts.push('gnu');
|
||||||
|
}
|
||||||
|
} else if (isWindows) {
|
||||||
|
parts.push('msvc');
|
||||||
|
}
|
||||||
|
|
||||||
|
const triple = parts.join('-');
|
||||||
|
|
||||||
|
function findExe(name) {
|
||||||
|
const pkgPath = require.resolve(`@moonrepo/core-${triple}/package.json`);
|
||||||
|
const exePath = path.join(path.dirname(pkgPath), isWindows ? `${name}.exe` : name);
|
||||||
|
|
||||||
|
if (fs.existsSync(exePath)) {
|
||||||
|
return exePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`moon executable "${exePath}" not found!`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function findMoonExe() {
|
||||||
|
return findExe('moon');
|
||||||
|
}
|
||||||
|
|
||||||
|
function findMoonxExe() {
|
||||||
|
return findExe('moonx');
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.findMoonExe = findMoonExe;
|
||||||
|
exports.findMoonxExe = findMoonxExe;
|
||||||
3
.tools/bun/install/global/node_modules/@moonrepo/core-macos-arm64/README.md
generated
vendored
Normal file
3
.tools/bun/install/global/node_modules/@moonrepo/core-macos-arm64/README.md
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# @moonrepo/core-macos-arm64
|
||||||
|
|
||||||
|
This is the `aarch64-apple-darwin` binary for `@moonrepo/cli`.
|
||||||
BIN
.tools/bun/install/global/node_modules/@moonrepo/core-macos-arm64/moon
generated
vendored
Executable file
BIN
.tools/bun/install/global/node_modules/@moonrepo/core-macos-arm64/moon
generated
vendored
Executable file
Binary file not shown.
BIN
.tools/bun/install/global/node_modules/@moonrepo/core-macos-arm64/moonx
generated
vendored
Executable file
BIN
.tools/bun/install/global/node_modules/@moonrepo/core-macos-arm64/moonx
generated
vendored
Executable file
Binary file not shown.
32
.tools/bun/install/global/node_modules/@moonrepo/core-macos-arm64/package.json
generated
vendored
Normal file
32
.tools/bun/install/global/node_modules/@moonrepo/core-macos-arm64/package.json
generated
vendored
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"name": "@moonrepo/core-macos-arm64",
|
||||||
|
"version": "2.0.4",
|
||||||
|
"description": "macOS ARM64 (Silicon) binary for moon.",
|
||||||
|
"keywords": [
|
||||||
|
"moon",
|
||||||
|
"repo",
|
||||||
|
"macos",
|
||||||
|
"darwin",
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"os": [
|
||||||
|
"darwin"
|
||||||
|
],
|
||||||
|
"cpu": [
|
||||||
|
"arm64"
|
||||||
|
],
|
||||||
|
"author": "Miles Johnson",
|
||||||
|
"license": "MIT",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/moonrepo/moon",
|
||||||
|
"directory": "packages/core-macos-arm64"
|
||||||
|
},
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public",
|
||||||
|
"executableFiles": [
|
||||||
|
"moon",
|
||||||
|
"moonx"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
201
.tools/bun/install/global/node_modules/detect-libc/LICENSE
generated
vendored
Normal file
201
.tools/bun/install/global/node_modules/detect-libc/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright {yyyy} {name of copyright owner}
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
163
.tools/bun/install/global/node_modules/detect-libc/README.md
generated
vendored
Normal file
163
.tools/bun/install/global/node_modules/detect-libc/README.md
generated
vendored
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
# detect-libc
|
||||||
|
|
||||||
|
Node.js module to detect details of the C standard library (libc)
|
||||||
|
implementation provided by a given Linux system.
|
||||||
|
|
||||||
|
Currently supports detection of GNU glibc and MUSL libc.
|
||||||
|
|
||||||
|
Provides asychronous and synchronous functions for the
|
||||||
|
family (e.g. `glibc`, `musl`) and version (e.g. `1.23`, `1.2.3`).
|
||||||
|
|
||||||
|
The version numbers of libc implementations
|
||||||
|
are not guaranteed to be semver-compliant.
|
||||||
|
|
||||||
|
For previous v1.x releases, please see the
|
||||||
|
[v1](https://github.com/lovell/detect-libc/tree/v1) branch.
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm install detect-libc
|
||||||
|
```
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
### GLIBC
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const GLIBC: string = 'glibc';
|
||||||
|
```
|
||||||
|
|
||||||
|
A String constant containing the value `glibc`.
|
||||||
|
|
||||||
|
### MUSL
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const MUSL: string = 'musl';
|
||||||
|
```
|
||||||
|
|
||||||
|
A String constant containing the value `musl`.
|
||||||
|
|
||||||
|
### family
|
||||||
|
|
||||||
|
```ts
|
||||||
|
function family(): Promise<string | null>;
|
||||||
|
```
|
||||||
|
|
||||||
|
Resolves asychronously with:
|
||||||
|
|
||||||
|
* `glibc` or `musl` when the libc family can be determined
|
||||||
|
* `null` when the libc family cannot be determined
|
||||||
|
* `null` when run on a non-Linux platform
|
||||||
|
|
||||||
|
```js
|
||||||
|
const { family, GLIBC, MUSL } = require('detect-libc');
|
||||||
|
|
||||||
|
switch (await family()) {
|
||||||
|
case GLIBC: ...
|
||||||
|
case MUSL: ...
|
||||||
|
case null: ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### familySync
|
||||||
|
|
||||||
|
```ts
|
||||||
|
function familySync(): string | null;
|
||||||
|
```
|
||||||
|
|
||||||
|
Synchronous version of `family()`.
|
||||||
|
|
||||||
|
```js
|
||||||
|
const { familySync, GLIBC, MUSL } = require('detect-libc');
|
||||||
|
|
||||||
|
switch (familySync()) {
|
||||||
|
case GLIBC: ...
|
||||||
|
case MUSL: ...
|
||||||
|
case null: ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### version
|
||||||
|
|
||||||
|
```ts
|
||||||
|
function version(): Promise<string | null>;
|
||||||
|
```
|
||||||
|
|
||||||
|
Resolves asychronously with:
|
||||||
|
|
||||||
|
* The version when it can be determined
|
||||||
|
* `null` when the libc family cannot be determined
|
||||||
|
* `null` when run on a non-Linux platform
|
||||||
|
|
||||||
|
```js
|
||||||
|
const { version } = require('detect-libc');
|
||||||
|
|
||||||
|
const v = await version();
|
||||||
|
if (v) {
|
||||||
|
const [major, minor, patch] = v.split('.');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### versionSync
|
||||||
|
|
||||||
|
```ts
|
||||||
|
function versionSync(): string | null;
|
||||||
|
```
|
||||||
|
|
||||||
|
Synchronous version of `version()`.
|
||||||
|
|
||||||
|
```js
|
||||||
|
const { versionSync } = require('detect-libc');
|
||||||
|
|
||||||
|
const v = versionSync();
|
||||||
|
if (v) {
|
||||||
|
const [major, minor, patch] = v.split('.');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### isNonGlibcLinux
|
||||||
|
|
||||||
|
```ts
|
||||||
|
function isNonGlibcLinux(): Promise<boolean>;
|
||||||
|
```
|
||||||
|
|
||||||
|
Resolves asychronously with:
|
||||||
|
|
||||||
|
* `false` when the libc family is `glibc`
|
||||||
|
* `true` when the libc family is not `glibc`
|
||||||
|
* `false` when run on a non-Linux platform
|
||||||
|
|
||||||
|
```js
|
||||||
|
const { isNonGlibcLinux } = require('detect-libc');
|
||||||
|
|
||||||
|
if (await isNonGlibcLinux()) { ... }
|
||||||
|
```
|
||||||
|
|
||||||
|
### isNonGlibcLinuxSync
|
||||||
|
|
||||||
|
```ts
|
||||||
|
function isNonGlibcLinuxSync(): boolean;
|
||||||
|
```
|
||||||
|
|
||||||
|
Synchronous version of `isNonGlibcLinux()`.
|
||||||
|
|
||||||
|
```js
|
||||||
|
const { isNonGlibcLinuxSync } = require('detect-libc');
|
||||||
|
|
||||||
|
if (isNonGlibcLinuxSync()) { ... }
|
||||||
|
```
|
||||||
|
|
||||||
|
## Licensing
|
||||||
|
|
||||||
|
Copyright 2017 Lovell Fuller and others.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0.html)
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
14
.tools/bun/install/global/node_modules/detect-libc/index.d.ts
generated
vendored
Normal file
14
.tools/bun/install/global/node_modules/detect-libc/index.d.ts
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
// Copyright 2017 Lovell Fuller and others.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
export const GLIBC: 'glibc';
|
||||||
|
export const MUSL: 'musl';
|
||||||
|
|
||||||
|
export function family(): Promise<string | null>;
|
||||||
|
export function familySync(): string | null;
|
||||||
|
|
||||||
|
export function isNonGlibcLinux(): Promise<boolean>;
|
||||||
|
export function isNonGlibcLinuxSync(): boolean;
|
||||||
|
|
||||||
|
export function version(): Promise<string | null>;
|
||||||
|
export function versionSync(): string | null;
|
||||||
313
.tools/bun/install/global/node_modules/detect-libc/lib/detect-libc.js
generated
vendored
Normal file
313
.tools/bun/install/global/node_modules/detect-libc/lib/detect-libc.js
generated
vendored
Normal file
@@ -0,0 +1,313 @@
|
|||||||
|
// Copyright 2017 Lovell Fuller and others.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const childProcess = require('child_process');
|
||||||
|
const { isLinux, getReport } = require('./process');
|
||||||
|
const { LDD_PATH, SELF_PATH, readFile, readFileSync } = require('./filesystem');
|
||||||
|
const { interpreterPath } = require('./elf');
|
||||||
|
|
||||||
|
let cachedFamilyInterpreter;
|
||||||
|
let cachedFamilyFilesystem;
|
||||||
|
let cachedVersionFilesystem;
|
||||||
|
|
||||||
|
const command = 'getconf GNU_LIBC_VERSION 2>&1 || true; ldd --version 2>&1 || true';
|
||||||
|
let commandOut = '';
|
||||||
|
|
||||||
|
const safeCommand = () => {
|
||||||
|
if (!commandOut) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
childProcess.exec(command, (err, out) => {
|
||||||
|
commandOut = err ? ' ' : out;
|
||||||
|
resolve(commandOut);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return commandOut;
|
||||||
|
};
|
||||||
|
|
||||||
|
const safeCommandSync = () => {
|
||||||
|
if (!commandOut) {
|
||||||
|
try {
|
||||||
|
commandOut = childProcess.execSync(command, { encoding: 'utf8' });
|
||||||
|
} catch (_err) {
|
||||||
|
commandOut = ' ';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return commandOut;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A String constant containing the value `glibc`.
|
||||||
|
* @type {string}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
const GLIBC = 'glibc';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Regexp constant to get the GLIBC Version.
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
const RE_GLIBC_VERSION = /LIBC[a-z0-9 \-).]*?(\d+\.\d+)/i;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A String constant containing the value `musl`.
|
||||||
|
* @type {string}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
const MUSL = 'musl';
|
||||||
|
|
||||||
|
const isFileMusl = (f) => f.includes('libc.musl-') || f.includes('ld-musl-');
|
||||||
|
|
||||||
|
const familyFromReport = () => {
|
||||||
|
const report = getReport();
|
||||||
|
if (report.header && report.header.glibcVersionRuntime) {
|
||||||
|
return GLIBC;
|
||||||
|
}
|
||||||
|
if (Array.isArray(report.sharedObjects)) {
|
||||||
|
if (report.sharedObjects.some(isFileMusl)) {
|
||||||
|
return MUSL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const familyFromCommand = (out) => {
|
||||||
|
const [getconf, ldd1] = out.split(/[\r\n]+/);
|
||||||
|
if (getconf && getconf.includes(GLIBC)) {
|
||||||
|
return GLIBC;
|
||||||
|
}
|
||||||
|
if (ldd1 && ldd1.includes(MUSL)) {
|
||||||
|
return MUSL;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const familyFromInterpreterPath = (path) => {
|
||||||
|
if (path) {
|
||||||
|
if (path.includes('/ld-musl-')) {
|
||||||
|
return MUSL;
|
||||||
|
} else if (path.includes('/ld-linux-')) {
|
||||||
|
return GLIBC;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getFamilyFromLddContent = (content) => {
|
||||||
|
content = content.toString();
|
||||||
|
if (content.includes('musl')) {
|
||||||
|
return MUSL;
|
||||||
|
}
|
||||||
|
if (content.includes('GNU C Library')) {
|
||||||
|
return GLIBC;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const familyFromFilesystem = async () => {
|
||||||
|
if (cachedFamilyFilesystem !== undefined) {
|
||||||
|
return cachedFamilyFilesystem;
|
||||||
|
}
|
||||||
|
cachedFamilyFilesystem = null;
|
||||||
|
try {
|
||||||
|
const lddContent = await readFile(LDD_PATH);
|
||||||
|
cachedFamilyFilesystem = getFamilyFromLddContent(lddContent);
|
||||||
|
} catch (e) {}
|
||||||
|
return cachedFamilyFilesystem;
|
||||||
|
};
|
||||||
|
|
||||||
|
const familyFromFilesystemSync = () => {
|
||||||
|
if (cachedFamilyFilesystem !== undefined) {
|
||||||
|
return cachedFamilyFilesystem;
|
||||||
|
}
|
||||||
|
cachedFamilyFilesystem = null;
|
||||||
|
try {
|
||||||
|
const lddContent = readFileSync(LDD_PATH);
|
||||||
|
cachedFamilyFilesystem = getFamilyFromLddContent(lddContent);
|
||||||
|
} catch (e) {}
|
||||||
|
return cachedFamilyFilesystem;
|
||||||
|
};
|
||||||
|
|
||||||
|
const familyFromInterpreter = async () => {
|
||||||
|
if (cachedFamilyInterpreter !== undefined) {
|
||||||
|
return cachedFamilyInterpreter;
|
||||||
|
}
|
||||||
|
cachedFamilyInterpreter = null;
|
||||||
|
try {
|
||||||
|
const selfContent = await readFile(SELF_PATH);
|
||||||
|
const path = interpreterPath(selfContent);
|
||||||
|
cachedFamilyInterpreter = familyFromInterpreterPath(path);
|
||||||
|
} catch (e) {}
|
||||||
|
return cachedFamilyInterpreter;
|
||||||
|
};
|
||||||
|
|
||||||
|
const familyFromInterpreterSync = () => {
|
||||||
|
if (cachedFamilyInterpreter !== undefined) {
|
||||||
|
return cachedFamilyInterpreter;
|
||||||
|
}
|
||||||
|
cachedFamilyInterpreter = null;
|
||||||
|
try {
|
||||||
|
const selfContent = readFileSync(SELF_PATH);
|
||||||
|
const path = interpreterPath(selfContent);
|
||||||
|
cachedFamilyInterpreter = familyFromInterpreterPath(path);
|
||||||
|
} catch (e) {}
|
||||||
|
return cachedFamilyInterpreter;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves with the libc family when it can be determined, `null` otherwise.
|
||||||
|
* @returns {Promise<?string>}
|
||||||
|
*/
|
||||||
|
const family = async () => {
|
||||||
|
let family = null;
|
||||||
|
if (isLinux()) {
|
||||||
|
family = await familyFromInterpreter();
|
||||||
|
if (!family) {
|
||||||
|
family = await familyFromFilesystem();
|
||||||
|
if (!family) {
|
||||||
|
family = familyFromReport();
|
||||||
|
}
|
||||||
|
if (!family) {
|
||||||
|
const out = await safeCommand();
|
||||||
|
family = familyFromCommand(out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return family;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the libc family when it can be determined, `null` otherwise.
|
||||||
|
* @returns {?string}
|
||||||
|
*/
|
||||||
|
const familySync = () => {
|
||||||
|
let family = null;
|
||||||
|
if (isLinux()) {
|
||||||
|
family = familyFromInterpreterSync();
|
||||||
|
if (!family) {
|
||||||
|
family = familyFromFilesystemSync();
|
||||||
|
if (!family) {
|
||||||
|
family = familyFromReport();
|
||||||
|
}
|
||||||
|
if (!family) {
|
||||||
|
const out = safeCommandSync();
|
||||||
|
family = familyFromCommand(out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return family;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves `true` only when the platform is Linux and the libc family is not `glibc`.
|
||||||
|
* @returns {Promise<boolean>}
|
||||||
|
*/
|
||||||
|
const isNonGlibcLinux = async () => isLinux() && await family() !== GLIBC;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns `true` only when the platform is Linux and the libc family is not `glibc`.
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
const isNonGlibcLinuxSync = () => isLinux() && familySync() !== GLIBC;
|
||||||
|
|
||||||
|
const versionFromFilesystem = async () => {
|
||||||
|
if (cachedVersionFilesystem !== undefined) {
|
||||||
|
return cachedVersionFilesystem;
|
||||||
|
}
|
||||||
|
cachedVersionFilesystem = null;
|
||||||
|
try {
|
||||||
|
const lddContent = await readFile(LDD_PATH);
|
||||||
|
const versionMatch = lddContent.match(RE_GLIBC_VERSION);
|
||||||
|
if (versionMatch) {
|
||||||
|
cachedVersionFilesystem = versionMatch[1];
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
return cachedVersionFilesystem;
|
||||||
|
};
|
||||||
|
|
||||||
|
const versionFromFilesystemSync = () => {
|
||||||
|
if (cachedVersionFilesystem !== undefined) {
|
||||||
|
return cachedVersionFilesystem;
|
||||||
|
}
|
||||||
|
cachedVersionFilesystem = null;
|
||||||
|
try {
|
||||||
|
const lddContent = readFileSync(LDD_PATH);
|
||||||
|
const versionMatch = lddContent.match(RE_GLIBC_VERSION);
|
||||||
|
if (versionMatch) {
|
||||||
|
cachedVersionFilesystem = versionMatch[1];
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
return cachedVersionFilesystem;
|
||||||
|
};
|
||||||
|
|
||||||
|
const versionFromReport = () => {
|
||||||
|
const report = getReport();
|
||||||
|
if (report.header && report.header.glibcVersionRuntime) {
|
||||||
|
return report.header.glibcVersionRuntime;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const versionSuffix = (s) => s.trim().split(/\s+/)[1];
|
||||||
|
|
||||||
|
const versionFromCommand = (out) => {
|
||||||
|
const [getconf, ldd1, ldd2] = out.split(/[\r\n]+/);
|
||||||
|
if (getconf && getconf.includes(GLIBC)) {
|
||||||
|
return versionSuffix(getconf);
|
||||||
|
}
|
||||||
|
if (ldd1 && ldd2 && ldd1.includes(MUSL)) {
|
||||||
|
return versionSuffix(ldd2);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves with the libc version when it can be determined, `null` otherwise.
|
||||||
|
* @returns {Promise<?string>}
|
||||||
|
*/
|
||||||
|
const version = async () => {
|
||||||
|
let version = null;
|
||||||
|
if (isLinux()) {
|
||||||
|
version = await versionFromFilesystem();
|
||||||
|
if (!version) {
|
||||||
|
version = versionFromReport();
|
||||||
|
}
|
||||||
|
if (!version) {
|
||||||
|
const out = await safeCommand();
|
||||||
|
version = versionFromCommand(out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return version;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the libc version when it can be determined, `null` otherwise.
|
||||||
|
* @returns {?string}
|
||||||
|
*/
|
||||||
|
const versionSync = () => {
|
||||||
|
let version = null;
|
||||||
|
if (isLinux()) {
|
||||||
|
version = versionFromFilesystemSync();
|
||||||
|
if (!version) {
|
||||||
|
version = versionFromReport();
|
||||||
|
}
|
||||||
|
if (!version) {
|
||||||
|
const out = safeCommandSync();
|
||||||
|
version = versionFromCommand(out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return version;
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
GLIBC,
|
||||||
|
MUSL,
|
||||||
|
family,
|
||||||
|
familySync,
|
||||||
|
isNonGlibcLinux,
|
||||||
|
isNonGlibcLinuxSync,
|
||||||
|
version,
|
||||||
|
versionSync
|
||||||
|
};
|
||||||
39
.tools/bun/install/global/node_modules/detect-libc/lib/elf.js
generated
vendored
Normal file
39
.tools/bun/install/global/node_modules/detect-libc/lib/elf.js
generated
vendored
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
// Copyright 2017 Lovell Fuller and others.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const interpreterPath = (elf) => {
|
||||||
|
if (elf.length < 64) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (elf.readUInt32BE(0) !== 0x7F454C46) {
|
||||||
|
// Unexpected magic bytes
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (elf.readUInt8(4) !== 2) {
|
||||||
|
// Not a 64-bit ELF
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (elf.readUInt8(5) !== 1) {
|
||||||
|
// Not little-endian
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const offset = elf.readUInt32LE(32);
|
||||||
|
const size = elf.readUInt16LE(54);
|
||||||
|
const count = elf.readUInt16LE(56);
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
const headerOffset = offset + (i * size);
|
||||||
|
const type = elf.readUInt32LE(headerOffset);
|
||||||
|
if (type === 3) {
|
||||||
|
const fileOffset = elf.readUInt32LE(headerOffset + 8);
|
||||||
|
const fileSize = elf.readUInt32LE(headerOffset + 32);
|
||||||
|
return elf.subarray(fileOffset, fileOffset + fileSize).toString().replace(/\0.*$/g, '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
interpreterPath
|
||||||
|
};
|
||||||
51
.tools/bun/install/global/node_modules/detect-libc/lib/filesystem.js
generated
vendored
Normal file
51
.tools/bun/install/global/node_modules/detect-libc/lib/filesystem.js
generated
vendored
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
// Copyright 2017 Lovell Fuller and others.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
const LDD_PATH = '/usr/bin/ldd';
|
||||||
|
const SELF_PATH = '/proc/self/exe';
|
||||||
|
const MAX_LENGTH = 2048;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read the content of a file synchronous
|
||||||
|
*
|
||||||
|
* @param {string} path
|
||||||
|
* @returns {Buffer}
|
||||||
|
*/
|
||||||
|
const readFileSync = (path) => {
|
||||||
|
const fd = fs.openSync(path, 'r');
|
||||||
|
const buffer = Buffer.alloc(MAX_LENGTH);
|
||||||
|
const bytesRead = fs.readSync(fd, buffer, 0, MAX_LENGTH, 0);
|
||||||
|
fs.close(fd, () => {});
|
||||||
|
return buffer.subarray(0, bytesRead);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read the content of a file
|
||||||
|
*
|
||||||
|
* @param {string} path
|
||||||
|
* @returns {Promise<Buffer>}
|
||||||
|
*/
|
||||||
|
const readFile = (path) => new Promise((resolve, reject) => {
|
||||||
|
fs.open(path, 'r', (err, fd) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
const buffer = Buffer.alloc(MAX_LENGTH);
|
||||||
|
fs.read(fd, buffer, 0, MAX_LENGTH, 0, (_, bytesRead) => {
|
||||||
|
resolve(buffer.subarray(0, bytesRead));
|
||||||
|
fs.close(fd, () => {});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
LDD_PATH,
|
||||||
|
SELF_PATH,
|
||||||
|
readFileSync,
|
||||||
|
readFile
|
||||||
|
};
|
||||||
24
.tools/bun/install/global/node_modules/detect-libc/lib/process.js
generated
vendored
Normal file
24
.tools/bun/install/global/node_modules/detect-libc/lib/process.js
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
// Copyright 2017 Lovell Fuller and others.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const isLinux = () => process.platform === 'linux';
|
||||||
|
|
||||||
|
let report = null;
|
||||||
|
const getReport = () => {
|
||||||
|
if (!report) {
|
||||||
|
/* istanbul ignore next */
|
||||||
|
if (isLinux() && process.report) {
|
||||||
|
const orig = process.report.excludeNetwork;
|
||||||
|
process.report.excludeNetwork = true;
|
||||||
|
report = process.report.getReport();
|
||||||
|
process.report.excludeNetwork = orig;
|
||||||
|
} else {
|
||||||
|
report = {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return report;
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = { isLinux, getReport };
|
||||||
44
.tools/bun/install/global/node_modules/detect-libc/package.json
generated
vendored
Normal file
44
.tools/bun/install/global/node_modules/detect-libc/package.json
generated
vendored
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
{
|
||||||
|
"name": "detect-libc",
|
||||||
|
"version": "2.1.2",
|
||||||
|
"description": "Node.js module to detect the C standard library (libc) implementation family and version",
|
||||||
|
"main": "lib/detect-libc.js",
|
||||||
|
"files": [
|
||||||
|
"lib/",
|
||||||
|
"index.d.ts"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"test": "semistandard && nyc --reporter=text --check-coverage --branches=100 ava test/unit.js",
|
||||||
|
"changelog": "conventional-changelog -i CHANGELOG.md -s",
|
||||||
|
"bench": "node benchmark/detect-libc",
|
||||||
|
"bench:calls": "node benchmark/call-familySync.js && sleep 1 && node benchmark/call-isNonGlibcLinuxSync.js && sleep 1 && node benchmark/call-versionSync.js"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git://github.com/lovell/detect-libc.git"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"libc",
|
||||||
|
"glibc",
|
||||||
|
"musl"
|
||||||
|
],
|
||||||
|
"author": "Lovell Fuller <npm@lovell.info>",
|
||||||
|
"contributors": [
|
||||||
|
"Niklas Salmoukas <niklas@salmoukas.com>",
|
||||||
|
"Vinícius Lourenço <vinyygamerlol@gmail.com>"
|
||||||
|
],
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"devDependencies": {
|
||||||
|
"ava": "^2.4.0",
|
||||||
|
"benchmark": "^2.1.4",
|
||||||
|
"conventional-changelog-cli": "^5.0.0",
|
||||||
|
"eslint-config-standard": "^13.0.1",
|
||||||
|
"nyc": "^15.1.0",
|
||||||
|
"proxyquire": "^2.1.3",
|
||||||
|
"semistandard": "^14.2.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
},
|
||||||
|
"types": "index.d.ts"
|
||||||
|
}
|
||||||
5
.tools/bun/install/global/package.json
Normal file
5
.tools/bun/install/global/package.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"@moonrepo/cli": "^2.0.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
200
README.md
200
README.md
@@ -1,91 +1,170 @@
|
|||||||
# repo-lib
|
# repo-lib
|
||||||
|
|
||||||
Simple Nix flake library for:
|
`repo-lib` is a pure-first Nix flake library for repo-level developer workflows:
|
||||||
|
|
||||||
- a shared development shell (`mkDevShell`)
|
- `mkRepo` for `devShells`, `checks`, `formatter`, and optional `packages.release`
|
||||||
- an optional release command (`mkRelease`)
|
- structured tool banners driven from package-backed tool specs
|
||||||
- a starter template (`template/`)
|
- structured release steps (`writeFile`, `replace`, `run`)
|
||||||
|
- a Bun-only Moonrepo + TypeScript + Varlock template in [`template/`](/Users/eric/Projects/repo-lib/template)
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
- [Nix](https://nixos.org/download/) with flakes enabled
|
- [Nix](https://nixos.org/download/) with flakes enabled
|
||||||
- [`direnv`](https://direnv.net/) (recommended)
|
- [`direnv`](https://direnv.net/) (recommended)
|
||||||
|
|
||||||
## Use the template (new repo)
|
## Use the template
|
||||||
|
|
||||||
From your new project folder:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
nix flake new myapp -t 'git+https://git.dgren.dev/eric/nix-flake-lib?ref=v2.1.0#default' --refresh
|
nix flake new myapp -t 'git+https://git.dgren.dev/eric/nix-flake-lib?ref=refs/tags/v3.5.0#default' --refresh
|
||||||
```
|
```
|
||||||
|
|
||||||
## Use the library (existing repo)
|
The generated repo includes:
|
||||||
|
|
||||||
|
- a `repo-lib`-managed Nix flake
|
||||||
|
- Bun as the only JS runtime and package manager
|
||||||
|
- Moonrepo root tasks
|
||||||
|
- shared TypeScript configs adapted from `../moon`
|
||||||
|
- Varlock with a committed `.env.schema`
|
||||||
|
- empty `apps/` and `packages/` directories for new projects
|
||||||
|
|
||||||
|
## Use the library
|
||||||
|
|
||||||
Add this flake input:
|
Add this flake input:
|
||||||
|
|
||||||
```nix
|
```nix
|
||||||
inputs.devshell-lib.url = "git+https://git.dgren.dev/eric/nix-flake-lib?ref=v2.1.0";
|
inputs.repo-lib.url = "git+https://git.dgren.dev/eric/nix-flake-lib?ref=refs/tags/v3.5.0";
|
||||||
inputs.devshell-lib.inputs.nixpkgs.follows = "nixpkgs";
|
inputs.repo-lib.inputs.nixpkgs.follows = "nixpkgs";
|
||||||
```
|
```
|
||||||
|
|
||||||
Create your shell from `mkDevShell`:
|
Build your repo outputs from `mkRepo`:
|
||||||
|
|
||||||
```nix
|
```nix
|
||||||
env = devshell-lib.lib.mkDevShell {
|
outputs = { self, nixpkgs, repo-lib, ... }:
|
||||||
inherit system;
|
repo-lib.lib.mkRepo {
|
||||||
|
inherit self nixpkgs;
|
||||||
src = ./.;
|
src = ./.;
|
||||||
extraPackages = [ ];
|
|
||||||
preToolHook = "";
|
config = {
|
||||||
tools = [ ];
|
checks.tests = {
|
||||||
additionalHooks = { };
|
command = "echo 'No tests defined yet.'";
|
||||||
|
stage = "pre-push";
|
||||||
|
passFilenames = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
release = {
|
||||||
|
steps = [ ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
perSystem = { pkgs, system, ... }: {
|
||||||
|
tools = [
|
||||||
|
(repo-lib.lib.tools.fromCommand {
|
||||||
|
name = "Nix";
|
||||||
|
version.args = [ "--version" ];
|
||||||
|
command = "nix";
|
||||||
|
})
|
||||||
|
];
|
||||||
|
|
||||||
|
shell.packages = [
|
||||||
|
self.packages.${system}.release
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
`mkRepo` generates:
|
||||||
|
|
||||||
|
- `devShells.${system}.default`
|
||||||
|
- `checks.${system}.hook-check`
|
||||||
|
- `checks.${system}.lefthook-check`
|
||||||
|
- `formatter.${system}`
|
||||||
|
- `packages.${system}.release` when `config.release != null`
|
||||||
|
- merged `packages` and `apps` from `perSystem`
|
||||||
|
|
||||||
|
Checks are installed through `lefthook`, with `pre-commit` and `pre-push` commands configured to run in parallel.
|
||||||
|
repo-lib also sets Lefthook `output = [ "failure" "summary" ]` by default.
|
||||||
|
|
||||||
|
For advanced Lefthook features, use raw `config.lefthook` or `perSystem.lefthook`. Those attrsets are merged after generated checks, so you can augment a generated command with fields that the simple `checks` abstraction does not carry, such as `stage_fixed`:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
config.lefthook.pre-push.commands.tests.stage_fixed = true;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Tool banners
|
||||||
|
|
||||||
|
Tools are declared once. Package-backed tools are added to the shell automatically, and both package-backed and command-backed tools are rendered in the startup banner.
|
||||||
|
|
||||||
|
```nix
|
||||||
|
(repo-lib.lib.tools.fromPackage {
|
||||||
|
name = "Go";
|
||||||
|
package = pkgs.go;
|
||||||
|
version.args = [ "version" ];
|
||||||
|
banner.color = "CYAN";
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Required tools fail shell startup if their version probe fails. This keeps banner output honest instead of silently hiding misconfiguration.
|
||||||
|
|
||||||
|
When a tool should come from the host environment instead of `nixpkgs`, use `fromCommand`:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
(repo-lib.lib.tools.fromCommand {
|
||||||
|
name = "Nix";
|
||||||
|
command = "nix";
|
||||||
|
version.args = [ "--version" ];
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Purity model
|
||||||
|
|
||||||
|
The default path is pure: declare tools and packages in Nix, then let `mkRepo` assemble the shell.
|
||||||
|
|
||||||
|
Impure bootstrap work is still possible, but it must be explicit:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
config.shell = {
|
||||||
|
bootstrap = ''
|
||||||
|
export GOBIN="$PWD/.tools/bin"
|
||||||
|
export PATH="$GOBIN:$PATH"
|
||||||
|
'';
|
||||||
|
allowImpureBootstrap = true;
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
Expose it in `devShells` as `default` and run:
|
## Release steps
|
||||||
|
|
||||||
```bash
|
Structured release steps are preferred over raw `sed` snippets:
|
||||||
nix develop
|
|
||||||
```
|
|
||||||
|
|
||||||
Use `preToolHook` when a tool needs bootstrap work before the shell prints tool versions. This is useful for tools you install outside `nixpkgs`, as long as the hook is idempotent.
|
|
||||||
|
|
||||||
```nix
|
```nix
|
||||||
env = devshell-lib.lib.mkDevShell {
|
config.release = {
|
||||||
inherit system;
|
steps = [
|
||||||
src = ./.;
|
{
|
||||||
|
writeFile = {
|
||||||
# assumes `go` is already available in PATH, for example via `extraPackages`
|
path = "src/version.ts";
|
||||||
|
text = ''
|
||||||
preToolHook = ''
|
export const APP_VERSION = "$FULL_VERSION" as const;
|
||||||
export GOBIN="$PWD/.tools/bin"
|
|
||||||
export PATH="$GOBIN:$PATH"
|
|
||||||
|
|
||||||
if ! command -v golangci-lint >/dev/null 2>&1; then
|
|
||||||
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
|
|
||||||
fi
|
|
||||||
'';
|
'';
|
||||||
|
};
|
||||||
tools = [
|
}
|
||||||
{ name = "golangci-lint"; bin = "golangci-lint"; versionCmd = "version"; color = "YELLOW"; }
|
{
|
||||||
|
replace = {
|
||||||
|
path = "README.md";
|
||||||
|
regex = ''^(version = ")[^"]*(")$'';
|
||||||
|
replacement = ''\1$FULL_VERSION\2'';
|
||||||
|
};
|
||||||
|
}
|
||||||
|
{
|
||||||
|
run = {
|
||||||
|
script = ''
|
||||||
|
echo "Released $FULL_TAG"
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
}
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
## Common commands
|
The generated `release` command still supports:
|
||||||
|
|
||||||
```bash
|
|
||||||
nix fmt # format files
|
|
||||||
```
|
|
||||||
|
|
||||||
## Optional: release command
|
|
||||||
|
|
||||||
If your flake defines:
|
|
||||||
|
|
||||||
```nix
|
|
||||||
packages.${system}.release = devshell-lib.lib.mkRelease { inherit system; };
|
|
||||||
```
|
|
||||||
|
|
||||||
Run releases with:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
release
|
release
|
||||||
@@ -96,5 +175,12 @@ release stable
|
|||||||
release set 1.2.3
|
release set 1.2.3
|
||||||
```
|
```
|
||||||
|
|
||||||
The release script uses `./VERSION` as the source of truth and creates tags like `v1.2.3`.
|
## Low-level APIs
|
||||||
When switching from stable to a prerelease channel without an explicit bump (for example, `release beta`), it applies a patch bump automatically (for example, `1.0.0` -> `1.0.1-beta.1`).
|
|
||||||
|
`mkDevShell` and `mkRelease` remain available for repos that want lower-level control or a migration path from the older library shape.
|
||||||
|
|
||||||
|
## Common command
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nix fmt
|
||||||
|
```
|
||||||
|
|||||||
@@ -0,0 +1,99 @@
|
|||||||
|
# TypeScript Monorepo Template Implementation Plan
|
||||||
|
|
||||||
|
> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||||
|
|
||||||
|
**Goal:** Replace the minimal starter template with a Bun-only Moonrepo + TypeScript + Varlock monorepo template exposed through the existing flake template.
|
||||||
|
|
||||||
|
**Architecture:** Expand `template/` into a complete repository skeleton while keeping `repo-lib.lib.mkRepo` as the integration point. Adapt the strict TypeScript config layout and Varlock command pattern from `../moon`, and update release tests so they evaluate the full template contents.
|
||||||
|
|
||||||
|
**Tech Stack:** Nix flakes, repo-lib, Bun, Moonrepo, Varlock, TypeScript
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Chunk 1: Documentation Baseline
|
||||||
|
|
||||||
|
### Task 1: Update public template docs
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `README.md`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Write the failing expectation mentally against current docs**
|
||||||
|
|
||||||
|
Current docs describe only a minimal starter template and do not mention Bun, Moonrepo, or Varlock.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Update the README to describe the new template**
|
||||||
|
|
||||||
|
Document the generated workspace shape and first-run commands.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Verify the README content is consistent with the template files**
|
||||||
|
|
||||||
|
Check all commands and filenames against the final template layout.
|
||||||
|
|
||||||
|
## Chunk 2: Template Skeleton
|
||||||
|
|
||||||
|
### Task 2: Replace the minimal template with a real monorepo skeleton
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `template/flake.nix`
|
||||||
|
- Create: `template/package.json`
|
||||||
|
- Create: `template/bunfig.toml`
|
||||||
|
- Create: `template/moon.yml`
|
||||||
|
- Create: `template/tsconfig.json`
|
||||||
|
- Create: `template/tsconfig.options.json`
|
||||||
|
- Create: `template/tsconfig/browser.json`
|
||||||
|
- Create: `template/tsconfig/bun.json`
|
||||||
|
- Create: `template/tsconfig/package.json`
|
||||||
|
- Create: `template/tsconfig/runtime.json`
|
||||||
|
- Create: `template/.env.schema`
|
||||||
|
- Modify: `template/.gitignore`
|
||||||
|
- Create: `template/README.md`
|
||||||
|
- Create: `template/apps/.gitkeep`
|
||||||
|
- Create: `template/packages/.gitkeep`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Add or update template files**
|
||||||
|
|
||||||
|
Use `../moon` as the source for Moonrepo, Varlock, and TypeScript patterns, removing product-specific details.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Verify the template tree is coherent**
|
||||||
|
|
||||||
|
Check that all referenced files exist and that scripts reference only template-safe commands.
|
||||||
|
|
||||||
|
## Chunk 3: Test Coverage
|
||||||
|
|
||||||
|
### Task 3: Update release tests for the full template
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `tests/release.sh`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Add a failing test expectation**
|
||||||
|
|
||||||
|
The current template fixture copies only `template/flake.nix`, which is insufficient for the new template layout.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Update fixture creation to copy the full template**
|
||||||
|
|
||||||
|
Rewrite template URL references in copied files as needed for local test evaluation.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Verify the existing template evaluation case now uses the real skeleton**
|
||||||
|
|
||||||
|
Confirm `nix flake show` runs against the expanded template fixture.
|
||||||
|
|
||||||
|
## Chunk 4: Verification
|
||||||
|
|
||||||
|
### Task 4: Run template verification
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Verify: `README.md`
|
||||||
|
- Verify: `template/**/*`
|
||||||
|
- Verify: `tests/release.sh`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Run the release test suite**
|
||||||
|
|
||||||
|
Run: `nix develop -c bash tests/release.sh`
|
||||||
|
|
||||||
|
- [ ] **Step 2: Inspect the template file tree**
|
||||||
|
|
||||||
|
Run: `find template -maxdepth 3 -type f | sort`
|
||||||
|
|
||||||
|
- [ ] **Step 3: Verify the README examples still match the tagged template release pattern**
|
||||||
|
|
||||||
|
Check that versioned `repo-lib` URLs remain in the documented commands and release replacements.
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
# TypeScript Monorepo Template Design
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Add a new default template to this repository that generates a Bun-only TypeScript monorepo using Moonrepo, Varlock, and the shared TypeScript configuration pattern from `../moon`.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
The generated template should include:
|
||||||
|
|
||||||
|
- a Nix flake wired through `repo-lib.lib.mkRepo`
|
||||||
|
- Bun-only JavaScript tooling
|
||||||
|
- Moonrepo root configuration
|
||||||
|
- strict shared TypeScript configs adapted from `../moon`
|
||||||
|
- Varlock enabled from day one
|
||||||
|
- a committed `.env.schema`
|
||||||
|
- empty `apps/` and `packages/` directories
|
||||||
|
- minimal documentation for first-run setup
|
||||||
|
|
||||||
|
The template should not include:
|
||||||
|
|
||||||
|
- demo apps or packages
|
||||||
|
- product-specific environment variables or OpenBao paths from `../moon`
|
||||||
|
- Node or pnpm support
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
The existing `template/` directory remains the exported flake template. Instead of containing only a starter `flake.nix`, it will become a complete repository skeleton.
|
||||||
|
|
||||||
|
The generated repository will keep the current `repo-lib` integration pattern:
|
||||||
|
|
||||||
|
- `template/flake.nix` calls `repo-lib.lib.mkRepo`
|
||||||
|
- the shell provisions Bun, Moonrepo CLI, Varlock, and supporting tooling
|
||||||
|
- repo checks remain driven through `mkRepo` and Lefthook
|
||||||
|
|
||||||
|
Moonrepo and Varlock will be configured at the workspace root. The template will expose root tasks and scripts that work even before any projects are added.
|
||||||
|
|
||||||
|
## Template Contents
|
||||||
|
|
||||||
|
The template should contain:
|
||||||
|
|
||||||
|
- `flake.nix`
|
||||||
|
- `package.json`
|
||||||
|
- `bunfig.toml`
|
||||||
|
- `moon.yml`
|
||||||
|
- `tsconfig.json`
|
||||||
|
- `tsconfig.options.json`
|
||||||
|
- `tsconfig/browser.json`
|
||||||
|
- `tsconfig/bun.json`
|
||||||
|
- `tsconfig/package.json`
|
||||||
|
- `tsconfig/runtime.json`
|
||||||
|
- `.env.schema`
|
||||||
|
- `.gitignore`
|
||||||
|
- `README.md`
|
||||||
|
- `apps/.gitkeep`
|
||||||
|
- `packages/.gitkeep`
|
||||||
|
|
||||||
|
It may also keep generic repo support files already useful in templates, such as `.envrc`, `.gitlint`, `.gitleaks.toml`, `.vscode/settings.json`, and `flake.lock`, as long as they remain template-safe.
|
||||||
|
|
||||||
|
## Data And Command Flow
|
||||||
|
|
||||||
|
On first use:
|
||||||
|
|
||||||
|
1. the user creates a repo from the flake template
|
||||||
|
2. the shell provides Bun, Moonrepo, Varlock, and release support
|
||||||
|
3. `bun install` installs `@moonrepo/cli`, `varlock`, and TypeScript-related dependencies
|
||||||
|
4. entering the repo loads `varlock/auto-load`
|
||||||
|
5. root commands like `bun run env:check`, `bun run env:scan`, and `moon run :typecheck` work without any sample projects
|
||||||
|
|
||||||
|
## Varlock Design
|
||||||
|
|
||||||
|
The template will include a minimal `.env.schema` with:
|
||||||
|
|
||||||
|
- one canonical environment selector
|
||||||
|
- safe local defaults where practical
|
||||||
|
- placeholders for OpenBao-backed secrets using generic template paths
|
||||||
|
|
||||||
|
Root scripts in `package.json` will follow the `../moon` pattern for `env:check` and `env:scan`, including `BAO_*` and `OPENBAO_*` compatibility exports. The template will not encode any product-specific namespace names.
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
Existing release tests must continue to validate the exported template. The template fixture helper in `tests/release.sh` will need to copy the full template directory, not only `template/flake.nix`, so `nix flake show` exercises the real generated repository structure.
|
||||||
|
|
||||||
|
## Risks
|
||||||
|
|
||||||
|
- Moonrepo root task behavior must remain valid with no projects present.
|
||||||
|
- Template-safe Varlock defaults must avoid broken first-run behavior while still demonstrating the intended pattern.
|
||||||
|
- The release test harness must not accidentally preserve upstream URLs inside the copied template.
|
||||||
77
flake.lock
generated
77
flake.lock
generated
@@ -1,79 +1,26 @@
|
|||||||
{
|
{
|
||||||
"nodes": {
|
"nodes": {
|
||||||
"flake-compat": {
|
"lefthook-nix": {
|
||||||
"flake": false,
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1767039857,
|
|
||||||
"narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=",
|
|
||||||
"owner": "NixOS",
|
|
||||||
"repo": "flake-compat",
|
|
||||||
"rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "NixOS",
|
|
||||||
"repo": "flake-compat",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"git-hooks": {
|
|
||||||
"inputs": {
|
|
||||||
"flake-compat": "flake-compat",
|
|
||||||
"gitignore": "gitignore",
|
|
||||||
"nixpkgs": "nixpkgs"
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1772024342,
|
|
||||||
"narHash": "sha256-+eXlIc4/7dE6EcPs9a2DaSY3fTA9AE526hGqkNID3Wg=",
|
|
||||||
"owner": "cachix",
|
|
||||||
"repo": "git-hooks.nix",
|
|
||||||
"rev": "6e34e97ed9788b17796ee43ccdbaf871a5c2b476",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "cachix",
|
|
||||||
"repo": "git-hooks.nix",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"gitignore": {
|
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
"git-hooks",
|
|
||||||
"nixpkgs"
|
"nixpkgs"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1709087332,
|
"lastModified": 1770377107,
|
||||||
"narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
|
"narHash": "sha256-/QEXSDeAo5RK81PtM0yDhmt9k3v1/pse/jsrT1yXNhU=",
|
||||||
"owner": "hercules-ci",
|
"owner": "sudosubin",
|
||||||
"repo": "gitignore.nix",
|
"repo": "lefthook.nix",
|
||||||
"rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
|
"rev": "9cdaf7ce95ae77cbabc5b556bdd35d3cf0b849f5",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "hercules-ci",
|
"owner": "sudosubin",
|
||||||
"repo": "gitignore.nix",
|
"repo": "lefthook.nix",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
|
||||||
"lastModified": 1770073757,
|
|
||||||
"narHash": "sha256-Vy+G+F+3E/Tl+GMNgiHl9Pah2DgShmIUBJXmbiQPHbI=",
|
|
||||||
"owner": "NixOS",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"rev": "47472570b1e607482890801aeaf29bfb749884f6",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "NixOS",
|
|
||||||
"ref": "nixpkgs-unstable",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nixpkgs_2": {
|
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1772542754,
|
"lastModified": 1772542754,
|
||||||
"narHash": "sha256-WGV2hy+VIeQsYXpsLjdr4GvHv5eECMISX1zKLTedhdg=",
|
"narHash": "sha256-WGV2hy+VIeQsYXpsLjdr4GvHv5eECMISX1zKLTedhdg=",
|
||||||
@@ -89,7 +36,7 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nixpkgs_3": {
|
"nixpkgs_2": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1770107345,
|
"lastModified": 1770107345,
|
||||||
"narHash": "sha256-tbS0Ebx2PiA1FRW8mt8oejR0qMXmziJmPaU1d4kYY9g=",
|
"narHash": "sha256-tbS0Ebx2PiA1FRW8mt8oejR0qMXmziJmPaU1d4kYY9g=",
|
||||||
@@ -107,14 +54,14 @@
|
|||||||
},
|
},
|
||||||
"root": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"git-hooks": "git-hooks",
|
"lefthook-nix": "lefthook-nix",
|
||||||
"nixpkgs": "nixpkgs_2",
|
"nixpkgs": "nixpkgs",
|
||||||
"treefmt-nix": "treefmt-nix"
|
"treefmt-nix": "treefmt-nix"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"treefmt-nix": {
|
"treefmt-nix": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs": "nixpkgs_3"
|
"nixpkgs": "nixpkgs_2"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1770228511,
|
"lastModified": 1770228511,
|
||||||
|
|||||||
367
flake.nix
367
flake.nix
@@ -1,10 +1,11 @@
|
|||||||
# flake.nix — devshell-lib
|
# flake.nix — repo-lib
|
||||||
{
|
{
|
||||||
description = "Shared devshell boilerplate library";
|
description = "Pure-first repo development platform for Nix flakes";
|
||||||
|
|
||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
|
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
|
||||||
git-hooks.url = "github:cachix/git-hooks.nix";
|
lefthook-nix.url = "github:sudosubin/lefthook.nix";
|
||||||
|
lefthook-nix.inputs.nixpkgs.follows = "nixpkgs";
|
||||||
treefmt-nix.url = "github:numtide/treefmt-nix";
|
treefmt-nix.url = "github:numtide/treefmt-nix";
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -13,334 +14,118 @@
|
|||||||
self,
|
self,
|
||||||
nixpkgs,
|
nixpkgs,
|
||||||
treefmt-nix,
|
treefmt-nix,
|
||||||
git-hooks,
|
lefthook-nix,
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
supportedSystems = [
|
lib = nixpkgs.lib;
|
||||||
"x86_64-linux"
|
repoLib = import ./packages/repo-lib/lib.nix {
|
||||||
"aarch64-linux"
|
inherit nixpkgs treefmt-nix;
|
||||||
"x86_64-darwin"
|
lefthookNix = lefthook-nix;
|
||||||
"aarch64-darwin"
|
releaseScriptPath = ./packages/release/release.sh;
|
||||||
];
|
shellHookTemplatePath = ./packages/repo-lib/shell-hook.sh;
|
||||||
forAllSystems = nixpkgs.lib.genAttrs supportedSystems;
|
};
|
||||||
in
|
supportedSystems = repoLib.systems.default;
|
||||||
{
|
importPkgs = nixpkgsInput: system: import nixpkgsInput { inherit system; };
|
||||||
lib = {
|
|
||||||
|
|
||||||
# ── mkDevShell ───────────────────────────────────────────────────────
|
projectOutputs = repoLib.mkRepo {
|
||||||
mkDevShell =
|
inherit self nixpkgs;
|
||||||
|
src = ./.;
|
||||||
|
config = {
|
||||||
|
release = {
|
||||||
|
steps = [
|
||||||
{
|
{
|
||||||
|
replace = {
|
||||||
|
path = "template/flake.nix";
|
||||||
|
regex = ''^([[:space:]]*repo-lib\.url = ")git\+https://git\.dgren\.dev/eric/nix-flake-lib[^"]*(";)'';
|
||||||
|
replacement = ''\1git+https://git.dgren.dev/eric/nix-flake-lib?ref=refs/tags/$FULL_TAG\2'';
|
||||||
|
};
|
||||||
|
}
|
||||||
|
{
|
||||||
|
replace = {
|
||||||
|
path = "README.md";
|
||||||
|
regex = ''(nix flake new myapp -t ')git\+https://git\.dgren\.dev/eric/nix-flake-lib[^']*(#default' --refresh)'';
|
||||||
|
replacement = ''\1git+https://git.dgren.dev/eric/nix-flake-lib?ref=refs/tags/$FULL_TAG\2'';
|
||||||
|
};
|
||||||
|
}
|
||||||
|
{
|
||||||
|
replace = {
|
||||||
|
path = "README.md";
|
||||||
|
regex = ''^([[:space:]]*inputs\.repo-lib\.url = ")git\+https://git\.dgren\.dev/eric/nix-flake-lib[^"]*(";)'';
|
||||||
|
replacement = ''\1git+https://git.dgren.dev/eric/nix-flake-lib?ref=refs/tags/$FULL_TAG\2'';
|
||||||
|
};
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
perSystem =
|
||||||
|
{
|
||||||
|
pkgs,
|
||||||
system,
|
system,
|
||||||
src ? ./.,
|
...
|
||||||
extraPackages ? [ ],
|
|
||||||
preToolHook ? "",
|
|
||||||
extraShellHook ? "",
|
|
||||||
additionalHooks ? { },
|
|
||||||
tools ? [ ],
|
|
||||||
includeStandardPackages ? true,
|
|
||||||
# tools = list of { name, bin, versionCmd, color? }
|
|
||||||
# e.g. { name = "Bun"; bin = "${pkgs.bun}/bin/bun"; versionCmd = "--version"; color = "YELLOW"; }
|
|
||||||
# preToolHook = shell snippet that runs before the ready banner and tool logs
|
|
||||||
# e.g. install tools outside nixpkgs, export PATH updates, warm caches
|
|
||||||
formatters ? { },
|
|
||||||
# formatters = treefmt-nix programs attrset, merged over { nixfmt.enable = true; }
|
|
||||||
# e.g. { gofmt.enable = true; shfmt.enable = true; }
|
|
||||||
formatterSettings ? { },
|
|
||||||
# formatterSettings = treefmt-nix settings.formatter attrset
|
|
||||||
# e.g. { shfmt.options = [ "-i" "2" "-s" "-w" ]; }
|
|
||||||
features ? { },
|
|
||||||
# features.oxfmt = true → adds pkgs.oxfmt + pkgs.oxlint, enables oxfmt in treefmt
|
|
||||||
}:
|
}:
|
||||||
let
|
|
||||||
pkgs = import nixpkgs { inherit system; };
|
|
||||||
standardPackages = with pkgs; [
|
|
||||||
nixfmt
|
|
||||||
gitlint
|
|
||||||
gitleaks
|
|
||||||
shfmt
|
|
||||||
];
|
|
||||||
selectedStandardPackages = pkgs.lib.optionals includeStandardPackages standardPackages;
|
|
||||||
|
|
||||||
oxfmtEnabled = features.oxfmt or false;
|
|
||||||
oxfmtPackages = pkgs.lib.optionals oxfmtEnabled [
|
|
||||||
pkgs.oxfmt
|
|
||||||
pkgs.oxlint
|
|
||||||
];
|
|
||||||
oxfmtFormatters = pkgs.lib.optionalAttrs oxfmtEnabled {
|
|
||||||
oxfmt.enable = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
treefmtEval = treefmt-nix.lib.evalModule pkgs {
|
|
||||||
projectRootFile = "flake.nix";
|
|
||||||
programs = {
|
|
||||||
nixfmt.enable = true; # always on — every repo has a flake.nix
|
|
||||||
}
|
|
||||||
// oxfmtFormatters
|
|
||||||
// formatters;
|
|
||||||
settings.formatter = { } // formatterSettings;
|
|
||||||
};
|
|
||||||
|
|
||||||
pre-commit-check = git-hooks.lib.${system}.run {
|
|
||||||
inherit src;
|
|
||||||
hooks = {
|
|
||||||
treefmt = {
|
|
||||||
enable = true;
|
|
||||||
entry = "${treefmtEval.config.build.wrapper}/bin/treefmt --ci";
|
|
||||||
pass_filenames = true;
|
|
||||||
};
|
|
||||||
gitlint.enable = true;
|
|
||||||
gitleaks = {
|
|
||||||
enable = true;
|
|
||||||
entry = "${pkgs.gitleaks}/bin/gitleaks protect --staged";
|
|
||||||
pass_filenames = false;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
// additionalHooks;
|
|
||||||
};
|
|
||||||
|
|
||||||
toolNameWidth = builtins.foldl' (
|
|
||||||
maxWidth: t: pkgs.lib.max maxWidth (builtins.stringLength t.name)
|
|
||||||
) 0 tools;
|
|
||||||
toolLabelWidth = toolNameWidth + 1;
|
|
||||||
|
|
||||||
toolBannerScript = pkgs.lib.concatMapStrings (
|
|
||||||
t:
|
|
||||||
let
|
|
||||||
colorVar = "$" + (t.color or "YELLOW");
|
|
||||||
in
|
|
||||||
''
|
|
||||||
if command -v ${t.bin} >/dev/null 2>&1; then
|
|
||||||
version="$(${t.bin} ${t.versionCmd} 2>/dev/null | head -n 1 | sed -E 's/^[[:space:]]+//; s/[[:space:]]+$//')"
|
|
||||||
printf " $CYAN %-${toString toolLabelWidth}s$RESET ${colorVar}%s$RESET\n" "${t.name}:" "$version"
|
|
||||||
fi
|
|
||||||
''
|
|
||||||
) tools;
|
|
||||||
|
|
||||||
in
|
|
||||||
{
|
{
|
||||||
inherit pre-commit-check;
|
|
||||||
|
|
||||||
formatter = treefmtEval.config.build.wrapper;
|
|
||||||
|
|
||||||
shell = pkgs.mkShell {
|
|
||||||
packages = selectedStandardPackages ++ extraPackages ++ oxfmtPackages;
|
|
||||||
|
|
||||||
buildInputs = pre-commit-check.enabledPackages;
|
|
||||||
|
|
||||||
shellHook = ''
|
|
||||||
${pre-commit-check.shellHook}
|
|
||||||
|
|
||||||
if [ -t 1 ]; then
|
|
||||||
command -v tput >/dev/null 2>&1 && tput clear || printf '\033c'
|
|
||||||
fi
|
|
||||||
|
|
||||||
GREEN='\033[1;32m'
|
|
||||||
CYAN='\033[1;36m'
|
|
||||||
YELLOW='\033[1;33m'
|
|
||||||
BLUE='\033[1;34m'
|
|
||||||
RED='\033[1;31m'
|
|
||||||
MAGENTA='\033[1;35m'
|
|
||||||
WHITE='\033[1;37m'
|
|
||||||
GRAY='\033[0;90m'
|
|
||||||
BOLD='\033[1m'
|
|
||||||
UNDERLINE='\033[4m'
|
|
||||||
RESET='\033[0m'
|
|
||||||
|
|
||||||
${preToolHook}
|
|
||||||
|
|
||||||
printf "\n$GREEN 🚀 Dev shell ready$RESET\n\n"
|
|
||||||
${toolBannerScript}
|
|
||||||
printf "\n"
|
|
||||||
|
|
||||||
${extraShellHook}
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
# ── mkRelease ────────────────────────────────────────────────────────
|
|
||||||
mkRelease =
|
|
||||||
{
|
|
||||||
system,
|
|
||||||
# Source of truth is always $ROOT_DIR/VERSION.
|
|
||||||
# Format:
|
|
||||||
# line 1: X.Y.Z
|
|
||||||
# line 2: CHANNEL (stable|alpha|beta|rc|internal|...)
|
|
||||||
# line 3: N (prerelease number, 0 for stable)
|
|
||||||
postVersion ? "",
|
|
||||||
# Shell string — runs after VERSION + release steps are written/run, before git add.
|
|
||||||
# Same env vars available.
|
|
||||||
release ? [ ],
|
|
||||||
# Unified list processed in declaration order:
|
|
||||||
# { file = "path/to/file"; content = ''...$FULL_VERSION...''; } # write file
|
|
||||||
# { run = ''...shell snippet...''; } # run script
|
|
||||||
# Example:
|
|
||||||
# release = [
|
|
||||||
# {
|
|
||||||
# file = "src/version.ts";
|
|
||||||
# content = ''export const APP_VERSION = "$FULL_VERSION" as const;'';
|
|
||||||
# }
|
|
||||||
# {
|
|
||||||
# file = "internal/version/version.go";
|
|
||||||
# content = ''
|
|
||||||
# package version
|
|
||||||
#
|
|
||||||
# const Version = "$FULL_VERSION"
|
|
||||||
# '';
|
|
||||||
# }
|
|
||||||
# {
|
|
||||||
# run = ''
|
|
||||||
# sed -E -i "s#^([[:space:]]*my-lib\\.url = \")github:org/my-lib[^"]*(\";)#\\1github:org/my-lib?ref=$FULL_TAG\\2#" "$ROOT_DIR/flake.nix"
|
|
||||||
# '';
|
|
||||||
# }
|
|
||||||
# ];
|
|
||||||
# Runtime env includes: BASE_VERSION, CHANNEL, PRERELEASE_NUM, FULL_VERSION, FULL_TAG.
|
|
||||||
channels ? [
|
|
||||||
"alpha"
|
|
||||||
"beta"
|
|
||||||
"rc"
|
|
||||||
"internal"
|
|
||||||
],
|
|
||||||
# Valid release channels beyond "stable". Validated at runtime.
|
|
||||||
extraRuntimeInputs ? [ ],
|
|
||||||
# Extra packages available in the release script's PATH.
|
|
||||||
}:
|
|
||||||
let
|
|
||||||
pkgs = import nixpkgs { inherit system; };
|
|
||||||
channelList = pkgs.lib.concatStringsSep " " channels;
|
|
||||||
|
|
||||||
releaseStepsScript = pkgs.lib.concatMapStrings (
|
|
||||||
entry:
|
|
||||||
if entry ? file then
|
|
||||||
''
|
|
||||||
mkdir -p "$(dirname "${entry.file}")"
|
|
||||||
cat > "${entry.file}" << NIXEOF
|
|
||||||
${entry.content}
|
|
||||||
NIXEOF
|
|
||||||
log "Generated version file: ${entry.file}"
|
|
||||||
''
|
|
||||||
else if entry ? run then
|
|
||||||
''
|
|
||||||
${entry.run}
|
|
||||||
''
|
|
||||||
else
|
|
||||||
builtins.throw "release entry must have either 'file' or 'run'"
|
|
||||||
) release;
|
|
||||||
|
|
||||||
script =
|
|
||||||
builtins.replaceStrings
|
|
||||||
[
|
|
||||||
"__CHANNEL_LIST__"
|
|
||||||
"__RELEASE_STEPS__"
|
|
||||||
"__POST_VERSION__"
|
|
||||||
]
|
|
||||||
[
|
|
||||||
channelList
|
|
||||||
releaseStepsScript
|
|
||||||
postVersion
|
|
||||||
]
|
|
||||||
(builtins.readFile ./packages/release/release.sh);
|
|
||||||
in
|
|
||||||
pkgs.writeShellApplication {
|
|
||||||
name = "release";
|
|
||||||
runtimeInputs =
|
|
||||||
with pkgs;
|
|
||||||
[
|
|
||||||
git
|
|
||||||
gnugrep
|
|
||||||
gawk
|
|
||||||
gnused
|
|
||||||
coreutils
|
|
||||||
]
|
|
||||||
++ extraRuntimeInputs;
|
|
||||||
text = script;
|
|
||||||
};
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
# ── packages ────────────────────────────────────────────────────────────
|
|
||||||
packages = forAllSystems (system: {
|
|
||||||
# Expose a no-op release package for the lib repo itself (dogfood)
|
|
||||||
release = self.lib.mkRelease {
|
|
||||||
inherit system;
|
|
||||||
release = [
|
|
||||||
{
|
|
||||||
run = ''
|
|
||||||
sed -E -i "s#^([[:space:]]*devshell-lib\\.url = \")git\\+https://git\\.dgren\\.dev/eric/nix-flake-lib[^\"]*(\";)#\\1git+https://git.dgren.dev/eric/nix-flake-lib?ref=$FULL_TAG\\2#" "$ROOT_DIR/template/flake.nix"
|
|
||||||
log "Updated template/flake.nix devshell-lib ref to $FULL_TAG"
|
|
||||||
|
|
||||||
sed -E -i "s|(nix flake new myapp -t ')git\\+https://git\\.dgren\\.dev/eric/nix-flake-lib[^']*(#default' --refresh)|\\1git+https://git.dgren.dev/eric/nix-flake-lib?ref=$FULL_TAG\\2|" "$ROOT_DIR/README.md"
|
|
||||||
sed -E -i "s#^([[:space:]]*inputs\\.devshell-lib\\.url = \")git\\+https://git\\.dgren\\.dev/eric/nix-flake-lib[^\"]*(\";)#\\1git+https://git.dgren.dev/eric/nix-flake-lib?ref=$FULL_TAG\\2#" "$ROOT_DIR/README.md"
|
|
||||||
log "Updated README.md devshell-lib refs to $FULL_TAG"
|
|
||||||
'';
|
|
||||||
}
|
|
||||||
];
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
# ── devShells ───────────────────────────────────────────────────────────
|
|
||||||
devShells = forAllSystems (
|
|
||||||
system:
|
|
||||||
let
|
|
||||||
pkgs = import nixpkgs { inherit system; };
|
|
||||||
env = self.lib.mkDevShell {
|
|
||||||
inherit system;
|
|
||||||
extraPackages = with pkgs; [
|
|
||||||
self.packages.${system}.release
|
|
||||||
];
|
|
||||||
tools = [
|
tools = [
|
||||||
{
|
(repoLib.tools.fromCommand {
|
||||||
name = "Nix";
|
name = "Nix";
|
||||||
bin = "${pkgs.nix}/bin/nix";
|
command = "nix";
|
||||||
versionCmd = "--version";
|
version = {
|
||||||
color = "YELLOW";
|
args = [ "--version" ];
|
||||||
}
|
group = 1;
|
||||||
];
|
|
||||||
};
|
};
|
||||||
in
|
banner = {
|
||||||
{
|
color = "BLUE";
|
||||||
default = env.shell;
|
icon = "";
|
||||||
}
|
};
|
||||||
);
|
})
|
||||||
|
];
|
||||||
|
|
||||||
# ── checks ──────────────────────────────────────────────────────────────
|
shell.packages = [ self.packages.${system}.release ];
|
||||||
checks = forAllSystems (
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
testChecks = lib.genAttrs supportedSystems (
|
||||||
system:
|
system:
|
||||||
let
|
let
|
||||||
pkgs = import nixpkgs { inherit system; };
|
pkgs = importPkgs nixpkgs system;
|
||||||
env = self.lib.mkDevShell { inherit system; };
|
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
inherit (env) pre-commit-check;
|
|
||||||
release-tests =
|
release-tests =
|
||||||
pkgs.runCommand "release-tests"
|
pkgs.runCommand "release-tests"
|
||||||
{
|
{
|
||||||
nativeBuildInputs = with pkgs; [
|
nativeBuildInputs = with pkgs; [
|
||||||
bash
|
bash
|
||||||
git
|
git
|
||||||
|
nix
|
||||||
gnused
|
gnused
|
||||||
coreutils
|
coreutils
|
||||||
gnugrep
|
gnugrep
|
||||||
|
perl
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
''
|
''
|
||||||
export REPO_LIB_ROOT=${./.}
|
export REPO_LIB_ROOT=${./.}
|
||||||
|
export NIXPKGS_FLAKE_PATH=${nixpkgs}
|
||||||
export HOME="$TMPDIR"
|
export HOME="$TMPDIR"
|
||||||
${pkgs.bash}/bin/bash ${./tests/release.sh}
|
${pkgs.bash}/bin/bash ${./tests/release.sh}
|
||||||
touch "$out"
|
touch "$out"
|
||||||
'';
|
'';
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
in
|
||||||
|
projectOutputs
|
||||||
|
// {
|
||||||
|
lib = repoLib;
|
||||||
|
|
||||||
# ── formatter ───────────────────────────────────────────────────────────
|
|
||||||
formatter = forAllSystems (system: (self.lib.mkDevShell { inherit system; }).formatter);
|
|
||||||
|
|
||||||
# ── templates ───────────────────────────────────────────────────────────
|
|
||||||
templates = {
|
templates = {
|
||||||
default = {
|
default = {
|
||||||
path = ./template;
|
path = ./template;
|
||||||
description = "Product repo using devshell-lib";
|
description = "Product repo using repo-lib";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
checks = lib.genAttrs supportedSystems (
|
||||||
|
system: projectOutputs.checks.${system} // testChecks.${system}
|
||||||
|
);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,57 +1,16 @@
|
|||||||
# release.nix
|
|
||||||
{
|
{
|
||||||
pkgs,
|
nixpkgs,
|
||||||
postVersion ? "",
|
treefmt-nix,
|
||||||
release ? [ ],
|
lefthookNix,
|
||||||
# Unified list, processed in declaration order:
|
releaseScriptPath ? ./release.sh,
|
||||||
# { file = "path/to/file"; content = "..."; } — write file
|
shellHookTemplatePath ? ../repo-lib/shell-hook.sh,
|
||||||
# { run = "shell snippet..."; } — run script
|
|
||||||
channels ? [
|
|
||||||
"alpha"
|
|
||||||
"beta"
|
|
||||||
"rc"
|
|
||||||
"internal"
|
|
||||||
],
|
|
||||||
extraRuntimeInputs ? [ ],
|
|
||||||
}:
|
}:
|
||||||
let
|
import ../repo-lib/lib.nix {
|
||||||
channelList = pkgs.lib.concatStringsSep " " channels;
|
inherit
|
||||||
|
nixpkgs
|
||||||
releaseScript = pkgs.lib.concatMapStrings (
|
treefmt-nix
|
||||||
entry:
|
lefthookNix
|
||||||
if entry ? file then
|
releaseScriptPath
|
||||||
''
|
shellHookTemplatePath
|
||||||
mkdir -p "$(dirname "${entry.file}")"
|
;
|
||||||
cat > "${entry.file}" << NIXEOF
|
|
||||||
${entry.content}
|
|
||||||
NIXEOF
|
|
||||||
log "Generated version file: ${entry.file}"
|
|
||||||
''
|
|
||||||
else if entry ? run then
|
|
||||||
''
|
|
||||||
${entry.run}
|
|
||||||
''
|
|
||||||
else
|
|
||||||
builtins.throw "release entry must have either 'file' or 'run'"
|
|
||||||
) release;
|
|
||||||
|
|
||||||
script =
|
|
||||||
builtins.replaceStrings
|
|
||||||
[ "__CHANNEL_LIST__" "__RELEASE_STEPS__" "__POST_VERSION__" ]
|
|
||||||
[ channelList releaseScript postVersion ]
|
|
||||||
(builtins.readFile ./release.sh);
|
|
||||||
in
|
|
||||||
pkgs.writeShellApplication {
|
|
||||||
name = "release";
|
|
||||||
runtimeInputs =
|
|
||||||
with pkgs;
|
|
||||||
[
|
|
||||||
git
|
|
||||||
gnugrep
|
|
||||||
gawk
|
|
||||||
gnused
|
|
||||||
coreutils
|
|
||||||
]
|
|
||||||
++ extraRuntimeInputs;
|
|
||||||
text = script;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ ROOT_DIR="$(git rev-parse --show-toplevel)"
|
|||||||
GITLINT_FILE="$ROOT_DIR/.gitlint"
|
GITLINT_FILE="$ROOT_DIR/.gitlint"
|
||||||
START_HEAD=""
|
START_HEAD=""
|
||||||
CREATED_TAG=""
|
CREATED_TAG=""
|
||||||
|
VERSION_META_LINES=()
|
||||||
|
VERSION_META_EXPORT_NAMES=()
|
||||||
|
|
||||||
# ── logging ────────────────────────────────────────────────────────────────
|
# ── logging ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -168,6 +170,119 @@ compute_full_version() {
|
|||||||
export BASE_VERSION CHANNEL PRERELEASE_NUM FULL_VERSION FULL_TAG
|
export BASE_VERSION CHANNEL PRERELEASE_NUM FULL_VERSION FULL_TAG
|
||||||
}
|
}
|
||||||
|
|
||||||
|
meta_env_name() {
|
||||||
|
local key="$1"
|
||||||
|
key="${key//[^[:alnum:]]/_}"
|
||||||
|
key="$(printf '%s' "$key" | tr '[:lower:]' '[:upper:]')"
|
||||||
|
printf 'VERSION_META_%s\n' "$key"
|
||||||
|
}
|
||||||
|
|
||||||
|
clear_version_meta_exports() {
|
||||||
|
local export_name
|
||||||
|
for export_name in "${VERSION_META_EXPORT_NAMES[@]:-}"; do
|
||||||
|
unset "$export_name"
|
||||||
|
done
|
||||||
|
VERSION_META_EXPORT_NAMES=()
|
||||||
|
}
|
||||||
|
|
||||||
|
load_version_metadata() {
|
||||||
|
VERSION_META_LINES=()
|
||||||
|
[[ ! -f "$ROOT_DIR/VERSION" ]] && return 0
|
||||||
|
|
||||||
|
while IFS= read -r line || [[ -n $line ]]; do
|
||||||
|
VERSION_META_LINES+=("$line")
|
||||||
|
done < <(tail -n +4 "$ROOT_DIR/VERSION" 2>/dev/null || true)
|
||||||
|
}
|
||||||
|
|
||||||
|
export_version_metadata() {
|
||||||
|
clear_version_meta_exports
|
||||||
|
|
||||||
|
local line key value export_name
|
||||||
|
for line in "${VERSION_META_LINES[@]:-}"; do
|
||||||
|
[[ $line != *=* ]] && continue
|
||||||
|
key="${line%%=*}"
|
||||||
|
value="${line#*=}"
|
||||||
|
[[ -z $key ]] && continue
|
||||||
|
export_name="$(meta_env_name "$key")"
|
||||||
|
printf -v "$export_name" '%s' "$value"
|
||||||
|
export "${export_name?}=$value"
|
||||||
|
VERSION_META_EXPORT_NAMES+=("$export_name")
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
write_version_file() {
|
||||||
|
local channel_to_write="$1"
|
||||||
|
local n_to_write="$2"
|
||||||
|
{
|
||||||
|
printf '%s\n%s\n%s\n' "$BASE_VERSION" "$channel_to_write" "$n_to_write"
|
||||||
|
local line
|
||||||
|
for line in "${VERSION_META_LINES[@]:-}"; do
|
||||||
|
printf '%s\n' "$line"
|
||||||
|
done
|
||||||
|
} >"$ROOT_DIR/VERSION"
|
||||||
|
}
|
||||||
|
|
||||||
|
version_meta_get() {
|
||||||
|
local key="${1-}"
|
||||||
|
local line
|
||||||
|
for line in "${VERSION_META_LINES[@]:-}"; do
|
||||||
|
if [[ $line == "$key="* ]]; then
|
||||||
|
printf '%s\n' "${line#*=}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
version_meta_set() {
|
||||||
|
local key="${1-}"
|
||||||
|
local value="${2-}"
|
||||||
|
[[ -z $key ]] && echo "Error: version_meta_set requires a key" >&2 && exit 1
|
||||||
|
|
||||||
|
local updated=0
|
||||||
|
local index
|
||||||
|
for index in "${!VERSION_META_LINES[@]}"; do
|
||||||
|
if [[ ${VERSION_META_LINES[index]} == "$key="* ]]; then
|
||||||
|
VERSION_META_LINES[index]="$key=$value"
|
||||||
|
updated=1
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ $updated -eq 0 ]]; then
|
||||||
|
VERSION_META_LINES+=("$key=$value")
|
||||||
|
fi
|
||||||
|
|
||||||
|
export_version_metadata
|
||||||
|
version_meta_write
|
||||||
|
}
|
||||||
|
|
||||||
|
version_meta_unset() {
|
||||||
|
local key="${1-}"
|
||||||
|
[[ -z $key ]] && echo "Error: version_meta_unset requires a key" >&2 && exit 1
|
||||||
|
|
||||||
|
local filtered=()
|
||||||
|
local line
|
||||||
|
for line in "${VERSION_META_LINES[@]:-}"; do
|
||||||
|
[[ $line == "$key="* ]] && continue
|
||||||
|
filtered+=("$line")
|
||||||
|
done
|
||||||
|
VERSION_META_LINES=("${filtered[@]}")
|
||||||
|
|
||||||
|
export_version_metadata
|
||||||
|
version_meta_write
|
||||||
|
}
|
||||||
|
|
||||||
|
version_meta_write() {
|
||||||
|
local channel_to_write="$CHANNEL"
|
||||||
|
local n_to_write="${PRERELEASE_NUM:-1}"
|
||||||
|
if [[ $channel_to_write == "stable" || -z $channel_to_write ]]; then
|
||||||
|
channel_to_write="stable"
|
||||||
|
n_to_write="0"
|
||||||
|
fi
|
||||||
|
write_version_file "$channel_to_write" "$n_to_write"
|
||||||
|
}
|
||||||
|
|
||||||
# ── gitlint ────────────────────────────────────────────────────────────────
|
# ── gitlint ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
get_gitlint_title_regex() {
|
get_gitlint_title_regex() {
|
||||||
@@ -205,6 +320,8 @@ run_release_steps() {
|
|||||||
# and never contaminates the stdout of do_read_version.
|
# and never contaminates the stdout of do_read_version.
|
||||||
init_version_file() {
|
init_version_file() {
|
||||||
if [[ -f "$ROOT_DIR/VERSION" ]]; then
|
if [[ -f "$ROOT_DIR/VERSION" ]]; then
|
||||||
|
load_version_metadata
|
||||||
|
export_version_metadata
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -233,11 +350,16 @@ init_version_file() {
|
|||||||
n_to_write="0"
|
n_to_write="0"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
printf '%s\n%s\n%s\n' "$BASE_VERSION" "$channel_to_write" "$n_to_write" >"$ROOT_DIR/VERSION"
|
VERSION_META_LINES=()
|
||||||
|
write_version_file "$channel_to_write" "$n_to_write"
|
||||||
|
export_version_metadata
|
||||||
log "Initialized $ROOT_DIR/VERSION from highest tag: v$highest_tag"
|
log "Initialized $ROOT_DIR/VERSION from highest tag: v$highest_tag"
|
||||||
}
|
}
|
||||||
|
|
||||||
do_read_version() {
|
do_read_version() {
|
||||||
|
load_version_metadata
|
||||||
|
export_version_metadata
|
||||||
|
|
||||||
local base_line channel_line n_line
|
local base_line channel_line n_line
|
||||||
base_line="$(sed -n '1p' "$ROOT_DIR/VERSION" | tr -d '\r')"
|
base_line="$(sed -n '1p' "$ROOT_DIR/VERSION" | tr -d '\r')"
|
||||||
channel_line="$(sed -n '2p' "$ROOT_DIR/VERSION" | tr -d '\r')"
|
channel_line="$(sed -n '2p' "$ROOT_DIR/VERSION" | tr -d '\r')"
|
||||||
@@ -257,7 +379,8 @@ do_write_version() {
|
|||||||
channel_to_write="stable"
|
channel_to_write="stable"
|
||||||
n_to_write="0"
|
n_to_write="0"
|
||||||
fi
|
fi
|
||||||
printf '%s\n%s\n%s\n' "$BASE_VERSION" "$channel_to_write" "$n_to_write" >"$ROOT_DIR/VERSION"
|
write_version_file "$channel_to_write" "$n_to_write"
|
||||||
|
export_version_metadata
|
||||||
}
|
}
|
||||||
|
|
||||||
# ── user-provided hook ─────────────────────────────────────────────────────
|
# ── user-provided hook ─────────────────────────────────────────────────────
|
||||||
|
|||||||
879
packages/repo-lib/lib.nix
Normal file
879
packages/repo-lib/lib.nix
Normal file
@@ -0,0 +1,879 @@
|
|||||||
|
{
|
||||||
|
nixpkgs,
|
||||||
|
treefmt-nix,
|
||||||
|
lefthookNix,
|
||||||
|
releaseScriptPath,
|
||||||
|
shellHookTemplatePath,
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
lib = nixpkgs.lib;
|
||||||
|
|
||||||
|
supportedSystems = [
|
||||||
|
"x86_64-linux"
|
||||||
|
"aarch64-linux"
|
||||||
|
"x86_64-darwin"
|
||||||
|
"aarch64-darwin"
|
||||||
|
];
|
||||||
|
|
||||||
|
defaultReleaseChannels = [
|
||||||
|
"alpha"
|
||||||
|
"beta"
|
||||||
|
"rc"
|
||||||
|
"internal"
|
||||||
|
];
|
||||||
|
|
||||||
|
importPkgs = nixpkgsInput: system: import nixpkgsInput { inherit system; };
|
||||||
|
|
||||||
|
duplicateStrings =
|
||||||
|
names:
|
||||||
|
lib.unique (
|
||||||
|
builtins.filter (
|
||||||
|
name: builtins.length (builtins.filter (candidate: candidate == name) names) > 1
|
||||||
|
) names
|
||||||
|
);
|
||||||
|
|
||||||
|
mergeUniqueAttrs =
|
||||||
|
label: left: right:
|
||||||
|
let
|
||||||
|
overlap = builtins.attrNames (lib.intersectAttrs left right);
|
||||||
|
in
|
||||||
|
if overlap != [ ] then
|
||||||
|
throw "repo-lib: duplicate ${label}: ${lib.concatStringsSep ", " overlap}"
|
||||||
|
else
|
||||||
|
left // right;
|
||||||
|
|
||||||
|
sanitizeName = name: lib.strings.sanitizeDerivationName name;
|
||||||
|
|
||||||
|
defaultShellBanner = {
|
||||||
|
style = "simple";
|
||||||
|
icon = "🚀";
|
||||||
|
title = "Dev shell ready";
|
||||||
|
titleColor = "GREEN";
|
||||||
|
subtitle = "";
|
||||||
|
subtitleColor = "GRAY";
|
||||||
|
borderColor = "BLUE";
|
||||||
|
};
|
||||||
|
|
||||||
|
normalizeShellBanner =
|
||||||
|
rawBanner:
|
||||||
|
let
|
||||||
|
banner = defaultShellBanner // rawBanner;
|
||||||
|
in
|
||||||
|
if
|
||||||
|
!(builtins.elem banner.style [
|
||||||
|
"simple"
|
||||||
|
"pretty"
|
||||||
|
])
|
||||||
|
then
|
||||||
|
throw "repo-lib: config.shell.banner.style must be one of simple or pretty"
|
||||||
|
else
|
||||||
|
banner;
|
||||||
|
|
||||||
|
normalizeStrictTool =
|
||||||
|
pkgs: tool:
|
||||||
|
let
|
||||||
|
version = {
|
||||||
|
args = [ "--version" ];
|
||||||
|
match = null;
|
||||||
|
regex = null;
|
||||||
|
group = 0;
|
||||||
|
line = 1;
|
||||||
|
}
|
||||||
|
// (tool.version or { });
|
||||||
|
banner = {
|
||||||
|
color = "YELLOW";
|
||||||
|
icon = null;
|
||||||
|
iconColor = null;
|
||||||
|
}
|
||||||
|
// (tool.banner or { });
|
||||||
|
executable =
|
||||||
|
if tool ? command && tool.command != null then
|
||||||
|
tool.command
|
||||||
|
else if tool ? exe && tool.exe != null then
|
||||||
|
"${lib.getExe' tool.package tool.exe}"
|
||||||
|
else
|
||||||
|
"${lib.getExe tool.package}";
|
||||||
|
in
|
||||||
|
if !(tool ? command && tool.command != null) && !(tool ? package) then
|
||||||
|
throw "repo-lib: tool '${tool.name or "<unnamed>"}' is missing 'package' or 'command'"
|
||||||
|
else
|
||||||
|
{
|
||||||
|
kind = "strict";
|
||||||
|
inherit executable version banner;
|
||||||
|
name = tool.name;
|
||||||
|
package = tool.package or null;
|
||||||
|
required = tool.required or true;
|
||||||
|
};
|
||||||
|
|
||||||
|
normalizeLegacyTool =
|
||||||
|
pkgs: tool:
|
||||||
|
if tool ? package then
|
||||||
|
normalizeStrictTool pkgs tool
|
||||||
|
else
|
||||||
|
{
|
||||||
|
kind = "legacy";
|
||||||
|
name = tool.name;
|
||||||
|
command = tool.bin;
|
||||||
|
versionCommand = tool.versionCmd or "--version";
|
||||||
|
banner = {
|
||||||
|
color = tool.color or "YELLOW";
|
||||||
|
icon = tool.icon or null;
|
||||||
|
iconColor = tool.iconColor or null;
|
||||||
|
};
|
||||||
|
required = tool.required or false;
|
||||||
|
};
|
||||||
|
|
||||||
|
checkToLefthookConfig =
|
||||||
|
pkgs: name: rawCheck:
|
||||||
|
let
|
||||||
|
check = {
|
||||||
|
stage = "pre-commit";
|
||||||
|
passFilenames = false;
|
||||||
|
runtimeInputs = [ ];
|
||||||
|
}
|
||||||
|
// rawCheck;
|
||||||
|
wrapperName = "repo-lib-check-${sanitizeName name}";
|
||||||
|
wrapper = pkgs.writeShellApplication {
|
||||||
|
name = wrapperName;
|
||||||
|
runtimeInputs = check.runtimeInputs;
|
||||||
|
text = ''
|
||||||
|
set -euo pipefail
|
||||||
|
${check.command}
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
in
|
||||||
|
if !(check ? command) then
|
||||||
|
throw "repo-lib: check '${name}' is missing 'command'"
|
||||||
|
else if
|
||||||
|
!(builtins.elem check.stage [
|
||||||
|
"pre-commit"
|
||||||
|
"pre-push"
|
||||||
|
])
|
||||||
|
then
|
||||||
|
throw "repo-lib: check '${name}' has unsupported stage '${check.stage}'"
|
||||||
|
else
|
||||||
|
lib.setAttrByPath [ check.stage "commands" name ] {
|
||||||
|
run = "${wrapper}/bin/${wrapperName}${hookStageFileArgs check.stage check.passFilenames}";
|
||||||
|
};
|
||||||
|
|
||||||
|
normalizeLefthookConfig =
|
||||||
|
label: raw: if builtins.isAttrs raw then raw else throw "repo-lib: ${label} must be an attrset";
|
||||||
|
|
||||||
|
normalizeHookStage =
|
||||||
|
hookName: stage:
|
||||||
|
if
|
||||||
|
builtins.elem stage [
|
||||||
|
"pre-commit"
|
||||||
|
"pre-push"
|
||||||
|
"commit-msg"
|
||||||
|
]
|
||||||
|
then
|
||||||
|
stage
|
||||||
|
else
|
||||||
|
throw "repo-lib: hook '${hookName}' has unsupported stage '${stage}' for lefthook";
|
||||||
|
|
||||||
|
hookStageFileArgs =
|
||||||
|
stage: passFilenames:
|
||||||
|
if !passFilenames then
|
||||||
|
""
|
||||||
|
else if stage == "pre-commit" then
|
||||||
|
" {staged_files}"
|
||||||
|
else if stage == "pre-push" then
|
||||||
|
" {push_files}"
|
||||||
|
else if stage == "commit-msg" then
|
||||||
|
" {1}"
|
||||||
|
else
|
||||||
|
throw "repo-lib: unsupported lefthook stage '${stage}'";
|
||||||
|
|
||||||
|
hookToLefthookConfig =
|
||||||
|
name: hook:
|
||||||
|
let
|
||||||
|
supportedFields = [
|
||||||
|
"description"
|
||||||
|
"enable"
|
||||||
|
"entry"
|
||||||
|
"name"
|
||||||
|
"package"
|
||||||
|
"pass_filenames"
|
||||||
|
"stages"
|
||||||
|
];
|
||||||
|
unsupportedFields = builtins.filter (field: !(builtins.elem field supportedFields)) (
|
||||||
|
builtins.attrNames hook
|
||||||
|
);
|
||||||
|
stages = builtins.map (stage: normalizeHookStage name stage) (hook.stages or [ "pre-commit" ]);
|
||||||
|
passFilenames = hook.pass_filenames or false;
|
||||||
|
in
|
||||||
|
if unsupportedFields != [ ] then
|
||||||
|
throw ''
|
||||||
|
repo-lib: hook '${name}' uses unsupported fields for lefthook: ${lib.concatStringsSep ", " unsupportedFields}
|
||||||
|
''
|
||||||
|
else if !(hook ? entry) then
|
||||||
|
throw "repo-lib: hook '${name}' is missing 'entry'"
|
||||||
|
else
|
||||||
|
lib.foldl' lib.recursiveUpdate { } (
|
||||||
|
builtins.map (
|
||||||
|
stage:
|
||||||
|
lib.setAttrByPath [ stage "commands" name ] {
|
||||||
|
run = "${hook.entry}${hookStageFileArgs stage passFilenames}";
|
||||||
|
}
|
||||||
|
) stages
|
||||||
|
);
|
||||||
|
|
||||||
|
parallelHookStageConfig =
|
||||||
|
stage:
|
||||||
|
if
|
||||||
|
builtins.elem stage [
|
||||||
|
"pre-commit"
|
||||||
|
"pre-push"
|
||||||
|
]
|
||||||
|
then
|
||||||
|
lib.setAttrByPath [ stage "parallel" ] true
|
||||||
|
else
|
||||||
|
{ };
|
||||||
|
|
||||||
|
normalizeReleaseStep =
|
||||||
|
step:
|
||||||
|
if step ? writeFile then
|
||||||
|
{
|
||||||
|
kind = "writeFile";
|
||||||
|
path = step.writeFile.path;
|
||||||
|
text = step.writeFile.text;
|
||||||
|
runtimeInputs = [ ];
|
||||||
|
}
|
||||||
|
else if step ? replace then
|
||||||
|
{
|
||||||
|
kind = "replace";
|
||||||
|
path = step.replace.path;
|
||||||
|
regex = step.replace.regex;
|
||||||
|
replacement = step.replace.replacement;
|
||||||
|
runtimeInputs = [ ];
|
||||||
|
}
|
||||||
|
else if step ? run && builtins.isAttrs step.run then
|
||||||
|
{
|
||||||
|
kind = "run";
|
||||||
|
script = step.run.script;
|
||||||
|
runtimeInputs = step.run.runtimeInputs or [ ];
|
||||||
|
}
|
||||||
|
else if step ? run then
|
||||||
|
{
|
||||||
|
kind = "run";
|
||||||
|
script = step.run;
|
||||||
|
runtimeInputs = [ ];
|
||||||
|
}
|
||||||
|
else if step ? file then
|
||||||
|
{
|
||||||
|
kind = "writeFile";
|
||||||
|
path = step.file;
|
||||||
|
text = step.content;
|
||||||
|
runtimeInputs = [ ];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
throw "repo-lib: release step must contain one of writeFile, replace, or run";
|
||||||
|
|
||||||
|
releaseStepScript =
|
||||||
|
step:
|
||||||
|
if step.kind == "writeFile" then
|
||||||
|
''
|
||||||
|
target_path="$ROOT_DIR/${step.path}"
|
||||||
|
mkdir -p "$(dirname "$target_path")"
|
||||||
|
cat >"$target_path" << NIXEOF
|
||||||
|
${step.text}
|
||||||
|
NIXEOF
|
||||||
|
log "Generated version file: ${step.path}"
|
||||||
|
''
|
||||||
|
else if step.kind == "replace" then
|
||||||
|
''
|
||||||
|
target_path="$ROOT_DIR/${step.path}"
|
||||||
|
REPO_LIB_STEP_REGEX=$(cat <<'NIXEOF'
|
||||||
|
${step.regex}
|
||||||
|
NIXEOF
|
||||||
|
)
|
||||||
|
REPO_LIB_STEP_REPLACEMENT=$(cat <<NIXEOF
|
||||||
|
${step.replacement}
|
||||||
|
NIXEOF
|
||||||
|
)
|
||||||
|
export REPO_LIB_STEP_REGEX REPO_LIB_STEP_REPLACEMENT
|
||||||
|
perl - "$target_path" <<'REPO_LIB_PERL_REPLACE'
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
my $path = shift @ARGV;
|
||||||
|
my $regex_src = $ENV{"REPO_LIB_STEP_REGEX"} // q{};
|
||||||
|
my $template = $ENV{"REPO_LIB_STEP_REPLACEMENT"} // q{};
|
||||||
|
|
||||||
|
open my $in, q{<}, $path or die "failed to open $path: $!";
|
||||||
|
local $/ = undef;
|
||||||
|
my $content = <$in>;
|
||||||
|
close $in;
|
||||||
|
|
||||||
|
my $regex = qr/$regex_src/ms;
|
||||||
|
$content =~ s{$regex}{
|
||||||
|
my @cap = map { defined $_ ? $_ : q{} } ($1, $2, $3, $4, $5, $6, $7, $8, $9);
|
||||||
|
my $result = $template;
|
||||||
|
$result =~ s{\\([1-9])}{$cap[$1 - 1]}ge;
|
||||||
|
$result;
|
||||||
|
}gems;
|
||||||
|
|
||||||
|
open my $out, q{>}, $path or die "failed to open $path for write: $!";
|
||||||
|
print {$out} $content;
|
||||||
|
close $out;
|
||||||
|
REPO_LIB_PERL_REPLACE
|
||||||
|
log "Updated ${step.path}"
|
||||||
|
''
|
||||||
|
else
|
||||||
|
''
|
||||||
|
${step.script}
|
||||||
|
'';
|
||||||
|
|
||||||
|
normalizeReleaseConfig =
|
||||||
|
raw:
|
||||||
|
let
|
||||||
|
hasLegacySteps = raw ? release;
|
||||||
|
hasStructuredSteps = raw ? steps;
|
||||||
|
steps =
|
||||||
|
if hasLegacySteps && hasStructuredSteps then
|
||||||
|
throw "repo-lib: pass either 'release' or 'steps' to mkRelease, not both"
|
||||||
|
else if hasStructuredSteps then
|
||||||
|
builtins.map normalizeReleaseStep raw.steps
|
||||||
|
else if hasLegacySteps then
|
||||||
|
builtins.map normalizeReleaseStep raw.release
|
||||||
|
else
|
||||||
|
[ ];
|
||||||
|
in
|
||||||
|
{
|
||||||
|
postVersion = raw.postVersion or "";
|
||||||
|
channels = raw.channels or defaultReleaseChannels;
|
||||||
|
runtimeInputs = (raw.runtimeInputs or [ ]) ++ (raw.extraRuntimeInputs or [ ]);
|
||||||
|
steps = steps;
|
||||||
|
};
|
||||||
|
|
||||||
|
buildShellHook =
|
||||||
|
{
|
||||||
|
hooksShellHook,
|
||||||
|
shellEnvScript,
|
||||||
|
bootstrap,
|
||||||
|
shellBannerScript,
|
||||||
|
extraShellText,
|
||||||
|
toolLabelWidth,
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
template = builtins.readFile shellHookTemplatePath;
|
||||||
|
in
|
||||||
|
builtins.replaceStrings
|
||||||
|
[
|
||||||
|
"@HOOKS_SHELL_HOOK@"
|
||||||
|
"@TOOL_LABEL_WIDTH@"
|
||||||
|
"@SHELL_ENV_SCRIPT@"
|
||||||
|
"@BOOTSTRAP@"
|
||||||
|
"@SHELL_BANNER_SCRIPT@"
|
||||||
|
"@EXTRA_SHELL_TEXT@"
|
||||||
|
]
|
||||||
|
[
|
||||||
|
hooksShellHook
|
||||||
|
(toString toolLabelWidth)
|
||||||
|
shellEnvScript
|
||||||
|
bootstrap
|
||||||
|
shellBannerScript
|
||||||
|
extraShellText
|
||||||
|
]
|
||||||
|
template;
|
||||||
|
|
||||||
|
buildShellArtifacts =
|
||||||
|
{
|
||||||
|
pkgs,
|
||||||
|
system,
|
||||||
|
src,
|
||||||
|
includeStandardPackages ? true,
|
||||||
|
formatting,
|
||||||
|
tools ? [ ],
|
||||||
|
shellConfig ? {
|
||||||
|
env = { };
|
||||||
|
extraShellText = "";
|
||||||
|
bootstrap = "";
|
||||||
|
banner = defaultShellBanner;
|
||||||
|
},
|
||||||
|
checkSpecs ? { },
|
||||||
|
rawHookEntries ? { },
|
||||||
|
lefthookConfig ? { },
|
||||||
|
extraPackages ? [ ],
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
standardPackages = with pkgs; [
|
||||||
|
nixfmt
|
||||||
|
gitlint
|
||||||
|
gitleaks
|
||||||
|
shfmt
|
||||||
|
];
|
||||||
|
toolPackages = lib.filter (pkg: pkg != null) (builtins.map (tool: tool.package or null) tools);
|
||||||
|
selectedStandardPackages = lib.optionals includeStandardPackages standardPackages;
|
||||||
|
|
||||||
|
treefmtEval = treefmt-nix.lib.evalModule pkgs {
|
||||||
|
projectRootFile = "flake.nix";
|
||||||
|
programs = {
|
||||||
|
nixfmt.enable = true;
|
||||||
|
}
|
||||||
|
// formatting.programs;
|
||||||
|
settings.formatter = { } // formatting.settings;
|
||||||
|
};
|
||||||
|
treefmtWrapper = treefmtEval.config.build.wrapper;
|
||||||
|
lefthookBinWrapper = pkgs.writeShellScript "lefthook-dumb-term" ''
|
||||||
|
exec env TERM=dumb ${lib.getExe pkgs.lefthook} "$@"
|
||||||
|
'';
|
||||||
|
|
||||||
|
normalizedLefthookConfig = normalizeLefthookConfig "lefthook config" lefthookConfig;
|
||||||
|
lefthookCheck = lefthookNix.lib.${system}.run {
|
||||||
|
inherit src;
|
||||||
|
config = lib.foldl' lib.recursiveUpdate { } (
|
||||||
|
[
|
||||||
|
{
|
||||||
|
output = [
|
||||||
|
"failure"
|
||||||
|
"summary"
|
||||||
|
];
|
||||||
|
}
|
||||||
|
(parallelHookStageConfig "pre-commit")
|
||||||
|
(parallelHookStageConfig "pre-push")
|
||||||
|
(lib.setAttrByPath [ "pre-commit" "commands" "treefmt" ] {
|
||||||
|
run = "${treefmtWrapper}/bin/treefmt --no-cache {staged_files}";
|
||||||
|
stage_fixed = true;
|
||||||
|
})
|
||||||
|
(lib.setAttrByPath [ "pre-commit" "commands" "gitleaks" ] {
|
||||||
|
run = "${pkgs.gitleaks}/bin/gitleaks protect --staged";
|
||||||
|
})
|
||||||
|
(lib.setAttrByPath [ "commit-msg" "commands" "gitlint" ] {
|
||||||
|
run = "${pkgs.gitlint}/bin/gitlint --staged --msg-filename {1}";
|
||||||
|
})
|
||||||
|
]
|
||||||
|
++ lib.mapAttrsToList (name: check: checkToLefthookConfig pkgs name check) checkSpecs
|
||||||
|
++ lib.mapAttrsToList hookToLefthookConfig rawHookEntries
|
||||||
|
++ [ normalizedLefthookConfig ]
|
||||||
|
);
|
||||||
|
};
|
||||||
|
selectedCheckOutputs = {
|
||||||
|
formatting-check = treefmtEval.config.build.check src;
|
||||||
|
hook-check = lefthookCheck;
|
||||||
|
lefthook-check = lefthookCheck;
|
||||||
|
};
|
||||||
|
|
||||||
|
toolNames = builtins.map (tool: tool.name) tools;
|
||||||
|
toolNameWidth =
|
||||||
|
if toolNames == [ ] then
|
||||||
|
0
|
||||||
|
else
|
||||||
|
builtins.foldl' (maxWidth: name: lib.max maxWidth (builtins.stringLength name)) 0 toolNames;
|
||||||
|
toolLabelWidth = toolNameWidth + 1;
|
||||||
|
|
||||||
|
shellEnvScript = lib.concatStringsSep "\n" (
|
||||||
|
lib.mapAttrsToList (
|
||||||
|
name: value: "export ${name}=${lib.escapeShellArg (toString value)}"
|
||||||
|
) shellConfig.env
|
||||||
|
);
|
||||||
|
|
||||||
|
banner = normalizeShellBanner (shellConfig.banner or { });
|
||||||
|
|
||||||
|
shellBannerScript =
|
||||||
|
if banner.style == "pretty" then
|
||||||
|
''
|
||||||
|
repo_lib_print_pretty_header \
|
||||||
|
${lib.escapeShellArg banner.borderColor} \
|
||||||
|
${lib.escapeShellArg banner.titleColor} \
|
||||||
|
${lib.escapeShellArg banner.icon} \
|
||||||
|
${lib.escapeShellArg banner.title} \
|
||||||
|
${lib.escapeShellArg banner.subtitleColor} \
|
||||||
|
${lib.escapeShellArg banner.subtitle}
|
||||||
|
''
|
||||||
|
+ lib.concatMapStrings (
|
||||||
|
tool:
|
||||||
|
if tool.kind == "strict" then
|
||||||
|
''
|
||||||
|
repo_lib_print_pretty_tool \
|
||||||
|
${lib.escapeShellArg banner.borderColor} \
|
||||||
|
${lib.escapeShellArg tool.name} \
|
||||||
|
${lib.escapeShellArg tool.banner.color} \
|
||||||
|
${lib.escapeShellArg (if tool.banner.icon == null then "" else tool.banner.icon)} \
|
||||||
|
${lib.escapeShellArg (if tool.banner.iconColor == null then "" else tool.banner.iconColor)} \
|
||||||
|
${lib.escapeShellArg (if tool.required then "1" else "0")} \
|
||||||
|
${lib.escapeShellArg (toString tool.version.line)} \
|
||||||
|
${lib.escapeShellArg (toString tool.version.group)} \
|
||||||
|
${lib.escapeShellArg (if tool.version.regex == null then "" else tool.version.regex)} \
|
||||||
|
${lib.escapeShellArg (if tool.version.match == null then "" else tool.version.match)} \
|
||||||
|
${lib.escapeShellArg tool.executable} \
|
||||||
|
${lib.escapeShellArgs tool.version.args}
|
||||||
|
''
|
||||||
|
else
|
||||||
|
''
|
||||||
|
repo_lib_print_pretty_legacy_tool \
|
||||||
|
${lib.escapeShellArg banner.borderColor} \
|
||||||
|
${lib.escapeShellArg tool.name} \
|
||||||
|
${lib.escapeShellArg tool.banner.color} \
|
||||||
|
${lib.escapeShellArg (if tool.banner.icon == null then "" else tool.banner.icon)} \
|
||||||
|
${lib.escapeShellArg (if tool.banner.iconColor == null then "" else tool.banner.iconColor)} \
|
||||||
|
${lib.escapeShellArg (if tool.required then "1" else "0")} \
|
||||||
|
${lib.escapeShellArg tool.command} \
|
||||||
|
${lib.escapeShellArg tool.versionCommand}
|
||||||
|
''
|
||||||
|
) tools
|
||||||
|
+ ''
|
||||||
|
repo_lib_print_pretty_footer \
|
||||||
|
${lib.escapeShellArg banner.borderColor}
|
||||||
|
''
|
||||||
|
else
|
||||||
|
''
|
||||||
|
repo_lib_print_simple_header \
|
||||||
|
${lib.escapeShellArg banner.titleColor} \
|
||||||
|
${lib.escapeShellArg banner.icon} \
|
||||||
|
${lib.escapeShellArg banner.title} \
|
||||||
|
${lib.escapeShellArg banner.subtitleColor} \
|
||||||
|
${lib.escapeShellArg banner.subtitle}
|
||||||
|
''
|
||||||
|
+ lib.concatMapStrings (
|
||||||
|
tool:
|
||||||
|
if tool.kind == "strict" then
|
||||||
|
''
|
||||||
|
repo_lib_print_simple_tool \
|
||||||
|
${lib.escapeShellArg tool.name} \
|
||||||
|
${lib.escapeShellArg tool.banner.color} \
|
||||||
|
${lib.escapeShellArg (if tool.banner.icon == null then "" else tool.banner.icon)} \
|
||||||
|
${lib.escapeShellArg (if tool.banner.iconColor == null then "" else tool.banner.iconColor)} \
|
||||||
|
${lib.escapeShellArg (if tool.required then "1" else "0")} \
|
||||||
|
${lib.escapeShellArg (toString tool.version.line)} \
|
||||||
|
${lib.escapeShellArg (toString tool.version.group)} \
|
||||||
|
${lib.escapeShellArg (if tool.version.regex == null then "" else tool.version.regex)} \
|
||||||
|
${lib.escapeShellArg (if tool.version.match == null then "" else tool.version.match)} \
|
||||||
|
${lib.escapeShellArg tool.executable} \
|
||||||
|
${lib.escapeShellArgs tool.version.args}
|
||||||
|
''
|
||||||
|
else
|
||||||
|
''
|
||||||
|
repo_lib_print_simple_legacy_tool \
|
||||||
|
${lib.escapeShellArg tool.name} \
|
||||||
|
${lib.escapeShellArg tool.banner.color} \
|
||||||
|
${lib.escapeShellArg (if tool.banner.icon == null then "" else tool.banner.icon)} \
|
||||||
|
${lib.escapeShellArg (if tool.banner.iconColor == null then "" else tool.banner.iconColor)} \
|
||||||
|
${lib.escapeShellArg (if tool.required then "1" else "0")} \
|
||||||
|
${lib.escapeShellArg tool.command} \
|
||||||
|
${lib.escapeShellArg tool.versionCommand}
|
||||||
|
''
|
||||||
|
) tools
|
||||||
|
+ ''
|
||||||
|
printf "\n"
|
||||||
|
'';
|
||||||
|
in
|
||||||
|
{
|
||||||
|
checks = selectedCheckOutputs;
|
||||||
|
formatter = treefmtWrapper;
|
||||||
|
shell = pkgs.mkShell {
|
||||||
|
LEFTHOOK_BIN = builtins.toString lefthookBinWrapper;
|
||||||
|
packages = lib.unique (
|
||||||
|
selectedStandardPackages
|
||||||
|
++ extraPackages
|
||||||
|
++ toolPackages
|
||||||
|
++ [
|
||||||
|
pkgs.lefthook
|
||||||
|
treefmtWrapper
|
||||||
|
]
|
||||||
|
);
|
||||||
|
shellHook = buildShellHook {
|
||||||
|
hooksShellHook = lefthookCheck.shellHook;
|
||||||
|
inherit toolLabelWidth shellEnvScript shellBannerScript;
|
||||||
|
bootstrap = shellConfig.bootstrap;
|
||||||
|
extraShellText = shellConfig.extraShellText;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// selectedCheckOutputs;
|
||||||
|
in
|
||||||
|
rec {
|
||||||
|
systems = {
|
||||||
|
default = supportedSystems;
|
||||||
|
};
|
||||||
|
|
||||||
|
tools = rec {
|
||||||
|
fromPackage =
|
||||||
|
{
|
||||||
|
name,
|
||||||
|
package,
|
||||||
|
exe ? null,
|
||||||
|
version ? { },
|
||||||
|
banner ? { },
|
||||||
|
required ? true,
|
||||||
|
}:
|
||||||
|
{
|
||||||
|
inherit
|
||||||
|
name
|
||||||
|
package
|
||||||
|
exe
|
||||||
|
version
|
||||||
|
banner
|
||||||
|
required
|
||||||
|
;
|
||||||
|
};
|
||||||
|
|
||||||
|
fromCommand =
|
||||||
|
{
|
||||||
|
name,
|
||||||
|
command,
|
||||||
|
version ? { },
|
||||||
|
banner ? { },
|
||||||
|
required ? true,
|
||||||
|
}:
|
||||||
|
{
|
||||||
|
inherit
|
||||||
|
name
|
||||||
|
command
|
||||||
|
version
|
||||||
|
banner
|
||||||
|
required
|
||||||
|
;
|
||||||
|
};
|
||||||
|
|
||||||
|
simple =
|
||||||
|
name: package: args:
|
||||||
|
fromPackage {
|
||||||
|
inherit name package;
|
||||||
|
version.args = args;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
normalizeRepoConfig =
|
||||||
|
rawConfig:
|
||||||
|
let
|
||||||
|
merged = lib.recursiveUpdate {
|
||||||
|
includeStandardPackages = true;
|
||||||
|
shell = {
|
||||||
|
env = { };
|
||||||
|
extraShellText = "";
|
||||||
|
allowImpureBootstrap = false;
|
||||||
|
bootstrap = "";
|
||||||
|
banner = { };
|
||||||
|
};
|
||||||
|
formatting = {
|
||||||
|
programs = { };
|
||||||
|
settings = { };
|
||||||
|
};
|
||||||
|
checks = { };
|
||||||
|
lefthook = { };
|
||||||
|
release = null;
|
||||||
|
} rawConfig;
|
||||||
|
release =
|
||||||
|
if merged.release == null then
|
||||||
|
null
|
||||||
|
else
|
||||||
|
{
|
||||||
|
channels = defaultReleaseChannels;
|
||||||
|
steps = [ ];
|
||||||
|
postVersion = "";
|
||||||
|
runtimeInputs = [ ];
|
||||||
|
}
|
||||||
|
// merged.release;
|
||||||
|
in
|
||||||
|
if merged.shell.bootstrap != "" && !merged.shell.allowImpureBootstrap then
|
||||||
|
throw "repo-lib: config.shell.bootstrap requires config.shell.allowImpureBootstrap = true"
|
||||||
|
else
|
||||||
|
merged
|
||||||
|
// {
|
||||||
|
inherit release;
|
||||||
|
shell = merged.shell // {
|
||||||
|
banner = normalizeShellBanner merged.shell.banner;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
mkDevShell =
|
||||||
|
{
|
||||||
|
system,
|
||||||
|
src ? ./.,
|
||||||
|
nixpkgsInput ? nixpkgs,
|
||||||
|
extraPackages ? [ ],
|
||||||
|
preToolHook ? "",
|
||||||
|
extraShellHook ? "",
|
||||||
|
additionalHooks ? { },
|
||||||
|
lefthook ? { },
|
||||||
|
tools ? [ ],
|
||||||
|
includeStandardPackages ? true,
|
||||||
|
formatters ? { },
|
||||||
|
formatterSettings ? { },
|
||||||
|
features ? { },
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
pkgs = importPkgs nixpkgsInput system;
|
||||||
|
oxfmtEnabled = features.oxfmt or false;
|
||||||
|
legacyTools = builtins.map (tool: normalizeLegacyTool pkgs tool) tools;
|
||||||
|
duplicateToolNames = duplicateStrings (builtins.map (tool: tool.name) legacyTools);
|
||||||
|
normalizedFormatting = {
|
||||||
|
programs =
|
||||||
|
(lib.optionalAttrs oxfmtEnabled {
|
||||||
|
oxfmt.enable = true;
|
||||||
|
})
|
||||||
|
// formatters;
|
||||||
|
settings = formatterSettings;
|
||||||
|
};
|
||||||
|
shellConfig = {
|
||||||
|
env = { };
|
||||||
|
extraShellText = extraShellHook;
|
||||||
|
allowImpureBootstrap = true;
|
||||||
|
bootstrap = preToolHook;
|
||||||
|
banner = defaultShellBanner;
|
||||||
|
};
|
||||||
|
in
|
||||||
|
if duplicateToolNames != [ ] then
|
||||||
|
throw "repo-lib: duplicate tool names: ${lib.concatStringsSep ", " duplicateToolNames}"
|
||||||
|
else
|
||||||
|
buildShellArtifacts {
|
||||||
|
inherit
|
||||||
|
pkgs
|
||||||
|
system
|
||||||
|
src
|
||||||
|
includeStandardPackages
|
||||||
|
;
|
||||||
|
formatting = normalizedFormatting;
|
||||||
|
rawHookEntries = additionalHooks;
|
||||||
|
lefthookConfig = lefthook;
|
||||||
|
shellConfig = shellConfig;
|
||||||
|
tools = legacyTools;
|
||||||
|
extraPackages =
|
||||||
|
extraPackages
|
||||||
|
++ lib.optionals oxfmtEnabled [
|
||||||
|
pkgs.oxfmt
|
||||||
|
pkgs.oxlint
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
mkRelease =
|
||||||
|
{
|
||||||
|
system,
|
||||||
|
nixpkgsInput ? nixpkgs,
|
||||||
|
...
|
||||||
|
}@rawArgs:
|
||||||
|
let
|
||||||
|
pkgs = importPkgs nixpkgsInput system;
|
||||||
|
release = normalizeReleaseConfig rawArgs;
|
||||||
|
channelList = lib.concatStringsSep " " release.channels;
|
||||||
|
releaseStepsScript = lib.concatMapStrings releaseStepScript release.steps;
|
||||||
|
script =
|
||||||
|
builtins.replaceStrings
|
||||||
|
[
|
||||||
|
"__CHANNEL_LIST__"
|
||||||
|
"__RELEASE_STEPS__"
|
||||||
|
"__POST_VERSION__"
|
||||||
|
]
|
||||||
|
[
|
||||||
|
channelList
|
||||||
|
releaseStepsScript
|
||||||
|
release.postVersion
|
||||||
|
]
|
||||||
|
(builtins.readFile releaseScriptPath);
|
||||||
|
in
|
||||||
|
pkgs.writeShellApplication {
|
||||||
|
name = "release";
|
||||||
|
runtimeInputs =
|
||||||
|
with pkgs;
|
||||||
|
[
|
||||||
|
git
|
||||||
|
gnugrep
|
||||||
|
gawk
|
||||||
|
gnused
|
||||||
|
coreutils
|
||||||
|
perl
|
||||||
|
]
|
||||||
|
++ release.runtimeInputs
|
||||||
|
++ lib.concatMap (step: step.runtimeInputs or [ ]) release.steps;
|
||||||
|
text = script;
|
||||||
|
};
|
||||||
|
|
||||||
|
mkRepo =
|
||||||
|
{
|
||||||
|
self,
|
||||||
|
nixpkgs,
|
||||||
|
src ? ./.,
|
||||||
|
systems ? supportedSystems,
|
||||||
|
config ? { },
|
||||||
|
perSystem ? (
|
||||||
|
{
|
||||||
|
pkgs,
|
||||||
|
system,
|
||||||
|
lib,
|
||||||
|
config,
|
||||||
|
}:
|
||||||
|
{ }
|
||||||
|
),
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
normalizedConfig = normalizeRepoConfig config;
|
||||||
|
systemResults = lib.genAttrs systems (
|
||||||
|
system:
|
||||||
|
let
|
||||||
|
pkgs = importPkgs nixpkgs system;
|
||||||
|
perSystemResult = {
|
||||||
|
tools = [ ];
|
||||||
|
shell = { };
|
||||||
|
checks = { };
|
||||||
|
lefthook = { };
|
||||||
|
packages = { };
|
||||||
|
apps = { };
|
||||||
|
}
|
||||||
|
// perSystem {
|
||||||
|
inherit pkgs system;
|
||||||
|
lib = nixpkgs.lib;
|
||||||
|
config = normalizedConfig;
|
||||||
|
};
|
||||||
|
|
||||||
|
strictTools = builtins.map (tool: normalizeStrictTool pkgs tool) perSystemResult.tools;
|
||||||
|
duplicateToolNames = duplicateStrings (builtins.map (tool: tool.name) strictTools);
|
||||||
|
mergedChecks = mergeUniqueAttrs "check" normalizedConfig.checks perSystemResult.checks;
|
||||||
|
mergedLefthookConfig =
|
||||||
|
lib.recursiveUpdate (normalizeLefthookConfig "config.lefthook" normalizedConfig.lefthook)
|
||||||
|
(normalizeLefthookConfig "perSystem.lefthook" (perSystemResult.lefthook or { }));
|
||||||
|
shellConfig = lib.recursiveUpdate normalizedConfig.shell (perSystemResult.shell or { });
|
||||||
|
env =
|
||||||
|
if duplicateToolNames != [ ] then
|
||||||
|
throw "repo-lib: duplicate tool names: ${lib.concatStringsSep ", " duplicateToolNames}"
|
||||||
|
else
|
||||||
|
buildShellArtifacts {
|
||||||
|
inherit
|
||||||
|
pkgs
|
||||||
|
system
|
||||||
|
src
|
||||||
|
;
|
||||||
|
includeStandardPackages = normalizedConfig.includeStandardPackages;
|
||||||
|
formatting = normalizedConfig.formatting;
|
||||||
|
tools = strictTools;
|
||||||
|
checkSpecs = mergedChecks;
|
||||||
|
lefthookConfig = mergedLefthookConfig;
|
||||||
|
shellConfig = shellConfig;
|
||||||
|
extraPackages = perSystemResult.shell.packages or [ ];
|
||||||
|
};
|
||||||
|
|
||||||
|
releasePackages =
|
||||||
|
if normalizedConfig.release == null then
|
||||||
|
{ }
|
||||||
|
else
|
||||||
|
{
|
||||||
|
release = mkRelease {
|
||||||
|
inherit system;
|
||||||
|
nixpkgsInput = nixpkgs;
|
||||||
|
channels = normalizedConfig.release.channels;
|
||||||
|
steps = normalizedConfig.release.steps;
|
||||||
|
postVersion = normalizedConfig.release.postVersion;
|
||||||
|
runtimeInputs = normalizedConfig.release.runtimeInputs;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
inherit env;
|
||||||
|
packages = mergeUniqueAttrs "package" releasePackages perSystemResult.packages;
|
||||||
|
apps = perSystemResult.apps;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
in
|
||||||
|
{
|
||||||
|
devShells = lib.genAttrs systems (system: {
|
||||||
|
default = systemResults.${system}.env.shell;
|
||||||
|
});
|
||||||
|
|
||||||
|
checks = lib.genAttrs systems (system: systemResults.${system}.env.checks);
|
||||||
|
|
||||||
|
formatter = lib.genAttrs systems (system: systemResults.${system}.env.formatter);
|
||||||
|
packages = lib.genAttrs systems (system: systemResults.${system}.packages);
|
||||||
|
apps = lib.genAttrs systems (system: systemResults.${system}.apps);
|
||||||
|
};
|
||||||
|
}
|
||||||
341
packages/repo-lib/shell-hook.sh
Normal file
341
packages/repo-lib/shell-hook.sh
Normal file
@@ -0,0 +1,341 @@
|
|||||||
|
@HOOKS_SHELL_HOOK@
|
||||||
|
|
||||||
|
if [ -t 1 ]; then
|
||||||
|
command -v tput >/dev/null 2>&1 && tput clear || printf '\033c'
|
||||||
|
fi
|
||||||
|
|
||||||
|
GREEN=$'\033[1;32m'
|
||||||
|
CYAN=$'\033[1;36m'
|
||||||
|
YELLOW=$'\033[1;33m'
|
||||||
|
BLUE=$'\033[1;34m'
|
||||||
|
RED=$'\033[1;31m'
|
||||||
|
MAGENTA=$'\033[1;35m'
|
||||||
|
WHITE=$'\033[1;37m'
|
||||||
|
GRAY=$'\033[0;90m'
|
||||||
|
BOLD=$'\033[1m'
|
||||||
|
UNDERLINE=$'\033[4m'
|
||||||
|
RESET=$'\033[0m'
|
||||||
|
|
||||||
|
REPO_LIB_TOOL_VERSION=""
|
||||||
|
REPO_LIB_TOOL_ERROR=""
|
||||||
|
|
||||||
|
repo_lib_capture_tool() {
|
||||||
|
local required="$1"
|
||||||
|
local line_no="$2"
|
||||||
|
local group_no="$3"
|
||||||
|
local regex="$4"
|
||||||
|
local match_regex="$5"
|
||||||
|
local executable="$6"
|
||||||
|
shift 6
|
||||||
|
|
||||||
|
local output=""
|
||||||
|
local selected=""
|
||||||
|
local version=""
|
||||||
|
|
||||||
|
REPO_LIB_TOOL_VERSION=""
|
||||||
|
REPO_LIB_TOOL_ERROR=""
|
||||||
|
|
||||||
|
if ! output="$("$executable" "$@" 2>&1)"; then
|
||||||
|
REPO_LIB_TOOL_ERROR="probe failed"
|
||||||
|
printf "%s\n" "$output" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$match_regex" ]; then
|
||||||
|
selected="$(printf '%s\n' "$output" | grep -E -m 1 "$match_regex" || true)"
|
||||||
|
else
|
||||||
|
selected="$(printf '%s\n' "$output" | sed -n "${line_no}p")"
|
||||||
|
fi
|
||||||
|
selected="$(printf '%s' "$selected" | sed -E 's/^[[:space:]]+//; s/[[:space:]]+$//')"
|
||||||
|
|
||||||
|
if [ -n "$regex" ]; then
|
||||||
|
if [[ "$selected" =~ $regex ]]; then
|
||||||
|
version="${BASH_REMATCH[$group_no]}"
|
||||||
|
else
|
||||||
|
REPO_LIB_TOOL_ERROR="version parse failed"
|
||||||
|
printf "%s\n" "$output" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
version="$selected"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$version" ]; then
|
||||||
|
REPO_LIB_TOOL_ERROR="empty version"
|
||||||
|
printf "%s\n" "$output" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
REPO_LIB_TOOL_VERSION="$version"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
repo_lib_capture_legacy_tool() {
|
||||||
|
local required="$1"
|
||||||
|
local command_name="$2"
|
||||||
|
local version_command="$3"
|
||||||
|
|
||||||
|
local output=""
|
||||||
|
local version=""
|
||||||
|
|
||||||
|
REPO_LIB_TOOL_VERSION=""
|
||||||
|
REPO_LIB_TOOL_ERROR=""
|
||||||
|
|
||||||
|
if ! command -v "$command_name" >/dev/null 2>&1; then
|
||||||
|
REPO_LIB_TOOL_ERROR="missing command"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! output="$(sh -c "$command_name $version_command" 2>&1)"; then
|
||||||
|
REPO_LIB_TOOL_ERROR="probe failed"
|
||||||
|
printf "%s\n" "$output" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
version="$(printf '%s\n' "$output" | head -n 1 | sed -E 's/^[[:space:]]+//; s/[[:space:]]+$//')"
|
||||||
|
if [ -z "$version" ]; then
|
||||||
|
REPO_LIB_TOOL_ERROR="empty version"
|
||||||
|
printf "%s\n" "$output" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
REPO_LIB_TOOL_VERSION="$version"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
repo_lib_print_simple_header() {
|
||||||
|
local title_color_name="$1"
|
||||||
|
local icon="$2"
|
||||||
|
local title="$3"
|
||||||
|
local subtitle_color_name="$4"
|
||||||
|
local subtitle="$5"
|
||||||
|
|
||||||
|
local title_color="${!title_color_name:-$GREEN}"
|
||||||
|
local subtitle_color="${!subtitle_color_name:-$GRAY}"
|
||||||
|
|
||||||
|
printf "\n%s" "$title_color"
|
||||||
|
if [ -n "$icon" ]; then
|
||||||
|
printf "%s " "$icon"
|
||||||
|
fi
|
||||||
|
printf "%s%s" "$title" "$RESET"
|
||||||
|
if [ -n "$subtitle" ]; then
|
||||||
|
printf " %s%s%s" "$subtitle_color" "$subtitle" "$RESET"
|
||||||
|
fi
|
||||||
|
printf "\n\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
repo_lib_print_simple_tool() {
|
||||||
|
local name="$1"
|
||||||
|
local color_name="$2"
|
||||||
|
local icon="$3"
|
||||||
|
local icon_color_name="$4"
|
||||||
|
local required="$5"
|
||||||
|
local line_no="$6"
|
||||||
|
local group_no="$7"
|
||||||
|
local regex="$8"
|
||||||
|
local match_regex="$9"
|
||||||
|
local executable="${10}"
|
||||||
|
shift 10
|
||||||
|
|
||||||
|
local color="${!color_name:-$YELLOW}"
|
||||||
|
local effective_icon_color_name="$icon_color_name"
|
||||||
|
local icon_color=""
|
||||||
|
|
||||||
|
if [ -z "$effective_icon_color_name" ]; then
|
||||||
|
effective_icon_color_name="$color_name"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if repo_lib_capture_tool "$required" "$line_no" "$group_no" "$regex" "$match_regex" "$executable" "$@"; then
|
||||||
|
icon_color="${!effective_icon_color_name:-$color}"
|
||||||
|
printf " "
|
||||||
|
if [ -n "$icon" ]; then
|
||||||
|
printf "%s%s%s " "$icon_color" "$icon" "$RESET"
|
||||||
|
fi
|
||||||
|
printf "$CYAN %-@TOOL_LABEL_WIDTH@s$RESET %s%s$RESET\n" "${name}:" "$color" "$REPO_LIB_TOOL_VERSION"
|
||||||
|
else
|
||||||
|
printf " "
|
||||||
|
if [ -n "$icon" ]; then
|
||||||
|
printf "%s%s%s " "$RED" "$icon" "$RESET"
|
||||||
|
fi
|
||||||
|
printf "$CYAN %-@TOOL_LABEL_WIDTH@s$RESET $RED%s$RESET\n" "${name}:" "$REPO_LIB_TOOL_ERROR"
|
||||||
|
if [ "$required" = "1" ]; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
repo_lib_print_simple_legacy_tool() {
|
||||||
|
local name="$1"
|
||||||
|
local color_name="$2"
|
||||||
|
local icon="$3"
|
||||||
|
local icon_color_name="$4"
|
||||||
|
local required="$5"
|
||||||
|
local command_name="$6"
|
||||||
|
local version_command="$7"
|
||||||
|
|
||||||
|
local color="${!color_name:-$YELLOW}"
|
||||||
|
local effective_icon_color_name="$icon_color_name"
|
||||||
|
local icon_color=""
|
||||||
|
|
||||||
|
if [ -z "$effective_icon_color_name" ]; then
|
||||||
|
effective_icon_color_name="$color_name"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if repo_lib_capture_legacy_tool "$required" "$command_name" "$version_command"; then
|
||||||
|
icon_color="${!effective_icon_color_name:-$color}"
|
||||||
|
printf " "
|
||||||
|
if [ -n "$icon" ]; then
|
||||||
|
printf "%s%s%s " "$icon_color" "$icon" "$RESET"
|
||||||
|
fi
|
||||||
|
printf "$CYAN %-@TOOL_LABEL_WIDTH@s$RESET %s%s$RESET\n" "${name}:" "$color" "$REPO_LIB_TOOL_VERSION"
|
||||||
|
else
|
||||||
|
printf " "
|
||||||
|
if [ -n "$icon" ]; then
|
||||||
|
printf "%s%s%s " "$RED" "$icon" "$RESET"
|
||||||
|
fi
|
||||||
|
printf "$CYAN %-@TOOL_LABEL_WIDTH@s$RESET $RED%s$RESET\n" "${name}:" "$REPO_LIB_TOOL_ERROR"
|
||||||
|
if [ "$required" = "1" ]; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
repo_lib_print_pretty_header() {
|
||||||
|
local border_color_name="$1"
|
||||||
|
local title_color_name="$2"
|
||||||
|
local icon="$3"
|
||||||
|
local title="$4"
|
||||||
|
local subtitle_color_name="$5"
|
||||||
|
local subtitle="$6"
|
||||||
|
|
||||||
|
local border_color="${!border_color_name:-$BLUE}"
|
||||||
|
local title_color="${!title_color_name:-$GREEN}"
|
||||||
|
local subtitle_color="${!subtitle_color_name:-$GRAY}"
|
||||||
|
|
||||||
|
printf "\n%s╭─%s %s" "$border_color" "$RESET" "$title_color"
|
||||||
|
if [ -n "$icon" ]; then
|
||||||
|
printf "%s " "$icon"
|
||||||
|
fi
|
||||||
|
printf "%s%s" "$title" "$RESET"
|
||||||
|
if [ -n "$subtitle" ]; then
|
||||||
|
printf " %s%s%s" "$subtitle_color" "$subtitle" "$RESET"
|
||||||
|
fi
|
||||||
|
printf "\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
repo_lib_print_pretty_row() {
|
||||||
|
local border_color_name="$1"
|
||||||
|
local icon="$2"
|
||||||
|
local icon_color_name="$3"
|
||||||
|
local label="$4"
|
||||||
|
local value="$5"
|
||||||
|
local value_color_name="$6"
|
||||||
|
|
||||||
|
local border_color="${!border_color_name:-$BLUE}"
|
||||||
|
local icon_color="${!icon_color_name:-$WHITE}"
|
||||||
|
local value_color="${!value_color_name:-$YELLOW}"
|
||||||
|
|
||||||
|
if [ -z "$icon" ]; then
|
||||||
|
icon="•"
|
||||||
|
fi
|
||||||
|
|
||||||
|
printf "%s│%s %s%s%s ${WHITE}%-@TOOL_LABEL_WIDTH@s${RESET} %s%s${RESET}\n" \
|
||||||
|
"$border_color" "$RESET" "$icon_color" "$icon" "$RESET" "$label" "$value_color" "$value"
|
||||||
|
}
|
||||||
|
|
||||||
|
repo_lib_print_pretty_tool() {
|
||||||
|
local border_color_name="$1"
|
||||||
|
local name="$2"
|
||||||
|
local color_name="$3"
|
||||||
|
local icon="$4"
|
||||||
|
local icon_color_name="$5"
|
||||||
|
local required="$6"
|
||||||
|
local line_no="$7"
|
||||||
|
local group_no="$8"
|
||||||
|
local regex="$9"
|
||||||
|
local match_regex="${10}"
|
||||||
|
local executable="${11}"
|
||||||
|
shift 11
|
||||||
|
|
||||||
|
local effective_icon_color_name="$icon_color_name"
|
||||||
|
local value_color_name="$color_name"
|
||||||
|
local value=""
|
||||||
|
|
||||||
|
if [ -z "$effective_icon_color_name" ]; then
|
||||||
|
effective_icon_color_name="$color_name"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if repo_lib_capture_tool "$required" "$line_no" "$group_no" "$regex" "$match_regex" "$executable" "$@"; then
|
||||||
|
value="$REPO_LIB_TOOL_VERSION"
|
||||||
|
else
|
||||||
|
value="$REPO_LIB_TOOL_ERROR"
|
||||||
|
effective_icon_color_name="RED"
|
||||||
|
value_color_name="RED"
|
||||||
|
fi
|
||||||
|
|
||||||
|
repo_lib_print_pretty_row \
|
||||||
|
"$border_color_name" \
|
||||||
|
"$icon" \
|
||||||
|
"$effective_icon_color_name" \
|
||||||
|
"$name" \
|
||||||
|
"$value" \
|
||||||
|
"$value_color_name"
|
||||||
|
|
||||||
|
if [ "$value_color_name" = "RED" ] && [ "$required" = "1" ]; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
repo_lib_print_pretty_legacy_tool() {
|
||||||
|
local border_color_name="$1"
|
||||||
|
local name="$2"
|
||||||
|
local color_name="$3"
|
||||||
|
local icon="$4"
|
||||||
|
local icon_color_name="$5"
|
||||||
|
local required="$6"
|
||||||
|
local command_name="$7"
|
||||||
|
local version_command="$8"
|
||||||
|
|
||||||
|
local effective_icon_color_name="$icon_color_name"
|
||||||
|
local value_color_name="$color_name"
|
||||||
|
local value=""
|
||||||
|
|
||||||
|
if [ -z "$effective_icon_color_name" ]; then
|
||||||
|
effective_icon_color_name="$color_name"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if repo_lib_capture_legacy_tool "$required" "$command_name" "$version_command"; then
|
||||||
|
value="$REPO_LIB_TOOL_VERSION"
|
||||||
|
else
|
||||||
|
value="$REPO_LIB_TOOL_ERROR"
|
||||||
|
effective_icon_color_name="RED"
|
||||||
|
value_color_name="RED"
|
||||||
|
fi
|
||||||
|
|
||||||
|
repo_lib_print_pretty_row \
|
||||||
|
"$border_color_name" \
|
||||||
|
"$icon" \
|
||||||
|
"$effective_icon_color_name" \
|
||||||
|
"$name" \
|
||||||
|
"$value" \
|
||||||
|
"$value_color_name"
|
||||||
|
|
||||||
|
if [ "$value_color_name" = "RED" ] && [ "$required" = "1" ]; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
repo_lib_print_pretty_footer() {
|
||||||
|
local border_color_name="$1"
|
||||||
|
local border_color="${!border_color_name:-$BLUE}"
|
||||||
|
|
||||||
|
printf "%s╰─%s\n\n" "$border_color" "$RESET"
|
||||||
|
}
|
||||||
|
|
||||||
|
@SHELL_ENV_SCRIPT@
|
||||||
|
|
||||||
|
@BOOTSTRAP@
|
||||||
|
|
||||||
|
@SHELL_BANNER_SCRIPT@
|
||||||
|
|
||||||
|
@EXTRA_SHELL_TEXT@
|
||||||
48
skills/repo-lib-consumer/SKILL.md
Normal file
48
skills/repo-lib-consumer/SKILL.md
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
---
|
||||||
|
name: repo-lib-consumer
|
||||||
|
description: Edit or extend repos that consume `repo-lib` through `repo-lib.lib.mkRepo`, `mkDevShell`, or `mkRelease`. Use when Codex needs to add or change tools, shell packages, checks or test phases, formatters, release steps, release channels, bootstrap hooks, or release automation in a Nix flake built on repo-lib.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Repo Lib Consumer
|
||||||
|
|
||||||
|
Use this skill to make idiomatic changes in a repo that already depends on `repo-lib`.
|
||||||
|
|
||||||
|
## Workflow
|
||||||
|
|
||||||
|
1. Detect the integration style.
|
||||||
|
Search for `repo-lib.lib.mkRepo`, `repo-lib.lib.mkDevShell`, `repo-lib.lib.mkRelease`, or `inputs.repo-lib`.
|
||||||
|
|
||||||
|
2. Prefer the repo's current abstraction level.
|
||||||
|
If the repo already uses `mkRepo`, stay on `mkRepo`.
|
||||||
|
If the repo still uses `mkDevShell` or `mkRelease`, preserve that style unless the user asked to migrate.
|
||||||
|
|
||||||
|
3. Load the right reference before editing.
|
||||||
|
Read `references/api.md` for exact option names, defaults, generated outputs, and limitations.
|
||||||
|
Read `references/recipes.md` for common edits such as adding a tool, adding a test phase, wiring release file updates, or handling webhooks.
|
||||||
|
|
||||||
|
4. Follow repo-lib conventions.
|
||||||
|
Add bannered CLIs through `perSystem.tools`, not `shell.packages`.
|
||||||
|
Use `shell.packages` for packages that should be present in the shell but not shown in the banner.
|
||||||
|
Keep shells pure-first; only use `bootstrap` with `allowImpureBootstrap = true`.
|
||||||
|
Prefer structured `release.steps` over free-form shell when the task fits `writeFile` or `replace`.
|
||||||
|
|
||||||
|
5. Verify after edits.
|
||||||
|
Run `nix flake show --json`.
|
||||||
|
Run `nix flake check` when feasible.
|
||||||
|
If local flake evaluation cannot see newly created files because the repo is being loaded as a git flake, stage the new files before rerunning checks.
|
||||||
|
|
||||||
|
## Decision Rules
|
||||||
|
|
||||||
|
- Prefer `repo-lib.lib.tools.fromPackage` for tools with explicit metadata.
|
||||||
|
- Use `repo-lib.lib.tools.simple` only for very simple `--version` or `version` probes.
|
||||||
|
- Put pre-commit and pre-push automation in `checks`, not shell hooks.
|
||||||
|
- Treat `postVersion` as pre-tag and pre-push. It is not a true post-tag hook.
|
||||||
|
- For a webhook that must fire after the tag exists remotely, prefer CI triggered by tag push over local release command changes.
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- `references/api.md`
|
||||||
|
Use for the exact consumer API, option matrix, generated outputs, release ordering, and legacy compatibility.
|
||||||
|
|
||||||
|
- `references/recipes.md`
|
||||||
|
Use for concrete change patterns: add a tool, add a test phase, update release-managed files, or wire webhook behavior.
|
||||||
4
skills/repo-lib-consumer/agents/openai.yaml
Normal file
4
skills/repo-lib-consumer/agents/openai.yaml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
interface:
|
||||||
|
display_name: "Repo Lib Consumer"
|
||||||
|
short_description: "Edit repos that use repo-lib safely"
|
||||||
|
default_prompt: "Use $repo-lib-consumer to update a repo that consumes repo-lib."
|
||||||
318
skills/repo-lib-consumer/references/api.md
Normal file
318
skills/repo-lib-consumer/references/api.md
Normal file
@@ -0,0 +1,318 @@
|
|||||||
|
# repo-lib Consumer API
|
||||||
|
|
||||||
|
## Detect the repo shape
|
||||||
|
|
||||||
|
Look for one of these patterns in the consuming repo:
|
||||||
|
|
||||||
|
- `repo-lib.lib.mkRepo`
|
||||||
|
- `repo-lib.lib.mkDevShell`
|
||||||
|
- `repo-lib.lib.mkRelease`
|
||||||
|
- `inputs.repo-lib`
|
||||||
|
|
||||||
|
Prefer editing the existing style instead of migrating incidentally.
|
||||||
|
|
||||||
|
## Preferred `mkRepo` shape
|
||||||
|
|
||||||
|
```nix
|
||||||
|
repo-lib.lib.mkRepo {
|
||||||
|
inherit self nixpkgs;
|
||||||
|
src = ./.;
|
||||||
|
systems = repo-lib.lib.systems.default; # optional
|
||||||
|
|
||||||
|
config = {
|
||||||
|
includeStandardPackages = true;
|
||||||
|
|
||||||
|
shell = {
|
||||||
|
env = { };
|
||||||
|
extraShellText = "";
|
||||||
|
allowImpureBootstrap = false;
|
||||||
|
bootstrap = "";
|
||||||
|
};
|
||||||
|
|
||||||
|
formatting = {
|
||||||
|
programs = { };
|
||||||
|
settings = { };
|
||||||
|
};
|
||||||
|
|
||||||
|
checks = { };
|
||||||
|
lefthook = { };
|
||||||
|
|
||||||
|
release = null; # or attrset below
|
||||||
|
};
|
||||||
|
|
||||||
|
perSystem = { pkgs, system, lib, config }: {
|
||||||
|
tools = [ ];
|
||||||
|
shell.packages = [ ];
|
||||||
|
checks = { };
|
||||||
|
lefthook = { };
|
||||||
|
packages = { };
|
||||||
|
apps = { };
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Generated outputs:
|
||||||
|
|
||||||
|
- `devShells.${system}.default`
|
||||||
|
- `checks.${system}.hook-check`
|
||||||
|
- `checks.${system}.lefthook-check`
|
||||||
|
- `formatter.${system}`
|
||||||
|
- `packages.${system}.release` when `config.release != null`
|
||||||
|
- merged `packages` and `apps` from `perSystem`
|
||||||
|
|
||||||
|
## `config.shell`
|
||||||
|
|
||||||
|
Fields:
|
||||||
|
|
||||||
|
- `env`
|
||||||
|
Attrset of environment variables exported in the shell.
|
||||||
|
- `extraShellText`
|
||||||
|
Extra shell snippet appended after the banner.
|
||||||
|
- `bootstrap`
|
||||||
|
Shell snippet that runs before the banner.
|
||||||
|
- `allowImpureBootstrap`
|
||||||
|
Must be `true` if `bootstrap` is non-empty.
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
|
||||||
|
- Default is pure-first.
|
||||||
|
- Do not add bootstrap work unless the user actually wants imperative setup.
|
||||||
|
- Use `bootstrap` for unavoidable local setup only.
|
||||||
|
|
||||||
|
## `config.formatting`
|
||||||
|
|
||||||
|
Fields:
|
||||||
|
|
||||||
|
- `programs`
|
||||||
|
Passed to `treefmt-nix.lib.evalModule`.
|
||||||
|
- `settings`
|
||||||
|
Passed to `settings.formatter`.
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
|
||||||
|
- `nixfmt` is always enabled.
|
||||||
|
- Use formatter settings instead of ad hoc shell formatting logic.
|
||||||
|
|
||||||
|
## Checks
|
||||||
|
|
||||||
|
`config.checks.<name>` and `perSystem.checks.<name>` use this shape:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
{
|
||||||
|
command = "go test ./...";
|
||||||
|
stage = "pre-push"; # or "pre-commit"
|
||||||
|
passFilenames = false;
|
||||||
|
runtimeInputs = [ pkgs.go ];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Defaults:
|
||||||
|
|
||||||
|
- `stage = "pre-commit"`
|
||||||
|
- `passFilenames = false`
|
||||||
|
- `runtimeInputs = [ ]`
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
|
||||||
|
- Only `pre-commit` and `pre-push` are supported.
|
||||||
|
- The command is wrapped as a script and connected into `lefthook.nix`.
|
||||||
|
- `pre-commit` and `pre-push` commands are configured to run in parallel.
|
||||||
|
|
||||||
|
## Raw Lefthook config
|
||||||
|
|
||||||
|
Use `config.lefthook` or `perSystem.lefthook` for advanced Lefthook features that the built-in `checks` abstraction does not carry.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
{
|
||||||
|
checks.tests = {
|
||||||
|
command = "go test ./...";
|
||||||
|
stage = "pre-push";
|
||||||
|
};
|
||||||
|
|
||||||
|
lefthook.pre-push.commands.tests.stage_fixed = true;
|
||||||
|
|
||||||
|
lefthook.commit-msg.commands.commitlint = {
|
||||||
|
run = "pnpm commitlint --edit {1}";
|
||||||
|
stage_fixed = true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
|
||||||
|
- These attrsets are passed through to `lefthook.nix`.
|
||||||
|
- They are merged after generated checks, so they can extend generated commands.
|
||||||
|
- Prefer `checks` for the simple common case and `lefthook` for advanced fields such as `stage_fixed`, `files`, `glob`, `exclude`, `jobs`, or `scripts`.
|
||||||
|
|
||||||
|
## Tools
|
||||||
|
|
||||||
|
Preferred shape in `perSystem.tools`:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
(repo-lib.lib.tools.fromPackage {
|
||||||
|
name = "Go";
|
||||||
|
package = pkgs.go;
|
||||||
|
exe = "go"; # optional
|
||||||
|
version = {
|
||||||
|
args = [ "version" ];
|
||||||
|
regex = null;
|
||||||
|
group = 0;
|
||||||
|
line = 1;
|
||||||
|
};
|
||||||
|
banner = {
|
||||||
|
color = "CYAN";
|
||||||
|
};
|
||||||
|
required = true;
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
For a tool that should come from the host `PATH` instead of `nixpkgs`:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
(repo-lib.lib.tools.fromCommand {
|
||||||
|
name = "Nix";
|
||||||
|
command = "nix";
|
||||||
|
version.args = [ "--version" ];
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
Helper:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
repo-lib.lib.tools.simple "Go" pkgs.go [ "version" ]
|
||||||
|
```
|
||||||
|
|
||||||
|
Tool behavior:
|
||||||
|
|
||||||
|
- Tool packages are added to the shell automatically.
|
||||||
|
- Command-backed tools are probed from the existing `PATH` and are not added to the shell automatically.
|
||||||
|
- Banner probing uses absolute executable paths.
|
||||||
|
- `required = true` by default.
|
||||||
|
- Required tool probe failure aborts shell startup.
|
||||||
|
|
||||||
|
Use `shell.packages` instead of `tools` when:
|
||||||
|
|
||||||
|
- the package should be in the shell but not in the banner
|
||||||
|
- the package is not a CLI tool with a stable version probe
|
||||||
|
|
||||||
|
## `config.release`
|
||||||
|
|
||||||
|
Shape:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
{
|
||||||
|
channels = [ "alpha" "beta" "rc" "internal" ];
|
||||||
|
steps = [ ];
|
||||||
|
postVersion = "";
|
||||||
|
runtimeInputs = [ ];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Defaults:
|
||||||
|
|
||||||
|
- `channels = [ "alpha" "beta" "rc" "internal" ]`
|
||||||
|
- `steps = [ ]`
|
||||||
|
- `postVersion = ""`
|
||||||
|
- `runtimeInputs = [ ]`
|
||||||
|
|
||||||
|
Set `release = null` to disable the generated release package.
|
||||||
|
|
||||||
|
## Release step shapes
|
||||||
|
|
||||||
|
### `writeFile`
|
||||||
|
|
||||||
|
```nix
|
||||||
|
{
|
||||||
|
writeFile = {
|
||||||
|
path = "src/version.ts";
|
||||||
|
text = ''
|
||||||
|
export const APP_VERSION = "$FULL_VERSION" as const;
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `replace`
|
||||||
|
|
||||||
|
```nix
|
||||||
|
{
|
||||||
|
replace = {
|
||||||
|
path = "README.md";
|
||||||
|
regex = ''^(version = ")[^"]*(")$'';
|
||||||
|
replacement = ''\1$FULL_VERSION\2'';
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `run`
|
||||||
|
|
||||||
|
```nix
|
||||||
|
{
|
||||||
|
run = {
|
||||||
|
script = ''
|
||||||
|
curl -fsS https://example.invalid/hook \
|
||||||
|
-H 'content-type: application/json' \
|
||||||
|
-d '{"tag":"'"$FULL_TAG"'"}'
|
||||||
|
'';
|
||||||
|
runtimeInputs = [ pkgs.curl ];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Also accepted for compatibility:
|
||||||
|
|
||||||
|
- `{ run = ''...''; }`
|
||||||
|
- legacy `mkRelease { release = [ { file = ...; content = ...; } ... ]; }`
|
||||||
|
|
||||||
|
## Release ordering
|
||||||
|
|
||||||
|
The generated `release` command does this:
|
||||||
|
|
||||||
|
1. Update `VERSION`
|
||||||
|
2. Run `release.steps`
|
||||||
|
3. Run `postVersion`
|
||||||
|
4. Run `nix fmt`
|
||||||
|
5. `git add -A`
|
||||||
|
6. Commit
|
||||||
|
7. Tag
|
||||||
|
8. Push branch
|
||||||
|
9. Push tags
|
||||||
|
|
||||||
|
Important consequence:
|
||||||
|
|
||||||
|
- `postVersion` is still before commit, tag, and push.
|
||||||
|
- There is no true post-tag or post-push hook in current `repo-lib`.
|
||||||
|
|
||||||
|
## Post-tag webhook limitation
|
||||||
|
|
||||||
|
If the user asks for a webhook after the tag exists remotely:
|
||||||
|
|
||||||
|
- Prefer CI triggered by pushed tags in the consuming repo.
|
||||||
|
- Do not claim `postVersion` is post-tag; it is not.
|
||||||
|
- Only extend `repo-lib` itself if the user explicitly wants a new library capability.
|
||||||
|
|
||||||
|
## Legacy API summary
|
||||||
|
|
||||||
|
`mkDevShell` still supports:
|
||||||
|
|
||||||
|
- `extraPackages`
|
||||||
|
- `preToolHook`
|
||||||
|
- `extraShellHook`
|
||||||
|
- `lefthook`
|
||||||
|
- `additionalHooks`
|
||||||
|
- old `tools = [ { name; bin; versionCmd; color; } ]`
|
||||||
|
- `features.oxfmt`
|
||||||
|
- `formatters`
|
||||||
|
- `formatterSettings`
|
||||||
|
|
||||||
|
`mkRelease` still supports:
|
||||||
|
|
||||||
|
- `release = [ ... ]` as legacy alias for `steps`
|
||||||
|
- `extraRuntimeInputs` as legacy alias merged into `runtimeInputs`
|
||||||
|
|
||||||
|
When a repo already uses these APIs:
|
||||||
|
|
||||||
|
- preserve them unless the user asked to migrate
|
||||||
|
- do not mix old and new styles accidentally in the same call
|
||||||
197
skills/repo-lib-consumer/references/recipes.md
Normal file
197
skills/repo-lib-consumer/references/recipes.md
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
# repo-lib Change Recipes
|
||||||
|
|
||||||
|
## Add a new bannered tool
|
||||||
|
|
||||||
|
Edit `perSystem.tools` in the consuming repo:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
tools = [
|
||||||
|
(repo-lib.lib.tools.fromPackage {
|
||||||
|
name = "Go";
|
||||||
|
package = pkgs.go;
|
||||||
|
version.args = [ "version" ];
|
||||||
|
banner.color = "CYAN";
|
||||||
|
})
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
|
||||||
|
- Do not also add `pkgs.go` to `shell.packages`; `tools` already adds it.
|
||||||
|
- Use `exe = "name"` only when the package exposes multiple binaries or the main program is not the desired one.
|
||||||
|
|
||||||
|
## Add a non-banner package to the shell
|
||||||
|
|
||||||
|
Use `shell.packages`:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
shell.packages = [
|
||||||
|
self.packages.${system}.release
|
||||||
|
pkgs.jq
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
Use this for:
|
||||||
|
|
||||||
|
- helper CLIs that do not need a banner entry
|
||||||
|
- internal scripts
|
||||||
|
- the generated `release` package itself
|
||||||
|
|
||||||
|
## Add a test phase or lint hook
|
||||||
|
|
||||||
|
For a simple global check:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
config.checks.tests = {
|
||||||
|
command = "go test ./...";
|
||||||
|
stage = "pre-push";
|
||||||
|
passFilenames = false;
|
||||||
|
runtimeInputs = [ pkgs.go ];
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
For a system-specific check:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
perSystem = { pkgs, ... }: {
|
||||||
|
checks.lint = {
|
||||||
|
command = "bun test";
|
||||||
|
stage = "pre-push";
|
||||||
|
runtimeInputs = [ pkgs.bun ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Guidance:
|
||||||
|
|
||||||
|
- Use `pre-commit` for fast format/lint work.
|
||||||
|
- Use `pre-push` for slower test suites.
|
||||||
|
- Prefer `runtimeInputs` over inline absolute paths when the command needs extra CLIs.
|
||||||
|
|
||||||
|
## Add or change formatters
|
||||||
|
|
||||||
|
Use `config.formatting`:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
config.formatting = {
|
||||||
|
programs = {
|
||||||
|
shfmt.enable = true;
|
||||||
|
gofmt.enable = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
settings = {
|
||||||
|
shfmt.options = [ "-i" "2" "-s" "-w" ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## Add release-managed files
|
||||||
|
|
||||||
|
Generate a file from the release version:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
config.release.steps = [
|
||||||
|
{
|
||||||
|
writeFile = {
|
||||||
|
path = "src/version.ts";
|
||||||
|
text = ''
|
||||||
|
export const APP_VERSION = "$FULL_VERSION" as const;
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
}
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
Update an existing file with a regex:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
config.release.steps = [
|
||||||
|
{
|
||||||
|
replace = {
|
||||||
|
path = "README.md";
|
||||||
|
regex = ''^(version = ")[^"]*(")$'';
|
||||||
|
replacement = ''\1$FULL_VERSION\2'';
|
||||||
|
};
|
||||||
|
}
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
## Add a webhook during release
|
||||||
|
|
||||||
|
If the webhook may run before commit and tag creation, use a `run` step or `postVersion`.
|
||||||
|
|
||||||
|
Use a `run` step when it belongs with other release mutations:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
config.release = {
|
||||||
|
runtimeInputs = [ pkgs.curl ];
|
||||||
|
steps = [
|
||||||
|
{
|
||||||
|
run = {
|
||||||
|
script = ''
|
||||||
|
curl -fsS https://example.invalid/release-hook \
|
||||||
|
-H 'content-type: application/json' \
|
||||||
|
-d '{"version":"'"$FULL_VERSION"'"}'
|
||||||
|
'';
|
||||||
|
runtimeInputs = [ pkgs.curl ];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Use `postVersion` when the action should happen after all `steps`:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
config.release.postVersion = ''
|
||||||
|
curl -fsS https://example.invalid/release-hook \
|
||||||
|
-H 'content-type: application/json' \
|
||||||
|
-d '{"version":"'"$FULL_VERSION"'","tag":"'"$FULL_TAG"'"}'
|
||||||
|
'';
|
||||||
|
config.release.runtimeInputs = [ pkgs.curl ];
|
||||||
|
```
|
||||||
|
|
||||||
|
Important:
|
||||||
|
|
||||||
|
- Both of these still run before commit, tag, and push.
|
||||||
|
- They are not true post-tag hooks.
|
||||||
|
|
||||||
|
## Add a true post-tag webhook
|
||||||
|
|
||||||
|
Do not fake this with `postVersion`.
|
||||||
|
|
||||||
|
Preferred approach in the consuming repo:
|
||||||
|
|
||||||
|
1. Keep local release generation in `repo-lib`.
|
||||||
|
2. Add CI triggered by tag push.
|
||||||
|
3. Put the webhook call in CI, where the tag is already created and pushed.
|
||||||
|
|
||||||
|
Only change `repo-lib` itself if the user explicitly asks for a new local post-tag capability.
|
||||||
|
|
||||||
|
## Add impure bootstrap work
|
||||||
|
|
||||||
|
Only do this when the user actually wants imperative shell setup:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
config.shell = {
|
||||||
|
bootstrap = ''
|
||||||
|
export GOBIN="$PWD/.tools/bin"
|
||||||
|
export PATH="$GOBIN:$PATH"
|
||||||
|
'';
|
||||||
|
allowImpureBootstrap = true;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Do not add bootstrap work for normal Nix-packaged tools.
|
||||||
|
|
||||||
|
## Migrate a legacy consumer to `mkRepo`
|
||||||
|
|
||||||
|
Only do this if requested.
|
||||||
|
|
||||||
|
Migration outline:
|
||||||
|
|
||||||
|
1. Move repeated shell/check/formatter config into `config`.
|
||||||
|
2. Move old banner tools into `perSystem.tools`.
|
||||||
|
3. Move extra shell packages into `perSystem.shell.packages`.
|
||||||
|
4. Replace old `mkRelease { release = [ ... ]; }` with `config.release.steps`.
|
||||||
|
5. Keep behavior the same first; do not redesign the repo in the same change unless asked.
|
||||||
18
template/.env.schema
Normal file
18
template/.env.schema
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# @currentEnv=$REPO_ENVIRONMENT
|
||||||
|
# ---
|
||||||
|
|
||||||
|
# Canonical repo environment used by Varlock.
|
||||||
|
# @type=enum(development,ci,production)
|
||||||
|
REPO_ENVIRONMENT=development
|
||||||
|
|
||||||
|
# Safe starter values for local development.
|
||||||
|
# @type=string
|
||||||
|
APP_NAME=typescript-monorepo
|
||||||
|
|
||||||
|
# @type=port
|
||||||
|
APP_PORT=3000
|
||||||
|
|
||||||
|
# Optional example secret resolved from OpenBao.
|
||||||
|
# Replace the namespace and secret path with values for your repo before use.
|
||||||
|
# @optional @sensitive
|
||||||
|
EXAMPLE_API_TOKEN=exec('bao kv get -mount=kv -namespace="${BAO_NAMESPACE:+$BAO_NAMESPACE/}template" -field=EXAMPLE_API_TOKEN "$REPO_ENVIRONMENT/shared" 2>/dev/null || true')
|
||||||
@@ -1 +1,2 @@
|
|||||||
|
watch_file flake.nix
|
||||||
use flake
|
use flake
|
||||||
|
|||||||
4
template/.gitignore
vendored
4
template/.gitignore
vendored
@@ -1,8 +1,12 @@
|
|||||||
.direnv/
|
.direnv/
|
||||||
|
.moon/cache/
|
||||||
.pre-commit-config.yaml
|
.pre-commit-config.yaml
|
||||||
|
lefthook.yml
|
||||||
|
.tools/
|
||||||
|
|
||||||
bazel-*
|
bazel-*
|
||||||
build/
|
build/
|
||||||
dist/
|
dist/
|
||||||
|
|
||||||
node_modules/
|
node_modules/
|
||||||
|
.env.sh
|
||||||
|
|||||||
11
template/.moon/workspace.yml
Normal file
11
template/.moon/workspace.yml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
$schema: "./cache/schemas/workspace.json"
|
||||||
|
|
||||||
|
projects:
|
||||||
|
globs:
|
||||||
|
- "apps/*"
|
||||||
|
- "packages/*"
|
||||||
|
sources:
|
||||||
|
root: "."
|
||||||
|
|
||||||
|
vcs:
|
||||||
|
defaultBranch: "main"
|
||||||
32
template/README.md
Normal file
32
template/README.md
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# TypeScript Monorepo Template
|
||||||
|
|
||||||
|
This template gives you a Bun-only monorepo with:
|
||||||
|
|
||||||
|
- Moonrepo at the workspace root
|
||||||
|
- strict shared TypeScript configs
|
||||||
|
- Varlock with a committed `.env.schema`
|
||||||
|
- a Nix flake shell built through `repo-lib`
|
||||||
|
|
||||||
|
## First Run
|
||||||
|
|
||||||
|
1. Enter the shell with `direnv allow` or `nix develop`.
|
||||||
|
2. Install workspace dependencies with `bun install`.
|
||||||
|
3. Review and customize `.env.schema` for your repo.
|
||||||
|
4. Run `bun run env:check`.
|
||||||
|
5. Run `moon run :typecheck`.
|
||||||
|
|
||||||
|
## Layout
|
||||||
|
|
||||||
|
- `apps/` for applications
|
||||||
|
- `packages/` for shared libraries
|
||||||
|
- `tsconfig/` for shared TypeScript profiles
|
||||||
|
- `moon.yml` for root Moonrepo tasks
|
||||||
|
|
||||||
|
## Varlock
|
||||||
|
|
||||||
|
`bunfig.toml` preloads `varlock/auto-load`, and the root scripts expose:
|
||||||
|
|
||||||
|
- `bun run env:check`
|
||||||
|
- `bun run env:scan`
|
||||||
|
|
||||||
|
If you use OpenBao locally, set `OPENBAO_ADDR`, `OPENBAO_NAMESPACE`, and `OPENBAO_CACERT` in your shell or an ignored `.env.sh` file before running those commands.
|
||||||
1
template/apps/.gitkeep
Normal file
1
template/apps/.gitkeep
Normal file
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
2
template/bunfig.toml
Normal file
2
template/bunfig.toml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
env = false
|
||||||
|
preload = ["varlock/auto-load"]
|
||||||
159
template/flake.lock
generated
159
template/flake.lock
generated
@@ -1,159 +0,0 @@
|
|||||||
{
|
|
||||||
"nodes": {
|
|
||||||
"devshell-lib": {
|
|
||||||
"inputs": {
|
|
||||||
"git-hooks": "git-hooks",
|
|
||||||
"nixpkgs": [
|
|
||||||
"nixpkgs"
|
|
||||||
],
|
|
||||||
"treefmt-nix": "treefmt-nix"
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1772603902,
|
|
||||||
"narHash": "sha256-GN5EC9m0flWDuc6qaB6QoIBD73yFnhl2PBIYXzSTGeQ=",
|
|
||||||
"ref": "v0.0.2",
|
|
||||||
"rev": "db4ed150e01e2f9245e668077245447d0089163f",
|
|
||||||
"revCount": 15,
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://git.dgren.dev/eric/nix-flake-lib"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"ref": "v0.0.2",
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://git.dgren.dev/eric/nix-flake-lib"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"flake-compat": {
|
|
||||||
"flake": false,
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1767039857,
|
|
||||||
"narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=",
|
|
||||||
"owner": "NixOS",
|
|
||||||
"repo": "flake-compat",
|
|
||||||
"rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "NixOS",
|
|
||||||
"repo": "flake-compat",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"git-hooks": {
|
|
||||||
"inputs": {
|
|
||||||
"flake-compat": "flake-compat",
|
|
||||||
"gitignore": "gitignore",
|
|
||||||
"nixpkgs": "nixpkgs"
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1772024342,
|
|
||||||
"narHash": "sha256-+eXlIc4/7dE6EcPs9a2DaSY3fTA9AE526hGqkNID3Wg=",
|
|
||||||
"owner": "cachix",
|
|
||||||
"repo": "git-hooks.nix",
|
|
||||||
"rev": "6e34e97ed9788b17796ee43ccdbaf871a5c2b476",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "cachix",
|
|
||||||
"repo": "git-hooks.nix",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"gitignore": {
|
|
||||||
"inputs": {
|
|
||||||
"nixpkgs": [
|
|
||||||
"devshell-lib",
|
|
||||||
"git-hooks",
|
|
||||||
"nixpkgs"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1709087332,
|
|
||||||
"narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
|
|
||||||
"owner": "hercules-ci",
|
|
||||||
"repo": "gitignore.nix",
|
|
||||||
"rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "hercules-ci",
|
|
||||||
"repo": "gitignore.nix",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nixpkgs": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1770073757,
|
|
||||||
"narHash": "sha256-Vy+G+F+3E/Tl+GMNgiHl9Pah2DgShmIUBJXmbiQPHbI=",
|
|
||||||
"owner": "NixOS",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"rev": "47472570b1e607482890801aeaf29bfb749884f6",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "NixOS",
|
|
||||||
"ref": "nixpkgs-unstable",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nixpkgs_2": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1770107345,
|
|
||||||
"narHash": "sha256-tbS0Ebx2PiA1FRW8mt8oejR0qMXmziJmPaU1d4kYY9g=",
|
|
||||||
"owner": "nixos",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"rev": "4533d9293756b63904b7238acb84ac8fe4c8c2c4",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nixos",
|
|
||||||
"ref": "nixpkgs-unstable",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nixpkgs_3": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1772542754,
|
|
||||||
"narHash": "sha256-WGV2hy+VIeQsYXpsLjdr4GvHv5eECMISX1zKLTedhdg=",
|
|
||||||
"owner": "nixos",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"rev": "8c809a146a140c5c8806f13399592dbcb1bb5dc4",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nixos",
|
|
||||||
"ref": "nixos-unstable",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"root": {
|
|
||||||
"inputs": {
|
|
||||||
"devshell-lib": "devshell-lib",
|
|
||||||
"nixpkgs": "nixpkgs_3"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"treefmt-nix": {
|
|
||||||
"inputs": {
|
|
||||||
"nixpkgs": "nixpkgs_2"
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1770228511,
|
|
||||||
"narHash": "sha256-wQ6NJSuFqAEmIg2VMnLdCnUc0b7vslUohqqGGD+Fyxk=",
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "treefmt-nix",
|
|
||||||
"rev": "337a4fe074be1042a35086f15481d763b8ddc0e7",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "numtide",
|
|
||||||
"repo": "treefmt-nix",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"root": "root",
|
|
||||||
"version": 7
|
|
||||||
}
|
|
||||||
@@ -1,197 +1,155 @@
|
|||||||
# flake.nix — product repo template
|
|
||||||
{
|
{
|
||||||
description = "my-product";
|
description = "typescript-monorepo";
|
||||||
|
|
||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
|
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
|
||||||
devshell-lib.url = "git+https://git.dgren.dev/eric/nix-flake-lib?ref=v2.1.0";
|
repo-lib.url = "git+https://git.dgren.dev/eric/nix-flake-lib?ref=refs/tags/v3.5.0";
|
||||||
devshell-lib.inputs.nixpkgs.follows = "nixpkgs";
|
repo-lib.inputs.nixpkgs.follows = "nixpkgs";
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs =
|
outputs =
|
||||||
{
|
{
|
||||||
self,
|
self,
|
||||||
nixpkgs,
|
nixpkgs,
|
||||||
devshell-lib,
|
repo-lib,
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
let
|
repo-lib.lib.mkRepo {
|
||||||
supportedSystems = [
|
inherit self nixpkgs;
|
||||||
"x86_64-linux"
|
src = ./.;
|
||||||
"aarch64-linux"
|
|
||||||
"x86_64-darwin"
|
config = {
|
||||||
"aarch64-darwin"
|
shell = {
|
||||||
|
banner = {
|
||||||
|
style = "pretty";
|
||||||
|
icon = "☾";
|
||||||
|
title = "Moonrepo shell ready";
|
||||||
|
titleColor = "GREEN";
|
||||||
|
subtitle = "Bun + TypeScript + Varlock";
|
||||||
|
subtitleColor = "GRAY";
|
||||||
|
borderColor = "BLUE";
|
||||||
|
};
|
||||||
|
|
||||||
|
extraShellText = ''
|
||||||
|
export PATH="$PWD/node_modules/.bin:$PATH"
|
||||||
|
'';
|
||||||
|
|
||||||
|
bootstrap = ''
|
||||||
|
repo_root="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
|
||||||
|
|
||||||
|
export BUN_INSTALL_GLOBAL_DIR="$repo_root/.tools/bun/install/global"
|
||||||
|
export BUN_INSTALL_BIN="$repo_root/.tools/bun/bin"
|
||||||
|
export PATH="$BUN_INSTALL_BIN:$PATH"
|
||||||
|
|
||||||
|
mkdir -p "$BUN_INSTALL_GLOBAL_DIR" "$BUN_INSTALL_BIN"
|
||||||
|
|
||||||
|
if [ ! -x "$BUN_INSTALL_BIN/moon" ]; then
|
||||||
|
bun add -g @moonrepo/cli
|
||||||
|
fi
|
||||||
|
'';
|
||||||
|
allowImpureBootstrap = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
formatting = {
|
||||||
|
programs = {
|
||||||
|
oxfmt.enable = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
settings = {
|
||||||
|
oxfmt.excludes = [
|
||||||
|
"*.css"
|
||||||
|
"*.graphql"
|
||||||
|
"*.hbs"
|
||||||
|
"*.html"
|
||||||
|
"*.md"
|
||||||
|
"*.mdx"
|
||||||
|
"*.mustache"
|
||||||
|
"*.scss"
|
||||||
|
"*.vue"
|
||||||
|
"*.yaml"
|
||||||
|
"*.yml"
|
||||||
];
|
];
|
||||||
forAllSystems = nixpkgs.lib.genAttrs supportedSystems;
|
};
|
||||||
|
|
||||||
mkDevShellConfig = pkgs: {
|
|
||||||
# includeStandardPackages = false; # opt out of nixfmt/gitlint/gitleaks/shfmt defaults
|
|
||||||
|
|
||||||
extraPackages = with pkgs; [
|
|
||||||
# add your tools here, e.g.:
|
|
||||||
# go
|
|
||||||
# bun
|
|
||||||
# rustc
|
|
||||||
];
|
|
||||||
|
|
||||||
features = {
|
|
||||||
# oxfmt = true; # enables oxfmt + oxlint from nixpkgs
|
|
||||||
};
|
};
|
||||||
|
|
||||||
formatters = {
|
release = {
|
||||||
# shfmt.enable = true;
|
steps = [ ];
|
||||||
# gofmt.enable = true;
|
};
|
||||||
};
|
|
||||||
|
|
||||||
formatterSettings = {
|
|
||||||
# shfmt.options = [ "-i" "2" "-s" "-w" ];
|
|
||||||
# oxfmt.includes = [ "*.ts" "*.tsx" "*.js" "*.json" ];
|
|
||||||
};
|
|
||||||
|
|
||||||
additionalHooks = {
|
|
||||||
tests = {
|
|
||||||
enable = true;
|
|
||||||
entry = "echo 'No tests defined yet.'"; # replace with your test command
|
|
||||||
pass_filenames = false;
|
|
||||||
stages = [ "pre-push" ];
|
|
||||||
};
|
|
||||||
# my-hook = {
|
|
||||||
# enable = true;
|
|
||||||
# entry = "${pkgs.some-tool}/bin/some-tool";
|
|
||||||
# pass_filenames = false;
|
|
||||||
# };
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
perSystem =
|
||||||
|
{
|
||||||
|
pkgs,
|
||||||
|
system,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
{
|
||||||
tools = [
|
tools = [
|
||||||
# { name = "Bun"; bin = "${pkgs.bun}/bin/bun"; versionCmd = "--version"; color = "YELLOW"; }
|
(repo-lib.lib.tools.fromCommand {
|
||||||
# { name = "Go"; bin = "${pkgs.go}/bin/go"; versionCmd = "version"; color = "CYAN"; }
|
name = "Nix";
|
||||||
# { name = "Rust"; bin = "${pkgs.rustc}/bin/rustc"; versionCmd = "--version"; color = "YELLOW"; }
|
command = "nix";
|
||||||
# { name = "golangci-lint"; bin = "golangci-lint"; versionCmd = "version"; color = "YELLOW"; }
|
version = {
|
||||||
|
args = [ "--version" ];
|
||||||
|
group = 1;
|
||||||
|
};
|
||||||
|
banner = {
|
||||||
|
color = "BLUE";
|
||||||
|
icon = "";
|
||||||
|
};
|
||||||
|
})
|
||||||
|
|
||||||
|
(repo-lib.lib.tools.fromPackage {
|
||||||
|
name = "Bun";
|
||||||
|
package = pkgs.bun;
|
||||||
|
version.args = [ "--version" ];
|
||||||
|
banner = {
|
||||||
|
color = "YELLOW";
|
||||||
|
icon = "";
|
||||||
|
};
|
||||||
|
})
|
||||||
];
|
];
|
||||||
|
|
||||||
preToolHook = ''
|
shell.packages = [
|
||||||
# runs before the ready banner + tool version logs
|
self.packages.${system}.release
|
||||||
# useful for installing tools outside nixpkgs and updating PATH first
|
pkgs.bun
|
||||||
#
|
pkgs.openbao
|
||||||
# export GOBIN="$PWD/.tools/bin"
|
pkgs.oxfmt
|
||||||
# export PATH="$GOBIN:$PATH"
|
pkgs.oxlint
|
||||||
# if ! command -v golangci-lint >/dev/null 2>&1; then
|
];
|
||||||
# go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
|
|
||||||
# fi
|
|
||||||
'';
|
|
||||||
|
|
||||||
extraShellHook = ''
|
checks.format = {
|
||||||
# any repo-specific shell setup here
|
command = "oxfmt --check .";
|
||||||
'';
|
stage = "pre-commit";
|
||||||
|
passFilenames = false;
|
||||||
|
runtimeInputs = [ pkgs.oxfmt ];
|
||||||
};
|
};
|
||||||
in
|
|
||||||
{
|
|
||||||
devShells = forAllSystems (
|
|
||||||
system:
|
|
||||||
let
|
|
||||||
pkgs = import nixpkgs { inherit system; };
|
|
||||||
config = mkDevShellConfig pkgs;
|
|
||||||
env = devshell-lib.lib.mkDevShell (
|
|
||||||
(
|
|
||||||
{
|
|
||||||
inherit system;
|
|
||||||
src = ./.;
|
|
||||||
}
|
|
||||||
// config
|
|
||||||
)
|
|
||||||
// {
|
|
||||||
extraPackages = config.extraPackages ++ [ self.packages.${system}.release ];
|
|
||||||
}
|
|
||||||
);
|
|
||||||
in
|
|
||||||
{
|
|
||||||
default = env.shell;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
packages = forAllSystems (system: {
|
checks.typecheck = {
|
||||||
release = devshell-lib.lib.mkRelease {
|
command = "bun run typecheck";
|
||||||
inherit system;
|
stage = "pre-push";
|
||||||
|
passFilenames = false;
|
||||||
|
runtimeInputs = [ pkgs.bun ];
|
||||||
};
|
};
|
||||||
});
|
|
||||||
|
|
||||||
checks = forAllSystems (
|
checks.env-check = {
|
||||||
system:
|
command = "bun run env:check";
|
||||||
let
|
stage = "pre-push";
|
||||||
pkgs = import nixpkgs { inherit system; };
|
passFilenames = false;
|
||||||
config = mkDevShellConfig pkgs;
|
runtimeInputs = [
|
||||||
env = devshell-lib.lib.mkDevShell (
|
pkgs.bun
|
||||||
{
|
pkgs.openbao
|
||||||
inherit system;
|
];
|
||||||
src = ./.;
|
};
|
||||||
}
|
|
||||||
// config
|
|
||||||
);
|
|
||||||
in
|
|
||||||
{
|
|
||||||
inherit (env) pre-commit-check;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
formatter = forAllSystems (
|
checks.env-scan = {
|
||||||
system:
|
command = "bun run env:scan";
|
||||||
let
|
stage = "pre-commit";
|
||||||
pkgs = import nixpkgs { inherit system; };
|
passFilenames = false;
|
||||||
config = mkDevShellConfig pkgs;
|
runtimeInputs = [
|
||||||
in
|
pkgs.bun
|
||||||
(devshell-lib.lib.mkDevShell (
|
pkgs.openbao
|
||||||
{
|
];
|
||||||
inherit system;
|
};
|
||||||
src = ./.;
|
};
|
||||||
}
|
|
||||||
// config
|
|
||||||
)).formatter
|
|
||||||
);
|
|
||||||
|
|
||||||
# Release command (`release`)
|
|
||||||
#
|
|
||||||
# The release script always updates VERSION first, then:
|
|
||||||
# 1) runs release steps in order (file writes and scripts)
|
|
||||||
# 2) runs postVersion hook
|
|
||||||
# 3) formats, stages, commits, tags, and pushes
|
|
||||||
#
|
|
||||||
# Runtime env vars available in release.run/postVersion:
|
|
||||||
# BASE_VERSION, CHANNEL, PRERELEASE_NUM, FULL_VERSION, FULL_TAG
|
|
||||||
#
|
|
||||||
# To customize release behavior in your repo, edit:
|
|
||||||
# packages = forAllSystems (
|
|
||||||
# system:
|
|
||||||
# {
|
|
||||||
# release = devshell-lib.lib.mkRelease {
|
|
||||||
# inherit system;
|
|
||||||
#
|
|
||||||
# release = [
|
|
||||||
# {
|
|
||||||
# file = "src/version.ts";
|
|
||||||
# content = ''
|
|
||||||
# export const APP_VERSION = "$FULL_VERSION" as const;
|
|
||||||
# '';
|
|
||||||
# }
|
|
||||||
# {
|
|
||||||
# file = "internal/version/version.go";
|
|
||||||
# content = ''
|
|
||||||
# package version
|
|
||||||
#
|
|
||||||
# const Version = "$FULL_VERSION"
|
|
||||||
# '';
|
|
||||||
# }
|
|
||||||
# {
|
|
||||||
# run = ''
|
|
||||||
# sed -E -i "s#^([[:space:]]*my-lib\\.url = \")github:org/my-lib[^"]*(\";)#\\1github:org/my-lib?ref=$FULL_TAG\\2#" "$ROOT_DIR/flake.nix"
|
|
||||||
# '';
|
|
||||||
# }
|
|
||||||
# ];
|
|
||||||
#
|
|
||||||
# postVersion = ''
|
|
||||||
# echo "Released $FULL_TAG"
|
|
||||||
# '';
|
|
||||||
# };
|
|
||||||
# }
|
|
||||||
# );
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
45
template/moon.yml
Normal file
45
template/moon.yml
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
$schema: "./.moon/cache/schemas/project.json"
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
typecheck:
|
||||||
|
command: "bun"
|
||||||
|
args:
|
||||||
|
- "run"
|
||||||
|
- "typecheck"
|
||||||
|
inputs:
|
||||||
|
- "package.json"
|
||||||
|
- "tsconfig.json"
|
||||||
|
- "tsconfig.options.json"
|
||||||
|
- "tsconfig/**/*"
|
||||||
|
options:
|
||||||
|
cache: false
|
||||||
|
runFromWorkspaceRoot: true
|
||||||
|
|
||||||
|
env-check:
|
||||||
|
command: "bun"
|
||||||
|
args:
|
||||||
|
- "run"
|
||||||
|
- "env:check"
|
||||||
|
inputs:
|
||||||
|
- ".env.schema"
|
||||||
|
- "package.json"
|
||||||
|
- "bunfig.toml"
|
||||||
|
toolchains: "system"
|
||||||
|
options:
|
||||||
|
cache: false
|
||||||
|
runFromWorkspaceRoot: true
|
||||||
|
|
||||||
|
env-scan:
|
||||||
|
command: "bun"
|
||||||
|
args:
|
||||||
|
- "run"
|
||||||
|
- "env:scan"
|
||||||
|
inputs:
|
||||||
|
- ".env.schema"
|
||||||
|
- "package.json"
|
||||||
|
- "bunfig.toml"
|
||||||
|
toolchains: "system"
|
||||||
|
options:
|
||||||
|
cache: false
|
||||||
|
runFromWorkspaceRoot: true
|
||||||
|
runInCI: "skip"
|
||||||
23
template/package.json
Normal file
23
template/package.json
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"name": "typescript-monorepo",
|
||||||
|
"private": true,
|
||||||
|
"workspaces": [
|
||||||
|
"apps/*",
|
||||||
|
"packages/*"
|
||||||
|
],
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"typecheck": "bunx tsc -p tsconfig.json --pretty false",
|
||||||
|
"env:check": "sh -ec 'export BAO_ADDR=\"${BAO_ADDR:-${OPENBAO_ADDR:-}}\"; export BAO_NAMESPACE=\"${BAO_NAMESPACE:-${OPENBAO_NAMESPACE:-}}\"; export BAO_CACERT=\"${BAO_CACERT:-${OPENBAO_CACERT:-}}\"; exec varlock load --show-all'",
|
||||||
|
"env:scan": "sh -ec 'export BAO_ADDR=\"${BAO_ADDR:-${OPENBAO_ADDR:-}}\"; export BAO_NAMESPACE=\"${BAO_NAMESPACE:-${OPENBAO_NAMESPACE:-}}\"; export BAO_CACERT=\"${BAO_CACERT:-${OPENBAO_CACERT:-}}\"; exec varlock scan --staged'",
|
||||||
|
"check": "bun run typecheck && bun run env:check"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@moonrepo/cli": "^2.0.4"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/bun": "latest",
|
||||||
|
"typescript": "^6.0.0-beta",
|
||||||
|
"varlock": "0.5.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
1
template/packages/.gitkeep
Normal file
1
template/packages/.gitkeep
Normal file
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
4
template/tsconfig.json
Normal file
4
template/tsconfig.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.options.json",
|
||||||
|
"files": []
|
||||||
|
}
|
||||||
19
template/tsconfig.options.json
Normal file
19
template/tsconfig.options.json
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ESNext",
|
||||||
|
"lib": ["ESNext"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Bundler",
|
||||||
|
"strict": true,
|
||||||
|
"noUncheckedIndexedAccess": true,
|
||||||
|
"exactOptionalPropertyTypes": true,
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"noUncheckedSideEffectImports": true,
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"noEmit": true
|
||||||
|
}
|
||||||
|
}
|
||||||
6
template/tsconfig/browser.json
Normal file
6
template/tsconfig/browser.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"extends": "./runtime.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"types": ["vite/client"]
|
||||||
|
}
|
||||||
|
}
|
||||||
6
template/tsconfig/bun.json
Normal file
6
template/tsconfig/bun.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"extends": "./runtime.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"types": ["@types/bun"]
|
||||||
|
}
|
||||||
|
}
|
||||||
6
template/tsconfig/package.json
Normal file
6
template/tsconfig/package.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"types": []
|
||||||
|
},
|
||||||
|
"extends": "../tsconfig.options.json"
|
||||||
|
}
|
||||||
6
template/tsconfig/runtime.json
Normal file
6
template/tsconfig/runtime.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"extends": "../tsconfig.options.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"lib": ["ESNext", "DOM", "DOM.Iterable"]
|
||||||
|
}
|
||||||
|
}
|
||||||
798
tests/release.sh
798
tests/release.sh
@@ -4,7 +4,13 @@ set -euo pipefail
|
|||||||
|
|
||||||
ROOT_DIR="${REPO_LIB_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}"
|
ROOT_DIR="${REPO_LIB_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}"
|
||||||
RELEASE_TEMPLATE="$ROOT_DIR/packages/release/release.sh"
|
RELEASE_TEMPLATE="$ROOT_DIR/packages/release/release.sh"
|
||||||
|
NIXPKGS_FLAKE_PATH="${NIXPKGS_FLAKE_PATH:-}"
|
||||||
CURRENT_LOG=""
|
CURRENT_LOG=""
|
||||||
|
QC_SEEN_TAGS=()
|
||||||
|
|
||||||
|
if [[ -z "$NIXPKGS_FLAKE_PATH" ]]; then
|
||||||
|
NIXPKGS_FLAKE_PATH="$(nix eval --raw --impure --expr "(builtins.getFlake (toString ${ROOT_DIR})).inputs.nixpkgs.outPath")"
|
||||||
|
fi
|
||||||
|
|
||||||
fail() {
|
fail() {
|
||||||
echo "[test] FAIL: $*" >&2
|
echo "[test] FAIL: $*" >&2
|
||||||
@@ -29,7 +35,7 @@ assert_contains() {
|
|||||||
local needle="$1"
|
local needle="$1"
|
||||||
local haystack_file="$2"
|
local haystack_file="$2"
|
||||||
local message="$3"
|
local message="$3"
|
||||||
if ! grep -Fq "$needle" "$haystack_file"; then
|
if ! grep -Fq -- "$needle" "$haystack_file"; then
|
||||||
fail "$message (missing '$needle')"
|
fail "$message (missing '$needle')"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
@@ -44,11 +50,20 @@ run_capture_ok() {
|
|||||||
|
|
||||||
make_release_script() {
|
make_release_script() {
|
||||||
local target="$1"
|
local target="$1"
|
||||||
sed \
|
make_release_script_with_content "$target" ":" ":"
|
||||||
-e 's/__CHANNEL_LIST__/alpha beta rc internal/g' \
|
}
|
||||||
-e 's/__RELEASE_STEPS__/:/' \
|
|
||||||
-e 's/__POST_VERSION__/:/' \
|
make_release_script_with_content() {
|
||||||
"$RELEASE_TEMPLATE" >"$target"
|
local target="$1"
|
||||||
|
local release_steps="$2"
|
||||||
|
local post_version="$3"
|
||||||
|
local script
|
||||||
|
|
||||||
|
script="$(cat "$RELEASE_TEMPLATE")"
|
||||||
|
script="${script//__CHANNEL_LIST__/alpha beta rc internal}"
|
||||||
|
script="${script//__RELEASE_STEPS__/$release_steps}"
|
||||||
|
script="${script//__POST_VERSION__/$post_version}"
|
||||||
|
printf '%s' "$script" >"$target"
|
||||||
chmod +x "$target"
|
chmod +x "$target"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,6 +75,8 @@ setup_repo() {
|
|||||||
run_capture_ok "setup_repo: git init failed" git -C "$repo_dir" init
|
run_capture_ok "setup_repo: git init failed" git -C "$repo_dir" init
|
||||||
run_capture_ok "setup_repo: git config user.name failed" git -C "$repo_dir" config user.name "Release Test"
|
run_capture_ok "setup_repo: git config user.name failed" git -C "$repo_dir" config user.name "Release Test"
|
||||||
run_capture_ok "setup_repo: git config user.email failed" git -C "$repo_dir" config user.email "release-test@example.com"
|
run_capture_ok "setup_repo: git config user.email failed" git -C "$repo_dir" config user.email "release-test@example.com"
|
||||||
|
run_capture_ok "setup_repo: git config commit.gpgsign failed" git -C "$repo_dir" config commit.gpgsign false
|
||||||
|
run_capture_ok "setup_repo: git config tag.gpgsign failed" git -C "$repo_dir" config tag.gpgsign false
|
||||||
|
|
||||||
cat >"$repo_dir/flake.nix" <<'EOF'
|
cat >"$repo_dir/flake.nix" <<'EOF'
|
||||||
{
|
{
|
||||||
@@ -110,6 +127,27 @@ EOF
|
|||||||
chmod +x "$repo_dir/bin/nix"
|
chmod +x "$repo_dir/bin/nix"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
prepare_case_repo_with_release_script() {
|
||||||
|
local repo_dir="$1"
|
||||||
|
local remote_dir="$2"
|
||||||
|
local release_steps="$3"
|
||||||
|
local post_version="$4"
|
||||||
|
|
||||||
|
setup_repo "$repo_dir" "$remote_dir"
|
||||||
|
make_release_script_with_content "$repo_dir/release" "$release_steps" "$post_version"
|
||||||
|
|
||||||
|
mkdir -p "$repo_dir/bin"
|
||||||
|
cat >"$repo_dir/bin/nix" <<'EOF'
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
if [[ "${1-}" == "fmt" ]]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo "unexpected nix invocation: $*" >&2
|
||||||
|
exit 1
|
||||||
|
EOF
|
||||||
|
chmod +x "$repo_dir/bin/nix"
|
||||||
|
}
|
||||||
|
|
||||||
run_release() {
|
run_release() {
|
||||||
local repo_dir="$1"
|
local repo_dir="$1"
|
||||||
shift
|
shift
|
||||||
@@ -119,6 +157,342 @@ run_release() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
run_expect_failure() {
|
||||||
|
local description="$1"
|
||||||
|
shift
|
||||||
|
if "$@" >>"$CURRENT_LOG" 2>&1; then
|
||||||
|
fail "$description (expected failure)"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
write_mk_repo_flake() {
|
||||||
|
local repo_dir="$1"
|
||||||
|
cat >"$repo_dir/flake.nix" <<EOF
|
||||||
|
{
|
||||||
|
description = "mkRepo ok";
|
||||||
|
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.url = "path:${NIXPKGS_FLAKE_PATH}";
|
||||||
|
repo-lib.url = "path:${ROOT_DIR}";
|
||||||
|
repo-lib.inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs = { self, nixpkgs, repo-lib, ... }:
|
||||||
|
repo-lib.lib.mkRepo {
|
||||||
|
inherit self nixpkgs;
|
||||||
|
src = ./.;
|
||||||
|
|
||||||
|
config = {
|
||||||
|
checks.tests = {
|
||||||
|
command = "echo test";
|
||||||
|
stage = "pre-push";
|
||||||
|
passFilenames = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
release = {
|
||||||
|
steps = [ ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
perSystem = { pkgs, system, ... }: {
|
||||||
|
tools = [
|
||||||
|
(repo-lib.lib.tools.fromPackage {
|
||||||
|
name = "Hello";
|
||||||
|
package = pkgs.hello;
|
||||||
|
exe = "hello";
|
||||||
|
version.args = [ "--version" ];
|
||||||
|
})
|
||||||
|
];
|
||||||
|
|
||||||
|
shell.packages = [
|
||||||
|
self.packages.\${system}.release
|
||||||
|
];
|
||||||
|
|
||||||
|
packages.example = pkgs.writeShellApplication {
|
||||||
|
name = "example";
|
||||||
|
text = ''
|
||||||
|
echo example
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
write_mk_repo_command_tool_flake() {
|
||||||
|
local repo_dir="$1"
|
||||||
|
cat >"$repo_dir/flake.nix" <<EOF
|
||||||
|
{
|
||||||
|
description = "mkRepo command-backed tool";
|
||||||
|
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.url = "path:${NIXPKGS_FLAKE_PATH}";
|
||||||
|
repo-lib.url = "path:${ROOT_DIR}";
|
||||||
|
repo-lib.inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs = { self, nixpkgs, repo-lib, ... }:
|
||||||
|
repo-lib.lib.mkRepo {
|
||||||
|
inherit self nixpkgs;
|
||||||
|
src = ./.;
|
||||||
|
|
||||||
|
config.release = {
|
||||||
|
steps = [ ];
|
||||||
|
};
|
||||||
|
|
||||||
|
perSystem = { system, ... }: {
|
||||||
|
tools = [
|
||||||
|
(repo-lib.lib.tools.fromCommand {
|
||||||
|
name = "Nix";
|
||||||
|
command = "nix";
|
||||||
|
version.args = [ "--version" ];
|
||||||
|
banner = {
|
||||||
|
color = "BLUE";
|
||||||
|
icon = "";
|
||||||
|
};
|
||||||
|
})
|
||||||
|
];
|
||||||
|
|
||||||
|
shell.packages = [
|
||||||
|
self.packages.\${system}.release
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
write_mk_repo_lefthook_flake() {
|
||||||
|
local repo_dir="$1"
|
||||||
|
cat >"$repo_dir/flake.nix" <<EOF
|
||||||
|
{
|
||||||
|
description = "mkRepo raw lefthook config";
|
||||||
|
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.url = "path:${NIXPKGS_FLAKE_PATH}";
|
||||||
|
repo-lib.url = "path:${ROOT_DIR}";
|
||||||
|
repo-lib.inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs = { self, nixpkgs, repo-lib, ... }:
|
||||||
|
repo-lib.lib.mkRepo {
|
||||||
|
inherit self nixpkgs;
|
||||||
|
src = ./.;
|
||||||
|
|
||||||
|
config = {
|
||||||
|
checks.tests = {
|
||||||
|
command = "echo test";
|
||||||
|
stage = "pre-push";
|
||||||
|
passFilenames = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
lefthook.pre-push.commands.tests.stage_fixed = true;
|
||||||
|
|
||||||
|
release.steps = [ ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
init_git_repo() {
|
||||||
|
local repo_dir="$1"
|
||||||
|
|
||||||
|
run_capture_ok "init_git_repo: git init failed" git -C "$repo_dir" init
|
||||||
|
run_capture_ok "init_git_repo: git config user.name failed" git -C "$repo_dir" config user.name "Repo Lib Test"
|
||||||
|
run_capture_ok "init_git_repo: git config user.email failed" git -C "$repo_dir" config user.email "repo-lib-test@example.com"
|
||||||
|
run_capture_ok "init_git_repo: git add failed" git -C "$repo_dir" add flake.nix
|
||||||
|
run_capture_ok "init_git_repo: git commit failed" git -C "$repo_dir" commit -m "init"
|
||||||
|
}
|
||||||
|
|
||||||
|
write_tool_failure_flake() {
|
||||||
|
local repo_dir="$1"
|
||||||
|
cat >"$repo_dir/flake.nix" <<EOF
|
||||||
|
{
|
||||||
|
description = "mkRepo tool failure";
|
||||||
|
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.url = "path:${NIXPKGS_FLAKE_PATH}";
|
||||||
|
repo-lib.url = "path:${ROOT_DIR}";
|
||||||
|
repo-lib.inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs = { self, nixpkgs, repo-lib, ... }:
|
||||||
|
repo-lib.lib.mkRepo {
|
||||||
|
inherit self nixpkgs;
|
||||||
|
src = ./.;
|
||||||
|
|
||||||
|
config.release = {
|
||||||
|
steps = [ ];
|
||||||
|
};
|
||||||
|
|
||||||
|
perSystem = { pkgs, ... }: {
|
||||||
|
tools = [
|
||||||
|
(repo-lib.lib.tools.fromPackage {
|
||||||
|
name = "Hello";
|
||||||
|
package = pkgs.hello;
|
||||||
|
exe = "hello";
|
||||||
|
version.args = [ "--definitely-invalid" ];
|
||||||
|
})
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
write_impure_bootstrap_flake() {
|
||||||
|
local repo_dir="$1"
|
||||||
|
cat >"$repo_dir/flake.nix" <<EOF
|
||||||
|
{
|
||||||
|
description = "mkRepo bootstrap validation";
|
||||||
|
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.url = "path:${NIXPKGS_FLAKE_PATH}";
|
||||||
|
repo-lib.url = "path:${ROOT_DIR}";
|
||||||
|
repo-lib.inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs = { self, nixpkgs, repo-lib, ... }:
|
||||||
|
repo-lib.lib.mkRepo {
|
||||||
|
inherit self nixpkgs;
|
||||||
|
src = ./.;
|
||||||
|
|
||||||
|
config.shell.bootstrap = ''
|
||||||
|
echo hi
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
write_release_replace_backref_flake() {
|
||||||
|
local repo_dir="$1"
|
||||||
|
cat >"$repo_dir/flake.nix" <<EOF
|
||||||
|
{
|
||||||
|
description = "mkRepo release replace backrefs";
|
||||||
|
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.url = "path:${NIXPKGS_FLAKE_PATH}";
|
||||||
|
repo-lib.url = "path:${ROOT_DIR}";
|
||||||
|
repo-lib.inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs = { self, nixpkgs, repo-lib, ... }:
|
||||||
|
repo-lib.lib.mkRepo {
|
||||||
|
inherit self nixpkgs;
|
||||||
|
src = ./.;
|
||||||
|
|
||||||
|
config.release = {
|
||||||
|
steps = [
|
||||||
|
{
|
||||||
|
replace = {
|
||||||
|
path = "template/flake.nix";
|
||||||
|
regex = ''^([[:space:]]*repo-lib\.url = ")[^"]*(";)$'';
|
||||||
|
replacement = ''\1git+https://example.invalid/repo-lib?ref=refs/tags/\$FULL_TAG\2'';
|
||||||
|
};
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
write_legacy_flake() {
|
||||||
|
local repo_dir="$1"
|
||||||
|
cat >"$repo_dir/flake.nix" <<EOF
|
||||||
|
{
|
||||||
|
description = "legacy api";
|
||||||
|
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.url = "path:${NIXPKGS_FLAKE_PATH}";
|
||||||
|
repo-lib.url = "path:${ROOT_DIR}";
|
||||||
|
repo-lib.inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs =
|
||||||
|
{ self, nixpkgs, repo-lib, ... }:
|
||||||
|
let
|
||||||
|
systems = [
|
||||||
|
"x86_64-linux"
|
||||||
|
"aarch64-linux"
|
||||||
|
"x86_64-darwin"
|
||||||
|
"aarch64-darwin"
|
||||||
|
];
|
||||||
|
forAllSystems = nixpkgs.lib.genAttrs systems;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
devShells = forAllSystems (
|
||||||
|
system:
|
||||||
|
let
|
||||||
|
pkgs = import nixpkgs { inherit system; };
|
||||||
|
env = repo-lib.lib.mkDevShell {
|
||||||
|
inherit system;
|
||||||
|
nixpkgsInput = nixpkgs;
|
||||||
|
src = ./.;
|
||||||
|
extraPackages = [ self.packages.\${system}.release ];
|
||||||
|
tools = [
|
||||||
|
{
|
||||||
|
name = "Nix";
|
||||||
|
bin = "\${pkgs.nix}/bin/nix";
|
||||||
|
versionCmd = "--version";
|
||||||
|
color = "YELLOW";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
default = env.shell;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
checks = forAllSystems (
|
||||||
|
system:
|
||||||
|
let
|
||||||
|
env = repo-lib.lib.mkDevShell {
|
||||||
|
inherit system;
|
||||||
|
nixpkgsInput = nixpkgs;
|
||||||
|
src = ./.;
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
inherit (env) lefthook-check;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
formatter = forAllSystems (
|
||||||
|
system:
|
||||||
|
(repo-lib.lib.mkDevShell {
|
||||||
|
inherit system;
|
||||||
|
nixpkgsInput = nixpkgs;
|
||||||
|
src = ./.;
|
||||||
|
}).formatter
|
||||||
|
);
|
||||||
|
|
||||||
|
packages = forAllSystems (system: {
|
||||||
|
release = repo-lib.lib.mkRelease {
|
||||||
|
inherit system;
|
||||||
|
nixpkgsInput = nixpkgs;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
write_template_fixture() {
|
||||||
|
local repo_dir="$1"
|
||||||
|
mkdir -p "$repo_dir"
|
||||||
|
cp -R "$ROOT_DIR/template/." "$repo_dir/"
|
||||||
|
sed -i.bak \
|
||||||
|
-e "s|git+https://git.dgren.dev/eric/nix-flake-lib?ref=refs/tags/v[0-9.]*|path:${ROOT_DIR}|" \
|
||||||
|
-e "s|github:nixos/nixpkgs?ref=nixos-unstable|path:${NIXPKGS_FLAKE_PATH}|" \
|
||||||
|
"$repo_dir/flake.nix"
|
||||||
|
rm -f "$repo_dir/flake.nix.bak"
|
||||||
|
}
|
||||||
|
|
||||||
qc_version_cmp() {
|
qc_version_cmp() {
|
||||||
# Returns: 0 if equal, 1 if v1 > v2, 2 if v1 < v2
|
# Returns: 0 if equal, 1 if v1 > v2, 2 if v1 < v2
|
||||||
local v1="$1" v2="$2"
|
local v1="$1" v2="$2"
|
||||||
@@ -218,6 +592,18 @@ qc_oracle_init() {
|
|||||||
QC_STATE_BASE="1.0.0"
|
QC_STATE_BASE="1.0.0"
|
||||||
QC_STATE_CHANNEL="stable"
|
QC_STATE_CHANNEL="stable"
|
||||||
QC_STATE_PRE=""
|
QC_STATE_PRE=""
|
||||||
|
QC_SEEN_TAGS=()
|
||||||
|
}
|
||||||
|
|
||||||
|
qc_seen_tag() {
|
||||||
|
local tag="$1"
|
||||||
|
local existing
|
||||||
|
for existing in "${QC_SEEN_TAGS[@]:-}"; do
|
||||||
|
if [[ "$existing" == "$tag" ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
qc_oracle_current_full() {
|
qc_oracle_current_full() {
|
||||||
@@ -298,12 +684,16 @@ qc_oracle_apply() {
|
|||||||
if [[ $cmp_status -eq 0 || $cmp_status -eq 2 ]]; then
|
if [[ $cmp_status -eq 0 || $cmp_status -eq 2 ]]; then
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
if qc_seen_tag "v$QC_FULL_VERSION"; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
QC_STATE_BASE="$QC_BASE_VERSION"
|
QC_STATE_BASE="$QC_BASE_VERSION"
|
||||||
QC_STATE_CHANNEL="$QC_CHANNEL"
|
QC_STATE_CHANNEL="$QC_CHANNEL"
|
||||||
QC_STATE_PRE="$QC_PRERELEASE_NUM"
|
QC_STATE_PRE="$QC_PRERELEASE_NUM"
|
||||||
QC_EXPECT_SUCCESS=1
|
QC_EXPECT_SUCCESS=1
|
||||||
QC_EXPECT_VERSION="$QC_FULL_VERSION"
|
QC_EXPECT_VERSION="$QC_FULL_VERSION"
|
||||||
|
QC_SEEN_TAGS+=("v$QC_FULL_VERSION")
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -371,12 +761,16 @@ qc_oracle_apply() {
|
|||||||
if [[ $QC_FULL_VERSION == "$current_full" ]]; then
|
if [[ $QC_FULL_VERSION == "$current_full" ]]; then
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
if qc_seen_tag "v$QC_FULL_VERSION"; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
QC_STATE_BASE="$QC_BASE_VERSION"
|
QC_STATE_BASE="$QC_BASE_VERSION"
|
||||||
QC_STATE_CHANNEL="$QC_CHANNEL"
|
QC_STATE_CHANNEL="$QC_CHANNEL"
|
||||||
QC_STATE_PRE="$QC_PRERELEASE_NUM"
|
QC_STATE_PRE="$QC_PRERELEASE_NUM"
|
||||||
QC_EXPECT_SUCCESS=1
|
QC_EXPECT_SUCCESS=1
|
||||||
QC_EXPECT_VERSION="$QC_FULL_VERSION"
|
QC_EXPECT_VERSION="$QC_FULL_VERSION"
|
||||||
|
QC_SEEN_TAGS+=("v$QC_FULL_VERSION")
|
||||||
}
|
}
|
||||||
|
|
||||||
run_randomized_quickcheck_cases() {
|
run_randomized_quickcheck_cases() {
|
||||||
@@ -520,6 +914,38 @@ run_set_prerelease_then_full_case() {
|
|||||||
echo "[test] PASS: $case_name" >&2
|
echo "[test] PASS: $case_name" >&2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
run_stable_then_beta_cannot_reuse_same_base_case() {
|
||||||
|
local case_name="stable release cannot go back to same-base beta"
|
||||||
|
|
||||||
|
local workdir
|
||||||
|
workdir="$(mktemp -d)"
|
||||||
|
local repo_dir="$workdir/repo"
|
||||||
|
local remote_dir="$workdir/remote.git"
|
||||||
|
CURRENT_LOG="$workdir/case.log"
|
||||||
|
|
||||||
|
prepare_case_repo "$repo_dir" "$remote_dir"
|
||||||
|
|
||||||
|
run_capture_ok "$case_name: initial beta release failed" run_release "$repo_dir" beta
|
||||||
|
run_capture_ok "$case_name: stable promotion failed" run_release "$repo_dir" full
|
||||||
|
run_capture_ok "$case_name: second beta release failed" run_release "$repo_dir" beta
|
||||||
|
|
||||||
|
local got_version
|
||||||
|
got_version="$(version_from_file "$repo_dir")"
|
||||||
|
assert_eq "1.0.2-beta.1" "$got_version" "$case_name: VERSION mismatch"
|
||||||
|
|
||||||
|
if ! git -C "$repo_dir" tag --list | grep -qx "v1.0.1"; then
|
||||||
|
fail "$case_name: expected stable tag v1.0.1 was not created"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! git -C "$repo_dir" tag --list | grep -qx "v1.0.2-beta.1"; then
|
||||||
|
fail "$case_name: expected tag v1.0.2-beta.1 was not created"
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -rf "$workdir"
|
||||||
|
CURRENT_LOG=""
|
||||||
|
echo "[test] PASS: $case_name" >&2
|
||||||
|
}
|
||||||
|
|
||||||
run_set_stable_then_full_noop_case() {
|
run_set_stable_then_full_noop_case() {
|
||||||
local case_name="set stable then full fails with no-op"
|
local case_name="set stable then full fails with no-op"
|
||||||
|
|
||||||
@@ -646,12 +1072,370 @@ run_patch_stable_from_prerelease_requires_full_case() {
|
|||||||
echo "[test] PASS: $case_name" >&2
|
echo "[test] PASS: $case_name" >&2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
run_structured_release_steps_case() {
|
||||||
|
local case_name="structured release steps update files"
|
||||||
|
local release_steps
|
||||||
|
local post_version
|
||||||
|
|
||||||
|
read -r -d '' release_steps <<'EOF' || true
|
||||||
|
target_path="$ROOT_DIR/generated/version.txt"
|
||||||
|
mkdir -p "$(dirname "$target_path")"
|
||||||
|
cat >"$target_path" << NIXEOF
|
||||||
|
$FULL_VERSION
|
||||||
|
NIXEOF
|
||||||
|
log "Generated version file: generated/version.txt"
|
||||||
|
|
||||||
|
target_path="$ROOT_DIR/notes.txt"
|
||||||
|
REPO_LIB_STEP_REGEX=$(cat <<'NIXEOF'
|
||||||
|
^version=.*$
|
||||||
|
NIXEOF
|
||||||
|
)
|
||||||
|
REPO_LIB_STEP_REPLACEMENT=$(cat <<NIXEOF
|
||||||
|
version=$FULL_VERSION
|
||||||
|
NIXEOF
|
||||||
|
)
|
||||||
|
export REPO_LIB_STEP_REGEX REPO_LIB_STEP_REPLACEMENT
|
||||||
|
perl -0pi -e 'my $regex = $ENV{"REPO_LIB_STEP_REGEX"}; my $replacement = $ENV{"REPO_LIB_STEP_REPLACEMENT"}; s/$regex/$replacement/gms;' "$target_path"
|
||||||
|
log "Updated notes.txt"
|
||||||
|
|
||||||
|
printf '%s\n' "$FULL_TAG" >"$ROOT_DIR/release.tag"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
read -r -d '' post_version <<'EOF' || true
|
||||||
|
printf '%s\n' "$FULL_VERSION" >"$ROOT_DIR/post-version.txt"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
local workdir
|
||||||
|
workdir="$(mktemp -d)"
|
||||||
|
local repo_dir="$workdir/repo"
|
||||||
|
local remote_dir="$workdir/remote.git"
|
||||||
|
CURRENT_LOG="$workdir/case.log"
|
||||||
|
|
||||||
|
prepare_case_repo_with_release_script "$repo_dir" "$remote_dir" "$release_steps" "$post_version"
|
||||||
|
printf 'version=old\n' >"$repo_dir/notes.txt"
|
||||||
|
run_capture_ok "$case_name: setup commit failed" git -C "$repo_dir" add notes.txt
|
||||||
|
run_capture_ok "$case_name: setup commit failed" git -C "$repo_dir" commit -m "chore: add notes"
|
||||||
|
|
||||||
|
run_capture_ok "$case_name: release command failed" run_release "$repo_dir" patch
|
||||||
|
|
||||||
|
assert_eq "1.0.1" "$(version_from_file "$repo_dir")" "$case_name: VERSION mismatch"
|
||||||
|
assert_eq "1.0.1" "$(tr -d '\r' <"$repo_dir/generated/version.txt")" "$case_name: generated version file mismatch"
|
||||||
|
assert_eq "version=1.0.1" "$(tr -d '\r' <"$repo_dir/notes.txt")" "$case_name: replace step mismatch"
|
||||||
|
assert_eq "v1.0.1" "$(tr -d '\r' <"$repo_dir/release.tag")" "$case_name: run step mismatch"
|
||||||
|
assert_eq "1.0.1" "$(tr -d '\r' <"$repo_dir/post-version.txt")" "$case_name: postVersion mismatch"
|
||||||
|
|
||||||
|
if ! git -C "$repo_dir" tag --list | grep -qx "v1.0.1"; then
|
||||||
|
fail "$case_name: expected tag v1.0.1 was not created"
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -rf "$workdir"
|
||||||
|
CURRENT_LOG=""
|
||||||
|
echo "[test] PASS: $case_name" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
run_version_metadata_case() {
|
||||||
|
local case_name="release metadata is preserved and exported"
|
||||||
|
local release_steps
|
||||||
|
|
||||||
|
read -r -d '' release_steps <<'EOF' || true
|
||||||
|
if [[ "$(version_meta_get desktop_backend_change_scope)" != "bindings" ]]; then
|
||||||
|
echo "metadata getter mismatch" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [[ "${VERSION_META_DESKTOP_BACKEND_CHANGE_SCOPE:-}" != "bindings" ]]; then
|
||||||
|
echo "metadata export mismatch" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [[ "${VERSION_META_DESKTOP_RELEASE_MODE:-}" != "binary" ]]; then
|
||||||
|
echo "metadata export mismatch" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
version_meta_set desktop_release_mode codepush
|
||||||
|
version_meta_set desktop_binary_version_min 1.0.0
|
||||||
|
version_meta_set desktop_binary_version_max "$FULL_VERSION"
|
||||||
|
version_meta_set desktop_backend_compat_id compat-123
|
||||||
|
version_meta_unset desktop_unused
|
||||||
|
EOF
|
||||||
|
|
||||||
|
local workdir
|
||||||
|
workdir="$(mktemp -d)"
|
||||||
|
local repo_dir="$workdir/repo"
|
||||||
|
local remote_dir="$workdir/remote.git"
|
||||||
|
CURRENT_LOG="$workdir/case.log"
|
||||||
|
|
||||||
|
prepare_case_repo_with_release_script "$repo_dir" "$remote_dir" "$release_steps" ":"
|
||||||
|
cat >"$repo_dir/VERSION" <<'EOF'
|
||||||
|
1.0.0
|
||||||
|
stable
|
||||||
|
0
|
||||||
|
desktop_backend_change_scope=bindings
|
||||||
|
desktop_release_mode=binary
|
||||||
|
desktop_unused=temporary
|
||||||
|
EOF
|
||||||
|
run_capture_ok "$case_name: setup commit failed" git -C "$repo_dir" add VERSION
|
||||||
|
run_capture_ok "$case_name: setup commit failed" git -C "$repo_dir" commit -m "chore: seed metadata"
|
||||||
|
run_capture_ok "$case_name: release command failed" run_release "$repo_dir" patch
|
||||||
|
|
||||||
|
assert_eq "1.0.1" "$(version_from_file "$repo_dir")" "$case_name: VERSION mismatch"
|
||||||
|
assert_contains "desktop_backend_change_scope=bindings" "$repo_dir/VERSION" "$case_name: missing preserved scope"
|
||||||
|
assert_contains "desktop_release_mode=codepush" "$repo_dir/VERSION" "$case_name: missing updated mode"
|
||||||
|
assert_contains "desktop_binary_version_min=1.0.0" "$repo_dir/VERSION" "$case_name: missing min version"
|
||||||
|
assert_contains "desktop_binary_version_max=1.0.1" "$repo_dir/VERSION" "$case_name: missing max version"
|
||||||
|
assert_contains "desktop_backend_compat_id=compat-123" "$repo_dir/VERSION" "$case_name: missing compat id"
|
||||||
|
if grep -Fq "desktop_unused=temporary" "$repo_dir/VERSION"; then
|
||||||
|
fail "$case_name: unset metadata key was preserved"
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -rf "$workdir"
|
||||||
|
CURRENT_LOG=""
|
||||||
|
echo "[test] PASS: $case_name" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
run_mk_repo_case() {
|
||||||
|
local case_name="mkRepo exposes outputs and auto-installs tools"
|
||||||
|
local workdir
|
||||||
|
workdir="$(mktemp -d)"
|
||||||
|
local repo_dir="$workdir/mk-repo"
|
||||||
|
mkdir -p "$repo_dir"
|
||||||
|
write_mk_repo_flake "$repo_dir"
|
||||||
|
CURRENT_LOG="$workdir/mk-repo.log"
|
||||||
|
|
||||||
|
run_capture_ok "$case_name: flake show failed" nix flake show --json --no-write-lock-file "$repo_dir"
|
||||||
|
assert_contains '"lefthook-check"' "$CURRENT_LOG" "$case_name: missing lefthook-check"
|
||||||
|
assert_contains '"release"' "$CURRENT_LOG" "$case_name: missing release package"
|
||||||
|
assert_contains '"example"' "$CURRENT_LOG" "$case_name: missing merged package"
|
||||||
|
|
||||||
|
run_capture_ok "$case_name: tool package should be available in shell" bash -c 'cd "$1" && nix develop --no-write-lock-file . -c hello --version' _ "$repo_dir"
|
||||||
|
run_capture_ok "$case_name: release package should be available in shell" bash -c 'cd "$1" && nix develop --no-write-lock-file . -c sh -c "command -v release >/dev/null"' _ "$repo_dir"
|
||||||
|
|
||||||
|
rm -rf "$workdir"
|
||||||
|
CURRENT_LOG=""
|
||||||
|
echo "[test] PASS: $case_name" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
run_mk_repo_command_tool_case() {
|
||||||
|
local case_name="mkRepo supports command-backed tools from PATH"
|
||||||
|
local workdir
|
||||||
|
workdir="$(mktemp -d)"
|
||||||
|
local repo_dir="$workdir/mk-repo-command-tool"
|
||||||
|
mkdir -p "$repo_dir"
|
||||||
|
write_mk_repo_command_tool_flake "$repo_dir"
|
||||||
|
CURRENT_LOG="$workdir/mk-repo-command-tool.log"
|
||||||
|
|
||||||
|
run_capture_ok "$case_name: flake show failed" nix flake show --json --no-write-lock-file "$repo_dir"
|
||||||
|
assert_contains '"lefthook-check"' "$CURRENT_LOG" "$case_name: missing lefthook-check"
|
||||||
|
assert_contains '"release"' "$CURRENT_LOG" "$case_name: missing release package"
|
||||||
|
|
||||||
|
run_capture_ok "$case_name: system nix should be available in shell" bash -c 'cd "$1" && nix develop --no-write-lock-file . -c nix --version' _ "$repo_dir"
|
||||||
|
assert_contains "" "$CURRENT_LOG" "$case_name: missing tool icon in banner"
|
||||||
|
run_capture_ok "$case_name: release package should be available in shell" bash -c 'cd "$1" && nix develop --no-write-lock-file . -c sh -c "command -v release >/dev/null"' _ "$repo_dir"
|
||||||
|
|
||||||
|
rm -rf "$workdir"
|
||||||
|
CURRENT_LOG=""
|
||||||
|
echo "[test] PASS: $case_name" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
run_mk_repo_lefthook_case() {
|
||||||
|
local case_name="mkRepo exposes raw lefthook config for advanced hook fields"
|
||||||
|
local workdir
|
||||||
|
workdir="$(mktemp -d)"
|
||||||
|
local repo_dir="$workdir/mk-repo-lefthook"
|
||||||
|
local system
|
||||||
|
local derivation_json="$workdir/lefthook-run.drv.json"
|
||||||
|
local lefthook_yml_drv
|
||||||
|
local lefthook_yml_json="$workdir/lefthook-yml.drv.json"
|
||||||
|
mkdir -p "$repo_dir"
|
||||||
|
write_mk_repo_lefthook_flake "$repo_dir"
|
||||||
|
CURRENT_LOG="$workdir/mk-repo-lefthook.log"
|
||||||
|
|
||||||
|
system="$(nix eval --raw --impure --expr 'builtins.currentSystem')"
|
||||||
|
run_capture_ok "$case_name: flake show failed" nix flake show --json --no-write-lock-file "$repo_dir"
|
||||||
|
run_capture_ok "$case_name: lefthook derivation show failed" bash -c 'nix derivation show "$1" >"$2"' _ "$repo_dir#checks.${system}.lefthook-check" "$derivation_json"
|
||||||
|
|
||||||
|
lefthook_yml_drv="$(perl -0ne 'print "/nix/store/$1\n" if /"([a-z0-9]{32}-lefthook\.yml\.drv)"/' "$derivation_json")"
|
||||||
|
if [[ -z "$lefthook_yml_drv" ]]; then
|
||||||
|
fail "$case_name: could not locate lefthook.yml derivation"
|
||||||
|
fi
|
||||||
|
|
||||||
|
run_capture_ok "$case_name: lefthook.yml derivation show failed" bash -c 'nix derivation show "$1" >"$2"' _ "$lefthook_yml_drv" "$lefthook_yml_json"
|
||||||
|
assert_contains '\"pre-push\":{\"commands\":{\"tests\":{' "$lefthook_yml_json" "$case_name: generated check missing from pre-push"
|
||||||
|
assert_contains 'repo-lib-check-tests' "$lefthook_yml_json" "$case_name: generated check command missing from lefthook config"
|
||||||
|
assert_contains '\"output\":[\"failure\",\"summary\"]' "$lefthook_yml_json" "$case_name: lefthook output config missing"
|
||||||
|
assert_contains '\"stage_fixed\":true' "$lefthook_yml_json" "$case_name: stage_fixed missing from lefthook config"
|
||||||
|
|
||||||
|
rm -rf "$workdir"
|
||||||
|
CURRENT_LOG=""
|
||||||
|
echo "[test] PASS: $case_name" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
run_mk_repo_treefmt_hook_case() {
|
||||||
|
local case_name="mkRepo configures treefmt and lefthook for dev shell hooks"
|
||||||
|
local workdir
|
||||||
|
workdir="$(mktemp -d)"
|
||||||
|
local repo_dir="$workdir/mk-repo-treefmt"
|
||||||
|
local system
|
||||||
|
local derivation_json="$workdir/treefmt-hook.drv.json"
|
||||||
|
local lefthook_yml_drv
|
||||||
|
local lefthook_yml_json="$workdir/treefmt-hook-yml.drv.json"
|
||||||
|
mkdir -p "$repo_dir"
|
||||||
|
write_mk_repo_flake "$repo_dir"
|
||||||
|
CURRENT_LOG="$workdir/mk-repo-treefmt.log"
|
||||||
|
|
||||||
|
init_git_repo "$repo_dir"
|
||||||
|
|
||||||
|
run_capture_ok "$case_name: treefmt should be available in shell" bash -c 'cd "$1" && nix develop --no-write-lock-file . -c sh -c '"'"'printf "%s\n" "$LEFTHOOK_BIN" && command -v treefmt'"'"'' _ "$repo_dir"
|
||||||
|
assert_contains 'lefthook-dumb-term' "$CURRENT_LOG" "$case_name: LEFTHOOK_BIN wrapper missing"
|
||||||
|
assert_contains '/bin/treefmt' "$CURRENT_LOG" "$case_name: treefmt missing from shell"
|
||||||
|
|
||||||
|
system="$(nix eval --raw --impure --expr 'builtins.currentSystem')"
|
||||||
|
run_capture_ok "$case_name: formatting check derivation show failed" bash -c 'nix derivation show "$1" >"$2"' _ "$repo_dir#checks.${system}.formatting-check" "$workdir/formatting-check.drv.json"
|
||||||
|
run_capture_ok "$case_name: lefthook derivation show failed" bash -c 'nix derivation show "$1" >"$2"' _ "$repo_dir#checks.${system}.lefthook-check" "$derivation_json"
|
||||||
|
|
||||||
|
lefthook_yml_drv="$(perl -0ne 'print "/nix/store/$1\n" if /"([a-z0-9]{32}-lefthook\.yml\.drv)"/' "$derivation_json")"
|
||||||
|
if [[ -z "$lefthook_yml_drv" ]]; then
|
||||||
|
fail "$case_name: could not locate lefthook.yml derivation"
|
||||||
|
fi
|
||||||
|
|
||||||
|
run_capture_ok "$case_name: lefthook.yml derivation show failed" bash -c 'nix derivation show "$1" >"$2"' _ "$lefthook_yml_drv" "$lefthook_yml_json"
|
||||||
|
assert_contains '--no-cache {staged_files}' "$lefthook_yml_json" "$case_name: treefmt hook missing staged-file format command"
|
||||||
|
assert_contains '\"stage_fixed\":true' "$lefthook_yml_json" "$case_name: treefmt hook should re-stage formatted files"
|
||||||
|
|
||||||
|
rm -rf "$workdir"
|
||||||
|
CURRENT_LOG=""
|
||||||
|
echo "[test] PASS: $case_name" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
run_mk_repo_tool_failure_case() {
|
||||||
|
local case_name="mkRepo required tools fail shell startup"
|
||||||
|
local workdir
|
||||||
|
workdir="$(mktemp -d)"
|
||||||
|
local repo_dir="$workdir/tool-failure"
|
||||||
|
mkdir -p "$repo_dir"
|
||||||
|
write_tool_failure_flake "$repo_dir"
|
||||||
|
CURRENT_LOG="$workdir/tool-failure.log"
|
||||||
|
|
||||||
|
run_expect_failure "$case_name: shell startup should fail" bash -c 'cd "$1" && nix develop . -c true' _ "$repo_dir"
|
||||||
|
assert_contains "probe failed" "$CURRENT_LOG" "$case_name: failure reason missing"
|
||||||
|
|
||||||
|
rm -rf "$workdir"
|
||||||
|
CURRENT_LOG=""
|
||||||
|
echo "[test] PASS: $case_name" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
run_impure_bootstrap_validation_case() {
|
||||||
|
local case_name="mkRepo rejects bootstrap without explicit opt-in"
|
||||||
|
local workdir
|
||||||
|
workdir="$(mktemp -d)"
|
||||||
|
local repo_dir="$workdir/bootstrap-validation"
|
||||||
|
mkdir -p "$repo_dir"
|
||||||
|
write_impure_bootstrap_flake "$repo_dir"
|
||||||
|
CURRENT_LOG="$workdir/bootstrap-validation.log"
|
||||||
|
|
||||||
|
run_expect_failure "$case_name: evaluation should fail" nix flake show --json "$repo_dir"
|
||||||
|
assert_contains "allowImpureBootstrap" "$CURRENT_LOG" "$case_name: validation message missing"
|
||||||
|
|
||||||
|
rm -rf "$workdir"
|
||||||
|
CURRENT_LOG=""
|
||||||
|
echo "[test] PASS: $case_name" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
run_legacy_api_eval_case() {
|
||||||
|
local case_name="legacy mkDevShell and mkRelease still evaluate"
|
||||||
|
local workdir
|
||||||
|
workdir="$(mktemp -d)"
|
||||||
|
local repo_dir="$workdir/legacy"
|
||||||
|
mkdir -p "$repo_dir"
|
||||||
|
write_legacy_flake "$repo_dir"
|
||||||
|
CURRENT_LOG="$workdir/legacy.log"
|
||||||
|
|
||||||
|
run_capture_ok "$case_name: flake show failed" nix flake show --json "$repo_dir"
|
||||||
|
assert_contains '"lefthook-check"' "$CURRENT_LOG" "$case_name: missing lefthook-check"
|
||||||
|
assert_contains '"release"' "$CURRENT_LOG" "$case_name: missing release package"
|
||||||
|
|
||||||
|
rm -rf "$workdir"
|
||||||
|
CURRENT_LOG=""
|
||||||
|
echo "[test] PASS: $case_name" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
run_template_eval_case() {
|
||||||
|
local case_name="template flake evaluates with mkRepo"
|
||||||
|
local workdir
|
||||||
|
workdir="$(mktemp -d)"
|
||||||
|
local repo_dir="$workdir/template"
|
||||||
|
mkdir -p "$repo_dir"
|
||||||
|
write_template_fixture "$repo_dir"
|
||||||
|
CURRENT_LOG="$workdir/template.log"
|
||||||
|
|
||||||
|
if [[ ! -f "$repo_dir/package.json" ]]; then
|
||||||
|
fail "$case_name: template fixture missing package.json"
|
||||||
|
fi
|
||||||
|
if [[ ! -f "$repo_dir/.moon/workspace.yml" ]]; then
|
||||||
|
fail "$case_name: template fixture missing .moon/workspace.yml"
|
||||||
|
fi
|
||||||
|
|
||||||
|
run_capture_ok "$case_name: flake show failed" nix flake show --json "$repo_dir"
|
||||||
|
assert_contains '"lefthook-check"' "$CURRENT_LOG" "$case_name: missing lefthook-check"
|
||||||
|
assert_contains '"release"' "$CURRENT_LOG" "$case_name: missing release package"
|
||||||
|
|
||||||
|
rm -rf "$workdir"
|
||||||
|
CURRENT_LOG=""
|
||||||
|
echo "[test] PASS: $case_name" >&2
|
||||||
|
}
|
||||||
|
|
||||||
|
run_release_replace_backref_case() {
|
||||||
|
local case_name="mkRepo release replace supports sed-style backrefs"
|
||||||
|
local workdir
|
||||||
|
workdir="$(mktemp -d)"
|
||||||
|
local repo_dir="$workdir/repo"
|
||||||
|
local remote_dir="$workdir/remote.git"
|
||||||
|
CURRENT_LOG="$workdir/release-backref.log"
|
||||||
|
|
||||||
|
setup_repo "$repo_dir" "$remote_dir"
|
||||||
|
mkdir -p "$repo_dir/template"
|
||||||
|
cat >"$repo_dir/template/flake.nix" <<'EOF'
|
||||||
|
{
|
||||||
|
inputs = {
|
||||||
|
repo-lib.url = "git+https://git.dgren.dev/eric/nix-flake-lib?ref=refs/tags/v0.0.0";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
write_release_replace_backref_flake "$repo_dir"
|
||||||
|
run_capture_ok "$case_name: setup commit failed" git -C "$repo_dir" add flake.nix template/flake.nix
|
||||||
|
run_capture_ok "$case_name: setup commit failed" git -C "$repo_dir" commit -m "chore: add replace fixture"
|
||||||
|
|
||||||
|
run_capture_ok "$case_name: nix run release failed" bash -c 'cd "$1" && nix run --no-write-lock-file .#release -- patch' _ "$repo_dir"
|
||||||
|
|
||||||
|
assert_contains 'repo-lib.url = "git+https://example.invalid/repo-lib?ref=refs/tags/v1.0.1";' "$repo_dir/template/flake.nix" "$case_name: replacement did not preserve captures"
|
||||||
|
if grep -Fq '\1git+https://example.invalid/repo-lib?ref=refs/tags/v1.0.1\2' "$repo_dir/template/flake.nix"; then
|
||||||
|
fail "$case_name: replacement left literal backreferences in output"
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -rf "$workdir"
|
||||||
|
CURRENT_LOG=""
|
||||||
|
echo "[test] PASS: $case_name" >&2
|
||||||
|
}
|
||||||
|
|
||||||
run_case "channel-only from stable bumps patch" "beta" "1.0.1-beta.1"
|
run_case "channel-only from stable bumps patch" "beta" "1.0.1-beta.1"
|
||||||
run_case "explicit minor bump keeps requested bump" "minor beta" "1.1.0-beta.1"
|
run_case "explicit minor bump keeps requested bump" "minor beta" "1.1.0-beta.1"
|
||||||
run_set_prerelease_then_full_case
|
run_set_prerelease_then_full_case
|
||||||
|
run_stable_then_beta_cannot_reuse_same_base_case
|
||||||
run_set_stable_then_full_noop_case
|
run_set_stable_then_full_noop_case
|
||||||
run_set_stable_from_prerelease_requires_full_case
|
run_set_stable_from_prerelease_requires_full_case
|
||||||
run_patch_stable_from_prerelease_requires_full_case
|
run_patch_stable_from_prerelease_requires_full_case
|
||||||
run_randomized_quickcheck_cases
|
run_structured_release_steps_case
|
||||||
|
run_version_metadata_case
|
||||||
|
run_mk_repo_case
|
||||||
|
run_mk_repo_command_tool_case
|
||||||
|
run_mk_repo_lefthook_case
|
||||||
|
run_mk_repo_treefmt_hook_case
|
||||||
|
run_mk_repo_tool_failure_case
|
||||||
|
run_impure_bootstrap_validation_case
|
||||||
|
run_legacy_api_eval_case
|
||||||
|
run_template_eval_case
|
||||||
|
run_release_replace_backref_case
|
||||||
|
if [[ "${QUICKCHECK:-0}" == "1" ]]; then
|
||||||
|
run_randomized_quickcheck_cases
|
||||||
|
fi
|
||||||
|
|
||||||
echo "[test] All release tests passed" >&2
|
echo "[test] All release tests passed" >&2
|
||||||
|
|||||||
Reference in New Issue
Block a user