diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b7983706..c4a68785 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -75,6 +75,13 @@ jobs: working-directory: packages/b2c-cli run: pnpm run pretest && pnpm run test:ci && pnpm run lint + - name: Run VS Extension lint + id: vs-extension-test + if: always() && steps.vs-extension-test.conclusion != 'cancelled' + working-directory: packages/b2c-vs-extension + # Testing not currently supported on CI + run: pnpm run lint + - name: Test Report uses: dorny/test-reporter@fe45e9537387dac839af0d33ba56eed8e24189e8 # v2.3.0 if: always() && steps.sdk-test.conclusion != 'cancelled' diff --git a/packages/b2c-vs-extension/.vscode-test.mjs b/packages/b2c-vs-extension/.vscode-test.mjs index b62ba25f..1e01259a 100644 --- a/packages/b2c-vs-extension/.vscode-test.mjs +++ b/packages/b2c-vs-extension/.vscode-test.mjs @@ -1,5 +1,5 @@ -import { defineConfig } from '@vscode/test-cli'; +import {defineConfig} from '@vscode/test-cli'; export default defineConfig({ - files: 'out/test/**/*.test.js', + files: 'out/test/**/*.test.js', }); diff --git a/packages/b2c-vs-extension/eslint.config.mjs b/packages/b2c-vs-extension/eslint.config.mjs index 78095e98..94458852 100644 --- a/packages/b2c-vs-extension/eslint.config.mjs +++ b/packages/b2c-vs-extension/eslint.config.mjs @@ -7,7 +7,7 @@ import {includeIgnoreFile} from '@eslint/compat'; import headerPlugin from 'eslint-plugin-header'; import path from 'node:path'; import {fileURLToPath} from 'node:url'; -import typescriptEslint from 'typescript-eslint'; +import tseslint from 'typescript-eslint'; import {copyrightHeader, sharedRules, prettierPlugin} from '../../eslint.config.mjs'; @@ -16,17 +16,19 @@ headerPlugin.rules.header.meta.schema = false; export default [ includeIgnoreFile(gitignorePath), - ...typescriptEslint.config({ - files: ['**/*.ts'], - languageOptions: { - parserOptions: {ecmaVersion: 2022, sourceType: 'module'}, - }, - }), + { + ignores: ['src/template/**'], + }, + ...tseslint.configs.recommended, prettierPlugin, { + files: ['**/*.ts'], plugins: { header: headerPlugin, }, + languageOptions: { + parserOptions: {ecmaVersion: 2022, sourceType: 'module'}, + }, rules: { 'header/header': ['error', 'block', copyrightHeader], ...sharedRules, diff --git a/packages/b2c-vs-extension/package.json b/packages/b2c-vs-extension/package.json index dabf26e8..b82cfd36 100644 --- a/packages/b2c-vs-extension/package.json +++ b/packages/b2c-vs-extension/package.json @@ -73,6 +73,7 @@ "format": "prettier --write src", "format:check": "prettier --check src", "test": "vscode-test", + "posttest": "pnpm run lint", "analyze": "ANALYZE_BUNDLE=1 node scripts/esbuild-bundle.mjs" }, "devDependencies": { diff --git a/packages/b2c-vs-extension/scripts/esbuild-bundle.mjs b/packages/b2c-vs-extension/scripts/esbuild-bundle.mjs index 5fa55917..16d08c46 100644 --- a/packages/b2c-vs-extension/scripts/esbuild-bundle.mjs +++ b/packages/b2c-vs-extension/scripts/esbuild-bundle.mjs @@ -7,16 +7,16 @@ * Bundles the extension with esbuild. Injects a shim for import.meta.url so * SDK code that uses createRequire(import.meta.url) works in CJS output. */ -import esbuild from "esbuild"; -import fs from "node:fs"; -import path from "path"; -import { fileURLToPath } from "url"; +import esbuild from 'esbuild'; +import fs from 'node:fs'; +import path from 'path'; +import {fileURLToPath} from 'url'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); // scripts/ -> package root -const pkgRoot = path.resolve(__dirname, ".."); +const pkgRoot = path.resolve(__dirname, '..'); // In CJS there is no import.meta; SDK's version.js uses createRequire(import.meta.url). Shim it. // Use globalThis so the value is visible inside all module wrappers in the bundle. @@ -25,19 +25,19 @@ const IMPORT_META_URL_SHIM = function loaderFor(filePath) { const ext = path.extname(filePath); - if (ext === ".ts" || filePath.endsWith(".tsx")) return "ts"; - return "js"; + if (ext === '.ts' || filePath.endsWith('.tsx')) return 'ts'; + return 'js'; } const importMetaUrlPlugin = { - name: "import-meta-url-shim", + name: 'import-meta-url-shim', setup(build) { - build.onLoad({ filter: /\.(ts|tsx|js|mjs|cjs)$/ }, (args) => { - const contents = fs.readFileSync(args.path, "utf-8"); - const replaced = contents.includes("import.meta.url") - ? contents.replace(/import\.meta\.url/g, "globalThis.__import_meta_url") + build.onLoad({filter: /\.(ts|tsx|js|mjs|cjs)$/}, (args) => { + const contents = fs.readFileSync(args.path, 'utf-8'); + const replaced = contents.includes('import.meta.url') + ? contents.replace(/import\.meta\.url/g, 'globalThis.__import_meta_url') : contents; - return { contents: replaced, loader: loaderFor(args.path) }; + return {contents: replaced, loader: loaderFor(args.path)}; }); }, }; @@ -47,69 +47,67 @@ const importMetaUrlPlugin = { // we replace that require in the bundle output with the actual JSON (post-build). // Also replace require.resolve('@salesforce/b2c-tooling-sdk/package.json') so it doesn't throw when // the extension runs from a VSIX (no node_modules). We use __dirname so path.dirname(...) is the extension dist. -const sdkPkgJsonPath = path.join(pkgRoot, "..", "b2c-tooling-sdk", "package.json"); -const REQUIRE_RESOLVE_PACKAGE_JSON_RE = /require\d*\.resolve\s*\(\s*["']@salesforce\/b2c-tooling-sdk\/package\.json["']\s*\)/g; +const sdkPkgJsonPath = path.join(pkgRoot, '..', 'b2c-tooling-sdk', 'package.json'); +const REQUIRE_RESOLVE_PACKAGE_JSON_RE = + /require\d*\.resolve\s*\(\s*["']@salesforce\/b2c-tooling-sdk\/package\.json["']\s*\)/g; const REQUIRE_RESOLVE_REPLACEMENT = "require('path').join(__dirname, 'package.json')"; function inlineSdkPackageJson() { - const outPath = path.join(pkgRoot, "dist", "extension.js"); - let str = fs.readFileSync(outPath, "utf8"); - const sdkPkg = JSON.stringify(JSON.parse(fs.readFileSync(sdkPkgJsonPath, "utf8"))); - str = str.replace( - /require\d*\s*\(\s*["']@salesforce\/b2c-tooling-sdk\/package\.json["']\s*\)/g, - sdkPkg - ); + const outPath = path.join(pkgRoot, 'dist', 'extension.js'); + let str = fs.readFileSync(outPath, 'utf8'); + const sdkPkg = JSON.stringify(JSON.parse(fs.readFileSync(sdkPkgJsonPath, 'utf8'))); + str = str.replace(/require\d*\s*\(\s*["']@salesforce\/b2c-tooling-sdk\/package\.json["']\s*\)/g, sdkPkg); str = str.replace(REQUIRE_RESOLVE_PACKAGE_JSON_RE, REQUIRE_RESOLVE_REPLACEMENT); - fs.writeFileSync(outPath, str, "utf8"); + fs.writeFileSync(outPath, str, 'utf8'); } -const watchMode = process.argv.includes("--watch"); +const watchMode = process.argv.includes('--watch'); const buildOptions = { - entryPoints: [path.join(pkgRoot, "src", "extension.ts")], + entryPoints: [path.join(pkgRoot, 'src', 'extension.ts')], bundle: true, - platform: "node", - format: "cjs", - target: "node18", - outfile: path.join(pkgRoot, "dist", "extension.js"), + platform: 'node', + format: 'cjs', + target: 'node18', + outfile: path.join(pkgRoot, 'dist', 'extension.js'), sourcemap: true, metafile: true, - external: ["vscode"], + external: ['vscode'], // In watch mode, include "development" so esbuild resolves the SDK's exports to .ts source files // directly (no SDK rebuild needed). Production builds use the built dist/ artifacts. - conditions: watchMode - ? ["development", "require", "node", "default"] - : ["require", "node", "default"], - mainFields: ["main", "module"], - banner: { js: IMPORT_META_URL_SHIM }, + conditions: watchMode ? ['development', 'require', 'node', 'default'] : ['require', 'node', 'default'], + mainFields: ['main', 'module'], + banner: {js: IMPORT_META_URL_SHIM}, plugins: [importMetaUrlPlugin], - logLevel: "info", + logLevel: 'info', }; if (watchMode) { const ctx = await esbuild.context(buildOptions); await ctx.watch(); - console.log("[esbuild] watching for changes..."); + console.log('[esbuild] watching for changes...'); } else { const result = await esbuild.build(buildOptions); inlineSdkPackageJson(); if (result.metafile && process.env.ANALYZE_BUNDLE) { - const metaPath = path.join(pkgRoot, "dist", "meta.json"); - fs.writeFileSync(metaPath, JSON.stringify(result.metafile, null, 2), "utf-8"); + const metaPath = path.join(pkgRoot, 'dist', 'meta.json'); + fs.writeFileSync(metaPath, JSON.stringify(result.metafile, null, 2), 'utf-8'); const inputs = Object.entries(result.metafile.inputs).map(([file, info]) => ({ file: path.relative(pkgRoot, file), bytes: info.bytes, })); inputs.sort((a, b) => b.bytes - a.bytes); const total = inputs.reduce((s, i) => s + i.bytes, 0); - console.log("\n--- Bundle analysis (top 40 by input size) ---"); + console.log('\n--- Bundle analysis (top 40 by input size) ---'); console.log(`Total inputs: ${(total / 1024 / 1024).toFixed(2)} MB\n`); - inputs.slice(0, 40).forEach(({ file, bytes }, i) => { + inputs.slice(0, 40).forEach(({file, bytes}, i) => { const pct = ((bytes / total) * 100).toFixed(1); - console.log(`${String(i + 1).padStart(2)} ${(bytes / 1024).toFixed(1).padStart(8)} KB ${pct.padStart(5)}% ${file}`); + console.log( + `${String(i + 1).padStart(2)} ${(bytes / 1024).toFixed(1).padStart(8)} KB ${pct.padStart(5)}% ${file}`, + ); }); - console.log("\nWrote", metaPath); + console.log('\nWrote', metaPath); } } diff --git a/packages/b2c-vs-extension/src/extension.ts b/packages/b2c-vs-extension/src/extension.ts index 05d210d3..81ddfb57 100644 --- a/packages/b2c-vs-extension/src/extension.ts +++ b/packages/b2c-vs-extension/src/extension.ts @@ -527,7 +527,7 @@ function activateInner(context: vscode.ExtensionContext, log: vscode.OutputChann return folder; } - function resolveCliScript(context: vscode.ExtensionContext): {node: string; script: string} | null { + function _resolveCliScript(context: vscode.ExtensionContext): {node: string; script: string} | null { const workspaceRoot = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath; if (workspaceRoot) { const distCli = path.join(workspaceRoot, 'dist', 'cli.js'); @@ -1040,7 +1040,7 @@ function activateInner(context: vscode.ExtensionContext, log: vscode.OutputChann 'sfcc.shopper-products', 'sfcc.shopper-stores', ]; - const {data, error, response} = await slasClient.PUT('/tenants/{tenantId}/clients/{clientId}', { + const {error, response} = await slasClient.PUT('/tenants/{tenantId}/clients/{clientId}', { params: {path: {tenantId, clientId}}, body: { clientId,