@exodus/stasis 1.0.0-alpha.2 → 1.0.0-beta.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/src/webpack.js CHANGED
@@ -1,48 +1,5 @@
1
- import assert from 'node:assert'
2
- import path from 'node:path'
3
- import { pathToFileURL } from 'node:url'
4
-
5
- import { State } from './state.js'
6
-
7
- const state = State.instance
8
- assert.ok(state, 'Stasis preload is not active')
9
-
10
- // Webpack plugin interface
11
- export class StasisWebpack {
12
- #seen = new Set()
13
-
14
- constructor(options) {
15
- assert.equal(options, undefined, 'No options expected')
16
- }
17
-
18
- apply(compiler) {
19
- compiler.plugin('normal-module-factory', (nmf) => {
20
- nmf.plugin('after-resolve', (data, callback) => {
21
- assert(data.resource === data.resourceResolveData.path)
22
- const filePath = path.resolve(data.resource) // resolve just in case, don't remove
23
-
24
- // TODO: record resolutions
25
- /*
26
- const meta = {
27
- resource: data.resource,
28
- source: data.resourceResolveData.context.issuer,
29
- query: data.rawRequest,
30
- // Note: data.userRequest is different and can contain loader prefixes
31
- moduleName: data.resourceResolveData.descriptionFileData.name,
32
- modulePackage: data.resourceResolveData.descriptionFilePath,
33
- }
34
-
35
- console.log(meta)
36
- //state.addImport(parentURL, specifier, url, { conditions: 'webpack ...', format })
37
- */
38
-
39
- if (!this.#seen.has(filePath)) {
40
- state.addFile(pathToFileURL(filePath).toString())
41
- this.#seen.add(filePath)
42
- }
43
-
44
- return callback(null, data)
45
- })
46
- })
47
- }
48
- }
1
+ // Public-export adapter: backs `@exodus/stasis/webpack`. The plugin lives in
2
+ // @exodus/stasis-plugins (which depends on @exodus/stasis-core for State /
3
+ // resolvePluginState). Internal code uses `@exodus/stasis-plugins/webpack` directly;
4
+ // this file exists only to back the package's `exports` map.
5
+ export * from '@exodus/stasis-plugins/webpack'
package/src/config.js DELETED
@@ -1,205 +0,0 @@
1
- import assert from 'node:assert/strict'
2
-
3
- const VALID_SCOPE = new Set(['node_modules', 'full'])
4
- const VALID_LOCK = new Set(['none', 'ignore', 'add', 'replace', 'frozen'])
5
- const VALID_BUNDLE = new Set(['none', 'ignore', 'add', 'replace', 'load'])
6
-
7
- const envDebugBool = (value) => Boolean(value && value !== '0')
8
-
9
- const OPTION_KEYS = ['scope', 'lock', 'bundle', 'bundleFile', 'debug']
10
-
11
- // Plugins accept the same options as Config but want to validate without constructing one,
12
- // since constructing State has side effects. Mirror Config's per-field validation.
13
- export function validatePluginOptions(label, options) {
14
- const rest = { ...options }
15
- for (const key of OPTION_KEYS) delete rest[key]
16
- assert.equal(Object.keys(rest).length, 0, `Unknown ${label} options: ${Object.keys(rest).join(', ')}`)
17
- const { scope, lock, bundle, bundleFile, debug } = options
18
- if (scope !== undefined) assert.ok(VALID_SCOPE.has(scope), `Invalid scope: ${scope}`)
19
- if (lock !== undefined) assert.ok(VALID_LOCK.has(lock), `Invalid lock: ${lock}`)
20
- if (bundle !== undefined) assert.ok(VALID_BUNDLE.has(bundle), `Invalid bundle: ${bundle}`)
21
- if (bundleFile !== undefined) assert.equal(typeof bundleFile, 'string', 'bundleFile must be a string')
22
- if (debug !== undefined) assert.equal(typeof debug, 'boolean', 'debug must be a boolean')
23
- }
24
-
25
- // When a plugin runs against a State that already exists (preload path), the active Config
26
- // is authoritative. Any options the plugin was given must agree with it.
27
- export function assertOptionsMatchConfig(config, options) {
28
- const { scope, lock, bundle, bundleFile, debug } = options
29
- try {
30
- if (scope !== undefined) assert.equal(config.scope, scope)
31
- if (lock !== undefined) assert.equal(config.lock, lock)
32
- if (bundle !== undefined) assert.equal(config.bundleMode, bundle)
33
- if (bundleFile !== undefined) assert.equal(config.bundleFile, bundleFile)
34
- if (debug !== undefined) assert.equal(config.debug, debug)
35
- } catch (cause) {
36
- throw new Error('Plugin options conflict with active stasis state', { cause })
37
- }
38
- }
39
-
40
- export class Config {
41
- #env
42
- #scope
43
- #lock
44
- #bundle
45
- #bundleFile
46
- #debug
47
-
48
- // Options match the CLI flags (lock/bundle/bundleFile/scope/debug). Env vars take effect at
49
- // construction time; if both env and an option are set they must agree.
50
- constructor(options = {}) {
51
- const { scope, lock, bundle, bundleFile, debug, ...rest } = options
52
- assert.equal(Object.keys(rest).length, 0, `Unknown Config options: ${Object.keys(rest).join(', ')}`)
53
-
54
- this.#env = {
55
- scope: process.env.EXODUS_STASIS_SCOPE || undefined,
56
- lock: process.env.EXODUS_STASIS_LOCK || undefined,
57
- bundle: process.env.EXODUS_STASIS_BUNDLE || undefined,
58
- bundleFile: process.env.EXODUS_STASIS_BUNDLE_FILE || undefined,
59
- debug: process.env.EXODUS_STASIS_DEBUG || undefined,
60
- }
61
-
62
- try {
63
- if (this.#env.scope !== undefined && scope !== undefined) assert.equal(this.#env.scope, scope)
64
- if (this.#env.lock !== undefined && lock !== undefined) assert.equal(this.#env.lock, lock)
65
- if (this.#env.bundle !== undefined && bundle !== undefined) assert.equal(this.#env.bundle, bundle)
66
- if (this.#env.bundleFile !== undefined && bundleFile !== undefined) {
67
- assert.equal(this.#env.bundleFile, bundleFile)
68
- }
69
- if (this.#env.debug !== undefined && debug !== undefined) {
70
- assert.equal(envDebugBool(this.#env.debug), debug)
71
- }
72
- } catch (cause) {
73
- throw new Error('Config options can not override stasis env', { cause })
74
- }
75
-
76
- this.#scope = this.#env.scope || scope || 'full'
77
- this.#lock = this.#env.lock || lock || 'add'
78
- this.#bundle = this.#env.bundle || bundle || 'none'
79
- this.#bundleFile = this.#env.bundleFile || bundleFile || undefined
80
- this.#debug = this.#env.debug !== undefined ? envDebugBool(this.#env.debug) : (debug ?? false)
81
-
82
- this.#checkInvariants()
83
- }
84
-
85
- // Per-field validation + cross-field invariants. Constructor (options path) and
86
- // loadConfig (disk path) both call this so the rules live in one place.
87
- #checkInvariants() {
88
- assert.ok(VALID_SCOPE.has(this.#scope), `Invalid scope: ${this.#scope}`)
89
- assert.ok(VALID_LOCK.has(this.#lock), `Invalid lock: ${this.#lock}`)
90
- assert.ok(VALID_BUNDLE.has(this.#bundle), `Invalid bundle: ${this.#bundle}`)
91
- assert.equal(typeof this.#debug, 'boolean', 'debug must be a boolean')
92
- if (this.#bundleFile !== undefined) assert.equal(typeof this.#bundleFile, 'string', 'bundleFile must be a string')
93
-
94
- // bundle=load needs a trust root for source bytes: frozen pins each file's sha512 in
95
- // the lockfile and we cross-check it on load; otherwise the bundle is itself
96
- // authoritative -- lock=none with no lockfile on disk, or lock=ignore with a lockfile
97
- // deliberately bypassed.
98
- // WARNING: lockfile attests only to source BYTES, not bundle metadata.
99
- if (this.#bundle === 'load' && this.#lock !== 'frozen' && this.#lock !== 'none' && this.#lock !== 'ignore') {
100
- throw new RangeError('bundle=load requires lock=(frozen|none|ignore)')
101
- }
102
-
103
- if (this.#lock === 'none' && this.#bundle === 'none') {
104
- throw new RangeError('lock=none requires bundle=(add|replace|load|ignore)')
105
- }
106
- }
107
-
108
- loadConfig(json) {
109
- const {
110
- scope = this.#scope,
111
- lock = this.#lock,
112
- bundle = this.#bundle,
113
- debug = this.#debug,
114
- ...rest
115
- } = JSON.parse(json)
116
- assert.equal(Object.keys(rest).length, 0)
117
- this.#scope = scope
118
- this.#lock = lock
119
- this.#bundle = bundle
120
- this.#debug = debug
121
- this.#checkInvariants()
122
-
123
- try {
124
- if (this.#env.scope !== undefined) assert.equal(this.#scope, this.#env.scope)
125
- if (this.#env.lock !== undefined) assert.equal(this.#lock, this.#env.lock)
126
- if (this.#env.bundle !== undefined) assert.equal(this.#bundle, this.#env.bundle)
127
- if (this.#env.debug !== undefined) assert.equal(this.#debug, envDebugBool(this.#env.debug))
128
- } catch (cause) {
129
- throw new Error('Flags/env can not override stasis.config.json', { cause })
130
- }
131
- }
132
-
133
- get bundleFile() {
134
- return this.#bundleFile
135
- }
136
-
137
- get debug() {
138
- return this.#debug
139
- }
140
-
141
- get values() {
142
- return { scope: this.#scope }
143
- }
144
-
145
- get bundle() {
146
- return this.#bundle !== 'none' && this.#bundle !== 'ignore'
147
- }
148
-
149
- get bundleMode() {
150
- return this.#bundle
151
- }
152
-
153
- get ignoreBundle() {
154
- return this.#bundle === 'ignore'
155
- }
156
-
157
- get writeBundle() {
158
- return this.#bundle === 'add' || this.#bundle === 'replace'
159
- }
160
-
161
- get replaceBundle() {
162
- return this.#bundle === 'replace'
163
- }
164
-
165
- get loadBundle() {
166
- return this.#bundle === 'load'
167
- }
168
-
169
- get full() {
170
- return this.#scope === 'full'
171
- }
172
-
173
- get frozen() {
174
- return this.#lock === 'frozen'
175
- }
176
-
177
- get useLockfile() {
178
- return this.#lock !== 'none' && this.#lock !== 'ignore'
179
- }
180
-
181
- get ignoreLockfile() {
182
- return this.#lock === 'ignore'
183
- }
184
-
185
- get writeLockfile() {
186
- return this.#lock === 'add' || this.#lock === 'replace'
187
- }
188
-
189
- get replaceLockfile() {
190
- return this.#lock === 'replace'
191
- }
192
-
193
- get scope() {
194
- return this.#scope
195
- }
196
-
197
- get lock() {
198
- return this.#lock
199
- }
200
-
201
- get json() {
202
- const data = { scope: this.#scope, lock: this.#lock, bundle: this.#bundle }
203
- return JSON.stringify(data, undefined, 2) + '\n'
204
- }
205
- }