@hitch42/applet 0.1.1-beta.1 → 0.1.1-beta.10
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/_internal/client.js +120 -11
- package/dist/_internal/log-context.d.ts +34 -0
- package/dist/_internal/log-context.js +2 -0
- package/dist/_internal/server.d.ts +14 -1
- package/dist/_internal/server.js +122 -118
- package/dist/applet.js +53 -29
- package/dist/build-Bh_S5BUv.js +2 -0
- package/dist/{build-Rk3xDZV7.js → build-CEfFoIZZ.js} +30 -14
- package/dist/ci-BKw14X2h.js +69 -0
- package/dist/client.d.ts +3 -3
- package/dist/{contract-DMrpbaLJ.d.ts → contract-CsUpTBe0.d.ts} +1 -1
- package/dist/{deploy-D3BGDqgC.js → deploy-wGYjwXrx.js} +249 -35
- package/dist/email-DBTVhNB_.js +62 -0
- package/dist/{generate-CH0VQmUi.js → generate-BEIYs17x.js} +79 -25
- package/dist/generate-Dox-OYt6.js +2 -0
- package/dist/headers-DkNr9MLJ.js +17 -0
- package/dist/index.d.ts +123 -11
- package/dist/index.js +142 -26
- package/dist/{init-BS1Y-eNL.js → init-6oGPM46R.js} +17 -18
- package/dist/load-config-xEdTXs0y.js +27 -0
- package/dist/log-context-zu8U9Sjm.js +73 -0
- package/dist/login-CUx3TmFQ.js +12 -0
- package/dist/{logs-m00pzr_O.js → logs-DSjuEN0Q.js} +20 -22
- package/dist/{manifest-BQCAxATS.d.ts → manifest-C6_F4bL8.d.ts} +9 -0
- package/dist/{manifest-DyoJ1MIL.js → manifest-CJSL9GuC.js} +25 -3
- package/dist/me-BW7Paary.js +32 -0
- package/dist/{project-CxN-Dtc7.js → project-BRZGd6xq.js} +227 -34
- package/dist/prompt-ChS-grYO.js +29 -0
- package/dist/query-cache-CXmAyLv3.js +82 -0
- package/dist/react.d.ts +3 -2
- package/dist/react.js +98 -80
- package/dist/schedules-C8_Svr4h.js +42 -0
- package/dist/server.d.ts +9 -4
- package/dist/server.js +29 -8
- package/dist/telephony-protocol-D-nf3Gca.d.ts +76 -0
- package/dist/telephony.d.ts +97 -0
- package/dist/telephony.js +726 -0
- package/dist/{upload-YbRKEqR6.d.ts → upload-DKZzz5kW.d.ts} +6 -6
- package/dist/{variables-CP7Ce3Np.js → variables-DT5RyHdH.js} +14 -39
- package/dist/vite.d.ts +3 -60
- package/dist/vite.js +503 -34
- package/dist/workflow-engine-BOTzURKM.js +111 -0
- package/dist/{auth-DaVK0k5R.js → workspace-D0dnnlxL.js} +75 -40
- package/package.json +19 -6
- package/scaffold/.agents/skills/hitch-applets/SKILL.md +60 -15
- package/scaffold/.agents/skills/hitch-applets/references/email-tracking.md +49 -0
- package/scaffold/.agents/skills/hitch-applets/references/files.md +3 -3
- package/scaffold/.agents/skills/hitch-applets/references/manifest.md +29 -2
- package/scaffold/.agents/skills/hitch-applets/references/markdown.md +2 -0
- package/scaffold/.agents/skills/hitch-applets/references/performance.md +74 -0
- package/scaffold/.agents/skills/hitch-applets/references/records.md +32 -8
- package/scaffold/.agents/skills/hitch-applets/references/schedules.md +7 -2
- package/scaffold/.agents/skills/hitch-applets/references/server.md +0 -3
- package/scaffold/.agents/skills/hitch-applets/references/workflows.md +11 -3
- package/scaffold/.agents/skills/hitch-cli/SKILL.md +129 -0
- package/scaffold/.agents/skills/hitch-data/SKILL.md +52 -0
- package/scaffold/.agents/skills/hitch-data/references/attributes.md +127 -0
- package/scaffold/.agents/skills/hitch-data/references/limits.md +64 -0
- package/scaffold/.agents/skills/hitch-theory/SKILL.md +151 -0
- package/scaffold/.agents/skills/react-best-practices/SKILL.md +127 -0
- package/scaffold/.agents/skills/react-best-practices/rules/advanced-effect-event-deps.md +50 -0
- package/scaffold/.agents/skills/react-best-practices/rules/advanced-event-handler-refs.md +55 -0
- package/scaffold/.agents/skills/react-best-practices/rules/advanced-init-once.md +42 -0
- package/scaffold/.agents/skills/react-best-practices/rules/advanced-use-latest.md +39 -0
- package/scaffold/.agents/skills/react-best-practices/rules/async-cheap-condition-before-await.md +37 -0
- package/scaffold/.agents/skills/react-best-practices/rules/async-defer-await.md +82 -0
- package/scaffold/.agents/skills/react-best-practices/rules/async-dependencies.md +48 -0
- package/scaffold/.agents/skills/react-best-practices/rules/async-parallel.md +24 -0
- package/scaffold/.agents/skills/react-best-practices/rules/bundle-analyzable-paths.md +42 -0
- package/scaffold/.agents/skills/react-best-practices/rules/bundle-barrel-imports.md +39 -0
- package/scaffold/.agents/skills/react-best-practices/rules/bundle-conditional.md +35 -0
- package/scaffold/.agents/skills/react-best-practices/rules/bundle-defer-third-party.md +46 -0
- package/scaffold/.agents/skills/react-best-practices/rules/bundle-dynamic-imports.md +38 -0
- package/scaffold/.agents/skills/react-best-practices/rules/bundle-preload.md +40 -0
- package/scaffold/.agents/skills/react-best-practices/rules/client-event-listeners.md +78 -0
- package/scaffold/.agents/skills/react-best-practices/rules/client-localstorage-schema.md +74 -0
- package/scaffold/.agents/skills/react-best-practices/rules/client-passive-event-listeners.md +48 -0
- package/scaffold/.agents/skills/react-best-practices/rules/js-batch-dom-css.md +110 -0
- package/scaffold/.agents/skills/react-best-practices/rules/js-cache-function-results.md +58 -0
- package/scaffold/.agents/skills/react-best-practices/rules/js-cache-property-access.md +28 -0
- package/scaffold/.agents/skills/react-best-practices/rules/js-cache-storage.md +56 -0
- package/scaffold/.agents/skills/react-best-practices/rules/js-combine-iterations.md +32 -0
- package/scaffold/.agents/skills/react-best-practices/rules/js-early-exit.md +50 -0
- package/scaffold/.agents/skills/react-best-practices/rules/js-flatmap-filter.md +51 -0
- package/scaffold/.agents/skills/react-best-practices/rules/js-hoist-regexp.md +45 -0
- package/scaffold/.agents/skills/react-best-practices/rules/js-index-maps.md +37 -0
- package/scaffold/.agents/skills/react-best-practices/rules/js-length-check-first.md +50 -0
- package/scaffold/.agents/skills/react-best-practices/rules/js-min-max-loop.md +82 -0
- package/scaffold/.agents/skills/react-best-practices/rules/js-request-idle-callback.md +104 -0
- package/scaffold/.agents/skills/react-best-practices/rules/js-set-map-lookups.md +24 -0
- package/scaffold/.agents/skills/react-best-practices/rules/js-tosorted-immutable.md +57 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rendering-activity.md +26 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rendering-animate-svg-wrapper.md +38 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rendering-conditional-render.md +32 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rendering-content-visibility.md +38 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rendering-hoist-jsx.md +36 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rendering-resource-hints.md +81 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rendering-script-defer-async.md +36 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rendering-svg-precision.md +28 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rendering-usetransition-loading.md +43 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rerender-defer-reads.md +39 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rerender-dependencies.md +45 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rerender-derived-state-no-effect.md +40 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rerender-derived-state.md +29 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rerender-functional-setstate.md +77 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rerender-lazy-state-init.md +56 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rerender-memo-with-default-value.md +36 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rerender-memo.md +44 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rerender-move-effect-to-event.md +45 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rerender-no-inline-components.md +75 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rerender-simple-expression-in-memo.md +35 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rerender-split-combined-hooks.md +64 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rerender-transitions.md +40 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rerender-use-deferred-value.md +59 -0
- package/scaffold/.agents/skills/react-best-practices/rules/rerender-use-ref-transient-values.md +73 -0
- package/scaffold/.agents/skills/tanstack-query/SKILL.md +136 -0
- package/scaffold/.agents/skills/tanstack-query/references/compositions/automatic-invalidation-after-mutations.md +124 -0
- package/scaffold/.agents/skills/tanstack-query/references/compositions/broadcast-realtime-and-multi-tab-synchronization.md +150 -0
- package/scaffold/.agents/skills/tanstack-query/references/compositions/compose-query-with-tanstack-router-and-start.md +248 -0
- package/scaffold/.agents/skills/tanstack-query/references/compositions/enforce-query-best-practices-with-eslint.md +167 -0
- package/scaffold/.agents/skills/tanstack-query/references/compositions/persist-offline-and-restore-caches.md +181 -0
- package/scaffold/.agents/skills/tanstack-query/references/compositions/query-data-and-forms.md +156 -0
- package/scaffold/.agents/skills/tanstack-query/references/core/build-query-abstractions.md +130 -0
- package/scaffold/.agents/skills/tanstack-query/references/core/cancel-queries-and-consume-abort-signals.md +176 -0
- package/scaffold/.agents/skills/tanstack-query/references/core/concurrent-optimistic-updates.md +135 -0
- package/scaffold/.agents/skills/tanstack-query/references/core/coordinate-query-execution.md +188 -0
- package/scaffold/.agents/skills/tanstack-query/references/core/design-query-keys-and-options.md +182 -0
- package/scaffold/.agents/skills/tanstack-query/references/core/fetch-and-observe-queries.md +193 -0
- package/scaffold/.agents/skills/tanstack-query/references/core/implement-optimistic-updates-and-cache-writes.md +168 -0
- package/scaffold/.agents/skills/tanstack-query/references/core/paginate-and-build-infinite-queries.md +188 -0
- package/scaffold/.agents/skills/tanstack-query/references/core/seed-placeholder-select-and-transform-data.md +188 -0
- package/scaffold/.agents/skills/tanstack-query/references/core/selectors-and-derived-state.md +145 -0
- package/scaffold/.agents/skills/tanstack-query/references/core/tune-defaults-freshness-retries-and-refetching.md +172 -0
- package/scaffold/.agents/skills/tanstack-query/references/core/understand-query-internals-and-observers.md +104 -0
- package/scaffold/.agents/skills/tanstack-query/references/core/write-mutations-and-invalidate-related-queries.md +183 -0
- package/scaffold/.agents/skills/tanstack-query/references/framework/debug-with-devtools.md +169 -0
- package/scaffold/.agents/skills/tanstack-query/references/framework/handle-status-and-errors.md +119 -0
- package/scaffold/.agents/skills/tanstack-query/references/framework/shape-data-and-render-efficiently.md +184 -0
- package/scaffold/.agents/skills/tanstack-query/references/framework/use-framework-adapter-reactivity.md +185 -0
- package/scaffold/.agents/skills/tanstack-query/references/framework/use-suspense-and-error-boundaries.md +208 -0
- package/scaffold/.agents/skills/tanstack-query/references/lifecycle/migrate-major-versions-and-codemods.md +169 -0
- package/scaffold/.agents/skills/tanstack-query/references/lifecycle/prefetch-and-remove-request-waterfalls.md +228 -0
- package/scaffold/.agents/skills/tanstack-query/references/lifecycle/setup-query-client-and-providers.md +161 -0
- package/scaffold/.agents/skills/tanstack-query/references/lifecycle/ssr-hydration-and-streaming.md +209 -0
- package/scaffold/.agents/skills/tanstack-query/references/lifecycle/test-query-code.md +160 -0
- package/scaffold/.agents/skills/tanstack-router/SKILL.md +134 -0
- package/scaffold/.agents/skills/tanstack-router/references/compositions/router-query.md +412 -0
- package/scaffold/.agents/skills/tanstack-router/references/react-router.md +495 -0
- package/scaffold/.agents/skills/tanstack-router/references/router-core/auth-and-guards.md +495 -0
- package/scaffold/.agents/skills/tanstack-router/references/router-core/code-splitting.md +318 -0
- package/scaffold/.agents/skills/tanstack-router/references/router-core/data-loading.md +490 -0
- package/scaffold/.agents/skills/tanstack-router/references/router-core/index.md +158 -0
- package/scaffold/.agents/skills/tanstack-router/references/router-core/navigation.md +433 -0
- package/scaffold/.agents/skills/tanstack-router/references/router-core/not-found-and-errors.md +436 -0
- package/scaffold/.agents/skills/tanstack-router/references/router-core/path-params.md +383 -0
- package/scaffold/.agents/skills/tanstack-router/references/router-core/search-params.md +343 -0
- package/scaffold/.agents/skills/tanstack-router/references/router-core/ssr.md +462 -0
- package/scaffold/.agents/skills/tanstack-router/references/router-core/type-safety.md +493 -0
- package/scaffold/.agents/skills/tanstack-router/references/router-plugin.md +250 -0
- package/scaffold/.agents/skills/tanstack-router/references/virtual-file-routes.md +215 -0
- package/scaffold/.agents/skills/web-design-guidelines/SKILL.md +184 -0
- package/scaffold/.github/workflows/hitch.yml +15 -0
- package/scaffold/AGENTS.md +2 -2
- package/scaffold/applet/src/client/components/ui/alert.tsx +1 -1
- package/scaffold/applet/src/client/components/ui/button.tsx +1 -1
- package/scaffold/applet/src/client/components/ui/checkbox.tsx +1 -1
- package/scaffold/applet/src/client/components/ui/dialog.tsx +2 -2
- package/scaffold/applet/src/client/components/ui/dropdown-menu.tsx +2 -2
- package/scaffold/applet/src/client/components/ui/input-group.tsx +2 -2
- package/scaffold/applet/src/client/components/ui/select.tsx +2 -2
- package/scaffold/applet/src/client/components/ui/sheet.tsx +2 -2
- package/scaffold/applet/src/client/components/ui/spinner.tsx +2 -1
- package/scaffold/applet/src/client/components/ui/tabs.tsx +1 -1
- package/scaffold/applet/src/client/lib/utils.ts +1 -6
- package/scaffold/applet/src/client/styles/globals.css +2 -0
- package/dist/build-CEku1lyb.js +0 -2
- package/dist/generate-6Nbt7uii.js +0 -2
- package/dist/load-config-BnxocLAQ.js +0 -56
package/dist/vite.js
CHANGED
|
@@ -1,16 +1,301 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { i as
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
1
|
+
import { n as meSchema } from "./me-BW7Paary.js";
|
|
2
|
+
import { i as APPLET_CODE_PATTERN, n as appletManifestSchema, r as appletSourceDefinitionSchema } from "./manifest-CJSL9GuC.js";
|
|
3
|
+
import { a as openQueryCacheDatabase } from "./query-cache-CXmAyLv3.js";
|
|
4
|
+
import { d as HITCH_USER_HEADER, u as HITCH_SESSION_HEADER } from "./headers-DkNr9MLJ.js";
|
|
5
|
+
import { a as workflowOutcomeSchema, n as runWorkflowJournal } from "./workflow-engine-BOTzURKM.js";
|
|
6
|
+
import { c as bearerToken, d as resolveAuthForOrg, f as resolveInstanceUrl, l as organizationInstanceUrl, t as resolveWorkspaceOrg, u as rejectEnvironmentKey } from "./workspace-D0dnnlxL.js";
|
|
7
|
+
import { c as writeClientEntry, i as writeRegisterDeclaration, l as writeManifestGen, r as syncContract, s as writeAppletRoot, u as writeServerEntry } from "./generate-BEIYs17x.js";
|
|
8
|
+
import { randomUUID } from "node:crypto";
|
|
9
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
10
|
+
import { appendFile, readdir, rm, writeFile } from "node:fs/promises";
|
|
11
|
+
import { createServer } from "node:http";
|
|
12
|
+
import { basename, dirname, relative, resolve } from "node:path";
|
|
8
13
|
import { cloudflare } from "@cloudflare/vite-plugin";
|
|
9
14
|
import { tanstackRouter } from "@tanstack/router-plugin/vite";
|
|
10
15
|
import react from "@vitejs/plugin-react";
|
|
16
|
+
import { Readable } from "node:stream";
|
|
17
|
+
import { pipeline } from "node:stream/promises";
|
|
18
|
+
//#region src/dev-proxy.ts
|
|
19
|
+
const REQUEST_DROP = /* @__PURE__ */ new Set([
|
|
20
|
+
"host",
|
|
21
|
+
"connection",
|
|
22
|
+
"content-length",
|
|
23
|
+
"cookie",
|
|
24
|
+
"authorization",
|
|
25
|
+
"origin",
|
|
26
|
+
"referer",
|
|
27
|
+
"accept-encoding"
|
|
28
|
+
]);
|
|
29
|
+
const RESPONSE_DROP = /* @__PURE__ */ new Set([
|
|
30
|
+
"content-encoding",
|
|
31
|
+
"content-length",
|
|
32
|
+
"transfer-encoding",
|
|
33
|
+
"connection",
|
|
34
|
+
"set-cookie"
|
|
35
|
+
]);
|
|
36
|
+
function refusesClient(headers, clientAddress) {
|
|
37
|
+
if (![
|
|
38
|
+
"127.0.0.1",
|
|
39
|
+
"::1",
|
|
40
|
+
"::ffff:127.0.0.1"
|
|
41
|
+
].includes(clientAddress ?? "")) return true;
|
|
42
|
+
const get = (name) => headers instanceof Headers ? headers.get(name) : headers[name]?.toString() ?? null;
|
|
43
|
+
const host = get("host") ?? get(":authority");
|
|
44
|
+
const origin = get("origin");
|
|
45
|
+
return origin !== null && (host === null || !URL.canParse(origin) || new URL(origin).host !== host) || get("sec-fetch-site") === "cross-site";
|
|
46
|
+
}
|
|
47
|
+
function createDevProxy(options) {
|
|
48
|
+
let warnedAboutUnauthorized = false;
|
|
49
|
+
return async (request, clientAddress) => {
|
|
50
|
+
if (refusesClient(request.headers, clientAddress)) return new Response(null, { status: 403 });
|
|
51
|
+
const intercepted = await options.intercept?.(request);
|
|
52
|
+
if (intercepted !== void 0) return intercepted;
|
|
53
|
+
const headers = new Headers(request.headers);
|
|
54
|
+
[...headers.keys()].filter((name) => REQUEST_DROP.has(name) || name.startsWith("x-hitch-")).forEach((name) => headers.delete(name));
|
|
55
|
+
headers.set("authorization", `Bearer ${options.token}`);
|
|
56
|
+
const url = new URL(request.url);
|
|
57
|
+
const upstream = await globalThis.fetch(`${options.orgUrl}${url.pathname}${url.search}`, {
|
|
58
|
+
method: request.method,
|
|
59
|
+
headers,
|
|
60
|
+
body: request.body,
|
|
61
|
+
signal: request.signal,
|
|
62
|
+
...request.body === null ? {} : { duplex: "half" }
|
|
63
|
+
});
|
|
64
|
+
if (upstream.status === 401 && !warnedAboutUnauthorized) {
|
|
65
|
+
warnedAboutUnauthorized = true;
|
|
66
|
+
options.warn(options.source === "environment" ? "HITCH_API_KEY was rejected." : "Your login expired. Run applet login, then restart the dev server.");
|
|
67
|
+
}
|
|
68
|
+
const responseHeaders = new Headers(upstream.headers);
|
|
69
|
+
[...responseHeaders.keys()].filter((name) => RESPONSE_DROP.has(name)).forEach((name) => responseHeaders.delete(name));
|
|
70
|
+
return new Response(upstream.body, {
|
|
71
|
+
status: upstream.status,
|
|
72
|
+
statusText: upstream.statusText,
|
|
73
|
+
headers: responseHeaders
|
|
74
|
+
});
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
async function serveDevProxy(request, response, handler) {
|
|
78
|
+
const controller = new AbortController();
|
|
79
|
+
const onClose = () => {
|
|
80
|
+
if (!response.writableFinished) controller.abort();
|
|
81
|
+
};
|
|
82
|
+
response.on("close", onClose);
|
|
83
|
+
try {
|
|
84
|
+
const authority = request.headers[":authority"];
|
|
85
|
+
const host = request.headers.host ?? (Array.isArray(authority) ? authority[0] : authority);
|
|
86
|
+
const url = `http://${host ?? "localhost"}${request.url ?? "/"}`;
|
|
87
|
+
const result = await handler(new Request(url, {
|
|
88
|
+
method: request.method,
|
|
89
|
+
headers: {
|
|
90
|
+
...Object.fromEntries(Object.entries(request.headers).filter(([name]) => !name.startsWith(":"))),
|
|
91
|
+
host: host ?? "localhost"
|
|
92
|
+
},
|
|
93
|
+
signal: controller.signal,
|
|
94
|
+
...["GET", "HEAD"].includes(request.method ?? "GET") ? {} : {
|
|
95
|
+
body: Readable.toWeb(request),
|
|
96
|
+
duplex: "half"
|
|
97
|
+
}
|
|
98
|
+
}), request.socket.remoteAddress);
|
|
99
|
+
response.writeHead(result.status, result.statusText, Object.fromEntries(result.headers));
|
|
100
|
+
if (result.body === null) {
|
|
101
|
+
response.end();
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
await pipeline(Readable.fromWeb(result.body), response);
|
|
105
|
+
} catch (error) {
|
|
106
|
+
if (!controller.signal.aborted || !(error instanceof Error) || error.name !== "AbortError" && error.code !== "ERR_STREAM_PREMATURE_CLOSE") throw error;
|
|
107
|
+
} finally {
|
|
108
|
+
response.off("close", onClose);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
//#endregion
|
|
112
|
+
//#region src/dev-workflows.ts
|
|
113
|
+
const RUN_STATUSES = [
|
|
114
|
+
"in_progress",
|
|
115
|
+
"sleeping",
|
|
116
|
+
"success",
|
|
117
|
+
"error",
|
|
118
|
+
"terminated",
|
|
119
|
+
"open"
|
|
120
|
+
];
|
|
121
|
+
const badRequest = (message) => Response.json({
|
|
122
|
+
code: "BAD_REQUEST",
|
|
123
|
+
message
|
|
124
|
+
}, { status: 400 });
|
|
125
|
+
const workflowCodeError = (code) => code.length < 1 || code.length > 64 ? badRequest("The workflow code must have 1 to 64 characters.") : void 0;
|
|
126
|
+
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
127
|
+
function createDevWorkflows(options) {
|
|
128
|
+
const runs = /* @__PURE__ */ new Map();
|
|
129
|
+
const controller = new AbortController();
|
|
130
|
+
const stopped = () => controller.signal.aborted;
|
|
131
|
+
const wait = (milliseconds) => {
|
|
132
|
+
if (controller.signal.aborted) return Promise.reject(/* @__PURE__ */ new Error("The dev server stopped."));
|
|
133
|
+
return new Promise((resolve, reject) => {
|
|
134
|
+
const deadline = Date.now() + Math.max(0, milliseconds);
|
|
135
|
+
let timer;
|
|
136
|
+
const schedule = () => {
|
|
137
|
+
const remaining = deadline - Date.now();
|
|
138
|
+
if (remaining <= 0) {
|
|
139
|
+
controller.signal.removeEventListener("abort", onAbort);
|
|
140
|
+
resolve();
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
timer = setTimeout(schedule, Math.min(remaining, 2147483647));
|
|
144
|
+
};
|
|
145
|
+
const onAbort = () => {
|
|
146
|
+
clearTimeout(timer);
|
|
147
|
+
controller.signal.removeEventListener("abort", onAbort);
|
|
148
|
+
reject(/* @__PURE__ */ new Error("The dev server stopped."));
|
|
149
|
+
};
|
|
150
|
+
controller.signal.addEventListener("abort", onAbort, { once: true });
|
|
151
|
+
schedule();
|
|
152
|
+
});
|
|
153
|
+
};
|
|
154
|
+
const record = (run, outcome) => {
|
|
155
|
+
switch (outcome.kind) {
|
|
156
|
+
case "step":
|
|
157
|
+
case "step_error":
|
|
158
|
+
run.status = "in_progress";
|
|
159
|
+
run.sleep_until = null;
|
|
160
|
+
break;
|
|
161
|
+
case "sleep":
|
|
162
|
+
run.status = "sleeping";
|
|
163
|
+
run.sleep_until = new Date(outcome.until).toISOString();
|
|
164
|
+
break;
|
|
165
|
+
case "done":
|
|
166
|
+
run.status = "success";
|
|
167
|
+
run.result = outcome.result ?? null;
|
|
168
|
+
run.sleep_until = null;
|
|
169
|
+
run.finished_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
170
|
+
break;
|
|
171
|
+
case "error":
|
|
172
|
+
run.status = "error";
|
|
173
|
+
run.error = outcome.message.slice(0, 2048);
|
|
174
|
+
run.sleep_until = null;
|
|
175
|
+
run.finished_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
const start = (workflow, payload) => {
|
|
179
|
+
const run = {
|
|
180
|
+
id: randomUUID(),
|
|
181
|
+
workflow,
|
|
182
|
+
status: "in_progress",
|
|
183
|
+
result: null,
|
|
184
|
+
error: null,
|
|
185
|
+
sleep_until: null,
|
|
186
|
+
started_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
187
|
+
finished_at: null
|
|
188
|
+
};
|
|
189
|
+
runs.set(run.id, run);
|
|
190
|
+
options.logger.info(`ℹ Workflow ${workflow} run ${run.id} started.`);
|
|
191
|
+
runWorkflowJournal({
|
|
192
|
+
invoke: async (_name, fn) => {
|
|
193
|
+
if (controller.signal.aborted) throw new Error("The dev server stopped.");
|
|
194
|
+
for (let attempt = 0;; attempt += 1) try {
|
|
195
|
+
const outcome = await fn();
|
|
196
|
+
if (stopped()) throw new Error("The dev server stopped.");
|
|
197
|
+
return outcome;
|
|
198
|
+
} catch (error) {
|
|
199
|
+
if (stopped() || attempt === 5) throw error;
|
|
200
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
201
|
+
options.logger.error(`Workflow ${workflow} run ${run.id} invoke failed, retrying: ${message}`);
|
|
202
|
+
await wait(1e3 * 2 ** attempt);
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
sleep: (_name, milliseconds) => options.skipSleep ? Promise.resolve() : wait(milliseconds),
|
|
206
|
+
sleepUntil: (_name, until) => options.skipSleep ? Promise.resolve() : wait(until.getTime() - Date.now())
|
|
207
|
+
}, async (journal) => {
|
|
208
|
+
const outcome = await options.invoke(workflow, {
|
|
209
|
+
payload,
|
|
210
|
+
journal
|
|
211
|
+
});
|
|
212
|
+
record(run, outcome);
|
|
213
|
+
return outcome;
|
|
214
|
+
}).then(() => options.logger.info(`ℹ Workflow ${workflow} run ${run.id} succeeded.`), (error) => {
|
|
215
|
+
if (controller.signal.aborted) return;
|
|
216
|
+
const message = (error instanceof Error ? error.message : String(error)).slice(0, 2048);
|
|
217
|
+
run.status = "error";
|
|
218
|
+
run.error = message;
|
|
219
|
+
run.sleep_until = null;
|
|
220
|
+
run.finished_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
221
|
+
options.logger.error(`Workflow ${workflow} run ${run.id} failed: ${message}`);
|
|
222
|
+
});
|
|
223
|
+
return run;
|
|
224
|
+
};
|
|
225
|
+
const handle = (request) => {
|
|
226
|
+
const url = new URL(request.url);
|
|
227
|
+
const prefix = `/api/v1/applets/${encodeURIComponent(options.appletCode)}`;
|
|
228
|
+
if (!url.pathname.startsWith(`${prefix}/`)) return void 0;
|
|
229
|
+
const path = url.pathname.slice(prefix.length);
|
|
230
|
+
const startMatch = /^\/workflows\/([^/]+)\/runs$/.exec(path);
|
|
231
|
+
if (request.method === "POST" && startMatch !== null) return (async () => {
|
|
232
|
+
let body;
|
|
233
|
+
try {
|
|
234
|
+
const text = await request.text();
|
|
235
|
+
body = text === "" ? {} : JSON.parse(text);
|
|
236
|
+
} catch {
|
|
237
|
+
return badRequest("The body must be a JSON object with a payload key.");
|
|
238
|
+
}
|
|
239
|
+
if (body === null || typeof body !== "object" || Array.isArray(body) || Object.keys(body).some((key) => key !== "payload")) return badRequest("The body must be a JSON object with a payload key.");
|
|
240
|
+
let workflow;
|
|
241
|
+
try {
|
|
242
|
+
workflow = decodeURIComponent(startMatch[1]);
|
|
243
|
+
} catch (error) {
|
|
244
|
+
if (error instanceof URIError) return badRequest(error.message);
|
|
245
|
+
throw error;
|
|
246
|
+
}
|
|
247
|
+
const codeError = workflowCodeError(workflow);
|
|
248
|
+
if (codeError !== void 0) return codeError;
|
|
249
|
+
const run = start(workflow, "payload" in body ? body.payload : void 0);
|
|
250
|
+
return Response.json({ data: {
|
|
251
|
+
id: run.id,
|
|
252
|
+
workflow: run.workflow,
|
|
253
|
+
status: "in_progress"
|
|
254
|
+
} }, { status: 201 });
|
|
255
|
+
})();
|
|
256
|
+
const getMatch = /^\/workflows\/([^/]+)\/runs\/([^/]+)$/.exec(path);
|
|
257
|
+
if (request.method === "GET" && getMatch !== null) {
|
|
258
|
+
let workflow;
|
|
259
|
+
let id;
|
|
260
|
+
try {
|
|
261
|
+
workflow = decodeURIComponent(getMatch[1]);
|
|
262
|
+
id = decodeURIComponent(getMatch[2]);
|
|
263
|
+
} catch (error) {
|
|
264
|
+
if (error instanceof URIError) return Promise.resolve(badRequest(error.message));
|
|
265
|
+
throw error;
|
|
266
|
+
}
|
|
267
|
+
const codeError = workflowCodeError(workflow);
|
|
268
|
+
if (codeError !== void 0) return Promise.resolve(codeError);
|
|
269
|
+
if (!UUID_PATTERN.test(id)) return Promise.resolve(badRequest("The run id must be a uuid."));
|
|
270
|
+
const run = runs.get(id);
|
|
271
|
+
return Promise.resolve(run?.workflow === workflow ? Response.json({ data: run }) : Response.json({
|
|
272
|
+
code: "workflow_run_not_found",
|
|
273
|
+
message: "The workflow run does not exist."
|
|
274
|
+
}, { status: 404 }));
|
|
275
|
+
}
|
|
276
|
+
if (request.method === "GET" && path === "/workflow-runs") {
|
|
277
|
+
const status = url.searchParams.get("status");
|
|
278
|
+
const limitValue = url.searchParams.get("limit");
|
|
279
|
+
const limit = limitValue === null ? 50 : Number(limitValue);
|
|
280
|
+
if (status !== null && !RUN_STATUSES.some((value) => value === status)) return Promise.resolve(badRequest("Invalid workflow run status."));
|
|
281
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 100) return Promise.resolve(badRequest("The limit must be an integer from 1 to 100."));
|
|
282
|
+
const workflow = url.searchParams.get("workflow");
|
|
283
|
+
const codeError = workflow === null ? void 0 : workflowCodeError(workflow);
|
|
284
|
+
if (codeError !== void 0) return Promise.resolve(codeError);
|
|
285
|
+
const data = [...runs.values()].filter((run) => (workflow === null || run.workflow === workflow) && (status === null || status === run.status || status === "open" && ["in_progress", "sleeping"].includes(run.status))).reverse().slice(0, limit).map(({ result: _result, ...summary }) => summary);
|
|
286
|
+
return Promise.resolve(Response.json({ data }));
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
return {
|
|
290
|
+
handle,
|
|
291
|
+
stop: () => controller.abort()
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
//#endregion
|
|
11
295
|
//#region src/vite.ts
|
|
12
296
|
const APPLET_COMPATIBILITY_DATE = "2026-07-14";
|
|
13
297
|
const APPLET_COMPATIBILITY_FLAGS = ["nodejs_compat"];
|
|
298
|
+
let activeDevListener;
|
|
14
299
|
function clientBootstrapScript(parentOrigin, appletCode) {
|
|
15
300
|
return `(function () {
|
|
16
301
|
var parentOrigin = ${parentOrigin === void 0 ? "null" : JSON.stringify(parentOrigin)};
|
|
@@ -34,6 +319,20 @@ function clientBootstrapScript(parentOrigin, appletCode) {
|
|
|
34
319
|
: null
|
|
35
320
|
});
|
|
36
321
|
|
|
322
|
+
// Opened here so it is ready by the time the applet module restores its
|
|
323
|
+
// query cache. A failure is reported when the module reads the promise.
|
|
324
|
+
Object.defineProperty(window, "__hitchCacheDatabase", {
|
|
325
|
+
configurable: false,
|
|
326
|
+
writable: false,
|
|
327
|
+
value: window.parent !== window && parentOrigin !== null
|
|
328
|
+
? (function () {
|
|
329
|
+
var database = (${openQueryCacheDatabase.toString()})();
|
|
330
|
+
database.catch(function () {});
|
|
331
|
+
return database;
|
|
332
|
+
})()
|
|
333
|
+
: null
|
|
334
|
+
});
|
|
335
|
+
|
|
37
336
|
function applyTheme(theme) {
|
|
38
337
|
document.documentElement.classList.toggle("dark", theme === "dark");
|
|
39
338
|
}
|
|
@@ -138,7 +437,7 @@ function clientBootstrapScript(parentOrigin, appletCode) {
|
|
|
138
437
|
});
|
|
139
438
|
})();`;
|
|
140
439
|
}
|
|
141
|
-
function applet(sourceDefinition
|
|
440
|
+
function applet(sourceDefinition) {
|
|
142
441
|
const parsedSourceDefinition = appletSourceDefinitionSchema.safeParse(sourceDefinition);
|
|
143
442
|
if (!parsedSourceDefinition.success) {
|
|
144
443
|
const raw = sourceDefinition;
|
|
@@ -152,6 +451,11 @@ function applet(sourceDefinition, options = {}) {
|
|
|
152
451
|
let clearedOutDir = false;
|
|
153
452
|
let contractSync;
|
|
154
453
|
let themeStylesheetUrl;
|
|
454
|
+
let devApiUrl;
|
|
455
|
+
let devListener;
|
|
456
|
+
let devLogger;
|
|
457
|
+
let devWorkflows;
|
|
458
|
+
let devContext;
|
|
155
459
|
const virtualRouteConfig = {
|
|
156
460
|
type: "root",
|
|
157
461
|
file: "__root.tsx",
|
|
@@ -170,6 +474,7 @@ function applet(sourceDefinition, options = {}) {
|
|
|
170
474
|
const routesDirectory = resolve(resolvedRoot, "src/client/routes");
|
|
171
475
|
const hasClient = existsSync(routesDirectory);
|
|
172
476
|
const hasServer = existsSync(resolve(resolvedRoot, "src/server/main.ts"));
|
|
477
|
+
if (!hasServer && parsedDefinition.data.telephony?.events?.length) throw new Error("An applet with telephony events must have src/server/main.ts.");
|
|
173
478
|
if (!hasServer && (parsedDefinition.data.schedules?.length ?? 0) > 0) throw new Error("An applet with schedules must have src/server/main.ts.");
|
|
174
479
|
const legacyMain = resolve(resolvedRoot, "src/client/main.tsx");
|
|
175
480
|
const indexHtml = resolve(resolvedRoot, "index.html");
|
|
@@ -200,6 +505,11 @@ function applet(sourceDefinition, options = {}) {
|
|
|
200
505
|
org: resolveWorkspaceOrg(callerRoot),
|
|
201
506
|
...parsedDefinition.data
|
|
202
507
|
});
|
|
508
|
+
const clsxFromCn = {
|
|
509
|
+
name: "hitch-clsx",
|
|
510
|
+
resolveId: (id) => id === "clsx" ? "\0hitch:clsx" : void 0,
|
|
511
|
+
load: (id) => id === "\0hitch:clsx" ? "export { clsx, clsx as default } from \"cn\";" : void 0
|
|
512
|
+
};
|
|
203
513
|
const orchestrator = {
|
|
204
514
|
name: "hitch-applet",
|
|
205
515
|
enforce: "pre",
|
|
@@ -219,12 +529,9 @@ function applet(sourceDefinition, options = {}) {
|
|
|
219
529
|
});
|
|
220
530
|
clearedOutDir = true;
|
|
221
531
|
}
|
|
222
|
-
if (environment.command === "build") themeStylesheetUrl = `${organizationInstanceUrl(await resolveInstanceUrl(
|
|
223
|
-
if (environment.command === "build"
|
|
224
|
-
contractSync ??= syncContract(resolve(root, ".hitch"), manifest
|
|
225
|
-
...options,
|
|
226
|
-
changesetId: (options.store?.env ?? process.env).HITCH_CHANGESET_ID
|
|
227
|
-
});
|
|
532
|
+
if (environment.command === "build") themeStylesheetUrl = `${organizationInstanceUrl(await resolveInstanceUrl(), manifest.org)}/theme.css`;
|
|
533
|
+
if (environment.command === "build") {
|
|
534
|
+
contractSync ??= syncContract(resolve(root, ".hitch"), manifest);
|
|
228
535
|
await contractSync;
|
|
229
536
|
}
|
|
230
537
|
const hasHitchGen = existsSync(resolve(root, ".hitch/index.ts"));
|
|
@@ -239,17 +546,162 @@ function applet(sourceDefinition, options = {}) {
|
|
|
239
546
|
hasServer ? writeServerEntry(root, hasHitchGen, manifest.code) : void 0,
|
|
240
547
|
hasHitchGen ? void 0 : writeRegisterDeclaration(resolve(root, ".hitch"))
|
|
241
548
|
]);
|
|
242
|
-
|
|
549
|
+
if (environment.command === "serve" && environment.mode !== "test" && environment.isPreview !== true) {
|
|
550
|
+
const instanceUrl = await resolveInstanceUrl();
|
|
551
|
+
const orgUrl = organizationInstanceUrl(instanceUrl, manifest.org);
|
|
552
|
+
const auth = await resolveAuthForOrg(instanceUrl, manifest.org);
|
|
553
|
+
if (auth === void 0) throw new Error("Not logged in. Run applet login.");
|
|
554
|
+
const token = bearerToken(auth);
|
|
555
|
+
const meResponse = await globalThis.fetch(`${orgUrl}/api/v1/me`, { headers: { authorization: `Bearer ${token}` } });
|
|
556
|
+
rejectEnvironmentKey(meResponse, auth);
|
|
557
|
+
if (meResponse.status === 401) throw new Error("Your login expired. Run applet login.");
|
|
558
|
+
if (!meResponse.ok) throw new Error(`Hitch returned ${meResponse.status} for /api/v1/me.`);
|
|
559
|
+
const { person } = meSchema.parse(await meResponse.json());
|
|
560
|
+
const proxy = createDevProxy({
|
|
561
|
+
orgUrl,
|
|
562
|
+
token,
|
|
563
|
+
source: auth.source,
|
|
564
|
+
warn: (message) => devLogger?.warn(message),
|
|
565
|
+
intercept: (request) => devWorkflows?.handle(request)
|
|
566
|
+
});
|
|
567
|
+
devContext = {
|
|
568
|
+
person,
|
|
569
|
+
proxy,
|
|
570
|
+
orgUrl,
|
|
571
|
+
cacheBuster: randomUUID()
|
|
572
|
+
};
|
|
573
|
+
if (hasServer && devListener === void 0) {
|
|
574
|
+
if (activeDevListener?.listening) activeDevListener.close();
|
|
575
|
+
const nonce = randomUUID();
|
|
576
|
+
devListener = createServer((request, response) => {
|
|
577
|
+
const url = request.url ?? "";
|
|
578
|
+
const prefix = `/${nonce}/api`;
|
|
579
|
+
const address = devListener?.address();
|
|
580
|
+
if (!(url === prefix || url.startsWith(`${prefix}/`) || url.startsWith(`${prefix}?`)) || address === null || address === void 0 || typeof address === "string" || request.headers.host !== `127.0.0.1:${address.port}`) {
|
|
581
|
+
response.writeHead(404).end();
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
request.url = url.slice(nonce.length + 1);
|
|
585
|
+
serveDevProxy(request, response, proxy).catch((error) => {
|
|
586
|
+
const failure = error instanceof Error ? error : new Error(String(error));
|
|
587
|
+
devLogger?.error(`Local Hitch proxy failed: ${failure.message}`);
|
|
588
|
+
response.destroy(failure);
|
|
589
|
+
});
|
|
590
|
+
});
|
|
591
|
+
activeDevListener = devListener;
|
|
592
|
+
try {
|
|
593
|
+
await new Promise((resolvePromise, rejectPromise) => {
|
|
594
|
+
devListener?.once("error", rejectPromise);
|
|
595
|
+
devListener?.listen(0, "127.0.0.1", resolvePromise);
|
|
596
|
+
});
|
|
597
|
+
const address = devListener.address();
|
|
598
|
+
if (address === null || typeof address === "string") throw new Error("The local Hitch proxy has no port.");
|
|
599
|
+
devApiUrl = `http://127.0.0.1:${address.port}/${nonce}/api`;
|
|
600
|
+
} catch (error) {
|
|
601
|
+
if (devListener.listening) devListener.close();
|
|
602
|
+
if (activeDevListener === devListener) activeDevListener = void 0;
|
|
603
|
+
devListener = void 0;
|
|
604
|
+
throw error;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
if (environment.mode === "test") {
|
|
609
|
+
const colocated = (await readdir(resolve(root, "src"), { recursive: true })).filter((path) => /\.(test|spec)\.[cm]?[jt]sx?$/.test(path));
|
|
610
|
+
if (colocated.length > 0) throw new Error(`Applet tests run only from the test folder. Move ${colocated.map((path) => `src/${path}`).join(", ")} to test/.`);
|
|
611
|
+
}
|
|
612
|
+
return {
|
|
613
|
+
base: `/${manifest.code}/`,
|
|
614
|
+
test: { include: ["test/**/*.test.{ts,tsx}"] },
|
|
615
|
+
resolve: { alias: {
|
|
616
|
+
"clsx/lite": "cn/lite",
|
|
617
|
+
"tailwind-merge": "cn"
|
|
618
|
+
} },
|
|
619
|
+
optimizeDeps: { rolldownOptions: { plugins: [clsxFromCn] } },
|
|
620
|
+
...hasServer && { environments: { worker: { build: { rollupOptions: { output: { codeSplitting: false } } } } } }
|
|
621
|
+
};
|
|
243
622
|
},
|
|
244
623
|
configResolved(config) {
|
|
245
624
|
apiManifest = resolveAppletConfig(config.root).manifest;
|
|
246
625
|
},
|
|
247
626
|
configureServer(server) {
|
|
248
|
-
|
|
627
|
+
if (devContext === void 0) return;
|
|
628
|
+
const { manifest } = requireAppletConfig();
|
|
629
|
+
const { person, proxy, orgUrl, cacheBuster } = devContext;
|
|
630
|
+
const orgOrigin = new URL(orgUrl).origin;
|
|
631
|
+
devLogger = server.config.logger;
|
|
632
|
+
const skipSleep = process.env.HITCH_DEV_SKIP_SLEEP === "1";
|
|
633
|
+
devWorkflows = createDevWorkflows({
|
|
634
|
+
appletCode: manifest.code,
|
|
635
|
+
skipSleep,
|
|
636
|
+
logger: server.config.logger,
|
|
637
|
+
invoke: async (workflow, body) => {
|
|
638
|
+
const address = server.httpServer?.address();
|
|
639
|
+
if (address === null || address === void 0 || typeof address === "string") throw new Error("The dev server has no port.");
|
|
640
|
+
const response = await fetch(`http://localhost:${address.port}/${manifest.code}/api/__hitch/workflows/${encodeURIComponent(workflow)}`, {
|
|
641
|
+
method: "POST",
|
|
642
|
+
headers: { "content-type": "application/json" },
|
|
643
|
+
body: JSON.stringify(body)
|
|
644
|
+
});
|
|
645
|
+
if (!response.ok) throw new Error(`Workflow worker returned HTTP ${response.status}.`);
|
|
646
|
+
return workflowOutcomeSchema.parse(await response.json());
|
|
647
|
+
}
|
|
648
|
+
});
|
|
649
|
+
server.config.logger.info(`ℹ Running as ${person.email} in production org "${manifest.org}". Reads and writes use real data with your access.`);
|
|
650
|
+
if (manifest.requiresChangeset !== void 0) server.config.logger.info(`ℹ This applet requires changeset ${manifest.requiresChangeset}. Local dev uses the live schema, so a pending changeset's schema is not available until it applies.`);
|
|
651
|
+
if (skipSleep) server.config.logger.info("ℹ HITCH_DEV_SKIP_SLEEP is set. Workflow sleeps and retry delays run at once.");
|
|
652
|
+
const devServerFile = resolve(requireAppletConfig().root, ".hitch/dev-server.json");
|
|
653
|
+
let devServerUrl;
|
|
654
|
+
const removeDevServerFile = () => {
|
|
655
|
+
if (devServerUrl === void 0) return;
|
|
656
|
+
try {
|
|
657
|
+
const file = JSON.parse(readFileSync(devServerFile, "utf8"));
|
|
658
|
+
if (file !== null && typeof file === "object" && "pid" in file && file.pid === process.pid && "url" in file && file.url === devServerUrl) rmSync(devServerFile, { force: true });
|
|
659
|
+
} catch (error) {
|
|
660
|
+
if (!(error instanceof SyntaxError) && !(error instanceof Error && "code" in error && error.code === "ENOENT")) throw error;
|
|
661
|
+
}
|
|
662
|
+
};
|
|
663
|
+
server.httpServer?.once("listening", () => {
|
|
664
|
+
const address = server.httpServer?.address();
|
|
665
|
+
if (address === null || address === void 0 || typeof address === "string") return;
|
|
666
|
+
mkdirSync(dirname(devServerFile), { recursive: true });
|
|
667
|
+
devServerUrl = `http://localhost:${address.port}`;
|
|
668
|
+
writeFileSync(devServerFile, JSON.stringify({
|
|
669
|
+
url: devServerUrl,
|
|
670
|
+
pid: process.pid
|
|
671
|
+
}));
|
|
672
|
+
server.config.logger.info(`ℹ Preview in Hitch: ${orgUrl}/applets/${manifest.code}?_hitch_dev=${address.port}`);
|
|
673
|
+
process.once("exit", removeDevServerFile);
|
|
674
|
+
});
|
|
675
|
+
server.httpServer?.once("close", () => {
|
|
676
|
+
devWorkflows?.stop();
|
|
677
|
+
devListener?.close();
|
|
678
|
+
if (activeDevListener === devListener) activeDevListener = void 0;
|
|
679
|
+
process.off("exit", removeDevServerFile);
|
|
680
|
+
removeDevServerFile();
|
|
681
|
+
});
|
|
249
682
|
const mount = `/${manifest.code}`;
|
|
250
683
|
const apiPrefix = `${mount}/api`;
|
|
251
684
|
server.middlewares.use((request, response, next) => {
|
|
685
|
+
if (request.url === mount || request.url?.startsWith(`${mount}?`)) request.url = `${mount}/${request.url.slice(mount.length)}`;
|
|
252
686
|
const url = request.url ?? "";
|
|
687
|
+
const pathname = url.split("?", 1)[0] ?? "";
|
|
688
|
+
if (pathname.startsWith("/__applet/context/")) {
|
|
689
|
+
if (pathname !== `/__applet/context/${manifest.code}` || request.method !== "GET") {
|
|
690
|
+
response.writeHead(404).end();
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
693
|
+
if (refusesClient(request.headers, request.socket.remoteAddress)) {
|
|
694
|
+
response.writeHead(403).end();
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
response.setHeader("Cache-Control", "no-store");
|
|
698
|
+
response.setHeader("Content-Type", "application/json; charset=utf-8");
|
|
699
|
+
response.end(JSON.stringify({
|
|
700
|
+
cacheBuster,
|
|
701
|
+
cacheScope: person.id
|
|
702
|
+
}));
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
253
705
|
if (url === "/_hitch/client.js" || url.startsWith("/_hitch/client.js?")) {
|
|
254
706
|
response.statusCode = 200;
|
|
255
707
|
response.setHeader("Cache-Control", "no-store");
|
|
@@ -257,29 +709,37 @@ function applet(sourceDefinition, options = {}) {
|
|
|
257
709
|
response.end("// PostHog is disabled in standalone applet development.\n");
|
|
258
710
|
return;
|
|
259
711
|
}
|
|
260
|
-
if (url === "/theme.css" || url.startsWith("/theme.css?")) {
|
|
261
|
-
|
|
262
|
-
response.
|
|
263
|
-
|
|
264
|
-
response.setHeader("Content-Type", "text/css; charset=utf-8");
|
|
265
|
-
response.end(theme);
|
|
266
|
-
}, (error) => {
|
|
267
|
-
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
268
|
-
next();
|
|
269
|
-
return;
|
|
270
|
-
}
|
|
271
|
-
next(error);
|
|
712
|
+
if (url === "/api" || url.startsWith("/api/") || url.startsWith("/api?") || url === "/theme.css" || url.startsWith("/theme.css?")) {
|
|
713
|
+
serveDevProxy(request, response, proxy).catch((error) => {
|
|
714
|
+
if (response.headersSent) response.destroy(error instanceof Error ? error : new Error(String(error)));
|
|
715
|
+
else next(error);
|
|
272
716
|
});
|
|
273
717
|
return;
|
|
274
718
|
}
|
|
275
|
-
if (url === apiPrefix || url.startsWith(`${apiPrefix}/`) || url.startsWith(`${apiPrefix}?`))
|
|
719
|
+
if (url === apiPrefix || url.startsWith(`${apiPrefix}/`) || url.startsWith(`${apiPrefix}?`)) {
|
|
720
|
+
if (refusesClient(request.headers, request.socket.remoteAddress)) {
|
|
721
|
+
response.writeHead(403).end();
|
|
722
|
+
return;
|
|
723
|
+
}
|
|
724
|
+
request.url = url.slice(mount.length);
|
|
725
|
+
if (request.url.startsWith("/api/__hitch/") && request.headers.origin !== void 0) {
|
|
726
|
+
response.writeHead(404).end();
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
request.originalUrl = request.url;
|
|
730
|
+
request.headers[HITCH_USER_HEADER] = person.id;
|
|
731
|
+
request.headers[HITCH_SESSION_HEADER] = "local-dev";
|
|
732
|
+
request.rawHeaders = request.rawHeaders.flatMap((name, index, headers) => index % 2 === 0 && name.toLowerCase() !== "x-hitch-user" && name.toLowerCase() !== "x-hitch-session" ? [name, headers[index + 1] ?? ""] : []);
|
|
733
|
+
request.rawHeaders.push(HITCH_USER_HEADER, person.id, HITCH_SESSION_HEADER, "local-dev");
|
|
734
|
+
}
|
|
735
|
+
response.setHeader("Content-Security-Policy", `frame-ancestors ${orgOrigin}`);
|
|
276
736
|
next();
|
|
277
737
|
});
|
|
278
738
|
},
|
|
279
739
|
transformIndexHtml: {
|
|
280
740
|
order: "post",
|
|
281
741
|
handler(html) {
|
|
282
|
-
const parentOrigin = themeStylesheetUrl === void 0 ? void 0 : new URL(themeStylesheetUrl).origin;
|
|
742
|
+
const parentOrigin = devContext?.orgUrl === void 0 ? themeStylesheetUrl === void 0 ? void 0 : new URL(themeStylesheetUrl).origin : new URL(devContext.orgUrl).origin;
|
|
283
743
|
return {
|
|
284
744
|
html: themeStylesheetUrl === void 0 ? html : html.replace("href=\"/theme.css\"", `href="${themeStylesheetUrl}"`),
|
|
285
745
|
tags: [{
|
|
@@ -313,7 +773,7 @@ function applet(sourceDefinition, options = {}) {
|
|
|
313
773
|
const workerDir = outputOptions.dir ?? resolve(buildRootOutDir, "worker");
|
|
314
774
|
const mainModule = Object.values(bundle).find((output) => output.type === "chunk" && output.isEntry)?.fileName;
|
|
315
775
|
const strayModules = (await readdir(workerDir, { recursive: true })).filter((path) => /\.(js|mjs)$/.test(path) && path !== mainModule);
|
|
316
|
-
if (strayModules.length > 0) throw new Error(`The applet server must bundle to a single worker/index.js module; found: ${strayModules.join(", ")}
|
|
776
|
+
if (strayModules.length > 0) throw new Error(`The applet server must bundle to a single worker/index.js module; found: ${strayModules.join(", ")}.`);
|
|
317
777
|
console.log(`server → ${relative(requireAppletConfig().root, resolve(workerDir, mainModule ?? ""))}`);
|
|
318
778
|
}
|
|
319
779
|
}
|
|
@@ -333,7 +793,11 @@ function applet(sourceDefinition, options = {}) {
|
|
|
333
793
|
not_found_handling: "single-page-application",
|
|
334
794
|
run_worker_first: ["/api/*"]
|
|
335
795
|
} } : {},
|
|
336
|
-
...hasServer ? { main: resolve(root, ".hitch/entry.server.gen.ts") } : {}
|
|
796
|
+
...hasServer ? { main: resolve(root, ".hitch/entry.server.gen.ts") } : {},
|
|
797
|
+
...devApiUrl === void 0 ? {} : { vars: {
|
|
798
|
+
...workerConfig.vars,
|
|
799
|
+
HITCH_API_URL: devApiUrl
|
|
800
|
+
} }
|
|
337
801
|
};
|
|
338
802
|
},
|
|
339
803
|
get viteEnvironment() {
|
|
@@ -343,14 +807,19 @@ function applet(sourceDefinition, options = {}) {
|
|
|
343
807
|
...plugin,
|
|
344
808
|
apply: ((_config, { mode }) => mode !== "test")
|
|
345
809
|
}));
|
|
810
|
+
const hasClientSource = existsSync(resolve("src/client/routes"));
|
|
346
811
|
return [
|
|
347
812
|
orchestrator,
|
|
348
|
-
|
|
813
|
+
{
|
|
814
|
+
...clsxFromCn,
|
|
815
|
+
enforce: "pre"
|
|
816
|
+
},
|
|
817
|
+
...hasClientSource ? [tanstackRouter({
|
|
349
818
|
generatedRouteTree: ".hitch/routeTree.gen.ts",
|
|
350
819
|
routesDirectory: "src/client/routes",
|
|
351
820
|
routeFileIgnorePattern: "__root",
|
|
352
821
|
virtualRouteConfig
|
|
353
|
-
}), react()] : [],
|
|
822
|
+
}), react({ compiler: true })] : [],
|
|
354
823
|
cloudflarePlugins
|
|
355
824
|
];
|
|
356
825
|
}
|