sveltekit-temporal 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,7 +4,20 @@ One-shot CLI that wires the Temporal API polyfill into a SvelteKit project, with
4
4
 
5
5
  ## Usage
6
6
 
7
+ ## Using with the Svelte CLI (`sv add`)
8
+
9
+ If you prefer the official Svelte CLI, this package also works as an `sv` add-on:
10
+
7
11
  ```bash
12
+ # Or when creating a new project
13
+ npx sv create my-app
14
+ cd my-app
15
+ npx sv add sveltekit-temporal
16
+
17
+ # Into an existing project
18
+ npx sv add sveltekit-temporal
19
+
20
+ #Alternatively, you can run the CLI directly
8
21
  npx sveltekit-temporal
9
22
  ```
10
23
 
@@ -59,18 +72,18 @@ export async function handle({ event, resolve }) {
59
72
 
60
73
  ## Runtime support
61
74
 
62
- | Runtime | Native Temporal | Notes |
63
- |---------|----------------|-------|
64
- | Node.js ≤ 25 | No | Polyfill required |
65
- | Node.js 26+ | Yes (behind `--experimental-temporal`) | Polyfill still safe to include — the conditional check skips loading it |
66
- | Bun | No | Polyfill required; Bun tracks V8 but has not shipped Temporal yet |
67
- | Deno | No | Polyfill required; Deno tracks V8 but has not shipped Temporal yet |
68
- | Chrome 144+ | Yes | Polyfill skipped automatically |
69
- | Firefox 139+ | Yes | Polyfill skipped automatically |
70
- | Safari | No | Polyfill required |
75
+ | Runtime | Native Temporal | Notes |
76
+ | ------------ | -------------------------------------- | ----------------------------------------------------------------------- |
77
+ | Node.js ≤ 25 | No | Polyfill required |
78
+ | Node.js 26+ | Yes (behind `--experimental-temporal`) | Polyfill still safe to include — the conditional check skips loading it |
79
+ | Bun | No | Polyfill required; Bun tracks V8 but has not shipped Temporal yet |
80
+ | Deno | No | Polyfill required; Deno tracks V8 but has not shipped Temporal yet |
81
+ | Chrome 144+ | Yes | Polyfill skipped automatically |
82
+ | Firefox 139+ | Yes | Polyfill skipped automatically |
83
+ | Safari | No | Polyfill required |
71
84
 
72
85
  The conditional bootstrap in `src/lib/temporal.ts` checks `typeof Temporal !== 'undefined'` before loading the polyfill, so upgrading your runtime later requires no code changes.
73
86
 
74
87
  ## Switching polyfills later
75
88
 
76
- Just re-run the CLI and pick the other option. Only the two files that name the package (`src/lib/temporal.*` and `src/app.d.ts`) will change.
89
+ Re-run either the CLI or `sv add` and pick the other option. Only the two files that name the package (`src/lib/temporal.*` and `src/app.d.ts`) will change.
package/package.json CHANGED
@@ -1,9 +1,13 @@
1
1
  {
2
2
  "name": "sveltekit-temporal",
3
3
  "author": "Scott Rhamy",
4
- "version": "0.1.0",
4
+ "version": "0.2.0",
5
5
  "description": "Set up the Temporal API polyfill in a SvelteKit project with conditional loading on client and server.",
6
6
  "type": "module",
7
+ "exports": {
8
+ ".": "./src/index.js",
9
+ "./sv": "./src/addon.js"
10
+ },
7
11
  "bin": {
8
12
  "sveltekit-temporal": "bin/cli.js"
9
13
  },
@@ -19,12 +23,21 @@
19
23
  "@clack/prompts": "^0.7.0",
20
24
  "picocolors": "^1.0.0"
21
25
  },
26
+ "peerDependencies": {
27
+ "sv": "*"
28
+ },
29
+ "peerDependenciesMeta": {
30
+ "sv": {
31
+ "optional": true
32
+ }
33
+ },
22
34
  "keywords": [
23
35
  "svelte",
24
36
  "sveltekit",
25
37
  "temporal",
26
38
  "polyfill",
27
- "cli"
39
+ "cli",
40
+ "sv-add"
28
41
  ],
29
42
  "license": "MIT"
30
43
  }
