@stacksjs/browser-extension 0.70.138 → 0.70.140

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,43 @@ 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("SCRIPT_INPUT_FILE_LIST_0"))
45
+ return project;
46
+ if (project.includes("0D0000000000000000000023 /* Copy Web Extension Resources */"))
47
+ return project.replace(/\/\* Begin PBXShellScriptBuildPhase section \*\/[\s\S]*?\/\* End PBXShellScriptBuildPhase section \*\/\n/, safariResourceCopyBuildPhase(appName));
48
+ if (!project.includes("0C0000000000000000000023 /* Resources in Resources */"))
49
+ return project;
50
+ return project.replace(/^\s*0C0000000000000000000023 \/\* Resources in Resources \*\/.*\n/m, "").replace(/^\s*0C0000000000000000000023 \/\* Resources in Resources \*\/,\n/mg, "").replace(` 0D0000000000000000000022 /* Resources */,
51
+ `, ` 0D0000000000000000000022 /* Resources */,
52
+ 0D0000000000000000000023 /* Copy Web Extension Resources */,
53
+ `).replace("/* Begin PBXSourcesBuildPhase section */", `${safariResourceCopyBuildPhase(appName)}/* Begin PBXSourcesBuildPhase section */`);
54
+ }
20
55
  function* walk(dir, prefix = "") {
21
- for (const entry of readdirSync(dir)) {
56
+ for (const entry of readdirSync(dir).sort()) {
22
57
  const rel = prefix ? `${prefix}/${entry}` : entry;
23
58
  if (statSync(join(dir, entry)).isDirectory())
24
59
  yield* walk(join(dir, entry), rel);
@@ -92,7 +127,13 @@ export async function syncSafariResources(config, options = {}) {
92
127
  const cwd = options.cwd ?? process.cwd(), outdir = resolve(cwd, options.outdir ?? resolveOutdir(config, "safari"));
93
128
  if (!existsSync(join(outdir, "manifest.json")))
94
129
  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 ?? []);
130
+ const appName = safariAppName(config), projectDir = safariProjectDir(cwd, options.dir), projectPath = join(projectDir, `${appName}.xcodeproj`, "project.pbxproj");
131
+ if (existsSync(projectPath)) {
132
+ const project = await Bun.file(projectPath).text(), migrated = migrateSafariResourceBuildPhase(project, appName);
133
+ if (migrated !== project)
134
+ await Bun.write(projectPath, migrated);
135
+ }
136
+ const resources = join(projectDir, `${appName} Extension`, "Resources"), exclude = new Set(config.safariExclude ?? []);
96
137
  if (existsSync(resources)) {
97
138
  for (const entry of readdirSync(resources))
98
139
  if (entry !== ".gitkeep")
@@ -100,14 +141,23 @@ export async function syncSafariResources(config, options = {}) {
100
141
  }
101
142
  await mkdir(resources, { recursive: !0 });
102
143
  let files = 0;
144
+ const synced = [];
103
145
  for (const rel of walk(outdir)) {
104
- if (exclude.has(rel))
146
+ if (exclude.has(rel) || rel.split("/").includes(".DS_Store"))
105
147
  continue;
106
148
  const dest = join(resources, rel);
107
149
  await mkdir(dirname(dest), { recursive: !0 });
108
150
  cpSync(join(outdir, rel), dest);
151
+ synced.push(rel);
109
152
  files += 1;
110
153
  }
154
+ const inputs = synced.map((rel) => join(resources, rel)).join(`
155
+ `), outputs = synced.map((rel) => `$(TARGET_BUILD_DIR)/$(UNLOCALIZED_RESOURCES_FOLDER_PATH)/${rel}`).join(`
156
+ `);
157
+ await Bun.write(join(projectDir, `${appName} Extension`, "Resources.inputs.xcfilelist"), `${inputs}
158
+ `);
159
+ await Bun.write(join(projectDir, `${appName} Extension`, "Resources.outputs.xcfilelist"), `${outputs}
160
+ `);
111
161
  return { resources, files };
112
162
  }
113
163
  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.138",
4
+ "version": "0.70.140",
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;