feat(regime)

This commit is contained in:
Dan Finch 2026-04-29 00:55:53 +02:00
commit 2a3ffc4d0e
33 changed files with 961 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
node_modules

12
README.md Normal file
View file

@ -0,0 +1,12 @@
# REGIME
> Tooling and unified configuration for managing a bunch of repositories and packages.
## TODO
- `check` action
- `check` bun script
- `test` bun script
- `release` action
- build with bun?
- run semantic release

55
actions/mirror/action.yml Normal file
View file

@ -0,0 +1,55 @@
name: Mirror to GitHub
description: Mirror the current repo to GitHub, optionally filtering paths listed in .mirrorignore
inputs:
target:
description: "GitHub repo (e.g. owner/repo)"
required: true
source:
description: "Authenticated clone URL for the source repo"
required: true
token:
description: "GitHub personal access token with push access"
required: true
runs:
using: composite
steps:
- name: Install git-filter-repo
shell: bash
run: pip install --break-system-packages git-filter-repo
- name: Clone mirror
shell: bash
run: git clone --bare "$GITHUB_WORKSPACE" /tmp/mirror-repo
- name: Filter ignored paths
shell: bash
run: |
MIRRORIGNORE="$GITHUB_WORKSPACE/.mirrorignore"
if [ ! -f "$MIRRORIGNORE" ]; then
echo "No .mirrorignore found, skipping filter"
exit 0
fi
ARGS=""
while IFS= read -r line || [ -n "$line" ]; do
line="$(echo "$line" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
[ -z "$line" ] && continue
[[ "$line" == \#* ]] && continue
ARGS="$ARGS --path $line"
done < "$MIRRORIGNORE"
# Always filter .mirrorignore itself
ARGS="$ARGS --path .mirrorignore"
if [ -n "$ARGS" ]; then
cd /tmp/mirror-repo
git filter-repo $ARGS --invert-paths --force
fi
- name: Push mirror
shell: bash
run: |
cd /tmp/mirror-repo
git push --mirror "https://${{ inputs.token }}@github.com/${{ inputs.target }}.git"

25
bin/regime Executable file
View file

@ -0,0 +1,25 @@
#!/usr/bin/env bun
import { resolve } from "path"
import { check } from "../src/check"
import { sync } from "../src/sync"
import { promote } from "../src/promote"
const [command, ...rawArgs] = process.argv.slice(2)
const hasYes = rawArgs.includes("--yes")
const args = rawArgs.filter(a => a !== "--yes")
const targetDir = resolve(args[0] ?? process.cwd())
switch (command) {
case "check":
await check(targetDir)
break
case "sync":
await sync(targetDir)
break
case "promote":
await promote(targetDir, hasYes)
break
default:
console.error("Usage: regime <check|sync|promote> [path] [--yes]")
process.exit(1)
}

20
bun.lock Normal file
View file

@ -0,0 +1,20 @@
{
"lockfileVersion": 1,
"configVersion": 1,
"workspaces": {
"": {
"devDependencies": {
"@types/bun": "^1.3.13",
},
},
},
"packages": {
"@types/bun": ["@types/bun@1.3.13", "", { "dependencies": { "bun-types": "1.3.13" } }, "sha512-9fqXWk5YIHGGnUau9TEi+qdlTYDAnOj+xLCmSTwXfAIqXr2x4tytJb43E9uCvt09zJURKXwAtkoH4nLQfzeTXw=="],
"@types/node": ["@types/node@25.6.0", "", { "dependencies": { "undici-types": "~7.19.0" } }, "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ=="],
"bun-types": ["bun-types@1.3.13", "", { "dependencies": { "@types/node": "*" } }, "sha512-QXKeHLlOLqQX9LgYaHJfzdBaV21T63HhFJnvuRCcjZiaUDpbs5ED1MgxbMra71CsryN/1dAoXuJJJwIv/2drVA=="],
"undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="],
}
}

5
package.json Normal file
View file

@ -0,0 +1,5 @@
{
"devDependencies": {
"@types/bun": "^1.3.13"
}
}

90
src/check.ts Normal file
View file

@ -0,0 +1,90 @@
import { dirname, relative, join } from "node:path"
import { existsSync } from "node:fs"
import {
type RegimeConfig,
findRegimeConfigs,
resolveTemplateChain,
getStrategy,
interpolate,
readFileSync,
diffJson,
mergeTemplateJsonFiles,
} from "./shared"
const red = Bun.color("red", "ansi")
const orange = Bun.color("orange", "ansi")
const green = Bun.color("green", "ansi")
const purple = Bun.color("purple", "ansi")
const reset = "\x1b[0m"
export async function check(targetDir: string): Promise<void> {
const rcFiles = await findRegimeConfigs(targetDir)
if (rcFiles.length === 0) {
console.log("No regime.config.json files found.")
return
}
for (const rcFile of rcFiles) {
const rcDir = dirname(rcFile)
const relDir = relative(targetDir, rcDir) || "."
console.log(`\n${purple}${relDir}/${reset}`)
const rc: RegimeConfig = JSON.parse(readFileSync(rcFile))
const templateNames = Array.isArray(rc.templates) ? rc.templates : [rc.templates]
const vars = rc.vars ?? {}
const { files, patterns } = resolveTemplateChain(templateNames)
if (files.size === 0) {
console.log(" (no template files)")
continue
}
let synced = true
for (const [relPath, templatePaths] of files) {
const targetPath = join(rcDir, relPath)
const strategy = getStrategy(relPath, patterns)
if (!existsSync(targetPath)) {
console.log(` ${relPath}: ${red}missing${reset}`)
synced = false
continue
}
const existingContent = readFileSync(targetPath)
if (strategy === "merge json") {
try {
const templateObj = mergeTemplateJsonFiles(templatePaths, vars, relPath)
const existingObj = JSON.parse(existingContent)
const diffs = diffJson(templateObj, existingObj)
if (diffs.length > 0) {
synced = false
console.log(` ${relPath}:`)
for (const d of diffs) {
const exp = JSON.stringify(d.expected)
const act = d.actual === undefined ? `${red}missing${reset}` : `${orange}${JSON.stringify(d.actual)}${reset}`
console.log(` ${d.field}: ${act} -> ${green}${exp}${reset}`)
}
}
} catch (e) {
console.log(` ${relPath}: ${red}failed to parse JSON${reset} - ${e}`)
synced = false
}
} else if (strategy === "overwrite") {
const templateContent = interpolate(readFileSync(templatePaths[templatePaths.length - 1]), vars, relPath)
if (existingContent !== templateContent) {
console.log(` ${relPath}: ${orange}differs${reset}`)
synced = false
}
}
}
if (synced) {
console.log(` ${green}in sync${reset}`)
}
}
}

163
src/promote.ts Normal file
View file

@ -0,0 +1,163 @@
import { dirname, join } from "node:path"
import { existsSync } from "node:fs"
import { writeFile } from "node:fs/promises"
import { $ } from "bun"
import {
type RegimeConfig,
findRegimeConfigs,
resolveTemplateChain,
getStrategy,
interpolate,
deinterpolate,
readFileSync,
templatesDir,
} from "./shared"
const green = Bun.color("green", "ansi")
const orange = Bun.color("orange", "ansi")
const purple = Bun.color("purple", "ansi")
const reset = "\x1b[0m"
async function gumStdin(command: string, input: string): Promise<string> {
const proc = Bun.spawn(["gum", command], {
stdin: Buffer.from(input),
stdout: "pipe",
stderr: "inherit",
})
const output = await new Response(proc.stdout).text()
await proc.exited
return output
}
interface PromotableFile {
relPath: string // relative path within template/repo (e.g. "tsconfig.json")
repoPath: string // absolute path in the repo
configDir: string // directory containing regime.config.json
vars: Record<string, string>
templateNames: string[] // all templates in the inheritance tree
}
export async function promote(targetDir: string, yes = false): Promise<void> {
// Step 1: Find regime configs
const rcFiles = await findRegimeConfigs(targetDir)
if (rcFiles.length === 0) {
console.error("No regime.config.json files found.")
process.exit(1)
}
// Step 2: Collect promotable files (overwrite-strategy files that differ)
const promotable: PromotableFile[] = []
for (const rcFile of rcFiles) {
const rcDir = dirname(rcFile)
const rc: RegimeConfig = JSON.parse(readFileSync(rcFile))
const templateNames = Array.isArray(rc.templates) ? rc.templates : [rc.templates]
const vars = rc.vars ?? {}
const { files, patterns, templateNames: chainNames } = resolveTemplateChain(templateNames)
for (const [relPath, templatePaths] of files) {
const strategy = getStrategy(relPath, patterns)
if (strategy !== "overwrite") continue
const repoPath = join(rcDir, relPath)
if (!existsSync(repoPath)) continue
const repoContent = readFileSync(repoPath)
const templateContent = interpolate(
readFileSync(templatePaths[templatePaths.length - 1]),
vars,
relPath,
)
if (repoContent !== templateContent) {
promotable.push({
relPath,
repoPath,
configDir: rcDir,
vars,
templateNames: chainNames,
})
}
}
}
if (promotable.length === 0) {
console.log("Nothing to promote.")
return
}
// Step 3: Interactive file selection via gum filter
const fileList = promotable.map(p => p.relPath).join("\n")
const selectedFile = (await gumStdin("filter", fileList)).trim()
if (!selectedFile) {
console.log("No file selected.")
return
}
const entry = promotable.find(p => p.relPath === selectedFile)
if (!entry) {
console.error(`File "${selectedFile}" not found in promotable list.`)
process.exit(1)
}
// Step 4: Interactive template selection via gum choose
let selectedTemplate: string
if (entry.templateNames.length === 1) {
selectedTemplate = entry.templateNames[0]
} else {
const templateList = entry.templateNames.join("\n")
selectedTemplate = (await gumStdin("choose", templateList)).trim()
}
if (!selectedTemplate) {
console.log("No template selected.")
return
}
// Step 5: De-interpolate
const repoContent = readFileSync(entry.repoPath)
const promoted = deinterpolate(repoContent, entry.vars)
// Step 6: Show diff
const templateFilePath = join(templatesDir, selectedTemplate, entry.relPath)
console.log(`\n${purple}Promoting${reset} ${entry.relPath} -> ${green}${selectedTemplate}${reset}`)
if (existsSync(templateFilePath)) {
const tmpFile = `/tmp/regime-promote-${Date.now()}`
await writeFile(tmpFile, promoted)
try {
const diff = await $`diff -u ${templateFilePath} ${tmpFile} || true`.text()
if (diff.trim()) {
console.log(diff)
} else {
console.log("No changes detected after de-interpolation.")
return
}
} finally {
await $`rm -f ${tmpFile}`.quiet()
}
} else {
console.log(`${orange}New file${reset} — will be created in template "${selectedTemplate}"`)
console.log(promoted)
}
// Step 7: Confirm
if (!yes) {
const confirmed = await $`gum confirm "Write to template?"`.nothrow()
if (confirmed.exitCode !== 0) {
console.log("Cancelled.")
return
}
}
// Step 8: Write
const targetFileDir = dirname(templateFilePath)
if (!existsSync(targetFileDir)) {
await $`mkdir -p ${targetFileDir}`.quiet()
}
await writeFile(templateFilePath, promoted)
console.log(`${green}Written${reset} ${templateFilePath}`)
}

236
src/shared.ts Normal file
View file

@ -0,0 +1,236 @@
import { resolve, join, dirname } from "node:path"
import { existsSync } from "node:fs"
import { readdir } from "node:fs/promises"
import { Glob } from "bun"
// --- Types ---
export interface RegimeConfig {
templates: string | string[]
vars?: Record<string, string>
}
export interface TemplateConfig {
inherits?: string[]
patterns?: Record<string, string>
}
export interface CollectedTemplate {
files: Map<string, string[]> // relative path -> absolute paths (in chain order)
patterns: Record<string, string>
templateNames: string[] // ordered list of visited template names
}
// --- Constants ---
export const regimeDir = resolve(dirname(import.meta.dir))
export const templatesDir = join(regimeDir, "templates")
// --- Utilities ---
export function readFileSync(path: string): string {
return require("fs").readFileSync(path, "utf-8")
}
export function readdirSyncRecursive(dir: string, prefix = ""): string[] {
const fs = require("fs")
const results: string[] = []
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
const rel = prefix ? `${prefix}/${entry.name}` : entry.name
if (entry.isDirectory()) {
results.push(...readdirSyncRecursive(join(dir, entry.name), rel))
} else {
results.push(rel)
}
}
return results
}
// --- Template resolution ---
export function resolveTemplateConfig(name: string): TemplateConfig {
const configPath = join(templatesDir, name, ".regime-template.json")
if (!existsSync(configPath)) return {}
const raw = JSON.parse(readFileSync(configPath))
return raw as TemplateConfig
}
export function resolveTemplateChain(names: string[]): CollectedTemplate {
const visited = new Set<string>()
const files = new Map<string, string[]>()
const patterns: Record<string, string> = {}
const templateNames: string[] = []
function walk(name: string) {
if (visited.has(name)) return
visited.add(name)
const dir = join(templatesDir, name)
if (!existsSync(dir)) {
console.error(` warning: template "${name}" not found at ${dir}`)
return
}
const config = resolveTemplateConfig(name)
// Walk parents first so children override
if (config.inherits) {
for (const parent of config.inherits) {
walk(parent)
}
}
templateNames.push(name)
// Collect patterns
if (config.patterns) {
Object.assign(patterns, config.patterns)
}
// Collect files (skip .regime-template.json)
const entries = readdirSyncRecursive(dir)
for (const entry of entries) {
if (entry === ".regime-template.json") continue
const existing = files.get(entry) ?? []
existing.push(join(dir, entry))
files.set(entry, existing)
}
}
for (const name of names) {
walk(name)
}
return { files, patterns, templateNames }
}
// --- Strategy matching ---
export function getStrategy(filePath: string, patterns: Record<string, string>): string {
// Check exact match first
if (patterns[filePath]) return patterns[filePath]
// Check glob patterns
for (const [pattern, strategy] of Object.entries(patterns)) {
if (pattern.includes("*")) {
const glob = new Glob(pattern)
if (glob.match(filePath)) return strategy
}
}
return "overwrite" // default
}
// --- Variable interpolation ---
export function interpolate(content: string, vars: Record<string, string>, context?: string): string {
return content.replace(/<<(\w+)>>/g, (_, key) => {
if (!(key in vars)) {
console.error(` warning: undeclared var "<<${key}>>"${context ? ` in ${context}` : ""}`)
}
return vars[key] ?? `<<${key}>>`
})
}
export function deinterpolate(content: string, vars: Record<string, string>): string {
let result = content
for (const [key, value] of Object.entries(vars)) {
result = result.replaceAll(value, `<<${key}>>`)
}
return result
}
// --- Deep merge (template values win; existing-only fields preserved) ---
export function deepMerge(base: any, overlay: any): any {
if (typeof base !== "object" || base === null || Array.isArray(base)) return overlay
if (typeof overlay !== "object" || overlay === null || Array.isArray(overlay)) return overlay
const result = { ...base }
for (const key of Object.keys(overlay)) {
if (key in result) {
result[key] = deepMerge(result[key], overlay[key])
} else {
result[key] = overlay[key]
}
}
return result
}
// --- Merge all template JSON files into one combined object ---
export function mergeTemplateJsonFiles(paths: string[], vars: Record<string, string>, relPath: string): any {
let merged: any = {}
for (const p of paths) {
const content = interpolate(readFileSync(p), vars, relPath)
merged = deepMerge(merged, JSON.parse(content))
}
return merged
}
// --- Indentation detection ---
export function detectIndent(content: string): string {
const match = content.match(/^(\s+)/m)
return match?.[1] ?? " "
}
// --- Diff reporting ---
export function diffJson(
templateObj: any,
existingObj: any,
path: string[] = [],
): { field: string; expected: any; actual: any }[] {
const diffs: { field: string; expected: any; actual: any }[] = []
for (const key of Object.keys(templateObj)) {
const fieldPath = [...path, key].join(".")
const expected = templateObj[key]
const actual = existingObj?.[key]
if (actual === undefined) {
diffs.push({ field: fieldPath, expected, actual: undefined })
} else if (
typeof expected === "object" &&
expected !== null &&
!Array.isArray(expected) &&
typeof actual === "object" &&
actual !== null &&
!Array.isArray(actual)
) {
diffs.push(...diffJson(expected, actual, [...path, key]))
} else if (JSON.stringify(expected) !== JSON.stringify(actual)) {
diffs.push({ field: fieldPath, expected, actual })
}
}
return diffs
}
// --- Find all regime.config.json files in a repo ---
export async function findRegimeConfigs(repoDir: string): Promise<string[]> {
const results: string[] = []
async function walk(dir: string) {
let entries
try {
entries = await readdir(dir, { withFileTypes: true })
} catch {
return
}
for (const entry of entries) {
if (entry.name === "node_modules" || entry.name === ".git") continue
const full = join(dir, entry.name)
if (entry.isDirectory()) {
await walk(full)
} else if (entry.name === "regime.config.json") {
results.push(full)
}
}
}
await walk(repoDir)
return results
}