package/src/addon.js ADDED
@@ -0,0 +1,54 @@
1
+ import { defineAddon, defineAddonOptions } from 'sv';
2
+ import { MARKER, buildBootstrap, buildNewAppDts, processAppDts } from './shared.js';
3
+
4
+ const options = defineAddonOptions()
5
+ .add('polyfill', {
6
+ question: 'Which Temporal polyfill would you like to use?',
7
+ type: 'select',
8
+ options: ['@js-temporal/polyfill', 'temporal-polyfill'],
9
+ default: '@js-temporal/polyfill'
10
+ })
11
+ .build();
12
+
13
+ export default defineAddon({
14
+ id: 'temporal',
15
+ shortDescription: 'Temporal API polyfill with conditional loading for SvelteKit',
16
+ options,
17
+
18
+ setup({ isKit, unsupported }) {
19
+ if (!isKit) unsupported('Requires SvelteKit.');
20
+ },
21
+
22
+ run({ sv, options, language, directory }) {
23
+ const pkgName = options.polyfill;
24
+ const isTs = language === 'ts';
25
+ const ext = language;
26
+
27
+ sv.dependency(pkgName, 'latest');
28
+
29
+ sv.file(`${directory.lib}/temporal.${ext}`, (content) => {
30
+ if (content && !content.includes(MARKER)) return false;
31
+ return buildBootstrap(pkgName, isTs);
32
+ });
33
+
34
+ sv.file(`${directory.kitRoutes}/+layout.${ext}`, (content) =>
35
+ content.includes("import '$lib/temporal'")
36
+ ? false
37
+ : `import '$lib/temporal';\n${content}`
38
+ );
39
+
40
+ sv.file(`${directory.src}/hooks.server.${ext}`, (content) =>
41
+ content.includes("import '$lib/temporal'")
42
+ ? false
43
+ : `import '$lib/temporal';\n${content}`
44
+ );
45
+
46
+ if (isTs) {
47
+ sv.file(`${directory.src}/app.d.ts`, (content) =>
48
+ content.trim() ? processAppDts(content, pkgName) : buildNewAppDts(pkgName)
49
+ );
50
+ }
51
+ },
52
+
53
+ nextSteps: () => ['Reference Temporal directly anywhere in your app — no imports needed.']
54
+ });
package/src/index.js CHANGED
@@ -1,8 +1,9 @@
1
1
  import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
2
- import { join, resolve, dirname } from 'node:path';
2
+ import { join, dirname } from 'node:path';
3
3
  import { execSync } from 'node:child_process';
4
4
  import * as p from '@clack/prompts';
5
5
  import pc from 'picocolors';
6
+ import { MARKER, buildBootstrap, buildNewAppDts, processAppDts } from './shared.js';
6
7
 
