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 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 = p.spinner();
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 = p.spinner();
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 = p.spinner();
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
- /** Install dependencies in `cwd` with the given manager. Returns success. */
4
- export const installDependencies = (pm, cwd) => {
5
- const result = spawn.sync(pm, installArgs(pm), { cwd, stdio: "ignore" });
6
- return result.status === 0;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-extro",
3
- "version": "0.0.0",
3
+ "version": "0.1.0",
4
4
  "description": "Scaffold a new Extro extension. The official starter for the Extro framework.",
5
5
  "keywords": [
6
6
  "extro",
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="13.6" y="13.6" width="56" height="56" rx="10.08" fill="#0a0a0a" />
3
- <rect x="30.4" y="30.4" width="56" height="56" rx="10.08" fill="#CC785C" />
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
- <div style={{ width: 240, padding: 16, fontFamily: "system-ui, sans-serif" }}>
9
- <div
10
- style={{
11
- display: "flex",
12
- alignItems: "center",
13
- gap: 8,
14
- marginBottom: 16,
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
- <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
23
- <button onClick={() => setCount((n) => n - 1)}>-</button>
24
- <span style={{ minWidth: 24, textAlign: "center" }}>{count}</span>
25
- <button onClick={() => setCount((n) => n + 1)}>+</button>
26
- </div>
27
- </div>
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
+ `