@webjsdev/server 0.8.91 → 0.10.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 +4 -1
- package/index.d.ts +461 -102
- package/index.js +9 -4
- package/package.json +3 -3
- package/src/action-flash.js +185 -0
- package/src/action-input.js +208 -0
- package/src/action-params.js +156 -0
- package/src/action-route.js +8 -9
- package/src/action-seed.js +44 -5
- package/src/actions.js +112 -15
- package/src/api.js +67 -10
- package/src/app-layout.js +133 -0
- package/src/asset-hash.js +82 -49
- package/src/auth.js +612 -108
- package/src/browser-entries.js +2 -17
- package/src/build-epoch.js +1 -1
- package/src/cache-tags.js +5 -1
- package/src/check/helpers.js +85 -0
- package/src/check/rules-actions.js +37 -1
- package/src/check/rules-components.js +133 -28
- package/src/check/rules-imports.js +175 -4
- package/src/check/rules-route-modules.js +200 -0
- package/src/check/rules-routes.js +55 -0
- package/src/check/rules-routing.js +143 -95
- package/src/check/rules-typescript.js +24 -2
- package/src/check/rules.js +109 -9
- package/src/check/runner-support.js +53 -171
- package/src/check/runner.js +24 -6
- package/src/client-signals.js +171 -0
- package/src/component-scanner.js +22 -16
- package/src/context.js +152 -16
- package/src/cookie-store.js +262 -0
- package/src/core-exports.js +190 -0
- package/src/dev/bun-app-source.js +95 -5
- package/src/dev/config.js +7 -51
- package/src/dev/dev-service-worker.js +48 -0
- package/src/dev/dev-status.js +122 -0
- package/src/dev/esm-hot-hooks.js +373 -0
- package/src/dev/handler.js +632 -155
- package/src/dev/helpers.js +127 -96
- package/src/dev/hot-host.js +33 -6
- package/src/dev/poke-state.js +110 -0
- package/src/dev/serve.js +191 -133
- package/src/dev/server.js +109 -37
- package/src/dev/specifier-versions.js +125 -0
- package/src/dev/watch-ignore.js +49 -4
- package/src/dev/watch-poll.js +78 -0
- package/src/dev/watch-tree.js +59 -22
- package/src/dev-classify.js +59 -26
- package/src/dev-error.js +104 -3
- package/src/dev-hot-classify.js +291 -0
- package/src/dev-hot-client.js +245 -0
- package/src/dev-import.js +22 -5
- package/src/dev-overlay.js +26 -0
- package/src/dev-ready-probe.js +69 -0
- package/src/dev-regenerate.js +322 -78
- package/src/dev-reload-hold.js +49 -20
- package/src/dev-reload-reason.js +61 -0
- package/src/dev-reload-worker.js +62 -13
- package/src/dev-secret.js +73 -0
- package/src/dev-source-locations-hook.js +4 -1
- package/src/dev-source-locations.js +33 -2
- package/src/dev-styles.js +64 -6
- package/src/dev.js +0 -1
- package/src/env-files.js +124 -0
- package/src/file-storage.js +2 -2
- package/src/foreign-imports.js +228 -0
- package/src/form-action-identity.js +52 -25
- package/src/form-dispatch.js +150 -69
- package/src/html-cache.js +35 -4
- package/src/importmap.js +2 -2
- package/src/instrumentation.js +59 -3
- package/src/js-scan.js +5 -4
- package/src/jwe.js +155 -0
- package/src/lazy-components.js +166 -0
- package/src/listener-core.js +4 -2
- package/src/lit-hints.js +172 -0
- package/src/middleware-load.js +248 -0
- package/src/module-graph.js +73 -47
- package/src/module-side-effect.js +284 -0
- package/src/public-files.js +73 -0
- package/src/redirect-location.js +19 -0
- package/src/redirects.js +9 -1
- package/src/route-frontier.js +151 -0
- package/src/route-report.js +130 -0
- package/src/router.js +120 -13
- package/src/session.js +32 -10
- package/src/ssr/client-router-flag.js +1 -1
- package/src/ssr/document.js +49 -17
- package/src/ssr/head.js +228 -25
- package/src/ssr/preloads.js +31 -45
- package/src/ssr/render.js +243 -55
- package/src/ssr/responses.js +51 -4
- package/src/ssr/shipped-modules.js +52 -16
- package/src/testing.d.ts +31 -2
- package/src/testing.js +85 -17
- package/src/thenable-params.js +26 -0
- package/src/vendor/jspm.js +12 -1
- package/src/vendor/pins.js +9 -8
- package/src/vendor/scanner.js +14 -9
- package/src/vendor.js +5 -3
- package/webjs-config.schema.json +3 -8
- package/src/component-elision.js +0 -1852
- package/src/elision-differential.js +0 -101
- package/src/elision-report.js +0 -193
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The differential-elision primitives, shared by the framework's own guard and
|
|
3
|
-
* by the app-facing one (#1308).
|
|
4
|
-
*
|
|
5
|
-
* Elision's defining invariant is that removing the elided JS NEVER changes
|
|
6
|
-
* observable output. `packages/server/test/elision/differential-elision.test.js`
|
|
7
|
-
* proves that for the blog corpus by rendering every route with elision ON and
|
|
8
|
-
* OFF and diffing the bytes with the JS-loaded set masked out. `webjs elision
|
|
9
|
-
* --verify` runs the identical comparison over an arbitrary app's own route
|
|
10
|
-
* table, so every app gets the framework's own guard rather than inheriting a
|
|
11
|
-
* guarantee it cannot verify locally.
|
|
12
|
-
*
|
|
13
|
-
* `maskJsSet` therefore lives HERE and not in either consumer: it is the
|
|
14
|
-
* definition of "the JS-loaded set", and two copies is the one way the two
|
|
15
|
-
* guards could silently disagree about what the invariant even means.
|
|
16
|
-
*
|
|
17
|
-
* A leaf module: `node:path` is not even needed, and nothing here reads the
|
|
18
|
-
* filesystem or builds a graph.
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Mask the JS-loaded set so the diff sees only observable output. The
|
|
23
|
-
* importmap, the boot module script, and the modulepreload hints are
|
|
24
|
-
* REMOVED (not placeheld) because their COUNT differs on vs off, and the
|
|
25
|
-
* build-id hash is derived from them; collapsing whitespace afterwards
|
|
26
|
-
* means the differing-length preload block in the head leaves no trace. The
|
|
27
|
-
* two responses come from the identical SSR template pipeline, so any
|
|
28
|
-
* legitimate text/whitespace is the same on both sides regardless.
|
|
29
|
-
*
|
|
30
|
-
* Because the whole JS-loaded set is masked BY CONSTRUCTION, only the
|
|
31
|
-
* DANGEROUS direction (elision changed what the SSR emits) can fail a diff
|
|
32
|
-
* built on this. The SAFE direction (over-ship) lives entirely inside the
|
|
33
|
-
* masked region and is invisible here, by design.
|
|
34
|
-
*
|
|
35
|
-
* @param {string} html
|
|
36
|
-
* @returns {string}
|
|
37
|
-
*/
|
|
38
|
-
export function maskJsSet(html) {
|
|
39
|
-
return html
|
|
40
|
-
.replace(/<script type="importmap"[\s\S]*?<\/script>/g, '')
|
|
41
|
-
.replace(/<script type="module"[\s\S]*?<\/script>/g, '')
|
|
42
|
-
.replace(/<link rel="modulepreload"[^>]*>/g, '')
|
|
43
|
-
// The auto vendor preconnect / dns-prefetch (#243) is a connection-warming
|
|
44
|
-
// HINT derived from the served vendor map, which legitimately differs on vs
|
|
45
|
-
// off (a vendor reachable only through an elided component is pruned on the
|
|
46
|
-
// ON side, so its preconnect drops too, exactly like its modulepreload). It
|
|
47
|
-
// is part of the same JS-loaded set, so mask it. The blog corpus declares
|
|
48
|
-
// no `metadata.preconnect` of its own, so every preconnect/dns-prefetch
|
|
49
|
-
// there is the auto vendor one.
|
|
50
|
-
.replace(/<link rel="preconnect"[^>]*>/g, '')
|
|
51
|
-
.replace(/<link rel="dns-prefetch"[^>]*>/g, '')
|
|
52
|
-
.replace(/ data-webjs-build="[^"]*"/g, '')
|
|
53
|
-
.replace(/ data-webjs-src="[^"]*"/g, '')
|
|
54
|
-
// Render-clock nondeterminism: a page that SSRs a live wall-clock time
|
|
55
|
-
// ("posts loaded · 3:10:10 AM") ticks between the on and off captures. This
|
|
56
|
-
// is unrelated to elision (elision never changes rendered text), so
|
|
57
|
-
// normalise it. The counterfactual in differential-elision.test.js still
|
|
58
|
-
// fails because a removed element is a structural change, not a clock tick.
|
|
59
|
-
.replace(/\b\d{1,2}:\d{2}:\d{2}\s?[AP]M\b/gi, 'TIME')
|
|
60
|
-
.replace(/\s+/g, ' ')
|
|
61
|
-
.trim();
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* The URL paths of every STATIC page route in a route table, sorted and
|
|
66
|
-
* deduped. The corpus `webjs elision --verify` renders on both sides.
|
|
67
|
-
*
|
|
68
|
-
* A DYNAMIC route is excluded rather than guessed at: rendering it would mean
|
|
69
|
-
* inventing param values, and a route that 404s or throws on invented params
|
|
70
|
-
* proves nothing. The command reports the excluded routes by name and takes
|
|
71
|
-
* `--routes` for an author who wants to cover them with real values.
|
|
72
|
-
*
|
|
73
|
-
* Normalization is `routePathFromDir`'s, restated here so this leaf does not
|
|
74
|
-
* depend on `@webjsdev/mcp`: a route group `(group)` and a `_private` segment
|
|
75
|
-
* drop out of the URL, and the root `.` is `/`.
|
|
76
|
-
*
|
|
77
|
-
* @param {{ pages?: Array<{ routeDir?: string, paramNames?: string[] }> }} table a `buildRouteTable(appDir)` result
|
|
78
|
-
* @returns {string[]}
|
|
79
|
-
*/
|
|
80
|
-
export function staticPageRoutes(table) {
|
|
81
|
-
const out = new Set();
|
|
82
|
-
for (const r of table?.pages || []) {
|
|
83
|
-
if (r.paramNames && r.paramNames.length) continue;
|
|
84
|
-
out.add(routePath(r.routeDir));
|
|
85
|
-
}
|
|
86
|
-
return [...out].sort();
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* `blog` -> `/blog`, the root `.` -> `/`. Route groups and `_private` segments
|
|
91
|
-
* drop, the same normalization `buildRouteTable` uses for matching.
|
|
92
|
-
* @param {string|undefined} routeDir
|
|
93
|
-
* @returns {string}
|
|
94
|
-
*/
|
|
95
|
-
function routePath(routeDir) {
|
|
96
|
-
if (!routeDir || routeDir === '.') return '/';
|
|
97
|
-
const segs = routeDir
|
|
98
|
-
.split('/')
|
|
99
|
-
.filter((s) => !(s.startsWith('(') && s.endsWith(')')) && !s.startsWith('_'));
|
|
100
|
-
return segs.length ? '/' + segs.join('/') : '/';
|
|
101
|
-
}
|
package/src/elision-report.js
DELETED
|
@@ -1,193 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* App-level elision report (#646, extended in #1308).
|
|
3
|
-
*
|
|
4
|
-
* A reporting layer over the `analyzeElision` verdict, NOT a second analysis
|
|
5
|
-
* and NOT a build (WebJs is no-build; elision is the server's analysis pass,
|
|
6
|
-
* run at dev-server start and re-derived after each fs.watch rebuild). It
|
|
7
|
-
* builds the module graph, runs `analyzeElision` ONCE, and projects the whole
|
|
8
|
-
* verdict into a sorted, app-relative, JSON-serializable object.
|
|
9
|
-
*
|
|
10
|
-
* It carries BOTH directions:
|
|
11
|
-
*
|
|
12
|
-
* - `components`: every component module with `elided` or `shipped`, and for
|
|
13
|
-
* a shipped one the evidence that forced it plus the module that did the
|
|
14
|
-
* forcing. This is the direction where a wrong verdict silently loses
|
|
15
|
-
* interactivity in production, and before #1308 an app had no way to ask
|
|
16
|
-
* "what did you drop from my page?".
|
|
17
|
-
* - `routeModules`: every page / layout as `inert` / `import-only` /
|
|
18
|
-
* `shipped`, a module that ships whole naming the first client-effecting
|
|
19
|
-
* blocker that pins it (a non-component on a component-free path from the
|
|
20
|
-
* module, #963, or its own signal when the module itself is the cause).
|
|
21
|
-
* - `orphans`: a `class X extends WebComponent` with no literal-tag
|
|
22
|
-
* registration, either because there is no registration call or because
|
|
23
|
-
* the tag is computed. The scanner never sees it (invariant 3 requires a
|
|
24
|
-
* literal tag), so it always loses its elision verdict, its tag-to-module
|
|
25
|
-
* registry entry, and its preload hint, and `static interactive = true`
|
|
26
|
-
* cannot rescue it. Whether the ELEMENT still upgrades differs by shape:
|
|
27
|
-
* with no registration call it never does, and with a computed tag only
|
|
28
|
-
* while its importing module ships WHOLE, which is the exception rather
|
|
29
|
-
* than the rule. It is the one shape the verdict cannot speak for.
|
|
30
|
-
*
|
|
31
|
-
* Consumed by `webjs elision` (the CLI report + `--json`), the MCP
|
|
32
|
-
* `list_elision` tool, and `webjs doctor`'s two elision checks, which share
|
|
33
|
-
* ONE call so the module graph is built once per doctor run. Advisory only: a
|
|
34
|
-
* page legitimately MAY ship, and the analyser is biased toward shipping by
|
|
35
|
-
* design (server AGENTS invariant 7), so this never fails anything.
|
|
36
|
-
*
|
|
37
|
-
* Nothing is cached here. Every out-of-process consumer runs the analysis once
|
|
38
|
-
* and exits, so a cache would never hit and would only add a staleness bug.
|
|
39
|
-
*/
|
|
40
|
-
import { readFile, stat } from 'node:fs/promises';
|
|
41
|
-
import { join, relative, sep } from 'node:path';
|
|
42
|
-
|
|
43
|
-
import { buildModuleGraph } from './module-graph.js';
|
|
44
|
-
import { scanComponents, findOrphanComponents } from './component-scanner.js';
|
|
45
|
-
import { buildRouteTable } from './router.js';
|
|
46
|
-
import { analyzeElision } from './component-elision.js';
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* @typedef {'own'|'observed'|'closure'|'render'|'import'|'unreadable'} ElisionEvidence
|
|
50
|
-
* @typedef {{ file: string, tags: string[], verdict: 'elided'|'shipped', evidence: ElisionEvidence|null, reason: string|null, by: string|null }} ElisionComponentRow
|
|
51
|
-
* @typedef {{ file: string, verdict: 'inert'|'import-only'|'shipped', emits: string[], blocker: string|null, reason: string|null }} ElisionRouteModuleRow
|
|
52
|
-
* @typedef {{ file: string, className: string }} ElisionOrphanRow
|
|
53
|
-
* @typedef {{ components: number, elided: number, shipped: number, routeModules: number, inert: number, importOnly: number, shippedWhole: number, orphans: number }} ElisionSummary
|
|
54
|
-
* @typedef {{ analysed: boolean, skipped: 'no-app'|'elide-off'|'unanalysable'|null, components: ElisionComponentRow[], routeModules: ElisionRouteModuleRow[], orphans: ElisionOrphanRow[], summary: ElisionSummary }} ElisionReport
|
|
55
|
-
*/
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* @param {string} appDir
|
|
59
|
-
* @returns {Promise<ElisionReport>}
|
|
60
|
-
*/
|
|
61
|
-
export async function analyzeAppElision(appDir) {
|
|
62
|
-
// No `app/` means this is not a routable app (a bare component library, a
|
|
63
|
-
// lib-only fixture); nothing ships, so there is nothing to report on.
|
|
64
|
-
if (!(await pathExists(join(appDir, 'app')))) return empty('no-app');
|
|
65
|
-
// Elision off (opt-out) ships everything by design, so the verdict is moot.
|
|
66
|
-
if (!(await readElideEnabled(appDir))) return empty('elide-off');
|
|
67
|
-
|
|
68
|
-
let moduleGraph, components, routeTable, orphans;
|
|
69
|
-
try {
|
|
70
|
-
moduleGraph = await buildModuleGraph(appDir);
|
|
71
|
-
components = await scanComponents(appDir);
|
|
72
|
-
routeTable = await buildRouteTable(appDir);
|
|
73
|
-
// Not a second elision analysis: a lexical scan for the one shape elision
|
|
74
|
-
// never gets to judge, reusing what the dev server already runs.
|
|
75
|
-
orphans = await findOrphanComponents(appDir);
|
|
76
|
-
} catch {
|
|
77
|
-
// A malformed app the analysis cannot process degrades to no report (the
|
|
78
|
-
// dev server and `webjs check` surface the real problem).
|
|
79
|
-
return empty('unanalysable');
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Exactly the page + layout set the dev server feeds to analyzeElision, so
|
|
83
|
-
// the verdict matches (error / loading / not-found modules always ship and
|
|
84
|
-
// are never elision candidates, so they are not in scope here).
|
|
85
|
-
const routeModuleSet = new Set();
|
|
86
|
-
for (const page of routeTable.pages || []) {
|
|
87
|
-
if (page.file) routeModuleSet.add(page.file);
|
|
88
|
-
for (const f of page.layouts || []) routeModuleSet.add(f);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const { inertRouteModules, importOnlyRouteModules, shippedRouteModules, componentVerdicts } =
|
|
92
|
-
await analyzeElision(components, [...routeModuleSet], moduleGraph, (f) => readFile(f, 'utf8'), appDir);
|
|
93
|
-
|
|
94
|
-
const rel = (f) => (f == null ? null : relative(appDir, f) || f);
|
|
95
|
-
const byFile = (a, b) => a.file.localeCompare(b.file);
|
|
96
|
-
|
|
97
|
-
/** @type {ElisionComponentRow[]} */
|
|
98
|
-
const componentRows = [...componentVerdicts.entries()].map(([file, v]) => ({
|
|
99
|
-
file: /** @type {string} */ (rel(file)),
|
|
100
|
-
tags: v.tags,
|
|
101
|
-
verdict: v.shipped ? 'shipped' : 'elided',
|
|
102
|
-
evidence: v.shipped ? /** @type {ElisionEvidence|null} */ (v.evidence) : null,
|
|
103
|
-
reason: v.shipped ? relativizeReason(v.reason, appDir) : null,
|
|
104
|
-
by: v.shipped ? rel(v.by) : null,
|
|
105
|
-
})).sort(byFile);
|
|
106
|
-
|
|
107
|
-
/** @type {ElisionRouteModuleRow[]} */
|
|
108
|
-
const routeRows = [
|
|
109
|
-
...[...inertRouteModules].map((f) => ({
|
|
110
|
-
file: /** @type {string} */ (rel(f)), verdict: /** @type {const} */ ('inert'), emits: [], blocker: null, reason: null,
|
|
111
|
-
})),
|
|
112
|
-
...[...importOnlyRouteModules].map(([f, emits]) => ({
|
|
113
|
-
file: /** @type {string} */ (rel(f)), verdict: /** @type {const} */ ('import-only'),
|
|
114
|
-
emits: emits.map((e) => /** @type {string} */ (rel(e))).sort(), blocker: null, reason: null,
|
|
115
|
-
})),
|
|
116
|
-
...[...shippedRouteModules].map(([f, v]) => ({
|
|
117
|
-
file: /** @type {string} */ (rel(f)), verdict: /** @type {const} */ ('shipped'), emits: [],
|
|
118
|
-
blocker: rel(v.blocker), reason: relativizeReason(v.reason, appDir),
|
|
119
|
-
})),
|
|
120
|
-
].sort(byFile);
|
|
121
|
-
|
|
122
|
-
/** @type {ElisionOrphanRow[]} */
|
|
123
|
-
const orphanRows = orphans
|
|
124
|
-
.map((o) => ({ file: /** @type {string} */ (rel(o.file)), className: o.className }))
|
|
125
|
-
.sort((a, b) => byFile(a, b) || a.className.localeCompare(b.className));
|
|
126
|
-
|
|
127
|
-
return {
|
|
128
|
-
analysed: true, skipped: null,
|
|
129
|
-
components: componentRows, routeModules: routeRows, orphans: orphanRows,
|
|
130
|
-
summary: {
|
|
131
|
-
components: componentRows.length,
|
|
132
|
-
elided: componentRows.filter((c) => c.verdict === 'elided').length,
|
|
133
|
-
shipped: componentRows.filter((c) => c.verdict === 'shipped').length,
|
|
134
|
-
routeModules: routeRows.length,
|
|
135
|
-
inert: inertRouteModules.size,
|
|
136
|
-
importOnly: importOnlyRouteModules.size,
|
|
137
|
-
shippedWhole: shippedRouteModules.size,
|
|
138
|
-
orphans: orphanRows.length,
|
|
139
|
-
},
|
|
140
|
-
};
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* The no-verdict report, naming WHY nothing was analysed so a machine consumer
|
|
145
|
-
* can tell "elision is switched off" from "this is not an app".
|
|
146
|
-
* @param {'no-app'|'elide-off'|'unanalysable'} skipped
|
|
147
|
-
* @returns {ElisionReport}
|
|
148
|
-
*/
|
|
149
|
-
function empty(skipped) {
|
|
150
|
-
return {
|
|
151
|
-
analysed: false, skipped,
|
|
152
|
-
components: [], routeModules: [], orphans: [],
|
|
153
|
-
summary: { components: 0, elided: 0, shipped: 0, routeModules: 0, inert: 0, importOnly: 0, shippedWhole: 0, orphans: 0 },
|
|
154
|
-
};
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* `analyzeElision` bakes ABSOLUTE paths into its reason phrases (it works in
|
|
159
|
-
* absolute paths throughout). No absolute filesystem path may reach the JSON
|
|
160
|
-
* contract, so strip the app dir prefix wherever it appears in the sentence.
|
|
161
|
-
* A plain split/join rather than a regex, so an appDir containing regex
|
|
162
|
-
* metacharacters (a `(group)` directory, a `+`) is safe.
|
|
163
|
-
*
|
|
164
|
-
* @param {string|null} reason
|
|
165
|
-
* @param {string} appDir
|
|
166
|
-
* @returns {string|null}
|
|
167
|
-
*/
|
|
168
|
-
function relativizeReason(reason, appDir) {
|
|
169
|
-
if (reason == null) return null;
|
|
170
|
-
return reason.split(appDir.endsWith(sep) ? appDir : appDir + sep).join('');
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
/** Mirror of the dev server's elide flag: WEBJS_ELIDE override, then webjs.elide. */
|
|
174
|
-
async function readElideEnabled(appDir) {
|
|
175
|
-
const raw = process.env.WEBJS_ELIDE;
|
|
176
|
-
if (raw != null) {
|
|
177
|
-
const v = raw.trim().toLowerCase();
|
|
178
|
-
if (v === '0' || v === 'false' || v === 'off' || v === 'no') return false;
|
|
179
|
-
if (v === '1' || v === 'true' || v === 'on' || v === 'yes') return true;
|
|
180
|
-
}
|
|
181
|
-
try {
|
|
182
|
-
const pkg = JSON.parse(await readFile(join(appDir, 'package.json'), 'utf8'));
|
|
183
|
-
if (pkg && pkg.webjs && pkg.webjs.elide === false) return false;
|
|
184
|
-
} catch {
|
|
185
|
-
// No package.json, malformed JSON, or unreadable: keep the default (on).
|
|
186
|
-
}
|
|
187
|
-
return true;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/** @param {string} p @returns {Promise<boolean>} */
|
|
191
|
-
async function pathExists(p) {
|
|
192
|
-
try { await stat(p); return true; } catch { return false; }
|
|
193
|
-
}
|