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.
- package/dist/lib/messages.d.ts +2 -3
- package/dist/lib/package-manager.d.ts +1 -1
- package/dist/lib/utils.d.ts +1 -1
- package/dist/module.cjs +112 -63
- package/package.json +3 -2
- package/templates/javascript/README.md +87 -0
- package/templates/javascript/extension.config.js +19 -0
- package/templates/javascript/package.json +13 -0
- package/templates/javascript/public/screenshot.png +0 -0
- package/templates/javascript/src/background.js +44 -0
- package/templates/javascript/src/content/ContentApp.js +42 -0
- package/templates/javascript/src/content/scripts.js +38 -0
- package/templates/javascript/src/content/styles.css +45 -0
- package/templates/javascript/src/images/icon.png +0 -0
- package/templates/javascript/src/manifest.json +54 -0
- package/templates/javascript/src/sidebar/SidebarApp.js +30 -0
- package/templates/javascript/src/sidebar/index.html +13 -0
- package/templates/javascript/src/sidebar/scripts.js +3 -0
- package/templates/javascript/src/sidebar/styles.css +60 -0
- package/dist/lib/progress.d.ts +0 -12
package/dist/lib/messages.d.ts
CHANGED
|
@@ -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
|
|
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 {};
|
package/dist/lib/utils.d.ts
CHANGED
|
@@ -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
|
|
138
|
-
return `${
|
|
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
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
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
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
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
|
-
}
|
|
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
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
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.
|
|
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
|
+

|
|
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
|
+
}
|
|
Binary file
|
|
@@ -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
|
+
}
|
|
Binary file
|
|
@@ -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,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
|
+
}
|
package/dist/lib/progress.d.ts
DELETED
|
@@ -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 {};
|