@tokamakdev/tok 0.1.1-beta.80 → 0.1.1-beta.82

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/README.md CHANGED
@@ -11,18 +11,21 @@ npx tok targets
11
11
 
12
12
  | Entry | Contents |
13
13
  | --- | --- |
14
- | `@tokamakdev/tok` | The `Config` type of `src/tokamak.ts` |
14
+ | `@tokamakdev/tok` | The `Config` type of the Vite plugin's options |
15
15
  | `@tokamakdev/tok/vite` | The Vite plugin, `tokamak()` |
16
16
  | `tok` | The CLI |
17
17
 
18
- The Vite plugin goes next to Cloudflare's:
18
+ The Vite plugin goes next to Cloudflare's, and its options are the app's
19
+ tokamak configuration:
19
20
 
20
21
  ```ts
21
22
  import { cloudflare } from "@cloudflare/vite-plugin";
22
23
  import { tokamak } from "@tokamakdev/tok/vite";
23
24
  import { defineConfig } from "vite";
24
25
 
25
- export default defineConfig({ plugins: [cloudflare(), tokamak()] });
26
+ export default defineConfig({
27
+ plugins: [cloudflare(), tokamak({ name: "My App", version: "1.0.0" })],
28
+ });
26
29
  ```
27
30
 
28
31
  It does nothing unless `tok` runs the build or development command.
package/dist/index.d.mts CHANGED
@@ -6,18 +6,18 @@ export interface PlatformConfig {
6
6
  name?: string;
7
7
  /** Application identifier. */
8
8
  identifier?: string;
9
- /** Icon in the platform's format, relative to the configuration file. */
9
+ /** Icon in the platform's format, relative to the Vite root. */
10
10
  icon?: string;
11
11
  /** A setting the platform pack declares; `tok` validates it. */
12
12
  [key: string]: Value | undefined;
13
13
  }
