extension-create 3.17.0 → 3.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -11,10 +11,9 @@ export declare function writingTypeDefinitions(projectName: string): string;
11
11
  export declare function writingTypeDefinitionsError(error: any): string;
12
12
  export declare function installingFromTemplate(projectName: string, templateName: string): string;
13
13
  export declare function installingFromTemplateError(projectName: string, template: string, error: any): string;
14
+ export declare function templateFetchTimedOut(templateName: string, ms: number): string;
14
15
  export declare function initializingGitForRepository(projectName: string): string;
15
- export declare function initializingGitForRepositoryFailed(gitCommand: string, gitArgs: string[], code: number | null): string;
16
- export declare function initializingGitForRepositoryProcessError(projectName: string, error: any): string;
17
- export declare function initializingGitForRepositoryError(projectName: string, error: any): string;
16
+ export declare function initializingGitSkipped(projectName: string, reason: string): string;
18
17
  export declare function installingDependencies(): string;
19
18
  export declare function foundSpecializedDependencies(count: number): string;
20
19
  export declare function installingProjectIntegrations(integrations: string[]): string;
@@ -1,4 +1,4 @@
1
- type PackageManagerName = 'pnpm' | 'yarn' | 'npm';
1
+ type PackageManagerName = 'pnpm' | 'yarn' | 'bun' | 'npm';
2
2
  export declare function detectPackageManagerFromEnv(): PackageManagerName;
3
3
  export declare function getPackageManagerSpecFromEnv(): string | null;
4
4
  export {};
@@ -1,6 +1,6 @@
1
1
  export declare function copyDirectoryWithSymlinks(source: string, destination: string): Promise<void>;
2
2
  export declare function moveDirectoryContents(source: string, destination: string): Promise<void>;