7
8
  const POLYFILLS = {
8
9
  official: {
@@ -17,8 +18,6 @@ const POLYFILLS = {
17
18
  }
18
19
  };
19
20
 
20
- const MARKER = '// sveltekit-temporal: managed block';
21
-
22
21
  export async function run() {
23
22
  console.log();
24
23
  p.intro(pc.bgCyan(pc.black(' sveltekit-temporal ')));
@@ -190,41 +189,6 @@ function installCommand(pm, pkg) {
190
189
  }
191
190
  }
192
191
 
193
- // ─── File generation ────────────────────────────────────────────────────────
194
-
195
- function buildBootstrap(pkgName, isTypeScript) {
196
- const tsLine1 = isTypeScript ? `\t// @ts-expect-error - polyfilling globalThis\n` : '';
197
- const tsLine2 = isTypeScript ? `\t// @ts-expect-error - patching Date.prototype\n` : '';
198
-
199
- return `${MARKER}
200
- // Conditionally load the Temporal polyfill when the runtime lacks native support.
201
- // Native (Chrome 144+, Firefox 139+) keeps zero overhead — the dynamic import is
202
- // code-split by Vite and only fetched on browsers that need it.
203
-
204
- if (typeof globalThis.Temporal === 'undefined') {
205
- const { Temporal, toTemporalInstant } = await import('${pkgName}');
206
- ${tsLine1}\tglobalThis.Temporal = Temporal;
207
- ${tsLine2}\tDate.prototype.toTemporalInstant = toTemporalInstant;
208
- }
209
-
210
- export {};
211
- `;
212
- }
213
-
214
- function buildAppDtsBlock(pkgName) {
215
- return `${MARKER}
216
- import type { Temporal as TemporalNS } from '${pkgName}';
217
-
218
- declare global {
219
- const Temporal: typeof TemporalNS;
220
-
221
- interface Date {
222
- toTemporalInstant(): TemporalNS.Instant;
223
- }
224
- }
225
- // end sveltekit-temporal managed block`;
226
- }
227
-
228
192
  // ─── File writers (idempotent) ──────────────────────────────────────────────
229
193
 
230
194
  function writeManaged(path, contents, log) {
@@ -266,58 +230,22 @@ function ensureImportInFile(path, importLine, log) {
266
230
  }
267
231
 
268
232
  function updateAppDts(path, pkgName, log) {
269
- const block = buildAppDtsBlock(pkgName);
270
-
271
233
  if (!existsSync(path)) {
272
- const stub = `// See https://svelte.dev/docs/kit/types#app.d.ts for information about these interfaces
273
- declare global {
274
- namespace App {
275
- // interface Error {}
276
- // interface Locals {}
277
- // interface PageData {}
278
- // interface PageState {}
279
- // interface Platform {}
280
- }
281
- }
282
-
283
- ${block}
284
-
285
- export {};
286
- `;
287
234
  mkdirSync(dirname(path), { recursive: true });
288
- writeFileSync(path, stub);
235
+ writeFileSync(path, buildNewAppDts(pkgName));
289
236
  log.created.push(rel(path));
290
237
  return;
291
238
  }
292
239
 
293
240
  const existing = readFileSync(path, 'utf8');
241
+ const result = processAppDts(existing, pkgName);
294
242
 
295
- // Already has our managed block — replace it (handles polyfill switch)
296
- if (existing.includes(MARKER)) {
297
- const pattern = new RegExp(
298
- `${escapeRegex(MARKER)}[\\s\\S]*?// end sveltekit-temporal managed block\\n?`,
299
- 'm'
300
- );
301
- const replaced = existing.replace(pattern, `${block}\n`);
302
- if (replaced === existing) {
303
- log.skipped.push(rel(path));
304
- } else {
305
- writeFileSync(path, replaced);
306
- log.updated.push(rel(path));
307
- }
243
+ if (result === false) {
244
+ log.skipped.push(rel(path));
308
245
  return;
309
246
  }
310
247
 
311
- // No managed block yet — append before any trailing `export {}` if present,
312
- // otherwise just append.
313
- const exportMatch = existing.match(/\n\s*export\s*\{\s*\};?\s*$/);
314
- let next;
315
- if (exportMatch) {
316
- next = existing.replace(exportMatch[0], `\n\n${block}\n${exportMatch[0]}`);
317
- } else {
318
- next = `${existing.trimEnd()}\n\n${block}\n\nexport {};\n`;
319
- }
320
- writeFileSync(path, next);
248
+ writeFileSync(path, result);
321
249
  log.updated.push(rel(path));
322
250
  }
323
251
 
@@ -326,7 +254,3 @@ export {};
326
254
  function rel(absPath) {
327
255
  return absPath.replace(process.cwd() + '/', '');
328
256
  }
329
-
330
- function escapeRegex(s) {
331
- return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
332
- }
package/src/shared.js ADDED
@@ -0,0 +1,76 @@
1
+ export const MARKER = '// sveltekit-temporal: managed block';
2
+ const END_MARKER = '// end sveltekit-temporal managed block';
3
+
4
+ export function buildBootstrap(pkgName, isTypeScript) {
5
+ const tsLine1 = isTypeScript ? `\t// @ts-expect-error - polyfilling globalThis\n` : '';
6
+ const tsLine2 = isTypeScript ? `\t// @ts-expect-error - patching Date.prototype\n` : '';
7
+
8
+ return `${MARKER}
9
+ // Conditionally load the Temporal polyfill when the runtime lacks native support.
10
+ // Native (Chrome 144+, Firefox 139+) keeps zero overhead — the dynamic import is
11
+ // code-split by Vite and only fetched on browsers that need it.
12
+
13
+ if (typeof globalThis.Temporal === 'undefined') {
14
+ const { Temporal, toTemporalInstant } = await import('${pkgName}');
15
+ ${tsLine1}\tglobalThis.Temporal = Temporal;
16
+ ${tsLine2}\tDate.prototype.toTemporalInstant = toTemporalInstant;
17
+ }
18
+
19
+ export {};
20
+ `;
21
+ }
22
+
23
+ export function buildAppDtsBlock(pkgName) {
24
+ return `${MARKER}
25
+ import type { Temporal as TemporalNS } from '${pkgName}';
26
+
27
+ declare global {
28
+ const Temporal: typeof TemporalNS;
29
+
30
+ interface Date {
31
+ toTemporalInstant(): TemporalNS.Instant;
32
+ }
33
+ }
34
+ ${END_MARKER}`;
35
+ }
36
+
37
+ export function buildNewAppDts(pkgName) {
38
+ return `// See https://svelte.dev/docs/kit/types#app.d.ts for information about these interfaces
39
+ declare global {
40
+ namespace App {
41
+ // interface Error {}
42
+ // interface Locals {}
43
+ // interface PageData {}
44
+ // interface PageState {}
45
+ // interface Platform {}
46
+ }
47
+ }
48
+
49
+ ${buildAppDtsBlock(pkgName)}
50
+
51
+ export {};
52
+ `;
53
+ }
54
+
55
+ export function processAppDts(content, pkgName) {
56
+ const block = buildAppDtsBlock(pkgName);
57
+
58
+ if (content.includes(MARKER)) {
59
+ const pattern = new RegExp(
60
+ `${escapeRegex(MARKER)}[\\s\\S]*?${escapeRegex(END_MARKER)}\\n?`,
61
+ 'm'
62
+ );
63
+ const replaced = content.replace(pattern, `${block}\n`);
64
+ return replaced === content ? false : replaced;
65
+ }
66
+
67
+ const exportMatch = content.match(/\n\s*export\s*\{\s*\};?\s*$/);
68
+ if (exportMatch) {
69
+ return content.replace(exportMatch[0], `\n\n${block}\n${exportMatch[0]}`);
70
+ }
71
+ return `${content.trimEnd()}\n\n${block}\n\nexport {};\n`;
72
+ }
73
+
74
+ export function escapeRegex(s) {
75
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
76
+ }