14
- /** The app's tokamak configuration, exported as `config` from `src/tokamak.ts`. */
14
+ /** The app's tokamak configuration: the tokamak Vite plugin's options other than `module`. */
15
15
  export interface Config {
16
16
  /** Display name for every platform. */
17
17
  name?: string;
18
18
  /** Application identifier for every platform. */
19
19
  identifier?: string;
20
- /** Icon for every platform, relative to the configuration file. */
20
+ /** Icon for every platform, relative to the Vite root. */
21
21
  icon?: string;
22
22
  /** App version. */
23
23
  version?: string;
package/dist/vite.d.mts CHANGED
@@ -1,8 +1,15 @@
1
1
  import { type Plugin } from "vite";
2
+ import type { Config } from "./index.mjs";
2
3
  /**
3
- * Reports the build's tokamak configuration, and the development server's
4
- * address and Worker name, to `tok`, and makes the entry Worker import the
5
- * configuration file. Without `TOKAMAK_VITE_OUTPUT`, which `tok` sets, it adds
6
- * no hooks.
4
+ * Reports the app's tokamak configuration, and the development server's
5
+ * address, HTTPS certificate and Worker name, to `tok`, and makes the entry
6
+ * Worker import `module`. Without `TOKAMAK_VITE_OUTPUT`, which `tok` sets, it
7
+ * adds no hooks.
7
8
  */
8
- export declare function tokamak(): Plugin[];
9
+ export declare function tokamak({ module, ...config }?: Config & {
10
+ /**
11
+ * The module the entry Worker imports, relative to the Vite root; by
12
+ * default `src/tokamak.ts`, then `src/tokamak.js`, when one exists.
13
+ */
14
+ module?: string;
15
+ }): Plugin[];
package/dist/vite.mjs CHANGED
@@ -1,57 +1,53 @@
1
+ import { X509Certificate } from "node:crypto";
1
2
  import fs from "node:fs";
2
3
  import path from "node:path";
3
- import { normalizePath, runnerImport, } from "vite";
4
- /** Configuration files tried in order when `tok` names none. */
5
- const CONFIG_FILES = ["src/tokamak.ts", "src/tokamak.js"];
4
+ import { normalizePath } from "vite";
5
+ /** Modules tried in order when the options name none. */
6
+ const DEFAULT_MODULES = ["src/tokamak.ts", "src/tokamak.js"];
6
7
  /** Wrangler configuration files Cloudflare's plugin tries in order. */
7
8
  const WRANGLER_FILES = ["wrangler.jsonc", "wrangler.json", "wrangler.toml"];
8
- /** The files each output directory's `config.json` was read from, once written. */
9
- const written = new Map();
9
+ const MIDDLEWARE_MODE_ERROR = "Vite is running in middleware mode, so tokamak cannot find the development server's address; tok dev needs Vite's own dev server";
10
10
  /**
11
- * Reports the build's tokamak configuration, and the development server's
12
- * address and Worker name, to `tok`, and makes the entry Worker import the
13
- * configuration file. Without `TOKAMAK_VITE_OUTPUT`, which `tok` sets, it adds
14
- * no hooks.
11
+ * Reports the app's tokamak configuration, and the development server's
12
+ * address, HTTPS certificate and Worker name, to `tok`, and makes the entry
13
+ * Worker import `module`. Without `TOKAMAK_VITE_OUTPUT`, which `tok` sets, it
14
+ * adds no hooks.
15
15
  */
16
- export function tokamak() {
16
+ export function tokamak({ module, ...config } = {}) {
17
17
  const output = process.env.TOKAMAK_VITE_OUTPUT;
18
18
  if (!output) {
19
19
  return [];
20
20
  }
21
- let config;
22
21
  let file;
23
22
  /** The module IDs of each entry environment's inputs. */
24
23
  const entryIds = new Map();
25
- /** The entry environments whose input imports the configuration file. */
24
+ /** The entry environments whose input imports the module. */
26
25
  const importing = new Set();
27
- const write = () => {
28
- const files = writeConfig(output, file, config);
29
- // A file that fails to load stays watched, so fixing it rewrites `config.json`.
30
- written.set(output, files.catch(() => (file ? [file] : [])));
31
- return files;
32
- };
33
26
  return [
34
27
  {
35
28
  name: "tokamak",
36
- async configResolved(resolved) {
37
- config = resolved;
38
- file = findConfigFile(resolved.root);
39
- await (written.has(output) ? written.get(output) : write());
40
- },
41
- async watchChange(id) {
42
- if ((await written.get(output))?.includes(id)) {
43
- await write();
44
- }
29
+ configResolved({ root }) {
30
+ file = findModule(root, module);
31
+ writeJson(output, "config.json", { root, config });
45
32
  },
46
33
  async configureServer(server) {
47
34
  const httpServer = server.httpServer;
35
+ // A middleware-mode server beside a reported one, such as the server
36
+ // Astro syncs content with, leaves the report in place.
48
37
  if (!httpServer) {
38
+ if (!fs.existsSync(path.join(output, "server.json"))) {
39
+ writeJson(output, "server.json", { error: MIDDLEWARE_MODE_ERROR });
40
+ }
49
41
  return;
50
42
  }
51
43
  const workerName = await readWorkerName(server.config.root);
52
44
  httpServer.on("listening", () => {
53
45
  const urls = server.resolvedUrls;
54
- writeJson(output, "server.json", { url: urls?.local[0] ?? urls?.network[0], workerName });
46
+ writeJson(output, "server.json", {
47
+ url: urls?.local[0] ?? urls?.network[0],
48
+ workerName,
49
+ certificates: endEntityCertificates(server.config.server.https),
50
+ });
55
51
  });
56
52
  },
57
53
  async buildApp(builder) {
@@ -65,9 +61,6 @@ export function tokamak() {
65
61
  name: "tokamak:entry",
66
62
  applyToEnvironment: ({ config }) => file !== undefined && buildsEntryWorker(config),
67
63
  async transform(code, id) {
68
- if (id === file) {
69
- return withoutConfig(this, code);
70
- }
71
64
  const environment = this.environment;
72
65
  const ids = entryIds.get(environment.name) ??
73
66
  Promise.all(inputs(environment.config.build).map(async (input) => (await this.resolve(input))?.id));
@@ -88,33 +81,14 @@ export function tokamak() {
88
81
  },
89
82
  ];
90
83
  }
91
- /** The configuration file `TOKAMAK_CONFIG` names, or the first default under `root` that exists. */
92
- function findConfigFile(root) {
93
- const named = process.env.TOKAMAK_CONFIG;
94
- if (named) {
95
- if (!fs.existsSync(named)) {
96
- throw new Error(`tokamak configuration file not found: ${named}`);
97
- }
98
- return normalizePath(path.resolve(named));
84
+ /** The module `name` under `root`, or else the first default under `root` that exists. */
85
+ function findModule(root, name) {
86
+ const candidates = (name ? [name] : DEFAULT_MODULES).map((candidate) => normalizePath(path.resolve(root, candidate)));
87
+ const file = candidates.find((candidate) => fs.existsSync(candidate));
88
+ if (name && !file) {
89
+ throw new Error(`tokamak module not found: ${candidates[0]}`);
99
90
  }
100
- const file = CONFIG_FILES.map((name) => path.resolve(root, name)).find((candidate) => fs.existsSync(candidate));
101
- return file && normalizePath(file);
102
- }
103
- /**
104
- * Writes the file's `config` export, with the file's path, to `config.json`,
105
- * returning the files it was read from.
106
- */
107
- async function writeConfig(output, file, config) {
108
- if (!file) {
109
- writeJson(output, "config.json", { config: {} });
110
- return [];
111
- }
112
- const { module, dependencies } = await runnerImport(file, {
113
- root: config.root,
114
- resolve: { alias: config.resolve.alias, tsconfigPaths: config.resolve.tsconfigPaths },
115
- });
116
- writeJson(output, "config.json", { file, config: module.config ?? {} });
117
- return [file, ...dependencies.map(normalizePath)];
91
+ return file;
118
92
  }
119
93
  /**
120
94
  * The name of the Worker in the Wrangler configuration file in `root`, found
@@ -130,6 +104,22 @@ async function readWorkerName(root) {
130
104
  const worker = unstable_readConfig({ config: file, env: process.env.CLOUDFLARE_ENV }, { hideWarnings: true });
131
105
  return worker.topLevelName ?? worker.name;
132
106
  }
107
+ /**
108
+ * The PEM of the end-entity certificate of each chain in `https.cert`, which
109
+ * Vite reads from a file when a string names one.
110
+ */
111
+ function endEntityCertificates(https) {
112
+ const cert = typeof https?.cert === "string" ? readFileIfExists(https.cert) : https?.cert;
113
+ return cert && [cert].flat().map((chain) => new X509Certificate(chain).toString()).join("");
114
+ }
115
+ function readFileIfExists(file) {
116
+ try {
117
+ return fs.readFileSync(path.resolve(file));
118
+ }
119
+ catch {
120
+ return file;
121
+ }
122
+ }
133
123
  /**
134
124
  * Whether an environment builds from the entry Worker: Cloudflare's plugin
135
125
  * gives a manifest to its entry and prerender Workers, and Astro to its server
@@ -143,50 +133,6 @@ function inputs(build) {
143
133
  const { input } = build.rollupOptions;
144
134
  return typeof input === "string" ? [input] : Object.values(input ?? {});
145
135
  }
146
- /**
147
- * `code` without its `config` export: the `export const config = ...`
148
- * statement is blanked, or only its `export` keyword while other statements
149
- * mention `config`, so that every other position, and so the source map, is
150
- * unchanged.
151
- */
152
- function withoutConfig(context, code) {
153
- const statements = context.parse(code).body;
154
- const statement = statements.find(exportsConfig);
155
- if (!statement) {
156
- return;
157
- }
158
- const declaration = statement.declaration;
159
- if (declaration?.declarations?.length !== 1) {
160
- return context.error("declare config in its own `export const config = ...` statement");
161
- }
162
- const mentioned = statements.some((other) => other !== statement && mentions(other, "config"));
163
- const end = mentioned ? declaration.start : statement.end;
164
- const blank = code.slice(statement.start, end).replace(/[^\n]/g, " ");
165
- return { code: code.slice(0, statement.start) + blank + code.slice(end), map: null };
166
- }
167
- /** Whether `node` contains an identifier named `name`, property names included. */
168
- function mentions(node, name) {
169
- if (typeof node !== "object" || node === null) {
170
- return false;
171
- }
172
- const identifier = node;
173
- if (identifier.type === "Identifier" && identifier.name === name) {
174
- return true;
175
- }
176
- return Object.values(node).some((child) => mentions(child, name));
177
- }
178
- function exportsConfig(statement) {
179
- if (statement.type !== "ExportNamedDeclaration") {
180
- return false;
181
- }
182
- const declaration = statement.declaration;
183
- const names = [
184
- declaration?.id?.name,
185
- ...(declaration?.declarations ?? []).map((declarator) => declarator.id.name),
186
- ...(statement.specifiers ?? []).map((specifier) => specifier.exported.name),
187
- ];
188
- return names.includes("config");
189
- }
190
136
  /** Writes `value` to `name` whole, as `tok` reads the file as soon as it exists. */
191
137
  function writeJson(output, name, value) {
192
138
  const file = path.join(output, name);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tokamakdev/tok",
3
- "version": "0.1.1-beta.80",
3
+ "version": "0.1.1-beta.82",
4
4
  "description": "The tokamak CLI, Vite plugin and app API: build native apps from a Cloudflare Workers codebase",
5
5
  "repository": {
6
6
  "type": "git",
@@ -55,17 +55,17 @@
55
55
  "wrangler": "4.147.0"
56
56
  },
57
57
  "optionalDependencies": {
58
- "@tokamakdev/platform-android-arm64": "0.1.1-beta.80",
59
- "@tokamakdev/platform-ios-arm64": "0.1.1-beta.80",
60
- "@tokamakdev/platform-ios-simulator-arm64": "0.1.1-beta.80",
61
- "@tokamakdev/platform-ios-simulator-x64": "0.1.1-beta.80",
62
- "@tokamakdev/platform-macos-arm64": "0.1.1-beta.80",
63
- "@tokamakdev/platform-macos-x64": "0.1.1-beta.80",
64
- "@tokamakdev/platform-windows-x64": "0.1.1-beta.80",
65
- "@tokamakdev/tok-darwin-arm64": "0.1.1-beta.80",
66
- "@tokamakdev/tok-darwin-x64": "0.1.1-beta.80",
67
- "@tokamakdev/tok-linux-x64": "0.1.1-beta.80",
68
- "@tokamakdev/tok-win32-x64": "0.1.1-beta.80"
58
+ "@tokamakdev/platform-android-arm64": "0.1.1-beta.82",
59
+ "@tokamakdev/platform-ios-arm64": "0.1.1-beta.82",
60
+ "@tokamakdev/platform-ios-simulator-arm64": "0.1.1-beta.82",
61
+ "@tokamakdev/platform-ios-simulator-x64": "0.1.1-beta.82",
62
+ "@tokamakdev/platform-macos-arm64": "0.1.1-beta.82",
63
+ "@tokamakdev/platform-macos-x64": "0.1.1-beta.82",
64
+ "@tokamakdev/platform-windows-x64": "0.1.1-beta.82",
65
+ "@tokamakdev/tok-darwin-arm64": "0.1.1-beta.82",
66
+ "@tokamakdev/tok-darwin-x64": "0.1.1-beta.82",
67
+ "@tokamakdev/tok-linux-x64": "0.1.1-beta.82",
68
+ "@tokamakdev/tok-win32-x64": "0.1.1-beta.82"
69
69
  },
70
70
  "publishConfig": {
71
71
  "access": "public"