3
- export declare function getInstallCommand(): Promise<"pnpm" | "yarn" | "npm">;
3
+ export declare function getInstallCommand(): Promise<"pnpm" | "yarn" | "bun" | "npm">;
4
4
  export declare function isDirectoryWriteable(directory: string, projectName: string, logger: {
5
5
  log(...args: any[]): void;
6
6
  error(...args: any[]): void;
package/dist/module.cjs CHANGED
@@ -42,15 +42,17 @@ const external_path_namespaceObject = require("path");
42
42
  const external_fs_namespaceObject = require("fs");
43
43
  const external_pintor_namespaceObject = require("pintor");
44
44
  var external_pintor_default = /*#__PURE__*/ __webpack_require__.n(external_pintor_namespaceObject);
45
- const userAgentPattern = /(pnpm|yarn|npm)\/([0-9]+\.[0-9]+\.[0-9]+[^ ]*)/i;
45
+ const userAgentPattern = /(pnpm|yarn|bun|npm)\/([0-9]+\.[0-9]+\.[0-9]+[^ ]*)/i;
46
46
  function detectPackageManagerFromEnv() {
47
- const userAgent = process.env.npm_config_user_agent || '';
47
+ const userAgent = (process.env.npm_config_user_agent || '').toLowerCase();
48
48
  if (userAgent.includes('pnpm')) return 'pnpm';
49
49
  if (userAgent.includes('yarn')) return 'yarn';
50
+ if (userAgent.includes('bun')) return 'bun';
50
51
  if (userAgent.includes('npm')) return 'npm';
51
- const execPath = process.env.npm_execpath || process.env.NPM_EXEC_PATH || '';
52
+ const execPath = (process.env.npm_execpath || process.env.NPM_EXEC_PATH || process.env.BUN_INSTALL || '').toLowerCase();
52
53
  if (execPath.includes('pnpm')) return 'pnpm';
53
54
  if (execPath.includes('yarn')) return 'yarn';
55
+ if (execPath.includes('bun')) return 'bun';
54
56
  execPath.includes('npm');
55
57
  return 'npm';
56
58
  }
@@ -131,17 +133,14 @@ function installingFromTemplate(projectName, templateName) {
131
133
  function installingFromTemplateError(projectName, template, error) {
132
134
  return `${external_pintor_default().red('Error')} Couldn't find template ${external_pintor_default().yellow(template)} for ${external_pintor_default().blue(projectName)}.\n${external_pintor_default().red(String(error))}\n${external_pintor_default().red('Next step: choose a valid template name or URL.')}`;
133
135
  }
136
+ function templateFetchTimedOut(templateName, ms) {
137
+ return `${external_pintor_default().red('Error')} Timed out after ${external_pintor_default().yellow(`${Math.round(ms / 1000)}s`)} fetching template ${external_pintor_default().yellow(templateName)}.\n${external_pintor_default().red('Next step: check your network, or set EXTENSION_CREATE_TIMEOUT_MS to allow more time.')}`;
138
+ }
134
139
  function initializingGitForRepository(projectName) {
135
140
  return `${statusPrefix} Initializing git repository for ${external_pintor_default().blue(projectName)}...`;
136
141
  }
137
- function initializingGitForRepositoryFailed(gitCommand, gitArgs, code) {
138
- return `${external_pintor_default().red('Error')} Command ${external_pintor_default().yellow(gitCommand)} ${external_pintor_default().yellow(gitArgs.join(' '))} failed.\n${external_pintor_default().red(`Exit code: ${external_pintor_default().yellow(String(code))}`)}\n${external_pintor_default().red('Next step: run the command manually to inspect the error.')}`;
139
- }
140
- function initializingGitForRepositoryProcessError(projectName, error) {
141
- return `${external_pintor_default().red('Error')} Child process failed while initializing ${external_pintor_default().yellow('git')} for ${external_pintor_default().blue(projectName)}.\n${external_pintor_default().red(String(error?.message || error))}\n${external_pintor_default().red('Next step: retry initialization or create the repository manually.')}`;
142
- }
143
- function initializingGitForRepositoryError(projectName, error) {
144
- return `${external_pintor_default().red('Error')} Couldn't initialize ${external_pintor_default().yellow('git')} for ${external_pintor_default().blue(projectName)}.\n${external_pintor_default().red(String(error?.message || error))}\n${external_pintor_default().red('Next step: retry initialization or create the repository manually.')}`;
142
+ function initializingGitSkipped(projectName, reason) {
143
+ return `${statusPrefix} Skipped git init for ${external_pintor_default().blue(projectName)} (${external_pintor_default().yellow(reason)}). Run ${external_pintor_default().yellow('git init')} yourself if you want version control.`;
145
144
  }
146
145
  function installingDependencies() {
147
146
  return `${statusPrefix} Installing project-specific dependencies... ${external_pintor_default().gray('(This may take a moment)')}`;
@@ -199,6 +198,31 @@ function cantSetupBuiltInTests(projectName, error) {
199
198
  return `${external_pintor_default().red('Error')} Couldn't set up built-in tests for ${external_pintor_default().yellow(projectName)}.\n${external_pintor_default().red(String(error))}\n${external_pintor_default().red('Next step: run the setup step again or skip tests.')}`;
200
199
  }
201
200
  const promises_namespaceObject = require("fs/promises");
201
+ async function copyDirectoryWithSymlinks(source, destination) {
202
+ const entries = await promises_namespaceObject.readdir(source, {
203
+ withFileTypes: true
204
+ });
205
+ await promises_namespaceObject.mkdir(destination, {
206
+ recursive: true
207
+ });
208
+ for (const entry of entries){
209
+ const sourcePath = external_path_namespaceObject.join(source, entry.name);
210
+ const destPath = external_path_namespaceObject.join(destination, entry.name);
211
+ if (entry.isDirectory()) await copyDirectoryWithSymlinks(sourcePath, destPath);
212
+ else if (entry.isSymbolicLink()) try {
213
+ const target = await promises_namespaceObject.readlink(sourcePath);
214
+ await promises_namespaceObject.symlink(target, destPath);
215
+ } catch (err) {
216
+ if (err?.code === 'EPERM' || err?.code === 'ENOTSUP') {
217
+ const real = await promises_namespaceObject.realpath(sourcePath);
218
+ await promises_namespaceObject.cp(real, destPath, {
219
+ recursive: true
220
+ });
221
+ } else throw err;
222
+ }
223
+ else await promises_namespaceObject.copyFile(sourcePath, destPath);
224
+ }
225
+ }
202
226
  async function moveDirectoryContents(source, destination) {
203
227
  await promises_namespaceObject.mkdir(destination, {
204
228
  recursive: true
@@ -289,7 +313,29 @@ const external_adm_zip_namespaceObject = require("adm-zip");
289
313
  var external_adm_zip_default = /*#__PURE__*/ __webpack_require__.n(external_adm_zip_namespaceObject);
290
314
  const external_go_git_it_namespaceObject = require("go-git-it");
291
315
  var external_go_git_it_default = /*#__PURE__*/ __webpack_require__.n(external_go_git_it_namespaceObject);
316
+ const NETWORK_TIMEOUT_MS = (()=>{
317
+ const raw = parseInt(String(process.env.EXTENSION_CREATE_TIMEOUT_MS || ''), 10);
318
+ return Number.isFinite(raw) && raw > 0 ? raw : 60000;
319
+ })();
320
+ function isAuthorOrDevMode() {
321
+ return 'development' === process.env.EXTENSION_ENV || 'true' === process.env.EXTENSION_AUTHOR_MODE;
322
+ }
323
+ async function withTimeout(task, ms, onTimeout) {
324
+ let timer;
325
+ const timeout = new Promise((_, reject)=>{
326
+ timer = setTimeout(()=>reject(onTimeout()), ms);
327
+ });
328
+ try {
329
+ return await Promise.race([
330
+ task,
331
+ timeout
332
+ ]);
333
+ } finally{
334
+ if (timer) clearTimeout(timer);
335
+ }
336
+ }
292
337
  async function withSuppressedOutput(task) {
338
+ if (isAuthorOrDevMode()) return task();
293
339
  const originalStdoutWrite = process.stdout.write.bind(process.stdout);
294
340
  const originalStderrWrite = process.stderr.write.bind(process.stderr);
295
341
  process.stdout.write = ()=>true;
@@ -301,6 +347,9 @@ async function withSuppressedOutput(task) {
301
347
  process.stderr.write = originalStderrWrite;
302
348
  }
303
349
  }
350
+ function bundledTemplateDir(templateName) {
351
+ return external_path_namespaceObject.join(__dirname, '..', 'templates', templateName);
352
+ }
304
353
  function getArchiveBaseName(url) {
305
354
  const withoutQuery = url.split('?')[0];
306
355
  const fileName = external_path_namespaceObject.basename(withoutQuery);
@@ -329,19 +378,23 @@ async function importExternalTemplate(projectPath, projectName, template, logger
329
378
  const resolvedTemplate = 'init' === templateName ? "javascript" : template;
330
379
  const resolvedTemplateName = 'init' === templateName ? "javascript" : templateName;
331
380
  const templateUrl = `${examplesUrl}/${resolvedTemplate}`;
381
+ const isHttp = /^https?:\/\//i.test(template);
382
+ const isGithub = /^https?:\/\/github\.com\//i.test(template);
332
383
  try {
333
384
  await promises_namespaceObject.mkdir(projectPath, {
334
385
  recursive: true
335
386
  });
387
+ if (!isHttp && !isGithub && "javascript" === resolvedTemplate) {
388
+ const localTemplate = bundledTemplateDir("javascript");
389
+ if ((0, external_fs_namespaceObject.existsSync)(localTemplate)) return void await copyDirectoryWithSymlinks(localTemplate, projectPath);
390
+ }
336
391
  const tempRoot = await promises_namespaceObject.mkdtemp(external_path_namespaceObject.join(external_os_namespaceObject.tmpdir(), 'extension-js-create-'));
337
392
  const tempPath = external_path_namespaceObject.join(tempRoot, projectName + '-temp');
338
393
  await promises_namespaceObject.mkdir(tempPath, {
339
394
  recursive: true
340
395
  });
341
- const isHttp = /^https?:\/\//i.test(template);
342
- const isGithub = /^https?:\/\/github.com\//i.test(template);
343
396
  const runGoGitIt = async (templatePath, destination)=>{
344
- await withSuppressedOutput(async ()=>external_go_git_it_default()(templatePath, destination, installingFromTemplate(projectName, templateName)));
397
+ await withTimeout(withSuppressedOutput(async ()=>external_go_git_it_default()(templatePath, destination, installingFromTemplate(projectName, templateName))), NETWORK_TIMEOUT_MS, ()=>new Error(templateFetchTimedOut(templateName, NETWORK_TIMEOUT_MS)));
345
398
  };
346
399
  if (isGithub) {
347
400
  await runGoGitIt(template, tempPath);
@@ -354,7 +407,8 @@ async function importExternalTemplate(projectPath, projectName, template, logger
354
407
  } else if (isHttp) {
355
408
  const { data, headers } = await external_axios_default().get(template, {
356
409
  responseType: 'arraybuffer',
357
- maxRedirects: 5
410
+ maxRedirects: 5,
411
+ timeout: NETWORK_TIMEOUT_MS
358
412
  });
359
413
  const contentType = String(headers?.['content-type'] || '');
360
414
  const looksZip = /zip|octet-stream/i.test(contentType) || template.toLowerCase().endsWith('.zip');
@@ -463,7 +517,7 @@ async function overridePackageJson(projectPath, projectName, { template = "javas
463
517
  };
464
518
  try {
465
519
  logger.log(writingPackageJsonMetadata());
466
- await promises_namespaceObject.writeFile(external_path_namespaceObject.join(projectPath, 'package.json'), JSON.stringify(packageMetadata, null, 2));
520
+ await promises_namespaceObject.writeFile(external_path_namespaceObject.join(projectPath, 'package.json'), JSON.stringify(packageMetadata, null, 2) + '\n');
467
521
  } catch (error) {
468
522
  logger.error(writingPackageJsonMetadataError(projectName, error));
469
523
  throw error;
@@ -510,7 +564,10 @@ async function runInstall(command, args, opts) {
510
564
  });
511
565
  });
512
566
  }
513
- function getInstallArgs() {
567
+ function getInstallArgs(packageManager) {
568
+ if ('bun' === packageManager) return [
569
+ 'install'
570
+ ];
514
571
  return [
515
572
  'install',
516
573
  '--silent'
@@ -567,7 +624,7 @@ async function installDependencies(projectPath, projectName, logger) {
567
624
  const shouldInstall = await hasDependenciesToInstall(projectPath);
568
625
  if (!shouldInstall) return;
569
626
  const command = await getInstallCommand();
570
- const dependenciesArgs = getInstallArgs();
627
+ const dependenciesArgs = getInstallArgs(command);
571
628
  const installMessage = installingDependencies();
572
629
  logger.log(installMessage);
573
630
  try {
@@ -781,26 +838,22 @@ async function initializeGitRepository(projectPath, projectName, logger) {
781
838
  '--quiet'
782
839
  ];
783
840
  logger.log(initializingGitForRepository(projectName));
784
- try {
785
- const stdio = 'development' === process.env.EXTENSION_ENV ? 'inherit' : 'ignore';
786
- const child = (0, external_cross_spawn_namespaceObject.spawn)(gitCommand, gitArgs, {
787
- stdio,
788
- cwd: projectPath
841
+ const stdio = 'development' === process.env.EXTENSION_ENV ? 'inherit' : 'ignore';
842
+ const child = (0, external_cross_spawn_namespaceObject.spawn)(gitCommand, gitArgs, {
843
+ stdio,
844
+ cwd: projectPath
845
+ });
846
+ await new Promise((resolve)=>{
847
+ child.on('close', (code)=>{
848
+ if (0 !== code) logger.log(initializingGitSkipped(projectName, `git exited with ${code}`));
849
+ resolve();
789
850
  });
790
- await new Promise((resolve, reject)=>{
791
- child.on('close', (code)=>{
792
- if (0 !== code) reject(new Error(initializingGitForRepositoryFailed(gitCommand, gitArgs, code)));
793
- else resolve();
794
- });
795
- child.on('error', (error)=>{
796
- logger.error(initializingGitForRepositoryProcessError(projectName, error));
797
- reject(error);
798
- });
851
+ child.on('error', (error)=>{
852
+ const reason = error?.code === 'ENOENT' ? 'git not found' : String(error?.message || error);
853
+ logger.log(initializingGitSkipped(projectName, reason));
854
+ resolve();
799
855
  });
800
- } catch (error) {
801
- logger.error(initializingGitForRepositoryError(projectName, error));
802
- throw error;
803
- }
856
+ });
804
857
  }
805
858
  async function setupBuiltInTests(projectPath, projectName, logger) {
806
859
  try {
@@ -1024,34 +1077,30 @@ async function extensionCreate(projectNameInput, { cliVersion, template = "javas
1024
1077
  if (projectNameInput.startsWith('http')) throw new Error(noUrlAllowed());
1025
1078
  const projectPath = external_path_namespaceObject.isAbsolute(projectNameInput) ? projectNameInput : external_path_namespaceObject.join(process.cwd(), projectNameInput);
1026
1079
  const projectName = external_path_namespaceObject.basename(projectPath);
1027
- try {
1028
- await createDirectory(projectPath, projectName, logger);
1029
- await importExternalTemplate(projectPath, projectName, template, logger);
1030
- await overridePackageJson(projectPath, projectName, {
1031
- template,
1032
- cliVersion
1033
- }, logger);
1034
- if (install) {
1035
- await installDependencies(projectPath, projectName, logger);
1036
- await installInternalDependencies(projectPath, logger);
1037
- }
1038
- await writeReadmeFile(projectPath, projectName, logger);
1039
- await writeManifestJson(projectPath, projectName, logger);
1040
- await initializeGitRepository(projectPath, projectName, logger);
1041
- await writeGitignore(projectPath, logger);
1042
- await setupBuiltInTests(projectPath, projectName, logger);
1043
- if (isTypeScriptTemplate(template)) await generateExtensionTypes(projectPath, projectName, logger);
1044
- const successfulInstall = await successfullInstall(projectPath, projectName, Boolean(install));
1045
- logger.log(successfulInstall);
1046
- return {
1047
- projectPath,
1048
- projectName,
1049
- template,
1050
- depsInstalled: install
1051
- };
1052
- } catch (error) {
1053
- throw error;
1080
+ await createDirectory(projectPath, projectName, logger);
1081
+ await importExternalTemplate(projectPath, projectName, template, logger);
1082
+ await overridePackageJson(projectPath, projectName, {
1083
+ template,
1084
+ cliVersion
1085
+ }, logger);
1086
+ if (install) {
1087
+ await installDependencies(projectPath, projectName, logger);
1088
+ await installInternalDependencies(projectPath, logger);
1054
1089
  }
1090
+ await writeReadmeFile(projectPath, projectName, logger);
1091
+ await writeManifestJson(projectPath, projectName, logger);
1092
+ await initializeGitRepository(projectPath, projectName, logger);
1093
+ await writeGitignore(projectPath, logger);
1094
+ await setupBuiltInTests(projectPath, projectName, logger);
1095
+ if (isTypeScriptTemplate(template)) await generateExtensionTypes(projectPath, projectName, logger);
1096
+ const successfulInstall = await successfullInstall(projectPath, projectName, Boolean(install));
1097
+ logger.log(successfulInstall);
1098
+ return {
1099
+ projectPath,
1100
+ projectName,
1101
+ template,
1102
+ depsInstalled: install
1103
+ };
1055
1104
  }
1056
1105
  exports.extensionCreate = __webpack_exports__.extensionCreate;
1057
1106
  for(var __rspack_i in __webpack_exports__)if (-1 === [
package/package.json CHANGED
@@ -21,10 +21,11 @@
21
21
  "main": "./dist/module.cjs",
22
22
  "types": "./dist/module.d.ts",
23
23
  "files": [
24
- "dist"
24
+ "dist",
25
+ "templates"
25
26
  ],
26
27
  "name": "extension-create",
27
- "version": "3.17.0",
28
+ "version": "3.18.0",
28
29
  "description": "The standalone extension creation engine for Extension.js",
29
30
  "author": {
30
31
  "name": "Cezar Augusto",
@@ -0,0 +1,87 @@
1
+ [powered-image]: https://img.shields.io/badge/Powered%20by-Extension.js-0971fe
2
+ [powered-url]: https://extension.js.org
3
+
4
+ ![Powered by Extension.js][powered-image]
5
+
6
+ # JavaScript Starter Extension
7
+
8
+ > JavaScript-based extension with a sidebar panel. Adds a sidebar with a simple page.
9
+
10
+ ![screenshot](./public/screenshot.png)
11
+
12
+ **What you'll see**: A small UI injected into any web page, isolated in a Shadow DOM so site styles don't bleed through.
13
+
14
+ **How it works**: A content script mounts a JavaScript UI inside a Shadow DOM and applies scoped styles so the host page can't bleed through.
15
+
16
+ Plain JavaScript starter. Useful as a baseline when you want to add framework or tooling support yourself, layer by layer.
17
+
18
+ ## Try it locally
19
+
20
+ ```bash
21
+ npx extension@latest create my-javascript --template javascript
22
+ cd my-javascript
23
+ npm install
24
+ npm run dev
25
+ ```
26
+
27
+ A fresh browser window opens with the extension already loaded.
28
+
29
+ ## Project layout
30
+
31
+ ```
32
+ src/
33
+ ├── content/
34
+ │ ├── ContentApp.js
35
+ │ ├── scripts.js
36
+ │ └── styles.css
37
+ ├── images/
38
+ │ └── icon.png
39
+ ├── sidebar/
40
+ │ ├── index.html
41
+ │ ├── scripts.js
42
+ │ ├── SidebarApp.js
43
+ │ └── styles.css
44
+ ├── background.js
45
+ └── manifest.json
46
+ ```
47
+
48
+ ## Commands
49
+
50
+ ### dev
51
+
52
+ Run the extension in development mode. Target a browser with `--browser`:
53
+
54
+ ```bash
55
+ npm run dev # Chromium (default)
56
+ npm run dev -- --browser=chrome
57
+ npm run dev -- --browser=edge
58
+ npm run dev -- --browser=firefox
59
+ ```
60
+
61
+ ### build
62
+
63
+ Build for production. Convenience scripts cover each browser:
64
+
65
+ ```bash
66
+ npm run build # Chrome (default)
67
+ npm run build:firefox
68
+ npm run build:edge
69
+ ```
70
+
71
+ ### preview
72
+
73
+ Preview the production build with the bundled browser:
74
+
75
+ ```bash
76
+ npm run preview
77
+ ```
78
+
79
+ ## Tests
80
+
81
+ This template ships an end-to-end check (`template.spec.ts`) validated by the examples-repo CI on every commit.
82
+
83
+ ## Learn more
84
+
85
+ - [Extension.js docs](https://extension.js.org)
86
+ - [Templates index](https://extension.js.org/docs/getting-started/templates)
87
+ - [GitHub: extension-js/extension.js](https://github.com/extension-js/extension.js)
@@ -0,0 +1,19 @@
1
+ /** @type {import('extension').FileConfig} */
2
+ // Extension.js uses a fresh profile on every run.
3
+ // Prefer that default? Remove the profile config below.
4
+ const profile = (name) => `./dist/extension-profile-${name}`
5
+ const ciFlags = process.env.CI ? ['--no-sandbox', '--disable-gpu'] : []
6
+
7
+ export default {
8
+ browser: {
9
+ chrome: {profile: profile('chrome'), browserFlags: ciFlags},
10
+ chromium: {profile: profile('chromium'), browserFlags: ciFlags},
11
+ edge: {profile: profile('edge'), browserFlags: ciFlags},
12
+ firefox: {profile: profile('firefox')},
13
+ 'chromium-based': {
14
+ profile: profile('chromium-based'),
15
+ browserFlags: ciFlags
16
+ },
17
+ 'gecko-based': {profile: profile('gecko-based')}
18
+ }
19
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "private": true,
3
+ "name": "javascript",
4
+ "description": "JavaScript-based extension with a sidebar panel. Adds a sidebar with a simple page.",
5
+ "version": "1.0.0",
6
+ "license": "MIT",
7
+ "type": "module",
8
+ "author": {
9
+ "name": "Cezar Augusto",
10
+ "email": "boss@cezaraugusto.net",
11
+ "url": "https://cezaraugusto.com"
12
+ }
13
+ }
@@ -0,0 +1,44 @@
1
+ console.log(
2
+ '[From the background context] Hello from the background worker/script!'
3
+ )
4
+
5
+ const isFirefoxLike =
6
+ import.meta.env.EXTENSION_PUBLIC_BROWSER === 'firefox' ||
7
+ import.meta.env.EXTENSION_PUBLIC_BROWSER === 'gecko-based'
8
+
9
+ if (isFirefoxLike) {
10
+ browser.browserAction.onClicked.addListener(() => {
11
+ browser.sidebarAction.open()
12
+ })
13
+
14
+ browser.runtime.onMessage.addListener((message) => {
15
+ if (!message || message.type !== 'openSidebar') return
16
+
17
+ browser.sidebarAction.open()
18
+ })
19
+ }
20
+
21
+ if (!isFirefoxLike) {
22
+ chrome.action.onClicked.addListener(() => {
23
+ chrome.sidePanel.setPanelBehavior({openPanelOnActionClick: true})
24
+ })
25
+ }
26
+
27
+ chrome.runtime.onMessage.addListener((message) => {
28
+ if (!message || message.type !== 'openSidebar') return
29
+
30
+ chrome.sidePanel.setPanelBehavior({openPanelOnActionClick: true})
31
+
32
+ if (!chrome.sidePanel.open) return
33
+
34
+ chrome.tabs.query({active: true, currentWindow: true}, (tabs) => {
35
+ const activeTabId = tabs && tabs[0] && tabs[0].id
36
+ if (!activeTabId) return
37
+
38
+ try {
39
+ chrome.sidePanel.open({tabId: activeTabId})
40
+ } catch (error) {
41
+ console.error(error)
42
+ }
43
+ })
44
+ })
@@ -0,0 +1,42 @@
1
+ import iconUrl from '../images/icon.png'
2
+ const logo = iconUrl
3
+
4
+ export default function createContentApp() {
5
+ const container = document.createElement('div')
6
+ container.className = 'content_script'
7
+
8
+ const pill = document.createElement('button')
9
+ pill.type = 'button'
10
+ pill.className = 'content_pill'
11
+ pill.setAttribute('aria-label', 'Open sidebar')
12
+ pill.addEventListener('click', () => {
13
+ try {
14
+ if (
15
+ import.meta.env.EXTENSION_PUBLIC_BROWSER === 'firefox' ||
16
+ import.meta.env.EXTENSION_PUBLIC_BROWSER === 'gecko-based'
17
+ ) {
18
+ browser.runtime.sendMessage({type: 'openSidebar'})
19
+ } else {
20
+ chrome.runtime.sendMessage({type: 'openSidebar'})
21
+ }
22
+ } catch (error) {
23
+ console.error(error)
24
+ }
25
+ })
26
+
27
+ const img = document.createElement('img')
28
+ img.className = 'content_pill_logo'
29
+ img.src = logo
30
+ img.alt = ''
31
+ img.setAttribute('aria-hidden', 'true')
32
+
33
+ const text = document.createElement('span')
34
+ text.className = 'content_pill_text'
35
+ text.textContent = 'Open sidebar'
36
+
37
+ pill.appendChild(img)
38
+ pill.appendChild(text)
39
+ container.appendChild(pill)
40
+
41
+ return container
42
+ }
@@ -0,0 +1,38 @@
1
+ console.log('[From the page context] Hello from content_scripts!')
2
+ import createContentApp from './ContentApp.js'
3
+ import './styles.css'
4
+
5
+ /**
6
+ * Extension.js content_script entrypoint. The framework calls this on
7
+ * injection and calls the returned function on HMR/teardown to clean up.
8
+ * Do not invoke it yourself.
9
+ */
10
+ export default function initial() {
11
+ const rootDiv = document.createElement('div')
12
+ rootDiv.setAttribute('data-extension-root', 'true')
13
+ document.body.appendChild(rootDiv)
14
+
15
+ // Injecting content_scripts inside a shadow dom
16
+ // prevents conflicts with the host page's styles.
17
+ // This way, styles from the extension won't leak into the host page.
18
+ const shadowRoot = rootDiv.attachShadow({mode: 'open'})
19
+
20
+ const styleElement = document.createElement('style')
21
+ shadowRoot.appendChild(styleElement)
22
+ fetchCSS().then((response) => (styleElement.textContent = response))
23
+
24
+ // Render ContentApp inside shadow root
25
+ const container = createContentApp()
26
+ shadowRoot.appendChild(container)
27
+
28
+ return () => {
29
+ rootDiv.remove()
30
+ }
31
+ }
32
+
33
+ async function fetchCSS() {
34
+ const cssUrl = new URL('./styles.css', import.meta.url)
35
+ const response = await fetch(cssUrl)
36
+ const text = await response.text()
37
+ return response.ok ? text : Promise.reject(text)
38
+ }
@@ -0,0 +1,45 @@
1
+ .content_script {
2
+ position: absolute;
3
+ right: 0.75rem;
4
+ bottom: 0.75rem;
5
+ z-index: 9999;
6
+ }
7
+
8
+ .content_pill {
9
+ -webkit-font-smoothing: antialiased;
10
+ -moz-osx-font-smoothing: grayscale;
11
+ appearance: none;
12
+ border: none;
13
+ outline: none;
14
+ cursor: pointer;
15
+ display: inline-flex;
16
+ align-items: center;
17
+ gap: 0.5rem;
18
+ background: var(--sidebar-bg, #0a0c10);
19
+ color: var(--sidebar-text, #c9c9c9);
20
+ padding: 0.5rem 1rem 0.5rem 0.5rem;
21
+ border-radius: 9999px;
22
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.25);
23
+ }
24
+
25
+ .content_pill:hover {
26
+ background: #11151c;
27
+ }
28
+
29
+ .content_pill_logo {
30
+ height: 1.5rem;
31
+ border-radius: 9999px;
32
+ background: rgba(255, 255, 255, 0.08);
33
+ object-fit: contain;
34
+ aspect-ratio: 1 / 1;
35
+ padding: 0.125rem;
36
+ }
37
+
38
+ .content_pill_text {
39
+ font-size: 0.85rem;
40
+ font-weight: 600;
41
+ line-height: 1;
42
+ font-family:
43
+ -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue",
44
+ Arial, "Noto Sans", sans-serif;
45
+ }
@@ -0,0 +1,54 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/chrome-manifest.json",
3
+ "chromium:manifest_version": 3,
4
+ "firefox:manifest_version": 2,
5
+ "version": "1.0.0",
6
+ "name": "JavaScript Sidebar Example",
7
+ "description": "JavaScript-based extension with a sidebar panel. Adds a sidebar with a simple page.",
8
+ "icons": {
9
+ "16": "images/icon.png",
10
+ "32": "images/icon.png",
11
+ "48": "images/icon.png",
12
+ "64": "images/icon.png",
13
+ "128": "images/icon.png"
14
+ },
15
+ "chromium:action": {
16
+ "default_icon": {
17
+ "16": "images/icon.png",
18
+ "32": "images/icon.png",
19
+ "48": "images/icon.png",
20
+ "64": "images/icon.png",
21
+ "128": "images/icon.png"
22
+ },
23
+ "default_title": "Open Side Panel"
24
+ },
25
+ "firefox:browser_action": {
26
+ "default_icon": {
27
+ "16": "images/icon.png",
28
+ "32": "images/icon.png",
29
+ "48": "images/icon.png",
30
+ "64": "images/icon.png",
31
+ "128": "images/icon.png"
32
+ },
33
+ "default_title": "Open Side Panel"
34
+ },
35
+ "chromium:side_panel": {
36
+ "default_path": "sidebar/index.html",
37
+ "default_title": "Side Panel Content"
38
+ },
39
+ "firefox:sidebar_action": {
40
+ "default_panel": "sidebar/index.html",
41
+ "default_title": "Side Panel Content"
42
+ },
43
+ "chromium:permissions": ["sidePanel"],
44
+ "background": {
45
+ "chromium:service_worker": "background.js",
46
+ "firefox:scripts": ["background.js"]
47
+ },
48
+ "content_scripts": [
49
+ {
50
+ "matches": ["<all_urls>"],
51
+ "js": ["content/scripts.js"]
52
+ }
53
+ ]
54
+ }
@@ -0,0 +1,30 @@
1
+ import './styles.css'
2
+ import iconUrl from '../images/icon.png'
3
+
4
+ const javascriptLogo = iconUrl
5
+
6
+ function SidebarApp() {
7
+ const root = document.getElementById('root')
8
+ if (!root) return
9
+
10
+ root.innerHTML = `
11
+ <div class="sidebar_app">
12
+ <img
13
+ class="sidebar_logo"
14
+ src="${javascriptLogo}"
15
+ alt="The JavaScript logo"
16
+ />
17
+ <h1 class="sidebar_title">Sidebar Panel</h1>
18
+ <p class="sidebar_description">
19
+ Learn more in the
20
+ <a
21
+ href="https://extension.js.org"
22
+ target="_blank" rel="noopener noreferrer"
23
+ >Extension.js docs</a>
24
+ .
25
+ </p>
26
+ </div>
27
+ `
28
+ }
29
+
30
+ SidebarApp()
@@ -0,0 +1,13 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>JavaScript Sidebar</title>
7
+ </head>
8
+ <body>
9
+ <noscript>You need to enable JavaScript to run this extension.</noscript>
10
+ <div id="root"></div>
11
+ </body>
12
+ <script src="./scripts.js"></script>
13
+ </html>
@@ -0,0 +1,3 @@
1
+ console.log('[From the sidebar page context] Hello regular page!')
2
+ import './SidebarApp.js'
3
+ import './styles.css'
@@ -0,0 +1,60 @@
1
+ /* @import 'sakura.css'; */
2
+
3
+ :root {
4
+ --sidebar-margin: 1rem;
5
+ --sidebar-bg: #0a0c10;
6
+ --sidebar-text: #c9c9c9;
7
+ --sidebar-link: #e5e7eb;
8
+ --sidebar-border: #c9c9c9;
9
+ }
10
+
11
+ body {
12
+ background-color: var(--sidebar-bg);
13
+ color: var(--sidebar-text);
14
+ height: 100vh;
15
+ margin: var(--sidebar-margin);
16
+ border-radius: 6px;
17
+ display: flex;
18
+ justify-content: center;
19
+ align-items: center;
20
+ overflow: hidden;
21
+ }
22
+
23
+ .sidebar_app {
24
+ display: flex;
25
+ flex-direction: column;
26
+ align-items: center;
27
+ padding: 0 1rem;
28
+ text-align: center;
29
+ max-height: 100vh;
30
+ overflow-y: auto;
31
+ }
32
+
33
+ .sidebar_logo {
34
+ width: 72px;
35
+ margin: 0 auto 1rem;
36
+ }
37
+
38
+ .sidebar_title {
39
+ font-size: 1.85em;
40
+ line-height: 1.1;
41
+ font-family:
42
+ -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue",
43
+ Arial, "Noto Sans", sans-serif;
44
+ font-weight: 700;
45
+ margin: 0;
46
+ text-align: center;
47
+ }
48
+
49
+ .sidebar_description {
50
+ font-size: small;
51
+ margin: 0.5rem 0 0;
52
+ text-align: center;
53
+ }
54
+
55
+ .sidebar_description a {
56
+ text-decoration: none;
57
+ border-bottom: 2px solid var(--sidebar-border);
58
+ color: var(--sidebar-link);
59
+ margin: 0;
60
+ }
@@ -1,12 +0,0 @@
1
- type ProgressOptions = {
2
- enabled?: boolean;
3
- intervalMs?: number;
4
- width?: number;
5
- persistLabel?: boolean;
6
- };
7
- type ProgressHandle = {
8
- stop: () => void;
9
- };
10
- export declare function shouldShowProgress(): boolean;
11
- export declare function startProgressBar(label: string, options?: ProgressOptions): ProgressHandle;
12
- export {};