@featherk/icons 0.15.7 → 0.15.8
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/bin/featherk-fontawesome-setup.mjs +4 -0
- package/dist/docs-manifest.js +5 -5
- package/dist/fontawesome/chunks/manifest-BB3q10t1.mjs +20 -0
- package/dist/fontawesome/chunks/manifest-C15Xa22L.cjs +19 -0
- package/dist/fontawesome/cli.cjs +5 -17
- package/dist/fontawesome/cli.mjs +5 -17
- package/dist/fontawesome/manifest.d.ts +14 -0
- package/dist/fontawesome/setup.cjs +477 -0
- package/dist/fontawesome/setup.d.ts +39 -0
- package/dist/fontawesome/setup.mjs +477 -0
- package/docs/ICONS.md +1 -1
- package/docs/fontawesome/FontAwesomeCLI.md +2 -0
- package/docs/fontawesome/LICENSING.md +132 -14
- package/package.json +5 -4
- package/dist/fontawesome/catalog.test.d.ts +0 -1
- package/dist/fontawesome/cli.test.d.ts +0 -1
- package/dist/fontawesome/fontawesome-service.test.d.ts +0 -1
- package/dist/fontawesome/generate.test.d.ts +0 -1
- package/dist/fontawesome/normalize.test.d.ts +0 -1
- package/dist/fontawesome/vite.test.d.ts +0 -1
- package/dist/icon-service.test.d.ts +0 -1
- /package/{scripts/fontawesome.mjs → bin/featherk-fontawesome.mjs} +0 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/** Runs npm with the given arguments and returns its exit status. */
|
|
2
|
+
export type FontAwesomeNpmRunner = (options: {
|
|
3
|
+
npmCli: string;
|
|
4
|
+
args: string[];
|
|
5
|
+
cwd: string;
|
|
6
|
+
env: NodeJS.ProcessEnv;
|
|
7
|
+
}) => number;
|
|
8
|
+
/** Injectable process I/O used by the setup entry point and its tests. */
|
|
9
|
+
export interface FontAwesomeSetupIO {
|
|
10
|
+
cwd?: string;
|
|
11
|
+
env?: NodeJS.ProcessEnv;
|
|
12
|
+
stdout?: (message: string) => void;
|
|
13
|
+
stderr?: (message: string) => void;
|
|
14
|
+
/** Called when the token is not in the environment. */
|
|
15
|
+
promptForToken?: () => Promise<string>;
|
|
16
|
+
runNpm?: FontAwesomeNpmRunner;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Checks where an .npmrc could send the package token before it is supplied.
|
|
20
|
+
* The token variable may appear only in the npm.fontawesome.com auth entry,
|
|
21
|
+
* that host may carry no other credential, and @fortawesome may route only to
|
|
22
|
+
* the Font Awesome registry.
|
|
23
|
+
*
|
|
24
|
+
* @returns The configured @fortawesome registry, if any, and whether the
|
|
25
|
+
* placeholder auth entry exists.
|
|
26
|
+
* @throws If the config could expose the token or a literal credential.
|
|
27
|
+
*/
|
|
28
|
+
export declare function checkNpmrc(config: string, file: string): {
|
|
29
|
+
fontAwesomeRegistry?: string;
|
|
30
|
+
hasAuth: boolean;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Parses setup arguments and runs app-root setup, source-project scaffolding,
|
|
34
|
+
* or source-project installation.
|
|
35
|
+
*
|
|
36
|
+
* @returns `0` on success, npm's exit status when an install fails, and `1`
|
|
37
|
+
* when argument, manifest, config, or environment validation fails.
|
|
38
|
+
*/
|
|
39
|
+
export declare function runFontAwesomeSetup(args: readonly string[], io?: FontAwesomeSetupIO): Promise<number>;
|
|
@@ -0,0 +1,477 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, rmSync } from "node:fs";
|
|
3
|
+
import { resolve, relative } from "node:path";
|
|
4
|
+
import { emitKeypressEvents } from "node:readline";
|
|
5
|
+
import { parseArgs } from "node:util";
|
|
6
|
+
import { fontAwesomeCatalog, resolveFontAwesomeKeys } from "./catalog.mjs";
|
|
7
|
+
import { r as readFontAwesomeManifest } from "./chunks/manifest-BB3q10t1.mjs";
|
|
8
|
+
const tokenVariable = "FONTAWESOME_PACKAGE_TOKEN";
|
|
9
|
+
const tokenPlaceholder = `\${${tokenVariable}}`;
|
|
10
|
+
const registry = "https://npm.fontawesome.com/";
|
|
11
|
+
const authKey = "//npm.fontawesome.com/:_authToken";
|
|
12
|
+
const pinRegistry = `--@fortawesome:registry=${registry}`;
|
|
13
|
+
const npmrcLines = [
|
|
14
|
+
"# Route @fortawesome packages to the Font Awesome Pro registry.",
|
|
15
|
+
`@fortawesome:registry=${registry}`,
|
|
16
|
+
"# Authenticate using the token from the environment; never commit a token.",
|
|
17
|
+
`${authKey}=${tokenPlaceholder}`
|
|
18
|
+
];
|
|
19
|
+
const workspaceVariables = [
|
|
20
|
+
"npm_config_workspace",
|
|
21
|
+
"npm_config_workspaces",
|
|
22
|
+
"NPM_CONFIG_WORKSPACE",
|
|
23
|
+
"NPM_CONFIG_WORKSPACES"
|
|
24
|
+
];
|
|
25
|
+
const usage = `Usage:
|
|
26
|
+
featherk-fontawesome-setup (--manifest <icons.json> | --keys <a,b>) [--free-only]
|
|
27
|
+
featherk-fontawesome-setup --source-root <dir> [--refresh-lock]
|
|
28
|
+
featherk-fontawesome-setup --source-root <dir> --init (--manifest <icons.json> | --keys <a,b>) [--force]
|
|
29
|
+
|
|
30
|
+
Default (Free + Pro): run once from the application root on a developer
|
|
31
|
+
machine. Adds the @fortawesome registry lines to .npmrc (token placeholder
|
|
32
|
+
only) and installs exact pins for all selected keys as devDependencies.
|
|
33
|
+
Commit .npmrc, package.json, and package-lock.json. Later installs, including
|
|
34
|
+
CI, give ${tokenVariable} only to "npm ci --ignore-scripts", then run any
|
|
35
|
+
needed install scripts (npm rebuild for dependencies, plus the app's own) and
|
|
36
|
+
builds without it. Do not export the token persistently.
|
|
37
|
+
|
|
38
|
+
--free-only: Free keys only from public npm. No .npmrc changes, no token.
|
|
39
|
+
|
|
40
|
+
--source-root <dir>: install licensed packages into an isolated private
|
|
41
|
+
source project instead (npm ci when its lockfile exists). --init scaffolds
|
|
42
|
+
that project; with --force, a lockfile for different dependencies is removed
|
|
43
|
+
so the next install regenerates it. --refresh-lock runs npm install to update
|
|
44
|
+
the lockfile after editing package.json by hand. In CI, --init and
|
|
45
|
+
--refresh-lock are refused and a lockfile is required.
|
|
46
|
+
|
|
47
|
+
The token comes from ${tokenVariable} or a hidden prompt in an interactive
|
|
48
|
+
terminal. App-root setup changes package.json and is refused in CI
|
|
49
|
+
(CI, TEAMCITY_VERSION, or GITHUB_ACTIONS is set).`;
|
|
50
|
+
function promptForTerminalToken() {
|
|
51
|
+
const input = process.stdin;
|
|
52
|
+
if (!input.isTTY || typeof input.setRawMode !== "function") {
|
|
53
|
+
throw new Error(
|
|
54
|
+
`${tokenVariable} is not set and this is not an interactive terminal. Provide it through an approved CI secret environment.`
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
emitKeypressEvents(input);
|
|
58
|
+
process.stdout.write("Font Awesome Pro Package Token (input hidden): ");
|
|
59
|
+
const wasRaw = input.isRaw;
|
|
60
|
+
input.setRawMode(true);
|
|
61
|
+
input.resume();
|
|
62
|
+
return new Promise((resolveToken, rejectToken) => {
|
|
63
|
+
const characters = [];
|
|
64
|
+
const finish = (error) => {
|
|
65
|
+
input.off("keypress", onKeypress);
|
|
66
|
+
input.setRawMode(wasRaw);
|
|
67
|
+
input.pause();
|
|
68
|
+
process.stdout.write("\n");
|
|
69
|
+
if (error) rejectToken(error);
|
|
70
|
+
else resolveToken(characters.join(""));
|
|
71
|
+
};
|
|
72
|
+
const onKeypress = (character, key = {}) => {
|
|
73
|
+
if (key.ctrl && key.name === "c") {
|
|
74
|
+
finish(new Error("Token entry cancelled."));
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
if (key.name === "return" || key.name === "enter") {
|
|
78
|
+
finish();
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (key.name === "backspace") {
|
|
82
|
+
if (characters.length) {
|
|
83
|
+
characters.pop();
|
|
84
|
+
process.stdout.write("\b \b");
|
|
85
|
+
}
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (!key.ctrl && !key.meta && character && !character.startsWith("\x1B")) {
|
|
89
|
+
for (const item of character) {
|
|
90
|
+
if (item >= " " && item <= "~") {
|
|
91
|
+
characters.push(item);
|
|
92
|
+
process.stdout.write("*");
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
input.on("keypress", onKeypress);
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
const spawnNpm = ({ npmCli, args, cwd, env }) => {
|
|
101
|
+
const result = spawnSync(process.execPath, [npmCli, ...args], {
|
|
102
|
+
cwd,
|
|
103
|
+
env,
|
|
104
|
+
stdio: "inherit"
|
|
105
|
+
});
|
|
106
|
+
if (result.error) throw result.error;
|
|
107
|
+
return result.status ?? 1;
|
|
108
|
+
};
|
|
109
|
+
async function readToken(context) {
|
|
110
|
+
const token = context.env[tokenVariable] || await context.promptForToken();
|
|
111
|
+
if (!token) {
|
|
112
|
+
throw new Error(
|
|
113
|
+
"A Font Awesome Pro Package Token is required to install Pro packages."
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
return token;
|
|
117
|
+
}
|
|
118
|
+
function requireNpm(context) {
|
|
119
|
+
const npmCli = context.env.npm_execpath;
|
|
120
|
+
if (!npmCli) {
|
|
121
|
+
throw new Error(
|
|
122
|
+
"Run this command through an npm script or npx so npm is available."
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
return npmCli;
|
|
126
|
+
}
|
|
127
|
+
async function readKeys(values, context) {
|
|
128
|
+
if (values.keys && values.manifest) {
|
|
129
|
+
throw new Error("Provide either --keys or --manifest, not both.");
|
|
130
|
+
}
|
|
131
|
+
if (values.keys) {
|
|
132
|
+
return values.keys.split(",").map((key) => key.trim()).filter(Boolean);
|
|
133
|
+
}
|
|
134
|
+
if (!values.manifest) {
|
|
135
|
+
throw new Error("Provide --manifest or --keys. Use --help for usage.");
|
|
136
|
+
}
|
|
137
|
+
const { free, pro } = await readFontAwesomeManifest(
|
|
138
|
+
resolve(context.cwd, values.manifest)
|
|
139
|
+
);
|
|
140
|
+
return [...free, ...pro];
|
|
141
|
+
}
|
|
142
|
+
function resolvePins(keys) {
|
|
143
|
+
const tiers = new Map(
|
|
144
|
+
fontAwesomeCatalog.map((entry) => [entry.key, entry.tier])
|
|
145
|
+
);
|
|
146
|
+
const pins = /* @__PURE__ */ new Map();
|
|
147
|
+
for (const selection of resolveFontAwesomeKeys(keys, { allowPro: true })) {
|
|
148
|
+
const spec = selection.package === selection.registryPackage ? selection.version : `npm:${selection.registryPackage}@${selection.version}`;
|
|
149
|
+
const existing = pins.get(selection.package);
|
|
150
|
+
if (existing && existing.spec !== spec) {
|
|
151
|
+
throw new Error(
|
|
152
|
+
`Conflicting pins for ${selection.package}: ${existing.spec} and ${spec}.`
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
if (!existing) {
|
|
156
|
+
pins.set(selection.package, {
|
|
157
|
+
tier: tiers.get(selection.key),
|
|
158
|
+
spec,
|
|
159
|
+
key: selection.key
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return pins;
|
|
164
|
+
}
|
|
165
|
+
function checkNpmrc(config, file) {
|
|
166
|
+
let fontAwesomeRegistry;
|
|
167
|
+
let hasAuth = false;
|
|
168
|
+
for (const raw of config.split(/\r?\n/)) {
|
|
169
|
+
const line = raw.trim();
|
|
170
|
+
if (!line || line.startsWith("#") || line.startsWith(";")) continue;
|
|
171
|
+
const separator = line.indexOf("=");
|
|
172
|
+
const key = (separator < 0 ? line : line.slice(0, separator)).trim();
|
|
173
|
+
const value = separator < 0 ? "" : line.slice(separator + 1).trim();
|
|
174
|
+
if (key === "@fortawesome:registry") {
|
|
175
|
+
fontAwesomeRegistry = value;
|
|
176
|
+
} else if (key === authKey && value === tokenPlaceholder) {
|
|
177
|
+
hasAuth = true;
|
|
178
|
+
} else if (key.includes("npm.fontawesome.com")) {
|
|
179
|
+
throw new Error(
|
|
180
|
+
`${file} may configure npm.fontawesome.com only as ${authKey}=${tokenPlaceholder}; remove "${key}".`
|
|
181
|
+
);
|
|
182
|
+
} else if (line.includes(tokenVariable)) {
|
|
183
|
+
throw new Error(
|
|
184
|
+
`${file} references ${tokenVariable} in "${key}"; the token may only authenticate npm.fontawesome.com.`
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
if (fontAwesomeRegistry !== void 0 && fontAwesomeRegistry.replace(/\/?$/, "/") !== registry) {
|
|
189
|
+
throw new Error(
|
|
190
|
+
`${file} routes @fortawesome to ${fontAwesomeRegistry}; the Pro token may only be sent to ${registry}.`
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
return { fontAwesomeRegistry, hasAuth };
|
|
194
|
+
}
|
|
195
|
+
async function setupAppRoot(values, context) {
|
|
196
|
+
const { log } = context;
|
|
197
|
+
if (context.isCi) {
|
|
198
|
+
throw new Error(
|
|
199
|
+
`App-root setup changes package.json and is a local, one-time step. In CI, give ${tokenVariable} only to "npm ci --ignore-scripts", then run needed install scripts and the build without it.`
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
const appRoot = context.cwd;
|
|
203
|
+
if (!existsSync(resolve(appRoot, "package.json"))) {
|
|
204
|
+
throw new Error(
|
|
205
|
+
"Run this command from the application root (package.json not found)."
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
const freeOnly = Boolean(values["free-only"]);
|
|
209
|
+
const pins = resolvePins(await readKeys(values, context));
|
|
210
|
+
const proPins = [...pins.values()].filter((pin) => pin.tier === "pro");
|
|
211
|
+
if (freeOnly && proPins.length) {
|
|
212
|
+
throw new Error(
|
|
213
|
+
`--free-only cannot install Pro keys: ${proPins.map((pin) => pin.key).join(", ")}.`
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
if (!pins.size) {
|
|
217
|
+
throw new Error("No Font Awesome keys selected.");
|
|
218
|
+
}
|
|
219
|
+
const npmrcPath = resolve(appRoot, ".npmrc");
|
|
220
|
+
const config = existsSync(npmrcPath) ? readFileSync(npmrcPath, "utf8") : "";
|
|
221
|
+
const { fontAwesomeRegistry, hasAuth } = checkNpmrc(config, ".npmrc");
|
|
222
|
+
if (freeOnly && fontAwesomeRegistry) {
|
|
223
|
+
throw new Error(
|
|
224
|
+
`.npmrc routes @fortawesome to ${fontAwesomeRegistry}, so this app is set up for Pro. Run without --free-only.`
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
const npmCli = requireNpm(context);
|
|
228
|
+
const environment = { ...context.env };
|
|
229
|
+
if (!freeOnly) {
|
|
230
|
+
environment[tokenVariable] = await readToken(context);
|
|
231
|
+
const missing = [];
|
|
232
|
+
if (!fontAwesomeRegistry) missing.push(npmrcLines[0], npmrcLines[1]);
|
|
233
|
+
if (!hasAuth) missing.push(npmrcLines[2], npmrcLines[3]);
|
|
234
|
+
if (missing.length) {
|
|
235
|
+
const separator = config && !config.endsWith("\n") ? "\n" : "";
|
|
236
|
+
writeFileSync(npmrcPath, `${config}${separator}${missing.join("\n")}
|
|
237
|
+
`);
|
|
238
|
+
log(
|
|
239
|
+
`${config ? "Updated" : "Created"} .npmrc with the Font Awesome registry and token placeholder.`
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
const specs = [...pins].map(([name, pin]) => `${name}@${pin.spec}`);
|
|
244
|
+
const status = context.runNpm({
|
|
245
|
+
npmCli,
|
|
246
|
+
args: [
|
|
247
|
+
"install",
|
|
248
|
+
"--save-dev",
|
|
249
|
+
"--save-exact",
|
|
250
|
+
"--ignore-scripts",
|
|
251
|
+
"--no-audit",
|
|
252
|
+
"--no-fund",
|
|
253
|
+
...freeOnly ? [] : [pinRegistry],
|
|
254
|
+
...specs
|
|
255
|
+
],
|
|
256
|
+
cwd: appRoot,
|
|
257
|
+
env: environment
|
|
258
|
+
});
|
|
259
|
+
if (status !== 0) return status;
|
|
260
|
+
log(`Installed ${specs.length} Font Awesome package(s): ${specs.join(" ")}`);
|
|
261
|
+
log(
|
|
262
|
+
"Lifecycle scripts were skipped. If needed, run npm rebuild for dependency scripts and the app's own scripts (e.g. npm run prepare --if-present)."
|
|
263
|
+
);
|
|
264
|
+
if (freeOnly) {
|
|
265
|
+
log(
|
|
266
|
+
"Next: commit package.json and package-lock.json. Keep the Vite plugin's allowPro unset (Free only); no CI token is needed."
|
|
267
|
+
);
|
|
268
|
+
} else {
|
|
269
|
+
log(
|
|
270
|
+
`Next: commit .npmrc, package.json, and package-lock.json. Set allowPro: true in the Vite plugin (no sourceRoot). Later installs, including CI, give ${tokenVariable} only to "npm ci --ignore-scripts"; run needed install scripts and builds without it. See LICENSING.md for the TeamCity setup.`
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
return 0;
|
|
274
|
+
}
|
|
275
|
+
function readDependencies(packageJsonPath) {
|
|
276
|
+
try {
|
|
277
|
+
const { dependencies = {} } = JSON.parse(
|
|
278
|
+
readFileSync(packageJsonPath, "utf8")
|
|
279
|
+
);
|
|
280
|
+
return JSON.stringify(
|
|
281
|
+
Object.fromEntries(Object.entries(dependencies).sort())
|
|
282
|
+
);
|
|
283
|
+
} catch {
|
|
284
|
+
return void 0;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
async function initSourceProject(sourceRoot, values, context) {
|
|
288
|
+
const { log, display } = context;
|
|
289
|
+
if (context.isCi) {
|
|
290
|
+
throw new Error(
|
|
291
|
+
"--init is a local, one-time step. Commit the scaffolded source project instead of generating it in CI."
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
const pins = resolvePins(await readKeys(values, context));
|
|
295
|
+
const dependencies = {};
|
|
296
|
+
const skipped = [];
|
|
297
|
+
for (const [name, pin] of pins) {
|
|
298
|
+
if (pin.tier === "pro") dependencies[name] = pin.spec;
|
|
299
|
+
else skipped.push(pin.key);
|
|
300
|
+
}
|
|
301
|
+
if (!Object.keys(dependencies).length) {
|
|
302
|
+
throw new Error(
|
|
303
|
+
"No Pro keys selected. Free packages belong in the application's own devDependencies."
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
const sortedDependencies = Object.fromEntries(
|
|
307
|
+
Object.entries(dependencies).sort()
|
|
308
|
+
);
|
|
309
|
+
const files = {
|
|
310
|
+
".npmrc": `${npmrcLines.join("\n")}
|
|
311
|
+
`,
|
|
312
|
+
".gitignore": "node_modules/\n",
|
|
313
|
+
"package.json": `${JSON.stringify(
|
|
314
|
+
{
|
|
315
|
+
name: "fontawesome-pro-sources",
|
|
316
|
+
version: "0.0.0",
|
|
317
|
+
private: true,
|
|
318
|
+
dependencies: sortedDependencies
|
|
319
|
+
},
|
|
320
|
+
null,
|
|
321
|
+
2
|
|
322
|
+
)}
|
|
323
|
+
`
|
|
324
|
+
};
|
|
325
|
+
const existing = Object.keys(files).filter(
|
|
326
|
+
(name) => existsSync(resolve(sourceRoot, name))
|
|
327
|
+
);
|
|
328
|
+
if (existing.length && !values.force) {
|
|
329
|
+
throw new Error(
|
|
330
|
+
`${display(sourceRoot)} already contains ${existing.join(", ")}. Use --force to overwrite.`
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
const lockfilePath = resolve(sourceRoot, "package-lock.json");
|
|
334
|
+
const removeStaleLockfile = existsSync(lockfilePath) && readDependencies(resolve(sourceRoot, "package.json")) !== JSON.stringify(sortedDependencies);
|
|
335
|
+
mkdirSync(sourceRoot, { recursive: true });
|
|
336
|
+
for (const [name, content] of Object.entries(files)) {
|
|
337
|
+
writeFileSync(resolve(sourceRoot, name), content);
|
|
338
|
+
}
|
|
339
|
+
if (removeStaleLockfile) rmSync(lockfilePath);
|
|
340
|
+
if (skipped.length) {
|
|
341
|
+
log(
|
|
342
|
+
`Skipped Free keys (install them in the application instead): ${skipped.join(", ")}`
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
log(
|
|
346
|
+
`Scaffolded ${display(sourceRoot)} with ${Object.keys(dependencies).length} Pro package(s).`
|
|
347
|
+
);
|
|
348
|
+
if (removeStaleLockfile) {
|
|
349
|
+
log(
|
|
350
|
+
`Removed ${display(sourceRoot)}/package-lock.json because the dependencies changed; the next install regenerates it.`
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
log(
|
|
354
|
+
"Next: run this command with --source-root and without --init, then commit the source project and its package-lock.json."
|
|
355
|
+
);
|
|
356
|
+
return 0;
|
|
357
|
+
}
|
|
358
|
+
async function installSourceProject(sourceRoot, values, context) {
|
|
359
|
+
const { log, display } = context;
|
|
360
|
+
const npmCli = requireNpm(context);
|
|
361
|
+
for (const name of ["package.json", ".npmrc"]) {
|
|
362
|
+
if (!existsSync(resolve(sourceRoot, name))) {
|
|
363
|
+
throw new Error(
|
|
364
|
+
`${display(sourceRoot)}/${name} was not found. Check --source-root, or scaffold the project with --init.`
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
const npmrc = `${display(sourceRoot)}/.npmrc`;
|
|
369
|
+
const { fontAwesomeRegistry, hasAuth } = checkNpmrc(
|
|
370
|
+
readFileSync(resolve(sourceRoot, ".npmrc"), "utf8"),
|
|
371
|
+
npmrc
|
|
372
|
+
);
|
|
373
|
+
if (!fontAwesomeRegistry || !hasAuth) {
|
|
374
|
+
throw new Error(
|
|
375
|
+
`${npmrc} must contain @fortawesome:registry=${registry} and ${authKey}=${tokenPlaceholder}. Scaffold it with --init.`
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
const hasLockfile = existsSync(resolve(sourceRoot, "package-lock.json"));
|
|
379
|
+
if (context.isCi && !hasLockfile) {
|
|
380
|
+
throw new Error(
|
|
381
|
+
`${display(sourceRoot)}/package-lock.json is required in CI. Install locally first and commit the lockfile.`
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
if (context.isCi && values["refresh-lock"]) {
|
|
385
|
+
throw new Error(
|
|
386
|
+
"--refresh-lock updates the lockfile and is a local step. In CI, install from the committed lockfile."
|
|
387
|
+
);
|
|
388
|
+
}
|
|
389
|
+
const locked = hasLockfile && !values["refresh-lock"];
|
|
390
|
+
const environment = {
|
|
391
|
+
...context.env,
|
|
392
|
+
[tokenVariable]: await readToken(context)
|
|
393
|
+
};
|
|
394
|
+
for (const name of workspaceVariables) delete environment[name];
|
|
395
|
+
const status = context.runNpm({
|
|
396
|
+
npmCli,
|
|
397
|
+
args: [
|
|
398
|
+
locked ? "ci" : "install",
|
|
399
|
+
"--workspaces=false",
|
|
400
|
+
"--ignore-scripts",
|
|
401
|
+
"--no-audit",
|
|
402
|
+
"--no-fund",
|
|
403
|
+
pinRegistry
|
|
404
|
+
],
|
|
405
|
+
cwd: sourceRoot,
|
|
406
|
+
env: environment
|
|
407
|
+
});
|
|
408
|
+
if (status !== 0) return status;
|
|
409
|
+
const lockfile = `${display(sourceRoot)}/package-lock.json`;
|
|
410
|
+
log(
|
|
411
|
+
locked ? `Pro sources installed from ${lockfile}.` : `Pro sources installed. Review and commit ${lockfile}.`
|
|
412
|
+
);
|
|
413
|
+
return 0;
|
|
414
|
+
}
|
|
415
|
+
async function runFontAwesomeSetup(args, io = {}) {
|
|
416
|
+
const cwd = io.cwd ?? process.cwd();
|
|
417
|
+
const env = io.env ?? process.env;
|
|
418
|
+
const stderr = io.stderr ?? console.error;
|
|
419
|
+
const context = {
|
|
420
|
+
cwd,
|
|
421
|
+
env,
|
|
422
|
+
// TeamCity and GitHub Actions are detected directly; TeamCity does not set CI.
|
|
423
|
+
isCi: Boolean(env.CI) && env.CI !== "false" || Boolean(env.TEAMCITY_VERSION || env.GITHUB_ACTIONS),
|
|
424
|
+
log: io.stdout ?? console.log,
|
|
425
|
+
promptForToken: io.promptForToken ?? promptForTerminalToken,
|
|
426
|
+
runNpm: io.runNpm ?? spawnNpm,
|
|
427
|
+
display: (path) => relative(cwd, path) || "."
|
|
428
|
+
};
|
|
429
|
+
try {
|
|
430
|
+
const { values } = parseArgs({
|
|
431
|
+
args: [...args],
|
|
432
|
+
options: {
|
|
433
|
+
"source-root": { type: "string" },
|
|
434
|
+
"free-only": { type: "boolean" },
|
|
435
|
+
init: { type: "boolean" },
|
|
436
|
+
keys: { type: "string" },
|
|
437
|
+
manifest: { type: "string" },
|
|
438
|
+
force: { type: "boolean" },
|
|
439
|
+
"refresh-lock": { type: "boolean" },
|
|
440
|
+
help: { type: "boolean", short: "h" }
|
|
441
|
+
},
|
|
442
|
+
strict: true,
|
|
443
|
+
allowPositionals: false
|
|
444
|
+
});
|
|
445
|
+
if (values.help) {
|
|
446
|
+
context.log(usage);
|
|
447
|
+
return 0;
|
|
448
|
+
}
|
|
449
|
+
if (values["source-root"] === void 0) {
|
|
450
|
+
if (values.init || values.force || values["refresh-lock"]) {
|
|
451
|
+
throw new Error(
|
|
452
|
+
"--init, --force, and --refresh-lock apply only with --source-root."
|
|
453
|
+
);
|
|
454
|
+
}
|
|
455
|
+
return await setupAppRoot(values, context);
|
|
456
|
+
}
|
|
457
|
+
if (values["free-only"]) {
|
|
458
|
+
throw new Error(
|
|
459
|
+
"--free-only applies only to app-root setup; an isolated source project holds Pro packages."
|
|
460
|
+
);
|
|
461
|
+
}
|
|
462
|
+
if (values.init && values["refresh-lock"]) {
|
|
463
|
+
throw new Error(
|
|
464
|
+
"--refresh-lock applies to the install step, not --init."
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
const sourceRoot = resolve(cwd, values["source-root"]);
|
|
468
|
+
return values.init ? await initSourceProject(sourceRoot, values, context) : await installSourceProject(sourceRoot, values, context);
|
|
469
|
+
} catch (error) {
|
|
470
|
+
stderr(error instanceof Error ? error.message : "Unknown error");
|
|
471
|
+
return 1;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
export {
|
|
475
|
+
checkNpmrc,
|
|
476
|
+
runFontAwesomeSetup
|
|
477
|
+
};
|
package/docs/ICONS.md
CHANGED
|
@@ -53,6 +53,8 @@ Output must remain inside the consumer project, outside `node_modules` and the `
|
|
|
53
53
|
|
|
54
54
|
The API and Vite plugin accept `sourceRoot` to resolve installed packages from a separate source project without moving the output boundary. The CLI equivalent is `--source-root`. Relative source paths resolve from `root`; authentication still happens during npm installation, never in the generator or browser.
|
|
55
55
|
|
|
56
|
+
To install source packages, use the separate `featherk-fontawesome-setup` command described in [Consuming Applications](LICENSING.md#consuming-applications). It supports the standard Free and Pro app-root setup, Free-only setup, and the isolated source project. The generator does not install anything; the setup command is the only FeatherK tool that receives the package token.
|
|
57
|
+
|
|
56
58
|
## Generated Output and Source Checks
|
|
57
59
|
|
|
58
60
|
Generated declarations belong in TypeScript so selected keys receive type checking, but should be excluded from source linting, formatting, and version control. Add the exact output path separately to ESLint's global ignores, `.prettierignore`, and `.gitignore`; these are independent settings. Do not ignore all `.d.ts` files or exclude generated declarations from TypeScript.
|