Skip to content

Commit fa53b85

Browse files
authored
fix: extension build and loading states (#408)
1 parent df3362a commit fa53b85

File tree

15 files changed

+292
-46
lines changed

15 files changed

+292
-46
lines changed

.github/workflows/deploy_release_to_marketplace.yml

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,13 @@ jobs:
1919
name: Publish to VSCode Marketplace
2020
runs-on: ubuntu-latest
2121
steps:
22+
# Workflow steps:
23+
# 1. Setup and install dependencies
24+
# 2. Validate code (lint, tests, type check)
25+
# 3. Build with sourcemaps for validation
26+
# 4. Semantic release creates new version and git tag
27+
# 5. Rebuild with production settings (minified, no sourcemaps)
28+
# 6. Package and publish to marketplace
2229
- name: Checkout code
2330
uses: actions/checkout@v4
2431
with:
@@ -32,19 +39,23 @@ jobs:
3239
run: git submodule update --init
3340
- name: Install dependencies
3441
run: npm ci
42+
- name: Lint Code
43+
run: npm run lint
3544
- name: Run Tests
3645
run: npm run test:all
37-
- name: Compile and Build
38-
run: npm run compile:all && npm run build:all
46+
- name: Type Check
47+
run: npm run check-types
48+
- name: Clean and Build
49+
run: npm run predebug
3950
- name: Set Release Type
4051
run: echo "RELEASE_TYPE=${{ github.event.inputs['release-type'] }}" >> $GITHUB_ENV
4152
- name: Release and Tag a New Version
4253
env:
4354
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
4455
RELEASE_TYPE: ${{ env.RELEASE_TYPE }}
4556
run: npx semantic-release
46-
- name: Re-Compile and Build with New Version
47-
run: npm run compile:all && npm run build:all
57+
- name: Clean and Build Production Bundle
58+
run: npm run build:all:prod:clean
4859
- name: Package & Publish VSCode Extension
4960
run: |
5061
VSCE_OPTIONS="--packagePath autokitteh.vsix"

esbuild.config.js

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
const esbuild = require("esbuild");
2+
const fs = require("fs");
3+
const path = require("path");
4+
5+
const production = process.argv.includes("--production");
6+
const watch = process.argv.includes("--watch");
7+
8+
/**
9+
* @type {import('esbuild').Plugin}
10+
*/
11+
const esbuildProblemMatcherPlugin = {
12+
name: "esbuild-problem-matcher",
13+
14+
setup(build) {
15+
build.onStart(() => {
16+
console.log("[watch] build started");
17+
});
18+
build.onEnd((result) => {
19+
result.errors.forEach(({ text, location }) => {
20+
console.error(`✘ [ERROR] ${text}`);
21+
console.error(` ${location.file}:${location.line}:${location.column}:`);
22+
});
23+
console.log("[watch] build finished");
24+
});
25+
},
26+
};
27+
28+
/**
29+
* Helper function to find the actual file with proper extension
30+
*/
31+
function findFileWithExtension(basePath) {
32+
const extensions = [".ts", ".tsx", ".js", ".jsx", ".json"];
33+
34+
// If basePath is a file with extension, return it
35+
if (fs.existsSync(basePath) && fs.statSync(basePath).isFile()) {
36+
return basePath;
37+
}
38+
39+
for (const ext of extensions) {
40+
const fullPath = basePath + ext;
41+
if (fs.existsSync(fullPath) && fs.statSync(fullPath).isFile()) {
42+
return fullPath;
43+
}
44+
}
45+
46+
if (fs.existsSync(basePath) && fs.statSync(basePath).isDirectory()) {
47+
for (const ext of extensions) {
48+
const indexPath = path.join(basePath, `index${ext}`);
49+
if (fs.existsSync(indexPath) && fs.statSync(indexPath).isFile()) {
50+
return indexPath;
51+
}
52+
}
53+
}
54+
55+
return basePath;
56+
}
57+
58+
/**
59+
* Plugin to resolve TypeScript path aliases
60+
* @type {import('esbuild').Plugin}
61+
*/
62+
const pathAliasPlugin = {
63+
name: "path-alias",
64+
setup(build) {
65+
/* eslint-disable @typescript-eslint/naming-convention */
66+
const aliases = {
67+
"@ak-proto-ts": "src/autokitteh/proto/gen/ts/autokitteh",
68+
"@eventEmitter": "src/eventEmitter",
69+
"@api": "src/api",
70+
"@constants": "src/constants",
71+
"@controllers": "src/controllers",
72+
"@enums": "src/enums",
73+
"@models": "src/models",
74+
"@i18n": "src/i18n",
75+
"@interfaces": "src/interfaces",
76+
"@providers": "src/providers",
77+
"@services": "src/services",
78+
"@type": "src/types",
79+
"@utilities": "src/utilities",
80+
"@views": "src/views",
81+
"@vscommands": "src/vscommands",
82+
"@tests": "tests",
83+
};
84+
/* eslint-enable @typescript-eslint/naming-convention */
85+
86+
for (const [alias, target] of Object.entries(aliases)) {
87+
build.onResolve({ filter: new RegExp(`^${alias.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`) }, () => {
88+
const basePath = path.resolve(__dirname, target);
89+
const resolvedPath = findFileWithExtension(basePath);
90+
return { path: resolvedPath };
91+
});
92+
93+
build.onResolve({ filter: new RegExp(`^${alias.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}/`) }, (args) => {
94+
const subPath = args.path.substring(alias.length + 1);
95+
const basePath = path.resolve(__dirname, target, subPath);
96+
const resolvedPath = findFileWithExtension(basePath);
97+
return { path: resolvedPath };
98+
});
99+
}
100+
},
101+
};
102+
103+
async function main() {
104+
const ctx = await esbuild.context({
105+
entryPoints: ["src/extension.ts"],
106+
bundle: true,
107+
format: "cjs",
108+
minify: production,
109+
sourcemap: !production,
110+
sourcesContent: false,
111+
platform: "node",
112+
outfile: "dist/main.js",
113+
external: ["vscode", "fswin"],
114+
logLevel: "silent",
115+
plugins: [pathAliasPlugin, esbuildProblemMatcherPlugin],
116+
});
117+
118+
if (watch) {
119+
await ctx.watch();
120+
} else {
121+
await ctx.rebuild();
122+
await ctx.dispose();
123+
}
124+
}
125+
126+
main().catch((e) => {
127+
console.error(e);
128+
process.exit(1);
129+
});

package.json

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -189,12 +189,12 @@
189189
{
190190
"command": "autokitteh.enable",
191191
"group": "navigation",
192-
"when": "view == autokittehSidebarTree && config.autokitteh.serviceEnabled!==true"
192+
"when": "view == autokittehSidebarTree && config.autokitteh.serviceEnabled!==true && !autokitteh.isLoading"
193193
},
194194
{
195195
"command": "autokitteh.disable",
196196
"group": "navigation",
197-
"when": "view == autokittehSidebarTree && config.autokitteh.serviceEnabled"
197+
"when": "view == autokittehSidebarTree && (config.autokitteh.serviceEnabled || autokitteh.isLoading)"
198198
}
199199
]
200200
},
@@ -355,7 +355,7 @@
355355
"homepage": "https://autokitteh.com",
356356
"icon": "resources/extensions-marketplace/ak-extension-icon.png",
357357
"license": "SEE LICENSE IN LICENSE.md",
358-
"main": "./dist/src/extension.js",
358+
"main": "./dist/main.js",
359359
"name": "autokitteh",
360360
"optionalDependencies": {
361361
"@rollup/rollup-linux-x64-gnu": "4.17.2"
@@ -367,9 +367,13 @@
367367
"url": "https://github.com/autokitteh/vscode-extension"
368368
},
369369
"scripts": {
370-
"predebug": "rimraf dist && rm -rf dist && npm run build:all && npm run compile",
370+
"rebuild-install": "npm run predebug && vsce package && code --install-extension autokitteh-$npm_package_version.vsix",
371+
"predebug": "rimraf dist && rm -rf dist && npm run build:all",
371372
"build": "npm run esbuild-base -- --sourcemap",
373+
"build:prod": "npm run esbuild-base -- --production",
372374
"build:all": "npm run build --workspace=vscode-react && npm run build",
375+
"build:all:prod": "npm run build --workspace=vscode-react && npm run build:prod",
376+
"build:all:prod:clean": "rimraf dist && rm -rf dist && npm run build:all:prod",
373377
"build:vscode-react": "npm run build --workspace=vscode-react",
374378
"build:webview": "cd vscode-react && npm run build",
375379
"check-format:staged": "prettier . --check",
@@ -378,7 +382,7 @@
378382
"compile": "tsc -p ./",
379383
"compile:all": "npm run compile --workspace=vscode-react && npm run compile",
380384
"compile:vscode-react": "npm run compile --workspace=vscode-react",
381-
"esbuild-base": "esbuild ./src/extension.ts --bundle --outfile=dist/main.js --external:vscode --format=cjs --platform=node --external:fswin",
385+
"esbuild-base": "node esbuild.config.js",
382386
"fix-eslint:staged": "npm run lint --fix",
383387
"lint": "eslint src --ext .ts,.tsx .",
384388
"package": "vsce package",
@@ -395,4 +399,4 @@
395399
"workspaces": [
396400
"vscode-react"
397401
]
398-
}
402+
}

