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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 5 additions & 16 deletions .github/actions/setup-node-pnpm/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ inputs:
node-version:
description: Node.js version
required: false
default: '25'
default: "25"
install:
description: Whether to run pnpm install
required: false
default: 'true'
default: "true"

runs:
using: composite
Expand All @@ -18,22 +18,11 @@ runs:
uses: pnpm/action-setup@v4

- name: Setup Node
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: ${{ inputs.node-version }}

- name: Get pnpm store directory
id: pnpm-cache
shell: bash
run: echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT

- name: Cache pnpm store
uses: actions/cache@v4
with:
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
cache: pnpm
cache-dependency-path: pnpm-lock.yaml

- name: Install workspace dependencies
if: inputs.install == 'true'
Expand Down
2 changes: 1 addition & 1 deletion .github/actions/setup-rust/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ runs:
targets: ${{ inputs.targets }}

- name: Cache cargo
uses: actions/cache@v4
uses: actions/cache@v5
with:
path: |
~/.cargo/registry
Expand Down
4 changes: 2 additions & 2 deletions .github/actions/setup-tauri/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ runs:
steps:
- name: Cache Linux package archives
if: runner.os == 'Linux'
uses: actions/cache@v4
uses: actions/cache@v5
with:
path: /var/cache/apt/archives
key: ${{ runner.os }}-tauri-apt-${{ hashFiles('.github/actions/setup-tauri/action.yml') }}
Expand Down Expand Up @@ -80,7 +80,7 @@ runs:
echo "hash=$HASH" >> $GITHUB_OUTPUT

- name: Cache cargo + tauri target
uses: actions/cache@v4
uses: actions/cache@v5
with:
path: |
~/.cargo/registry
Expand Down
10 changes: 5 additions & 5 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions cli/env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,7 @@ declare const __CLI_PACKAGE_NAME__: string
* Kiro global powers registry JSON string injected at build time
*/
declare const __KIRO_GLOBAL_POWERS_REGISTRY__: string

interface GlobalThis {
__TNMSC_TEST_NATIVE_BINDING__?: object
}
2 changes: 1 addition & 1 deletion cli/eslint.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const config = await eslint10({
strictTypescriptEslint: true,
tsconfigPath: resolve(configDir, 'tsconfig.eslint.json'),
parserOptions: {
allowDefaultProject: ['*.config.ts']
allowDefaultProject: ['*.config.ts', 'test/**/*.ts']
}
},
ignores: [
Expand Down
4 changes: 3 additions & 1 deletion cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,9 @@
},
"scripts": {
"build": "run-s build:deps build:napi bundle finalize:bundle generate:schema",
"build:napi": "tsx ../scripts/copy-napi.ts",
"build:napi": "run-s build:native build:napi:copy",
"build:napi:copy": "tsx ../scripts/copy-napi.ts",
"build:native": "napi build --platform --release --output-dir dist -- --features napi",
"build:deps": "pnpm -F @truenine/logger -F @truenine/md-compiler -F @truenine/script-runtime run build",
"build:deps:ts": "pnpm -F @truenine/logger -F @truenine/md-compiler -F @truenine/script-runtime run build:ts",
"bundle": "tsx ../scripts/build-quiet.ts",
Expand Down
20 changes: 5 additions & 15 deletions cli/src/core/native-binding.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,16 @@
import {createRequire} from 'node:module'
import process from 'node:process'

declare global {
interface GlobalThis {
__TNMSC_TEST_NATIVE_BINDING__?: object
}
}

function shouldSkipNativeBinding(): boolean {
if (process.env['TNMSC_FORCE_NATIVE_BINDING'] === '1') return false
if (process.env['TNMSC_DISABLE_NATIVE_BINDING'] === '1') return true

return process.env['NODE_ENV'] === 'test'
|| process.env['VITEST'] != null
|| process.env['VITEST_WORKER_ID'] != null
return process.env['NODE_ENV'] === 'test' || process.env['VITEST'] != null || process.env['VITEST_WORKER_ID'] != null
}

export function tryLoadNativeBinding<T extends object>(): T | undefined {
const testBinding: unknown = globalThis.__TNMSC_TEST_NATIVE_BINDING__
const testGlobals = globalThis as typeof globalThis & {__TNMSC_TEST_NATIVE_BINDING__?: object}
const testBinding: unknown = testGlobals.__TNMSC_TEST_NATIVE_BINDING__
if (testBinding != null && typeof testBinding === 'object') return testBinding as T
if (shouldSkipNativeBinding()) return void 0

Expand Down Expand Up @@ -58,12 +51,9 @@ export function tryLoadNativeBinding<T extends object>(): T | undefined {
for (const candidate of possibleBindings) {
if (candidate != null && typeof candidate === 'object') return candidate as T
}
}
catch {}
} catch {}
}
}
catch {
}
} catch {}

