Skip to content

Commit 4dec8a6

Browse files
authored
Merge pull request #123 from TrueNine/dev
Update release CLI workflow to publish SDK native bindings
2 parents 0149d94 + 1ae227e commit 4dec8a6

File tree

15 files changed

+1227
-149
lines changed

15 files changed

+1227
-149
lines changed

cli/src/commands/CleanCommand.ts

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,35 @@ export class CleanCommand implements Command {
55
readonly name = 'clean'
66

77
async execute(ctx: CommandContext): Promise<CommandResult> {
8-
const {logger, outputPlugins, createCleanContext} = ctx
9-
logger.info('running clean pipeline', {command: 'clean'})
8+
const {logger, outputPlugins, createCleanContext, collectedOutputContext} = ctx
9+
logger.info('started', {
10+
command: 'clean',
11+
pluginCount: outputPlugins.length,
12+
projectCount: collectedOutputContext.workspace.projects.length,
13+
workspaceDir: collectedOutputContext.workspace.directory.path
14+
})
15+
logger.info('clean phase started', {phase: 'cleanup'})
1016
const result = await performCleanup(outputPlugins, createCleanContext(false), logger)
1117
if (result.violations.length > 0 || result.conflicts.length > 0) {
18+
logger.info('clean halted', {
19+
phase: 'cleanup',
20+
conflicts: result.conflicts.length,
21+
violations: result.violations.length,
22+
...result.message != null ? {message: result.message} : {}
23+
})
1224
return {success: false, filesAffected: 0, dirsAffected: 0, ...result.message != null ? {message: result.message} : {}}
1325
}
14-
logger.info('clean complete', {deletedFiles: result.deletedFiles, deletedDirs: result.deletedDirs})
26+
logger.info('clean phase complete', {
27+
phase: 'cleanup',
28+
deletedFiles: result.deletedFiles,
29+
deletedDirs: result.deletedDirs,
30+
errors: result.errors.length
31+
})
32+
logger.info('complete', {
33+
command: 'clean',
34+
filesAffected: result.deletedFiles,
35+
dirsAffected: result.deletedDirs
36+
})
1537
return {success: true, filesAffected: result.deletedFiles, dirsAffected: result.deletedDirs}
1638
}
1739
}

cli/src/commands/ExecuteCommand.ts