src/controllers/project.controller.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -433,7 +433,7 @@ export class ProjectController {
433433
LoggerService.error(namespaces.projectController, log);
434434
}
435435
this.project = project;
436-
this.view.show(project!.name);
436+
await this.view.show(project!.name);
437437
this.setProjectNameInView();
438438
this.sessions = undefined;
439439
}
@@ -1028,12 +1028,17 @@ export class ProjectController {
10281028
}
10291029

10301030
async loadInitialDataOnceViewReady() {
1031+
LoggerService.info(namespaces.projectController, "loadInitialDataOnceViewReady called");
10311032
if (!this.isNewPageInstance) {
1033+
LoggerService.info(namespaces.projectController, "Not a new page instance, skipping");
10321034
return;
10331035
}
10341036
this.isNewPageInstance = false;
1035-
this.loadAndDisplayDeployments();
1036-
this.notifyViewResourcesPathChanged();
1037+
1038+
this.view.update({ type: MessageType.initialDataLoaded });
1039+
1040+
await this.loadAndDisplayDeployments();
1041+
await this.notifyViewResourcesPathChanged();
10371042
}
10381043

10391044
async refreshUI() {

src/controllers/sidebar.controller.ts

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as fs from "fs";
22
import isEqual from "lodash.isequal";
3-
import { commands, window } from "vscode";
3+
import { commands } from "vscode";
44

55
import { ISidebarView } from "interfaces";
66

@@ -33,7 +33,6 @@ export class SidebarController {
3333
this.organizationName = organizationName;
3434
this.organizationId = organizationId;
3535
this.organizations = organizations;
36-
window.registerTreeDataProvider("autokittehSidebarTree", this.view);
3736
}
3837

3938
public fetchData = async (
@@ -49,6 +48,11 @@ export class SidebarController {
4948
this.updateViewOrganizationsList(this.organizations || []);
5049
return;
5150
}
51+
52+
if (!this.projectsSidebarItems) {
53+
this.view.displayLoading();
54+
}
55+
5256
const organization = {
5357
name: organizationName || this.organizationName,
5458
organizationId: organizationId || this.organizationId,
@@ -72,6 +76,7 @@ export class SidebarController {
7276
};
7377

7478
public enable = async () => {
79+
this.view.displayLoading();
7580
this.retryScheduler?.startFetchInterval();
7681
};
7782

@@ -114,14 +119,13 @@ export class SidebarController {
114119
}
115120

116121
if (projects!.length) {
117-
return projects!
118-
.sort((a, b) => a.name.localeCompare(b.name))
119-
.map((project) => ({
120-
label: project.name,
121-
key: project.projectId,
122-
}));
122+
const sorted = projects!.sort((a, b) => a.name.localeCompare(b.name));
123+
const result = sorted.map((project) => ({
124+
label: project.name,
125+
key: project.projectId,
126+
}));
127+
return result;
123128
}
124-
125129
return [
126130
{
127131
label: `${translate().t("projects.noProjectsFound")} ${organizationNameToDisplay} at ${this.strippedBaseURL}`,
@@ -138,10 +142,17 @@ export class SidebarController {
138142
) {
139143
const refreshOrganizationName = organizationName || this.organizationName;
140144
const refreshOrganizationId = organizationId || this.organizationId;
145+
146+
if (!this.projectsSidebarItems || force) {
147+
this.view.displayLoading();
148+
}
149+
141150
const projects = await this.fetchProjects(resetCountdown, refreshOrganizationId, refreshOrganizationName);
142-
if (!isEqual(projects, this.projectsSidebarItems) || force) {
151+
const shouldUpdate = !isEqual(projects, this.projectsSidebarItems) || force;
152+
if (shouldUpdate) {
143153
this.projectsSidebarItems = projects;
144154
this.view.refresh(projects!, refreshOrganizationName);
155+
} else {
145156
}
146157
}
147158

src/controllers/tabsManager.controller.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export class TabsManagerController {
1919
const newView = new ProjectView(this.context);
2020

2121
const newController = new ProjectController(newView, project.key);
22-
newController.openProject(
22+
await newController.openProject(
2323
() => this.onProjectDispose(project.key as string),
2424
() => this.onProjectDelete(project.key as string)
2525
);

src/enums/messageType.enum.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export enum MessageType {
2626
deleteProject = "DELETE_PROJECT",
2727
setSessionsStateFilter = "SET_SESSIONS_STATE_FILTER",
2828
loadInitialDataOnceViewReady = "LOAD_INITIAL_DATA_ONCE_VIEW_READY",
29+
initialDataLoaded = "INITIAL_DATA_LOADED",
2930
startLoader = "START_LOADER",
3031
stopLoader = "STOP_LOADER",
3132
loadMoreSessions = "LOAD_MORE_SESSIONS",

0 commit comments

Comments
 (0)