124
src/sync.ts Normal file
View file

@ -0,0 +1,124 @@
import { dirname, relative, join } from "node:path"
import { existsSync } from "node:fs"
import { writeFile } from "node:fs/promises"
import {
type RegimeConfig,
findRegimeConfigs,
resolveTemplateChain,
getStrategy,
interpolate,
readFileSync,
deepMerge,
mergeTemplateJsonFiles,
detectIndent,
} from "./shared"
const green = Bun.color("green", "ansi")
const yellow = Bun.color("orange", "ansi")
const red = Bun.color("red", "ansi")
const purple = Bun.color("purple", "ansi")
const reset = "\x1b[0m"
export async function sync(targetDir: string): Promise<void> {
const rcFiles = await findRegimeConfigs(targetDir)
if (rcFiles.length === 0) {
console.log("No regime.config.json files found.")
return
}
for (const rcFile of rcFiles) {
const rcDir = dirname(rcFile)
const relDir = relative(targetDir, rcDir) || "."
console.log(`\n${purple}${relDir}/${reset}`)
const rc: RegimeConfig = JSON.parse(readFileSync(rcFile))
const templateNames = Array.isArray(rc.templates) ? rc.templates : [rc.templates]
const vars = rc.vars ?? {}
const { files, patterns } = resolveTemplateChain(templateNames)
if (files.size === 0) {
console.log(" (no template files)")
continue
}
let allSynced = true
for (const [relPath, templatePaths] of files) {
const targetPath = join(rcDir, relPath)
const strategy = getStrategy(relPath, patterns)
// Check target directory exists
const targetFileDir = dirname(targetPath)
if (!existsSync(targetFileDir)) {
console.log(` ${relPath}: ${yellow}warning: directory does not exist, skipping${reset}`)
continue
}
if (strategy === "merge json") {
let templateObj: any
try {
templateObj = mergeTemplateJsonFiles(templatePaths, vars, relPath)
} catch (e) {
console.log(` ${relPath}: ${red}failed to parse template JSON${reset} - ${e}`)
continue
}
if (existsSync(targetPath)) {
const existingContent = readFileSync(targetPath)
let existingObj: any
try {
existingObj = JSON.parse(existingContent)
} catch (e) {
console.log(` ${relPath}: ${red}failed to parse existing JSON${reset} - ${e}`)
continue
}
const merged = deepMerge(existingObj, templateObj)
const indent = detectIndent(existingContent)
const mergedContent = JSON.stringify(merged, null, indent) + "\n"
if (mergedContent === existingContent) {
// already in sync
continue
}
await writeFile(targetPath, mergedContent)
console.log(` ${relPath}: ${green}updated${reset}`)
allSynced = false
} else {
const content = JSON.stringify(templateObj, null, " ") + "\n"
await writeFile(targetPath, content)
console.log(` ${relPath}: ${green}created${reset}`)
allSynced = false
}
} else if (strategy === "overwrite") {
const templateContent = interpolate(
readFileSync(templatePaths[templatePaths.length - 1]),
vars,
relPath,
)
if (existsSync(targetPath)) {
const existingContent = readFileSync(targetPath)
if (existingContent === templateContent) {
// already in sync
continue
}
await writeFile(targetPath, templateContent)
console.log(` ${relPath}: ${green}updated${reset}`)
allSynced = false
} else {
await writeFile(targetPath, templateContent)
console.log(` ${relPath}: ${green}created${reset}`)
allSynced = false
}
}
}
if (allSynced) {
console.log(` ${green}in sync${reset}`)
}
}
}

