create-extro 0.0.0 → 0.1.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/dist/cli.js +6 -4
- package/dist/fs.js +10 -0
- package/dist/scaffold.js +12 -0
- package/dist/system.js +10 -5
- package/package.json +1 -1
- package/templates/default/icons/128.png +0 -0
- package/templates/default/icons/16.png +0 -0
- package/templates/default/icons/32.png +0 -0
- package/templates/default/icons/48.png +0 -0
- package/templates/default/public/logo.svg +2 -2
- package/templates/default/src/app/popup/page.tsx +122 -20
package/dist/cli.js
CHANGED
|
@@ -21,6 +21,8 @@ const unwrap = (value) => {
|
|
|
21
21
|
bail();
|
|
22
22
|
return value;
|
|
23
23
|
};
|
|
24
|
+
/** A clack spinner with its frames in the brand terracotta, not clack's magenta. */
|
|
25
|
+
const spinner = () => p.spinner({ styleFrame: brand });
|
|
24
26
|
/** First validation error/warning for a package name, or undefined if valid. */
|
|
25
27
|
const packageNameError = (name) => {
|
|
26
28
|
const { validForNewPackages, errors, warnings } = validatePackageName(name);
|
|
@@ -140,7 +142,7 @@ export const run = async () => {
|
|
|
140
142
|
doGit = false;
|
|
141
143
|
}
|
|
142
144
|
// 6. Write the template.
|
|
143
|
-
const scaffolding =
|
|
145
|
+
const scaffolding = spinner();
|
|
144
146
|
scaffolding.start(`Scaffolding the ${brand(template)} template`);
|
|
145
147
|
try {
|
|
146
148
|
scaffold({ templateName: template, targetDir, packageName });
|
|
@@ -152,9 +154,9 @@ export const run = async () => {
|
|
|
152
154
|
scaffolding.stop(`Created ${bold(rel)}`);
|
|
153
155
|
// 7. Install.
|
|
154
156
|
if (doInstall) {
|
|
155
|
-
const installing =
|
|
157
|
+
const installing = spinner();
|
|
156
158
|
installing.start(`Installing dependencies with ${brand(pm)}`);
|
|
157
|
-
if (installDependencies(pm, targetDir)) {
|
|
159
|
+
if (await installDependencies(pm, targetDir)) {
|
|
158
160
|
installing.stop("Installed dependencies");
|
|
159
161
|
}
|
|
160
162
|
else {
|
|
@@ -165,7 +167,7 @@ export const run = async () => {
|
|
|
165
167
|
}
|
|
166
168
|
// 8. Git.
|
|
167
169
|
if (doGit) {
|
|
168
|
-
const initializing =
|
|
170
|
+
const initializing = spinner();
|
|
169
171
|
initializing.start("Initializing a git repository");
|
|
170
172
|
initializing.stop(initGitRepo(targetDir)
|
|
171
173
|
? "Initialized a git repository"
|
package/dist/fs.js
CHANGED
|
@@ -29,5 +29,15 @@ export const toValidPackageName = (input) => input
|
|
|
29
29
|
.replace(/^[._]+/, "")
|
|
30
30
|
.replace(/[^a-z0-9-~]+/g, "-")
|
|
31
31
|
.replace(/^-+|-+$/g, "") || "extro-extension";
|
|
32
|
+
/**
|
|
33
|
+
* @describe Humanize a package name into the Chrome-visible extension name
|
|
34
|
+
* stamped into the scaffolded extro.config.ts: "cool-tabs" becomes
|
|
35
|
+
* "Cool Tabs". Mirrors the displayName Plasmo derives for its manifests.
|
|
36
|
+
*/
|
|
37
|
+
export const toDisplayName = (packageName) => packageName
|
|
38
|
+
.split(/[-_.~\s]+/)
|
|
39
|
+
.filter(Boolean)
|
|
40
|
+
.map((word) => word[0].toUpperCase() + word.slice(1))
|
|
41
|
+
.join(" ") || "My Extension";
|
|
32
42
|
/** Display form of the target: the cwd-relative path, or "." for the cwd itself. */
|
|
33
43
|
export const relativeTarget = (cwd, targetDir) => path.relative(cwd, targetDir) || ".";
|
package/dist/scaffold.js
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { toDisplayName } from "./fs.js";
|
|
4
5
|
const TEMPLATES_ROOT = fileURLToPath(new URL("../templates", import.meta.url));
|
|
6
|
+
// The config.name every template ships. Stamped with the humanized project
|
|
7
|
+
// name on copy; config.name wins over package.json in manifest generation,
|
|
8
|
+
// so this is the name Chrome shows.
|
|
9
|
+
const NAME_PLACEHOLDER = `name: "My Extension"`;
|
|
5
10
|
// Dotfiles ship under `_`-prefixed names so npm does not rewrite them at
|
|
6
11
|
// publish time (it renames a packaged `.gitignore` to `.npmignore`) and so
|
|
7
12
|
// they survive `npm pack`. They are restored to their real names on copy.
|
|
@@ -29,11 +34,18 @@ const copyDir = (srcDir, destDir, packageName) => {
|
|
|
29
34
|
else if (entry.name === "package.json") {
|
|
30
35
|
writePackageJson(srcPath, destPath, packageName);
|
|
31
36
|
}
|
|
37
|
+
else if (entry.name === "extro.config.ts") {
|
|
38
|
+
writeExtroConfig(srcPath, destPath, packageName);
|
|
39
|
+
}
|
|
32
40
|
else {
|
|
33
41
|
fs.copyFileSync(srcPath, destPath);
|
|
34
42
|
}
|
|
35
43
|
}
|
|
36
44
|
};
|
|
45
|
+
const writeExtroConfig = (srcPath, destPath, packageName) => {
|
|
46
|
+
const source = fs.readFileSync(srcPath, "utf8");
|
|
47
|
+
fs.writeFileSync(destPath, source.replace(NAME_PLACEHOLDER, `name: "${toDisplayName(packageName)}"`));
|
|
48
|
+
};
|
|
37
49
|
const writePackageJson = (srcPath, destPath, packageName) => {
|
|
38
50
|
const manifest = JSON.parse(fs.readFileSync(srcPath, "utf8"));
|
|
39
51
|
manifest.name = packageName;
|
package/dist/system.js
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import spawn from "cross-spawn";
|
|
2
2
|
import { installArgs } from "./pkg-manager.js";
|
|
3
|
-
/**
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
/**
|
|
4
|
+
* @describe Install dependencies in `cwd` with the given manager. Resolves
|
|
5
|
+
* with success. Async on purpose: a `spawn.sync` child blocks the event loop
|
|
6
|
+
* for the whole install, which freezes the clack spinner shown around it.
|
|
7
|
+
*/
|
|
8
|
+
export const installDependencies = (pm, cwd) => new Promise((resolve) => {
|
|
9
|
+
const child = spawn(pm, installArgs(pm), { cwd, stdio: "ignore" });
|
|
10
|
+
child.on("error", () => resolve(false));
|
|
11
|
+
child.on("close", (code) => resolve(code === 0));
|
|
12
|
+
});
|
|
8
13
|
/** Whether a `git` binary is on PATH. */
|
|
9
14
|
export const isGitInstalled = () => spawn.sync("git", ["--version"], { stdio: "ignore" }).status === 0;
|
|
10
15
|
/** Whether `dir` already sits inside a git work tree (so we should not re-init). */
|
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="100" height="100">
|
|
2
|
-
<rect x="
|
|
3
|
-
<rect x="
|
|
2
|
+
<rect x="4.5" y="4.5" width="70" height="70" rx="12.6" fill="#fafafa" />
|
|
3
|
+
<rect x="25.5" y="25.5" width="70" height="70" rx="12.6" fill="#CC785C" />
|
|
4
4
|
</svg>
|
|
@@ -2,28 +2,130 @@ import { useState } from "react"
|
|
|
2
2
|
import { asset } from "extrojs/asset"
|
|
3
3
|
|
|
4
4
|
export default function Popup() {
|
|
5
|
-
const [count, setCount] = useState(0)
|
|
5
|
+
const [count, setCount] = useState<number>(0)
|
|
6
6
|
|
|
7
7
|
return (
|
|
8
|
-
|
|
9
|
-
<
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
{/* asset() wraps chrome.runtime.getURL. logo.svg lives in public/. */}
|
|
18
|
-
<img src={asset("logo.svg")} width={20} height={20} alt="Extro" />
|
|
19
|
-
<strong>My Extension</strong>
|
|
20
|
-
</div>
|
|
8
|
+
<>
|
|
9
|
+
<style>{styles}</style>
|
|
10
|
+
<main className="popup">
|
|
11
|
+
<header className="brand">
|
|
12
|
+
{/* asset() wraps chrome.runtime.getURL. logo.svg lives in public/. */}
|
|
13
|
+
<img src={asset("logo.svg")} width={56} height={56} alt="Extro" />
|
|
14
|
+
<span className="brand-x">×</span>
|
|
15
|
+
<ReactLogo />
|
|
16
|
+
</header>
|
|
21
17
|
|
|
22
|
-
|
|
23
|
-
<
|
|
24
|
-
|
|
25
|
-
<
|
|
26
|
-
|
|
27
|
-
|
|
18
|
+
<h1>extro × react</h1>
|
|
19
|
+
<p className="tagline">Next.js for Chrome extensions.</p>
|
|
20
|
+
|
|
21
|
+
<section className="counter">
|
|
22
|
+
<button
|
|
23
|
+
className="ghost"
|
|
24
|
+
aria-label="Decrement"
|
|
25
|
+
onClick={() => setCount((n) => n - 1)}
|
|
26
|
+
>
|
|
27
|
+
−
|
|
28
|
+
</button>
|
|
29
|
+
<span className="count">{count}</span>
|
|
30
|
+
<button
|
|
31
|
+
className="solid"
|
|
32
|
+
aria-label="Increment"
|
|
33
|
+
onClick={() => setCount((n) => n + 1)}
|
|
34
|
+
>
|
|
35
|
+
+
|
|
36
|
+
</button>
|
|
37
|
+
</section>
|
|
38
|
+
|
|
39
|
+
<p className="hint">
|
|
40
|
+
Edit <code>src/app/popup/page.tsx</code> to get started.
|
|
41
|
+
</p>
|
|
42
|
+
</main>
|
|
43
|
+
</>
|
|
28
44
|
)
|
|
29
45
|
}
|
|
46
|
+
|
|
47
|
+
const ReactLogo = () => (
|
|
48
|
+
<svg viewBox="-11.5 -10.23 23 20.46" width={56} height={56} aria-label="React">
|
|
49
|
+
<circle r="2.05" fill="#58C4DC" />
|
|
50
|
+
<g stroke="#58C4DC" fill="none">
|
|
51
|
+
<ellipse rx="11" ry="4.2" />
|
|
52
|
+
<ellipse rx="11" ry="4.2" transform="rotate(60)" />
|
|
53
|
+
<ellipse rx="11" ry="4.2" transform="rotate(120)" />
|
|
54
|
+
</g>
|
|
55
|
+
</svg>
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
const styles = `
|
|
59
|
+
:root { color-scheme: dark; }
|
|
60
|
+
body {
|
|
61
|
+
margin: 0;
|
|
62
|
+
background: #0a0a0a;
|
|
63
|
+
color: #fafafa;
|
|
64
|
+
font-family: ui-sans-serif, system-ui, -apple-system, sans-serif;
|
|
65
|
+
}
|
|
66
|
+
.popup {
|
|
67
|
+
box-sizing: border-box;
|
|
68
|
+
width: 360px;
|
|
69
|
+
min-height: 420px;
|
|
70
|
+
padding: 40px 28px 28px;
|
|
71
|
+
display: flex;
|
|
72
|
+
flex-direction: column;
|
|
73
|
+
align-items: center;
|
|
74
|
+
background: radial-gradient(
|
|
75
|
+
320px circle at 50% 0%,
|
|
76
|
+
rgba(204, 120, 92, 0.14),
|
|
77
|
+
transparent 60%
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
.brand { display: flex; align-items: center; gap: 16px; }
|
|
81
|
+
.brand-x { font-size: 20px; color: #525252; }
|
|
82
|
+
h1 {
|
|
83
|
+
margin: 22px 0 0;
|
|
84
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
85
|
+
font-size: 21px;
|
|
86
|
+
font-weight: 600;
|
|
87
|
+
letter-spacing: -0.02em;
|
|
88
|
+
}
|
|
89
|
+
.tagline { margin: 6px 0 0; font-size: 13px; color: #a3a3a3; }
|
|
90
|
+
.counter {
|
|
91
|
+
margin-top: 28px;
|
|
92
|
+
display: flex;
|
|
93
|
+
align-items: center;
|
|
94
|
+
gap: 16px;
|
|
95
|
+
padding: 14px 18px;
|
|
96
|
+
background: #141414;
|
|
97
|
+
border: 1px solid #262626;
|
|
98
|
+
border-radius: 10px;
|
|
99
|
+
}
|
|
100
|
+
.count {
|
|
101
|
+
min-width: 48px;
|
|
102
|
+
text-align: center;
|
|
103
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
104
|
+
font-size: 22px;
|
|
105
|
+
font-variant-numeric: tabular-nums;
|
|
106
|
+
}
|
|
107
|
+
button {
|
|
108
|
+
width: 34px;
|
|
109
|
+
height: 34px;
|
|
110
|
+
display: grid;
|
|
111
|
+
place-items: center;
|
|
112
|
+
border-radius: 6px;
|
|
113
|
+
font-size: 18px;
|
|
114
|
+
line-height: 1;
|
|
115
|
+
cursor: pointer;
|
|
116
|
+
transition: background 0.15s;
|
|
117
|
+
}
|
|
118
|
+
.solid { border: none; background: #CC785C; color: #fff; }
|
|
119
|
+
.solid:hover { background: #b8674e; }
|
|
120
|
+
.ghost { border: 1px solid #2e2e2e; background: transparent; color: #fafafa; }
|
|
121
|
+
.ghost:hover { background: #1a1a1a; }
|
|
122
|
+
.hint { margin: auto 0 0; padding-top: 28px; font-size: 12px; color: #737373; }
|
|
123
|
+
.hint code {
|
|
124
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
125
|
+
font-size: 11px;
|
|
126
|
+
padding: 2px 6px;
|
|
127
|
+
color: #CC785C;
|
|
128
|
+
background: #1a1a1a;
|
|
129
|
+
border-radius: 4px;
|
|
130
|
+
}
|
|
131
|
+
`
|