@somewhere-tech/cli 0.31.17 → 0.31.19
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/dist/commands/browser.js +1 -1
- package/dist/commands/browser.js.map +1 -1
- package/dist/commands/check.js +1 -2
- package/dist/commands/check.js.map +1 -1
- package/dist/commands/dev.js +18 -383
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/status.js +0 -36
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/typecheck.js +1 -1
- package/dist/commands/typecheck.js.map +1 -1
- package/dist/commands/verify.js +1 -1
- package/dist/commands/verify.js.map +1 -1
- package/dist/index.js +0 -2
- package/dist/index.js.map +1 -1
- package/dist/{local → lib}/browser-run.js +1 -1
- package/dist/lib/browser-run.js.map +1 -0
- package/dist/lib/chrome.js.map +1 -0
- package/dist/lib/envfile-write.js +2 -2
- package/dist/lib/envfile-write.js.map +1 -1
- package/dist/lib/frontend-dev.js +122 -0
- package/dist/lib/frontend-dev.js.map +1 -0
- package/dist/lib/init-agent-guide.js +15 -19
- package/dist/lib/init-agent-guide.js.map +1 -1
- package/dist/lib/init-green-template.js +1 -1
- package/dist/lib/platform-tools.js +4 -1
- package/dist/lib/platform-tools.js.map +1 -1
- package/dist/lib/project-urls.js +13 -0
- package/dist/lib/project-urls.js.map +1 -1
- package/npm-shrinkwrap.json +2 -17
- package/package.json +4 -6
- package/runtime/VENDOR.json +0 -2
- package/dist/commands/exec.js +0 -145
- package/dist/commands/exec.js.map +0 -1
- package/dist/local/browser-run.js.map +0 -1
- package/dist/local/chrome.js.map +0 -1
- package/dist/local/compiler-core.js +0 -51
- package/dist/local/compiler-core.js.map +0 -1
- package/dist/local/compiler.js +0 -683
- package/dist/local/compiler.js.map +0 -1
- package/dist/local/dev-server.js +0 -517
- package/dist/local/dev-server.js.map +0 -1
- package/dist/local/envfile.js +0 -40
- package/dist/local/envfile.js.map +0 -1
- package/dist/local/loader.js +0 -299
- package/dist/local/loader.js.map +0 -1
- package/dist/local/loopback.js +0 -129
- package/dist/local/loopback.js.map +0 -1
- package/dist/local/request-timing.js +0 -101
- package/dist/local/request-timing.js.map +0 -1
- package/dist/local/router.js +0 -114
- package/dist/local/router.js.map +0 -1
- package/dist/local/runtime.js +0 -345
- package/dist/local/runtime.js.map +0 -1
- package/dist/local/server.js +0 -309
- package/dist/local/server.js.map +0 -1
- package/dist/local/telemetry-filter.js +0 -120
- package/dist/local/telemetry-filter.js.map +0 -1
- package/node_modules/esbuild-wasm/LICENSE.md +0 -21
- package/node_modules/esbuild-wasm/README.md +0 -3
- package/node_modules/esbuild-wasm/bin/esbuild +0 -91
- package/node_modules/esbuild-wasm/esbuild.wasm +0 -0
- package/node_modules/esbuild-wasm/esm/browser.d.ts +0 -705
- package/node_modules/esbuild-wasm/esm/browser.js +0 -2393
- package/node_modules/esbuild-wasm/esm/browser.min.js +0 -20
- package/node_modules/esbuild-wasm/lib/browser.d.ts +0 -705
- package/node_modules/esbuild-wasm/lib/browser.js +0 -2438
- package/node_modules/esbuild-wasm/lib/browser.min.js +0 -22
- package/node_modules/esbuild-wasm/lib/main.d.ts +0 -705
- package/node_modules/esbuild-wasm/lib/main.js +0 -2051
- package/node_modules/esbuild-wasm/package.json +0 -19
- package/node_modules/esbuild-wasm/wasm_exec.js +0 -561
- package/node_modules/esbuild-wasm/wasm_exec_node.js +0 -39
- package/runtime/compiler/VENDOR.json +0 -64
- package/runtime/compiler/compile-core.cjs +0 -2007
- package/runtime/compiler/graph-contract.cjs +0 -124
- package/runtime/compiler/typed-functions.cjs +0 -575
- package/runtime/platform-context.mjs +0 -8193
- package/runtime/sw-init.mjs +0 -283
- /package/dist/{local → lib}/chrome.js +0 -0
package/dist/local/compiler.js
DELETED
|
@@ -1,683 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The platform's compiler, running on your machine.
|
|
3
|
-
*
|
|
4
|
-
* `somewhere dev` does not build your app with a second toolchain that
|
|
5
|
-
* resembles the platform's. It runs the SAME compiler the compile container
|
|
6
|
-
* runs on deploy — `runtime/compiler/compile-core.cjs`, vendored verbatim out
|
|
7
|
-
* of the monorepo with a drift guard (scripts/extract-runtime.mjs,
|
|
8
|
-
* test/compiler-vendor.test.mjs) — so what renders on localhost is the deploy
|
|
9
|
-
* artifact, not an approximation of it. The parity fixture
|
|
10
|
-
* (test/compiler-parity.test.mjs) asserts that byte for byte.
|
|
11
|
-
*
|
|
12
|
-
* The compiler is host-parameterized: everything that differs between the
|
|
13
|
-
* container and this machine goes in one `host` object. Concretely, three
|
|
14
|
-
* things differ.
|
|
15
|
-
*
|
|
16
|
-
* esbuild. The container runs native esbuild; the CLI runs esbuild-wasm at
|
|
17
|
-
* the SAME pinned version. Not a preference — the published CLI's
|
|
18
|
-
* supply-chain rule is that every production dependency sits inside the
|
|
19
|
-
* signed artifact, and native esbuild ships 24 optional platform packages of
|
|
20
|
-
* which only the build machine's own can ever be installed. esbuild-wasm is
|
|
21
|
-
* one platform-independent package. Same version in, same bytes out; the
|
|
22
|
-
* parity fixture proves it every run.
|
|
23
|
-
*
|
|
24
|
-
* Where the app's dependencies come from. The container has a baked
|
|
25
|
-
* node_modules; you have YOUR node_modules, which is better — it is the
|
|
26
|
-
* exact tree you installed. When it is absent or does not satisfy a pin, the
|
|
27
|
-
* CLI resolves the way the container does, into a cache under
|
|
28
|
-
* ~/.somewhere/dev-deps, silently. See resolveAppDependencies below.
|
|
29
|
-
*
|
|
30
|
-
* Where the BUILD toolchain comes from. Never your project. The compiler
|
|
31
|
-
* treats typescript / postcss / autoprefixer / tailwind as its own
|
|
32
|
-
* machinery, not your app's (it deliberately refuses to install them from
|
|
33
|
-
* your package.json), so the CLI runs the container's EXACT pins from
|
|
34
|
-
* runtime/compiler/VENDOR.json into ~/.somewhere/dev-toolchain. Borrowing
|
|
35
|
-
* your tailwind 3.3 or typescript 5.2 would compile different CSS and
|
|
36
|
-
* resolve aliases differently — the precise local-vs-deploy divergence this
|
|
37
|
-
* whole loop exists to remove.
|
|
38
|
-
*/
|
|
39
|
-
import { execFile } from 'node:child_process';
|
|
40
|
-
import { createHash } from 'node:crypto';
|
|
41
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
42
|
-
import { createRequire } from 'node:module';
|
|
43
|
-
import { homedir } from 'node:os';
|
|
44
|
-
import { dirname, join, resolve } from 'node:path';
|
|
45
|
-
import { fileURLToPath } from 'node:url';
|
|
46
|
-
import { promisify } from 'node:util';
|
|
47
|
-
import semver from 'semver';
|
|
48
|
-
import { readPlatformModules } from './compiler-core.js';
|
|
49
|
-
const execFileAsync = promisify(execFile);
|
|
50
|
-
/** A compile that failed, located in the developer's own source. */
|
|
51
|
-
export class CompileFailure extends Error {
|
|
52
|
-
locations;
|
|
53
|
-
constructor(message, locations) {
|
|
54
|
-
super(message);
|
|
55
|
-
this.locations = locations;
|
|
56
|
-
this.name = 'CompileFailure';
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
function packageRoot() {
|
|
60
|
-
// dist/local/compiler.js → package root is two levels up.
|
|
61
|
-
return join(dirname(fileURLToPath(import.meta.url)), '..', '..');
|
|
62
|
-
}
|
|
63
|
-
/**
|
|
64
|
-
* esbuild-wasm, forbidden from writing to its own stderr.
|
|
65
|
-
*
|
|
66
|
-
* esbuild-wasm is not a library call: it is a child `node` process running the
|
|
67
|
-
* WASM build, spoken to over a length-prefixed stdio protocol. That child is
|
|
68
|
-
* spawned by esbuild's own code as `stdio: ['pipe', 'pipe', 'inherit']` — its
|
|
69
|
-
* stderr is OUR file descriptor 2, whatever ours happens to be.
|
|
70
|
-
*
|
|
71
|
-
* The child's entry shim monkey-patches `fs.writeSync` to route fd 1 and fd 2
|
|
72
|
-
* through `process.stdout.write` / `process.stderr.write`. That patch is only
|
|
73
|
-
* sound while those streams are sockets or TTYs. When fd 2 is a plain FILE —
|
|
74
|
-
* `somewhere dev 2> dev.log`, any agent or CI harness that captures output to a
|
|
75
|
-
* file, a session recorder — node backs `process.stderr` with a SyncWriteStream
|
|
76
|
-
* whose `_write` calls `fs.writeSync`, which the patch has redirected back to
|
|
77
|
-
* `process.stderr.write`. Each write re-enters the stream it is servicing, the
|
|
78
|
-
* pending-write array grows without bound, and `Array.push` finally throws
|
|
79
|
-
* `RangeError: Invalid array length` — an unhandled 'error' event that kills the
|
|
80
|
-
* child. To the CLI the service simply stopped, and every later rebuild fails
|
|
81
|
-
* with "The service is no longer running": one bad import took the loop down for
|
|
82
|
-
* the rest of the session (tsk_d63b3b6a).
|
|
83
|
-
*
|
|
84
|
-
* Which builds write to stderr decides which builds crash, and that is exactly
|
|
85
|
-
* the reported split. The core parses every source file with
|
|
86
|
-
* `logLevel: 'silent'`, so a SYNTAX error is reported cleanly and the loop
|
|
87
|
-
* survives. The bundle step passes no logLevel, so esbuild's default ('warning')
|
|
88
|
-
* prints the "Could not resolve" banner — and any warning — to stderr. An
|
|
89
|
-
* unresolvable import therefore crashed on every input.
|
|
90
|
-
*
|
|
91
|
-
* We cannot choose the child's stdio, so we remove its reason to write.
|
|
92
|
-
* `logLevel: 'silent'` costs nothing: errors and warnings come back over the
|
|
93
|
-
* protocol in `result.errors` / `result.warnings`, which is where the CLI has
|
|
94
|
-
* always read them from — the banner was duplicate output that only ever
|
|
95
|
-
* appeared on a terminal. This is a host difference, so it belongs here with the
|
|
96
|
-
* other three, not in the vendored core.
|
|
97
|
-
*/
|
|
98
|
-
export function silenceEsbuild(esbuild) {
|
|
99
|
-
const silent = (options) => ({ ...(options ?? {}), logLevel: 'silent' });
|
|
100
|
-
return {
|
|
101
|
-
...esbuild,
|
|
102
|
-
build: (options) => esbuild.build(silent(options)),
|
|
103
|
-
context: (options) => esbuild.context(silent(options)),
|
|
104
|
-
transform: (input, options) => esbuild.transform(input, silent(options)),
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
/**
|
|
108
|
-
* True when the error says the bridge itself is gone rather than the source is
|
|
109
|
-
* bad. esbuild reports both service death and post-mortem calls with these two
|
|
110
|
-
* sentences and no structured errors.
|
|
111
|
-
*/
|
|
112
|
-
export function isServiceDeath(err) {
|
|
113
|
-
const message = err instanceof Error ? err.message : typeof err === 'string' ? err : '';
|
|
114
|
-
return /The service (?:was stopped|is no longer running)/i.test(message);
|
|
115
|
-
}
|
|
116
|
-
export function readVendorManifest(root = packageRoot()) {
|
|
117
|
-
return JSON.parse(readFileSync(join(root, 'runtime', 'compiler', 'VENDOR.json'), 'utf8'));
|
|
118
|
-
}
|
|
119
|
-
// ─── Dependency resolution, with zero steps for the developer ───────────────
|
|
120
|
-
function cacheHome() {
|
|
121
|
-
return join(process.env.SOMEWHERE_DEV_CACHE ?? join(homedir(), '.somewhere'), '');
|
|
122
|
-
}
|
|
123
|
-
/**
|
|
124
|
-
* Install `specs` into `dir` as a bare node_modules tree.
|
|
125
|
-
*
|
|
126
|
-
* `--ignore-scripts` is the same supply-chain guard the container uses, and it
|
|
127
|
-
* matters MORE here: this runs on your machine, not in a throwaway container.
|
|
128
|
-
* A dependency's postinstall never executes. esbuild bundles a package's files
|
|
129
|
-
* without running them, so frontend dependencies do not need install scripts.
|
|
130
|
-
*/
|
|
131
|
-
async function installInto(dir, specs) {
|
|
132
|
-
mkdirSync(dir, { recursive: true });
|
|
133
|
-
const manifest = join(dir, 'package.json');
|
|
134
|
-
if (!existsSync(manifest)) {
|
|
135
|
-
writeFileSync(manifest, JSON.stringify({ name: 'somewhere-dev-cache', private: true }, null, 2));
|
|
136
|
-
}
|
|
137
|
-
try {
|
|
138
|
-
await execFileAsync('npm', ['install', '--ignore-scripts', '--no-bin-links', '--no-audit', '--no-fund',
|
|
139
|
-
'--no-package-lock', '--prefix', dir, ...specs], { cwd: dir, maxBuffer: 32 * 1024 * 1024, env: { ...process.env, npm_config_ignore_scripts: 'true' } });
|
|
140
|
-
}
|
|
141
|
-
catch (err) {
|
|
142
|
-
const detail = err instanceof Error
|
|
143
|
-
? (err.stderr ?? err.message).toString().slice(-600)
|
|
144
|
-
: String(err);
|
|
145
|
-
throw new Error(`Could not resolve ${specs.join(', ')}: ${detail}`);
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
/** True when `dir/node_modules/<name>` is a package esbuild would resolve. */
|
|
149
|
-
function installed(dir, name) {
|
|
150
|
-
return existsSync(join(dir, 'node_modules', name, 'package.json'));
|
|
151
|
-
}
|
|
152
|
-
/**
|
|
153
|
-
* The build toolchain, at the container's exact pins, in the CLI's cache.
|
|
154
|
-
*
|
|
155
|
-
* One directory per group because tailwind v3 and v4 are the same package NAME
|
|
156
|
-
* at incompatible majors — the container isolates them for that reason and so
|
|
157
|
-
* does this. Groups are prepared only when the project needs them: a project
|
|
158
|
-
* with no Tailwind never installs a Tailwind engine.
|
|
159
|
-
*
|
|
160
|
-
* Returns the group directories, already populated.
|
|
161
|
-
*/
|
|
162
|
-
async function prepareToolchain(manifest, groups, onFirstInstall) {
|
|
163
|
-
const dirs = {};
|
|
164
|
-
for (const group of groups) {
|
|
165
|
-
const pins = manifest.toolchain[group];
|
|
166
|
-
if (!pins)
|
|
167
|
-
throw new Error(`the vendored compiler declares no "${group}" toolchain group`);
|
|
168
|
-
const dir = join(cacheHome(), 'dev-toolchain', `${group}-${digestOf(pins)}`);
|
|
169
|
-
dirs[group] = dir;
|
|
170
|
-
const missing = Object.entries(pins)
|
|
171
|
-
.filter(([name]) => !installed(dir, name))
|
|
172
|
-
.map(([name, range]) => `${name}@${range}`);
|
|
173
|
-
if (!missing.length)
|
|
174
|
-
continue;
|
|
175
|
-
onFirstInstall?.(`build toolchain (${missing.map((s) => s.split('@')[0] || s).join(', ')})`);
|
|
176
|
-
await installInto(dir, missing);
|
|
177
|
-
}
|
|
178
|
-
return dirs;
|
|
179
|
-
}
|
|
180
|
-
/**
|
|
181
|
-
* Install a dependency at the version the compile image BAKES, not the floor of
|
|
182
|
-
* the declared range (tsk_0312cf17 for React, tsk_a8cb3d23 for the rest of the
|
|
183
|
-
* baked set).
|
|
184
|
-
*
|
|
185
|
-
* The image keeps React 19 in a tree of its own and prefers it over installing
|
|
186
|
-
* the app's range; locally that set does not exist, so the compiler took its
|
|
187
|
-
* other branch and floor-pinned instead — `react: ^19.2.0` became exactly
|
|
188
|
-
* 19.2.0 while the image served 19.2.7. The same tree, compiled by the same
|
|
189
|
-
* compiler, against two different Reacts.
|
|
190
|
-
*
|
|
191
|
-
* That was never only about React. The image bakes ~20 more packages (router,
|
|
192
|
-
* query, forms, validation, dates, charts, icons…), and `ensureDeps` prefers a
|
|
193
|
-
* baked copy whenever its version satisfies the declared range — so `zod:
|
|
194
|
-
* ^3.23.0` served the image's baked 3.24.x on deploy and a floor-pinned 3.23.0
|
|
195
|
-
* locally. Same divergence, same fix: every group the vendor records is offered
|
|
196
|
-
* here, in the container's own order (baked first, then the isolated React 19
|
|
197
|
-
* set), each guarded by imagePinSatisfies so a pin is only ever taken when the
|
|
198
|
-
* container itself would have taken the baked copy.
|
|
199
|
-
*
|
|
200
|
-
* WHAT THIS DOES NOT COVER, stated plainly: when the project has its own
|
|
201
|
-
* node_modules, that tree is first on the search path and the local build uses
|
|
202
|
-
* the copy the developer installed. Deliberate — it is the exact tree they
|
|
203
|
-
* installed, and it is why a local loop beats a cloud one. These pins decide
|
|
204
|
-
* what the CLI's own dependency cache installs when a package is NOT already
|
|
205
|
-
* on the search path.
|
|
206
|
-
*
|
|
207
|
-
* The fix is to rewrite the spec, NOT to add a second node_modules tree. That
|
|
208
|
-
* was tried and is actively worse: a package that physically lives inside the
|
|
209
|
-
* dependency cache resolves `react` to its own sibling before any search path
|
|
210
|
-
* is consulted, so a separate pinned tree gets bundled ALONGSIDE the cache's
|
|
211
|
-
* copy — two Reacts in one bundle, which renders a blank page. The image has no
|
|
212
|
-
* such problem because it ends up with one flat tree, and this keeps one too.
|
|
213
|
-
*
|
|
214
|
-
* Pinning the spec also stops npm resolving React on its own as a peer of
|
|
215
|
-
* something else (react-router-dom pulls one in), which is how a warm cache
|
|
216
|
-
* ended up serving a third version again.
|
|
217
|
-
*/
|
|
218
|
-
export function applyImagePins(specs, deps, pinGroups) {
|
|
219
|
-
const groups = pinGroups.filter((g) => g && Object.keys(g).length > 0);
|
|
220
|
-
if (!groups.length)
|
|
221
|
-
return specs;
|
|
222
|
-
return specs.map((spec) => {
|
|
223
|
-
const at = spec.lastIndexOf('@');
|
|
224
|
-
const name = at > 0 ? spec.slice(0, at) : spec;
|
|
225
|
-
const range = deps[name];
|
|
226
|
-
for (const group of groups) {
|
|
227
|
-
const version = group[name];
|
|
228
|
-
if (version && imagePinSatisfies(version, range))
|
|
229
|
-
return `${name}@${version}`;
|
|
230
|
-
}
|
|
231
|
-
return spec;
|
|
232
|
-
});
|
|
233
|
-
}
|
|
234
|
-
/**
|
|
235
|
-
* May the image's baked VERSION stand in for the project's declared RANGE?
|
|
236
|
-
*
|
|
237
|
-
* This is `bakedSatisfies` from the compile core, asked from the other side:
|
|
238
|
-
* the container prefers its baked copy only when that copy satisfies the range,
|
|
239
|
-
* so the local loop may pin to it only under the same condition. Without the
|
|
240
|
-
* check a project that declares `react-router-dom: ^7.9.5` would be pinned to
|
|
241
|
-
* the image's baked 6.x — a build that works today refused tomorrow.
|
|
242
|
-
*
|
|
243
|
-
* The bias is the core's, one-directional: anything we cannot POSITIVELY prove
|
|
244
|
-
* satisfies — an unparseable range (`latest`, a git or alias spec), a version
|
|
245
|
-
* semver rejects, semver itself unavailable — returns false and leaves the
|
|
246
|
-
* core's own spec alone. A false negative costs an install of the floor-pin,
|
|
247
|
-
* which is exactly today's behaviour; a false positive would compile the
|
|
248
|
-
* developer's app against a library they did not ask for.
|
|
249
|
-
*/
|
|
250
|
-
export function imagePinSatisfies(version, range) {
|
|
251
|
-
const wanted = typeof range === 'string' && range.trim() ? range.trim() : '*';
|
|
252
|
-
try {
|
|
253
|
-
if (semver.validRange(wanted) === null)
|
|
254
|
-
return false;
|
|
255
|
-
return semver.satisfies(version, wanted, { includePrerelease: false });
|
|
256
|
-
}
|
|
257
|
-
catch {
|
|
258
|
-
return false;
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
function digestOf(value) {
|
|
262
|
-
return createHash('sha256').update(JSON.stringify(value)).digest('hex').slice(0, 12);
|
|
263
|
-
}
|
|
264
|
-
/**
|
|
265
|
-
* Where the APP's own dependencies come from, in resolution order.
|
|
266
|
-
*
|
|
267
|
-
* 1. The project's own node_modules, when it exists. This is the whole reason
|
|
268
|
-
* a local loop can beat a cloud one: the tree is already on disk and it is
|
|
269
|
-
* the exact tree the developer installed.
|
|
270
|
-
* 2. A CLI-managed cache keyed by the project's dependency map, for the case
|
|
271
|
-
* where node_modules is absent (a fresh `somewhere init`, a clone with no
|
|
272
|
-
* install) or a declared pin is not satisfied by what is installed.
|
|
273
|
-
*
|
|
274
|
-
* Keying the cache on the dependency map rather than sharing one flat tree
|
|
275
|
-
* means two projects on incompatible majors of the same package never
|
|
276
|
-
* overwrite each other, and two projects with identical dependencies share the
|
|
277
|
-
* install for free.
|
|
278
|
-
*/
|
|
279
|
-
function resolveAppDependencies(cwd, pkg, pinGroups) {
|
|
280
|
-
const search = [];
|
|
281
|
-
const projectModules = join(cwd, 'node_modules');
|
|
282
|
-
if (existsSync(projectModules))
|
|
283
|
-
search.push(projectModules);
|
|
284
|
-
const deps = pkg.dependencies ?? {};
|
|
285
|
-
// The pins are part of the cache identity (tsk_0312cf17): when the image
|
|
286
|
-
// bumps a baked package and the CLI re-vendors, the old cache is simply not
|
|
287
|
-
// reused, so a warm cache can never keep serving the version we moved off.
|
|
288
|
-
const cacheDir = join(cacheHome(), 'dev-deps', digestOf([Object.entries(deps).sort(), pinGroups]));
|
|
289
|
-
// The core decides what is actually missing (its baked-satisfies rules run
|
|
290
|
-
// against this exact search path) and calls host.installPackages for the
|
|
291
|
-
// rest. All we do here is make the cache dir part of the search path.
|
|
292
|
-
search.push(join(cacheDir, 'node_modules'));
|
|
293
|
-
return search;
|
|
294
|
-
}
|
|
295
|
-
// ─── Entry detection — the same rule the deploy pipeline uses ───────────────
|
|
296
|
-
//
|
|
297
|
-
// The HTML's `<script type="module" src>` tags are the single source of truth
|
|
298
|
-
// for what the compiler is allowed to touch. Mirrors
|
|
299
|
-
// worker/src/utils/module-entry.ts; a divergence here would compile a
|
|
300
|
-
// different entry locally than on deploy, which is the one thing this loop
|
|
301
|
-
// must never do.
|
|
302
|
-
export function isCompilableEntry(path) {
|
|
303
|
-
return /\.(?:tsx?|jsx|mts|cts)$/i.test(path);
|
|
304
|
-
}
|
|
305
|
-
export function collectModuleEntryScripts(files) {
|
|
306
|
-
const entries = new Set();
|
|
307
|
-
for (const [path, content] of Object.entries(files)) {
|
|
308
|
-
if (typeof content !== 'string')
|
|
309
|
-
continue;
|
|
310
|
-
if (!path.toLowerCase().endsWith('.html'))
|
|
311
|
-
continue;
|
|
312
|
-
const tagRe = /<script\b[^>]*>/gi;
|
|
313
|
-
let m;
|
|
314
|
-
while ((m = tagRe.exec(content)) !== null) {
|
|
315
|
-
const tag = m[0];
|
|
316
|
-
if (!/\btype\s*=\s*["']module["']/i.test(tag))
|
|
317
|
-
continue;
|
|
318
|
-
const srcMatch = /\bsrc\s*=\s*["']([^"']+)["']/i.exec(tag);
|
|
319
|
-
if (!srcMatch)
|
|
320
|
-
continue;
|
|
321
|
-
let src = srcMatch[1];
|
|
322
|
-
if (src.startsWith('/'))
|
|
323
|
-
src = src.slice(1);
|
|
324
|
-
if (isCompilableEntry(src) && files[src] !== undefined)
|
|
325
|
-
entries.add(src);
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
return entries;
|
|
329
|
-
}
|
|
330
|
-
export function detectBundleEntry(files) {
|
|
331
|
-
const entries = collectModuleEntryScripts(files);
|
|
332
|
-
const indexHtml = files['index.html'];
|
|
333
|
-
if (typeof indexHtml === 'string') {
|
|
334
|
-
for (const src of moduleScriptSrcs(indexHtml)) {
|
|
335
|
-
if (entries.has(src))
|
|
336
|
-
return src;
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
for (const e of entries)
|
|
340
|
-
return e;
|
|
341
|
-
return null;
|
|
342
|
-
}
|
|
343
|
-
/** Every `<script type="module" src>` in one HTML document, root-relative
|
|
344
|
-
* paths normalized to deploy keys. Shared by every entry rule below so they
|
|
345
|
-
* cannot disagree about what counts as a module script. */
|
|
346
|
-
function moduleScriptSrcs(html) {
|
|
347
|
-
const out = [];
|
|
348
|
-
const tagRe = /<script\b[^>]*>/gi;
|
|
349
|
-
let m;
|
|
350
|
-
while ((m = tagRe.exec(html)) !== null) {
|
|
351
|
-
const tag = m[0];
|
|
352
|
-
if (!/\btype\s*=\s*["']module["']/i.test(tag))
|
|
353
|
-
continue;
|
|
354
|
-
const srcMatch = /\bsrc\s*=\s*["']([^"']+)["']/i.exec(tag);
|
|
355
|
-
if (!srcMatch)
|
|
356
|
-
continue;
|
|
357
|
-
let src = srcMatch[1];
|
|
358
|
-
if (src.startsWith('/'))
|
|
359
|
-
src = src.slice(1);
|
|
360
|
-
out.push(src);
|
|
361
|
-
}
|
|
362
|
-
return out;
|
|
363
|
-
}
|
|
364
|
-
/**
|
|
365
|
-
* A module entry the platform serves EXACTLY as written: plain JavaScript.
|
|
366
|
-
*
|
|
367
|
-
* Deliberately the complement of isCompilableEntry, not an oversight in it.
|
|
368
|
-
* The deploy pipeline compiles the TypeScript family and .jsx and serves
|
|
369
|
-
* .js/.mjs/.cjs raw as native ESM, which is what protects browser-Babel
|
|
370
|
-
* projects from being compiled twice. Both are entries; they differ only in
|
|
371
|
-
* whether the platform compiles them.
|
|
372
|
-
*/
|
|
373
|
-
export function isRawServedEntry(path) {
|
|
374
|
-
return /\.[mc]?js$/i.test(path);
|
|
375
|
-
}
|
|
376
|
-
/** The client entry forms `somewhere dev` and `somewhere deploy` both accept,
|
|
377
|
-
* for the message a project with no usable entry gets. */
|
|
378
|
-
export const ACCEPTED_ENTRY_FORMS = '.tsx / .ts / .jsx / .mts / .cts are compiled by the platform; .js / .mjs / .cjs are served as written';
|
|
379
|
-
/**
|
|
380
|
-
* What the local loop should do with this project's frontend, decided from
|
|
381
|
-
* index.html exactly as the deploy pipeline decides it.
|
|
382
|
-
*
|
|
383
|
-
* `somewhere dev` used to refuse any project whose entry was not compilable,
|
|
384
|
-
* so a project with `<script type="module" src="/src/main.js">` could be
|
|
385
|
-
* deployed and served but not run locally (pfb_e32a4e630c45). Deploy does not
|
|
386
|
-
* refuse it — `bundleProject` simply returns null and the file is served raw
|
|
387
|
-
* — and the local loop's whole promise is that it is the same build. So:
|
|
388
|
-
*
|
|
389
|
-
* compiled → index.html points at a TS-family/.jsx entry: compile it.
|
|
390
|
-
* raw → index.html points at a .js/.mjs/.cjs entry that exists on
|
|
391
|
-
* disk: serve it untouched, which is what deploy does.
|
|
392
|
-
* none → index.html declares module scripts but none of them is a file
|
|
393
|
-
* in this directory. `declared` carries what it asked for, so
|
|
394
|
-
* the message can name the missing path.
|
|
395
|
-
*
|
|
396
|
-
* An index.html with NO module script at all is `raw` with no entry to name:
|
|
397
|
-
* a browser-Babel page or a plain static site is a legitimate project that
|
|
398
|
-
* deploys and must therefore run here too.
|
|
399
|
-
*/
|
|
400
|
-
export function resolveDevEntry(files) {
|
|
401
|
-
const compiled = detectBundleEntry(files);
|
|
402
|
-
if (compiled)
|
|
403
|
-
return { kind: 'compiled', entry: compiled };
|
|
404
|
-
const htmlFiles = Object.entries(files).filter(([path, content]) => typeof content === 'string' && path.toLowerCase().endsWith('.html'));
|
|
405
|
-
// index.html first — it is the document the deploy serves at the root.
|
|
406
|
-
htmlFiles.sort(([a], [b]) => (a === 'index.html' ? -1 : b === 'index.html' ? 1 : 0));
|
|
407
|
-
const declared = [];
|
|
408
|
-
for (const [, content] of htmlFiles) {
|
|
409
|
-
for (const src of moduleScriptSrcs(content)) {
|
|
410
|
-
declared.push(src);
|
|
411
|
-
if (isRawServedEntry(src) && files[src] !== undefined)
|
|
412
|
-
return { kind: 'raw', entry: src };
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
if (declared.length)
|
|
416
|
-
return { kind: 'none', declared };
|
|
417
|
-
return { kind: 'raw', entry: '' };
|
|
418
|
-
}
|
|
419
|
-
/**
|
|
420
|
-
* Point index.html at the compiled bundle instead of the raw entry, and add a
|
|
421
|
-
* <link> for each CSS chunk esbuild split out. The deploy pipeline does exactly
|
|
422
|
-
* this rewrite before serving (jsx-compile.ts) — the browser must receive the
|
|
423
|
-
* same HTML locally or the page it renders is not the page deploy renders.
|
|
424
|
-
*/
|
|
425
|
-
export function rewriteIndexHtml(html, entry, entryChunk, chunkNames) {
|
|
426
|
-
const escaped = entry.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
427
|
-
const reSourceEntry = new RegExp(`(<script\\b[^>]*\\bsrc\\s*=\\s*)(["'])/?${escaped}\\2`, 'i');
|
|
428
|
-
const reCompiledEntry = /(<script\b[^>]*\bsrc\s*=\s*)(["'])\/_compiled\/[^"']+\.js\2/i;
|
|
429
|
-
const newUrl = `/_compiled/${entryChunk}`;
|
|
430
|
-
let next = html.replace(reSourceEntry, `$1$2${newUrl}$2`);
|
|
431
|
-
if (next === html)
|
|
432
|
-
next = html.replace(reCompiledEntry, `$1$2${newUrl}$2`);
|
|
433
|
-
const cssChunks = chunkNames.filter((name) => name.endsWith('.css'));
|
|
434
|
-
const reCompiledCssLink = /<link\b[^>]*\brel\s*=\s*(["'])stylesheet\1[^>]*\bhref\s*=\s*(["'])\/_compiled\/[^"']+\.css\2[^>]*>/gi;
|
|
435
|
-
next = next.replace(reCompiledCssLink, '');
|
|
436
|
-
if (cssChunks.length) {
|
|
437
|
-
const linkTags = cssChunks.map((name) => `<link rel="stylesheet" href="/_compiled/${name}">`).join('\n');
|
|
438
|
-
const headClose = /<\/head\s*>/i;
|
|
439
|
-
const bodyOpen = /<body\b/i;
|
|
440
|
-
if (headClose.test(next))
|
|
441
|
-
next = next.replace(headClose, `${linkTags}\n</head>`);
|
|
442
|
-
else if (bodyOpen.test(next))
|
|
443
|
-
next = next.replace(bodyOpen, `${linkTags}\n<body`);
|
|
444
|
-
else
|
|
445
|
-
next = `${linkTags}\n${next}`;
|
|
446
|
-
}
|
|
447
|
-
return next;
|
|
448
|
-
}
|
|
449
|
-
export class LocalCompiler {
|
|
450
|
-
opts;
|
|
451
|
-
core = null;
|
|
452
|
-
searchPath = [];
|
|
453
|
-
depCacheDir = '';
|
|
454
|
-
esbuild = null;
|
|
455
|
-
constructor(opts) {
|
|
456
|
-
this.opts = opts;
|
|
457
|
-
}
|
|
458
|
-
/**
|
|
459
|
-
* Resolve everything the compiler needs before the first build, so every
|
|
460
|
-
* rebuild afterwards is pure compile time. Idempotent.
|
|
461
|
-
*/
|
|
462
|
-
/**
|
|
463
|
-
* The node_modules dirs the compiler resolves the app's dependencies from,
|
|
464
|
-
* in order. The local FUNCTION runtime resolves against the same list, so a
|
|
465
|
-
* function and a component import the same copy of a package (tsk_3269026d).
|
|
466
|
-
* Empty until prepare() has run.
|
|
467
|
-
*/
|
|
468
|
-
get moduleSearchPath() {
|
|
469
|
-
return this.searchPath;
|
|
470
|
-
}
|
|
471
|
-
async prepare(pkg, tailwindVersion) {
|
|
472
|
-
if (this.core)
|
|
473
|
-
return;
|
|
474
|
-
const root = packageRoot();
|
|
475
|
-
const manifest = readVendorManifest(root);
|
|
476
|
-
const requireVendored = createRequire(join(root, 'runtime', 'compiler', 'compile-core.cjs'));
|
|
477
|
-
const requireCli = createRequire(join(root, 'package.json'));
|
|
478
|
-
const groups = ['base', ...(tailwindVersion === 4 ? ['tw4'] : tailwindVersion === 3 ? ['tw3'] : [])];
|
|
479
|
-
const toolchainDirs = await prepareToolchain(manifest, groups, this.opts.onPrepare);
|
|
480
|
-
// The image's own versions, in the container's preference order: the baked
|
|
481
|
-
// tree first, the isolated React 19 set second (ensureDeps only reaches
|
|
482
|
-
// react19 when the baked React 18 does not satisfy the pin).
|
|
483
|
-
const imagePinGroups = [manifest.toolchain.baked ?? {}, manifest.toolchain.react19 ?? {}];
|
|
484
|
-
this.searchPath = resolveAppDependencies(this.opts.cwd, pkg, imagePinGroups);
|
|
485
|
-
this.depCacheDir = resolve(this.searchPath[this.searchPath.length - 1], '..');
|
|
486
|
-
const requireToolchain = (group) => createRequire(join(toolchainDirs[group], 'package.json'));
|
|
487
|
-
const requireBase = requireToolchain('base');
|
|
488
|
-
const requireTw3 = toolchainDirs.tw3 ? requireToolchain('tw3') : null;
|
|
489
|
-
const requireTw4 = toolchainDirs.tw4 ? requireToolchain('tw4') : null;
|
|
490
|
-
const { createCompileCore } = requireVendored('./compile-core.cjs');
|
|
491
|
-
// esbuild-wasm at the container's exact version. Loaded through the CLI's
|
|
492
|
-
// own require so it comes from the CLI's bundled tree, never the project's,
|
|
493
|
-
// and silenced so the WASM child never writes to an inherited stderr —
|
|
494
|
-
// see silenceEsbuild.
|
|
495
|
-
this.esbuild = requireCli('esbuild-wasm');
|
|
496
|
-
this.core = createCompileCore({
|
|
497
|
-
esbuild: silenceEsbuild(this.esbuild),
|
|
498
|
-
imageNodeModules: this.searchPath,
|
|
499
|
-
// No isolated React-19 set locally: it exists in the image to skip a cold
|
|
500
|
-
// install of react/react-dom, and here those either sit in the project's
|
|
501
|
-
// node_modules already or land in the dependency cache once and stay.
|
|
502
|
-
react19NodeModules: null,
|
|
503
|
-
tw4TailwindDir: toolchainDirs.tw4 ? join(toolchainDirs.tw4, 'node_modules', 'tailwindcss') : null,
|
|
504
|
-
requireImage: (spec) => {
|
|
505
|
-
// tailwindcss means v3 here, exactly as it does in the image; the v4
|
|
506
|
-
// engine is reached only through requireTw4.
|
|
507
|
-
if (spec === 'tailwindcss' && requireTw3)
|
|
508
|
-
return requireTw3(spec);
|
|
509
|
-
// semver rides along in the CLI's own dependencies already.
|
|
510
|
-
if (spec === 'semver')
|
|
511
|
-
return requireCli(spec);
|
|
512
|
-
return requireBase(spec);
|
|
513
|
-
},
|
|
514
|
-
requireTw4: requireTw4 ? (spec) => requireTw4(spec) : undefined,
|
|
515
|
-
// The container refuses to install without its per-build scoped proxy
|
|
516
|
-
// because it is egress-locked. This is the developer's own machine
|
|
517
|
-
// running their own npm; the proxy does not exist here and its absence
|
|
518
|
-
// is not a safety signal.
|
|
519
|
-
requiresPackageProxy: false,
|
|
520
|
-
installPackages: async ({ specs }) => {
|
|
521
|
-
const pinned = applyImagePins(specs, pkg.dependencies ?? {}, imagePinGroups);
|
|
522
|
-
this.opts.onPrepare?.(`${pinned.length} ${pinned.length === 1 ? 'dependency' : 'dependencies'} (${pinned.join(', ')})`);
|
|
523
|
-
await installInto(this.depCacheDir, pinned);
|
|
524
|
-
},
|
|
525
|
-
// The local loop is not a publication: no artifact upload capability, and
|
|
526
|
-
// an identity that says plainly where this build came from.
|
|
527
|
-
stamp: { source: `cli-vendored-${manifest.commit}`, toolchain: digestOf(manifest.files) },
|
|
528
|
-
});
|
|
529
|
-
}
|
|
530
|
-
/** Compile the project. Throws CompileFailure for an error in the developer's source. */
|
|
531
|
-
async compile(sources) {
|
|
532
|
-
const { files, binaryFiles } = sources;
|
|
533
|
-
const entry = detectBundleEntry(files);
|
|
534
|
-
if (!entry) {
|
|
535
|
-
throw new CompileFailure('No app entry found. index.html needs a <script type="module" src="/src/main.tsx"> pointing at your app entry — that tag is what the platform compiles, locally and on deploy.', []);
|
|
536
|
-
}
|
|
537
|
-
const transformEntries = [...collectModuleEntryScripts(files)].filter((path) => path !== entry);
|
|
538
|
-
if (!this.core)
|
|
539
|
-
throw new Error('LocalCompiler.prepare() must run before compile()');
|
|
540
|
-
const body = {
|
|
541
|
-
project_id: 'somewhere-dev-local',
|
|
542
|
-
build_id: 'somewhere-dev-local',
|
|
543
|
-
entry,
|
|
544
|
-
files,
|
|
545
|
-
binary_files: binaryFiles,
|
|
546
|
-
function_entries: [],
|
|
547
|
-
transform_entries: transformEntries,
|
|
548
|
-
package_json: files['package.json'],
|
|
549
|
-
tsconfig: files['tsconfig.json'],
|
|
550
|
-
vite_env: this.opts.viteEnv ?? {},
|
|
551
|
-
};
|
|
552
|
-
let result;
|
|
553
|
-
try {
|
|
554
|
-
result = await this.core.compile(body);
|
|
555
|
-
}
|
|
556
|
-
catch (err) {
|
|
557
|
-
// A dead bridge is not the developer's bug and must not end the session.
|
|
558
|
-
// esbuild caches one long-lived service per process, so once its child
|
|
559
|
-
// is gone EVERY later rebuild fails with the same sentence until the
|
|
560
|
-
// service is dropped. Drop it and build again: one save should never cost
|
|
561
|
-
// more than one build (tsk_d63b3b6a).
|
|
562
|
-
if (!isServiceDeath(err))
|
|
563
|
-
throw toCompileFailure(err);
|
|
564
|
-
try {
|
|
565
|
-
this.esbuild?.stop?.();
|
|
566
|
-
}
|
|
567
|
-
catch {
|
|
568
|
-
// stop() is best-effort teardown of an already-dead child.
|
|
569
|
-
}
|
|
570
|
-
try {
|
|
571
|
-
result = await this.core.compile(body);
|
|
572
|
-
}
|
|
573
|
-
catch (retryErr) {
|
|
574
|
-
if (!isServiceDeath(retryErr))
|
|
575
|
-
throw toCompileFailure(retryErr);
|
|
576
|
-
throw new CompileFailure('The local compiler service stopped and could not be restarted. Re-run `somewhere dev`; if it keeps happening, `somewhere feedback` with this message.', []);
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
if (!result.entry_chunk)
|
|
580
|
-
throw new CompileFailure('The compiler produced no entry chunk.', []);
|
|
581
|
-
const html = typeof files['index.html'] === 'string'
|
|
582
|
-
? rewriteIndexHtml(files['index.html'], entry, result.entry_chunk, Object.keys(result.chunks))
|
|
583
|
-
: null;
|
|
584
|
-
return {
|
|
585
|
-
entryChunk: result.entry_chunk,
|
|
586
|
-
chunks: result.chunks,
|
|
587
|
-
html,
|
|
588
|
-
entry,
|
|
589
|
-
warnings: result.warnings ?? [],
|
|
590
|
-
sourceDigest: result.source_digest,
|
|
591
|
-
artifacts: result.artifact_manifest.artifacts,
|
|
592
|
-
};
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
/**
|
|
596
|
-
* Turn a compiler error into something with a file and a line.
|
|
597
|
-
*
|
|
598
|
-
* The compiler reports a syntax error as SOURCE_PARSE_ERROR with every bad
|
|
599
|
-
* file joined into one message (`src/App.tsx: Expected ";" ...`). esbuild's own
|
|
600
|
-
* errors carry a structured location. Both become a list of
|
|
601
|
-
* {file, line, column, text} so the terminal and the browser overlay can point
|
|
602
|
-
* at the exact line instead of printing a paragraph.
|
|
603
|
-
*/
|
|
604
|
-
/**
|
|
605
|
-
* Root package name for a specifier: `@scope/pkg/sub` → `@scope/pkg`,
|
|
606
|
-
* `pkg/sub` → `pkg`. Mirrors the vendored core's specRootPkg, so the CLI and
|
|
607
|
-
* the compiler agree on what "the package" is in an import.
|
|
608
|
-
*/
|
|
609
|
-
function rootPackage(spec) {
|
|
610
|
-
return spec.startsWith('@') ? spec.split('/').slice(0, 2).join('/') : spec.split('/')[0];
|
|
611
|
-
}
|
|
612
|
-
/**
|
|
613
|
-
* One sentence saying what to do about an unresolvable import.
|
|
614
|
-
*
|
|
615
|
-
* Two cases, and they need opposite advice. A normal npm package is missing
|
|
616
|
-
* from the tree and `npm install` fixes it. A PLATFORM specifier is not on npm
|
|
617
|
-
* at all — `npm install somewhere` fetches an unrelated package — so the fix is
|
|
618
|
-
* always the import path, never an install. Which is which comes from the
|
|
619
|
-
* vendored compiler's own PLATFORM_MODULES list (readPlatformModules), the same
|
|
620
|
-
* enumeration that builds the compiler's virtual-module resolver, so the CLI
|
|
621
|
-
* cannot drift into calling one of our own modules a missing dependency.
|
|
622
|
-
*
|
|
623
|
-
* Returns undefined for anything that is not an unresolved-import error, which
|
|
624
|
-
* leaves every other message exactly as esbuild wrote it.
|
|
625
|
-
*/
|
|
626
|
-
export function resolutionHint(text) {
|
|
627
|
-
const match = /^Could not resolve "([^"]+)"/.exec(text);
|
|
628
|
-
if (!match)
|
|
629
|
-
return undefined;
|
|
630
|
-
const spec = match[1];
|
|
631
|
-
const platformRoots = new Set(readPlatformModules().map(rootPackage));
|
|
632
|
-
if (platformRoots.has(rootPackage(spec)) || readPlatformModules().includes(spec)) {
|
|
633
|
-
return `\`${spec}\` is provided by the platform, not npm — do not add it to package.json. Check the import path: platform modules are only available where the platform supplies them (\`somewhere/db\` belongs in db/schema.ts, which the deploy reads as data and never bundles).`;
|
|
634
|
-
}
|
|
635
|
-
return `Add \`${rootPackage(spec)}\` to the dependencies in your package.json and run \`npm install\`.`;
|
|
636
|
-
}
|
|
637
|
-
export function toCompileFailure(err) {
|
|
638
|
-
const locations = [];
|
|
639
|
-
// The compiler's own parse phase hands back {file, line, column, message}
|
|
640
|
-
// per bad file (source_errors). Use it verbatim — it is the most precise
|
|
641
|
-
// location available, and it is what a syntax error produces.
|
|
642
|
-
const sourceErrors = err?.source_errors;
|
|
643
|
-
if (Array.isArray(sourceErrors) && sourceErrors.length) {
|
|
644
|
-
for (const e of sourceErrors) {
|
|
645
|
-
locations.push({ file: e.file, line: e.line, column: e.column, text: e.message });
|
|
646
|
-
}
|
|
647
|
-
return new CompileFailure(sourceErrors[0].message, locations);
|
|
648
|
-
}
|
|
649
|
-
const esbuildErrors = err?.errors;
|
|
650
|
-
if (Array.isArray(esbuildErrors) && esbuildErrors.length) {
|
|
651
|
-
for (const e of esbuildErrors) {
|
|
652
|
-
locations.push({
|
|
653
|
-
file: e.location?.file ?? '',
|
|
654
|
-
line: e.location?.line,
|
|
655
|
-
column: e.location ? e.location.column + 1 : undefined,
|
|
656
|
-
text: e.text,
|
|
657
|
-
hint: resolutionHint(e.text),
|
|
658
|
-
});
|
|
659
|
-
}
|
|
660
|
-
return new CompileFailure(esbuildErrors[0].text, locations);
|
|
661
|
-
}
|
|
662
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
663
|
-
for (const part of message.split('; ')) {
|
|
664
|
-
// `src/App.tsx:12:4: Expected ";" but found "}"` and the shorter
|
|
665
|
-
// `src/App.tsx: Expected ...` the parse phase produces.
|
|
666
|
-
const withPos = /^([^\s:]+\.[a-z]+):(\d+):(\d+):\s*(.+)$/i.exec(part);
|
|
667
|
-
if (withPos) {
|
|
668
|
-
locations.push({
|
|
669
|
-
file: withPos[1],
|
|
670
|
-
line: Number(withPos[2]),
|
|
671
|
-
column: Number(withPos[3]),
|
|
672
|
-
text: withPos[4],
|
|
673
|
-
hint: resolutionHint(withPos[4]),
|
|
674
|
-
});
|
|
675
|
-
continue;
|
|
676
|
-
}
|
|
677
|
-
const fileOnly = /^([^\s:]+\.[a-z]+):\s*(.+)$/i.exec(part);
|
|
678
|
-
if (fileOnly)
|
|
679
|
-
locations.push({ file: fileOnly[1], text: fileOnly[2], hint: resolutionHint(fileOnly[2]) });
|
|
680
|
-
}
|
|
681
|
-
return new CompileFailure(message, locations);
|
|
682
|
-
}
|
|
683
|
-
//# sourceMappingURL=compiler.js.map
|