create-addfox-app 0.1.1-beta.7 → 0.1.1
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/README.md +1 -1
- package/dist/cli.js +184 -99
- package/package.json +9 -5
- package/templates/README.md +3 -0
- package/templates/template-preact-js/AGENTS.md +54 -0
- package/templates/template-preact-js/addfox.config.js +27 -0
- package/templates/template-preact-js/app/background/index.js +8 -0
- package/templates/template-preact-js/app/content/index.js +1 -0
- package/templates/template-preact-js/app/options/App.jsx +5 -0
- package/templates/template-preact-js/app/options/index.jsx +5 -0
- package/templates/template-preact-js/app/popup/App.jsx +31 -0
- package/templates/template-preact-js/app/popup/index.jsx +5 -0
- package/templates/template-preact-js/app/sidepanel/App.jsx +5 -0
- package/templates/template-preact-js/app/sidepanel/index.jsx +5 -0
- package/templates/template-preact-js/package.json +18 -0
- package/templates/template-preact-js/public/icons/icon_128.png +0 -0
- package/templates/template-preact-js/public/welcome.html +20 -0
- package/templates/template-preact-ts/AGENTS.md +61 -0
- package/templates/template-preact-ts/addfox.config.ts +27 -0
- package/templates/template-preact-ts/app/background/index.ts +8 -0
- package/templates/template-preact-ts/app/content/index.ts +1 -0
- package/templates/template-preact-ts/app/options/App.tsx +5 -0
- package/templates/template-preact-ts/app/options/index.tsx +5 -0
- package/templates/template-preact-ts/app/popup/App.tsx +31 -0
- package/templates/template-preact-ts/app/popup/index.tsx +5 -0
- package/templates/template-preact-ts/app/sidepanel/App.tsx +5 -0
- package/templates/template-preact-ts/app/sidepanel/index.tsx +5 -0
- package/templates/template-preact-ts/package.json +20 -0
- package/templates/template-preact-ts/public/icons/icon_128.png +0 -0
- package/templates/template-preact-ts/public/welcome.html +20 -0
- package/templates/template-preact-ts/tsconfig.json +19 -0
- package/templates/template-react-js/AGENTS.md +66 -0
- package/templates/template-react-js/addfox.config.js +27 -0
- package/templates/template-react-js/app/background/index.js +8 -0
- package/templates/template-react-js/app/content/index.js +1 -0
- package/templates/template-react-js/app/options/App.jsx +3 -0
- package/templates/template-react-js/app/options/index.jsx +5 -0
- package/templates/template-react-js/app/popup/App.jsx +29 -0
- package/templates/template-react-js/app/popup/index.jsx +5 -0
- package/templates/template-react-js/app/sidepanel/App.jsx +3 -0
- package/templates/template-react-js/app/sidepanel/index.jsx +5 -0
- package/templates/template-react-js/package.json +19 -0
- package/templates/template-react-js/public/icons/icon_128.png +0 -0
- package/templates/template-react-js/public/welcome.html +20 -0
- package/templates/template-react-ts/AGENTS.md +93 -0
- package/templates/template-react-ts/addfox.config.ts +27 -0
- package/templates/template-react-ts/app/background/index.ts +8 -0
- package/templates/template-react-ts/app/content/index.ts +1 -0
- package/templates/template-react-ts/app/options/App.tsx +3 -0
- package/templates/template-react-ts/app/options/index.tsx +5 -0
- package/templates/template-react-ts/app/popup/App.tsx +29 -0
- package/templates/template-react-ts/app/popup/index.tsx +5 -0
- package/templates/template-react-ts/app/sidepanel/App.tsx +3 -0
- package/templates/template-react-ts/app/sidepanel/index.tsx +5 -0
- package/templates/template-react-ts/package.json +23 -0
- package/templates/template-react-ts/public/icons/icon_128.png +0 -0
- package/templates/template-react-ts/public/welcome.html +20 -0
- package/templates/template-react-ts/tsconfig.json +19 -0
- package/templates/template-solid-js/AGENTS.md +54 -0
- package/templates/template-solid-js/addfox.config.js +28 -0
- package/templates/template-solid-js/app/background/index.js +8 -0
- package/templates/template-solid-js/app/content/index.js +1 -0
- package/templates/template-solid-js/app/options/App.jsx +3 -0
- package/templates/template-solid-js/app/options/index.jsx +5 -0
- package/templates/template-solid-js/app/popup/App.jsx +34 -0
- package/templates/template-solid-js/app/popup/index.jsx +5 -0
- package/templates/template-solid-js/app/sidepanel/App.jsx +3 -0
- package/templates/template-solid-js/app/sidepanel/index.jsx +5 -0
- package/templates/template-solid-js/package.json +19 -0
- package/templates/template-solid-js/public/icons/icon_128.png +0 -0
- package/templates/template-solid-js/public/welcome.html +20 -0
- package/templates/template-solid-ts/AGENTS.md +61 -0
- package/templates/template-solid-ts/addfox.config.ts +28 -0
- package/templates/template-solid-ts/app/background/index.ts +8 -0
- package/templates/template-solid-ts/app/content/index.ts +1 -0
- package/templates/template-solid-ts/app/options/App.tsx +3 -0
- package/templates/template-solid-ts/app/options/index.tsx +5 -0
- package/templates/template-solid-ts/app/popup/App.tsx +34 -0
- package/templates/template-solid-ts/app/popup/index.tsx +5 -0
- package/templates/template-solid-ts/app/sidepanel/App.tsx +3 -0
- package/templates/template-solid-ts/app/sidepanel/index.tsx +5 -0
- package/templates/template-solid-ts/package.json +21 -0
- package/templates/template-solid-ts/public/icons/icon_128.png +0 -0
- package/templates/template-solid-ts/public/welcome.html +20 -0
- package/templates/template-solid-ts/tsconfig.json +19 -0
- package/templates/template-svelte-js/AGENTS.md +54 -0
- package/templates/template-svelte-js/addfox.config.js +27 -0
- package/templates/template-svelte-js/app/background/index.js +8 -0
- package/templates/template-svelte-js/app/content/index.js +1 -0
- package/templates/template-svelte-js/app/options/App.svelte +4 -0
- package/templates/template-svelte-js/app/options/index.js +4 -0
- package/templates/template-svelte-js/app/popup/App.svelte +30 -0
- package/templates/template-svelte-js/app/popup/index.js +4 -0
- package/templates/template-svelte-js/app/sidepanel/App.svelte +4 -0
- package/templates/template-svelte-js/app/sidepanel/index.js +4 -0
- package/templates/template-svelte-js/package.json +18 -0
- package/templates/template-svelte-js/public/icons/icon_128.png +0 -0
- package/templates/template-svelte-js/public/welcome.html +20 -0
- package/templates/template-svelte-ts/AGENTS.md +61 -0
- package/templates/template-svelte-ts/addfox.config.ts +27 -0
- package/templates/template-svelte-ts/app/background/index.ts +8 -0
- package/templates/template-svelte-ts/app/content/index.ts +1 -0
- package/templates/template-svelte-ts/app/options/App.svelte +4 -0
- package/templates/template-svelte-ts/app/options/index.ts +4 -0
- package/templates/template-svelte-ts/app/popup/App.svelte +30 -0
- package/templates/template-svelte-ts/app/popup/index.ts +4 -0
- package/templates/template-svelte-ts/app/sidepanel/App.svelte +4 -0
- package/templates/template-svelte-ts/app/sidepanel/index.ts +4 -0
- package/templates/template-svelte-ts/package.json +20 -0
- package/templates/template-svelte-ts/public/icons/icon_128.png +0 -0
- package/templates/template-svelte-ts/public/welcome.html +20 -0
- package/templates/template-svelte-ts/tsconfig.json +19 -0
- package/templates/template-vanilla-js/AGENTS.md +52 -0
- package/templates/template-vanilla-js/addfox.config.js +25 -0
- package/templates/template-vanilla-js/app/background/index.js +8 -0
- package/templates/template-vanilla-js/app/content/index.js +1 -0
- package/templates/template-vanilla-js/app/options/index.js +2 -0
- package/templates/template-vanilla-js/app/popup/index.js +48 -0
- package/templates/template-vanilla-js/app/sidepanel/index.js +2 -0
- package/templates/template-vanilla-js/package.json +13 -0
- package/templates/template-vanilla-js/public/icons/icon_128.png +0 -0
- package/templates/template-vanilla-js/public/welcome.html +20 -0
- package/templates/template-vanilla-ts/AGENTS.md +59 -0
- package/templates/template-vanilla-ts/addfox.config.ts +25 -0
- package/templates/template-vanilla-ts/app/background/index.ts +8 -0
- package/templates/template-vanilla-ts/app/content/index.ts +1 -0
- package/templates/template-vanilla-ts/app/options/index.ts +2 -0
- package/templates/template-vanilla-ts/app/popup/index.ts +48 -0
- package/templates/template-vanilla-ts/app/sidepanel/index.ts +2 -0
- package/templates/template-vanilla-ts/package.json +14 -0
- package/templates/template-vanilla-ts/public/icons/icon_128.png +0 -0
- package/templates/template-vanilla-ts/public/welcome.html +20 -0
- package/templates/template-vanilla-ts/tsconfig.json +17 -0
- package/templates/template-vue-js/AGENTS.md +60 -0
- package/templates/template-vue-js/addfox.config.js +27 -0
- package/templates/template-vue-js/app/background/index.js +8 -0
- package/templates/template-vue-js/app/content/index.js +1 -0
- package/templates/template-vue-js/app/options/App.vue +6 -0
- package/templates/template-vue-js/app/options/index.js +5 -0
- package/templates/template-vue-js/app/popup/App.vue +34 -0
- package/templates/template-vue-js/app/popup/index.js +5 -0
- package/templates/template-vue-js/app/sidepanel/App.vue +6 -0
- package/templates/template-vue-js/app/sidepanel/index.js +5 -0
- package/templates/template-vue-js/package.json +18 -0
- package/templates/template-vue-js/public/icons/icon_128.png +0 -0
- package/templates/template-vue-js/public/welcome.html +20 -0
- package/templates/template-vue-ts/AGENTS.md +65 -0
- package/templates/template-vue-ts/addfox.config.ts +27 -0
- package/templates/template-vue-ts/app/background/index.ts +8 -0
- package/templates/template-vue-ts/app/content/index.ts +1 -0
- package/templates/template-vue-ts/app/options/App.vue +6 -0
- package/templates/template-vue-ts/app/options/index.ts +5 -0
- package/templates/template-vue-ts/app/popup/App.vue +32 -0
- package/templates/template-vue-ts/app/popup/index.ts +5 -0
- package/templates/template-vue-ts/app/sidepanel/App.vue +6 -0
- package/templates/template-vue-ts/app/sidepanel/index.ts +5 -0
- package/templates/template-vue-ts/package.json +20 -0
- package/templates/template-vue-ts/public/icons/icon_128.png +0 -0
- package/templates/template-vue-ts/public/welcome.html +20 -0
- package/templates/template-vue-ts/tsconfig.json +19 -0
package/README.md
CHANGED
|
@@ -15,4 +15,4 @@ Interactive scaffolder: generates a addfox-based extension project from options
|
|
|
15
15
|
|
|
16
16
|
## Templates
|
|
17
17
|
|
|
18
|
-
Scaffold templates
|
|
18
|
+
Scaffold templates ship inside this package under **`templates/`** (e.g. `template-vanilla-ts`, `template-react-ts`). They are included in the published npm tarball; the CLI copies the chosen folder into your new project (no remote download). The template’s **`addfox.config`** (including `manifest`) is **preserved**; the CLI only **merges** Rsbuild Less/Sass plugins when those style engines are selected. If no config file exists, a full fallback config is generated.
|
package/dist/cli.js
CHANGED
|
@@ -1,82 +1,93 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import { dirname, join, relative, resolve } from "node:path";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { dirname, join, relative, resolve as external_node_path_resolve } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { createRequire } from "node:module";
|
|
6
6
|
import { execSync } from "node:child_process";
|
|
7
7
|
import prompts from "prompts";
|
|
8
8
|
import { blue, cyan, dim, gray, green, lightBlue, magenta, red, trueColor, yellow } from "kolorist";
|
|
9
9
|
import minimist from "minimist";
|
|
10
|
-
import {
|
|
10
|
+
import { cp, mkdir } from "node:fs/promises";
|
|
11
11
|
import { detectPackageManager, getExecCommand, getInstallCommand, getRunCommand } from "@addfox/pkg-manager";
|
|
12
|
-
const GITHUB_REPO = "addfox/addfox";
|
|
13
12
|
const TEMPLATE_BASE = "templates";
|
|
14
|
-
function
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
13
|
+
function getPackageRoot() {
|
|
14
|
+
return external_node_path_resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
15
|
+
}
|
|
16
|
+
function getBundledTemplatesDir() {
|
|
17
|
+
return join(getPackageRoot(), TEMPLATE_BASE);
|
|
18
|
+
}
|
|
19
|
+
function hasLocalTemplate(templateName) {
|
|
20
|
+
return existsSync(join(getBundledTemplatesDir(), templateName));
|
|
21
|
+
}
|
|
22
|
+
const SKIP_RELATIVE_SEGMENTS = new Set([
|
|
23
|
+
"node_modules",
|
|
24
|
+
".git",
|
|
25
|
+
".pnpm"
|
|
26
|
+
]);
|
|
27
|
+
function shouldCopyLocalTemplatePath(src, templateRoot) {
|
|
28
|
+
const root = external_node_path_resolve(templateRoot);
|
|
29
|
+
const rel = relative(root, external_node_path_resolve(src));
|
|
30
|
+
if (rel.startsWith("..")) return true;
|
|
31
|
+
for (const part of rel.split(/[/\\]/))if (SKIP_RELATIVE_SEGMENTS.has(part)) return false;
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
async function copyBundledTemplate(templateName, destDir) {
|
|
35
|
+
const templatePath = join(getBundledTemplatesDir(), templateName);
|
|
36
|
+
if (!existsSync(templatePath)) throw new Error(`Bundled template "${templateName}" not found under create-addfox-app. Reinstall the package.`);
|
|
37
|
+
await mkdir(destDir, {
|
|
23
38
|
recursive: true
|
|
24
39
|
});
|
|
25
|
-
|
|
26
|
-
|
|
40
|
+
const resolvedTemplate = external_node_path_resolve(templatePath);
|
|
41
|
+
await cp(resolvedTemplate, destDir, {
|
|
42
|
+
recursive: true,
|
|
43
|
+
filter: (src)=>shouldCopyLocalTemplatePath(src, resolvedTemplate)
|
|
27
44
|
});
|
|
28
|
-
return true;
|
|
29
|
-
}
|
|
30
|
-
function parseTarHeader(buf) {
|
|
31
|
-
if (buf.every((b)=>0 === b)) return null;
|
|
32
|
-
const rawName = buf.subarray(0, 100).toString("utf8").replace(/\0+$/, "");
|
|
33
|
-
const sizeStr = buf.subarray(124, 136).toString("utf8").replace(/\0+$/, "").trim();
|
|
34
|
-
const type = String.fromCharCode(buf[156]);
|
|
35
|
-
const prefix = buf.subarray(345, 500).toString("utf8").replace(/\0+$/, "");
|
|
36
|
-
return {
|
|
37
|
-
name: prefix ? `${prefix}/${rawName}` : rawName,
|
|
38
|
-
size: parseInt(sizeStr, 8) || 0,
|
|
39
|
-
type
|
|
40
|
-
};
|
|
41
45
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
});
|
|
58
|
-
else if ("0" === header.type || "\0" === header.type) {
|
|
59
|
-
mkdirSync(dirname(destPath), {
|
|
60
|
-
recursive: true
|
|
61
|
-
});
|
|
62
|
-
writeFileSync(destPath, tarBuffer.subarray(offset, offset + header.size));
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
offset += dataBlocks;
|
|
67
|
-
}
|
|
46
|
+
const FRAMES = [
|
|
47
|
+
"⠋",
|
|
48
|
+
"⠙",
|
|
49
|
+
"⠹",
|
|
50
|
+
"⠸",
|
|
51
|
+
"⠼",
|
|
52
|
+
"⠴",
|
|
53
|
+
"⠦",
|
|
54
|
+
"⠧",
|
|
55
|
+
"⠇",
|
|
56
|
+
"⠏"
|
|
57
|
+
];
|
|
58
|
+
const INTERVAL_MS = 80;
|
|
59
|
+
function clearSpinnerLine() {
|
|
60
|
+
process.stdout.write("\r\x1b[K");
|
|
68
61
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const response = await fetch(url);
|
|
73
|
-
if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
74
|
-
const compressed = Buffer.from(await response.arrayBuffer());
|
|
75
|
-
const tarBuffer = gunzipSync(compressed);
|
|
76
|
-
mkdirSync(destDir, {
|
|
77
|
-
recursive: true
|
|
62
|
+
function sleep(ms) {
|
|
63
|
+
return new Promise((resolve)=>{
|
|
64
|
+
setTimeout(resolve, ms);
|
|
78
65
|
});
|
|
79
|
-
|
|
66
|
+
}
|
|
67
|
+
async function runWithTemplateSpinner(label, fn, options) {
|
|
68
|
+
const minVisibleMs = options?.minVisibleMs ?? 0;
|
|
69
|
+
if (!process.stdout.isTTY) {
|
|
70
|
+
console.log(yellow(`\n ${label}\n`));
|
|
71
|
+
return fn();
|
|
72
|
+
}
|
|
73
|
+
let frameIndex = 0;
|
|
74
|
+
process.stdout.write("\n");
|
|
75
|
+
const timer = setInterval(()=>{
|
|
76
|
+
const frame = FRAMES[frameIndex % FRAMES.length];
|
|
77
|
+
frameIndex += 1;
|
|
78
|
+
process.stdout.write(`\r ${yellow(frame)} ${label}`);
|
|
79
|
+
}, INTERVAL_MS);
|
|
80
|
+
const startedAt = Date.now();
|
|
81
|
+
try {
|
|
82
|
+
const result = await fn();
|
|
83
|
+
const elapsed = Date.now() - startedAt;
|
|
84
|
+
if (elapsed < minVisibleMs) await sleep(minVisibleMs - elapsed);
|
|
85
|
+
return result;
|
|
86
|
+
} finally{
|
|
87
|
+
clearInterval(timer);
|
|
88
|
+
clearSpinnerLine();
|
|
89
|
+
process.stdout.write("\n");
|
|
90
|
+
}
|
|
80
91
|
}
|
|
81
92
|
const STYLE_ENGINES = [
|
|
82
93
|
{
|
|
@@ -233,7 +244,7 @@ function getStylePlugin(engine) {
|
|
|
233
244
|
};
|
|
234
245
|
return null;
|
|
235
246
|
}
|
|
236
|
-
const MINIMAL_MANIFEST = ' name: "My Extension",\n version: "1.0.0",\n manifest_version: 3,\n description: "Browser extension built with addfox",\n permissions: ["
|
|
247
|
+
const MINIMAL_MANIFEST = ' name: "My Extension",\n version: "1.0.0",\n manifest_version: 3,\n description: "Browser extension built with addfox",\n permissions: ["activeTab"],\n icons: {\n "16": "icons/icon_128.png",\n "48": "icons/icon_128.png",\n "128": "icons/icon_128.png",\n },\n action: {\n default_icon: {\n "16": "icons/icon_128.png",\n "48": "icons/icon_128.png",\n "128": "icons/icon_128.png",\n },\n },';
|
|
237
248
|
function generateAddfoxConfig(framework, _language, styleEngine) {
|
|
238
249
|
const importLines = [];
|
|
239
250
|
const fwImport = getFrameworkPluginImport(framework);
|
|
@@ -261,6 +272,67 @@ function generateAddfoxConfig(framework, _language, styleEngine) {
|
|
|
261
272
|
];
|
|
262
273
|
return parts.filter(Boolean).join("\n");
|
|
263
274
|
}
|
|
275
|
+
const MANIFEST_LINE = " manifest: { chromium: manifest, firefox: { ...manifest } },";
|
|
276
|
+
function findMatchingArrayClose(source, openBracketIdx) {
|
|
277
|
+
if ("[" !== source[openBracketIdx]) return -1;
|
|
278
|
+
let depth = 0;
|
|
279
|
+
for(let i = openBracketIdx; i < source.length; i++){
|
|
280
|
+
const c = source[i];
|
|
281
|
+
if ("[" === c) depth++;
|
|
282
|
+
else if ("]" === c) {
|
|
283
|
+
depth--;
|
|
284
|
+
if (0 === depth) return i;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return -1;
|
|
288
|
+
}
|
|
289
|
+
function hasImportFromModule(source, modulePath) {
|
|
290
|
+
return source.includes(`from "${modulePath}"`) || source.includes(`from '${modulePath}'`) || source.includes(`from \`${modulePath}\``);
|
|
291
|
+
}
|
|
292
|
+
function insertImportAfterLastImport(source, importLine) {
|
|
293
|
+
const trimmed = importLine.trim();
|
|
294
|
+
const moduleMatch = /from\s+["']([^"']+)["']/.exec(trimmed);
|
|
295
|
+
if (moduleMatch && hasImportFromModule(source, moduleMatch[1])) return source;
|
|
296
|
+
const lines = source.split("\n");
|
|
297
|
+
let lastImportIdx = -1;
|
|
298
|
+
for(let i = 0; i < lines.length; i++)if (lines[i].trim().startsWith("import ")) lastImportIdx = i;
|
|
299
|
+
if (-1 === lastImportIdx) return `${importLine}\n${source}`;
|
|
300
|
+
const next = [
|
|
301
|
+
...lines
|
|
302
|
+
];
|
|
303
|
+
next.splice(lastImportIdx + 1, 0, importLine);
|
|
304
|
+
return next.join("\n");
|
|
305
|
+
}
|
|
306
|
+
function pluginCallPresent(inner, call) {
|
|
307
|
+
const fn = call.replace(/\(\)$/, "");
|
|
308
|
+
return inner.includes(`${fn}(`);
|
|
309
|
+
}
|
|
310
|
+
function mergeCallsIntoPluginsArray(source, calls) {
|
|
311
|
+
const pluginsIdx = source.indexOf("plugins:");
|
|
312
|
+
if (-1 === pluginsIdx) return addPluginsBlockAfterManifest(source, calls);
|
|
313
|
+
const bracketStart = source.indexOf("[", pluginsIdx);
|
|
314
|
+
if (-1 === bracketStart) return source;
|
|
315
|
+
const bracketEnd = findMatchingArrayClose(source, bracketStart);
|
|
316
|
+
if (-1 === bracketEnd) return source;
|
|
317
|
+
let inner = source.slice(bracketStart + 1, bracketEnd).trim();
|
|
318
|
+
for (const c of calls)if (!pluginCallPresent(inner, c)) inner = inner ? `${inner}, ${c}` : c;
|
|
319
|
+
return source.slice(0, bracketStart + 1) + inner + source.slice(bracketEnd);
|
|
320
|
+
}
|
|
321
|
+
function addPluginsBlockAfterManifest(source, calls) {
|
|
322
|
+
const idx = source.indexOf(MANIFEST_LINE);
|
|
323
|
+
if (-1 === idx) return source;
|
|
324
|
+
const insertPos = idx + MANIFEST_LINE.length;
|
|
325
|
+
return `${source.slice(0, insertPos)}\n plugins: [${calls.join(", ")}],${source.slice(insertPos)}`;
|
|
326
|
+
}
|
|
327
|
+
function mergeScaffoldIntoAddfoxConfig(source, styleEngine) {
|
|
328
|
+
const st = getStylePlugin(styleEngine);
|
|
329
|
+
if (!st) return source;
|
|
330
|
+
let out = insertImportAfterLastImport(source, st.importLine);
|
|
331
|
+
out = mergeCallsIntoPluginsArray(out, [
|
|
332
|
+
st.call
|
|
333
|
+
]);
|
|
334
|
+
return out;
|
|
335
|
+
}
|
|
264
336
|
const UTF8_BOM = "\uFEFF";
|
|
265
337
|
function stripUtf8Bom(text) {
|
|
266
338
|
return text.startsWith(UTF8_BOM) ? text.slice(UTF8_BOM.length) : text;
|
|
@@ -346,10 +418,10 @@ function generateRstestConfig(language, testKinds) {
|
|
|
346
418
|
}
|
|
347
419
|
function writeUnitSample(root, language) {
|
|
348
420
|
const ext = getTestExt(language);
|
|
349
|
-
mkdirSync(
|
|
421
|
+
mkdirSync(external_node_path_resolve(root, "__tests__"), {
|
|
350
422
|
recursive: true
|
|
351
423
|
});
|
|
352
|
-
const path =
|
|
424
|
+
const path = external_node_path_resolve(root, "__tests__", `example.test.${ext}`);
|
|
353
425
|
const body = "ts" === language ? `import { describe, expect, it } from "@rstest/core";
|
|
354
426
|
|
|
355
427
|
describe("example unit tests", () => {
|
|
@@ -369,11 +441,11 @@ describe("example unit tests", () => {
|
|
|
369
441
|
}
|
|
370
442
|
function writeE2eSample(root, language) {
|
|
371
443
|
const ext = getTestExt(language);
|
|
372
|
-
const dir =
|
|
444
|
+
const dir = external_node_path_resolve(root, "__tests__", "e2e");
|
|
373
445
|
mkdirSync(dir, {
|
|
374
446
|
recursive: true
|
|
375
447
|
});
|
|
376
|
-
const path =
|
|
448
|
+
const path = external_node_path_resolve(dir, `example.browser.test.${ext}`);
|
|
377
449
|
const body = `import { describe, expect, it } from "@rstest/core";
|
|
378
450
|
import { page } from "@rstest/browser";
|
|
379
451
|
|
|
@@ -410,7 +482,7 @@ function mergeScripts(pkg, testKinds) {
|
|
|
410
482
|
function applyTestAndReportSetup(root, language, selection) {
|
|
411
483
|
const { testKinds, installRsdoctor } = selection;
|
|
412
484
|
if (0 === testKinds.length && !installRsdoctor) return;
|
|
413
|
-
const pkgPath =
|
|
485
|
+
const pkgPath = external_node_path_resolve(root, "package.json");
|
|
414
486
|
const pkg = readJsonFile(pkgPath);
|
|
415
487
|
mergeDevDeps(pkg, testKinds, installRsdoctor);
|
|
416
488
|
mergeScripts(pkg, testKinds);
|
|
@@ -418,22 +490,12 @@ function applyTestAndReportSetup(root, language, selection) {
|
|
|
418
490
|
if (testKinds.length > 0) {
|
|
419
491
|
const configName = "ts" === language ? "rstest.config.ts" : "rstest.config.js";
|
|
420
492
|
const content = generateRstestConfig(language, testKinds);
|
|
421
|
-
writeFileSync(
|
|
493
|
+
writeFileSync(external_node_path_resolve(root, configName), content, "utf-8");
|
|
422
494
|
if (testKinds.includes("unit")) writeUnitSample(root, language);
|
|
423
495
|
if (testKinds.includes("e2e")) writeE2eSample(root, language);
|
|
424
496
|
}
|
|
425
497
|
}
|
|
426
498
|
const RESET = "\x1b[0m";
|
|
427
|
-
const ORANGE = [
|
|
428
|
-
255,
|
|
429
|
-
118,
|
|
430
|
-
38
|
|
431
|
-
];
|
|
432
|
-
const PINK = [
|
|
433
|
-
255,
|
|
434
|
-
92,
|
|
435
|
-
186
|
|
436
|
-
];
|
|
437
499
|
const LOGO_LINES = [
|
|
438
500
|
" █████╗ ██████╗ ██████╗ ███████╗ ██████╗ ██╗ ██╗",
|
|
439
501
|
"██╔══██╗██╔══██╗██╔══██╗██╔════╝██╔═══██╗╚██╗██╔╝",
|
|
@@ -442,13 +504,15 @@ const LOGO_LINES = [
|
|
|
442
504
|
"██║ ██║██████╔╝██████╔╝██║ ╚██████╔╝██╔╝ ██╗",
|
|
443
505
|
"╚═╝ ╚═╝╚═════╝ ╚═════╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝"
|
|
444
506
|
];
|
|
445
|
-
|
|
446
|
-
|
|
507
|
+
const GRADIENT_START_256 = 208;
|
|
508
|
+
const GRADIENT_END_256 = 213;
|
|
509
|
+
function shouldEmitAnsiColor() {
|
|
510
|
+
return !process.env.NO_COLOR;
|
|
447
511
|
}
|
|
448
|
-
function
|
|
449
|
-
return `\x1b[38;
|
|
512
|
+
function ansi256Fg(code) {
|
|
513
|
+
return `\x1b[38;5;${code}m`;
|
|
450
514
|
}
|
|
451
|
-
function
|
|
515
|
+
function colorizeLineAnsi256(line) {
|
|
452
516
|
const len = line.length;
|
|
453
517
|
if (0 === len) return "";
|
|
454
518
|
let out = "";
|
|
@@ -459,16 +523,20 @@ function colorizeLine(line) {
|
|
|
459
523
|
continue;
|
|
460
524
|
}
|
|
461
525
|
const t = len <= 1 ? 0 : i / (len - 1);
|
|
462
|
-
const
|
|
463
|
-
|
|
464
|
-
const b = lerpChannel(ORANGE[2], PINK[2], t);
|
|
465
|
-
out += rgbFg(r, g, b) + ch;
|
|
526
|
+
const code = Math.round(GRADIENT_START_256 + t * (GRADIENT_END_256 - GRADIENT_START_256));
|
|
527
|
+
out += ansi256Fg(code) + ch;
|
|
466
528
|
}
|
|
467
529
|
return out + RESET;
|
|
468
530
|
}
|
|
531
|
+
function printPlainLogo() {
|
|
532
|
+
console.log("");
|
|
533
|
+
for (const line of LOGO_LINES)console.log(line);
|
|
534
|
+
console.log("");
|
|
535
|
+
}
|
|
469
536
|
function printAddfoxLogo() {
|
|
537
|
+
if (!shouldEmitAnsiColor()) return void printPlainLogo();
|
|
470
538
|
console.log("");
|
|
471
|
-
for (const line of LOGO_LINES)console.log(
|
|
539
|
+
for (const line of LOGO_LINES)console.log(colorizeLineAnsi256(line));
|
|
472
540
|
console.log("");
|
|
473
541
|
}
|
|
474
542
|
const SKIP_STYLE_ENTRIES = new Set([
|
|
@@ -671,7 +739,7 @@ function getStyleEngineChoicesColored() {
|
|
|
671
739
|
}
|
|
672
740
|
function getVersion() {
|
|
673
741
|
try {
|
|
674
|
-
const pkgPath =
|
|
742
|
+
const pkgPath = external_node_path_resolve(dirname(fileURLToPath(import.meta.url)), "..", "package.json");
|
|
675
743
|
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
676
744
|
return pkg.version ?? "0.0.0";
|
|
677
745
|
} catch {
|
|
@@ -841,7 +909,7 @@ function resolveTestSelectionFromArgv(argv) {
|
|
|
841
909
|
};
|
|
842
910
|
}
|
|
843
911
|
function updatePackageName(destDir, projectName) {
|
|
844
|
-
const pkgPath =
|
|
912
|
+
const pkgPath = external_node_path_resolve(destDir, "package.json");
|
|
845
913
|
if (!existsSync(pkgPath)) return;
|
|
846
914
|
const pkg = readJsonFile(pkgPath);
|
|
847
915
|
pkg.name = projectName.replace(/\s+/g, "-").toLowerCase();
|
|
@@ -856,10 +924,14 @@ async function main() {
|
|
|
856
924
|
const cliLanguage = argv.language;
|
|
857
925
|
printAddfoxLogo();
|
|
858
926
|
console.log(blue("\n Create Addfox App\n"));
|
|
859
|
-
const root =
|
|
927
|
+
const root = external_node_path_resolve(process.cwd(), targetDir);
|
|
860
928
|
if (existsSync(root)) {
|
|
861
929
|
const confirmed = await confirmOverwrite(targetDir);
|
|
862
930
|
if (!confirmed) process.exit(0);
|
|
931
|
+
rmSync(root, {
|
|
932
|
+
recursive: true,
|
|
933
|
+
force: true
|
|
934
|
+
});
|
|
863
935
|
}
|
|
864
936
|
const options = cliFramework && cliLanguage ? {
|
|
865
937
|
framework: cliFramework,
|
|
@@ -875,11 +947,20 @@ async function main() {
|
|
|
875
947
|
if (!testSelection) process.exit(0);
|
|
876
948
|
const pm = options.packageManager;
|
|
877
949
|
const templateName = getTemplateName(options.framework, options.language);
|
|
878
|
-
|
|
950
|
+
const templateReady = hasLocalTemplate(templateName);
|
|
951
|
+
const templateLabel = "Copying template...";
|
|
952
|
+
const TEMPLATE_SPINNER_MIN_MS = 800;
|
|
879
953
|
try {
|
|
880
|
-
|
|
954
|
+
await runWithTemplateSpinner(templateLabel, async ()=>{
|
|
955
|
+
await new Promise((resolveSpinner)=>{
|
|
956
|
+
setImmediate(resolveSpinner);
|
|
957
|
+
});
|
|
958
|
+
await copyBundledTemplate(templateName, root);
|
|
959
|
+
}, templateReady ? {
|
|
960
|
+
minVisibleMs: TEMPLATE_SPINNER_MIN_MS
|
|
961
|
+
} : void 0);
|
|
881
962
|
} catch (err) {
|
|
882
|
-
console.error(red(`\n Failed to
|
|
963
|
+
console.error(red(`\n Failed to copy template: ${err.message}\n`));
|
|
883
964
|
process.exit(1);
|
|
884
965
|
}
|
|
885
966
|
const useAllEntries = options.entries.includes("__all__");
|
|
@@ -889,8 +970,12 @@ async function main() {
|
|
|
889
970
|
] : options.entries.filter((e)=>"__all__" !== e && existingDirs.includes(e));
|
|
890
971
|
if (!useAllEntries && effectiveEntries.length > 0) filterAppEntries(root, effectiveEntries);
|
|
891
972
|
const configExt = "ts" === options.language ? "ts" : "js";
|
|
892
|
-
const configPath =
|
|
893
|
-
|
|
973
|
+
const configPath = external_node_path_resolve(root, `addfox.config.${configExt}`);
|
|
974
|
+
let configContent;
|
|
975
|
+
if (existsSync(configPath)) {
|
|
976
|
+
const existing = readFileSync(configPath, "utf-8");
|
|
977
|
+
configContent = existing.trim().length > 0 && existing.includes("defineConfig") ? mergeScaffoldIntoAddfoxConfig(existing, options.styleEngine) : generateAddfoxConfig(options.framework, options.language, options.styleEngine);
|
|
978
|
+
} else configContent = generateAddfoxConfig(options.framework, options.language, options.styleEngine);
|
|
894
979
|
writeFileSync(configPath, configContent, "utf-8");
|
|
895
980
|
updatePackageName(root, targetDir);
|
|
896
981
|
applyStyleEngine(root, options.framework, options.language, options.styleEngine);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-addfox-app",
|
|
3
|
-
"version": "0.1.1
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Interactive scaffolder for addfox extension projects",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
},
|
|
10
10
|
"files": [
|
|
11
11
|
"dist",
|
|
12
|
+
"templates",
|
|
12
13
|
"LICENSE"
|
|
13
14
|
],
|
|
14
15
|
"dependencies": {
|
|
@@ -16,12 +17,15 @@
|
|
|
16
17
|
"prompts": "^2.4.2",
|
|
17
18
|
"kolorist": "^1.8.0",
|
|
18
19
|
"minimist": "^1.2.8",
|
|
19
|
-
"@addfox/pkg-manager": "0.1.1
|
|
20
|
+
"@addfox/pkg-manager": "0.1.1"
|
|
21
|
+
},
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=18.0.0"
|
|
20
24
|
},
|
|
21
25
|
"devDependencies": {
|
|
22
|
-
"@rslib/core": "^0.
|
|
23
|
-
"@rstest/core": "^0.9.
|
|
24
|
-
"@rstest/coverage-istanbul": "^0.3.
|
|
26
|
+
"@rslib/core": "^0.21.3",
|
|
27
|
+
"@rstest/core": "^0.9.9",
|
|
28
|
+
"@rstest/coverage-istanbul": "^0.3.2",
|
|
25
29
|
"@types/node": "^20.0.0",
|
|
26
30
|
"typescript": "^5.0.0"
|
|
27
31
|
},
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
# Scaffold templates
|
|
2
|
+
|
|
3
|
+
Templates used by **create-addfox-app**. Each subdirectory (e.g. `template-vanilla-ts`, `template-react-ts`) is a full project skeleton. They live under this package and ship with the published npm tarball; the CLI copies the chosen folder into the new project directory.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# AI Agent Guide - Preact Addfox Extension
|
|
2
|
+
|
|
3
|
+
> **Priority**: Follow `.agents/skills/` first, then this guide, then [official docs](https://addfox.dev)
|
|
4
|
+
|
|
5
|
+
## Project Overview
|
|
6
|
+
|
|
7
|
+
Preact browser extension using Addfox build tool. Uses `@rsbuild/plugin-preact` for JSX support.
|
|
8
|
+
|
|
9
|
+
## Key Configuration Fields (addfox.config.js)
|
|
10
|
+
|
|
11
|
+
### Required Fields
|
|
12
|
+
|
|
13
|
+
- **`manifest`** - Extension manifest.json content
|
|
14
|
+
- Supports `chromium` and `firefox` variants
|
|
15
|
+
- Addfox auto-injects entry paths
|
|
16
|
+
|
|
17
|
+
- **`plugins`** - Rsbuild plugins array
|
|
18
|
+
- Must include `pluginPreact()` for JSX support
|
|
19
|
+
|
|
20
|
+
### Optional Fields (use when needed)
|
|
21
|
+
|
|
22
|
+
- **`appDir`** - Source directory (default: "app")
|
|
23
|
+
- **`outDir`** - Build output directory
|
|
24
|
+
- **`entry`** - Manual entry mapping
|
|
25
|
+
- **`hotReload`** - Dev server settings
|
|
26
|
+
- **`debug`** - Enable debug logging
|
|
27
|
+
- **`zip`** - Output zip configuration
|
|
28
|
+
- **`rsbuild`** - Override Rsbuild configuration
|
|
29
|
+
|
|
30
|
+
## Entry Structure
|
|
31
|
+
|
|
32
|
+
Entries auto-discovered from `appDir` subdirectories. Each needs `index.jsx` as entry.
|
|
33
|
+
|
|
34
|
+
## Framework-Specific Notes
|
|
35
|
+
|
|
36
|
+
### Preact
|
|
37
|
+
|
|
38
|
+
- JSX-based (`.jsx` files)
|
|
39
|
+
- React-compatible API with smaller bundle size
|
|
40
|
+
|
|
41
|
+
### Browser Extension APIs
|
|
42
|
+
|
|
43
|
+
- Install `webextension-polyfill` for cross-browser compatibility
|
|
44
|
+
|
|
45
|
+
### Styling
|
|
46
|
+
|
|
47
|
+
- Supports CSS Modules
|
|
48
|
+
- Content scripts: use `@addfox/utils` with Shadow DOM
|
|
49
|
+
|
|
50
|
+
## Resources
|
|
51
|
+
|
|
52
|
+
- Addfox docs: https://addfox.dev
|
|
53
|
+
- Rsbuild Preact plugin: https://rsbuild.dev/plugins/list/plugin-preact
|
|
54
|
+
- Preact docs: https://preactjs.com
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { defineConfig } from "addfox";
|
|
2
|
+
import { pluginPreact } from "@rsbuild/plugin-preact";
|
|
3
|
+
|
|
4
|
+
const manifest = {
|
|
5
|
+
name: "My Extension",
|
|
6
|
+
version: "1.0.0",
|
|
7
|
+
manifest_version: 3,
|
|
8
|
+
description: "Browser extension built with addfox",
|
|
9
|
+
permissions: ["activeTab"],
|
|
10
|
+
icons: {
|
|
11
|
+
"16": "icons/icon_128.png",
|
|
12
|
+
"48": "icons/icon_128.png",
|
|
13
|
+
"128": "icons/icon_128.png",
|
|
14
|
+
},
|
|
15
|
+
action: {
|
|
16
|
+
default_icon: {
|
|
17
|
+
"16": "icons/icon_128.png",
|
|
18
|
+
"48": "icons/icon_128.png",
|
|
19
|
+
"128": "icons/icon_128.png",
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export default defineConfig({
|
|
25
|
+
manifest: { chromium: manifest, firefox: { ...manifest } },
|
|
26
|
+
plugins: [pluginPreact()],
|
|
27
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
function openWelcomePage() {
|
|
2
|
+
chrome.tabs.create({ url: chrome.runtime.getURL("welcome.html") });
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
chrome.runtime.onInstalled.addListener((details) => {
|
|
6
|
+
if (details.reason === "install" || details.reason === "update") openWelcomePage();
|
|
7
|
+
console.log("Extension installed");
|
|
8
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
console.log("Content script loaded");
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { h } from "preact";
|
|
2
|
+
|
|
3
|
+
export default function App() {
|
|
4
|
+
const popupStyle = {
|
|
5
|
+
width: 320,
|
|
6
|
+
minHeight: 200,
|
|
7
|
+
padding: 16,
|
|
8
|
+
boxSizing: "border-box",
|
|
9
|
+
display: "flex",
|
|
10
|
+
flexDirection: "column",
|
|
11
|
+
alignItems: "center",
|
|
12
|
+
justifyContent: "center",
|
|
13
|
+
gap: 12,
|
|
14
|
+
fontFamily: 'system-ui, -apple-system, "Segoe UI", sans-serif',
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<div style={popupStyle}>
|
|
19
|
+
<img src="/icons/icon_128.png" alt="" width={64} height={64} />
|
|
20
|
+
<div style={{ fontSize: 18, fontWeight: 600 }}>AddFox Preact</div>
|
|
21
|
+
<a
|
|
22
|
+
href="https://addfox.dev/"
|
|
23
|
+
target="_blank"
|
|
24
|
+
rel="noopener noreferrer"
|
|
25
|
+
style={{ fontSize: 13, color: "#2563eb" }}
|
|
26
|
+
>
|
|
27
|
+
addfox.dev
|
|
28
|
+
</a>
|
|
29
|
+
</div>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "my-extension",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "addfox dev",
|
|
8
|
+
"build": "addfox build"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"@addfox/utils": "^0.1.1",
|
|
12
|
+
"preact": "^10.29.0"
|
|
13
|
+
},
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"addfox": "^0.1.1",
|
|
16
|
+
"@rsbuild/plugin-preact": "^1.7.2"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
Binary file
|