extension-create 4.0.26 → 4.0.28

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.
@@ -13,6 +13,7 @@ export declare function installingFromTemplateError(template: string, error: unk
13
13
  export declare function templateFetchTimedOut(templateName: string, ms: number): string;
14
14
  export declare function templateNotFoundInCatalog(templateName: string, error?: unknown): string;
15
15
  export declare function templateDownloadFailed(templateName: string, error: unknown): string;
16
+ export declare function removedStaleTemplateLockfiles(fileNames: string[]): string;
16
17
  export declare function initializingGitForRepository(projectName: string): string;
17
18
  export declare function initializingGitSkipped(projectName: string, reason: string): string;
18
19
  export declare function firstCommitSkipped(projectName: string, reason: string): string;
@@ -22,6 +23,7 @@ export declare function installingProjectIntegrations(integrations: string[]): s
22
23
  export declare function installingDependenciesFailed(pmCommand: string, pmArgs: string[], code: number | null): string;
23
24
  export declare function installingDependenciesProcessError(projectName: string, error: unknown): string;
24
25
  export declare function cantInstallDependencies(projectName: string, error: unknown): string;
26
+ export declare function malformedPackageJson(packageJsonPath: string, error: unknown): string;
25
27
  export declare function writingPackageJsonMetadata(): string;
26
28
  export declare function writingPackageJsonMetadataError(error: unknown): string;
27
29
  export declare function writingDenoJsonc(): string;
package/dist/module.cjs CHANGED
@@ -195,7 +195,7 @@ function writingTypeDefinitionsError(error) {
195
195
  return `${prefix('error')} Couldn't write the extension type definitions.\n${fmt.label('REASON')} ${fmt.val(fmt.truncate(String(error)))}\n${external_pintor_default().red('Check the file permissions, then try again.')}`;
196
196
  }
197
197
  function installingFromTemplate(projectName, templateName) {
198
- if ('init' === templateName || "javascript" === templateName) return `${prefix('info')} Copying the template files…`;
198
+ if ("javascript" === templateName) return `${prefix('info')} Copying the template files…`;
199
199
  return `${prefix('info')} Copying the template ${external_pintor_default().blue(templateName)}…`;
200
200
  }
201
201
  function usingTemplate(templateName, source) {
@@ -214,6 +214,9 @@ function templateNotFoundInCatalog(templateName, error) {
214
214
  function templateDownloadFailed(templateName, error) {
215
215
  return `${prefix('error')} Couldn't download the template ${external_pintor_default().blue(templateName)} from ${fmt.val('github.com/extension-js/examples')}.\n${fmt.label('REASON')} ${fmt.val(fmt.truncate(String(error?.message || error)))}\n${external_pintor_default().red('- Check your network connection and any GitHub rate limit, then try again.')}\n${external_pintor_default().red('- Set')} ${external_pintor_default().blue('EXTENSION_CREATE_TIMEOUT_MS')} ${external_pintor_default().red('to allow more time.')}`;
216
216
  }
217
+ function removedStaleTemplateLockfiles(fileNames) {
218
+ return `${prefix('info')} Removed the template ${fileNames.map((name)=>fmt.val(name)).join(', ')}. The first install writes a fresh one.`;
219
+ }
217
220
  function initializingGitForRepository(projectName) {
218
221
  return `${prefix('debug')} create git init name=${projectName}`;
219
222
  }
@@ -248,6 +251,9 @@ function installingDependenciesProcessError(projectName, error) {
248
251
  function cantInstallDependencies(projectName, error) {
249
252
  return `${prefix('error')} Couldn't install the dependencies for ${external_pintor_default().blue(projectName)}.\n${fmt.label('REASON')} ${fmt.val(fmt.truncate(String(error?.message || error)))}\n${external_pintor_default().red('Check your package manager settings, then try again.')}`;
250
253
  }
254
+ function malformedPackageJson(packageJsonPath, error) {
255
+ return `${prefix('warn')} Couldn't parse ${external_pintor_default().blue('package.json')}, so no integrations were detected.\n${fmt.label('PATH')} ${fmt.val(packageJsonPath)}\n${fmt.label('REASON')} ${fmt.val(fmt.truncate(String(error?.message || error)))}`;
256
+ }
251
257
  function writingPackageJsonMetadata() {
252
258
  return `${prefix('debug')} create write file=package.json`;
253
259
  }
@@ -377,12 +383,14 @@ async function isDirectoryWriteable(directory, logger) {
377
383
  function isTypeScriptTemplate(templateName) {
378
384
  return templateName.includes("typescript") || templateName.includes('react') || templateName.includes('preact') || templateName.includes('svelte') || templateName.includes('solid');
379
385
  }
386
+ const external_node_fs_namespaceObject = require("node:fs");
380
387
  const allowlist = [
381
388
  'LICENSE',
382
389
  'node_modules'
383
390
  ];
384
391
  async function createDirectory(projectPath, projectName, logger) {
385
392
  logger.log(startingNewExtension(projectName));
393
+ const directoryPreExisted = (0, external_node_fs_namespaceObject.existsSync)(projectPath);
386
394
  try {
387
395
  const isCurrentDirWriteable = await isDirectoryWriteable(projectPath, logger);
388
396
  if (!isCurrentDirWriteable) {
@@ -401,6 +409,9 @@ async function createDirectory(projectPath, projectName, logger) {
401
409
  } catch (error) {
402
410
  throw new Error(createDirectoryError(projectName, error));
403
411
  }
412
+ return {
413
+ directoryCreated: !directoryPreExisted
414
+ };
404
415
  }
405
416
  async function generateExtensionTypes(projectPath, projectName, logger) {
406
417
  const extensionEnvFile = external_node_path_namespaceObject.join(projectPath, 'extension-env.d.ts');
@@ -427,7 +438,6 @@ async function generateExtensionTypes(projectPath, projectName, logger) {
427
438
  throw error;
428
439
  }
429
440
  }
430
- const external_node_fs_namespaceObject = require("node:fs");
431
441
  const external_node_os_namespaceObject = require("node:os");
432
442
  const external_adm_zip_namespaceObject = require("adm-zip");
433
443
  var external_adm_zip_default = /*#__PURE__*/ __webpack_require__.n(external_adm_zip_namespaceObject);
@@ -450,6 +460,7 @@ const NETWORK_TIMEOUT_MS = (()=>{
450
460
  return Number.isFinite(raw) && raw > 0 ? raw : 60000;
451
461
  })();
452
462
  const CODELOAD_BASE = 'https://codeload.github.com/extension-js/examples/zip';
463
+ const DEFAULT_TEMPLATES_REF = '52c0d871c433d9c54878767175ce8ffc9a951756';
453
464
  function resolveCatalogUrls(ref, overrideUrl) {
454
465
  if (overrideUrl) return [
455
466
  overrideUrl
@@ -526,7 +537,7 @@ async function extractExamplesTemplateFromZip(zipBuffer, templateName, projectPa
526
537
  return written;
527
538
  }
528
539
  async function importFromExamplesCatalog(templateName, projectPath) {
529
- const ref = process.env.EXTENSION_CREATE_TEMPLATE_REF || 'main';
540
+ const ref = process.env.EXTENSION_CREATE_TEMPLATE_REF || DEFAULT_TEMPLATES_REF;
530
541
  const overrideUrl = process.env.EXTENSION_CREATE_TEMPLATE_URL || void 0;
531
542
  const urls = resolveCatalogUrls(ref, overrideUrl);
532
543
  let buffer;
@@ -589,6 +600,28 @@ async function removeTemplateScaffoldingFiles(projectPath) {
589
600
  force: true
590
601
  })));
591
602
  }
603
+ const TEMPLATE_LOCKFILE_NAMES = [
604
+ 'package-lock.json',
605
+ 'npm-shrinkwrap.json',
606
+ 'yarn.lock',
607
+ 'pnpm-lock.yaml',
608
+ 'bun.lockb',
609
+ 'bun.lock',
610
+ 'deno.lock'
611
+ ];
612
+ async function removeStaleTemplateLockfiles(projectPath) {
613
+ const removed = [];
614
+ for (const name of TEMPLATE_LOCKFILE_NAMES){
615
+ const target = external_node_path_namespaceObject.join(projectPath, name);
616
+ if ((0, external_node_fs_namespaceObject.existsSync)(target)) {
617
+ await promises_namespaceObject.rm(target, {
618
+ force: true
619
+ });
620
+ removed.push(name);
621
+ }
622
+ }
623
+ return removed;
624
+ }
592
625
  function getArchiveBaseName(url) {
593
626
  const withoutQuery = url.split('?')[0];
594
627
  const fileName = external_node_path_namespaceObject.basename(withoutQuery);
@@ -611,12 +644,35 @@ async function getZipSourcePath(tempPath, templateUrl) {
611
644
  if (onlyDir.name === archiveBase) return external_node_path_namespaceObject.join(tempPath, onlyDir.name);
612
645
  return tempPath;
613
646
  }
614
- async function importExternalTemplate(projectPath, projectName, template, logger) {
647
+ async function cleanupFailedImport(projectPath, ownsProjectDir, preExistingEntries) {
648
+ if (ownsProjectDir) return void await promises_namespaceObject.rm(projectPath, {
649
+ recursive: true,
650
+ force: true
651
+ }).catch(()=>{});
652
+ const keep = new Set(preExistingEntries);
653
+ let entries = [];
654
+ try {
655
+ entries = await promises_namespaceObject.readdir(projectPath);
656
+ } catch {
657
+ return;
658
+ }
659
+ await Promise.all(entries.filter((entry)=>!keep.has(entry)).map((entry)=>promises_namespaceObject.rm(external_node_path_namespaceObject.join(projectPath, entry), {
660
+ recursive: true,
661
+ force: true
662
+ }).catch(()=>{})));
663
+ }
664
+ async function importExternalTemplate(projectPath, projectName, template, logger, options) {
615
665
  const templateName = external_node_path_namespaceObject.basename(template);
616
666
  const resolvedTemplate = template;
617
667
  const resolvedTemplateName = templateName;
618
668
  const isHttp = /^https?:\/\//i.test(template);
619
669
  const isGithub = /^https?:\/\/github\.com\//i.test(template);
670
+ const dirExistedBeforeImport = (0, external_node_fs_namespaceObject.existsSync)(projectPath);
671
+ const ownsProjectDir = options?.ownsProjectDir ?? !dirExistedBeforeImport;
672
+ let preExistingEntries = [];
673
+ if (dirExistedBeforeImport) try {
674
+ preExistingEntries = await promises_namespaceObject.readdir(projectPath);
675
+ } catch {}
620
676
  try {
621
677
  await promises_namespaceObject.mkdir(projectPath, {
622
678
  recursive: true
@@ -626,6 +682,8 @@ async function importExternalTemplate(projectPath, projectName, template, logger
626
682
  if ((0, external_node_fs_namespaceObject.existsSync)(localTemplate)) {
627
683
  await copyDirectoryWithSymlinks(localTemplate, projectPath);
628
684
  await removeTemplateScaffoldingFiles(projectPath);
685
+ const dropped = await removeStaleTemplateLockfiles(projectPath);
686
+ if (dropped.length) logger.log(removedStaleTemplateLockfiles(dropped));
629
687
  return {
630
688
  template: resolvedTemplateName,
631
689
  source: 'bundled'
@@ -696,6 +754,8 @@ async function importExternalTemplate(projectPath, projectName, template, logger
696
754
  };
697
755
  }
698
756
  await removeTemplateScaffoldingFiles(projectPath);
757
+ const droppedLockfiles = await removeStaleTemplateLockfiles(projectPath);
758
+ if (droppedLockfiles.length) logger.log(removedStaleTemplateLockfiles(droppedLockfiles));
699
759
  await promises_namespaceObject.rm(tempRoot, {
700
760
  recursive: true,
701
761
  force: true
@@ -705,10 +765,7 @@ async function importExternalTemplate(projectPath, projectName, template, logger
705
765
  if (error instanceof TemplateNotFoundError) logger.error(templateNotFoundInCatalog(templateName, error.cause));
706
766
  else if (error instanceof TemplateDownloadError) logger.error(templateDownloadFailed(templateName, error));
707
767
  else logger.error(installingFromTemplateError(templateName, error));
708
- await promises_namespaceObject.rm(projectPath, {
709
- recursive: true,
710
- force: true
711
- }).catch(()=>{});
768
+ await cleanupFailedImport(projectPath, ownsProjectDir, preExistingEntries);
712
769
  throw error;
713
770
  }
714
771
  }
@@ -1025,25 +1082,25 @@ function parseNpmSpecifier(specifier) {
1025
1082
  }
1026
1083
  function readDenoConfigDependencies(projectPath) {
1027
1084
  const dependencies = {};
1028
- for (const filename of [
1029
- 'deno.jsonc',
1030
- 'deno.json'
1031
- ]){
1032
- const configPath = external_node_path_namespaceObject.join(projectPath, filename);
1033
- let config;
1034
- try {
1035
- config = parseJsoncSafe(external_node_fs_namespaceObject.readFileSync(configPath, 'utf8'));
1036
- } catch {
1037
- continue;
1038
- }
1039
- const imports = config?.imports;
1040
- if (imports && 'object' == typeof imports) for (const [rawAlias, specifier] of Object.entries(imports)){
1041
- const parsed = parseNpmSpecifier(specifier);
1042
- if (!parsed) continue;
1043
- dependencies[parsed.name] = dependencies[parsed.name] || parsed.version;
1044
- const alias = rawAlias.endsWith('/') ? rawAlias.slice(0, -1) : rawAlias;
1045
- if (alias && alias !== parsed.name) dependencies[alias] = dependencies[alias] || parsed.version;
1046
- }
1085
+ const filename = [
1086
+ 'deno.json',
1087
+ 'deno.jsonc'
1088
+ ].find((candidate)=>external_node_fs_namespaceObject.existsSync(external_node_path_namespaceObject.join(projectPath, candidate)));
1089
+ if (!filename) return dependencies;
1090
+ let config;
1091
+ try {
1092
+ config = parseJsoncSafe(external_node_fs_namespaceObject.readFileSync(external_node_path_namespaceObject.join(projectPath, filename), 'utf8'));
1093
+ } catch {
1094
+ return dependencies;
1095
+ }
1096
+ const imports = config?.imports;
1097
+ if (!imports || 'object' != typeof imports) return dependencies;
1098
+ for (const [rawAlias, specifier] of Object.entries(imports)){
1099
+ const parsed = parseNpmSpecifier(specifier);
1100
+ if (!parsed) continue;
1101
+ dependencies[parsed.name] = dependencies[parsed.name] || parsed.version;
1102
+ const alias = rawAlias.endsWith('/') ? rawAlias.slice(0, -1) : rawAlias;
1103
+ if (alias && alias !== parsed.name) dependencies[alias] = dependencies[alias] || parsed.version;
1047
1104
  }
1048
1105
  return dependencies;
1049
1106
  }
@@ -1068,12 +1125,15 @@ function resolveDevelopRoot(projectPath) {
1068
1125
  return null;
1069
1126
  }
1070
1127
  }
1071
- function readPackageJson(projectPath) {
1128
+ function readPackageJson(projectPath, logger) {
1072
1129
  let pkg = {};
1130
+ const packageJsonPath = external_node_path_namespaceObject.join(projectPath, 'package.json');
1073
1131
  try {
1074
- const raw = external_node_fs_namespaceObject.readFileSync(external_node_path_namespaceObject.join(projectPath, 'package.json'), 'utf8');
1132
+ const raw = external_node_fs_namespaceObject.readFileSync(packageJsonPath, 'utf8');
1075
1133
  pkg = JSON.parse(raw);
1076
- } catch {}
1134
+ } catch (error) {
1135
+ if (error?.code !== 'ENOENT') logger.log(malformedPackageJson(packageJsonPath, error));
1136
+ }
1077
1137
  const denoDependencies = readDenoConfigDependencies(projectPath);
1078
1138
  if (Object.keys(denoDependencies).length > 0) pkg = {
1079
1139
  ...pkg,
@@ -1100,8 +1160,8 @@ function canResolve(dependency, paths) {
1100
1160
  function findConfigFile(projectPath, candidates) {
1101
1161
  return candidates.some((file)=>external_node_fs_namespaceObject.existsSync(external_node_path_namespaceObject.join(projectPath, file)));
1102
1162
  }
1103
- function detectOptionalDependencies(projectPath) {
1104
- const pkg = readPackageJson(projectPath);
1163
+ function detectOptionalDependencies(projectPath, logger) {
1164
+ const pkg = readPackageJson(projectPath, logger);
1105
1165
  const usesReact = hasDependency(pkg, 'react') || hasDependency(pkg, 'react-dom');
1106
1166
  const usesPreact = hasDependency(pkg, 'preact');
1107
1167
  const usesVue = hasDependency(pkg, 'vue');
@@ -1194,7 +1254,8 @@ function buildOptionalInstallArgs(pm, dependencies, installDir) {
1194
1254
  ...dependencies,
1195
1255
  '--dir',
1196
1256
  installDir,
1197
- '--save-optional'
1257
+ '--save-optional',
1258
+ '--silent'
1198
1259
  ];
1199
1260
  if ('bun' === pm) return [
1200
1261
  'add',
@@ -1210,8 +1271,8 @@ function buildOptionalInstallArgs(pm, dependencies, installDir) {
1210
1271
  '--legacy-peer-deps'
1211
1272
  ];
1212
1273
  }
1213
- function resolveMissingOptionalDeps(developRoot, projectPath) {
1214
- const plan = detectOptionalDependencies(projectPath);
1274
+ function resolveMissingOptionalDeps(developRoot, projectPath, logger) {
1275
+ const plan = detectOptionalDependencies(projectPath, logger);
1215
1276
  const dependenciesByIntegration = {};
1216
1277
  const integrations = [];
1217
1278
  const missing = new Set();
@@ -1260,7 +1321,7 @@ async function installInternalDependencies(projectPath, logger) {
1260
1321
  if ('test' === process.env.EXTENSION_ENV || 'true' === process.env.EXTENSION_SKIP_INTERNAL_INSTALL) return;
1261
1322
  const developRoot = resolveDevelopRoot(projectPath);
1262
1323
  if (!developRoot) return;
1263
- const optionalPlan = resolveMissingOptionalDeps(developRoot, projectPath);
1324
+ const optionalPlan = resolveMissingOptionalDeps(developRoot, projectPath, logger);
1264
1325
  if (optionalPlan.dependencies.length > 0) await installOptionalDependencies(developRoot, projectPath, optionalPlan, logger);
1265
1326
  }
1266
1327
  async function setupBuiltInTests(projectPath, logger) {
@@ -1392,13 +1453,6 @@ async function overridePackageJson(projectPath, { template = "javascript", cliVe
1392
1453
  onlyBuiltDependencies: onlyBuilt
1393
1454
  } : {}
1394
1455
  };
1395
- const identity = readGitIdentity(projectPath);
1396
- const author = identity.name ? {
1397
- name: identity.name,
1398
- ...identity.email ? {
1399
- email: identity.email
1400
- } : {}
1401
- } : void 0;
1402
1456
  const templateFields = {
1403
1457
  ...packageJson
1404
1458
  };
@@ -1421,9 +1475,6 @@ async function overridePackageJson(projectPath, { template = "javascript", cliVe
1421
1475
  } : {},
1422
1476
  ...trustedDeps.length ? {
1423
1477
  trustedDependencies: trustedDeps
1424
- } : {},
1425
- ...author ? {
1426
- author
1427
1478
  } : {}
1428
1479
  };
1429
1480
  try {
@@ -1476,16 +1527,31 @@ async function collectTemplateImports(projectPath, cliVersion) {
1476
1527
  }
1477
1528
  async function writeDenoJsonc(projectPath, { template = "javascript", cliVersion, primary = false }, logger) {
1478
1529
  if (!isDenoRuntime()) return;
1479
- for (const existing of [
1480
- 'deno.jsonc',
1481
- 'deno.json'
1482
- ])if (await pathExists(external_node_path_namespaceObject.join(projectPath, existing))) return;
1530
+ let existingConfig;
1531
+ for (const candidate of [
1532
+ 'deno.json',
1533
+ 'deno.jsonc'
1534
+ ])if (await pathExists(external_node_path_namespaceObject.join(projectPath, candidate))) {
1535
+ existingConfig = candidate;
1536
+ break;
1537
+ }
1538
+ if (existingConfig && !primary) return;
1483
1539
  const extensionBinary = await resolveExtensionBinary();
1484
1540
  const tasks = getTemplateAwareScripts(template, extensionBinary);
1485
1541
  const imports = primary ? await collectTemplateImports(projectPath, cliVersion) : void 0;
1486
1542
  try {
1487
1543
  if (isDebug()) logger.log(writingDenoJsonc());
1488
- await promises_namespaceObject.writeFile(external_node_path_namespaceObject.join(projectPath, 'deno.jsonc'), renderDenoJsonc(tasks, imports));
1544
+ if (existingConfig) {
1545
+ const configPath = external_node_path_namespaceObject.join(projectPath, existingConfig);
1546
+ const config = parseJsoncSafe(await promises_namespaceObject.readFile(configPath, 'utf8'));
1547
+ config.imports = {
1548
+ ...imports || {},
1549
+ ...config.imports || {}
1550
+ };
1551
+ if (void 0 === config.nodeModulesDir) config.nodeModulesDir = 'auto';
1552
+ if (!config.tasks) config.tasks = tasks;
1553
+ await promises_namespaceObject.writeFile(configPath, `${JSON.stringify(config, null, 2)}\n`);
1554
+ } else await promises_namespaceObject.writeFile(external_node_path_namespaceObject.join(projectPath, 'deno.jsonc'), renderDenoJsonc(tasks, imports));
1489
1555
  if (primary) await promises_namespaceObject.rm(external_node_path_namespaceObject.join(projectPath, 'package.json'), {
1490
1556
  force: true
1491
1557
  });
@@ -1522,6 +1588,9 @@ const localSessionState = [
1522
1588
  const envFiles = [
1523
1589
  '',
1524
1590
  '# local env files',
1591
+ '.env',
1592
+ '.env*',
1593
+ '!.env.example',
1525
1594
  '.env.local',
1526
1595
  '.env.development.local',
1527
1596
  '.env.test.local',
@@ -1646,11 +1715,12 @@ async function writeReadmeFile(projectPath, projectName, logger) {
1646
1715
  const manifestJsonPath = await findManifestJsonPath(projectPath);
1647
1716
  const manifestJson = JSON.parse(await promises_namespaceObject.readFile(manifestJsonPath, 'utf-8'));
1648
1717
  const description = String(manifestJson.description || '').trim();
1649
- const screenshotPath = external_node_path_namespaceObject.join(projectPath, 'public', 'screenshot.png');
1650
- const hasScreenshot = await write_readme_file_pathExists(screenshotPath);
1651
- const screenshotEmbed = hasScreenshot ? `\n![screenshot](./public/screenshot.png)\n` : '';
1718
+ const hasPublicScreenshot = await write_readme_file_pathExists(external_node_path_namespaceObject.join(projectPath, 'public', 'screenshot.png'));
1719
+ const hasRootScreenshot = await write_readme_file_pathExists(external_node_path_namespaceObject.join(projectPath, 'screenshot.png'));
1720
+ const screenshotHref = hasPublicScreenshot ? './public/screenshot.png' : hasRootScreenshot ? './screenshot.png' : null;
1721
+ const screenshotEmbed = screenshotHref ? `\n![screenshot](${screenshotHref})\n` : '';
1652
1722
  const blockquote = description ? `> ${description}\n\n` : '';
1653
- const readme = `<a href="https://extension.js.org" target="_blank" rel="noopener noreferrer"><img src="https://img.shields.io/badge/Powered%20by%20%7C%20Extension.js-0971fe" alt="Powered by Extension.js" align="right" /></a>\n\n# ${projectName}\n\n` + blockquote + `${screenshotEmbed}` + `## Commands\n` + `\n` + `### dev\n` + `\n` + "Run the extension in development mode. Target a browser with `--browser`:\n" + `\n` + "```bash\n" + `${runPrefix} dev\n` + `${runPrefix} dev${argSeparator} --browser=firefox\n` + `${runPrefix} dev${argSeparator} --browser=edge\n` + "```\n" + `\n` + `### build\n` + `\n` + `Build for production. Convenience scripts target each browser:\n` + `\n` + "```bash\n" + `${runPrefix} build # Chrome (default)\n` + `${runPrefix} build:firefox\n` + `${runPrefix} build:edge\n` + "```\n" + `\n` + `### preview\n` + `\n` + `Preview the production build in the browser:\n` + `\n` + "```bash\n" + `${runPrefix} preview\n` + "```\n" + `\n` + `## Learn more\n` + `\n` + `[Extension.js docs](https://extension.js.org).\n` + `\n` + `## Ship it\n` + `\n` + "Building and running your extension is local and free. When you are ready to share a build or submit it to the stores, [extension.dev](https://docs.extension.dev/publish/overview?utm_source=create-readme) " + `does that side and sponsors Extension.js.\n`;
1723
+ const readme = `<a href="https://extension.js.org" target="_blank" rel="noopener noreferrer"><img src="https://img.shields.io/badge/Powered%20by%20%7C%20Extension.js-0971fe" alt="Powered by Extension.js" align="right" /></a>\n\n# ${projectName}\n\n` + blockquote + `${screenshotEmbed}` + `## Commands\n` + `\n` + `### dev\n` + `\n` + "Run the extension in development mode. Target a browser with `--browser`:\n" + `\n` + "```bash\n" + `${runPrefix} dev\n` + `${runPrefix} dev${argSeparator} --browser=firefox\n` + `${runPrefix} dev${argSeparator} --browser=edge\n` + "```\n" + `\n` + `### build\n` + `\n` + `Build for production. Convenience scripts target each browser:\n` + `\n` + "```bash\n" + `${runPrefix} build # Chromium (default)\n` + `${runPrefix} build:firefox\n` + `${runPrefix} build:edge\n` + "```\n" + `\n` + `### preview\n` + `\n` + `Preview the production build in the browser:\n` + `\n` + "```bash\n" + `${runPrefix} preview\n` + "```\n" + `\n` + `## Learn more\n` + `\n` + `[Extension.js docs](https://extension.js.org).\n` + `\n` + `## Ship it\n` + `\n` + "Building and running your extension is local and free. When you are ready to share a build or submit it to the stores, [extension.dev](https://docs.extension.dev/publish/overview?utm_source=create-readme) " + `does that side and sponsors Extension.js.\n`;
1654
1724
  try {
1655
1725
  if (isDebug()) logger.log(writingReadmeMetaData());
1656
1726
  await promises_namespaceObject.mkdir(projectPath, {
@@ -1666,11 +1736,13 @@ const STORE_METADATA_FILE = 'STORE.md';
1666
1736
  function escapeForRegExp(value) {
1667
1737
  return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
1668
1738
  }
1739
+ const NAME_PLACEHOLDER = '\u0000extension-create-name\u0000';
1669
1740
  function rewriteStoreMetadata(content, projectName, templateName, today) {
1670
- let next = content.replace(/^(\s*-\s*Name:).*$/m, (_match, label)=>`${label} ${projectName}`);
1671
- if (templateName && templateName !== projectName) next = next.replace(new RegExp(escapeForRegExp(templateName), 'g'), projectName);
1741
+ let next = content;
1742
+ if (templateName && templateName !== projectName) next = next.replace(new RegExp(escapeForRegExp(templateName), 'g'), NAME_PLACEHOLDER);
1743
+ next = next.replace(/^(\s*-\s*Name:).*$/m, (_match, label)=>`${label} ${NAME_PLACEHOLDER}`);
1672
1744
  next = next.replace(/^(Last updated:).*$/m, (_match, label)=>`${label} ${today}`);
1673
- return next;
1745
+ return next.split(NAME_PLACEHOLDER).join(projectName);
1674
1746
  }
1675
1747
  async function writeStoreMetadata(projectPath, projectName, templateName, logger) {
1676
1748
  const storePath = external_node_path_namespaceObject.join(projectPath, STORE_METADATA_FILE);
@@ -1728,7 +1800,7 @@ async function extensionCreate(projectNameInput, { cliVersion, template = "javas
1728
1800
  const projectName = external_node_path_namespaceObject.basename(projectPath);
1729
1801
  const updateSuffix = process.env.EXTENSION_CLI_UPDATE_SUFFIX || '';
1730
1802
  if (updateSuffix) delete process.env.EXTENSION_CLI_UPDATE_SUFFIX;
1731
- const requestedTemplate = 'init' === external_node_path_namespaceObject.basename(String(template)) ? "javascript" : String(template);
1803
+ const requestedTemplate = String(template);
1732
1804
  logger.log(' ');
1733
1805
  logger.log(card({
1734
1806
  version: cliVersion || process.env.EXTENSION_CLI_VERSION,
@@ -1750,8 +1822,10 @@ async function extensionCreate(projectNameInput, { cliVersion, template = "javas
1750
1822
  }));
1751
1823
  logger.log(' ');
1752
1824
  process.env.EXTENSION_CLI_BANNER_PRINTED = 'true';
1753
- await createDirectory(projectPath, projectName, logger);
1754
- const templateProvenance = await importExternalTemplate(projectPath, projectName, template, logger);
1825
+ const createResult = await createDirectory(projectPath, projectName, logger);
1826
+ const templateProvenance = await importExternalTemplate(projectPath, projectName, template, logger, {
1827
+ ownsProjectDir: createResult?.directoryCreated ?? false
1828
+ });
1755
1829
  if (templateProvenance?.template) logger.log(usingTemplate(templateProvenance.template, templateProvenance.source));
1756
1830
  const isMonorepoTemplate = String(template).toLowerCase().includes('monorepo');
1757
1831
  if (isDenoRuntime() && !isMonorepoTemplate) await writeDenoJsonc(projectPath, {
@@ -1,4 +1,7 @@
1
+ export interface CreateDirectoryResult {
2
+ directoryCreated: boolean;
3
+ }
1
4
  export declare function createDirectory(projectPath: string, projectName: string, logger: {
2
5
  log(...args: unknown[]): void;
3
6
  error(...args: unknown[]): void;
4
- }): Promise<void>;
7
+ }): Promise<CreateDirectoryResult>;
@@ -1,3 +1,4 @@
1
+ export declare const DEFAULT_TEMPLATES_REF = "52c0d871c433d9c54878767175ce8ffc9a951756";
1
2
  export declare function resolveCatalogUrls(ref: string, overrideUrl?: string): string[];
2
3
  export interface TemplateProvenance {
3
4
  template: string;
@@ -15,7 +16,13 @@ export declare class TemplateDownloadError extends Error {
15
16
  export declare function extractExamplesTemplateFromZip(zipBuffer: Buffer, templateName: string, projectPath: string): Promise<number>;
16
17
  export declare const TEMPLATE_SCAFFOLDING_FILES: string[];
17
18
  export declare function removeTemplateScaffoldingFiles(projectPath: string): Promise<void>;
19
+ export declare const TEMPLATE_LOCKFILE_NAMES: string[];
20
+ export declare function removeStaleTemplateLockfiles(projectPath: string): Promise<string[]>;
21
+ export interface ImportExternalTemplateOptions {
22
+ ownsProjectDir?: boolean;
23
+ }
24
+ export declare function cleanupFailedImport(projectPath: string, ownsProjectDir: boolean, preExistingEntries: string[]): Promise<void>;
18
25
  export declare function importExternalTemplate(projectPath: string, projectName: string, template: string, logger: {
19
26
  log(...args: unknown[]): void;
20
27
  error(...args: unknown[]): void;
21
- }): Promise<TemplateProvenance>;
28
+ }, options?: ImportExternalTemplateOptions): Promise<TemplateProvenance>;
@@ -1,5 +1,5 @@
1
1
  {
2
- "createdWith": "extension-create@4.0.25",
2
+ "createdWith": "extension-create@4.0.27",
3
3
  "template": "javascript",
4
4
  "source": "bundled"
5
5
  }
@@ -4,8 +4,6 @@
4
4
 
5
5
  > Adds a sidebar panel to the browser with a simple page.
6
6
 
7
-
8
- ![screenshot](./public/screenshot.png)
9
7
  ## Commands
10
8
 
11
9
  ### dev
@@ -23,7 +21,7 @@ pnpm run dev -- --browser=edge
23
21
  Build for production. Convenience scripts target each browser:
24
22
 
25
23
  ```bash
26
- pnpm run build # Chrome (default)
24
+ pnpm run build # Chromium (default)
27
25
  pnpm run build:firefox
28
26
  pnpm run build:edge
29
27
  ```
@@ -9,7 +9,7 @@ Packaging your extension is local and free. Submitting the result to a
9
9
  store is what [extension.dev](https://docs.extension.dev/publish/overview?utm_source=store-md)
10
10
  does, and it sponsors Extension.js.
11
11
 
12
- Last updated: 2026-07-31
12
+ Last updated: 2026-08-03
13
13
 
14
14
  ## Listing
15
15
 
@@ -16,7 +16,7 @@
16
16
  },
17
17
  "dependencies": {},
18
18
  "devDependencies": {
19
- "extension": "^4.0.25"
19
+ "extension": "^4.0.27"
20
20
  },
21
21
  "packageManager": "pnpm@10.28.0",
22
22
  "pnpm": {
package/package.json CHANGED
@@ -25,7 +25,7 @@
25
25
  "templates"
26
26
  ],
27
27
  "name": "extension-create",
28
- "version": "4.0.26",
28
+ "version": "4.0.28",
29
29
  "description": "The standalone extension creation engine for Extension.js",
30
30
  "author": {
31
31
  "name": "Cezar Augusto",
@@ -1,13 +1,13 @@
1
1
  [powered-image]: https://img.shields.io/badge/Powered%20by-Extension.js-0971fe
2
2
  [powered-url]: https://extension.js.org
3
3
 
4
- [![Powered by Extension.js][powered-image]][powered-url]
4
+ ![Powered by Extension.js][powered-image]
5
5
 
6
6
  # JavaScript Starter Extension
7
7
 
8
8
  > Adds a sidebar panel to the browser with a simple page.
9
9
 
10
- ![screenshot](./public/screenshot.png)
10
+ ![screenshot](./screenshot.png)
11
11
 
12
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
13
 
@@ -47,25 +47,27 @@ src/
47
47
 
48
48
  ## Commands
49
49
 
50
+ Cloned this repo instead? The examples ship without npm scripts, so run Extension.js directly from the example directory. Run `npm install` first when the example declares dependencies.
51
+
50
52
  ### dev
51
53
 
52
54
  Run the extension in development mode. Target a browser with `--browser`:
53
55
 
54
56
  ```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
57
+ npx extension@latest dev . # Chromium (default)
58
+ npx extension@latest dev . --browser=chrome
59
+ npx extension@latest dev . --browser=edge
60
+ npx extension@latest dev . --browser=firefox
59
61
  ```
60
62
 
61
63
  ### build
62
64
 
63
- Build for production. Convenience scripts cover each browser:
65
+ Build for production:
64
66
 
65
67
  ```bash
66
- npm run build # Chrome (default)
67
- npm run build:firefox
68
- npm run build:edge
68
+ npx extension@latest build . # Chromium (default)
69
+ npx extension@latest build . --browser=firefox
70
+ npx extension@latest build . --browser=edge
69
71
  ```
70
72
 
71
73
  ### preview
@@ -73,7 +75,7 @@ npm run build:edge
73
75
  Preview the production build with the bundled browser:
74
76
 
75
77
  ```bash
76
- npm run preview
78
+ npx extension@latest preview .
77
79
  ```
78
80
 
79
81
  ## Tests
@@ -4,10 +4,5 @@
4
4
  "description": "Adds a sidebar panel to the browser with a simple page.",
5
5
  "version": "1.0.0",
6
6
  "license": "MIT",
7
- "type": "module",
8
- "author": {
9
- "name": "Cezar Augusto",
10
- "email": "boss@cezaraugusto.net",
11
- "url": "https://cezaraugusto.com"
12
- }
7
+ "type": "module"
13
8
  }