@tokenoftrust/storefront-runner 1.3.4-rc.1 → 1.3.4-rc.2
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/apps/storefront/astro.config.mjs +13 -4
- package/apps/storefront/package.json +1 -1
- package/apps/storefront/src/lib/dev/runtimeStore.ts +74 -32
- package/apps/storefront/src/pages/cockpit.astro +4 -4
- package/package.json +6 -2
- package/pnpm-workspace.yaml +1 -0
- package/scripts/dev/checkout-watch.mjs +5 -24
- package/scripts/dev/file-browser.mjs +1 -1
- package/scripts/dev/transient-files.mjs +34 -0
- package/scripts/tot-dev.mjs +16 -3
|
@@ -16,10 +16,19 @@ import cloudflare from "@astrojs/cloudflare";
|
|
|
16
16
|
// npm before/as this storefront ships, else the pasted `npm i -g @tokenoftrust/cli@<v>` 404s.
|
|
17
17
|
// The CLI publish workflow (.github/workflows/publish-cli.yml) is the publisher; there is no
|
|
18
18
|
// deploy-time guard yet that asserts the embedded version exists on npm — a worthwhile add.
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const
|
|
19
|
+
// NEVER THROWS (like gitSha below): the published RUNNER tree prunes packages/cli, and a
|
|
20
|
+
// bare read here crashed `astro dev` for every standalone invited-dev boot. The runner's
|
|
21
|
+
// local page falls back to the floating spec — only the HOSTED cockpit needs the pin.
|
|
22
|
+
const cliVersion = (() => {
|
|
23
|
+
try {
|
|
24
|
+
return JSON.parse(
|
|
25
|
+
readFileSync(fileURLToPath(new URL("../../packages/cli/package.json", import.meta.url)), "utf8"),
|
|
26
|
+
).version;
|
|
27
|
+
} catch {
|
|
28
|
+
return null; // standalone runner tree — packages/cli isn't shipped
|
|
29
|
+
}
|
|
30
|
+
})();
|
|
31
|
+
const cliInstallSpec = cliVersion ? `@tokenoftrust/cli@${cliVersion}` : "@tokenoftrust/cli";
|
|
23
32
|
|
|
24
33
|
// The commit THIS build was cut from, baked in at BUILD time so /health can report
|
|
25
34
|
// exactly which code is live (see src/lib/health.ts + src/middleware). Prefer a CI-
|
|
@@ -1,16 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Per-
|
|
2
|
+
* Per-DEVELOPER "local dev runtime" latest-state — the LIVE half of the
|
|
3
3
|
* local→hosted activity bridge (G1). Where activityStore.ts holds a ring buffer
|
|
4
|
-
* of file-SAVE events, this holds
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* of file-SAVE events, this holds the most recent heartbeat a developer's LOCAL
|
|
5
|
+
* `tot dev`/`tot start` sent (see the CLI's dev-heartbeat.mjs). The hosted cockpit
|
|
6
|
+
* reads it to show a LIVE, clickable local-dev link + the running CLI version.
|
|
7
|
+
*
|
|
8
|
+
* SCOPED PER (tenant, developer email), NOT per tenant: a store can have many
|
|
9
|
+
* team members, each running their OWN local loop. Keying on the tenant alone
|
|
10
|
+
* made every teammate's heartbeat collapse into one latest-wins slot, so the
|
|
11
|
+
* owner saw "connected" whenever ANYONE with access was running `tot dev` —
|
|
12
|
+
* never their own machine specifically. The key is now
|
|
13
|
+
* `dev-runtime:<tenant>:<email>`; the POST writes under the HEARTBEATING dev's
|
|
14
|
+
* verified email, and the GET reads the VIEWER's own — so "connected" means
|
|
15
|
+
* "YOUR loop", not "someone on this store".
|
|
8
16
|
*
|
|
9
17
|
* Deliberately SEPARATE from the file-activity buffer: a heartbeat is
|
|
10
|
-
* latest-wins state, not an appended event, so it lives under its own
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* (low-sensitivity, per decision).
|
|
18
|
+
* latest-wins state, not an appended event, so it lives under its own key and
|
|
19
|
+
* never touches the ring buffer. No file CONTENT is ever stored — `url` is only
|
|
20
|
+
* the local `http://localhost:<port>/<tenant>/` (low-sensitivity, per decision).
|
|
14
21
|
*
|
|
15
22
|
* Liveness is computed SERVER-side from `aliveAt` (the ingest-time server clock,
|
|
16
23
|
* NOT the client's `at`) so a skewed local clock can't make a dead loop look
|
|
@@ -73,11 +80,12 @@ export interface DevRuntimeView extends Omit<DevRuntime, "email"> {
|
|
|
73
80
|
|
|
74
81
|
const KEY_PREFIX = "dev-runtime:";
|
|
75
82
|
/**
|
|
76
|
-
* Secondary index: traceId ->
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
83
|
+
* Secondary index: traceId -> {appDomain, email} (JSON), so support can look a
|
|
84
|
+
* developer's LIVE session up by the trace id minted at sign-in without scanning.
|
|
85
|
+
* Stores BOTH components because the record key is now per (tenant, email).
|
|
86
|
+
* Mirrors the developer+tenant index in activityIngestToken.ts; a KV store has no
|
|
87
|
+
* GSI, so the smallest correct discovery path is a maintained index key (written
|
|
88
|
+
* on every heartbeat, cleared on reset). Same TTL as the record.
|
|
81
89
|
*/
|
|
82
90
|
const TRACE_INDEX_PREFIX = "dev-runtime-by-trace:";
|
|
83
91
|
/**
|
|
@@ -107,8 +115,14 @@ const MAX_EMAIL_LEN = 320;
|
|
|
107
115
|
/** A UUID; a touch of headroom for other id shapes. */
|
|
108
116
|
const MAX_TRACE_LEN = 64;
|
|
109
117
|
|
|
110
|
-
|
|
111
|
-
|
|
118
|
+
/**
|
|
119
|
+
* The per-developer record key: `dev-runtime:<tenant>:<email>`. Email is the
|
|
120
|
+
* session principal (lowercased here for a stable key). An absent email degrades
|
|
121
|
+
* to a tenant-only suffix — never silently shared, just an unattributed slot the
|
|
122
|
+
* viewer's own-email read won't match.
|
|
123
|
+
*/
|
|
124
|
+
function key(appDomain: string, email: string | undefined): string {
|
|
125
|
+
return `${KEY_PREFIX}${appDomain}:${(email ?? "").toLowerCase()}`;
|
|
112
126
|
}
|
|
113
127
|
|
|
114
128
|
function traceKey(traceId: string): string {
|
|
@@ -188,10 +202,15 @@ export function shapeRuntime(
|
|
|
188
202
|
return rt;
|
|
189
203
|
}
|
|
190
204
|
|
|
191
|
-
/** Read
|
|
192
|
-
|
|
205
|
+
/** Read a developer's latest runtime (their tenant + email), or null when
|
|
206
|
+
* none/malformed. Never throws. */
|
|
207
|
+
export async function readRuntime(
|
|
208
|
+
kv: KVNamespace,
|
|
209
|
+
appDomain: string,
|
|
210
|
+
email: string | undefined,
|
|
211
|
+
): Promise<DevRuntime | null> {
|
|
193
212
|
try {
|
|
194
|
-
const raw = await kv.get(key(appDomain));
|
|
213
|
+
const raw = await kv.get(key(appDomain, email));
|
|
195
214
|
if (!raw) return null;
|
|
196
215
|
const parsed = JSON.parse(raw);
|
|
197
216
|
if (!parsed || typeof parsed !== "object" || typeof parsed.aliveAt !== "number") return null;
|
|
@@ -201,17 +220,21 @@ export async function readRuntime(kv: KVNamespace, appDomain: string): Promise<D
|
|
|
201
220
|
}
|
|
202
221
|
}
|
|
203
222
|
|
|
204
|
-
/** Overwrite
|
|
205
|
-
* When the record carries a traceId, also (re)write the
|
|
206
|
-
*
|
|
223
|
+
/** Overwrite a developer's runtime with their latest heartbeat (latest-wins, TTL'd),
|
|
224
|
+
* scoped to (tenant, email). When the record carries a traceId, also (re)write the
|
|
225
|
+
* by-trace index (storing BOTH tenant + email) so support can resolve
|
|
226
|
+
* trace -> record without a scan. */
|
|
207
227
|
export async function writeRuntime(
|
|
208
228
|
kv: KVNamespace,
|
|
209
229
|
appDomain: string,
|
|
230
|
+
email: string | undefined,
|
|
210
231
|
rt: DevRuntime,
|
|
211
232
|
): Promise<void> {
|
|
212
|
-
await kv.put(key(appDomain), JSON.stringify(rt), { expirationTtl: TTL_SECONDS });
|
|
233
|
+
await kv.put(key(appDomain, email), JSON.stringify(rt), { expirationTtl: TTL_SECONDS });
|
|
213
234
|
if (rt.traceId) {
|
|
214
|
-
await kv.put(traceKey(rt.traceId), appDomain,
|
|
235
|
+
await kv.put(traceKey(rt.traceId), JSON.stringify({ appDomain, email: email ?? "" }), {
|
|
236
|
+
expirationTtl: TTL_SECONDS,
|
|
237
|
+
});
|
|
215
238
|
}
|
|
216
239
|
}
|
|
217
240
|
|
|
@@ -227,17 +250,36 @@ export async function readRuntimeByTrace(
|
|
|
227
250
|
traceId: string,
|
|
228
251
|
): Promise<DevRuntime | null> {
|
|
229
252
|
if (!traceId) return null;
|
|
230
|
-
const
|
|
253
|
+
const ptr = await kv.get(traceKey(traceId));
|
|
254
|
+
if (!ptr) return null;
|
|
255
|
+
// Pointer is JSON {appDomain, email}. A bare-string pointer is a legacy
|
|
256
|
+
// (pre per-developer) entry with no email to scope by — treat as unresolvable
|
|
257
|
+
// (it re-establishes on the next heartbeat, well within the 1h TTL).
|
|
258
|
+
let appDomain: string | undefined;
|
|
259
|
+
let email: string | undefined;
|
|
260
|
+
try {
|
|
261
|
+
const parsed = JSON.parse(ptr);
|
|
262
|
+
if (parsed && typeof parsed === "object") {
|
|
263
|
+
appDomain = parsed.appDomain;
|
|
264
|
+
email = parsed.email;
|
|
265
|
+
}
|
|
266
|
+
} catch {
|
|
267
|
+
return null;
|
|
268
|
+
}
|
|
231
269
|
if (!appDomain) return null;
|
|
232
|
-
return readRuntime(kv, appDomain);
|
|
270
|
+
return readRuntime(kv, appDomain, email);
|
|
233
271
|
}
|
|
234
272
|
|
|
235
|
-
/** Delete
|
|
236
|
-
* reading as up immediately instead of lingering until the
|
|
237
|
-
* Also drops the by-trace index entry for its current trace
|
|
238
|
-
export async function clearRuntime(
|
|
239
|
-
|
|
240
|
-
|
|
273
|
+
/** Delete a developer's runtime record (their tenant + email) — a "true reset" so a
|
|
274
|
+
* killed loop stops reading as up immediately instead of lingering until the
|
|
275
|
+
* TTL/live-window expires. Also drops the by-trace index entry for its current trace. */
|
|
276
|
+
export async function clearRuntime(
|
|
277
|
+
kv: KVNamespace,
|
|
278
|
+
appDomain: string,
|
|
279
|
+
email: string | undefined,
|
|
280
|
+
): Promise<void> {
|
|
281
|
+
const existing = await readRuntime(kv, appDomain, email);
|
|
282
|
+
await kv.delete(key(appDomain, email));
|
|
241
283
|
if (existing?.traceId) await kv.delete(traceKey(existing.traceId));
|
|
242
284
|
}
|
|
243
285
|
|
|
@@ -1927,7 +1927,7 @@ const hostedActivityScript = `
|
|
|
1927
1927
|
<h1>Get your store running.
|
|
1928
1928
|
<span class="info" id="info1">
|
|
1929
1929
|
<button class="info-btn" type="button" aria-label="What does this command do?">i</button>
|
|
1930
|
-
<span class="info-pop" role="tooltip">Installs the <code>tot</code> command, signs your terminal in with a single-use code, checks out your store, runs it, and opens your browser. No Docker, no browser sign-in, nothing to provision. From then on it's just <code>tot start</code>.</span>
|
|
1930
|
+
<span class="info-pop" role="tooltip">Installs the <code>tot</code> command, signs your terminal in with a single-use code, checks out your store into a new <code>{appDomain}</code> folder, runs it, and opens your browser. No Docker, no browser sign-in, nothing to provision. From then on it's just <code>tot start</code>.</span>
|
|
1931
1931
|
</span>
|
|
1932
1932
|
</h1>
|
|
1933
1933
|
<p class="goal">{hasStoreAccess
|
|
@@ -1951,10 +1951,10 @@ const hostedActivityScript = `
|
|
|
1951
1951
|
</div>
|
|
1952
1952
|
)}
|
|
1953
1953
|
<p class="cmd-note" id="cli-cmd-note">{hasStoreAccess
|
|
1954
|
-
? <>This mints a <em>single-use, short-lived</em> sign-in code, shows the full command here, and copies it to your clipboard —
|
|
1955
|
-
: <>You'll need <em>Node.js and your invite</em>. Sign in through your invite link and this page hands you a one-paste setup command — no Docker, nothing to provision.</>}</p>
|
|
1954
|
+
? <>This mints a <em>single-use, short-lived</em> sign-in code, shows the full command here, and copies it to your clipboard. Paste it <em>from the folder where you keep projects</em> — it creates a <code>{appDomain}</code> folder there with your store's code, signs you in, runs your store, and opens your browser. Nothing to retype.</>
|
|
1955
|
+
: <>You'll need <em>Node.js 22.12+ (24 LTS recommended) and your invite</em>. Sign in through your invite link and this page hands you a one-paste setup command — no Docker, nothing to provision.</>}</p>
|
|
1956
1956
|
|
|
1957
|
-
<p class="cmd-steps">Prefer the steps? <code>npm i -g {cliInstallSpec}</code>, then <code>tot checkout {appDomain}</code>, <code>cd {appDomain}</code>, <code>tot dev</code>. New to Node? Get
|
|
1957
|
+
<p class="cmd-steps">Prefer the steps? <code>npm i -g {cliInstallSpec}</code>, then <code>tot checkout {appDomain}</code>, <code>cd {appDomain}</code>, <code>tot dev</code>. New to Node? Get Node 24 (LTS) at <a href="https://nodejs.org">nodejs.org</a> — it includes <code>npm</code>.</p>
|
|
1958
1958
|
|
|
1959
1959
|
{isLocalDevLoop && (
|
|
1960
1960
|
<div class="detect" id="detect" data-listening="true">
|
package/package.json
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tokenoftrust/storefront-runner",
|
|
3
|
-
"version": "1.3.4-rc.
|
|
3
|
+
"version": "1.3.4-rc.2",
|
|
4
4
|
"license": "SEE LICENSE IN LICENSE",
|
|
5
5
|
"description": "World-shareable storefront runner: multi-tenant renderer on Astro/Cloudflare. No control plane.",
|
|
6
6
|
"packageManager": "pnpm@11.9.0",
|
|
7
7
|
"engines": {
|
|
8
|
-
"node": ">=
|
|
8
|
+
"node": ">=22.12.0"
|
|
9
9
|
},
|
|
10
|
+
"workspaces": [
|
|
11
|
+
"apps/*",
|
|
12
|
+
"packages/*"
|
|
13
|
+
],
|
|
10
14
|
"scripts": {
|
|
11
15
|
"dev": "pnpm --filter @tot/storefront-runner dev",
|
|
12
16
|
"build": "pnpm --filter @tot/storefront-runner build",
|
package/pnpm-workspace.yaml
CHANGED
|
@@ -1,36 +1,17 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
import { watch, readdirSync, statSync, existsSync, cpSync, rmSync, mkdirSync } from "node:fs";
|
|
3
3
|
import { join, dirname } from "node:path";
|
|
4
|
+
import { IGNORED_WATCH_DIRS, isTransientWatchFile } from "./transient-files.mjs";
|
|
5
|
+
|
|
6
|
+
// Re-exported so existing importers (checkout-watch.test.mjs) keep working after the
|
|
7
|
+
// predicate moved to its own typed module.
|
|
8
|
+
export { isTransientWatchFile };
|
|
4
9
|
|
|
5
10
|
// Standalone (`tot dev --workspace`) re-sync: the developer's checkout is COPIED
|
|
6
11
|
// into the storefront's in-tree tenants/<id>/ (Vite's server.fs.allow refuses files
|
|
7
12
|
// outside its root), and each save is mirrored back so Vite's own watcher fires the
|
|
8
13
|
// browser reload. This module owns detecting the save + doing the copy.
|
|
9
14
|
|
|
10
|
-
/** Directories never worth descending into — VCS/dep/build noise that would churn the poll. */
|
|
11
|
-
const IGNORED_WATCH_DIRS = new Set([".git", "node_modules", ".astro", ".cache", ".turbo"]);
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* True for editor/OS transient files that are NOT real content edits. A raw poll of
|
|
15
|
-
* the checkout subtree would otherwise treat a vim swap (`.home.html.swp`), a Finder
|
|
16
|
-
* `.DS_Store`, an emacs autosave (`#home.html#`) or lock (`.#home.html`), a `~` backup,
|
|
17
|
-
* or vim's `4913` write-probe as a content change — re-syncing it into the in-tree copy
|
|
18
|
-
* and firing a spurious reload / "changed" report (the ".swp shows up as a change" bug).
|
|
19
|
-
* Matched on the BASENAME so it's independent of depth. "Official" changes track the
|
|
20
|
-
* store's real content; untracked editor junk is surfaced only at publish time, not here.
|
|
21
|
-
*/
|
|
22
|
-
export function isTransientWatchFile(name) {
|
|
23
|
-
return (
|
|
24
|
-
/\.(sw[a-z]{1,2}|tmp|temp|orig|bak)$/i.test(name) || // vim swap (.swp/.swo/.swx/.swpx…), temp/backup
|
|
25
|
-
/~$/.test(name) || // gedit/emacs/kate trailing-tilde backup
|
|
26
|
-
/^\.#/.test(name) || // emacs lockfile (.#file)
|
|
27
|
-
/^#.*#$/.test(name) || // emacs autosave (#file#)
|
|
28
|
-
name === ".DS_Store" ||
|
|
29
|
-
name === "Thumbs.db" ||
|
|
30
|
-
name === "4913" // vim's write-probe file
|
|
31
|
-
);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
15
|
/** Re-sync one changed checkout path into its in-tree copy (copy on save, remove on delete). */
|
|
35
16
|
export function resync(sourceRoot, targetRoot, rel) {
|
|
36
17
|
const from = rel ? join(sourceRoot, rel) : sourceRoot;
|
|
@@ -17,7 +17,7 @@ import { readFileSync, readdirSync, statSync } from "node:fs";
|
|
|
17
17
|
import { join, extname, sep } from "node:path";
|
|
18
18
|
import { resolveSafePath, matchesWriteAllowlist } from "./safe-path.mjs";
|
|
19
19
|
import { resolveTenantSourceRoot } from "./tenant-source-root.mjs";
|
|
20
|
-
import { isTransientWatchFile } from "./
|
|
20
|
+
import { isTransientWatchFile } from "./transient-files.mjs";
|
|
21
21
|
|
|
22
22
|
/** Above this, the viewer degrades to "not shown" rather than shipping a huge string. */
|
|
23
23
|
const MAX_FILE_BYTES = 512 * 1024;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
/**
|
|
3
|
+
* Editor/OS transient-file predicate — shared by the local dev loop's file WATCHER
|
|
4
|
+
* (checkout-watch.mjs) and the source-tree browser (file-browser.mjs) so both hide
|
|
5
|
+
* the same non-content noise: a vim swap (`.home.html.swp`), an emacs autosave
|
|
6
|
+
* (`#home.html#`) or lock (`.#home.html`), a `~` backup, `.DS_Store`/`Thumbs.db`, or
|
|
7
|
+
* vim's `4913` write-probe. Kept in its own tiny, fully-typed module so a consumer
|
|
8
|
+
* that only needs the predicate never drags the watcher's other internals into its
|
|
9
|
+
* typecheck graph.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/** Directories never worth descending into — VCS/dep/build noise that would churn a poll. */
|
|
13
|
+
export const IGNORED_WATCH_DIRS = new Set([".git", "node_modules", ".astro", ".cache", ".turbo"]);
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* True for editor/OS transient files that are NOT real content edits. A raw poll of
|
|
17
|
+
* a checkout subtree would otherwise treat one as a content change — re-syncing it,
|
|
18
|
+
* firing a spurious reload, and showing it as an untracked change. Matched on the
|
|
19
|
+
* BASENAME so it's independent of depth. "Official" changes track the store's real
|
|
20
|
+
* content; untracked editor junk is surfaced only at publish time, not here.
|
|
21
|
+
* @param {string} name the file basename
|
|
22
|
+
* @returns {boolean}
|
|
23
|
+
*/
|
|
24
|
+
export function isTransientWatchFile(name) {
|
|
25
|
+
return (
|
|
26
|
+
/\.(sw[a-z]{1,2}|tmp|temp|orig|bak)$/i.test(name) || // vim swap (.swp/.swo/.swx/.swpx…), temp/backup
|
|
27
|
+
/~$/.test(name) || // gedit/emacs/kate trailing-tilde backup
|
|
28
|
+
/^\.#/.test(name) || // emacs lockfile (.#file)
|
|
29
|
+
/^#.*#$/.test(name) || // emacs autosave (#file#)
|
|
30
|
+
name === ".DS_Store" ||
|
|
31
|
+
name === "Thumbs.db" ||
|
|
32
|
+
name === "4913" // vim's write-probe file
|
|
33
|
+
);
|
|
34
|
+
}
|
package/scripts/tot-dev.mjs
CHANGED
|
@@ -49,6 +49,7 @@
|
|
|
49
49
|
* Ctrl-C stops the server. Exit 1 = can't resolve tenant/domain/workspace; 2 = bad usage.
|
|
50
50
|
*/
|
|
51
51
|
import { spawn, spawnSync } from "node:child_process";
|
|
52
|
+
import { createRequire } from "node:module";
|
|
52
53
|
import {
|
|
53
54
|
readFileSync,
|
|
54
55
|
existsSync,
|
|
@@ -356,10 +357,22 @@ const devSourceRoot = resolveDevSourceRoot(workspacePath, repoRoot, tenant);
|
|
|
356
357
|
const spawnEnv = workspacePath
|
|
357
358
|
? { ...process.env, ASTRO_DEV_BACKGROUND: "1", TOT_DEV_TENANT: tenant, TOT_DEV_SOURCE_ROOT: devSourceRoot }
|
|
358
359
|
: { ...process.env, TOT_DEV_TENANT: tenant, TOT_DEV_SOURCE_ROOT: devSourceRoot };
|
|
360
|
+
// LAUNCHER-AGNOSTIC astro spawn: the published runner tree may have been
|
|
361
|
+
// installed by pnpm OR npm (npm ships with every Node — the runner must not
|
|
362
|
+
// require pnpm at RUNTIME), so resolve astro's real entry through Node's own
|
|
363
|
+
// resolver from the app package and exec it with our Node. No package-manager
|
|
364
|
+
// binary, no .bin shims, and no coupling to the package's name (the build
|
|
365
|
+
// renames @tot/storefront → @tot/storefront-runner in the shipped tree, which
|
|
366
|
+
// the old `pnpm --filter <name> exec` had to track).
|
|
367
|
+
const appDir = resolve(repoRoot, "apps/storefront");
|
|
368
|
+
const astroEntry = resolve(
|
|
369
|
+
dirname(createRequire(join(appDir, "package.json")).resolve("astro/package.json")),
|
|
370
|
+
"bin/astro.mjs", // astro's package.json bin entry
|
|
371
|
+
);
|
|
359
372
|
const child = spawn(
|
|
360
|
-
|
|
361
|
-
[
|
|
362
|
-
{ cwd:
|
|
373
|
+
process.execPath,
|
|
374
|
+
[astroEntry, "dev", "--port", port, "--host"],
|
|
375
|
+
{ cwd: appDir, stdio: "inherit", env: spawnEnv },
|
|
363
376
|
);
|
|
364
377
|
|
|
365
378
|
// Clean up grafts on every exit path so we never leave symlinks in the tree.
|