View file

@ -0,0 +1,6 @@
{
"inherits": [
"shared",
"package"
]
}

View file

@ -0,0 +1,6 @@
{
"inherits": [
"shared",
"repo"
]
}

View file

@ -0,0 +1,11 @@
{
"workspaces": {
"catalog": {
"@types/bun": "1.3.11"
}
},
"scripts": {
"test": "bun run --workspaces --parallel --no-exit-on-error test",
"check": "bun run --workspaces --parallel --no-exit-on-error check"
}
}

View file

@ -0,0 +1,5 @@
{
"inherits": [
"tools-ts"
]
}

View file

@ -0,0 +1,5 @@
{
"scripts": {
"test": "bun test --pass-with-no-tests"
}
}

View file

@ -0,0 +1,8 @@
{
"inherits": [
"tools-oxc",
"tools-mirror",
"tools-commitlint",
"tools-semantic-release"
]
}

View file

@ -0,0 +1,5 @@
{
"scripts": {
"lint": "oxlint"
}
}

View file

@ -0,0 +1,6 @@
{
"patterns": {
"package.json": "merge json",
"tsconfig.json": "merge json"
}
}

7
templates/shared/LICENSE Normal file
View file

@ -0,0 +1,7 @@
Copyright © 2026 Sigitex
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.

