@stacksjs/browser-extension 0.70.139 → 0.70.141

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.
@@ -1,6 +1,8 @@
1
1
  import type { ExtensionConfig, FirefoxAddonsConfig } from './types';
2
2
  /** Metadata accepted by AMO v5 when web-ext creates the first listed version. */
3
3
  export declare function firefoxListingMetadata(config: ExtensionConfig, store: FirefoxAddonsConfig): Record<string, unknown> | undefined;
4
+ /** Resolve web-ext from PATH or from this package's declared dependency. */
5
+ export declare function resolveWebExtExecutable(which?: (command: string) => string | null): string | undefined;
4
6
  /** Build and submit a Firefox extension through Mozilla's official web-ext/AMO v5 flow. */
5
7
  export declare function publishFirefoxExtension(config: ExtensionConfig, options: FirefoxPublishOptions): Promise<FirefoxPublishResult>;
6
8
  export declare interface FirefoxAddonsAuth {
@@ -1,5 +1,6 @@
1
- import { existsSync, readdirSync } from "node:fs";
1
+ import { existsSync, readFileSync, readdirSync } from "node:fs";
2
2
  import { mkdir, mkdtemp, rm } from "node:fs/promises";
3
+ import { createRequire } from "node:module";
3
4
  import { tmpdir } from "node:os";
4
5
  import { join, resolve } from "node:path";
5
6
  import { buildExtension, resolveOutdir } from "./build";
@@ -23,13 +24,27 @@ export function firefoxListingMetadata(config, store) {
23
24
  requires_payment: store.requiresPayment ?? !1
24
25
  };
25
26
  }
