extension-create 4.0.25 → 4.0.27

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);
@@ -589,6 +599,28 @@ async function removeTemplateScaffoldingFiles(projectPath) {
589
599
  force: true
590
600
  })));
591
601
  }
602
+ const TEMPLATE_LOCKFILE_NAMES = [
603
+ 'package-lock.json',
604
+ 'npm-shrinkwrap.json',
605
+ 'yarn.lock',
606
+ 'pnpm-lock.yaml',
607
+ 'bun.lockb',
608
+ 'bun.lock',
609
+ 'deno.lock'
610
+ ];
611
+ async function removeStaleTemplateLockfiles(projectPath) {
612
+ const removed = [];
613
+ for (const name of TEMPLATE_LOCKFILE_NAMES){
614
+ const target = external_node_path_namespaceObject.join(projectPath, name);
615
+ if ((0, external_node_fs_namespaceObject.existsSync)(target)) {
616
+ await promises_namespaceObject.rm(target, {
617
+ force: true
618
+ });
619
+ removed.push(name);
620
+ }
621
+ }
622
+ return removed;
623
+ }
592
624
  function getArchiveBaseName(url) {
593
625
  const withoutQuery = url.split('?')[0];
594
626
  const fileName = external_node_path_namespaceObject.basename(withoutQuery);
@@ -611,12 +643,35 @@ async function getZipSourcePath(tempPath, templateUrl) {
611
643
  if (onlyDir.name === archiveBase) return external_node_path_namespaceObject.join(tempPath, onlyDir.name);
612
644
  return tempPath;
613
645
  }
614
- async function importExternalTemplate(projectPath, projectName, template, logger) {
646
+ async function cleanupFailedImport(projectPath, ownsProjectDir, preExistingEntries) {
647
+ if (ownsProjectDir) return void await promises_namespaceObject.rm(projectPath, {
648
+ recursive: true,
649
+ force: true
650
+ }).catch(()=>{});
651
+ const keep = new Set(preExistingEntries);
652
+ let entries = [];
653
+ try {
654
+ entries = await promises_namespaceObject.readdir(projectPath);
655
+ } catch {
656
+ return;
657
+ }
658
+ await Promise.all(entries.filter((entry)=>!keep.has(entry)).map((entry)=>promises_namespaceObject.rm(external_node_path_namespaceObject.join(projectPath, entry), {
659
+ recursive: true,
660
+ force: true
661
+ }).catch(()=>{})));
662
+ }
663
+ async function importExternalTemplate(projectPath, projectName, template, logger, options) {
615
664
  const templateName = external_node_path_namespaceObject.basename(template);
616
- const resolvedTemplate = 'init' === templateName ? "javascript" : template;
617
- const resolvedTemplateName = 'init' === templateName ? "javascript" : templateName;
665
+ const resolvedTemplate = template;
666
+ const resolvedTemplateName = templateName;
618
667
  const isHttp = /^https?:\/\//i.test(template);
619
668
  const isGithub = /^https?:\/\/github\.com\//i.test(template);
669
+ const dirExistedBeforeImport = (0, external_node_fs_namespaceObject.existsSync)(projectPath);
670
+ const ownsProjectDir = options?.ownsProjectDir ?? !dirExistedBeforeImport;
671
+ let preExistingEntries = [];
672
+ if (dirExistedBeforeImport) try {
673
+ preExistingEntries = await promises_namespaceObject.readdir(projectPath);
674
+ } catch {}
620
675
  try {
621
676
  await promises_namespaceObject.mkdir(projectPath, {
622
677
  recursive: true
@@ -626,6 +681,8 @@ async function importExternalTemplate(projectPath, projectName, template, logger
626
681
  if ((0, external_node_fs_namespaceObject.existsSync)(localTemplate)) {
627
682
  await copyDirectoryWithSymlinks(localTemplate, projectPath);
628
683
  await removeTemplateScaffoldingFiles(projectPath);
684
+ const dropped = await removeStaleTemplateLockfiles(projectPath);
685
+ if (dropped.length) logger.log(removedStaleTemplateLockfiles(dropped));
629
686
  return {
630
687
  template: resolvedTemplateName,
631
688
  source: 'bundled'
@@ -696,6 +753,8 @@ async function importExternalTemplate(projectPath, projectName, template, logger
696
753
  };
697
754
  }
698
755
  await removeTemplateScaffoldingFiles(projectPath);
756
+ const droppedLockfiles = await removeStaleTemplateLockfiles(projectPath);
757
+ if (droppedLockfiles.length) logger.log(removedStaleTemplateLockfiles(droppedLockfiles));
699
758
  await promises_namespaceObject.rm(tempRoot, {
700
759
  recursive: true,
701
760
  force: true
@@ -705,10 +764,7 @@ async function importExternalTemplate(projectPath, projectName, template, logger
705
764
  if (error instanceof TemplateNotFoundError) logger.error(templateNotFoundInCatalog(templateName, error.cause));
706
765
  else if (error instanceof TemplateDownloadError) logger.error(templateDownloadFailed(templateName, error));
707
766
  else logger.error(installingFromTemplateError(templateName, error));
708
- await promises_namespaceObject.rm(projectPath, {
709
- recursive: true,
710
- force: true
711
- }).catch(()=>{});
767
+ await cleanupFailedImport(projectPath, ownsProjectDir, preExistingEntries);
712
768
  throw error;
713
769
  }
714
770
  }
@@ -1025,25 +1081,25 @@ function parseNpmSpecifier(specifier) {
1025
1081
  }
1026
1082
  function readDenoConfigDependencies(projectPath) {
1027
1083
  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
- }
1084
+ const filename = [
1085
+ 'deno.json',
1086
+ 'deno.jsonc'
1087
+ ].find((candidate)=>external_node_fs_namespaceObject.existsSync(external_node_path_namespaceObject.join(projectPath, candidate)));
1088
+ if (!filename) return dependencies;
1089
+ let config;
1090
+ try {
1091
+ config = parseJsoncSafe(external_node_fs_namespaceObject.readFileSync(external_node_path_namespaceObject.join(projectPath, filename), 'utf8'));
1092
+ } catch {
1093
+ return dependencies;
1094
+ }
1095
+ const imports = config?.imports;
1096
+ if (!imports || 'object' != typeof imports) return dependencies;
1097
+ for (const [rawAlias, specifier] of Object.entries(imports)){
1098
+ const parsed = parseNpmSpecifier(specifier);
1099
+ if (!parsed) continue;
1100
+ dependencies[parsed.name] = dependencies[parsed.name] || parsed.version;
1101
+ const alias = rawAlias.endsWith('/') ? rawAlias.slice(0, -1) : rawAlias;
1102
+ if (alias && alias !== parsed.name) dependencies[alias] = dependencies[alias] || parsed.version;
1047
1103
  }
1048
1104
  return dependencies;
1049
1105
  }
@@ -1068,12 +1124,15 @@ function resolveDevelopRoot(projectPath) {
1068
1124
  return null;
1069
1125
  }
1070
1126
  }
1071
- function readPackageJson(projectPath) {
1127
+ function readPackageJson(projectPath, logger) {
1072
1128
  let pkg = {};
1129
+ const packageJsonPath = external_node_path_namespaceObject.join(projectPath, 'package.json');
1073
1130
  try {
1074
- const raw = external_node_fs_namespaceObject.readFileSync(external_node_path_namespaceObject.join(projectPath, 'package.json'), 'utf8');
1131
+ const raw = external_node_fs_namespaceObject.readFileSync(packageJsonPath, 'utf8');
1075
1132
  pkg = JSON.parse(raw);
1076
- } catch {}
1133
+ } catch (error) {
1134
+ if (error?.code !== 'ENOENT') logger.log(malformedPackageJson(packageJsonPath, error));
1135
+ }
1077
1136
  const denoDependencies = readDenoConfigDependencies(projectPath);
1078
1137
  if (Object.keys(denoDependencies).length > 0) pkg = {
1079
1138
  ...pkg,
@@ -1100,8 +1159,8 @@ function canResolve(dependency, paths) {
1100
1159
  function findConfigFile(projectPath, candidates) {
1101
1160
  return candidates.some((file)=>external_node_fs_namespaceObject.existsSync(external_node_path_namespaceObject.join(projectPath, file)));
1102
1161
  }
1103
- function detectOptionalDependencies(projectPath) {
1104
- const pkg = readPackageJson(projectPath);
1162
+ function detectOptionalDependencies(projectPath, logger) {
1163
+ const pkg = readPackageJson(projectPath, logger);
1105
1164
  const usesReact = hasDependency(pkg, 'react') || hasDependency(pkg, 'react-dom');
1106
1165
  const usesPreact = hasDependency(pkg, 'preact');
1107
1166
  const usesVue = hasDependency(pkg, 'vue');
@@ -1194,7 +1253,8 @@ function buildOptionalInstallArgs(pm, dependencies, installDir) {
1194
1253
  ...dependencies,
1195
1254
  '--dir',
1196
1255
  installDir,
1197
- '--save-optional'
1256
+ '--save-optional',
1257
+ '--silent'
1198
1258
  ];
1199
1259
  if ('bun' === pm) return [
1200
1260
  'add',
@@ -1210,8 +1270,8 @@ function buildOptionalInstallArgs(pm, dependencies, installDir) {
1210
1270
  '--legacy-peer-deps'
1211
1271
  ];
1212
1272
  }
1213
- function resolveMissingOptionalDeps(developRoot, projectPath) {
1214
- const plan = detectOptionalDependencies(projectPath);
1273
+ function resolveMissingOptionalDeps(developRoot, projectPath, logger) {
1274
+ const plan = detectOptionalDependencies(projectPath, logger);
1215
1275
  const dependenciesByIntegration = {};
1216
1276
  const integrations = [];
1217
1277
  const missing = new Set();
@@ -1260,7 +1320,7 @@ async function installInternalDependencies(projectPath, logger) {
1260
1320
  if ('test' === process.env.EXTENSION_ENV || 'true' === process.env.EXTENSION_SKIP_INTERNAL_INSTALL) return;
1261
1321
  const developRoot = resolveDevelopRoot(projectPath);
1262
1322
  if (!developRoot) return;
1263
- const optionalPlan = resolveMissingOptionalDeps(developRoot, projectPath);
1323
+ const optionalPlan = resolveMissingOptionalDeps(developRoot, projectPath, logger);
1264
1324
  if (optionalPlan.dependencies.length > 0) await installOptionalDependencies(developRoot, projectPath, optionalPlan, logger);
1265
1325
  }
1266
1326
  async function setupBuiltInTests(projectPath, logger) {
@@ -1476,16 +1536,31 @@ async function collectTemplateImports(projectPath, cliVersion) {
1476
1536
  }
1477
1537
  async function writeDenoJsonc(projectPath, { template = "javascript", cliVersion, primary = false }, logger) {
1478
1538
  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;
1539
+ let existingConfig;
1540
+ for (const candidate of [
1541
+ 'deno.json',
1542
+ 'deno.jsonc'
1543
+ ])if (await pathExists(external_node_path_namespaceObject.join(projectPath, candidate))) {
1544
+ existingConfig = candidate;
1545
+ break;
1546
+ }
1547
+ if (existingConfig && !primary) return;
1483
1548
  const extensionBinary = await resolveExtensionBinary();
1484
1549
  const tasks = getTemplateAwareScripts(template, extensionBinary);
1485
1550
  const imports = primary ? await collectTemplateImports(projectPath, cliVersion) : void 0;
1486
1551
  try {
1487
1552
  if (isDebug()) logger.log(writingDenoJsonc());
1488
- await promises_namespaceObject.writeFile(external_node_path_namespaceObject.join(projectPath, 'deno.jsonc'), renderDenoJsonc(tasks, imports));
1553
+ if (existingConfig) {
1554
+ const configPath = external_node_path_namespaceObject.join(projectPath, existingConfig);
1555
+ const config = parseJsoncSafe(await promises_namespaceObject.readFile(configPath, 'utf8'));
1556
+ config.imports = {
1557
+ ...imports || {},
1558
+ ...config.imports || {}
1559
+ };
1560
+ if (void 0 === config.nodeModulesDir) config.nodeModulesDir = 'auto';
1561
+ if (!config.tasks) config.tasks = tasks;
1562
+ await promises_namespaceObject.writeFile(configPath, `${JSON.stringify(config, null, 2)}\n`);
1563
+ } else await promises_namespaceObject.writeFile(external_node_path_namespaceObject.join(projectPath, 'deno.jsonc'), renderDenoJsonc(tasks, imports));
1489
1564
  if (primary) await promises_namespaceObject.rm(external_node_path_namespaceObject.join(projectPath, 'package.json'), {
1490
1565
  force: true
1491
1566
  });
@@ -1522,6 +1597,9 @@ const localSessionState = [
1522
1597
  const envFiles = [
1523
1598
  '',
1524
1599
  '# local env files',
1600
+ '.env',
1601
+ '.env*',
1602
+ '!.env.example',
1525
1603
  '.env.local',
1526
1604
  '.env.development.local',
1527
1605
  '.env.test.local',
@@ -1646,11 +1724,12 @@ async function writeReadmeFile(projectPath, projectName, logger) {
1646
1724
  const manifestJsonPath = await findManifestJsonPath(projectPath);
1647
1725
  const manifestJson = JSON.parse(await promises_namespaceObject.readFile(manifestJsonPath, 'utf-8'));
1648
1726
  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` : '';
1727
+ const hasPublicScreenshot = await write_readme_file_pathExists(external_node_path_namespaceObject.join(projectPath, 'public', 'screenshot.png'));
1728
+ const hasRootScreenshot = await write_readme_file_pathExists(external_node_path_namespaceObject.join(projectPath, 'screenshot.png'));
1729
+ const screenshotHref = hasPublicScreenshot ? './public/screenshot.png' : hasRootScreenshot ? './screenshot.png' : null;
1730
+ const screenshotEmbed = screenshotHref ? `\n![screenshot](${screenshotHref})\n` : '';
1652
1731
  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`;
1732
+ 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
1733
  try {
1655
1734
  if (isDebug()) logger.log(writingReadmeMetaData());
1656
1735
  await promises_namespaceObject.mkdir(projectPath, {
@@ -1666,11 +1745,13 @@ const STORE_METADATA_FILE = 'STORE.md';
1666
1745
  function escapeForRegExp(value) {
1667
1746
  return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
1668
1747
  }
1748
+ const NAME_PLACEHOLDER = '\u0000extension-create-name\u0000';
1669
1749
  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);
1750
+ let next = content;
1751
+ if (templateName && templateName !== projectName) next = next.replace(new RegExp(escapeForRegExp(templateName), 'g'), NAME_PLACEHOLDER);
1752
+ next = next.replace(/^(\s*-\s*Name:).*$/m, (_match, label)=>`${label} ${NAME_PLACEHOLDER}`);
1672
1753
  next = next.replace(/^(Last updated:).*$/m, (_match, label)=>`${label} ${today}`);
1673
- return next;
1754
+ return next.split(NAME_PLACEHOLDER).join(projectName);
1674
1755
  }
1675
1756
  async function writeStoreMetadata(projectPath, projectName, templateName, logger) {
1676
1757
  const storePath = external_node_path_namespaceObject.join(projectPath, STORE_METADATA_FILE);
@@ -1728,7 +1809,7 @@ async function extensionCreate(projectNameInput, { cliVersion, template = "javas
1728
1809
  const projectName = external_node_path_namespaceObject.basename(projectPath);
1729
1810
  const updateSuffix = process.env.EXTENSION_CLI_UPDATE_SUFFIX || '';
1730
1811
  if (updateSuffix) delete process.env.EXTENSION_CLI_UPDATE_SUFFIX;
1731
- const requestedTemplate = 'init' === external_node_path_namespaceObject.basename(String(template)) ? "javascript" : String(template);
1812
+ const requestedTemplate = String(template);
1732
1813
  logger.log(' ');
1733
1814
  logger.log(card({
1734
1815
  version: cliVersion || process.env.EXTENSION_CLI_VERSION,
@@ -1750,8 +1831,10 @@ async function extensionCreate(projectNameInput, { cliVersion, template = "javas
1750
1831
  }));
1751
1832
  logger.log(' ');
1752
1833
  process.env.EXTENSION_CLI_BANNER_PRINTED = 'true';
1753
- await createDirectory(projectPath, projectName, logger);
1754
- const templateProvenance = await importExternalTemplate(projectPath, projectName, template, logger);
1834
+ const createResult = await createDirectory(projectPath, projectName, logger);
1835
+ const templateProvenance = await importExternalTemplate(projectPath, projectName, template, logger, {
1836
+ ownsProjectDir: createResult?.directoryCreated ?? false
1837
+ });
1755
1838
  if (templateProvenance?.template) logger.log(usingTemplate(templateProvenance.template, templateProvenance.source));
1756
1839
  const isMonorepoTemplate = String(template).toLowerCase().includes('monorepo');
1757
1840
  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>;
@@ -15,7 +15,13 @@ export declare class TemplateDownloadError extends Error {
15
15
  export declare function extractExamplesTemplateFromZip(zipBuffer: Buffer, templateName: string, projectPath: string): Promise<number>;
16
16
  export declare const TEMPLATE_SCAFFOLDING_FILES: string[];
17
17
  export declare function removeTemplateScaffoldingFiles(projectPath: string): Promise<void>;
18
+ export declare const TEMPLATE_LOCKFILE_NAMES: string[];
19
+ export declare function removeStaleTemplateLockfiles(projectPath: string): Promise<string[]>;
20
+ export interface ImportExternalTemplateOptions {
21
+ ownsProjectDir?: boolean;
22
+ }
23
+ export declare function cleanupFailedImport(projectPath: string, ownsProjectDir: boolean, preExistingEntries: string[]): Promise<void>;
18
24
  export declare function importExternalTemplate(projectPath: string, projectName: string, template: string, logger: {
19
25
  log(...args: unknown[]): void;
20
26
  error(...args: unknown[]): void;
21
- }): Promise<TemplateProvenance>;
27
+ }, options?: ImportExternalTemplateOptions): Promise<TemplateProvenance>;
@@ -1,5 +1,5 @@
1
1
  {
2
- "createdWith": "extension-create@4.0.24",
2
+ "createdWith": "extension-create@4.0.26",
3
3
  "template": "javascript",
4
4
  "source": "bundled"
5
5
  }
@@ -23,7 +23,7 @@ pnpm run dev -- --browser=edge
23
23
  Build for production. Convenience scripts target each browser:
24
24
 
25
25
  ```bash
26
- pnpm run build # Chrome (default)
26
+ pnpm run build # Chromium (default)
27
27
  pnpm run build:firefox
28
28
  pnpm run build:edge
29
29
  ```
@@ -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-30
12
+ Last updated: 2026-08-02
13
13
 
14
14
  ## Listing
15
15
 
@@ -16,7 +16,7 @@
16
16
  },
17
17
  "dependencies": {},
18
18
  "devDependencies": {
19
- "extension": "^4.0.24"
19
+ "extension": "^4.0.26"
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.25",
28
+ "version": "4.0.27",
29
29
  "description": "The standalone extension creation engine for Extension.js",
30
30
  "author": {
31
31
  "name": "Cezar Augusto",
@@ -1,7 +1,7 @@
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
 
@@ -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