View file

@ -0,0 +1,14 @@
{
"license": "MIT",
"author": {
"name": "Sigitex",
"url": "http://github.com/sigitex"
},
"repository": {
"type": "git",
"url": "https://github.com/sigitex/<<repo>>.git"
},
"scripts": {
"test": "bun test"
}
}

View file

@ -0,0 +1,13 @@
{
"compilerOptions": {
"module": "esnext",
"target": "esnext",
"lib": ["esnext"],
"moduleResolution": "bundler",
"esModuleInterop": true,
"skipDefaultLibCheck": true,
"skipLibCheck": true,
"strict": true,
"outDir": "lib"
}
}

View file

@ -0,0 +1,7 @@
{
"inherits": [
"shared",
"repo",
"package"
]
}

View file

@ -0,0 +1,3 @@
export default {
extends: ["@commitlint/config-conventional"]
}

View file

@ -0,0 +1,7 @@
{
"devDependencies": {
"@commitlint/cli": "^20.5.3",
"@commitlint/config-conventional": "^20.5.3",
"husky": "^9.1.7"
}
}

View file

@ -0,0 +1,16 @@
name: Mirror to GitHub
on:
push:
branches:
- main
jobs:
mirror:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: https://${{ secrets.FORGE_TOKEN }}@code.quickbasic.org/sigitex/regime/actions/mirror@main
with:
target: sigitex/<<repo>>
token: ${{ secrets.MIRROR_TOKEN }}

