@exodus/stasis-plugins 1.0.0-beta.12 → 1.0.0-beta.14

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/src/rollup.js CHANGED
@@ -6,12 +6,12 @@ import { dirname, isAbsolute, resolve as resolvePath } from 'node:path'
6
6
  import { fileURLToPath, pathToFileURL } from 'node:url'
7
7
 
8
8
  import { Bundle } from '@exodus/stasis-core/bundle'
9
- import { resolvePluginState } from './plugins.js'
9
+ import { attestedImport, resolvePluginState, unsupportedExtension } from './plugins.js'
10
10
  import { State } from '@exodus/stasis-core/state'
11
11
  // Pre-patch snapshot, not `import { readFile }`: under --fs=async that builtin is patched, and
12
12
  // this plugin loads after, so a direct import would route rollup's reads through the capture hook.
13
- import { realReadFile } from '@exodus/stasis-core/state-util'
14
- import { classifyExtension } from '@exodus/stasis-core/util'
13
+ import { inScope, realReadFile } from '@exodus/stasis-core/state-util'
14
+ import { classifyExtension, isPathWithin } from '@exodus/stasis-core/util'
15
15
 
16
16
  // Rollup chains resolveId/load hooks first-non-null-wins, so put this plugin FIRST in `plugins`:
17
17
  // listed later, a sibling resolver (node-resolve, commonjs) answers before stasis sees the edge --
