diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ee0e2129..157a2b07 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,9 +7,9 @@ on: - main # Set up for Trusted Publishing by NPM -permissions: +permissions: id-token: write - contents: read + contents: write jobs: release: diff --git a/packages/cli/src/commands/generate/implementation/repo.ts b/packages/cli/src/commands/generate/implementation/repo.ts index 82dd651a..b03e5e41 100644 --- a/packages/cli/src/commands/generate/implementation/repo.ts +++ b/packages/cli/src/commands/generate/implementation/repo.ts @@ -56,11 +56,22 @@ async function generateRepo({ printOutput, }); - //const resolvedContext = await resolveEffectiveContext({ instance, workspace, branch }, core); - const { instanceConfig, workspaceConfig, branchConfig } = await resolveConfigs({ - cliContext: { instance, workspace, branch }, - core, - }); + let instanceConfig, workspaceConfig, branchConfig; + if (input && !fetch) { + // Skip context validation, provide dummy configs or minimal required fields + instanceConfig = { + name: instance || 'defaultInstance', + process: { output: output || './output' }, + }; + workspaceConfig = { name: workspace || 'defaultWorkspace', id: 'dummyId' }; + branchConfig = { label: branch || 'main' }; + } else { + // Perform normal context resolution and validation + ({ instanceConfig, workspaceConfig, branchConfig } = await resolveConfigs({ + cliContext: { instance, workspace, branch }, + core, + })); + } // Resolve output dir const outputDir = output @@ -96,25 +107,42 @@ async function generateRepo({ log.step(`Reading and parsing YAML file -> ${inputFile}`); const fileContents = await core.storage.readFile(inputFile, 'utf8'); const jsonData = load(fileContents); - const plannedWrites: { path: string; content: string }[] = await core.generateRepo({ jsonData, instance: instanceConfig.name, workspace: workspaceConfig.name, branch: branchConfig.label, }); + log.step(`Writing Repository to the output directory -> ${outputDir}`); - await Promise.all( + + // Track results for logging + const writeResults = await Promise.all( plannedWrites.map(async ({ path, content }) => { const outputPath = joinPath(outputDir, path); const writeDir = dirname(outputPath); - if (!(await core.storage.exists(writeDir))) { - await core.storage.mkdir(writeDir, { recursive: true }); + + try { + if (!(await core.storage.exists(writeDir))) { + await core.storage.mkdir(writeDir, { recursive: true }); + } + await core.storage.writeFile(outputPath, content); + return { path: outputPath, success: true }; + } catch (err) { + return { path: outputPath, success: false, error: err }; } - await core.storage.writeFile(outputPath, content); }) ); + // Summary log + const failedWrites = writeResults.filter((r) => !r.success); + if (failedWrites.length) { + log.warn(`Some files failed to write (${failedWrites.length}):`); + failedWrites.forEach((r) => log.warn(` - ${r.path}: ${r.error}`)); + } else { + log.info('All files written successfully.'); + } + printOutputDir(printOutput, outputDir); outro('Directory structure rebuilt successfully!'); } diff --git a/packages/cli/src/commands/test/implementation/test.ts b/packages/cli/src/commands/test/implementation/test.ts index 4fe7a54d..81b3a55b 100644 --- a/packages/cli/src/commands/test/implementation/test.ts +++ b/packages/cli/src/commands/test/implementation/test.ts @@ -10,7 +10,25 @@ import { } from '../../../utils/index'; /** - * Print a formatted table of test outcomes and an optional detailed warnings section to the log. + * + * @param cliEnvVars - object of CLI provided env vars (e.g., {DEMO_ADMIN_PWD: 'xyz'}) + * @returns flat object with keys as-is, to be used with {{ENVIRONMENT.KEY}} template pattern + */ +function collectInitialRuntimeValues(cliEnvVars = {}) { + // 1. Collect process.env XANO_* vars (Node only) + const envVars = {}; + for (const [k, v] of Object.entries(process.env)) { + if (k.startsWith('XANO_')) envVars[k] = v; + } + + // 2. Merge CLI over ENV, CLI wins + const merged = { ...envVars, ...cliEnvVars }; + + return merged; +} + +/** + * Prints a formatted summary table of test outcomes to the log. * * The table includes columns for status, HTTP method, path, warnings count, and duration (ms), * followed by an aggregate summary line with total, passed, failed, and total duration. @@ -154,6 +172,7 @@ async function runTest({ isAll = false, printOutput = false, core, + cliTestEnvVars, }: { instance: string; workspace: string; @@ -163,6 +182,7 @@ async function runTest({ isAll: boolean; printOutput: boolean; core: any; + cliTestEnvVars: any; }) { intro('☣️ Starting up the testing...'); @@ -188,6 +208,11 @@ async function runTest({ const testConfig = await loadTestConfig(testConfigPath); const s = spinner(); s.start('Running tests based on the provided spec'); + + // Collect env vars to set up + const initialRuntimeValues = collectInitialRuntimeValues(cliTestEnvVars); + + // Run tests const testResults = await core.runTests({ context: { instance: instanceConfig.name, @@ -196,6 +221,7 @@ async function runTest({ }, groups: groups, testConfig, + initialRuntimeValues, }); s.stop(); @@ -227,4 +253,4 @@ async function runTest({ } } -export { runTest }; \ No newline at end of file +export { runTest }; diff --git a/packages/cli/src/commands/test/index.ts b/packages/cli/src/commands/test/index.ts index 9d666b81..749f5533 100644 --- a/packages/cli/src/commands/test/index.ts +++ b/packages/cli/src/commands/test/index.ts @@ -1,4 +1,9 @@ -import { addApiGroupOptions, addFullContextOptions, addPrintOutputFlag, withErrorHandler } from '../../utils'; +import { + addApiGroupOptions, + addFullContextOptions, + addPrintOutputFlag, + withErrorHandler, +} from '../../utils'; import { runTest } from './implementation/test'; function registerTestCommands(program, core) { @@ -21,13 +26,27 @@ function registerTestCommands(program, core) { runTestsCommand .option('--test-config-path ', 'Local path to the test configuration file.') + .option( + '--test-env ', + 'Inject environment variables (KEY=VALUE) for tests. Can be repeated to set multiple.' + ) .action( withErrorHandler(async (options) => { + const cliTestEnvVars = {}; + if (options.testEnv) { + for (const arg of options.testEnv) { + const [key, ...rest] = arg.split('='); + if (key && rest.length > 0) { + cliTestEnvVars[key] = rest.join('='); + } + } + } await runTest({ ...options, isAll: options.all, printOutput: options.printOutputDir, core, + cliTestEnvVars, }); }) ); diff --git a/packages/core/src/features/testing/index.ts b/packages/core/src/features/testing/index.ts index 5b03ec7e..cea5e5db 100644 --- a/packages/core/src/features/testing/index.ts +++ b/packages/core/src/features/testing/index.ts @@ -67,6 +67,7 @@ async function testRunner({ testConfig, core, storage, + initialRuntimeValues = {}, }: { context: CoreContext; groups: ApiGroupConfig[]; @@ -81,6 +82,7 @@ async function testRunner({ }[]; core: Caly; storage: Caly['storage']; + initialRuntimeValues?: Record; }): Promise< { group: ApiGroupConfig; @@ -109,7 +111,7 @@ async function testRunner({ 'X-Data-Source': 'test', 'X-Branch': branchConfig.label, }; - let runtimeValues = {}; + let runtimeValues = initialRuntimeValues ?? {}; let finalOutput = []; @@ -259,4 +261,4 @@ async function testRunner({ return finalOutput; } -export { testRunner }; \ No newline at end of file +export { testRunner }; diff --git a/packages/core/src/implementations/run-tests.ts b/packages/core/src/implementations/run-tests.ts index 5197a28b..eb8cea11 100644 --- a/packages/core/src/implementations/run-tests.ts +++ b/packages/core/src/implementations/run-tests.ts @@ -8,6 +8,7 @@ async function runTestsImplementation({ testConfig, core, storage, + initialRuntimeValues, }: { context: CoreContext; groups: ApiGroupConfig[]; @@ -22,6 +23,7 @@ async function runTestsImplementation({ }[]; core: Caly; storage: Caly['storage']; + initialRuntimeValues: Record; }): Promise< { group: ApiGroupConfig; @@ -35,7 +37,7 @@ async function runTestsImplementation({ }[]; }[] > { - return await testRunner({ context, groups, testConfig, core, storage }); + return await testRunner({ context, groups, testConfig, core, storage, initialRuntimeValues }); } export { runTestsImplementation }; diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 706fa5f2..e038fa94 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -336,10 +336,12 @@ export class Caly extends TypedEmitter { context, groups, testConfig, + initialRuntimeValues, }: { context: Context; groups: ApiGroupConfig[]; testConfig: any; + initialRuntimeValues: Record; }): Promise< { group: ApiGroupConfig; @@ -359,6 +361,7 @@ export class Caly extends TypedEmitter { testConfig, core: this, storage: this.storage, + initialRuntimeValues, }); }