From 1f2f7d5e2761572cb0f8691e93a4817e84ca7570 Mon Sep 17 00:00:00 2001 From: Ryan Kienstra Date: Fri, 17 Dec 2021 00:09:52 -0600 Subject: [PATCH 01/24] A very rough beginning to a nested job structure This shows the dependencies, so you don't have to guess. Todo: a bash warning when attach_workspace doesn't find anything. --- src/classes/Job.ts | 2 +- src/classes/JobProvider.ts | 105 ++++++++++++++++++--------- src/extension.ts | 4 +- src/test/suite/utils/getJobs.test.ts | 83 --------------------- src/utils/getJobs.ts | 79 +++++++------------- src/utils/index.d.ts | 2 +- 6 files changed, 101 insertions(+), 174 deletions(-) delete mode 100644 src/test/suite/utils/getJobs.test.ts diff --git a/src/classes/Job.ts b/src/classes/Job.ts index 0d4ae8f2..7e0bbd91 100644 --- a/src/classes/Job.ts +++ b/src/classes/Job.ts @@ -8,7 +8,7 @@ export default class Job extends vscode.TreeItem { super(label); const tooltip = `Runs the CircleCI® job ${this.label}`; this.jobName = label; - this.collapsibleState = vscode.TreeItemCollapsibleState.None; + this.collapsibleState = vscode.TreeItemCollapsibleState.Expanded; this.iconPath = new vscode.ThemeIcon('debug-start'); this.tooltip = `Runs the CircleCI® job ${this.label}`; diff --git a/src/classes/JobProvider.ts b/src/classes/JobProvider.ts index 67b58cec..777cec29 100644 --- a/src/classes/JobProvider.ts +++ b/src/classes/JobProvider.ts @@ -21,6 +21,12 @@ import isDockerRunning from '../utils/isDockerRunning'; import isLicenseValid from '../utils/isLicenseValid'; import isTrialExpired from '../utils/isTrialExpired'; import writeProcessFile from '../utils/writeProcessFile'; +import getJobTreeItems from '../utils/getJobTreeItems'; + +interface Element { + type: string; + label: string; +} export default class JobProvider implements vscode.TreeDataProvider @@ -29,25 +35,54 @@ export default class JobProvider new vscode.EventEmitter(); readonly onDidChangeTreeData: vscode.Event = this._onDidChangeTreeData.event; - private jobs: vscode.TreeItem[] | [] = []; + private jobs: Element[]; private runningJob: string | undefined; + private jobDependencies: Map | undefined; private suppressMessage: boolean | undefined; constructor( private readonly context: vscode.ExtensionContext, private readonly reporter: TelemetryReporter - ) {} + ) { + this.jobs = []; + this.loadJobs(); + } refresh(job?: Job, suppressMessage?: boolean): void { this.suppressMessage = suppressMessage; this._onDidChangeTreeData.fire(job); + this.loadJobs(); } getTreeItem(element: Job): vscode.TreeItem { return element; } - async getChildren(): Promise { + getChildren(element: Element): vscode.TreeItem[] | undefined{ + if (!element) { + return this.jobs.filter((job) => { + return !this?.jobDependencies.get(job.label); + }).map((job) => new Job(job.label, false)); + } + + const jobNames = this.jobDependencies?.keys(); + if (!jobNames) { + return []; + } + + const children = []; + for (const jobName of jobNames) { + if (this.jobDependencies?.get(jobName)?.length && element.label === this.jobDependencies.get(jobName)[this.jobDependencies.get(jobName)?.length - 1]) { + children.push(jobName); + } + } + + return children.map((jobName) => + new Job(jobName, false) + ); + } + + async loadJobs(): Promise { const configFilePath = await getConfigFilePath(this.context); if (!configFilePath || !fs.existsSync(configFilePath)) { this.reporter.sendTelemetryEvent('configFilePath'); @@ -58,14 +93,10 @@ export default class JobProvider this.reporter.sendTelemetryErrorEvent('noConfigFile'); } - return [ - new Warning('Error: No jobs found'), - doExistConfigPaths - ? new Command('Select repo', 'localCiJobs.selectRepo') - : new vscode.TreeItem( - 'Please add a .circleci/config.yml to this workspace' - ), - ]; + this.jobs = [{ + type: 'warning', + label: 'Error: No jobs found', + }]; } let processedConfig = ''; @@ -95,18 +126,19 @@ export default class JobProvider const dockerRunning = isDockerRunning(); if (shouldEnableExtension && dockerRunning) { - this.jobs = processError - ? [ - new Warning('Error processing the CircleCI config:'), - new vscode.TreeItem(processError), - new Command('Try Again', `${JOB_TREE_VIEW_ID}.refresh`), - ] - : await getJobs( - this.context, - processedConfig, - this.reporter, - this.runningJob - ); + if (!processError) { + this.jobDependencies = getJobs(processedConfig); + this.jobs = []; + for ( const jobName of this.jobDependencies.keys()) { + this.jobs.push({type: 'job', label: jobName}); + } + } else { + this.jobs = [{ + label:'Error processing the CircleCI config', + type: 'warning', + }] + } + this.runningJob = undefined; } @@ -118,23 +150,26 @@ export default class JobProvider this.reporter.sendTelemetryErrorEvent('dockerNotRunning'); } - if (!this.jobs.length) { + if (!this.jobs?.length) { this.reporter.sendTelemetryErrorEvent('noJobsFound'); } - return shouldEnableExtension + this.jobs = shouldEnableExtension ? dockerRunning ? this.jobs - : [ - new Warning('Error: is Docker running?'), - new vscode.TreeItem(`${getDockerError()}`), - new Command('Try Again', `${JOB_TREE_VIEW_ID}.refresh`), - ] - : [ - new Warning('Please enter a Local CI license key.'), - new Command('Get License', GET_LICENSE_COMMAND), - new Command('Enter License', ENTER_LICENSE_COMMAND), - ]; + : [{ + type: 'warning', + label: 'Error: is Docker running?', + }] + : + [{ + type: 'warning', + label: 'Please enter a Local CI license key', + }]; + } + + getParent(element: vscode.TreeItem): vscode.TreeItem { + return element; } getJob(jobName: string): vscode.TreeItem | undefined { diff --git a/src/extension.ts b/src/extension.ts index f350d1e1..523846b3 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -66,10 +66,10 @@ export function activate(context: vscode.ExtensionContext): void { }); } - const jobProvider = new JobProvider(context, reporter); reporter.sendTelemetryEvent('activate'); - + const jobProvider = new JobProvider(context, reporter); vscode.window.registerTreeDataProvider(JOB_TREE_VIEW_ID, jobProvider); + context.subscriptions.push( reporter, vscode.commands.registerCommand(`${JOB_TREE_VIEW_ID}.refresh`, () => diff --git a/src/test/suite/utils/getJobs.test.ts b/src/test/suite/utils/getJobs.test.ts deleted file mode 100644 index 6b52a4e2..00000000 --- a/src/test/suite/utils/getJobs.test.ts +++ /dev/null @@ -1,83 +0,0 @@ -import * as assert from 'assert'; -import * as fs from 'fs'; -import * as mocha from 'mocha'; -import * as sinon from 'sinon'; -import * as vscode from 'vscode'; -import * as yaml from 'js-yaml'; -import { Substitute } from '@fluffy-spoon/substitute'; -import getJobs from '../../../utils/getJobs'; -import TelemetryReporter from 'vscode-extension-telemetry'; - -mocha.afterEach(() => { - sinon.restore(); -}); - -function getMockContext() { - return Substitute.for(); -} - -suite('getJobs', () => { - test('Single job', async () => { - sinon.mock(fs).expects('existsSync').once().returns(true); - sinon.mock(fs).expects('readFileSync').once().returns(''); - sinon - .mock(yaml) - .expects('load') - .once() - .returns({ - jobs: {}, - workflows: { - 'test-deploy': { - jobs: [ - { test: { docker: [{ image: 'cimg/node:16.8.0-browsers' }] } }, - ], - }, - }, - }); - - assert.strictEqual( - ( - await getJobs( - getMockContext(), - 'example-path', - Substitute.for(), - 'build' - ) - ).length, - 1 - ); - }); - - test('Multiple jobs', async () => { - sinon.mock(fs).expects('existsSync').once().returns(true); - sinon.mock(fs).expects('readFileSync').once().returns(''); - sinon - .mock(yaml) - .expects('load') - .once() - .returns({ - jobs: {}, - workflows: { - 'test-deploy': { - jobs: [ - { lint: { docker: [{ image: 'cimg/node:16.8.0' }] } }, - { test: { docker: [{ image: 'cimg/node:16.8.0-browsers' }] } }, - { deploy: { docker: [{ image: 'cimg/node:16.8.0-browsers' }] } }, - ], - }, - }, - }); - - assert.strictEqual( - ( - await getJobs( - getMockContext(), - 'example-path', - Substitute.for(), - 'build' - ) - ).length, - 3 - ); - }); -}); diff --git a/src/utils/getJobs.ts b/src/utils/getJobs.ts index 16bae6bf..e6907c17 100644 --- a/src/utils/getJobs.ts +++ b/src/utils/getJobs.ts @@ -1,59 +1,34 @@ -import * as vscode from 'vscode'; -import TelemetryReporter from 'vscode-extension-telemetry'; -import Job from '../classes/Job'; -import Warning from '../classes/Warning'; -import Command from '../classes/Command'; -import getAllConfigFilePaths from './getAllConfigFilePaths'; import getConfig from './getConfig'; -import isWindows from './isWindows'; -export default async function getJobs( - context: vscode.ExtensionContext, - processedConfig: string, - reporter: TelemetryReporter, - runningJob?: string -): Promise { - if (isWindows()) { - reporter.sendTelemetryErrorEvent('osNotCompatible'); - - return Promise.resolve([ - new Warning(`Sorry, this doesn't work on Windows`), - ]); - } +export default function getJobs( + processedConfig: string +): Map { const config = getConfig(processedConfig); - const jobs = - !!config && Object.values(config?.workflows)?.length - ? Object.values(config?.workflows).reduce( - (accumulator: string[], workflow) => { - if (!workflow?.jobs) { - return accumulator; - } - return [ - ...accumulator, - ...workflow.jobs.reduce((accumulator: string[], job) => { - return [ - ...accumulator, - ...(typeof job === 'string' ? [job] : Object.keys(job)), - ]; - }, []), - ]; - }, - [] - ) - : []; + const allJobs = new Map(); + if (config && Object.values(config?.workflows)?.length) { + for (const workflowName in config?.workflows) { + const workflow = config.workflows[workflowName]; + if (!workflow?.jobs) { + continue; + } + + for (const job of workflow.jobs) { + if (typeof job === 'string') { + allJobs.set(job, null); + } + + const jobName = Object.keys(job).length ? Object.keys(job)[0] : null; + if (!jobName) { + continue; + } - if (!jobs.length) { - reporter.sendTelemetryEvent('noJobs'); + const jobConfig = Object.values(job).length + ? Object.values(job)[0] + : null; + allJobs.set(jobName, jobConfig?.requires); + } + } } - return jobs.length - ? jobs.map((jobName) => new Job(jobName, jobName === runningJob)) - : [ - new Warning('Error: No jobs found'), - (await getAllConfigFilePaths(context)).length - ? new Command('Select repo to get jobs', 'localCiJobs.selectRepo') - : new vscode.TreeItem( - 'Please add a .circleci/config.yml to this workspace' - ), - ]; + return allJobs; } diff --git a/src/utils/index.d.ts b/src/utils/index.d.ts index 1263cde9..3e8d3e03 100644 --- a/src/utils/index.d.ts +++ b/src/utils/index.d.ts @@ -6,7 +6,7 @@ interface CiConfigWithJobs { jobs: Jobs; workflows: { [key: string]: { - jobs: (Record|string)[]; + jobs: (Record>|string)[]; } } } From 72a409f8c4dbeb3f023e0fd559a8c336f429b9ab Mon Sep 17 00:00:00 2001 From: Ryan Kienstra Date: Sat, 18 Dec 2021 14:20:44 -0600 Subject: [PATCH 02/24] Render a nested tree structure for the jobs This still needs more work, including handling error messages. --- package.json | 20 ++-- src/classes/Job.ts | 10 +- src/classes/JobProvider.ts | 135 +++++++++++++------------ src/utils/getAttachWorkspaceCommand.ts | 11 +- 4 files changed, 98 insertions(+), 78 deletions(-) diff --git a/package.json b/package.json index 074c0567..17d984e3 100644 --- a/package.json +++ b/package.json @@ -74,11 +74,6 @@ "command": "local-ci.debug.repo", "title": "Local CI: Run Jobs Locally" }, - { - "command": "localCiJobs.enterToken", - "title": "Local CI: Enter API Token", - "icon": "$(record-keys)" - }, { "command": "localCiJobs.exitAllJobs", "title": "Local CI: Exit All Jobs", @@ -99,6 +94,11 @@ "title": "Local CI: Select Repo ", "icon": "$(debug-configure)" }, + { + "command": "localCiJobs.enterToken", + "title": "Local CI: Token Entry", + "icon": "$(record-keys)" + }, { "command": "localCiLicense.refresh", "title": "Local CI: Refresh License Information", @@ -114,11 +114,6 @@ ], "menus": { "view/title": [ - { - "command": "localCiJobs.enterToken", - "when": "view == localCiJobs", - "group": "navigation" - }, { "command": "localCiJobs.exitAllJobs", "when": "view == localCiJobs", @@ -139,6 +134,11 @@ "when": "view == localCiJobs", "group": "navigation" }, + { + "command": "localCiJobs.enterToken", + "when": "view == localCiJobs", + "group": "navigation" + }, { "command": "localCiLicense.refresh", "when": "view == localCiLicense", diff --git a/src/classes/Job.ts b/src/classes/Job.ts index 7e0bbd91..241f9777 100644 --- a/src/classes/Job.ts +++ b/src/classes/Job.ts @@ -4,11 +4,17 @@ import { EXIT_JOB_COMMAND, RUN_JOB_COMMAND } from '../constants'; export default class Job extends vscode.TreeItem { private jobName: string; - constructor(public readonly label: string, isRunning?: boolean) { + constructor( + public readonly label: string, + isRunning?: boolean, + hasChildJob?: boolean + ) { super(label); const tooltip = `Runs the CircleCI® job ${this.label}`; this.jobName = label; - this.collapsibleState = vscode.TreeItemCollapsibleState.Expanded; + this.collapsibleState = hasChildJob + ? vscode.TreeItemCollapsibleState.Expanded + : vscode.TreeItemCollapsibleState.None; this.iconPath = new vscode.ThemeIcon('debug-start'); this.tooltip = `Runs the CircleCI® job ${this.label}`; diff --git a/src/classes/JobProvider.ts b/src/classes/JobProvider.ts index 777cec29..edeb0eb0 100644 --- a/src/classes/JobProvider.ts +++ b/src/classes/JobProvider.ts @@ -1,33 +1,33 @@ import * as fs from 'fs'; import * as vscode from 'vscode'; import TelemetryReporter from 'vscode-extension-telemetry'; -import Command from './Command'; import Job from './Job'; -import Warning from './Warning'; import getJobs from '../utils/getJobs'; import getProcessedConfig from '../utils/getProcessedConfig'; -import { - GET_LICENSE_COMMAND, - ENTER_LICENSE_COMMAND, - TRIAL_STARTED_TIMESTAMP, - JOB_TREE_VIEW_ID, -} from '../constants'; +import { TRIAL_STARTED_TIMESTAMP } from '../constants'; import getAllConfigFilePaths from '../utils/getAllConfigFilePaths'; import getConfigFilePath from '../utils/getConfigFilePath'; -import getDockerError from '../utils/getDockerError'; import getProcessFilePath from '../utils/getProcessFilePath'; import getTrialLength from '../utils/getTrialLength'; import isDockerRunning from '../utils/isDockerRunning'; import isLicenseValid from '../utils/isLicenseValid'; import isTrialExpired from '../utils/isTrialExpired'; import writeProcessFile from '../utils/writeProcessFile'; -import getJobTreeItems from '../utils/getJobTreeItems'; interface Element { type: string; label: string; } +enum JobError { + dockerNotRunning, + noConfigFilePathInWorkspace, + noConfigFilePathSelected, + noJobsFound, + licenseKey, + processFile, +} + export default class JobProvider implements vscode.TreeDataProvider { @@ -35,7 +35,8 @@ export default class JobProvider new vscode.EventEmitter(); readonly onDidChangeTreeData: vscode.Event = this._onDidChangeTreeData.event; - private jobs: Element[]; + private jobs: string[]; + private jobError: JobError | undefined; private runningJob: string | undefined; private jobDependencies: Map | undefined; private suppressMessage: boolean | undefined; @@ -58,11 +59,15 @@ export default class JobProvider return element; } - getChildren(element: Element): vscode.TreeItem[] | undefined{ - if (!element) { - return this.jobs.filter((job) => { - return !this?.jobDependencies.get(job.label); - }).map((job) => new Job(job.label, false)); + getChildren(parentElement: Element): vscode.TreeItem[] | undefined { + if (!parentElement) { + // This has no parent element, so it's at the root level. + // Only include jobs that have no dependency. + return this.getJobTreeItems( + this.jobs.filter((jobName) => { + return !this?.jobDependencies?.get(jobName); + }) + ); } const jobNames = this.jobDependencies?.keys(); @@ -72,31 +77,59 @@ export default class JobProvider const children = []; for (const jobName of jobNames) { - if (this.jobDependencies?.get(jobName)?.length && element.label === this.jobDependencies.get(jobName)[this.jobDependencies.get(jobName)?.length - 1]) { + const jobDependencies = this?.jobDependencies?.get(jobName); + const dependencyLength = jobDependencies?.length; + if ( + dependencyLength && + parentElement.label === jobDependencies[dependencyLength - 1] + ) { children.push(jobName); } } - return children.map((jobName) => - new Job(jobName, false) + return this.getJobTreeItems(children); + } + + getJobTreeItems(jobs: string[]): Job[] { + return jobs.map( + (jobName) => + new Job(jobName, jobName === this.runningJob, this.hasChildJob(jobName)) ); } + getErrorTreeItems(): void {} + + hasChildJob(jobName: string): boolean { + for (const [, possibleChildDependecies] of this?.jobDependencies ?? []) { + if ( + possibleChildDependecies?.length && + jobName === + possibleChildDependecies[possibleChildDependecies.length - 1] + ) { + return true; + } + } + + return false; + } + async loadJobs(): Promise { + this.jobs = []; + this.jobError = undefined; + const configFilePath = await getConfigFilePath(this.context); if (!configFilePath || !fs.existsSync(configFilePath)) { this.reporter.sendTelemetryEvent('configFilePath'); + this.jobError = JobError.noConfigFilePathSelected; const doExistConfigPaths = !!(await getAllConfigFilePaths(this.context)) .length; if (!doExistConfigPaths) { this.reporter.sendTelemetryErrorEvent('noConfigFile'); + this.jobError = JobError.noConfigFilePathInWorkspace; } - this.jobs = [{ - type: 'warning', - label: 'Error: No jobs found', - }]; + return; } let processedConfig = ''; @@ -123,59 +156,35 @@ export default class JobProvider this.context.globalState.get(TRIAL_STARTED_TIMESTAMP), getTrialLength(this.context) ); - const dockerRunning = isDockerRunning(); - - if (shouldEnableExtension && dockerRunning) { - if (!processError) { - this.jobDependencies = getJobs(processedConfig); - this.jobs = []; - for ( const jobName of this.jobDependencies.keys()) { - this.jobs.push({type: 'job', label: jobName}); - } - } else { - this.jobs = [{ - label:'Error processing the CircleCI config', - type: 'warning', - }] - } - - this.runningJob = undefined; - } - if (processError) { - this.reporter.sendTelemetryErrorEvent('processError'); + if (!shouldEnableExtension) { + this.jobError = JobError.licenseKey; + return; } - if (!dockerRunning) { + if (!isDockerRunning()) { this.reporter.sendTelemetryErrorEvent('dockerNotRunning'); + this.jobError = JobError.dockerNotRunning; + return; } - if (!this.jobs?.length) { - this.reporter.sendTelemetryErrorEvent('noJobsFound'); + if (processError) { + this.reporter.sendTelemetryErrorEvent('processError'); + this.jobError = JobError.processFile; + return; } - this.jobs = shouldEnableExtension - ? dockerRunning - ? this.jobs - : [{ - type: 'warning', - label: 'Error: is Docker running?', - }] - : - [{ - type: 'warning', - label: 'Please enter a Local CI license key', - }]; + this.runningJob = undefined; + this.jobDependencies = getJobs(processedConfig); + for (const jobName of this.jobDependencies.keys()) { + this.jobs.push(jobName); + } } - getParent(element: vscode.TreeItem): vscode.TreeItem { + getParent(element: Element): Element { return element; } - getJob(jobName: string): vscode.TreeItem | undefined { - return this.jobs.find((job) => jobName === (job as Job)?.getJobName()); - } - setRunningJob(jobName: string): void { this.runningJob = jobName; } diff --git a/src/utils/getAttachWorkspaceCommand.ts b/src/utils/getAttachWorkspaceCommand.ts index d42c9ed0..bb5d6670 100644 --- a/src/utils/getAttachWorkspaceCommand.ts +++ b/src/utils/getAttachWorkspaceCommand.ts @@ -8,7 +8,12 @@ export default function getAttachWorkspaceCommand(step: Step): string { const attachFrom = `${path.join(CONTAINER_STORAGE_DIRECTORY, path.sep)}.`; - // BusyBox doesn't have the -n option. - // Check if the directory is empty before copying it. - return `if [ -d ${CONTAINER_STORAGE_DIRECTORY} ] && [ "$(ls -A ${CONTAINER_STORAGE_DIRECTORY})" ]; then cp -rn ${attachFrom} ${step?.attach_workspace?.at} || cp -ru ${attachFrom} ${step?.attach_workspace?.at}; fi`; + // BusyBox doesn't allow cp -n. + return `if [ ! -d ${CONTAINER_STORAGE_DIRECTORY} ]; then + echo "Error: tried to attach_workspace to ${CONTAINER_STORAGE_DIRECTORY}, but it's not a directory. It might require a job to run before it." + elif [ ! "$(ls -A ${CONTAINER_STORAGE_DIRECTORY})" ]; then + echo "Error: tried to attach_workspace to ${CONTAINER_STORAGE_DIRECTORY}, but it's empty. It might require a job to run before it." + else + cp -rn ${attachFrom} ${step?.attach_workspace?.at} || cp -ru ${attachFrom} ${step?.attach_workspace?.at} + fi`; } From e123e638671ccfad34a4aab03578651d18f03605 Mon Sep 17 00:00:00 2001 From: Ryan Kienstra Date: Sat, 18 Dec 2021 15:19:12 -0600 Subject: [PATCH 03/24] Fix failed unit tests, including the expected .yml file --- src/classes/JobProvider.ts | 94 ++++++++++++++----- src/test/expected/config-with-cache.yml | 71 +++++++++++--- .../utils/getAttachWorkspaceCommand.test.ts | 16 +++- 3 files changed, 140 insertions(+), 41 deletions(-) diff --git a/src/classes/JobProvider.ts b/src/classes/JobProvider.ts index edeb0eb0..76cf21cb 100644 --- a/src/classes/JobProvider.ts +++ b/src/classes/JobProvider.ts @@ -4,7 +4,12 @@ import TelemetryReporter from 'vscode-extension-telemetry'; import Job from './Job'; import getJobs from '../utils/getJobs'; import getProcessedConfig from '../utils/getProcessedConfig'; -import { TRIAL_STARTED_TIMESTAMP } from '../constants'; +import { + ENTER_LICENSE_COMMAND, + GET_LICENSE_COMMAND, + JOB_TREE_VIEW_ID, + TRIAL_STARTED_TIMESTAMP, +} from '../constants'; import getAllConfigFilePaths from '../utils/getAllConfigFilePaths'; import getConfigFilePath from '../utils/getConfigFilePath'; import getProcessFilePath from '../utils/getProcessFilePath'; @@ -13,6 +18,9 @@ import isDockerRunning from '../utils/isDockerRunning'; import isLicenseValid from '../utils/isLicenseValid'; import isTrialExpired from '../utils/isTrialExpired'; import writeProcessFile from '../utils/writeProcessFile'; +import Warning from './Warning'; +import Command from './Command'; +import getDockerError from '../utils/getDockerError'; interface Element { type: string; @@ -23,7 +31,6 @@ enum JobError { dockerNotRunning, noConfigFilePathInWorkspace, noConfigFilePathSelected, - noJobsFound, licenseKey, processFile, } @@ -36,7 +43,8 @@ export default class JobProvider readonly onDidChangeTreeData: vscode.Event = this._onDidChangeTreeData.event; private jobs: string[]; - private jobError: JobError | undefined; + private jobErrorType: JobError | undefined; + private jobErrorMessage: string | undefined; private runningJob: string | undefined; private jobDependencies: Map | undefined; private suppressMessage: boolean | undefined; @@ -49,10 +57,10 @@ export default class JobProvider this.loadJobs(); } - refresh(job?: Job, suppressMessage?: boolean): void { + async refresh(job?: Job, suppressMessage?: boolean): Promise { + await this.loadJobs(); this.suppressMessage = suppressMessage; this._onDidChangeTreeData.fire(job); - this.loadJobs(); } getTreeItem(element: Job): vscode.TreeItem { @@ -61,13 +69,13 @@ export default class JobProvider getChildren(parentElement: Element): vscode.TreeItem[] | undefined { if (!parentElement) { - // This has no parent element, so it's at the root level. - // Only include jobs that have no dependency. - return this.getJobTreeItems( - this.jobs.filter((jobName) => { - return !this?.jobDependencies?.get(jobName); - }) - ); + return this.jobs.length + ? this.getJobTreeItems( + this.jobs.filter((jobName) => { + return !this?.jobDependencies?.get(jobName); + }) + ) + : this.getErrorTreeItems(); } const jobNames = this.jobDependencies?.keys(); @@ -77,7 +85,7 @@ export default class JobProvider const children = []; for (const jobName of jobNames) { - const jobDependencies = this?.jobDependencies?.get(jobName); + const jobDependencies = this?.jobDependencies?.get(jobName) ?? []; const dependencyLength = jobDependencies?.length; if ( dependencyLength && @@ -97,7 +105,47 @@ export default class JobProvider ); } - getErrorTreeItems(): void {} + getErrorTreeItems(): vscode.TreeItem[] { + switch (this.jobErrorType) { + case JobError.processFile: + return [ + new Warning('Error processing the CircleCI config:'), + new vscode.TreeItem(this.getJobErrorMessage()), + new Command('Try Again', `${JOB_TREE_VIEW_ID}.refresh`), + ]; + + case JobError.dockerNotRunning: + return [ + new Warning('Error: is Docker running?'), + new vscode.TreeItem(`${this.getJobErrorMessage()}`), + new Command('Try Again', `${JOB_TREE_VIEW_ID}.refresh`), + ]; + case JobError.licenseKey: + return [ + new Warning('Please enter a Local CI license key.'), + new Command('Get License', GET_LICENSE_COMMAND), + new Command('Enter License', ENTER_LICENSE_COMMAND), + ]; + case JobError.noConfigFilePathInWorkspace: + return [ + new Warning('Error: No jobs found'), + new vscode.TreeItem( + 'Please add a .circleci/config.yml to this workspace' + ), + ]; + case JobError.noConfigFilePathSelected: + return [ + new Warning('Error: No jobs found'), + new Command('Select repo', 'localCiJobs.selectRepo'), + ]; + default: + return [new Warning('Error: No jobs found')]; + } + } + + getJobErrorMessage(): string { + return this.jobErrorMessage || ''; + } hasChildJob(jobName: string): boolean { for (const [, possibleChildDependecies] of this?.jobDependencies ?? []) { @@ -115,18 +163,18 @@ export default class JobProvider async loadJobs(): Promise { this.jobs = []; - this.jobError = undefined; + this.jobErrorType = undefined; const configFilePath = await getConfigFilePath(this.context); if (!configFilePath || !fs.existsSync(configFilePath)) { this.reporter.sendTelemetryEvent('configFilePath'); - this.jobError = JobError.noConfigFilePathSelected; + this.jobErrorType = JobError.noConfigFilePathSelected; const doExistConfigPaths = !!(await getAllConfigFilePaths(this.context)) .length; if (!doExistConfigPaths) { this.reporter.sendTelemetryErrorEvent('noConfigFile'); - this.jobError = JobError.noConfigFilePathInWorkspace; + this.jobErrorType = JobError.noConfigFilePathInWorkspace; } return; @@ -158,19 +206,21 @@ export default class JobProvider ); if (!shouldEnableExtension) { - this.jobError = JobError.licenseKey; + this.jobErrorType = JobError.licenseKey; return; } if (!isDockerRunning()) { this.reporter.sendTelemetryErrorEvent('dockerNotRunning'); - this.jobError = JobError.dockerNotRunning; + this.jobErrorType = JobError.dockerNotRunning; + this.jobErrorMessage = getDockerError(); return; } if (processError) { this.reporter.sendTelemetryErrorEvent('processError'); - this.jobError = JobError.processFile; + this.jobErrorType = JobError.processFile; + this.jobErrorMessage = processError; return; } @@ -181,10 +231,6 @@ export default class JobProvider } } - getParent(element: Element): Element { - return element; - } - setRunningJob(jobName: string): void { this.runningJob = jobName; } diff --git a/src/test/expected/config-with-cache.yml b/src/test/expected/config-with-cache.yml index b7b6fd7e..749d440a 100644 --- a/src/test/expected/config-with-cache.yml +++ b/src/test/expected/config-with-cache.yml @@ -61,8 +61,14 @@ jobs: steps: - run: name: Attach workspace - command: >- - if [ -d /tmp/local-ci ] && [ "$(ls -A /tmp/local-ci)" ]; then cp -rn /tmp/local-ci/. . || cp -ru /tmp/local-ci/. .; fi + command: |- + if [ ! -d /tmp/local-ci ]; then + echo "Error: tried to attach_workspace to /tmp/local-ci, but it's not a directory. It might require a job to run before it." + elif [ ! "$(ls -A /tmp/local-ci)" ]; then + echo "Error: tried to attach_workspace to /tmp/local-ci, but it's empty. It might require a job to run before it." + else + cp -rn /tmp/local-ci/. . || cp -ru /tmp/local-ci/. . + fi - run: name: Run prettier check on project files command: npm run prettier:check @@ -83,8 +89,14 @@ jobs: steps: - run: name: Attach workspace - command: >- - if [ -d /tmp/local-ci ] && [ "$(ls -A /tmp/local-ci)" ]; then cp -rn /tmp/local-ci/. . || cp -ru /tmp/local-ci/. .; fi + command: |- + if [ ! -d /tmp/local-ci ]; then + echo "Error: tried to attach_workspace to /tmp/local-ci, but it's not a directory. It might require a job to run before it." + elif [ ! "$(ls -A /tmp/local-ci)" ]; then + echo "Error: tried to attach_workspace to /tmp/local-ci, but it's empty. It might require a job to run before it." + else + cp -rn /tmp/local-ci/. . || cp -ru /tmp/local-ci/. . + fi - run: name: Run linter command: npm run lint @@ -93,8 +105,14 @@ jobs: steps: - run: name: Attach workspace - command: >- - if [ -d /tmp/local-ci ] && [ "$(ls -A /tmp/local-ci)" ]; then cp -rn /tmp/local-ci/. . || cp -ru /tmp/local-ci/. .; fi + command: |- + if [ ! -d /tmp/local-ci ]; then + echo "Error: tried to attach_workspace to /tmp/local-ci, but it's not a directory. It might require a job to run before it." + elif [ ! "$(ls -A /tmp/local-ci)" ]; then + echo "Error: tried to attach_workspace to /tmp/local-ci, but it's empty. It might require a job to run before it." + else + cp -rn /tmp/local-ci/. . || cp -ru /tmp/local-ci/. . + fi - run: name: Run unit tests command: npm run test:unit @@ -103,8 +121,14 @@ jobs: steps: - run: name: Attach workspace - command: >- - if [ -d /tmp/local-ci ] && [ "$(ls -A /tmp/local-ci)" ]; then cp -rn /tmp/local-ci/. . || cp -ru /tmp/local-ci/. .; fi + command: |- + if [ ! -d /tmp/local-ci ]; then + echo "Error: tried to attach_workspace to /tmp/local-ci, but it's not a directory. It might require a job to run before it." + elif [ ! "$(ls -A /tmp/local-ci)" ]; then + echo "Error: tried to attach_workspace to /tmp/local-ci, but it's empty. It might require a job to run before it." + else + cp -rn /tmp/local-ci/. . || cp -ru /tmp/local-ci/. . + fi - run: name: Restore cache command: > @@ -134,9 +158,14 @@ jobs: steps: - run: name: Attach workspace - command: >- - if [ -d /tmp/local-ci ] && [ "$(ls -A /tmp/local-ci)" ]; then cp -rn - /tmp/local-ci/. . || cp -ru /tmp/local-ci/. .; fi + command: |- + if [ ! -d /tmp/local-ci ]; then + echo "Error: tried to attach_workspace to /tmp/local-ci, but it's not a directory. It might require a job to run before it." + elif [ ! "$(ls -A /tmp/local-ci)" ]; then + echo "Error: tried to attach_workspace to /tmp/local-ci, but it's empty. It might require a job to run before it." + else + cp -rn /tmp/local-ci/. . || cp -ru /tmp/local-ci/. . + fi - run: name: Build command: npm run build @@ -148,8 +177,14 @@ jobs: steps: - run: name: Attach workspace - command: >- - if [ -d /tmp/local-ci ] && [ "$(ls -A /tmp/local-ci)" ]; then cp -rn /tmp/local-ci/. . || cp -ru /tmp/local-ci/. .; fi + command: |- + if [ ! -d /tmp/local-ci ]; then + echo "Error: tried to attach_workspace to /tmp/local-ci, but it's not a directory. It might require a job to run before it." + elif [ ! "$(ls -A /tmp/local-ci)" ]; then + echo "Error: tried to attach_workspace to /tmp/local-ci, but it's empty. It might require a job to run before it." + else + cp -rn /tmp/local-ci/. . || cp -ru /tmp/local-ci/. . + fi - run: name: Check bundles sizes command: npm run bundlesize @@ -158,8 +193,14 @@ jobs: steps: - run: name: Attach workspace - command: >- - if [ -d /tmp/local-ci ] && [ "$(ls -A /tmp/local-ci)" ]; then cp -rn /tmp/local-ci/. . || cp -ru /tmp/local-ci/. .; fi + command: |- + if [ ! -d /tmp/local-ci ]; then + echo "Error: tried to attach_workspace to /tmp/local-ci, but it's not a directory. It might require a job to run before it." + elif [ ! "$(ls -A /tmp/local-ci)" ]; then + echo "Error: tried to attach_workspace to /tmp/local-ci, but it's empty. It might require a job to run before it." + else + cp -rn /tmp/local-ci/. . || cp -ru /tmp/local-ci/. . + fi - run: name: Firebase deploy command: | diff --git a/src/test/suite/utils/getAttachWorkspaceCommand.test.ts b/src/test/suite/utils/getAttachWorkspaceCommand.test.ts index 57f6c55e..bf95c864 100644 --- a/src/test/suite/utils/getAttachWorkspaceCommand.test.ts +++ b/src/test/suite/utils/getAttachWorkspaceCommand.test.ts @@ -1,4 +1,5 @@ import * as assert from 'assert'; +import { normalize } from '../../helpers'; import getAttachWorkspaceCommand from '../../../utils/getAttachWorkspaceCommand'; suite('getAttachWorkspaceCommand', () => { @@ -8,8 +9,19 @@ suite('getAttachWorkspaceCommand', () => { test('With attach_workspace', () => { assert.strictEqual( - getAttachWorkspaceCommand({ attach_workspace: { at: '/foo/baz' } }), // eslint-disable-line @typescript-eslint/naming-convention - 'if [ -d /tmp/local-ci ] && [ "$(ls -A /tmp/local-ci)" ]; then cp -rn /tmp/local-ci/. /foo/baz || cp -ru /tmp/local-ci/. /foo/baz; fi' + normalize( + // eslint-disable-next-line @typescript-eslint/naming-convention + getAttachWorkspaceCommand({ attach_workspace: { at: '/foo/baz' } }) + ), + normalize( + `if [ ! -d /tmp/local-ci ]; then + echo "Error: tried to attach_workspace to /tmp/local-ci, but it's not a directory. It might require a job to run before it." + elif [ ! "$(ls -A /tmp/local-ci)" ]; then + echo "Error: tried to attach_workspace to /tmp/local-ci, but it's empty. It might require a job to run before it." + else + cp -rn /tmp/local-ci/. /foo/baz || cp -ru /tmp/local-ci/. /foo/baz + fi` + ) ); }); }); From be0f9e5d1366b9c312fa0a8514cb910c35662716 Mon Sep 17 00:00:00 2001 From: Ryan Kienstra Date: Sat, 18 Dec 2021 15:29:42 -0600 Subject: [PATCH 04/24] Fix a bug where there was sometimes a 0 job Simply continue if the job is a string. --- src/classes/JobProvider.ts | 11 ++++++----- src/utils/getJobs.ts | 1 + 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/classes/JobProvider.ts b/src/classes/JobProvider.ts index 76cf21cb..f1f1f734 100644 --- a/src/classes/JobProvider.ts +++ b/src/classes/JobProvider.ts @@ -126,6 +126,11 @@ export default class JobProvider new Command('Get License', GET_LICENSE_COMMAND), new Command('Enter License', ENTER_LICENSE_COMMAND), ]; + case JobError.noConfigFilePathSelected: + return [ + new Warning('Error: No jobs found'), + new Command('Select repo', 'localCiJobs.selectRepo'), + ]; case JobError.noConfigFilePathInWorkspace: return [ new Warning('Error: No jobs found'), @@ -133,11 +138,6 @@ export default class JobProvider 'Please add a .circleci/config.yml to this workspace' ), ]; - case JobError.noConfigFilePathSelected: - return [ - new Warning('Error: No jobs found'), - new Command('Select repo', 'localCiJobs.selectRepo'), - ]; default: return [new Warning('Error: No jobs found')]; } @@ -164,6 +164,7 @@ export default class JobProvider async loadJobs(): Promise { this.jobs = []; this.jobErrorType = undefined; + this.jobErrorMessage = undefined; const configFilePath = await getConfigFilePath(this.context); if (!configFilePath || !fs.existsSync(configFilePath)) { diff --git a/src/utils/getJobs.ts b/src/utils/getJobs.ts index e6907c17..b9a699b7 100644 --- a/src/utils/getJobs.ts +++ b/src/utils/getJobs.ts @@ -15,6 +15,7 @@ export default function getJobs( for (const job of workflow.jobs) { if (typeof job === 'string') { allJobs.set(job, null); + continue; } const jobName = Object.keys(job).length ? Object.keys(job)[0] : null; From b9dd55b6075c3f1cd13f1fedc1ce61190ad5f4db Mon Sep 17 00:00:00 2001 From: Ryan Kienstra Date: Sat, 18 Dec 2021 15:35:51 -0600 Subject: [PATCH 05/24] Rename possibleChildDependecies to dependencies The longer name didn't make it any easier to understand. --- .circleci/config.yml | 2 +- src/classes/JobProvider.ts | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index a98482e0..d6b9b253 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -21,7 +21,7 @@ jobs: - run: npm test workflows: - test-publish: + test-lint: jobs: - test: filters: diff --git a/src/classes/JobProvider.ts b/src/classes/JobProvider.ts index f1f1f734..cff48fbc 100644 --- a/src/classes/JobProvider.ts +++ b/src/classes/JobProvider.ts @@ -148,11 +148,10 @@ export default class JobProvider } hasChildJob(jobName: string): boolean { - for (const [, possibleChildDependecies] of this?.jobDependencies ?? []) { + for (const [, dependecies] of this?.jobDependencies ?? []) { if ( - possibleChildDependecies?.length && - jobName === - possibleChildDependecies[possibleChildDependecies.length - 1] + dependecies?.length && + jobName === dependecies[dependecies.length - 1] ) { return true; } From 16296786ebee7b08341cc5d3631a23d9ee9ea5a9 Mon Sep 17 00:00:00 2001 From: Ryan Kienstra Date: Sat, 18 Dec 2021 15:56:53 -0600 Subject: [PATCH 06/24] Ensure jobs are loaded on instantiation Somtimes, the jobs didn't load in time to do getChildren(). --- src/classes/JobProvider.ts | 6 +++--- src/extension.ts | 6 +++++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/classes/JobProvider.ts b/src/classes/JobProvider.ts index cff48fbc..8ecf65b6 100644 --- a/src/classes/JobProvider.ts +++ b/src/classes/JobProvider.ts @@ -54,7 +54,6 @@ export default class JobProvider private readonly reporter: TelemetryReporter ) { this.jobs = []; - this.loadJobs(); } async refresh(job?: Job, suppressMessage?: boolean): Promise { @@ -168,11 +167,12 @@ export default class JobProvider const configFilePath = await getConfigFilePath(this.context); if (!configFilePath || !fs.existsSync(configFilePath)) { this.reporter.sendTelemetryEvent('configFilePath'); - this.jobErrorType = JobError.noConfigFilePathSelected; const doExistConfigPaths = !!(await getAllConfigFilePaths(this.context)) .length; - if (!doExistConfigPaths) { + if (doExistConfigPaths) { + this.jobErrorType = JobError.noConfigFilePathSelected; + } else { this.reporter.sendTelemetryErrorEvent('noConfigFile'); this.jobErrorType = JobError.noConfigFilePathInWorkspace; } diff --git a/src/extension.ts b/src/extension.ts index 523846b3..17888517 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -68,7 +68,11 @@ export function activate(context: vscode.ExtensionContext): void { reporter.sendTelemetryEvent('activate'); const jobProvider = new JobProvider(context, reporter); - vscode.window.registerTreeDataProvider(JOB_TREE_VIEW_ID, jobProvider); + jobProvider + .loadJobs() + .then(() => + vscode.window.registerTreeDataProvider(JOB_TREE_VIEW_ID, jobProvider) + ); context.subscriptions.push( reporter, From 4947601efcc5f24a5e175dd0a9f9df643093b379 Mon Sep 17 00:00:00 2001 From: Ryan Kienstra Date: Sat, 18 Dec 2021 15:59:32 -0600 Subject: [PATCH 07/24] Prevent an error message from including the string undefined --- src/utils/showLicenseInput.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/showLicenseInput.ts b/src/utils/showLicenseInput.ts index df404b58..1fae6138 100644 --- a/src/utils/showLicenseInput.ts +++ b/src/utils/showLicenseInput.ts @@ -38,7 +38,7 @@ export default async function showLicenseInput( completedCallback(); const warningMessage = enteredLicenseKey ? `Sorry, there was a problem activating the Local CI license key: ${getLicenseErrorMessage( - String(await context.secrets.get(LICENSE_ERROR)) + (await context.secrets.get(LICENSE_ERROR)) || '' )}` : 'Please enter a Local CI license key'; const clicked = await vscode.window.showWarningMessage( From 680f518ee36062588ccf34a701ff2a69e92a93ca Mon Sep 17 00:00:00 2001 From: Ryan Kienstra Date: Sun, 19 Dec 2021 15:51:41 -0600 Subject: [PATCH 08/24] Update CircleCI link to point to First Steps page Also, add Trending Monthly badge for the VS Code Marketplace. --- README.md | 5 +++-- package.json | 5 +++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d3bc3e35..c8fc6d53 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,8 @@ [![2 day free preview](https://img.shields.io/badge/trial-2%20day-orange)](https://getlocalci.com/pricing/?utm_medium=extension&utm_source=readme) [![Buy license key](https://img.shields.io/badge/%24-paid-orange)](https://getlocalci.com/pricing/?utm_medium=extension&utm_source=readme) +[![Trending Monthly][https://vsmarketplacebadge.apphb.com/trending-monthly/LocalCI.local-ci.svg?logo=tinder&color=orange&logoColor=white&label=trending%20monthly](https://marketplace.visualstudio.com/items?itemName=LocalCI.local-ci)] [![Platform: macOS](https://img.shields.io/badge/platform-macOS-yellow)](https://en.wikipedia.org/wiki/MacOS) -[![Requires CircleCI®](https://img.shields.io/badge/requires-CirlcleCI%C2%AE-yellow)](https://circleci.com) +[![Requires CircleCI®](https://img.shields.io/badge/requires-CirlcleCI%C2%AE-yellow)](https://circleci.com/docs/2.0/first-steps/) Debug entire CircleCI® workflows locally, with Bash access and persistence between jobs. @@ -51,7 +52,7 @@ But first you'll get a free 2-day preview, no sign-up or credit card needed. ## Requirements -[CircleCI®](https://circleci.com/), [macOS](https://en.wikipedia.org/wiki/MacOS), [Docker](https://www.docker.com/) +[CircleCI®](https://circleci.com/docs/2.0/first-steps/), [macOS](https://en.wikipedia.org/wiki/MacOS), [Docker](https://www.docker.com/) If you don't have a CircleCI® account, you can [get started](https://circleci.com/docs/2.0/first-steps/) with CircleCI® for free. diff --git a/package.json b/package.json index 17d984e3..a4d1625e 100644 --- a/package.json +++ b/package.json @@ -242,6 +242,11 @@ "href": "https://getlocalci.com/pricing/?utm_medium=extension&utm_source=badge", "description": "Buy license key" }, + { + "url": "https://vsmarketplacebadge.apphb.com/trending-monthly/LocalCI.local-ci.svg?logo=tinder&color=orange&logoColor=white&label=trending%20monthly", + "href": "https://marketplace.visualstudio.com/items?itemName=LocalCI.local-ci", + "description": "Trending Monthly" + }, { "url": "https://img.shields.io/badge/platform-macOS-yellow", "href": "https://en.wikipedia.org/wiki/MacOS", From 49574b49726ba8ba444b9182ccdb876f3f78a4a9 Mon Sep 17 00:00:00 2001 From: Ryan Kienstra Date: Sun, 19 Dec 2021 15:53:36 -0600 Subject: [PATCH 09/24] Correct the brackets in README.md badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c8fc6d53..2cf31f0e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ [![2 day free preview](https://img.shields.io/badge/trial-2%20day-orange)](https://getlocalci.com/pricing/?utm_medium=extension&utm_source=readme) [![Buy license key](https://img.shields.io/badge/%24-paid-orange)](https://getlocalci.com/pricing/?utm_medium=extension&utm_source=readme) -[![Trending Monthly][https://vsmarketplacebadge.apphb.com/trending-monthly/LocalCI.local-ci.svg?logo=tinder&color=orange&logoColor=white&label=trending%20monthly](https://marketplace.visualstudio.com/items?itemName=LocalCI.local-ci)] +[![Trending Monthly](https://vsmarketplacebadge.apphb.com/trending-monthly/LocalCI.local-ci.svg?logo=tinder&color=orange&logoColor=white&label=trending%20monthly)](https://marketplace.visualstudio.com/items?itemName=LocalCI.local-ci) [![Platform: macOS](https://img.shields.io/badge/platform-macOS-yellow)](https://en.wikipedia.org/wiki/MacOS) [![Requires CircleCI®](https://img.shields.io/badge/requires-CirlcleCI%C2%AE-yellow)](https://circleci.com/docs/2.0/first-steps/) From cc39aa25b0c998e99d49036a6025a9cf2d39513c Mon Sep 17 00:00:00 2001 From: Ryan Kienstra Date: Sun, 19 Dec 2021 15:55:02 -0600 Subject: [PATCH 10/24] Remove trending monthly badge from README.md It's gaudy and doesn't add to what users need to know. --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 2cf31f0e..2a170618 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,5 @@ [![2 day free preview](https://img.shields.io/badge/trial-2%20day-orange)](https://getlocalci.com/pricing/?utm_medium=extension&utm_source=readme) [![Buy license key](https://img.shields.io/badge/%24-paid-orange)](https://getlocalci.com/pricing/?utm_medium=extension&utm_source=readme) -[![Trending Monthly](https://vsmarketplacebadge.apphb.com/trending-monthly/LocalCI.local-ci.svg?logo=tinder&color=orange&logoColor=white&label=trending%20monthly)](https://marketplace.visualstudio.com/items?itemName=LocalCI.local-ci) [![Platform: macOS](https://img.shields.io/badge/platform-macOS-yellow)](https://en.wikipedia.org/wiki/MacOS) [![Requires CircleCI®](https://img.shields.io/badge/requires-CirlcleCI%C2%AE-yellow)](https://circleci.com/docs/2.0/first-steps/) From 8b8fdbf06d0fe40822f4988e36161a3c9b406010 Mon Sep 17 00:00:00 2001 From: Ryan Kienstra Date: Sun, 19 Dec 2021 20:49:22 -0600 Subject: [PATCH 11/24] Add 'caching' to the description --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a4d1625e..cc0b1fa9 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "local-ci", "displayName": "Local CI", - "description": "Debug CircleCI® workflows locally, with Bash access during and after. Free preview, then paid.", + "description": "Debug CircleCI® workflows locally, with Bash access and caching. Free preview, then paid.", "version": "1.3.1", "publisher": "LocalCI", "contributors": [ From 813da14ad234d72dc29ed420adf7112d550a0684 Mon Sep 17 00:00:00 2001 From: Ryan Kienstra Date: Sun, 19 Dec 2021 20:50:29 -0600 Subject: [PATCH 12/24] Change Error: to Warning: Error might be a little too alarming, as it might be expected for it to happen. --- src/test/expected/config-with-cache.yml | 28 +++++++++---------- .../utils/getAttachWorkspaceCommand.test.ts | 4 +-- src/utils/getAttachWorkspaceCommand.ts | 4 +-- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/test/expected/config-with-cache.yml b/src/test/expected/config-with-cache.yml index 749d440a..3529d88b 100644 --- a/src/test/expected/config-with-cache.yml +++ b/src/test/expected/config-with-cache.yml @@ -63,9 +63,9 @@ jobs: name: Attach workspace command: |- if [ ! -d /tmp/local-ci ]; then - echo "Error: tried to attach_workspace to /tmp/local-ci, but it's not a directory. It might require a job to run before it." + echo "Warning: tried to attach_workspace to /tmp/local-ci, but it's not a directory. It might require a job to run before it." elif [ ! "$(ls -A /tmp/local-ci)" ]; then - echo "Error: tried to attach_workspace to /tmp/local-ci, but it's empty. It might require a job to run before it." + echo "Warning: tried to attach_workspace to /tmp/local-ci, but it's empty. It might require a job to run before it." else cp -rn /tmp/local-ci/. . || cp -ru /tmp/local-ci/. . fi @@ -91,9 +91,9 @@ jobs: name: Attach workspace command: |- if [ ! -d /tmp/local-ci ]; then - echo "Error: tried to attach_workspace to /tmp/local-ci, but it's not a directory. It might require a job to run before it." + echo "Warning: tried to attach_workspace to /tmp/local-ci, but it's not a directory. It might require a job to run before it." elif [ ! "$(ls -A /tmp/local-ci)" ]; then - echo "Error: tried to attach_workspace to /tmp/local-ci, but it's empty. It might require a job to run before it." + echo "Warning: tried to attach_workspace to /tmp/local-ci, but it's empty. It might require a job to run before it." else cp -rn /tmp/local-ci/. . || cp -ru /tmp/local-ci/. . fi @@ -107,9 +107,9 @@ jobs: name: Attach workspace command: |- if [ ! -d /tmp/local-ci ]; then - echo "Error: tried to attach_workspace to /tmp/local-ci, but it's not a directory. It might require a job to run before it." + echo "Warning: tried to attach_workspace to /tmp/local-ci, but it's not a directory. It might require a job to run before it." elif [ ! "$(ls -A /tmp/local-ci)" ]; then - echo "Error: tried to attach_workspace to /tmp/local-ci, but it's empty. It might require a job to run before it." + echo "Warning: tried to attach_workspace to /tmp/local-ci, but it's empty. It might require a job to run before it." else cp -rn /tmp/local-ci/. . || cp -ru /tmp/local-ci/. . fi @@ -123,9 +123,9 @@ jobs: name: Attach workspace command: |- if [ ! -d /tmp/local-ci ]; then - echo "Error: tried to attach_workspace to /tmp/local-ci, but it's not a directory. It might require a job to run before it." + echo "Warning: tried to attach_workspace to /tmp/local-ci, but it's not a directory. It might require a job to run before it." elif [ ! "$(ls -A /tmp/local-ci)" ]; then - echo "Error: tried to attach_workspace to /tmp/local-ci, but it's empty. It might require a job to run before it." + echo "Warning: tried to attach_workspace to /tmp/local-ci, but it's empty. It might require a job to run before it." else cp -rn /tmp/local-ci/. . || cp -ru /tmp/local-ci/. . fi @@ -160,9 +160,9 @@ jobs: name: Attach workspace command: |- if [ ! -d /tmp/local-ci ]; then - echo "Error: tried to attach_workspace to /tmp/local-ci, but it's not a directory. It might require a job to run before it." + echo "Warning: tried to attach_workspace to /tmp/local-ci, but it's not a directory. It might require a job to run before it." elif [ ! "$(ls -A /tmp/local-ci)" ]; then - echo "Error: tried to attach_workspace to /tmp/local-ci, but it's empty. It might require a job to run before it." + echo "Warning: tried to attach_workspace to /tmp/local-ci, but it's empty. It might require a job to run before it." else cp -rn /tmp/local-ci/. . || cp -ru /tmp/local-ci/. . fi @@ -179,9 +179,9 @@ jobs: name: Attach workspace command: |- if [ ! -d /tmp/local-ci ]; then - echo "Error: tried to attach_workspace to /tmp/local-ci, but it's not a directory. It might require a job to run before it." + echo "Warning: tried to attach_workspace to /tmp/local-ci, but it's not a directory. It might require a job to run before it." elif [ ! "$(ls -A /tmp/local-ci)" ]; then - echo "Error: tried to attach_workspace to /tmp/local-ci, but it's empty. It might require a job to run before it." + echo "Warning: tried to attach_workspace to /tmp/local-ci, but it's empty. It might require a job to run before it." else cp -rn /tmp/local-ci/. . || cp -ru /tmp/local-ci/. . fi @@ -195,9 +195,9 @@ jobs: name: Attach workspace command: |- if [ ! -d /tmp/local-ci ]; then - echo "Error: tried to attach_workspace to /tmp/local-ci, but it's not a directory. It might require a job to run before it." + echo "Warning: tried to attach_workspace to /tmp/local-ci, but it's not a directory. It might require a job to run before it." elif [ ! "$(ls -A /tmp/local-ci)" ]; then - echo "Error: tried to attach_workspace to /tmp/local-ci, but it's empty. It might require a job to run before it." + echo "Warning: tried to attach_workspace to /tmp/local-ci, but it's empty. It might require a job to run before it." else cp -rn /tmp/local-ci/. . || cp -ru /tmp/local-ci/. . fi diff --git a/src/test/suite/utils/getAttachWorkspaceCommand.test.ts b/src/test/suite/utils/getAttachWorkspaceCommand.test.ts index bf95c864..8c9df879 100644 --- a/src/test/suite/utils/getAttachWorkspaceCommand.test.ts +++ b/src/test/suite/utils/getAttachWorkspaceCommand.test.ts @@ -15,9 +15,9 @@ suite('getAttachWorkspaceCommand', () => { ), normalize( `if [ ! -d /tmp/local-ci ]; then - echo "Error: tried to attach_workspace to /tmp/local-ci, but it's not a directory. It might require a job to run before it." + echo "Warning: tried to attach_workspace to /tmp/local-ci, but it's not a directory. It might require a job to run before it." elif [ ! "$(ls -A /tmp/local-ci)" ]; then - echo "Error: tried to attach_workspace to /tmp/local-ci, but it's empty. It might require a job to run before it." + echo "Warning: tried to attach_workspace to /tmp/local-ci, but it's empty. It might require a job to run before it." else cp -rn /tmp/local-ci/. /foo/baz || cp -ru /tmp/local-ci/. /foo/baz fi` diff --git a/src/utils/getAttachWorkspaceCommand.ts b/src/utils/getAttachWorkspaceCommand.ts index bb5d6670..dcbbcd3a 100644 --- a/src/utils/getAttachWorkspaceCommand.ts +++ b/src/utils/getAttachWorkspaceCommand.ts @@ -10,9 +10,9 @@ export default function getAttachWorkspaceCommand(step: Step): string { // BusyBox doesn't allow cp -n. return `if [ ! -d ${CONTAINER_STORAGE_DIRECTORY} ]; then - echo "Error: tried to attach_workspace to ${CONTAINER_STORAGE_DIRECTORY}, but it's not a directory. It might require a job to run before it." + echo "Warning: tried to attach_workspace to ${CONTAINER_STORAGE_DIRECTORY}, but it's not a directory. It might require a job to run before it." elif [ ! "$(ls -A ${CONTAINER_STORAGE_DIRECTORY})" ]; then - echo "Error: tried to attach_workspace to ${CONTAINER_STORAGE_DIRECTORY}, but it's empty. It might require a job to run before it." + echo "Warning: tried to attach_workspace to ${CONTAINER_STORAGE_DIRECTORY}, but it's empty. It might require a job to run before it." else cp -rn ${attachFrom} ${step?.attach_workspace?.at} || cp -ru ${attachFrom} ${step?.attach_workspace?.at} fi`; From 6706cc8ab4936dc9a4337dc4581fabebd920cf44 Mon Sep 17 00:00:00 2001 From: Ryan Kienstra Date: Sun, 19 Dec 2021 20:51:02 -0600 Subject: [PATCH 13/24] Add back in the test for getJobs(), but modify it for the new function --- src/test/suite/utils/getJobs.test.ts | 54 ++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 src/test/suite/utils/getJobs.test.ts diff --git a/src/test/suite/utils/getJobs.test.ts b/src/test/suite/utils/getJobs.test.ts new file mode 100644 index 00000000..18b37071 --- /dev/null +++ b/src/test/suite/utils/getJobs.test.ts @@ -0,0 +1,54 @@ +import * as assert from 'assert'; +import * as yaml from 'js-yaml'; +import getJobs from '../../../utils/getJobs'; + +suite('getJobs', () => { + test('Single job', () => { + assert.strictEqual( + getJobs( + yaml.dump({ + jobs: {}, + workflows: { + 'test-deploy': { + jobs: [{ test: {} }], + workflows: { + 'test-deploy': { + jobs: [ + { + test: { + docker: [{ image: 'cimg/node:16.8.0-browsers' }], + }, + }, + ], + }, + }, + }, + }, + }) + ).size, + 1 + ); + }); + + test('Multiple jobs', () => { + assert.strictEqual( + getJobs( + yaml.dump({ + jobs: [{ lint: {}, test: {}, deploy: {} }], + workflows: { + 'test-deploy': { + jobs: [ + { lint: { docker: [{ image: 'cimg/node:16.8.0' }] } }, + { test: { docker: [{ image: 'cimg/node:16.8.0-browsers' }] } }, + { + deploy: { docker: [{ image: 'cimg/node:16.8.0-browsers' }] }, + }, + ], + }, + }, + }) + ).size, + 3 + ); + }); +}); From f51c99bf4727de0ec0befa3952b00c993944824e Mon Sep 17 00:00:00 2001 From: Ryan Kienstra Date: Sun, 19 Dec 2021 21:03:55 -0600 Subject: [PATCH 14/24] Remove needless property from Element, change to TreeElement --- src/classes/JobProvider.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/classes/JobProvider.ts b/src/classes/JobProvider.ts index 8ecf65b6..1e00011a 100644 --- a/src/classes/JobProvider.ts +++ b/src/classes/JobProvider.ts @@ -22,8 +22,7 @@ import Warning from './Warning'; import Command from './Command'; import getDockerError from '../utils/getDockerError'; -interface Element { - type: string; +interface TreeElement { label: string; } @@ -66,7 +65,7 @@ export default class JobProvider return element; } - getChildren(parentElement: Element): vscode.TreeItem[] | undefined { + getChildren(parentElement: TreeElement): vscode.TreeItem[] | undefined { if (!parentElement) { return this.jobs.length ? this.getJobTreeItems( From 20ad1bb2d3ff419b205f0497dd753d4f7e922b41 Mon Sep 17 00:00:00 2001 From: Ryan Kienstra Date: Sun, 19 Dec 2021 21:11:56 -0600 Subject: [PATCH 15/24] Make constructor arguments required, other cleanups --- src/classes/Job.ts | 4 ++-- src/classes/JobProvider.ts | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/classes/Job.ts b/src/classes/Job.ts index 241f9777..b5d27b55 100644 --- a/src/classes/Job.ts +++ b/src/classes/Job.ts @@ -6,8 +6,8 @@ export default class Job extends vscode.TreeItem { constructor( public readonly label: string, - isRunning?: boolean, - hasChildJob?: boolean + isRunning: boolean, + hasChildJob: boolean ) { super(label); const tooltip = `Runs the CircleCI® job ${this.label}`; diff --git a/src/classes/JobProvider.ts b/src/classes/JobProvider.ts index 1e00011a..cba0c615 100644 --- a/src/classes/JobProvider.ts +++ b/src/classes/JobProvider.ts @@ -28,9 +28,9 @@ interface TreeElement { enum JobError { dockerNotRunning, + licenseKey, noConfigFilePathInWorkspace, noConfigFilePathSelected, - licenseKey, processFile, } @@ -85,6 +85,7 @@ export default class JobProvider for (const jobName of jobNames) { const jobDependencies = this?.jobDependencies?.get(jobName) ?? []; const dependencyLength = jobDependencies?.length; + // This element's children should be the jobs that list it as their last dependency. if ( dependencyLength && parentElement.label === jobDependencies[dependencyLength - 1] @@ -111,7 +112,6 @@ export default class JobProvider new vscode.TreeItem(this.getJobErrorMessage()), new Command('Try Again', `${JOB_TREE_VIEW_ID}.refresh`), ]; - case JobError.dockerNotRunning: return [ new Warning('Error: is Docker running?'), @@ -145,6 +145,7 @@ export default class JobProvider return this.jobErrorMessage || ''; } + // A job has a child job if any other job has it as the last value in its requires array. hasChildJob(jobName: string): boolean { for (const [, dependecies] of this?.jobDependencies ?? []) { if ( From 27e0b792b245c82d9912d5f8586e1428e45e5aa4 Mon Sep 17 00:00:00 2001 From: Ryan Kienstra Date: Sun, 19 Dec 2021 21:27:14 -0600 Subject: [PATCH 16/24] Remove the needless TreeElement --- src/classes/JobProvider.ts | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/classes/JobProvider.ts b/src/classes/JobProvider.ts index cba0c615..7c06e7a8 100644 --- a/src/classes/JobProvider.ts +++ b/src/classes/JobProvider.ts @@ -22,10 +22,6 @@ import Warning from './Warning'; import Command from './Command'; import getDockerError from '../utils/getDockerError'; -interface TreeElement { - label: string; -} - enum JobError { dockerNotRunning, licenseKey, @@ -61,11 +57,11 @@ export default class JobProvider this._onDidChangeTreeData.fire(job); } - getTreeItem(element: Job): vscode.TreeItem { - return element; + getTreeItem(treeItem: vscode.TreeItem): vscode.TreeItem { + return treeItem; } - getChildren(parentElement: TreeElement): vscode.TreeItem[] | undefined { + getChildren(parentElement: vscode.TreeItem): vscode.TreeItem[] { if (!parentElement) { return this.jobs.length ? this.getJobTreeItems( @@ -163,6 +159,7 @@ export default class JobProvider this.jobs = []; this.jobErrorType = undefined; this.jobErrorMessage = undefined; + this.runningJob = undefined; const configFilePath = await getConfigFilePath(this.context); if (!configFilePath || !fs.existsSync(configFilePath)) { @@ -224,7 +221,6 @@ export default class JobProvider return; } - this.runningJob = undefined; this.jobDependencies = getJobs(processedConfig); for (const jobName of this.jobDependencies.keys()) { this.jobs.push(jobName); From b6fc09bfafa1286eb4c98013c48d02f7124ef21c Mon Sep 17 00:00:00 2001 From: Ryan Kienstra Date: Sun, 19 Dec 2021 21:41:17 -0600 Subject: [PATCH 17/24] Make a comment more clear --- src/classes/JobProvider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/classes/JobProvider.ts b/src/classes/JobProvider.ts index 7c06e7a8..8e8c8cd8 100644 --- a/src/classes/JobProvider.ts +++ b/src/classes/JobProvider.ts @@ -81,7 +81,7 @@ export default class JobProvider for (const jobName of jobNames) { const jobDependencies = this?.jobDependencies?.get(jobName) ?? []; const dependencyLength = jobDependencies?.length; - // This element's children should be the jobs that list it as their last dependency. + // This element's children should be the jobs that list it as their last dependency in the requires array. if ( dependencyLength && parentElement.label === jobDependencies[dependencyLength - 1] From d7cd78aac31d53daa0adab3153b0aa1e15265d85 Mon Sep 17 00:00:00 2001 From: Ryan Kienstra Date: Sun, 19 Dec 2021 21:58:38 -0600 Subject: [PATCH 18/24] Remove the warning for no jobs found, as it flashes before jobs are found Sometimes that warning will show, and then the jobs will appear 0.5 seconds later. --- src/classes/JobProvider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/classes/JobProvider.ts b/src/classes/JobProvider.ts index 8e8c8cd8..6cb993d4 100644 --- a/src/classes/JobProvider.ts +++ b/src/classes/JobProvider.ts @@ -133,7 +133,7 @@ export default class JobProvider ), ]; default: - return [new Warning('Error: No jobs found')]; + return []; } } From d5e8745e07b263a2afea3f1e201228c1c138a4e2 Mon Sep 17 00:00:00 2001 From: Ryan Kienstra Date: Sun, 19 Dec 2021 22:00:46 -0600 Subject: [PATCH 19/24] Assign jobs above the constructor --- src/classes/JobProvider.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/classes/JobProvider.ts b/src/classes/JobProvider.ts index 6cb993d4..ebc2d5fd 100644 --- a/src/classes/JobProvider.ts +++ b/src/classes/JobProvider.ts @@ -37,7 +37,7 @@ export default class JobProvider new vscode.EventEmitter(); readonly onDidChangeTreeData: vscode.Event = this._onDidChangeTreeData.event; - private jobs: string[]; + private jobs: string[] = []; private jobErrorType: JobError | undefined; private jobErrorMessage: string | undefined; private runningJob: string | undefined; @@ -47,9 +47,7 @@ export default class JobProvider constructor( private readonly context: vscode.ExtensionContext, private readonly reporter: TelemetryReporter - ) { - this.jobs = []; - } + ) {} async refresh(job?: Job, suppressMessage?: boolean): Promise { await this.loadJobs(); From eef651b65de83cca2839f5aab58ca6f1b1f3bb83 Mon Sep 17 00:00:00 2001 From: Ryan Kienstra Date: Sun, 19 Dec 2021 22:19:29 -0600 Subject: [PATCH 20/24] Move loadJobs() higher in the class, as it's really important --- src/classes/JobProvider.ts | 144 ++++++++++++++++++------------------- 1 file changed, 72 insertions(+), 72 deletions(-) diff --git a/src/classes/JobProvider.ts b/src/classes/JobProvider.ts index ebc2d5fd..829c43aa 100644 --- a/src/classes/JobProvider.ts +++ b/src/classes/JobProvider.ts @@ -55,6 +55,78 @@ export default class JobProvider this._onDidChangeTreeData.fire(job); } + async loadJobs(): Promise { + this.jobs = []; + this.jobErrorType = undefined; + this.jobErrorMessage = undefined; + this.runningJob = undefined; + + const configFilePath = await getConfigFilePath(this.context); + if (!configFilePath || !fs.existsSync(configFilePath)) { + this.reporter.sendTelemetryEvent('configFilePath'); + + const doExistConfigPaths = !!(await getAllConfigFilePaths(this.context)) + .length; + if (doExistConfigPaths) { + this.jobErrorType = JobError.noConfigFilePathSelected; + } else { + this.reporter.sendTelemetryErrorEvent('noConfigFile'); + this.jobErrorType = JobError.noConfigFilePathInWorkspace; + } + + return; + } + + let processedConfig = ''; + let processError = ''; + try { + processedConfig = getProcessedConfig(configFilePath); + writeProcessFile(processedConfig, getProcessFilePath(configFilePath)); + } catch (e) { + processError = (e as ErrorWithMessage)?.message; + if (!this.suppressMessage) { + vscode.window.showErrorMessage( + `There was an error processing the CircleCI config: ${ + (e as ErrorWithMessage)?.message + }` + ); + } + + this.reporter.sendTelemetryErrorEvent('writeProcessFile'); + } + + const shouldEnableExtension = + (await isLicenseValid(this.context)) || + !isTrialExpired( + this.context.globalState.get(TRIAL_STARTED_TIMESTAMP), + getTrialLength(this.context) + ); + + if (!shouldEnableExtension) { + this.jobErrorType = JobError.licenseKey; + return; + } + + if (!isDockerRunning()) { + this.reporter.sendTelemetryErrorEvent('dockerNotRunning'); + this.jobErrorType = JobError.dockerNotRunning; + this.jobErrorMessage = getDockerError(); + return; + } + + if (processError) { + this.reporter.sendTelemetryErrorEvent('processError'); + this.jobErrorType = JobError.processFile; + this.jobErrorMessage = processError; + return; + } + + this.jobDependencies = getJobs(processedConfig); + for (const jobName of this.jobDependencies.keys()) { + this.jobs.push(jobName); + } + } + getTreeItem(treeItem: vscode.TreeItem): vscode.TreeItem { return treeItem; } @@ -153,78 +225,6 @@ export default class JobProvider return false; } - async loadJobs(): Promise { - this.jobs = []; - this.jobErrorType = undefined; - this.jobErrorMessage = undefined; - this.runningJob = undefined; - - const configFilePath = await getConfigFilePath(this.context); - if (!configFilePath || !fs.existsSync(configFilePath)) { - this.reporter.sendTelemetryEvent('configFilePath'); - - const doExistConfigPaths = !!(await getAllConfigFilePaths(this.context)) - .length; - if (doExistConfigPaths) { - this.jobErrorType = JobError.noConfigFilePathSelected; - } else { - this.reporter.sendTelemetryErrorEvent('noConfigFile'); - this.jobErrorType = JobError.noConfigFilePathInWorkspace; - } - - return; - } - - let processedConfig = ''; - let processError = ''; - try { - processedConfig = getProcessedConfig(configFilePath); - writeProcessFile(processedConfig, getProcessFilePath(configFilePath)); - } catch (e) { - processError = (e as ErrorWithMessage)?.message; - if (!this.suppressMessage) { - vscode.window.showErrorMessage( - `There was an error processing the CircleCI config: ${ - (e as ErrorWithMessage)?.message - }` - ); - } - - this.reporter.sendTelemetryErrorEvent('writeProcessFile'); - } - - const shouldEnableExtension = - (await isLicenseValid(this.context)) || - !isTrialExpired( - this.context.globalState.get(TRIAL_STARTED_TIMESTAMP), - getTrialLength(this.context) - ); - - if (!shouldEnableExtension) { - this.jobErrorType = JobError.licenseKey; - return; - } - - if (!isDockerRunning()) { - this.reporter.sendTelemetryErrorEvent('dockerNotRunning'); - this.jobErrorType = JobError.dockerNotRunning; - this.jobErrorMessage = getDockerError(); - return; - } - - if (processError) { - this.reporter.sendTelemetryErrorEvent('processError'); - this.jobErrorType = JobError.processFile; - this.jobErrorMessage = processError; - return; - } - - this.jobDependencies = getJobs(processedConfig); - for (const jobName of this.jobDependencies.keys()) { - this.jobs.push(jobName); - } - } - setRunningJob(jobName: string): void { this.runningJob = jobName; } From 981ae01c4a385f33607690e8fad91fa6ac389a55 Mon Sep 17 00:00:00 2001 From: Ryan Kienstra Date: Sun, 19 Dec 2021 22:27:05 -0600 Subject: [PATCH 21/24] Alphabetize errors in getErrorTreeItems --- src/classes/JobProvider.ts | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/classes/JobProvider.ts b/src/classes/JobProvider.ts index 829c43aa..267fa2f2 100644 --- a/src/classes/JobProvider.ts +++ b/src/classes/JobProvider.ts @@ -172,12 +172,6 @@ export default class JobProvider getErrorTreeItems(): vscode.TreeItem[] { switch (this.jobErrorType) { - case JobError.processFile: - return [ - new Warning('Error processing the CircleCI config:'), - new vscode.TreeItem(this.getJobErrorMessage()), - new Command('Try Again', `${JOB_TREE_VIEW_ID}.refresh`), - ]; case JobError.dockerNotRunning: return [ new Warning('Error: is Docker running?'), @@ -190,11 +184,6 @@ export default class JobProvider new Command('Get License', GET_LICENSE_COMMAND), new Command('Enter License', ENTER_LICENSE_COMMAND), ]; - case JobError.noConfigFilePathSelected: - return [ - new Warning('Error: No jobs found'), - new Command('Select repo', 'localCiJobs.selectRepo'), - ]; case JobError.noConfigFilePathInWorkspace: return [ new Warning('Error: No jobs found'), @@ -202,6 +191,17 @@ export default class JobProvider 'Please add a .circleci/config.yml to this workspace' ), ]; + case JobError.noConfigFilePathSelected: + return [ + new Warning('Error: No jobs found'), + new Command('Select repo', 'localCiJobs.selectRepo'), + ]; + case JobError.processFile: + return [ + new Warning('Error processing the CircleCI config:'), + new vscode.TreeItem(this.getJobErrorMessage()), + new Command('Try Again', `${JOB_TREE_VIEW_ID}.refresh`), + ]; default: return []; } From 12998cffc8d063f320e2f56175430dc6c77635f0 Mon Sep 17 00:00:00 2001 From: Ryan Kienstra Date: Sun, 19 Dec 2021 22:45:17 -0600 Subject: [PATCH 22/24] Change the order of caching and bash access --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index cc0b1fa9..d853ecb2 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "local-ci", "displayName": "Local CI", - "description": "Debug CircleCI® workflows locally, with Bash access and caching. Free preview, then paid.", + "description": "Debug CircleCI® workflows locally, with caching and Bash access. Free preview, then paid.", "version": "1.3.1", "publisher": "LocalCI", "contributors": [ From 666b80a3c363c08f152d55be03bfe07d308a7a2c Mon Sep 17 00:00:00 2001 From: Ryan Kienstra Date: Sun, 19 Dec 2021 22:48:04 -0600 Subject: [PATCH 23/24] Change the description back, slightly change README.md --- README.md | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2a170618..6c34e9ae 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![Platform: macOS](https://img.shields.io/badge/platform-macOS-yellow)](https://en.wikipedia.org/wiki/MacOS) [![Requires CircleCI®](https://img.shields.io/badge/requires-CirlcleCI%C2%AE-yellow)](https://circleci.com/docs/2.0/first-steps/) -Debug entire CircleCI® workflows locally, with Bash access and persistence between jobs. +Debug entire CircleCI® workflows locally, with Bash access and caching between jobs. All in your local, no pushing commits and waiting. Not affiliated with CircleCI®. diff --git a/package.json b/package.json index d853ecb2..a4d1625e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "local-ci", "displayName": "Local CI", - "description": "Debug CircleCI® workflows locally, with caching and Bash access. Free preview, then paid.", + "description": "Debug CircleCI® workflows locally, with Bash access during and after. Free preview, then paid.", "version": "1.3.1", "publisher": "LocalCI", "contributors": [ From 4d07fe230dc507436877bca8afe3b7ad205f9182 Mon Sep 17 00:00:00 2001 From: Ryan Kienstra Date: Sun, 19 Dec 2021 23:04:25 -0600 Subject: [PATCH 24/24] Bump the version to 1.3.2, add a CHANGELOG entry --- CHANGELOG.md | 5 +++++ package-lock.json | 4 ++-- package.json | 2 +- src/constants/index.ts | 2 +- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c2c01eb..d6b0922a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Change Log +## 1.3.2 - 19 December 2021 + +### Added +- Nest jobs by dependencies, so you can see the order to run jobs. [#58](https://github.com/getlocalci/local-ci/pull/58) + ## 1.3.1 - 16 December 2021 ### Added diff --git a/package-lock.json b/package-lock.json index 406d122f..d6cdf1e5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "local-ci", - "version": "1.3.1", + "version": "1.3.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "local-ci", - "version": "1.3.1", + "version": "1.3.2", "hasInstallScript": true, "license": "GPL-2.0-or-later", "os": [ diff --git a/package.json b/package.json index a4d1625e..91e8c56d 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "local-ci", "displayName": "Local CI", "description": "Debug CircleCI® workflows locally, with Bash access during and after. Free preview, then paid.", - "version": "1.3.1", + "version": "1.3.2", "publisher": "LocalCI", "contributors": [ "Ryan Kienstra" diff --git a/src/constants/index.ts b/src/constants/index.ts index 35d83b2c..37b01249 100644 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -1,4 +1,4 @@ -export const EXTENSION_VERSION = '1.3.1'; +export const EXTENSION_VERSION = '1.3.2'; export const EXTENSION_ID = 'LocalCI.local-ci'; export const COMMITTED_IMAGE_NAMESPACE = 'local-ci'; export const SELECTED_CONFIG_PATH = 'local-ci.config.path';