Lines changed: 63 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,47 @@ export class ExecuteCommand implements Command {
55
readonly name = 'execute'
66

77
async execute(ctx: CommandContext): Promise<CommandResult> {
8-
const {logger, outputPlugins, createCleanContext, createWriteContext} = ctx
9-
logger.info('started', {command: 'execute'})
8+
const {logger, outputPlugins, createCleanContext, createWriteContext, collectedOutputContext} = ctx
9+
logger.info('started', {
10+
command: 'execute',
11+
pluginCount: outputPlugins.length,
12+
projectCount: collectedOutputContext.workspace.projects.length,
13+
workspaceDir: collectedOutputContext.workspace.directory.path
14+
})
1015

1116
const writeCtx = createWriteContext(false)
17+
logger.info('execute phase started', {phase: 'collect-output-declarations'})
1218
const predeclaredOutputs = await collectOutputDeclarations(outputPlugins, writeCtx)
19+
const declarationCount = [...predeclaredOutputs.values()]
20+
.reduce((total, declarations) => total + declarations.length, 0)
21+
logger.info('execute phase complete', {
22+
phase: 'collect-output-declarations',
23+
pluginCount: predeclaredOutputs.size,
24+
declarationCount
25+
})
26+
27+
logger.info('execute phase started', {phase: 'cleanup-before-write'})
1328
const cleanupResult = await performCleanup(outputPlugins, createCleanContext(false), logger, predeclaredOutputs)
1429
if (cleanupResult.violations.length > 0 || cleanupResult.conflicts.length > 0) {
30+
logger.info('execute halted', {
31+
phase: 'cleanup-before-write',
32+
conflicts: cleanupResult.conflicts.length,
33+
violations: cleanupResult.violations.length,
34+
...cleanupResult.message != null ? {message: cleanupResult.message} : {}
35+
})
1536
return {success: false, filesAffected: 0, dirsAffected: 0, ...cleanupResult.message != null ? {message: cleanupResult.message} : {}}
1637
}
1738

18-
logger.info('cleanup complete', {deletedFiles: cleanupResult.deletedFiles, deletedDirs: cleanupResult.deletedDirs})
39+
logger.info('execute phase complete', {
40+
phase: 'cleanup-before-write',
41+
deletedFiles: cleanupResult.deletedFiles,
42+
deletedDirs: cleanupResult.deletedDirs
43+
})
44+
45+
logger.info('execute phase started', {
46+
phase: 'write-output-files',
47+
declarationCount
48+
})
1949
const results = await executeDeclarativeWriteOutputs(outputPlugins, writeCtx, predeclaredOutputs)
2050

2151
let totalFiles = 0
@@ -29,17 +59,46 @@ export class ExecuteCommand implements Command {
2959
}
3060
}
3161

62+
logger.info('execute phase complete', {
63+
phase: 'write-output-files',
64+
pluginCount: results.size,
65+
filesAffected: totalFiles,
66+
dirsAffected: totalDirs,
67+
writeErrors: writeErrors.length
68+
})
69+
3270
if (writeErrors.length > 0) {
71+
logger.info('execute halted', {
72+
phase: 'write-output-files',
73+
writeErrors: writeErrors.length
74+
})
3375
return {success: false, filesAffected: totalFiles, dirsAffected: totalDirs, message: writeErrors.join('\n')}
3476
}
3577

78+
logger.info('execute phase started', {phase: 'sync-wsl-mirrors'})
3679
const wslMirrorResult = await syncWindowsConfigIntoWsl(outputPlugins, writeCtx, void 0, predeclaredOutputs)
3780
if (wslMirrorResult.errors.length > 0) {
81+
logger.info('execute halted', {
82+
phase: 'sync-wsl-mirrors',
83+
mirroredFiles: wslMirrorResult.mirroredFiles,
84+
errors: wslMirrorResult.errors.length
85+
})
3886
return {success: false, filesAffected: totalFiles, dirsAffected: totalDirs, message: wslMirrorResult.errors.join('\n')}
3987
}
4088

4189
totalFiles += wslMirrorResult.mirroredFiles
42-
logger.info('complete', {command: 'execute', pluginCount: results.size})
90+
logger.info('execute phase complete', {
91+
phase: 'sync-wsl-mirrors',
92+
mirroredFiles: wslMirrorResult.mirroredFiles,
93+
warnings: wslMirrorResult.warnings.length,
94+
errors: wslMirrorResult.errors.length
95+
})
96+
logger.info('complete', {
97+
command: 'execute',
98+
pluginCount: results.size,
99+
filesAffected: totalFiles,
100+
dirsAffected: totalDirs
101+
})
43102
return {success: true, filesAffected: totalFiles, dirsAffected: totalDirs}
44103
}
45104
}

