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
5 changes: 4 additions & 1 deletion .github/actions/npm-auth-preflight/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ name: NPM Auth Preflight
description: Validate npm authentication and report package access

inputs:
npm-token:
description: npm authentication token
required: true
registry-url:
description: npm registry URL
required: false
Expand All @@ -19,7 +22,7 @@ runs:
- name: Preflight npm auth
shell: bash
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
NODE_AUTH_TOKEN: ${{ inputs.npm-token }}
REGISTRY_URL: ${{ inputs.registry-url }}
PACKAGE_DIR: ${{ inputs.package-dir }}
PACKAGE_NAME: ${{ inputs.package-name }}
Expand Down
5 changes: 4 additions & 1 deletion .github/actions/npm-publish-package/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ name: NPM Publish Package
description: Publish a package to npm with retry and verification logic

inputs:
npm-token:
description: npm authentication token
required: true
registry-url:
description: npm registry URL
required: false
Expand All @@ -24,7 +27,7 @@ runs:
- name: Publish to npm
shell: bash
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
NODE_AUTH_TOKEN: ${{ inputs.npm-token }}
REGISTRY_URL: ${{ inputs.registry-url }}
PACKAGE_DIR: ${{ inputs.package-dir }}
VERIFY_ATTEMPTS: ${{ inputs.verify-attempts }}
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/release-cli.yml
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,7 @@ jobs:
- name: Publish CLI platform sub-packages
uses: ./.github/actions/npm-publish-package
with:
npm-token: ${{ secrets.NPM_TOKEN }}
registry-url: ${{ env.NPM_REGISTRY_URL }}
package-dir: cli/npm

Expand All @@ -267,13 +268,15 @@ jobs:
registry-url: https://registry.npmjs.org/
- uses: ./.github/actions/npm-auth-preflight
with:
npm-token: ${{ secrets.NPM_TOKEN }}
registry-url: ${{ env.NPM_REGISTRY_URL }}
package-dir: cli
package-name: "@truenine/memory-sync-cli"
- name: Build
run: pnpm -F @truenine/memory-sync-cli run build
- uses: ./.github/actions/npm-publish-package
with:
npm-token: ${{ secrets.NPM_TOKEN }}
registry-url: ${{ env.NPM_REGISTRY_URL }}
package-dir: cli

Expand All @@ -292,13 +295,15 @@ jobs:
registry-url: https://registry.npmjs.org/
- uses: ./.github/actions/npm-auth-preflight
with:
npm-token: ${{ secrets.NPM_TOKEN }}
registry-url: ${{ env.NPM_REGISTRY_URL }}
package-dir: mcp
package-name: "@truenine/memory-sync-mcp"
- name: Build
run: pnpm exec turbo run build --filter=@truenine/memory-sync-mcp
- uses: ./.github/actions/npm-publish-package
with:
npm-token: ${{ secrets.NPM_TOKEN }}
registry-url: ${{ env.NPM_REGISTRY_URL }}
package-dir: mcp

Expand Down
12 changes: 6 additions & 6 deletions Cargo.lock

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

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ members = [
]

