getfilepress 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/LICENSE +21 -0
- package/README.md +178 -4
- package/package.json +89 -7
- package/packages/app/package.json +31 -0
- package/packages/app/src/app.d.ts +12 -0
- package/packages/app/src/app.html +12 -0
- package/packages/app/src/critical-theme.d.ts +4 -0
- package/packages/app/src/lib/content.server.ts +8 -0
- package/packages/app/src/lib/empty-theme.css +1 -0
- package/packages/app/src/lib/genie/GenieHost.svelte +16 -0
- package/packages/app/src/lib/genie/GeniePanel.svelte +429 -0
- package/packages/app/src/lib/genie/ops.ts +237 -0
- package/packages/app/src/lib/genie/store.ts +217 -0
- package/packages/app/src/lib/genie/types.ts +61 -0
- package/packages/app/src/lib/pages.server.ts +7 -0
- package/packages/app/src/lib/site.server.ts +52 -0
- package/packages/app/src/lib/theme-entry.ts +7 -0
- package/packages/app/src/routes/+layout.svelte +29 -0
- package/packages/app/src/routes/+layout.ts +10 -0
- package/packages/app/src/routes/+page.server.ts +28 -0
- package/packages/app/src/routes/+page.svelte +67 -0
- package/packages/app/src/routes/[slug]/+page.server.ts +20 -0
- package/packages/app/src/routes/[slug]/+page.svelte +47 -0
- package/packages/app/src/routes/page/[n]/+page.server.ts +21 -0
- package/packages/app/src/routes/page/[n]/+page.svelte +36 -0
- package/packages/app/src/routes/posts/[slug]/+page.server.ts +24 -0
- package/packages/app/src/routes/posts/[slug]/+page.svelte +95 -0
- package/packages/app/src/routes/robots.txt/+server.ts +11 -0
- package/packages/app/src/routes/rss.xml/+server.ts +13 -0
- package/packages/app/src/routes/sitemap.xml/+server.ts +19 -0
- package/packages/app/src/routes/tags/+page.server.ts +4 -0
- package/packages/app/src/routes/tags/+page.svelte +26 -0
- package/packages/app/src/routes/tags/[tag]/+page.server.ts +15 -0
- package/packages/app/src/routes/tags/[tag]/+page.svelte +23 -0
- package/packages/app/src/routes/topics/+page.server.ts +28 -0
- package/packages/app/src/routes/topics/+page.svelte +47 -0
- package/packages/app/src/routes/writing/+page.server.ts +12 -0
- package/packages/app/src/routes/writing/+page.svelte +38 -0
- package/packages/app/src/site-theme.d.ts +2 -0
- package/packages/app/static/.gitkeep +0 -0
- package/packages/app/tsconfig.json +15 -0
- package/packages/app/vite-plugin-critical-theme.ts +70 -0
- package/packages/app/vite-plugin-genie.ts +114 -0
- package/packages/app/vite.config.ts +141 -0
- package/packages/core/package.json +51 -0
- package/packages/core/src/lib/assets/favicon.svg +1 -0
- package/packages/core/src/lib/components/Newsletter.svelte +13 -0
- package/packages/core/src/lib/components/PostCard.svelte +34 -0
- package/packages/core/src/lib/components/PostIndex.svelte +86 -0
- package/packages/core/src/lib/components/SiteFooter.svelte +15 -0
- package/packages/core/src/lib/components/SiteHeader.svelte +28 -0
- package/packages/core/src/lib/config.ts +154 -0
- package/packages/core/src/lib/content/content.ts +193 -0
- package/packages/core/src/lib/content/feeds.ts +102 -0
- package/packages/core/src/lib/content/markdown.ts +78 -0
- package/packages/core/src/lib/content/pages.ts +103 -0
- package/packages/core/src/lib/content/parse.ts +214 -0
- package/packages/core/src/lib/content/rehype-figure.ts +79 -0
- package/packages/core/src/lib/content/types.ts +88 -0
- package/packages/core/src/lib/format.ts +25 -0
- package/packages/core/src/lib/index.ts +27 -0
- package/packages/core/src/lib/server.ts +33 -0
- package/packages/core/src/lib/styles/fonts.css +72 -0
- package/packages/core/src/lib/styles/theme.css +762 -0
- package/packages/core/src/lib/theme.ts +5 -0
- package/packages/import/package.json +27 -0
- package/packages/import/src/cli.ts +346 -0
- package/packages/import/src/discover.ts +190 -0
- package/packages/import/src/extract.ts +378 -0
- package/packages/import/src/fetch.ts +63 -0
- package/packages/import/src/html-to-md.ts +21 -0
- package/packages/import/src/images.ts +247 -0
- package/packages/import/src/inspire.ts +417 -0
- package/packages/import/src/ir.ts +109 -0
- package/packages/import/src/ollama.ts +145 -0
- package/packages/import/src/stock.ts +218 -0
- package/packages/import/src/theme.ts +478 -0
- package/packages/import/src/write-site.ts +371 -0
- package/packages/import/tsconfig.json +13 -0
- package/pnpm-workspace.yaml +2 -0
- package/scripts/create-site.mjs +260 -0
- package/scripts/filepress.mjs +273 -0
- package/scripts/link-embedded-packages.mjs +40 -0
- package/scripts/postinstall.mjs +56 -0
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Run the shared @filepress/app SvelteKit project against a content site.
|
|
3
|
+
//
|
|
4
|
+
// Modes:
|
|
5
|
+
// Monorepo: filepress <cmd> --site <name> → sites/<name>/
|
|
6
|
+
// Linked: filepress <cmd> → process.cwd() (content-only site)
|
|
7
|
+
// Explicit: filepress <cmd> --root <path>
|
|
8
|
+
//
|
|
9
|
+
// Install:
|
|
10
|
+
// Local: "getfilepress": "link:../filepress"
|
|
11
|
+
// Git: "getfilepress": "github:Catalyst-Forge-LLC/filepress#<tag-or-sha>"
|
|
12
|
+
// npm: "getfilepress": "^0.1.0"
|
|
13
|
+
|
|
14
|
+
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
|
|
15
|
+
import { dirname, join, resolve } from 'node:path';
|
|
16
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
17
|
+
import { spawn } from 'node:child_process';
|
|
18
|
+
|
|
19
|
+
const scriptDir = dirname(fileURLToPath(import.meta.url));
|
|
20
|
+
/** Install root of the filepress package (monorepo root, or linked/npm copy). */
|
|
21
|
+
const packageRoot = resolve(scriptDir, '..');
|
|
22
|
+
const appDir = join(packageRoot, 'packages', 'app');
|
|
23
|
+
const importDir = join(packageRoot, 'packages', 'import');
|
|
24
|
+
const sitesDir = join(packageRoot, 'sites');
|
|
25
|
+
|
|
26
|
+
const COMMANDS = new Set(['dev', 'build', 'preview', 'check']);
|
|
27
|
+
|
|
28
|
+
function fail(msg) {
|
|
29
|
+
console.error(`filepress: ${msg}`);
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** If npm skipped postinstall, still wire embedded @filepress/* packages. */
|
|
34
|
+
async function ensureEmbeddedLinks() {
|
|
35
|
+
if (!packageRoot.split(/[/\\]/).includes('node_modules')) return;
|
|
36
|
+
const corePkg = join(packageRoot, 'node_modules', '@filepress', 'core', 'package.json');
|
|
37
|
+
if (existsSync(corePkg)) return;
|
|
38
|
+
const mod = await import(pathToFileURL(join(scriptDir, 'link-embedded-packages.mjs')).href);
|
|
39
|
+
mod.linkEmbeddedPackages(packageRoot);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function parseArgs(argv) {
|
|
43
|
+
const args = { _: [], site: null, root: null, port: null, host: null, extra: [] };
|
|
44
|
+
for (let i = 0; i < argv.length; i++) {
|
|
45
|
+
const a = argv[i];
|
|
46
|
+
if (a === '--site') {
|
|
47
|
+
args.site = argv[++i];
|
|
48
|
+
if (!args.site) fail('`--site` requires a site name.');
|
|
49
|
+
} else if (a.startsWith('--site=')) {
|
|
50
|
+
args.site = a.slice('--site='.length);
|
|
51
|
+
} else if (a === '--root') {
|
|
52
|
+
args.root = argv[++i];
|
|
53
|
+
if (!args.root) fail('`--root` requires a path.');
|
|
54
|
+
} else if (a.startsWith('--root=')) {
|
|
55
|
+
args.root = a.slice('--root='.length);
|
|
56
|
+
} else if (a === '--port' || a === '-p') {
|
|
57
|
+
args.port = argv[++i];
|
|
58
|
+
if (!args.port) fail('`--port` requires a number.');
|
|
59
|
+
} else if (a.startsWith('--port=')) {
|
|
60
|
+
args.port = a.slice('--port='.length);
|
|
61
|
+
} else if (a === '--host') {
|
|
62
|
+
args.host = argv[++i] ?? 'true';
|
|
63
|
+
} else if (a.startsWith('--host=')) {
|
|
64
|
+
args.host = a.slice('--host='.length);
|
|
65
|
+
} else if (a === '--') {
|
|
66
|
+
args.extra.push(...argv.slice(i + 1));
|
|
67
|
+
break;
|
|
68
|
+
} else {
|
|
69
|
+
args._.push(a);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return args;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Resolve a package bin JS entry by walking node_modules from start dirs. */
|
|
76
|
+
function findPackageBin(pkgName, binName, starts = [appDir, importDir, packageRoot]) {
|
|
77
|
+
for (const start of starts) {
|
|
78
|
+
let dir = start;
|
|
79
|
+
for (;;) {
|
|
80
|
+
const pkgJsonPath = join(dir, 'node_modules', ...pkgName.split('/'), 'package.json');
|
|
81
|
+
if (existsSync(pkgJsonPath)) {
|
|
82
|
+
try {
|
|
83
|
+
const pkg = JSON.parse(readFileSync(pkgJsonPath, 'utf8'));
|
|
84
|
+
const binField = pkg.bin;
|
|
85
|
+
const binRel =
|
|
86
|
+
typeof binField === 'string' ? binField : binField?.[binName];
|
|
87
|
+
if (binRel) {
|
|
88
|
+
const abs = join(dirname(pkgJsonPath), binRel);
|
|
89
|
+
if (existsSync(abs)) return abs;
|
|
90
|
+
}
|
|
91
|
+
} catch {
|
|
92
|
+
/* continue */
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const parent = dirname(dir);
|
|
96
|
+
if (parent === dir) break;
|
|
97
|
+
dir = parent;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function runNodeBin(pkgName, binName, args, { cwd, env } = {}) {
|
|
104
|
+
const bin = findPackageBin(pkgName, binName);
|
|
105
|
+
if (!bin) {
|
|
106
|
+
fail(
|
|
107
|
+
`cannot find \`${binName}\` from package \`${pkgName}\`.\n` +
|
|
108
|
+
` Install getfilepress dependencies (pnpm/npm install), then retry.`
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
const child = spawn(process.execPath, [bin, ...args], {
|
|
112
|
+
cwd: cwd ?? packageRoot,
|
|
113
|
+
env: env ?? process.env,
|
|
114
|
+
stdio: 'inherit',
|
|
115
|
+
shell: false
|
|
116
|
+
});
|
|
117
|
+
child.on('exit', (code, signal) => {
|
|
118
|
+
if (signal) process.kill(process.pid, signal);
|
|
119
|
+
process.exit(code ?? 1);
|
|
120
|
+
});
|
|
121
|
+
return child;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** `filepress import …` → @filepress/import CLI (does not need a site root). */
|
|
125
|
+
function runImport(argv) {
|
|
126
|
+
const importCli = join(importDir, 'src', 'cli.ts');
|
|
127
|
+
if (!existsSync(importCli)) fail(`import CLI missing at ${importCli}`);
|
|
128
|
+
runNodeBin('tsx', 'tsx', [importCli, ...argv], { cwd: packageRoot });
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function listSites() {
|
|
132
|
+
if (!existsSync(sitesDir)) return [];
|
|
133
|
+
return readdirSync(sitesDir).filter((name) => {
|
|
134
|
+
const dir = join(sitesDir, name);
|
|
135
|
+
return statSync(dir).isDirectory() && existsSync(join(dir, 'filepress.config.ts'));
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function resolveSiteRoot(args) {
|
|
140
|
+
if (args.site && args.root) {
|
|
141
|
+
fail('use either `--site` or `--root`, not both.');
|
|
142
|
+
}
|
|
143
|
+
if (args.site) {
|
|
144
|
+
const siteRoot = join(sitesDir, args.site);
|
|
145
|
+
if (!existsSync(siteRoot)) fail(`site not found: sites/${args.site}`);
|
|
146
|
+
return siteRoot;
|
|
147
|
+
}
|
|
148
|
+
if (args.root) {
|
|
149
|
+
return resolve(args.root);
|
|
150
|
+
}
|
|
151
|
+
// Linked / external site: the content root is the caller's cwd.
|
|
152
|
+
return resolve(process.cwd());
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const argv = process.argv.slice(2);
|
|
156
|
+
await ensureEmbeddedLinks();
|
|
157
|
+
if (argv[0] === 'import') {
|
|
158
|
+
runImport(argv.slice(1));
|
|
159
|
+
} else {
|
|
160
|
+
runSiteCommand(argv);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function runSiteCommand(argv) {
|
|
164
|
+
const args = parseArgs(argv);
|
|
165
|
+
const command = args._[0];
|
|
166
|
+
|
|
167
|
+
if (!command || !COMMANDS.has(command)) {
|
|
168
|
+
fail(
|
|
169
|
+
`usage:\n` +
|
|
170
|
+
` filepress <${[...COMMANDS].join('|')}> --site <name> # monorepo\n` +
|
|
171
|
+
` filepress <${[...COMMANDS].join('|')}> # cwd is the site\n` +
|
|
172
|
+
` filepress <${[...COMMANDS].join('|')}> --root <path>\n` +
|
|
173
|
+
` filepress preview [--port 27777] # serves <site>/build (default 27777)\n` +
|
|
174
|
+
` filepress dev --port <n> # vite dev; optional fixed port\n` +
|
|
175
|
+
` filepress import --source <url> [--inspire <url>] …\n` +
|
|
176
|
+
`monorepo sites: ${listSites().join(', ') || '(none)'}`
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const siteRoot = resolveSiteRoot(args);
|
|
181
|
+
const configPath = join(siteRoot, 'filepress.config.ts');
|
|
182
|
+
if (!existsSync(configPath)) {
|
|
183
|
+
fail(
|
|
184
|
+
`no filepress.config.ts in ${siteRoot}\n` +
|
|
185
|
+
` (monorepo: pass --site <name>; linked site: run from the site root)`
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
if (!existsSync(appDir)) fail(`packages/app is missing under ${packageRoot}`);
|
|
189
|
+
|
|
190
|
+
if (!findPackageBin('vite', 'vite')) {
|
|
191
|
+
fail(
|
|
192
|
+
`filepress dependencies are missing (vite not found).\n` +
|
|
193
|
+
` If this is the engine repo: run \`pnpm install\` here, then retry.\n` +
|
|
194
|
+
` If getfilepress is an npm dependency: reinstall it in the site.`
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (args.port !== null) {
|
|
199
|
+
const n = Number(args.port);
|
|
200
|
+
if (!Number.isInteger(n) || n < 1 || n > 65535) {
|
|
201
|
+
fail(`\`--port\` must be an integer 1–65535 (got "${args.port}").`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const env = {
|
|
206
|
+
...process.env,
|
|
207
|
+
FILEPRESS_SITE_ROOT: siteRoot
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
// Preview must serve the site's own build/ — not packages/app/.svelte-kit
|
|
211
|
+
// (shared across sites; another agent's last build can leak onto vite preview).
|
|
212
|
+
if (command === 'preview') {
|
|
213
|
+
const buildDir = join(siteRoot, 'build');
|
|
214
|
+
if (!existsSync(join(buildDir, 'index.html'))) {
|
|
215
|
+
fail(
|
|
216
|
+
`no build at ${buildDir}\n` +
|
|
217
|
+
` Run \`filepress build\` first, then preview.`
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
const port = args.port || process.env.FILEPRESS_PORT || '27777';
|
|
221
|
+
console.log(`filepress: preview http://localhost:${port} → ${buildDir}`);
|
|
222
|
+
runNodeBin(
|
|
223
|
+
'sirv-cli',
|
|
224
|
+
'sirv',
|
|
225
|
+
[
|
|
226
|
+
buildDir,
|
|
227
|
+
'--dev',
|
|
228
|
+
'--port',
|
|
229
|
+
String(port),
|
|
230
|
+
'--host',
|
|
231
|
+
args.host && args.host !== 'true' ? args.host : '127.0.0.1'
|
|
232
|
+
],
|
|
233
|
+
{ cwd: appDir, env }
|
|
234
|
+
);
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (args.port) {
|
|
239
|
+
env.FILEPRESS_PORT = String(args.port);
|
|
240
|
+
console.log(`filepress: ${command} on http://localhost:${args.port} (site: ${siteRoot})`);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (command === 'check') {
|
|
244
|
+
const syncBin = findPackageBin('@sveltejs/kit', 'svelte-kit');
|
|
245
|
+
if (syncBin) {
|
|
246
|
+
const sync = spawn(process.execPath, [syncBin, 'sync'], {
|
|
247
|
+
cwd: appDir,
|
|
248
|
+
env,
|
|
249
|
+
stdio: 'inherit'
|
|
250
|
+
});
|
|
251
|
+
sync.on('exit', (code, signal) => {
|
|
252
|
+
if (signal) process.kill(process.pid, signal);
|
|
253
|
+
if (code) process.exit(code);
|
|
254
|
+
runNodeBin('svelte-check', 'svelte-check', ['--tsconfig', './tsconfig.json'], {
|
|
255
|
+
cwd: appDir,
|
|
256
|
+
env
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
runNodeBin('svelte-check', 'svelte-check', ['--tsconfig', './tsconfig.json'], {
|
|
262
|
+
cwd: appDir,
|
|
263
|
+
env
|
|
264
|
+
});
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// dev | build → vite
|
|
269
|
+
const viteArgs = [command];
|
|
270
|
+
if (args.host !== null) viteArgs.push('--host', args.host === 'true' ? 'true' : args.host);
|
|
271
|
+
viteArgs.push(...args.extra);
|
|
272
|
+
runNodeBin('vite', 'vite', viteArgs, { cwd: appDir, env });
|
|
273
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Link packages/{core,app,import} → node_modules/@filepress/* under packageRoot.
|
|
3
|
+
// Used by postinstall and by the CLI when install scripts were skipped.
|
|
4
|
+
import { existsSync, mkdirSync, rmSync, symlinkSync } from 'node:fs';
|
|
5
|
+
import { dirname, join, relative } from 'node:path';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @param {string} packageRoot
|
|
9
|
+
* @returns {number} number of links created/updated
|
|
10
|
+
*/
|
|
11
|
+
export function linkEmbeddedPackages(packageRoot) {
|
|
12
|
+
const packages = [
|
|
13
|
+
['core', '@filepress/core'],
|
|
14
|
+
['app', '@filepress/app'],
|
|
15
|
+
['import', '@filepress/import']
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
const scopeDir = join(packageRoot, 'node_modules', '@filepress');
|
|
19
|
+
mkdirSync(scopeDir, { recursive: true });
|
|
20
|
+
|
|
21
|
+
let n = 0;
|
|
22
|
+
for (const [folder, name] of packages) {
|
|
23
|
+
const target = join(packageRoot, 'packages', folder);
|
|
24
|
+
const linkPath = join(packageRoot, 'node_modules', ...name.split('/'));
|
|
25
|
+
if (!existsSync(target)) continue;
|
|
26
|
+
try {
|
|
27
|
+
rmSync(linkPath, { recursive: true, force: true });
|
|
28
|
+
} catch {
|
|
29
|
+
/* ignore */
|
|
30
|
+
}
|
|
31
|
+
const rel = relative(dirname(linkPath), target);
|
|
32
|
+
try {
|
|
33
|
+
symlinkSync(rel, linkPath, process.platform === 'win32' ? 'junction' : 'dir');
|
|
34
|
+
n++;
|
|
35
|
+
} catch (err) {
|
|
36
|
+
console.warn(`getfilepress: could not link ${name}: ${err.message}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return n;
|
|
40
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// When getfilepress is installed as a dependency (npm/pnpm), link the embedded
|
|
3
|
+
// workspace packages into node_modules/@filepress/* so `@filepress/core` etc.
|
|
4
|
+
// resolve. Skipped in the monorepo itself (pnpm workspace already links them).
|
|
5
|
+
|
|
6
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
7
|
+
import { dirname, join } from 'node:path';
|
|
8
|
+
import { fileURLToPath } from 'node:url';
|
|
9
|
+
import { spawnSync } from 'node:child_process';
|
|
10
|
+
import { linkEmbeddedPackages } from './link-embedded-packages.mjs';
|
|
11
|
+
|
|
12
|
+
const packageRoot = join(dirname(fileURLToPath(import.meta.url)), '..');
|
|
13
|
+
const isDependencyInstall = packageRoot.split(/[/\\]/).includes('node_modules');
|
|
14
|
+
|
|
15
|
+
if (!isDependencyInstall) {
|
|
16
|
+
process.exit(0);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
linkEmbeddedPackages(packageRoot);
|
|
20
|
+
|
|
21
|
+
const appDir = join(packageRoot, 'packages', 'app');
|
|
22
|
+
const svelteKitJs = findPackageBin('@sveltejs/kit', 'svelte-kit', [appDir, packageRoot]);
|
|
23
|
+
if (svelteKitJs) {
|
|
24
|
+
spawnSync(process.execPath, [svelteKitJs, 'sync'], {
|
|
25
|
+
cwd: appDir,
|
|
26
|
+
stdio: 'ignore',
|
|
27
|
+
env: process.env
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function findPackageBin(pkgName, binName, starts) {
|
|
32
|
+
for (const start of starts) {
|
|
33
|
+
let dir = start;
|
|
34
|
+
for (;;) {
|
|
35
|
+
const pkgJsonPath = join(dir, 'node_modules', ...pkgName.split('/'), 'package.json');
|
|
36
|
+
if (existsSync(pkgJsonPath)) {
|
|
37
|
+
try {
|
|
38
|
+
const pkg = JSON.parse(readFileSync(pkgJsonPath, 'utf8'));
|
|
39
|
+
const binField = pkg.bin;
|
|
40
|
+
const binRel =
|
|
41
|
+
typeof binField === 'string' ? binField : binField?.[binName];
|
|
42
|
+
if (binRel) {
|
|
43
|
+
const abs = join(dirname(pkgJsonPath), binRel);
|
|
44
|
+
if (existsSync(abs)) return abs;
|
|
45
|
+
}
|
|
46
|
+
} catch {
|
|
47
|
+
/* continue */
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const parent = dirname(dir);
|
|
51
|
+
if (parent === dir) break;
|
|
52
|
+
dir = parent;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return null;
|
|
56
|
+
}
|