@@ -145,20 +145,13 @@ export class StasisRollup {
145
145
  #classifyOrThrow(id) {
146
146
  const kind = classifyExtension(id, this.#resources)
147
147
  if (kind === 'unknown') {
148
- throw new Error(
149
- `StasisRollup: unsupported extension for '${id}' -- add its extension or filename to the plugin's resources option or stop importing it`
150
- )
148
+ throw new Error(unsupportedExtension('StasisRollup', `'${id}'`))
151
149
  }
152
150
  return kind
153
151
  }
154
152
 
155
153
  #inRoot(path) {
156
- try {
157
- this.#state.relative(path)
158
- return true
159
- } catch {
160
- return false
161
- }
154
+ return isPathWithin(this.#state.root, path)
162
155
  }
163
156
 
164
157
  async #resolveId(ctx, source, importer, opts) {
@@ -191,21 +184,15 @@ export class StasisRollup {
191
184
  this.#assertPersistableAttributes(attrs)
192
185
  // Forward import attributes (`with { type: 'json' }`) so plugin-capture round-trips to
193
186
  // plugin-load, both keyed under `* (with: ...)`.
194
- let url, format
195
- try {
196
- ;({ url, format } = this.#state.getImport(parentURL, source, { importAttributes: attrs }))
197
- } catch (err) {
198
- // No attested edge -- at a successful capture that's an EXTERNAL (never recorded as an
199
- // edge), so hand it back to rollup. File BYTES still fail closed at the load hook.
200
- // A non-MODULE_NOT_FOUND error is a real fault -- rethrow.
201
- if (err?.code === 'ERR_MODULE_NOT_FOUND') return null
202
- throw err
203
- }
187
+ // No attested edge: rollup's. File BYTES still fail closed at the load hook.
188
+ const attested = attestedImport(this.#state, parentURL, source, { importAttributes: attrs })
189
+ if (attested === undefined) return null
190
+ const { url, format } = attested
204
191
  const path = fileURLToPath(url)
205
192
  // Refuse a resource only when load mode would actually serve it: an out-of-scope workspace
206
193
  // resource under node_modules scope comes from disk via the user's asset plugin, exactly
207
194
  // like a capture build (the load hook defers it with the same scope gate).
208
- if (Bundle.isResourceFormat(format) && (this.#state.config.full || this.#state.inNodeModules(url))) {
195
+ if (Bundle.isResourceFormat(format) && inScope(this.#state, url)) {
209
196
  this.#refuseResourceLoad(path)
210
197
  }
211
198
  // KNOWN GAP: sibling resolution metadata (moduleSideEffects, syntheticNamedExports, meta)
@@ -253,7 +240,7 @@ export class StasisRollup {
253
240
  // a sibling's bare resolve() probe carries isEntry too but never loads, so only genuine
254
241
  // roots reach this gate.
255
242
  if (this.#state.config.full && ctx.getModuleInfo(id)?.isEntry) this.#state.assertEntry(url)
256
- if (!this.#state.config.full && !this.#state.inNodeModules(url)) return null
243
+ if (!inScope(this.#state, url)) return null
257
244
  const { source, format } = this.#state.getFile(url)
258
245
  if (Bundle.isResourceFormat(format)) this.#refuseResourceLoad(id)
259
246
  // Code is stored as UTF-8 text, exactly what rollup's parser expects.
package/src/webpack.js CHANGED
@@ -5,9 +5,9 @@ import { createRequire, isBuiltin } from 'node:module'
5
5
  import path from 'node:path'
6
6
  import { fileURLToPath, pathToFileURL } from 'node:url'
7
7
 
8
- import { resolvePluginState } from './plugins.js'
8
+ import { attestedImport, resolvePluginState, unsupportedExtension } from './plugins.js'
9
9
  import { State } from '@exodus/stasis-core/state'
10
- import { realReadFileSync } from '@exodus/stasis-core/state-util'
10
+ import { realReadFileSync, servedByBundle } from '@exodus/stasis-core/state-util'
11
11
  import { classifyExtension } from '@exodus/stasis-core/util'
12
12
 
13
13
  // CJS require anchored here, used only to locate the PROJECT's babel-plugin-module-resolver below.
@@ -95,14 +95,8 @@ export class StasisWebpack {
95
95
  const state = this.#state
96
96
  const orig = compiler.inputFileSystem
97
97
 
98
- // True iff `p` is in scope for load mode. state.relative throws outside state.root (treated
99
- // as out-of-scope: system files, scratch).
100
- const inScope = (p) => {
101
- if (typeof p !== 'string') return false
102
- try { state.relative(p) } catch { return false }
103
- if (state.config.full) return true
104
- return state.inNodeModules(pathToFileURL(p).toString())
105
- }
98
+ // True iff `p` is served from the bundle under load (servedByBundle).
99
+ const served = (p) => typeof p === 'string' && p !== '' && servedByBundle(state, p)
106
100
 
107
101
  // Family = the sidecar's own app-graph bundle first, then its parent (loaders + resolution-path
108
102
  // dirs the ambient preload captured). Both share one lockfile; no parent -> just [state].
@@ -121,7 +115,7 @@ export class StasisWebpack {
121
115
  // must keep `this` = orig; we override only readFile and stat.
122
116
  const wrappedReadFile = (p, options, callback) => {
123
117
  if (typeof options === 'function') { callback = options; options = undefined }
124
- if (inScope(p)) {
118
+ if (served(p)) {
125
119
  try {
126
120
  const src = serveFromBundle(p)
127
121
  const buf = Buffer.isBuffer(src) ? src : Buffer.from(src)
@@ -136,7 +130,7 @@ export class StasisWebpack {
136
130
  return orig.readFile(p, options, callback)
137
131
  }
138
132
  const wrappedStat = (p, callback) => {
139
- if (inScope(p)) {
133
+ if (served(p)) {
140
134
  // Directories have no bundle content, so serve a synthetic dir Stats from the family's
141
135
  // existence record (getFsStat) -- webpack's resolver stat()s intermediate dirs while walking.
142
136
  const url = pathToFileURL(p).toString()
@@ -168,21 +162,14 @@ export class StasisWebpack {
168
162
  const issuer = data.contextInfo?.issuer
169
163
  if (!issuer) return // entries: already absolute, inputFileSystem covers them
170
164
  // Out-of-scope parents were never the bundle's responsibility -- defer to webpack's resolver.
171
- if (!inScope(issuer)) return
165
+ if (!served(issuer)) return
172
166
  // Built-ins are never bundled or recorded as edges; skip up front. SECURITY: never consult
173
167
  // getImport for a builtin name, so a tampered bundle can't redirect `fs` to an arbitrary file.
174
168
  if (isBuiltin(data.request)) return
175
169
  const parentURL = pathToFileURL(issuer).toString()
176
- let resolved
177
- try {
178
- resolved = state.getImport(parentURL, data.request)
179
- } catch (err) {
180
- // No attested edge -- at a successful capture that's an EXTERNAL (never recorded as an
181
- // edge), so defer to webpack. File BYTES still fail closed at the inputFileSystem wrapper.
182
- // A non-MODULE_NOT_FOUND error is a real fault -- rethrow.
183
- if (err?.code === 'ERR_MODULE_NOT_FOUND') return
184
- throw err
185
- }
170
+ // No attested edge: defer to webpack. File BYTES still fail closed at the inputFileSystem wrapper.
171
+ const resolved = attestedImport(state, parentURL, data.request)
172
+ if (resolved === undefined) return
186
173
  // Redirect the resolver to the absolute bundle path; the stat wrapper then reports it exists.
187
174
  data.request = fileURLToPath(resolved.url)
188
175
  })
@@ -267,10 +254,7 @@ export class StasisWebpack {
267
254
 
268
255
  const kind = classifyExtension(filePath, this.#resources)
269
256
  if (kind === 'unknown') {
270
- throw new Error(
271
- `StasisWebpack: unsupported extension for '${filePath}' -- ` +
272
- `add its extension or filename to the plugin's resources option or stop importing it`
273
- )
257
+ throw new Error(unsupportedExtension('StasisWebpack', `'${filePath}'`))
274
258
  }
275
259
 
276
260
  const url = pathToFileURL(filePath).toString()