[workspace.package]
version = "2026.10402.116"
version = "2026.10403.117"
edition = "2024"
rust-version = "1.88"
license = "AGPL-3.0-only"
Expand Down
2 changes: 1 addition & 1 deletion cli/npm/darwin-arm64/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@truenine/memory-sync-cli-darwin-arm64",
"version": "2026.10402.116",
"version": "2026.10403.117",
"os": [
"darwin"
],
Expand Down
2 changes: 1 addition & 1 deletion cli/npm/darwin-x64/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@truenine/memory-sync-cli-darwin-x64",
"version": "2026.10402.116",
"version": "2026.10403.117",
"os": [
"darwin"
],
Expand Down
2 changes: 1 addition & 1 deletion cli/npm/linux-arm64-gnu/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@truenine/memory-sync-cli-linux-arm64-gnu",
"version": "2026.10402.116",
"version": "2026.10403.117",
"os": [
"linux"
],
Expand Down
2 changes: 1 addition & 1 deletion cli/npm/linux-x64-gnu/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@truenine/memory-sync-cli-linux-x64-gnu",
"version": "2026.10402.116",
"version": "2026.10403.117",
"os": [
"linux"
],
Expand Down
2 changes: 1 addition & 1 deletion cli/npm/win32-x64-msvc/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@truenine/memory-sync-cli-win32-x64-msvc",
"version": "2026.10402.116",
"version": "2026.10403.117",
"os": [
"win32"
],
Expand Down
2 changes: 1 addition & 1 deletion cli/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@truenine/memory-sync-cli",
"type": "module",
"version": "2026.10402.116",
"version": "2026.10403.117",
"description": "TrueNine Memory Synchronization CLI shell",
"author": "TrueNine",
"license": "AGPL-3.0-only",
Expand Down
61 changes: 44 additions & 17 deletions cli/src/PluginPipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,22 @@ import type {
PipelineConfig,
PluginOptions
} from '@truenine/memory-sync-sdk'
import type {Command, CommandContext, CommandResult} from '@/commands/Command'
import type {
Command,
CommandContext,
CommandResult
} from '@/commands/Command'
import type {ParsedCliArgs} from '@/pipeline/CliArgumentParser'
import {createLogger, discoverOutputRuntimeTargets, setGlobalLogLevel} from '@truenine/memory-sync-sdk'
import {JsonOutputCommand} from '@/commands/JsonOutputCommand'
import {extractUserArgs, parseArgs, resolveCommand} from '@/pipeline/CliArgumentParser'
import {
createLogger,
discoverOutputRuntimeTargets,
setGlobalLogLevel
} from '@truenine/memory-sync-sdk'
import {
extractUserArgs,
parseArgs,
resolveCommand
} from '@/pipeline/CliArgumentParser'

