create-addfox-app 0.1.1-beta.7 → 0.2.0
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 +18 -18
- package/dist/cli.js +259 -102
- package/package.json +9 -8
- 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
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
<p align="center">
|
|
2
|
-
<img width="200" src="https://raw.githubusercontent.com/addfox/addfox/main/addfox.png" alt="Addfox">
|
|
3
|
-
</p>
|
|
4
|
-
|
|
5
|
-
# create-addfox-app
|
|
6
|
-
|
|
7
|
-
[中文](README-zh_CN.md) | English
|
|
8
|
-
|
|
9
|
-
---
|
|
10
|
-
|
|
11
|
-
Interactive scaffolder: generates a addfox-based extension project from options (template, package manager, entries, skills).
|
|
12
|
-
|
|
13
|
-
- Commands: `create-addfox-app` or `pnpm create addfox-app` (and npm/yarn/bun equivalents)
|
|
14
|
-
- Flow: (1) select framework (vanilla / vue / react / preact / svelte / solid), (2) language (TypeScript / JavaScript), (3) package manager (pnpm / npm / yarn / bun), (4) entries to include (multi-select), (5) whether to install addfox skills (yes/no). Output to cwd or a given directory. Generated project uses **addfox.config.ts** or **addfox.config.js** with minimal manifest (entry discovery; no built-in entry paths in manifest).
|
|
15
|
-
|
|
16
|
-
## Templates
|
|
17
|
-
|
|
18
|
-
Scaffold templates
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img width="200" src="https://raw.githubusercontent.com/addfox/addfox/main/addfox.png" alt="Addfox">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
# create-addfox-app
|
|
6
|
+
|
|
7
|
+
[中文](README-zh_CN.md) | English
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
Interactive scaffolder: generates a addfox-based extension project from options (template, package manager, entries, skills).
|
|
12
|
+
|
|
13
|
+
- Commands: `create-addfox-app` or `pnpm create addfox-app` (and npm/yarn/bun equivalents)
|
|
14
|
+
- Flow: (1) select framework (vanilla / vue / react / preact / svelte / solid), (2) language (TypeScript / JavaScript), (3) package manager (pnpm / npm / yarn / bun), (4) entries to include (multi-select), (5) whether to install addfox skills (yes/no). Output to cwd or a given directory. Generated project uses **addfox.config.ts** or **addfox.config.js** with minimal manifest (entry discovery; no built-in entry paths in manifest).
|
|
15
|
+
|
|
16
|
+
## Templates
|
|
17
|
+
|
|
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,165 @@
|
|
|
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
|
-
import {
|
|
9
|
-
import minimist from "minimist";
|
|
10
|
-
import { gunzipSync } from "node:zlib";
|
|
8
|
+
import { cp, mkdir } from "node:fs/promises";
|
|
11
9
|
import { detectPackageManager, getExecCommand, getInstallCommand, getRunCommand } from "@addfox/pkg-manager";
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const fromDist = resolve(dirname(fileURLToPath(import.meta.url)), "..", "..", "..");
|
|
16
|
-
return fromDist;
|
|
17
|
-
}
|
|
18
|
-
function tryLocalTemplates(templateName, destDir) {
|
|
19
|
-
const repoRoot = getRepoRoot();
|
|
20
|
-
const templatePath = join(repoRoot, TEMPLATE_BASE, templateName);
|
|
21
|
-
if (!existsSync(templatePath)) return false;
|
|
22
|
-
mkdirSync(destDir, {
|
|
23
|
-
recursive: true
|
|
24
|
-
});
|
|
25
|
-
cpSync(templatePath, destDir, {
|
|
26
|
-
recursive: true
|
|
27
|
-
});
|
|
28
|
-
return true;
|
|
10
|
+
const RESET = "\x1b[0m";
|
|
11
|
+
function color(code) {
|
|
12
|
+
return (s)=>`\x1b[${code}m${s}${RESET}`;
|
|
29
13
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
14
|
+
color(30);
|
|
15
|
+
const red = color(31);
|
|
16
|
+
const green = color(32);
|
|
17
|
+
const yellow = color(33);
|
|
18
|
+
const blue = color(34);
|
|
19
|
+
const magenta = color(35);
|
|
20
|
+
const cyan = color(36);
|
|
21
|
+
color(37);
|
|
22
|
+
const gray = color(90);
|
|
23
|
+
color(91);
|
|
24
|
+
color(92);
|
|
25
|
+
color(93);
|
|
26
|
+
const lightBlue = color(94);
|
|
27
|
+
color(95);
|
|
28
|
+
color(96);
|
|
29
|
+
color(97);
|
|
30
|
+
const dim = color(2);
|
|
31
|
+
function trueColor(r, g, b) {
|
|
32
|
+
return (s)=>`\x1b[38;2;${r};${g};${b}m${s}${RESET}`;
|
|
41
33
|
}
|
|
42
|
-
function
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
34
|
+
function parseArgv(argv) {
|
|
35
|
+
const result = {
|
|
36
|
+
_: []
|
|
37
|
+
};
|
|
38
|
+
for(let i = 0; i < argv.length; i++){
|
|
39
|
+
const arg = argv[i];
|
|
40
|
+
if ("--" === arg) {
|
|
41
|
+
result._.push(...argv.slice(i + 1));
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
if (arg.startsWith("--")) {
|
|
45
|
+
const eqIdx = arg.indexOf("=");
|
|
46
|
+
let key;
|
|
47
|
+
let value;
|
|
48
|
+
if (eqIdx >= 0) {
|
|
49
|
+
key = arg.slice(2, eqIdx);
|
|
50
|
+
value = arg.slice(eqIdx + 1);
|
|
51
|
+
} else {
|
|
52
|
+
key = arg.slice(2);
|
|
53
|
+
const next = argv[i + 1];
|
|
54
|
+
if (null == next || next.startsWith("-")) value = true;
|
|
55
|
+
else {
|
|
56
|
+
value = next;
|
|
57
|
+
i++;
|
|
63
58
|
}
|
|
64
59
|
}
|
|
60
|
+
const num = Number(value);
|
|
61
|
+
if (!isNaN(num) && String(value) === String(num)) value = num;
|
|
62
|
+
result[key] = value;
|
|
63
|
+
continue;
|
|
65
64
|
}
|
|
66
|
-
|
|
65
|
+
if (arg.startsWith("-") && arg.length > 1) {
|
|
66
|
+
const chars = arg.slice(1).split("");
|
|
67
|
+
for(let j = 0; j < chars.length; j++){
|
|
68
|
+
const key = chars[j];
|
|
69
|
+
const next = argv[i + 1];
|
|
70
|
+
if (j !== chars.length - 1 || null == next || next.startsWith("-")) result[key] = true;
|
|
71
|
+
else {
|
|
72
|
+
const num = Number(next);
|
|
73
|
+
result[key] = isNaN(num) || String(next) !== String(num) ? next : num;
|
|
74
|
+
i++;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
const num = Number(arg);
|
|
80
|
+
result._.push(isNaN(num) || String(arg) !== String(num) ? arg : num);
|
|
67
81
|
}
|
|
82
|
+
return result;
|
|
83
|
+
}
|
|
84
|
+
const TEMPLATE_BASE = "templates";
|
|
85
|
+
function getPackageRoot() {
|
|
86
|
+
return external_node_path_resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
68
87
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
88
|
+
function getBundledTemplatesDir() {
|
|
89
|
+
return join(getPackageRoot(), TEMPLATE_BASE);
|
|
90
|
+
}
|
|
91
|
+
function hasLocalTemplate(templateName) {
|
|
92
|
+
return existsSync(join(getBundledTemplatesDir(), templateName));
|
|
93
|
+
}
|
|
94
|
+
const SKIP_RELATIVE_SEGMENTS = new Set([
|
|
95
|
+
"node_modules",
|
|
96
|
+
".git",
|
|
97
|
+
".pnpm"
|
|
98
|
+
]);
|
|
99
|
+
function shouldCopyLocalTemplatePath(src, templateRoot) {
|
|
100
|
+
const root = external_node_path_resolve(templateRoot);
|
|
101
|
+
const rel = relative(root, external_node_path_resolve(src));
|
|
102
|
+
if (rel.startsWith("..")) return true;
|
|
103
|
+
for (const part of rel.split(/[/\\]/))if (SKIP_RELATIVE_SEGMENTS.has(part)) return false;
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
async function copyBundledTemplate(templateName, destDir) {
|
|
107
|
+
const templatePath = join(getBundledTemplatesDir(), templateName);
|
|
108
|
+
if (!existsSync(templatePath)) throw new Error(`Bundled template "${templateName}" not found under create-addfox-app. Reinstall the package.`);
|
|
109
|
+
await mkdir(destDir, {
|
|
77
110
|
recursive: true
|
|
78
111
|
});
|
|
79
|
-
|
|
112
|
+
const resolvedTemplate = external_node_path_resolve(templatePath);
|
|
113
|
+
await cp(resolvedTemplate, destDir, {
|
|
114
|
+
recursive: true,
|
|
115
|
+
filter: (src)=>shouldCopyLocalTemplatePath(src, resolvedTemplate)
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
const FRAMES = [
|
|
119
|
+
"⠋",
|
|
120
|
+
"⠙",
|
|
121
|
+
"⠹",
|
|
122
|
+
"⠸",
|
|
123
|
+
"⠼",
|
|
124
|
+
"⠴",
|
|
125
|
+
"⠦",
|
|
126
|
+
"⠧",
|
|
127
|
+
"⠇",
|
|
128
|
+
"⠏"
|
|
129
|
+
];
|
|
130
|
+
const INTERVAL_MS = 80;
|
|
131
|
+
function clearSpinnerLine() {
|
|
132
|
+
process.stdout.write("\r\x1b[K");
|
|
133
|
+
}
|
|
134
|
+
function sleep(ms) {
|
|
135
|
+
return new Promise((resolve)=>{
|
|
136
|
+
setTimeout(resolve, ms);
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
async function runWithTemplateSpinner(label, fn, options) {
|
|
140
|
+
const minVisibleMs = options?.minVisibleMs ?? 0;
|
|
141
|
+
if (!process.stdout.isTTY) {
|
|
142
|
+
console.log(yellow(`\n ${label}\n`));
|
|
143
|
+
return fn();
|
|
144
|
+
}
|
|
145
|
+
let frameIndex = 0;
|
|
146
|
+
process.stdout.write("\n");
|
|
147
|
+
const timer = setInterval(()=>{
|
|
148
|
+
const frame = FRAMES[frameIndex % FRAMES.length];
|
|
149
|
+
frameIndex += 1;
|
|
150
|
+
process.stdout.write(`\r ${yellow(frame)} ${label}`);
|
|
151
|
+
}, INTERVAL_MS);
|
|
152
|
+
const startedAt = Date.now();
|
|
153
|
+
try {
|
|
154
|
+
const result = await fn();
|
|
155
|
+
const elapsed = Date.now() - startedAt;
|
|
156
|
+
if (elapsed < minVisibleMs) await sleep(minVisibleMs - elapsed);
|
|
157
|
+
return result;
|
|
158
|
+
} finally{
|
|
159
|
+
clearInterval(timer);
|
|
160
|
+
clearSpinnerLine();
|
|
161
|
+
process.stdout.write("\n");
|
|
162
|
+
}
|
|
80
163
|
}
|
|
81
164
|
const STYLE_ENGINES = [
|
|
82
165
|
{
|
|
@@ -233,7 +316,7 @@ function getStylePlugin(engine) {
|
|
|
233
316
|
};
|
|
234
317
|
return null;
|
|
235
318
|
}
|
|
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: ["
|
|
319
|
+
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
320
|
function generateAddfoxConfig(framework, _language, styleEngine) {
|
|
238
321
|
const importLines = [];
|
|
239
322
|
const fwImport = getFrameworkPluginImport(framework);
|
|
@@ -261,6 +344,67 @@ function generateAddfoxConfig(framework, _language, styleEngine) {
|
|
|
261
344
|
];
|
|
262
345
|
return parts.filter(Boolean).join("\n");
|
|
263
346
|
}
|
|
347
|
+
const MANIFEST_LINE = " manifest: { chromium: manifest, firefox: { ...manifest } },";
|
|
348
|
+
function findMatchingArrayClose(source, openBracketIdx) {
|
|
349
|
+
if ("[" !== source[openBracketIdx]) return -1;
|
|
350
|
+
let depth = 0;
|
|
351
|
+
for(let i = openBracketIdx; i < source.length; i++){
|
|
352
|
+
const c = source[i];
|
|
353
|
+
if ("[" === c) depth++;
|
|
354
|
+
else if ("]" === c) {
|
|
355
|
+
depth--;
|
|
356
|
+
if (0 === depth) return i;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
return -1;
|
|
360
|
+
}
|
|
361
|
+
function hasImportFromModule(source, modulePath) {
|
|
362
|
+
return source.includes(`from "${modulePath}"`) || source.includes(`from '${modulePath}'`) || source.includes(`from \`${modulePath}\``);
|
|
363
|
+
}
|
|
364
|
+
function insertImportAfterLastImport(source, importLine) {
|
|
365
|
+
const trimmed = importLine.trim();
|
|
366
|
+
const moduleMatch = /from\s+["']([^"']+)["']/.exec(trimmed);
|
|
367
|
+
if (moduleMatch && hasImportFromModule(source, moduleMatch[1])) return source;
|
|
368
|
+
const lines = source.split("\n");
|
|
369
|
+
let lastImportIdx = -1;
|
|
370
|
+
for(let i = 0; i < lines.length; i++)if (lines[i].trim().startsWith("import ")) lastImportIdx = i;
|
|
371
|
+
if (-1 === lastImportIdx) return `${importLine}\n${source}`;
|
|
372
|
+
const next = [
|
|
373
|
+
...lines
|
|
374
|
+
];
|
|
375
|
+
next.splice(lastImportIdx + 1, 0, importLine);
|
|
376
|
+
return next.join("\n");
|
|
377
|
+
}
|
|
378
|
+
function pluginCallPresent(inner, call) {
|
|
379
|
+
const fn = call.replace(/\(\)$/, "");
|
|
380
|
+
return inner.includes(`${fn}(`);
|
|
381
|
+
}
|
|
382
|
+
function mergeCallsIntoPluginsArray(source, calls) {
|
|
383
|
+
const pluginsIdx = source.indexOf("plugins:");
|
|
384
|
+
if (-1 === pluginsIdx) return addPluginsBlockAfterManifest(source, calls);
|
|
385
|
+
const bracketStart = source.indexOf("[", pluginsIdx);
|
|
386
|
+
if (-1 === bracketStart) return source;
|
|
387
|
+
const bracketEnd = findMatchingArrayClose(source, bracketStart);
|
|
388
|
+
if (-1 === bracketEnd) return source;
|
|
389
|
+
let inner = source.slice(bracketStart + 1, bracketEnd).trim();
|
|
390
|
+
for (const c of calls)if (!pluginCallPresent(inner, c)) inner = inner ? `${inner}, ${c}` : c;
|
|
391
|
+
return source.slice(0, bracketStart + 1) + inner + source.slice(bracketEnd);
|
|
392
|
+
}
|
|
393
|
+
function addPluginsBlockAfterManifest(source, calls) {
|
|
394
|
+
const idx = source.indexOf(MANIFEST_LINE);
|
|
395
|
+
if (-1 === idx) return source;
|
|
396
|
+
const insertPos = idx + MANIFEST_LINE.length;
|
|
397
|
+
return `${source.slice(0, insertPos)}\n plugins: [${calls.join(", ")}],${source.slice(insertPos)}`;
|
|
398
|
+
}
|
|
399
|
+
function mergeScaffoldIntoAddfoxConfig(source, styleEngine) {
|
|
400
|
+
const st = getStylePlugin(styleEngine);
|
|
401
|
+
if (!st) return source;
|
|
402
|
+
let out = insertImportAfterLastImport(source, st.importLine);
|
|
403
|
+
out = mergeCallsIntoPluginsArray(out, [
|
|
404
|
+
st.call
|
|
405
|
+
]);
|
|
406
|
+
return out;
|
|
407
|
+
}
|
|
264
408
|
const UTF8_BOM = "\uFEFF";
|
|
265
409
|
function stripUtf8Bom(text) {
|
|
266
410
|
return text.startsWith(UTF8_BOM) ? text.slice(UTF8_BOM.length) : text;
|
|
@@ -346,10 +490,10 @@ function generateRstestConfig(language, testKinds) {
|
|
|
346
490
|
}
|
|
347
491
|
function writeUnitSample(root, language) {
|
|
348
492
|
const ext = getTestExt(language);
|
|
349
|
-
mkdirSync(
|
|
493
|
+
mkdirSync(external_node_path_resolve(root, "__tests__"), {
|
|
350
494
|
recursive: true
|
|
351
495
|
});
|
|
352
|
-
const path =
|
|
496
|
+
const path = external_node_path_resolve(root, "__tests__", `example.test.${ext}`);
|
|
353
497
|
const body = "ts" === language ? `import { describe, expect, it } from "@rstest/core";
|
|
354
498
|
|
|
355
499
|
describe("example unit tests", () => {
|
|
@@ -369,11 +513,11 @@ describe("example unit tests", () => {
|
|
|
369
513
|
}
|
|
370
514
|
function writeE2eSample(root, language) {
|
|
371
515
|
const ext = getTestExt(language);
|
|
372
|
-
const dir =
|
|
516
|
+
const dir = external_node_path_resolve(root, "__tests__", "e2e");
|
|
373
517
|
mkdirSync(dir, {
|
|
374
518
|
recursive: true
|
|
375
519
|
});
|
|
376
|
-
const path =
|
|
520
|
+
const path = external_node_path_resolve(dir, `example.browser.test.${ext}`);
|
|
377
521
|
const body = `import { describe, expect, it } from "@rstest/core";
|
|
378
522
|
import { page } from "@rstest/browser";
|
|
379
523
|
|
|
@@ -410,7 +554,7 @@ function mergeScripts(pkg, testKinds) {
|
|
|
410
554
|
function applyTestAndReportSetup(root, language, selection) {
|
|
411
555
|
const { testKinds, installRsdoctor } = selection;
|
|
412
556
|
if (0 === testKinds.length && !installRsdoctor) return;
|
|
413
|
-
const pkgPath =
|
|
557
|
+
const pkgPath = external_node_path_resolve(root, "package.json");
|
|
414
558
|
const pkg = readJsonFile(pkgPath);
|
|
415
559
|
mergeDevDeps(pkg, testKinds, installRsdoctor);
|
|
416
560
|
mergeScripts(pkg, testKinds);
|
|
@@ -418,22 +562,12 @@ function applyTestAndReportSetup(root, language, selection) {
|
|
|
418
562
|
if (testKinds.length > 0) {
|
|
419
563
|
const configName = "ts" === language ? "rstest.config.ts" : "rstest.config.js";
|
|
420
564
|
const content = generateRstestConfig(language, testKinds);
|
|
421
|
-
writeFileSync(
|
|
565
|
+
writeFileSync(external_node_path_resolve(root, configName), content, "utf-8");
|
|
422
566
|
if (testKinds.includes("unit")) writeUnitSample(root, language);
|
|
423
567
|
if (testKinds.includes("e2e")) writeE2eSample(root, language);
|
|
424
568
|
}
|
|
425
569
|
}
|
|
426
|
-
const
|
|
427
|
-
const ORANGE = [
|
|
428
|
-
255,
|
|
429
|
-
118,
|
|
430
|
-
38
|
|
431
|
-
];
|
|
432
|
-
const PINK = [
|
|
433
|
-
255,
|
|
434
|
-
92,
|
|
435
|
-
186
|
|
436
|
-
];
|
|
570
|
+
const logo_RESET = "\x1b[0m";
|
|
437
571
|
const LOGO_LINES = [
|
|
438
572
|
" █████╗ ██████╗ ██████╗ ███████╗ ██████╗ ██╗ ██╗",
|
|
439
573
|
"██╔══██╗██╔══██╗██╔══██╗██╔════╝██╔═══██╗╚██╗██╔╝",
|
|
@@ -442,13 +576,15 @@ const LOGO_LINES = [
|
|
|
442
576
|
"██║ ██║██████╔╝██████╔╝██║ ╚██████╔╝██╔╝ ██╗",
|
|
443
577
|
"╚═╝ ╚═╝╚═════╝ ╚═════╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝"
|
|
444
578
|
];
|
|
445
|
-
|
|
446
|
-
|
|
579
|
+
const GRADIENT_START_256 = 208;
|
|
580
|
+
const GRADIENT_END_256 = 213;
|
|
581
|
+
function shouldEmitAnsiColor() {
|
|
582
|
+
return !process.env.NO_COLOR;
|
|
447
583
|
}
|
|
448
|
-
function
|
|
449
|
-
return `\x1b[38;
|
|
584
|
+
function ansi256Fg(code) {
|
|
585
|
+
return `\x1b[38;5;${code}m`;
|
|
450
586
|
}
|
|
451
|
-
function
|
|
587
|
+
function colorizeLineAnsi256(line) {
|
|
452
588
|
const len = line.length;
|
|
453
589
|
if (0 === len) return "";
|
|
454
590
|
let out = "";
|
|
@@ -459,16 +595,20 @@ function colorizeLine(line) {
|
|
|
459
595
|
continue;
|
|
460
596
|
}
|
|
461
597
|
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;
|
|
598
|
+
const code = Math.round(GRADIENT_START_256 + t * (GRADIENT_END_256 - GRADIENT_START_256));
|
|
599
|
+
out += ansi256Fg(code) + ch;
|
|
466
600
|
}
|
|
467
|
-
return out +
|
|
601
|
+
return out + logo_RESET;
|
|
602
|
+
}
|
|
603
|
+
function printPlainLogo() {
|
|
604
|
+
console.log("");
|
|
605
|
+
for (const line of LOGO_LINES)console.log(line);
|
|
606
|
+
console.log("");
|
|
468
607
|
}
|
|
469
608
|
function printAddfoxLogo() {
|
|
609
|
+
if (!shouldEmitAnsiColor()) return void printPlainLogo();
|
|
470
610
|
console.log("");
|
|
471
|
-
for (const line of LOGO_LINES)console.log(
|
|
611
|
+
for (const line of LOGO_LINES)console.log(colorizeLineAnsi256(line));
|
|
472
612
|
console.log("");
|
|
473
613
|
}
|
|
474
614
|
const SKIP_STYLE_ENTRIES = new Set([
|
|
@@ -671,7 +811,7 @@ function getStyleEngineChoicesColored() {
|
|
|
671
811
|
}
|
|
672
812
|
function getVersion() {
|
|
673
813
|
try {
|
|
674
|
-
const pkgPath =
|
|
814
|
+
const pkgPath = external_node_path_resolve(dirname(fileURLToPath(import.meta.url)), "..", "package.json");
|
|
675
815
|
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
676
816
|
return pkg.version ?? "0.0.0";
|
|
677
817
|
} catch {
|
|
@@ -841,14 +981,14 @@ function resolveTestSelectionFromArgv(argv) {
|
|
|
841
981
|
};
|
|
842
982
|
}
|
|
843
983
|
function updatePackageName(destDir, projectName) {
|
|
844
|
-
const pkgPath =
|
|
984
|
+
const pkgPath = external_node_path_resolve(destDir, "package.json");
|
|
845
985
|
if (!existsSync(pkgPath)) return;
|
|
846
986
|
const pkg = readJsonFile(pkgPath);
|
|
847
987
|
pkg.name = projectName.replace(/\s+/g, "-").toLowerCase();
|
|
848
988
|
writeJsonFile(pkgPath, pkg);
|
|
849
989
|
}
|
|
850
990
|
async function main() {
|
|
851
|
-
const argv =
|
|
991
|
+
const argv = parseArgv(process.argv.slice(2));
|
|
852
992
|
if (argv.help || argv.h) return void printHelp();
|
|
853
993
|
if (argv.version || argv.v) return void console.log(getVersion());
|
|
854
994
|
const targetDir = argv._[0] ?? "my-extension";
|
|
@@ -856,10 +996,14 @@ async function main() {
|
|
|
856
996
|
const cliLanguage = argv.language;
|
|
857
997
|
printAddfoxLogo();
|
|
858
998
|
console.log(blue("\n Create Addfox App\n"));
|
|
859
|
-
const root =
|
|
999
|
+
const root = external_node_path_resolve(process.cwd(), targetDir);
|
|
860
1000
|
if (existsSync(root)) {
|
|
861
1001
|
const confirmed = await confirmOverwrite(targetDir);
|
|
862
1002
|
if (!confirmed) process.exit(0);
|
|
1003
|
+
rmSync(root, {
|
|
1004
|
+
recursive: true,
|
|
1005
|
+
force: true
|
|
1006
|
+
});
|
|
863
1007
|
}
|
|
864
1008
|
const options = cliFramework && cliLanguage ? {
|
|
865
1009
|
framework: cliFramework,
|
|
@@ -875,11 +1019,20 @@ async function main() {
|
|
|
875
1019
|
if (!testSelection) process.exit(0);
|
|
876
1020
|
const pm = options.packageManager;
|
|
877
1021
|
const templateName = getTemplateName(options.framework, options.language);
|
|
878
|
-
|
|
1022
|
+
const templateReady = hasLocalTemplate(templateName);
|
|
1023
|
+
const templateLabel = "Copying template...";
|
|
1024
|
+
const TEMPLATE_SPINNER_MIN_MS = 800;
|
|
879
1025
|
try {
|
|
880
|
-
|
|
1026
|
+
await runWithTemplateSpinner(templateLabel, async ()=>{
|
|
1027
|
+
await new Promise((resolveSpinner)=>{
|
|
1028
|
+
setImmediate(resolveSpinner);
|
|
1029
|
+
});
|
|
1030
|
+
await copyBundledTemplate(templateName, root);
|
|
1031
|
+
}, templateReady ? {
|
|
1032
|
+
minVisibleMs: TEMPLATE_SPINNER_MIN_MS
|
|
1033
|
+
} : void 0);
|
|
881
1034
|
} catch (err) {
|
|
882
|
-
console.error(red(`\n Failed to
|
|
1035
|
+
console.error(red(`\n Failed to copy template: ${err.message}\n`));
|
|
883
1036
|
process.exit(1);
|
|
884
1037
|
}
|
|
885
1038
|
const useAllEntries = options.entries.includes("__all__");
|
|
@@ -889,8 +1042,12 @@ async function main() {
|
|
|
889
1042
|
] : options.entries.filter((e)=>"__all__" !== e && existingDirs.includes(e));
|
|
890
1043
|
if (!useAllEntries && effectiveEntries.length > 0) filterAppEntries(root, effectiveEntries);
|
|
891
1044
|
const configExt = "ts" === options.language ? "ts" : "js";
|
|
892
|
-
const configPath =
|
|
893
|
-
|
|
1045
|
+
const configPath = external_node_path_resolve(root, `addfox.config.${configExt}`);
|
|
1046
|
+
let configContent;
|
|
1047
|
+
if (existsSync(configPath)) {
|
|
1048
|
+
const existing = readFileSync(configPath, "utf-8");
|
|
1049
|
+
configContent = existing.trim().length > 0 && existing.includes("defineConfig") ? mergeScaffoldIntoAddfoxConfig(existing, options.styleEngine) : generateAddfoxConfig(options.framework, options.language, options.styleEngine);
|
|
1050
|
+
} else configContent = generateAddfoxConfig(options.framework, options.language, options.styleEngine);
|
|
894
1051
|
writeFileSync(configPath, configContent, "utf-8");
|
|
895
1052
|
updatePackageName(root, targetDir);
|
|
896
1053
|
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.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Interactive scaffolder for addfox extension projects",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -9,19 +9,20 @@
|
|
|
9
9
|
},
|
|
10
10
|
"files": [
|
|
11
11
|
"dist",
|
|
12
|
+
"templates",
|
|
12
13
|
"LICENSE"
|
|
13
14
|
],
|
|
14
15
|
"dependencies": {
|
|
15
|
-
"chalk": "^5.3.0",
|
|
16
16
|
"prompts": "^2.4.2",
|
|
17
|
-
"
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
"@addfox/pkg-manager": "0.2.0"
|
|
18
|
+
},
|
|
19
|
+
"engines": {
|
|
20
|
+
"node": ">=18.0.0"
|
|
20
21
|
},
|
|
21
22
|
"devDependencies": {
|
|
22
|
-
"@rslib/core": "^0.
|
|
23
|
-
"@rstest/core": "^0.9.
|
|
24
|
-
"@rstest/coverage-istanbul": "^0.3.
|
|
23
|
+
"@rslib/core": "^0.21.5",
|
|
24
|
+
"@rstest/core": "^0.9.9",
|
|
25
|
+
"@rstest/coverage-istanbul": "^0.3.2",
|
|
25
26
|
"@types/node": "^20.0.0",
|
|
26
27
|
"typescript": "^5.0.0"
|
|
27
28
|
},
|
|
@@ -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
|
+
});
|