cli/src/plugin-runtime.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,14 +71,27 @@ function flushAndExit(code: number): never {
7171
async function main(): Promise<void> {
7272
const {subcommand, json, dryRun} = parseRuntimeArgs(process.argv)
7373
if (json) setGlobalLogLevel('silent')
74+
const logger = createLogger('PluginRuntime')
75+
76+
logger.info('runtime bootstrap started', {subcommand, json, dryRun})
7477

7578
const userPluginConfig = await createDefaultPluginConfig(process.argv, subcommand)
7679
let command = resolveRuntimeCommand(subcommand, dryRun)
7780
if (json && !new Set(['plugins']).has(command.name)) command = new JsonOutputCommand(command)
7881

7982
const {context, outputPlugins, userConfigOptions} = userPluginConfig
80-
const logger = createLogger('PluginRuntime')
83+
logger.info('runtime configuration resolved', {
84+
command: command.name,
85+
pluginCount: outputPlugins.length,
86+
projectCount: context.workspace.projects.length,
87+
workspaceDir: context.workspace.directory.path,
88+
...context.aindexDir != null ? {aindexDir: context.aindexDir} : {}
89+
})
8190
const runtimeTargets = discoverOutputRuntimeTargets(logger)
91+
logger.info('runtime targets discovered', {
92+
command: command.name,
93+
jetbrainsCodexDirs: runtimeTargets.jetbrainsCodexDirs.length
94+
})
8295
const createCleanContext = (dry: boolean): OutputCleanContext => ({
8396
logger,
8497
collectedOutputContext: context,
@@ -102,7 +115,15 @@ async function main(): Promise<void> {
102115
createCleanContext,
103116
createWriteContext
104117
}
118+
logger.info('command dispatch started', {command: command.name})
105119
const result = await command.execute(commandCtx)
120+
logger.info('command dispatch complete', {
121+
command: command.name,
122+
success: result.success,
123+
filesAffected: result.filesAffected,
124+
dirsAffected: result.dirsAffected,
125+
...result.message != null ? {message: result.message} : {}
126+
})
106127
if (!result.success) flushAndExit(1)
107128
flushOutput()
108129
}

libraries/logger/src/index.test.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,10 +83,28 @@ describe('logger bindings', () => {
8383

8484
logger.info('hello', {count: 1})
8585

86-
expect(nativeLogger.log).toHaveBeenCalledWith('info', 'hello', '{"count":1}')
86+
expect(nativeLogger.log).toHaveBeenCalledTimes(1)
87+
expect(nativeLogger.log).toHaveBeenCalledWith(
88+
'info',
89+
'hello',
90+
expect.any(String)
91+
)
92+
const payload = JSON.parse(String(nativeLogger.log.mock.calls[0]?.[2])) as Record<string, unknown>
93+
expect(payload['count']).toBe(1)
94+
expect(payload['loggerTiming']).toEqual(expect.any(String))
8795
expect(nativeLogger.logDiagnostic).not.toHaveBeenCalled()
8896
})
8997

98+
it('adds logger timing even when no metadata is provided', async () => {
99+
const {createLogger} = await import('./index')
100+
const logger = createLogger('logger-test')
101+
102+
logger.info('hello')
103+
104+
const payload = JSON.parse(String(nativeLogger.log.mock.calls[0]?.[2])) as Record<string, unknown>
105+
expect(payload['loggerTiming']).toEqual(expect.any(String))
106+
})
107+
90108
it('skips serializing filtered plain logs on the JS side', async () => {
91109
const {createLogger} = await import('./index')
92110
const logger = createLogger('logger-test', 'info')

libraries/logger/src/index.ts

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,11 @@ const LOG_LEVEL_PRIORITY: Readonly<Record<LogLevel, number>> = {
7474
let napiBinding: NapiLoggerModule | undefined,
7575
napiBindingError: Error | undefined
7676

77+
const LOGGER_TIMING_STATE = {
78+
processStartNs: process.hrtime.bigint(),
79+
lastLogNs: void 0 as bigint | undefined
80+
}
81+
7782
function isNapiLoggerModule(value: unknown): value is NapiLoggerModule {
7883
if (value == null || typeof value !== 'object') return false
7984

@@ -267,12 +272,55 @@ function normalizeLogArguments(message: string | object, meta: unknown[]): {mess
267272
}
268273
}
269274

275+
function formatElapsedMilliseconds(milliseconds: number): string {
276+
if (!Number.isFinite(milliseconds) || milliseconds <= 0) return '0ms'
277+
if (milliseconds >= 1000) return `${(milliseconds / 1000).toFixed(2)}s`
278+
if (milliseconds >= 100) return `${Math.round(milliseconds)}ms`
279+
return `${milliseconds.toFixed(1)}ms`
280+
}
281+
282+
function createLoggerTimingLabel(): string {
283+
const nowNs = process.hrtime.bigint()
284+
const sinceStartMs = Number(nowNs - LOGGER_TIMING_STATE.processStartNs) / 1_000_000
285+
const sincePreviousMs = LOGGER_TIMING_STATE.lastLogNs == null
286+
? sinceStartMs
287+
: Number(nowNs - LOGGER_TIMING_STATE.lastLogNs) / 1_000_000
288+
289+
LOGGER_TIMING_STATE.lastLogNs = nowNs
290+
return `+${formatElapsedMilliseconds(sincePreviousMs)} since previous log, ${formatElapsedMilliseconds(sinceStartMs)} since process start`
291+
}
292+
293+
function injectLoggerTiming(metaJson: string | undefined): string {
294+
const loggerTiming = createLoggerTimingLabel()
295+
if (metaJson == null) return serializePayload({loggerTiming})
296+
297+
try {
298+
const parsed = JSON.parse(metaJson) as unknown
299+
if (parsed != null && typeof parsed === 'object' && !Array.isArray(parsed)) {
300+
return serializePayload({
301+
...(parsed as Record<string, unknown>),
302+
loggerTiming
303+
})
304+
}
305+
306+
return serializePayload({
307+
loggerTiming,
308+
meta: parsed
309+
})
310+
} catch {
311+
return serializePayload({
312+
loggerTiming,
313+
meta: metaJson
314+
})
315+
}
316+
}
317+
270318
function createLogMethod(instance: NapiLoggerInstance, loggerLevel: LogLevel, level: PlainLogLevel): LoggerMethod {
271319
return (message: string | object, ...meta: unknown[]): void => {
272320
if (!shouldEmitLog(level, loggerLevel)) return
273321

274322
const {message: normalizedMessage, metaJson} = normalizeLogArguments(message, meta)
275-
instance.log(level, normalizedMessage, metaJson)
323+
instance.log(level, normalizedMessage, injectLoggerTiming(metaJson))
276324
}
277325
}
278326

0 commit comments

Comments
 (0)