View file

@ -0,0 +1,3 @@
AGENTS.md
.forgejo
openspec

View file

@ -0,0 +1,16 @@
import { defineConfig } from "oxfmt"
export default defineConfig({
useTabs: false,
tabWidth: 2,
printWidth: 80,
singleQuote: false,
jsxSingleQuote: false,
quoteProps: "as-needed",
trailingComma: "all",
semi: false,
arrowParens: "always",
bracketSameLine: false,
bracketSpacing: true,
ignorePatterns: ["**/*.gen.ts"],
});

View file

@ -0,0 +1,45 @@
import { defineConfig } from "oxlint"
export default defineConfig({
plugins: ["typescript", "unicorn", "oxc"],
categories: {
correctness: "error",
suspicious: "warn",
perf: "warn",
style: "warn",
restriction: "error",
},
rules: {
"no-shadow-restricted-names": "off",
"prefer-template": "off",
"typescript/no-non-null-assertion": "off",
"typescript/no-empty-interface": "off",
"prefer-ternary": "off",
"sort-keys": "off",
"typescript/consistent-type-definitions": ["error", "type"],
"typescript/explicit-function-return-type": "off",
"typescript/explicit-member-accessibility": "off",
"typescript/explicit-module-boundary-types": "off",
"no-use-before-define": "off",
"no-dynamic-delete": "off",
"id-length": "off",
"new-cap": "off",
"no-shadow": "off",
"no-ternary": "off",
"func-style": ["error", "declaration"],
"typescript/prefer-function-type": "off",
"typescript/consistent-indexed-object-style": "off",
"no-undefined": "off",
"filename-case": "off",
"no-magic-numbers": "off",
"max-statements": "off",
"no-plusplus": "off",
"no-array-for-each": "off",
"sort-imports": "off",
"no-optional-chaining": "off",
"default-case": "off",
"prefer-for-of": "off",
"switch-case-braces": "off",
},
ignorePatterns: ["**/*.gen.ts"],
})

View file

@ -0,0 +1,9 @@
{
"devDependencies": {
"oxfmt": "^0.47.0",
"oxlint": "^1.62.0"
},
"scripts": {
"prepare": "husky"
}
}

View file

@ -0,0 +1,5 @@
{
"devDependencies": {
"semantic-release": "^25.0.3"
}
}

View file

@ -0,0 +1,6 @@
/**
* @type {import('semantic-release').GlobalConfig}
*/
export default {
branches: ["main"],
}

View file

@ -0,0 +1,8 @@
{
"devDependencies": {
"@typescript/native-preview": "beta"
},
"scripts": {
"check": "tsgo --noEmit"
}
}

19
tsconfig.json Normal file
View file

@ -0,0 +1,19 @@
{
"compilerOptions": {
"composite": true,
"module": "esnext",
"target": "esnext",
"lib": ["esnext"],
"types": ["bun"],
"moduleResolution": "bundler",
"esModuleInterop": true,
"skipDefaultLibCheck": true,
"skipLibCheck": true,
"strict": true,
"outDir": "lib"
},
"include": [
"src/**/*",
"./bin/regime"
]
}