return void 0
}
Expand Down
83 changes: 36 additions & 47 deletions cli/src/plugins/plugin-core/filters.ts
Original file line number Diff line number Diff line change
@@ -1,45 +1,51 @@
import type {
ProjectConfig,
RulePrompt,
SeriName
} from './types'
import type {ProjectConfig, RulePrompt, SeriName} from './types'
import * as fs from 'node:fs'
import * as path from 'node:path'
import {getNativeBinding} from '@/core/native-binding'

interface SeriesFilterFns {
readonly resolveEffectiveIncludeSeries: (
topLevel?: readonly string[],
typeSpecific?: readonly string[]
) => string[]
readonly matchesSeries: (
seriName: string | readonly string[] | null | undefined,
effectiveIncludeSeries: readonly string[]
) => boolean
readonly resolveEffectiveIncludeSeries: (topLevel?: readonly string[], typeSpecific?: readonly string[]) => string[]
readonly matchesSeries: (seriName: string | readonly string[] | null | undefined, effectiveIncludeSeries: readonly string[]) => boolean
readonly resolveSubSeries: (
topLevel?: Readonly<Record<string, readonly string[]>>,
typeSpecific?: Readonly<Record<string, readonly string[]>>
) => Record<string, string[]>
}

function requireSeriesFilterFns(): SeriesFilterFns {
let seriesFilterFnsCache: SeriesFilterFns | undefined

function getSeriesFilterFns(): SeriesFilterFns {
if (seriesFilterFnsCache != null) return seriesFilterFnsCache

const candidate = getNativeBinding<SeriesFilterFns>()
if (candidate == null) {
throw new TypeError('Native series-filter binding is required. Build or install the Rust NAPI package before running tnmsc.')
}
if (typeof candidate.matchesSeries !== 'function'
if (
typeof candidate.matchesSeries !== 'function'
|| typeof candidate.resolveEffectiveIncludeSeries !== 'function'
|| typeof candidate.resolveSubSeries !== 'function') {
|| typeof candidate.resolveSubSeries !== 'function'
) {
throw new TypeError('Native series-filter binding is incomplete. Rebuild the Rust NAPI package before running tnmsc.')
}
seriesFilterFnsCache = candidate
return candidate
}

const {
resolveEffectiveIncludeSeries,
matchesSeries,
resolveSubSeries
}: SeriesFilterFns = requireSeriesFilterFns()
function resolveEffectiveIncludeSeries(topLevel?: readonly string[], typeSpecific?: readonly string[]): string[] {
return getSeriesFilterFns().resolveEffectiveIncludeSeries(topLevel, typeSpecific)
}

function matchesSeries(seriName: string | readonly string[] | null | undefined, effectiveIncludeSeries: readonly string[]): boolean {
return getSeriesFilterFns().matchesSeries(seriName, effectiveIncludeSeries)
}

function resolveSubSeries(
topLevel?: Readonly<Record<string, readonly string[]>>,
typeSpecific?: Readonly<Record<string, readonly string[]>>
): Record<string, string[]> {
return getSeriesFilterFns().resolveSubSeries(topLevel, typeSpecific)
}

/**
* Interface for items that can be filtered by series name
Expand All @@ -58,10 +64,7 @@ export function filterByProjectConfig<T extends SeriesFilterable>(
projectConfig: ProjectConfig | undefined,
configPath: FilterConfigPath
): readonly T[] {
const effectiveSeries = resolveEffectiveIncludeSeries(
projectConfig?.includeSeries,
projectConfig?.[configPath]?.includeSeries
)
const effectiveSeries = resolveEffectiveIncludeSeries(projectConfig?.includeSeries, projectConfig?.[configPath]?.includeSeries)
return items.filter(item => matchesSeries(item.seriName, effectiveSeries))
}

Expand All @@ -77,10 +80,7 @@ function smartConcatGlob(prefix: string, glob: string): string {
return `${prefix}/${glob}`
}

function extractPrefixAndBaseGlob(
glob: string,
prefixes: readonly string[]
): {prefix: string | null, baseGlob: string} {
function extractPrefixAndBaseGlob(glob: string, prefixes: readonly string[]): {prefix: string | null, baseGlob: string} {
for (const prefix of prefixes) {
const normalizedPrefix = prefix.replaceAll(/\/+$/g, '')
const patterns = [
Expand All @@ -95,10 +95,7 @@ function extractPrefixAndBaseGlob(
return {prefix: null, baseGlob: glob}
}

export function applySubSeriesGlobPrefix(
rules: readonly RulePrompt[],
projectConfig: ProjectConfig | undefined
): readonly RulePrompt[] {
export function applySubSeriesGlobPrefix(rules: readonly RulePrompt[], projectConfig: ProjectConfig | undefined): readonly RulePrompt[] {
const subSeries = resolveSubSeries(projectConfig?.subSeries, projectConfig?.rules?.subSeries)
if (Object.keys(subSeries).length === 0) return rules

Expand All @@ -115,9 +112,7 @@ export function applySubSeriesGlobPrefix(

const matchedPrefixes: string[] = []
for (const [subdir, seriNames] of Object.entries(normalizedSubSeries)) {
const matched = Array.isArray(rule.seriName)
? rule.seriName.some(name => seriNames.includes(name))
: seriNames.includes(rule.seriName)
const matched = Array.isArray(rule.seriName) ? rule.seriName.some(name => seriNames.includes(name)) : seriNames.includes(rule.seriName)
if (matched) matchedPrefixes.push(subdir)
}

Expand Down Expand Up @@ -168,9 +163,7 @@ export function resolveGitInfoDir(projectDir: string): string | null {
const gitdir = path.resolve(projectDir, match[1])
return path.join(gitdir, 'info')
}
}
catch {
} // ignore read errors
} catch {} // ignore read errors
}

return null
Expand All @@ -193,8 +186,7 @@ export function findAllGitRepos(rootDir: string, maxDepth = 5): string[] {
const raw = fs.readdirSync(dir, {withFileTypes: true})
if (!Array.isArray(raw)) return
entries = raw
}
catch {
} catch {
return
}

Expand Down Expand Up @@ -229,8 +221,7 @@ export function findGitModuleInfoDirs(dotGitDir: string): string[] {
const raw = fs.readdirSync(dir, {withFileTypes: true})
if (!Array.isArray(raw)) return
entries = raw
}
catch {
} catch {
return
}

Expand All @@ -245,8 +236,7 @@ export function findGitModuleInfoDirs(dotGitDir: string): string[] {
const raw = fs.readdirSync(path.join(dir, 'modules'), {withFileTypes: true})
if (!Array.isArray(raw)) return
subEntries = raw
}
catch {
} catch {
return
}
for (const sub of subEntries) {
Expand All @@ -259,8 +249,7 @@ export function findGitModuleInfoDirs(dotGitDir: string): string[] {
const raw = fs.readdirSync(modulesDir, {withFileTypes: true})
if (!Array.isArray(raw)) return results
topEntries = raw
}
catch {
} catch {
return results
}

Expand Down
Loading
Loading