27
+ export function resolveWebExtExecutable(which = Bun.which) {
28
+ const fromPath = which("web-ext");
29
+ if (fromPath)
30
+ return fromPath;
31
+ try {
32
+ const packagePath = createRequire(import.meta.url).resolve("web-ext/package.json"), packageJson = JSON.parse(readFileSync(packagePath, "utf8")), relativeBin = typeof packageJson.bin === "string" ? packageJson.bin : packageJson.bin?.["web-ext"];
33
+ if (!relativeBin)
34
+ return;
35
+ const executable = resolve(packagePath, "..", relativeBin);
36
+ return existsSync(executable) ? executable : void 0;
37
+ } catch {
38
+ return;
39
+ }
40
+ }
26
41
  export async function publishFirefoxExtension(config, options) {
27
42
  if (!config.geckoId)
28
43
  throw Error("[browser-extension] Firefox publishing needs geckoId in config/extension.ts");
29
44
  const store = config.firefoxAddons ?? {}, auth = resolveFirefoxAuth(options), cwd = options.cwd ?? process.cwd();
30
45
  if (options.build !== !1)
31
46
  await buildExtension(config, { target: "firefox", version: options.version, cwd });
32
- const sourceDir = resolve(cwd, resolveOutdir(config, "firefox")), artifactsDir = resolve(cwd, store.artifactsDir ?? "web-ext-artifacts"), executable = Bun.which("web-ext");
47
+ const sourceDir = resolve(cwd, resolveOutdir(config, "firefox")), artifactsDir = resolve(cwd, store.artifactsDir ?? "web-ext-artifacts"), executable = resolveWebExtExecutable();
33
48
  if (!executable)
34
49
  throw Error("[browser-extension] web-ext is unavailable; reinstall @stacksjs/browser-extension dependencies");
35
50
  await mkdir(artifactsDir, { recursive: !0 });
package/dist/safari.d.ts CHANGED
@@ -18,6 +18,8 @@ import type { ExtensionConfig } from './types';
18
18
  export declare function safariAppName(config: ExtensionConfig): string;
19
19
  /** Directory the container app is scaffolded into. */
20
20
  export declare function safariProjectDir(cwd?: unknown, dir?: string): string;
21
+ /** Upgrade a Stacks-generated project that copied Resources as a nested folder. */
22
+ export declare function migrateSafariResourceBuildPhase(project: string, appName: string): string;
21
23
  /**
22
24
  * Scaffold the macOS container app (Xcode project + SwiftUI app + appex) from
23
25
  * the tokenized template. Idempotent: existing files are kept unless `force`.
package/dist/safari.js CHANGED
@@ -17,8 +17,45 @@ function templateRoot() {
17
17
  function fillTemplate(text, vars) {
18
18
  return text.replaceAll("__EXT_BUNDLE_ID__", vars.extBundleId).replaceAll("__BUNDLE_ID__", vars.bundleId).replaceAll("__APP_DISPLAY_NAME__", vars.displayName).replaceAll("__APP_NAME__", vars.appName).replaceAll("__MARKETING_VERSION__", vars.version).replaceAll("__DEVELOPMENT_TEAM__", vars.teamId).replaceAll("__YEAR__", String(new Date().getFullYear()));
19
19
  }
20
+ function safariResourceCopyBuildPhase(appName) {
21
+ return `/* Begin PBXShellScriptBuildPhase section */
22
+ 0D0000000000000000000023 /* Copy Web Extension Resources */ = {
23
+ isa = PBXShellScriptBuildPhase;
24
+ alwaysOutOfDate = 1;
25
+ buildActionMask = 2147483647;
26
+ files = (
27
+ );
28
+ inputFileListPaths = (
29
+ "$(SRCROOT)/${appName} Extension/Resources.inputs.xcfilelist",
30
+ );
31
+ name = "Copy Web Extension Resources";
32
+ outputFileListPaths = (
33
+ "$(SRCROOT)/${appName} Extension/Resources.outputs.xcfilelist",
34
+ );
35
+ runOnlyForDeploymentPostprocessing = 0;
36
+ shellPath = /bin/sh;
37
+ shellScript = "set -e\\nresources_root=\\"\${SRCROOT}/${appName} Extension/Resources/\\"\\nwhile IFS= read -r source_file; do\\n relative_path=\\"\${source_file#$resources_root}\\"\\n output_file=\\"\${TARGET_BUILD_DIR}/\${UNLOCALIZED_RESOURCES_FOLDER_PATH}/\${relative_path}\\"\\n /bin/mkdir -p \\"$(/usr/bin/dirname \\"$output_file\\")\\"\\n /bin/cp \\"$source_file\\" \\"$output_file\\"\\ndone < \\"$SCRIPT_INPUT_FILE_LIST_0\\"\\n";
38
+ };
39
+ /* End PBXShellScriptBuildPhase section */
40
+
41
+ `;
42
+ }
43
+ export function migrateSafariResourceBuildPhase(project, appName) {
44
+ if (project.includes("resources_prefix='$(SRCROOT)"))
45
+ return project.replace(/\/\* Begin PBXShellScriptBuildPhase section \*\/[\s\S]*?\/\* End PBXShellScriptBuildPhase section \*\/\n/, safariResourceCopyBuildPhase(appName));
46
+ if (project.includes("SCRIPT_INPUT_FILE_LIST_0"))
47
+ return project;
48
+ if (project.includes("0D0000000000000000000023 /* Copy Web Extension Resources */"))
49
+ return project.replace(/\/\* Begin PBXShellScriptBuildPhase section \*\/[\s\S]*?\/\* End PBXShellScriptBuildPhase section \*\/\n/, safariResourceCopyBuildPhase(appName));
50
+ if (!project.includes("0C0000000000000000000023 /* Resources in Resources */"))
51
+ return project;
52
+ return project.replace(/^\s*0C0000000000000000000023 \/\* Resources in Resources \*\/.*\n/m, "").replace(/^\s*0C0000000000000000000023 \/\* Resources in Resources \*\/,\n/mg, "").replace(` 0D0000000000000000000022 /* Resources */,
53
+ `, ` 0D0000000000000000000022 /* Resources */,
54
+ 0D0000000000000000000023 /* Copy Web Extension Resources */,
55
+ `).replace("/* Begin PBXSourcesBuildPhase section */", `${safariResourceCopyBuildPhase(appName)}/* Begin PBXSourcesBuildPhase section */`);
56
+ }
20
57
  function* walk(dir, prefix = "") {
21
- for (const entry of readdirSync(dir)) {
58
+ for (const entry of readdirSync(dir).sort()) {
22
59
  const rel = prefix ? `${prefix}/${entry}` : entry;
23
60
  if (statSync(join(dir, entry)).isDirectory())
24
61
  yield* walk(join(dir, entry), rel);
@@ -92,7 +129,13 @@ export async function syncSafariResources(config, options = {}) {
92
129
  const cwd = options.cwd ?? process.cwd(), outdir = resolve(cwd, options.outdir ?? resolveOutdir(config, "safari"));
93
130
  if (!existsSync(join(outdir, "manifest.json")))
94
131
  throw Error(`[browser-extension] ${outdir}/manifest.json is missing. Run extension:build --target safari first.`);
95
- const appName = safariAppName(config), resources = join(safariProjectDir(cwd, options.dir), `${appName} Extension`, "Resources"), exclude = new Set(config.safariExclude ?? []);
132
+ const appName = safariAppName(config), projectDir = safariProjectDir(cwd, options.dir), projectPath = join(projectDir, `${appName}.xcodeproj`, "project.pbxproj");
133
+ if (existsSync(projectPath)) {
134
+ const project = await Bun.file(projectPath).text(), migrated = migrateSafariResourceBuildPhase(project, appName);
135
+ if (migrated !== project)
136
+ await Bun.write(projectPath, migrated);
137
+ }
138
+ const resources = join(projectDir, `${appName} Extension`, "Resources"), exclude = new Set(config.safariExclude ?? []);
96
139
  if (existsSync(resources)) {
97
140
  for (const entry of readdirSync(resources))
98
141
  if (entry !== ".gitkeep")
@@ -100,14 +143,23 @@ export async function syncSafariResources(config, options = {}) {
100
143
  }
101
144
  await mkdir(resources, { recursive: !0 });
102
145
  let files = 0;
146
+ const synced = [];
103
147
  for (const rel of walk(outdir)) {
104
- if (exclude.has(rel))
148
+ if (exclude.has(rel) || rel.split("/").includes(".DS_Store"))
105
149
  continue;
106
150
  const dest = join(resources, rel);
107
151
  await mkdir(dirname(dest), { recursive: !0 });
108
152
  cpSync(join(outdir, rel), dest);
153
+ synced.push(rel);
109
154
  files += 1;
110
155
  }
156
+ const inputs = synced.map((rel) => `$(SRCROOT)/${appName} Extension/Resources/${rel}`).join(`
157
+ `), outputs = synced.map((rel) => `$(TARGET_BUILD_DIR)/$(UNLOCALIZED_RESOURCES_FOLDER_PATH)/${rel}`).join(`
158
+ `);
159
+ await Bun.write(join(projectDir, `${appName} Extension`, "Resources.inputs.xcfilelist"), `${inputs}
160
+ `);
161
+ await Bun.write(join(projectDir, `${appName} Extension`, "Resources.outputs.xcfilelist"), `${outputs}
162
+ `);
111
163
  return { resources, files };
112
164
  }
113
165
  export async function buildSafariApp(config, options = {}) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@stacksjs/browser-extension",
3
3
  "type": "module",
4
- "version": "0.70.139",
4
+ "version": "0.70.141",
5
5
  "description": "Build MV3 browser extensions (Chrome, Firefox, Safari) the Stacks way — manifest, content/background scripts, DNR rules, packaging, Safari container app, all config-driven.",
6
6
  "author": "Chris Breuer",
7
7
  "contributors": [
@@ -11,7 +11,6 @@
11
11
  0C0000000000000000000011 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B0000000000000000000011 /* ContentView.swift */; };
12
12
  0C0000000000000000000012 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0B0000000000000000000012 /* Assets.xcassets */; };
13
13
  0C0000000000000000000020 /* SafariWebExtensionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B0000000000000000000020 /* SafariWebExtensionHandler.swift */; };
14
- 0C0000000000000000000023 /* Resources in Resources */ = {isa = PBXBuildFile; fileRef = 0B0000000000000000000023 /* Resources */; };
15
14
  0C0000000000000000000030 /* __APP_NAME__ Extension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 0B0000000000000000000002 /* __APP_NAME__ Extension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
16
15
  /* End PBXBuildFile section */
17
16
 
@@ -141,6 +140,7 @@
141
140
  0D0000000000000000000020 /* Sources */,
142
141
  0D0000000000000000000021 /* Frameworks */,
143
142
  0D0000000000000000000022 /* Resources */,
143
+ 0D0000000000000000000023 /* Copy Web Extension Resources */,
144
144
  );
145
145
  buildRules = (
146
146
  );
@@ -201,12 +201,31 @@
201
201
  isa = PBXResourcesBuildPhase;
202
202
  buildActionMask = 2147483647;
203
203
  files = (
204
- 0C0000000000000000000023 /* Resources in Resources */,
205
204
  );
206
205
  runOnlyForDeploymentPostprocessing = 0;
207
206
  };
208
207
  /* End PBXResourcesBuildPhase section */
209
208
 
209
+ /* Begin PBXShellScriptBuildPhase section */
210
+ 0D0000000000000000000023 /* Copy Web Extension Resources */ = {
211
+ isa = PBXShellScriptBuildPhase;
212
+ alwaysOutOfDate = 1;
213
+ buildActionMask = 2147483647;
214
+ files = (
215
+ );
216
+ inputFileListPaths = (
217
+ "$(SRCROOT)/__APP_NAME__ Extension/Resources.inputs.xcfilelist",
218
+ );
219
+ name = "Copy Web Extension Resources";
220
+ outputFileListPaths = (
221
+ "$(SRCROOT)/__APP_NAME__ Extension/Resources.outputs.xcfilelist",
222
+ );
223
+ runOnlyForDeploymentPostprocessing = 0;
224
+ shellPath = /bin/sh;
225
+ shellScript = "set -e\nresources_root=\"${SRCROOT}/__APP_NAME__ Extension/Resources/\"\nwhile IFS= read -r source_file; do\n relative_path=\"${source_file#$resources_root}\"\n output_file=\"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/${relative_path}\"\n /bin/mkdir -p \"$(/usr/bin/dirname \"$output_file\")\"\n /bin/cp \"$source_file\" \"$output_file\"\ndone < \"$SCRIPT_INPUT_FILE_LIST_0\"\n";
226
+ };
227
+ /* End PBXShellScriptBuildPhase section */
228
+
210
229
  /* Begin PBXSourcesBuildPhase section */
211
230
  0D0000000000000000000010 /* Sources */ = {
212
231
  isa = PBXSourcesBuildPhase;