export class PluginPipeline {
private readonly logger: ILogger
Expand All @@ -34,44 +45,60 @@ export class PluginPipeline {
}

async run(config: PipelineConfig): Promise<CommandResult> {
const {context, outputPlugins, userConfigOptions} = config
const {context, outputPlugins, userConfigOptions, executionPlan} = config
this.registerOutputPlugins([...outputPlugins])
let command: Command = resolveCommand(this.args)

if (!this.args.jsonFlag) return command.execute(this.createCommandContext(context, userConfigOptions))

setGlobalLogLevel('silent')
if (!new Set(['config-show', 'plugins']).has(command.name)) command = new JsonOutputCommand(command)
return command.execute(this.createCommandContext(context, userConfigOptions))
const command: Command = resolveCommand(this.args)
return command.execute(
this.createCommandContext(context, userConfigOptions, executionPlan)
)
}

private createCommandContext(ctx: OutputCollectedContext, userConfigOptions: Required<PluginOptions>): CommandContext {
private createCommandContext(
ctx: OutputCollectedContext,
userConfigOptions: Required<PluginOptions>,
executionPlan: PipelineConfig['executionPlan']
): CommandContext {
return {
logger: this.logger,
outputPlugins: this.outputPlugins,
collectedOutputContext: ctx,
userConfigOptions,
createCleanContext: dryRun => this.createCleanContext(ctx, userConfigOptions, dryRun),
createWriteContext: dryRun => this.createWriteContext(ctx, userConfigOptions, dryRun)
executionPlan,
createCleanContext: dryRun =>
this.createCleanContext(ctx, userConfigOptions, executionPlan, dryRun),
createWriteContext: dryRun =>
this.createWriteContext(ctx, userConfigOptions, executionPlan, dryRun)
}
}

private createCleanContext(ctx: OutputCollectedContext, userConfigOptions: Required<PluginOptions>, dryRun: boolean): OutputCleanContext {
private createCleanContext(
ctx: OutputCollectedContext,
userConfigOptions: Required<PluginOptions>,
executionPlan: PipelineConfig['executionPlan'],
dryRun: boolean
): OutputCleanContext {
return {
logger: this.logger,
collectedOutputContext: ctx,
pluginOptions: userConfigOptions,
runtimeTargets: this.getRuntimeTargets(),
executionPlan,
dryRun
}
}

private createWriteContext(ctx: OutputCollectedContext, userConfigOptions: Required<PluginOptions>, dryRun: boolean): OutputWriteContext {
private createWriteContext(
ctx: OutputCollectedContext,
userConfigOptions: Required<PluginOptions>,
executionPlan: PipelineConfig['executionPlan'],
dryRun: boolean
): OutputWriteContext {
return {
logger: this.logger,
collectedOutputContext: ctx,
pluginOptions: userConfigOptions,
runtimeTargets: this.getRuntimeTargets(),
executionPlan,
dryRun,
registeredPluginNames: this.outputPlugins.map(plugin => plugin.name)
}
Expand Down
66 changes: 51 additions & 15 deletions cli/src/cli-runtime.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,24 @@
import {afterEach, describe, expect, it, vi} from 'vitest'

const {createDefaultPluginConfigMock, pipelineRunMock, pluginPipelineCtorMock} = vi.hoisted(() => ({
const {
createDefaultPluginConfigMock,
pipelineRunMock,
pluginPipelineCtorMock
} = vi.hoisted(() => ({
createDefaultPluginConfigMock: vi.fn(),
pipelineRunMock: vi.fn(),
pluginPipelineCtorMock: vi.fn()
}))

function createEmptyProjectsBySeries() {
return {
app: [],
ext: [],
arch: [],
softwares: []
}
}

vi.mock('./plugin.config', () => ({
createDefaultPluginConfig: createDefaultPluginConfigMock
}))
Expand All @@ -32,20 +45,43 @@ describe('cli runtime lightweight commands', () => {
expect(pipelineRunMock).not.toHaveBeenCalled()
})

it('emits JSON for --version --json without loading plugin config', async () => {
it('passes the real cwd into the standard plugin config path', async () => {
const {runCli} = await import('./cli-runtime')
const writeSpy = vi.spyOn(process.stdout, 'write').mockImplementation(() => true)
try {
const exitCode = await runCli(['node', 'tnmsc', '--version', '--json'])
expect(exitCode).toBe(0)
expect(createDefaultPluginConfigMock).not.toHaveBeenCalled()
expect(pluginPipelineCtorMock).not.toHaveBeenCalled()
expect(pipelineRunMock).not.toHaveBeenCalled()
const payload = JSON.parse(String(writeSpy.mock.calls[0]?.[0])) as {readonly success: boolean, readonly message?: string}
expect(payload.success).toBe(true)
expect(payload.message).toBe('Version displayed')
} finally {
writeSpy.mockRestore()
}
createDefaultPluginConfigMock.mockResolvedValue({
context: {
workspace: {
directory: {
pathKind: 'absolute',
path: process.cwd(),
getDirectoryName: () => 'cwd'
},
projects: []
}
},
outputPlugins: [],
userConfigOptions: {},
executionPlan: {
scope: 'workspace',
cwd: process.cwd(),
workspaceDir: process.cwd(),
projectsBySeries: createEmptyProjectsBySeries()
}
})
pipelineRunMock.mockResolvedValue({
success: true,
filesAffected: 0,
dirsAffected: 0
})

const exitCode = await runCli(['node', 'tnmsc'])

expect(exitCode).toBe(0)
expect(createDefaultPluginConfigMock).toHaveBeenCalledWith(
['node', 'tnmsc'],
void 0,
process.cwd()
)
expect(pluginPipelineCtorMock).toHaveBeenCalledWith('node', 'tnmsc')
expect(pipelineRunMock).toHaveBeenCalledTimes(1)
})
})
Loading