sveltekit-temporal 0.1.0 → 0.3.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/package.json CHANGED
@@ -1,30 +1,49 @@
1
1
  {
2
2
  "name": "sveltekit-temporal",
3
3
  "author": "Scott Rhamy",
4
- "version": "0.1.0",
4
+ "version": "0.3.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
+ ".": "./dist/index.js",
9
+ "./sv": "./dist/addon.js"
10
+ },
7
11
  "bin": {
8
12
  "sveltekit-temporal": "bin/cli.js"
9
13
  },
10
14
  "files": [
11
15
  "bin",
12
- "src",
16
+ "dist",
13
17
  "README.md"
14
18
  ],
19
+ "scripts": {
20
+ "build": "esbuild src/index.js --bundle --platform=node --format=esm --outfile=dist/index.js --external:node:* && esbuild src/addon.js --bundle --platform=node --format=esm --outfile=dist/addon.js --external:sv --external:node:*",
21
+ "prepublishOnly": "npm run build"
22
+ },
15
23
  "engines": {
16
24
  "node": ">=18"
17
25
  },
18
- "dependencies": {
26
+ "dependencies": {},
27
+ "devDependencies": {
19
28
  "@clack/prompts": "^0.7.0",
29
+ "esbuild": "^0.25.0",
20
30
  "picocolors": "^1.0.0"
21
31
  },
32
+ "peerDependencies": {
33
+ "sv": "*"
34
+ },
35
+ "peerDependenciesMeta": {
36
+ "sv": {
37
+ "optional": true
38
+ }
39
+ },
22
40
  "keywords": [
23
41
  "svelte",
24
42
  "sveltekit",
25
43
  "temporal",
26
44
  "polyfill",
27
- "cli"
45
+ "cli",
46
+ "sv-add"
28
47
  ],
29
48
  "license": "MIT"
30
49
  }
package/src/index.js DELETED
@@ -1,332 +0,0 @@
1
- import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
2
- import { join, resolve, dirname } from 'node:path';
3
- import { execSync } from 'node:child_process';
4
- import * as p from '@clack/prompts';
5
- import pc from 'picocolors';
6
-
7
- const POLYFILLS = {
8
- official: {
9
- pkg: '@js-temporal/polyfill',
10
- label: '@js-temporal/polyfill',
11
- hint: 'Official TC39 polyfill, ~100KB gzipped, most spec-conservative'
12
- },
13
- small: {
14
- pkg: 'temporal-polyfill',
15
- label: 'temporal-polyfill',
16
- hint: 'Smaller alternative (~40KB gzipped) by Adam Shaw, same API surface'
17
- }
18
- };
19
-
20
- const MARKER = '// sveltekit-temporal: managed block';
21
-
22
- export async function run() {
23
- console.log();
24
- p.intro(pc.bgCyan(pc.black(' sveltekit-temporal ')));
25
-
26
- const cwd = process.cwd();
27
-
28
- // 1. Validate we're in a SvelteKit project
29
- const projectInfo = detectProject(cwd);
30
- if (!projectInfo.ok) {
31
- p.cancel(projectInfo.reason);
32
- process.exit(1);
33
- }
34
-
35
- const { isTypeScript, pkgJson, pkgManager } = projectInfo;
36
-
37
- p.log.info(
38
- `Detected ${pc.cyan('SvelteKit')} project ${
39
- isTypeScript ? pc.dim('(TypeScript)') : pc.dim('(JavaScript)')
40
- } using ${pc.cyan(pkgManager)}`
41
- );
42
-
43
- // 2. Pick polyfill
44
- const choice = await p.select({
45
- message: 'Which Temporal polyfill would you like to use?',
46
- options: [
47
- {
48
- value: 'official',
49
- label: POLYFILLS.official.label,
50
- hint: POLYFILLS.official.hint
51
- },
52
- {
53
- value: 'small',
54
- label: POLYFILLS.small.label,
55
- hint: POLYFILLS.small.hint
56
- }
57
- ]
58
- });
59
-
60
- if (p.isCancel(choice)) {
61
- p.cancel('Cancelled.');
62
- process.exit(0);
63
- }
64
-
65
- const polyfill = POLYFILLS[choice];
66
-
67
- // 3. Check for existing setup
68
- const alreadyInstalled =
69
- pkgJson.dependencies?.[polyfill.pkg] || pkgJson.devDependencies?.[polyfill.pkg];
70
-
71
- // 4. Install dependency
72
- if (!alreadyInstalled) {
73
- const spin = p.spinner();
74
- spin.start(`Installing ${polyfill.pkg}`);
75
- try {
76
- execSync(installCommand(pkgManager, polyfill.pkg), {
77
- cwd,
78
- stdio: 'pipe'
79
- });
80
- spin.stop(`Installed ${pc.green(polyfill.pkg)}`);
81
- } catch (err) {
82
- spin.stop(pc.red(`Failed to install ${polyfill.pkg}`));
83
- p.log.error(err.message);
84
- process.exit(1);
85
- }
86
- } else {
87
- p.log.info(`${polyfill.pkg} already in package.json — skipping install`);
88
- }
89
-
90
- // 5. Write files
91
- const ext = isTypeScript ? 'ts' : 'js';
92
- const created = [];
93
- const updated = [];
94
- const skipped = [];
95
-
96
- // 5a. src/lib/temporal.{ts,js}
97
- const libDir = join(cwd, 'src', 'lib');
98
- mkdirSync(libDir, { recursive: true });
99
- const bootstrapPath = join(libDir, `temporal.${ext}`);
100
- const bootstrapContents = buildBootstrap(polyfill.pkg, isTypeScript);
101
- writeManaged(bootstrapPath, bootstrapContents, { created, updated, skipped });
102
-
103
- // 5b. src/routes/+layout.{ts,js} — preserve existing content, ensure import
104
- const layoutPath = join(cwd, 'src', 'routes', `+layout.${ext}`);
105
- ensureImportInFile(layoutPath, "import '$lib/temporal';", { created, updated, skipped });
106
-
107
- // 5c. src/hooks.server.{ts,js} — preserve existing content, ensure import
108
- const hooksPath = join(cwd, 'src', `hooks.server.${ext}`);
109
- ensureImportInFile(hooksPath, "import '$lib/temporal';", { created, updated, skipped });
110
-
111
- // 5d. app.d.ts (TypeScript only)
112
- if (isTypeScript) {
113
- const dtsPath = join(cwd, 'src', 'app.d.ts');
114
- updateAppDts(dtsPath, polyfill.pkg, { created, updated, skipped });
115
- }
116
-
117
- // 6. Summary
118
- const summary = [];
119
- if (created.length) summary.push(pc.green('Created:') + '\n ' + created.join('\n '));
120
- if (updated.length) summary.push(pc.yellow('Updated:') + '\n ' + updated.join('\n '));
121
- if (skipped.length)
122
- summary.push(pc.dim('Already configured:') + '\n ' + skipped.join('\n '));
123
-
124
- p.note(summary.join('\n\n') || 'No changes needed.', 'Summary');
125
-
126
- p.outro(
127
- `${pc.green('✓')} Temporal is wired up. Reference ${pc.cyan('Temporal')} anywhere in your app.`
128
- );
129
- }
130
-
131
- // ─── Detection ──────────────────────────────────────────────────────────────
132
-
133
- function detectProject(cwd) {
134
- const pkgPath = join(cwd, 'package.json');
135
- if (!existsSync(pkgPath)) {
136
- return { ok: false, reason: 'No package.json found in current directory.' };
137
- }
138
-
139
- let pkgJson;
140
- try {
141
- pkgJson = JSON.parse(readFileSync(pkgPath, 'utf8'));
142
- } catch {
143
- return { ok: false, reason: 'Could not parse package.json.' };
144
- }
145
-
146
- const allDeps = { ...pkgJson.dependencies, ...pkgJson.devDependencies };
147
- const hasSvelte = !!allDeps.svelte;
148
- const hasKit = !!allDeps['@sveltejs/kit'];
149
-
150
- if (!hasSvelte) {
151
- return {
152
- ok: false,
153
- reason: 'This does not look like a Svelte project (svelte not in dependencies).'
154
- };
155
- }
156
-
157
- if (!hasKit) {
158
- return {
159
- ok: false,
160
- reason:
161
- 'This script targets SvelteKit projects (@sveltejs/kit not found). For a plain Svelte app, the layout/hooks setup does not apply.'
162
- };
163
- }
164
-
165
- const isTypeScript =
166
- existsSync(join(cwd, 'tsconfig.json')) || existsSync(join(cwd, 'src', 'app.d.ts'));
167
-
168
- const pkgManager = detectPackageManager(cwd);
169
-
170
- return { ok: true, isTypeScript, pkgJson, pkgManager };
171
- }
172
-
173
- function detectPackageManager(cwd) {
174
- if (existsSync(join(cwd, 'bun.lockb')) || existsSync(join(cwd, 'bun.lock'))) return 'bun';
175
- if (existsSync(join(cwd, 'pnpm-lock.yaml'))) return 'pnpm';
176
- if (existsSync(join(cwd, 'yarn.lock'))) return 'yarn';
177
- return 'npm';
178
- }
179
-
180
- function installCommand(pm, pkg) {
181
- switch (pm) {
182
- case 'pnpm':
183
- return `pnpm add ${pkg}`;
184
- case 'yarn':
185
- return `yarn add ${pkg}`;
186
- case 'bun':
187
- return `bun add ${pkg}`;
188
- default:
189
- return `npm install ${pkg}`;
190
- }
191
- }
192
-
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
- // ─── File writers (idempotent) ──────────────────────────────────────────────
229
-
230
- function writeManaged(path, contents, log) {
231
- if (existsSync(path)) {
232
- const existing = readFileSync(path, 'utf8');
233
- if (existing.includes(MARKER)) {
234
- if (existing.trim() === contents.trim()) {
235
- log.skipped.push(rel(path));
236
- return;
237
- }
238
- writeFileSync(path, contents);
239
- log.updated.push(rel(path));
240
- return;
241
- }
242
- // File exists but isn't ours — back off
243
- log.skipped.push(`${rel(path)} ${pc.dim('(exists, not managed — left alone)')}`);
244
- return;
245
- }
246
- mkdirSync(dirname(path), { recursive: true });
247
- writeFileSync(path, contents);
248
- log.created.push(rel(path));
249
- }
250
-
251
- function ensureImportInFile(path, importLine, log) {
252
- if (!existsSync(path)) {
253
- mkdirSync(dirname(path), { recursive: true });
254
- writeFileSync(path, `${importLine}\n`);
255
- log.created.push(rel(path));
256
- return;
257
- }
258
- const existing = readFileSync(path, 'utf8');
259
- if (existing.includes(importLine)) {
260
- log.skipped.push(rel(path));
261
- return;
262
- }
263
- // Prepend import, preserve everything else
264
- writeFileSync(path, `${importLine}\n${existing}`);
265
- log.updated.push(rel(path));
266
- }
267
-
268
- function updateAppDts(path, pkgName, log) {
269
- const block = buildAppDtsBlock(pkgName);
270
-
271
- 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
- mkdirSync(dirname(path), { recursive: true });
288
- writeFileSync(path, stub);
289
- log.created.push(rel(path));
290
- return;
291
- }
292
-
293
- const existing = readFileSync(path, 'utf8');
294
-
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
- }
308
- return;
309
- }
310
-
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);
321
- log.updated.push(rel(path));
322
- }
323
-
324
- // ─── Utils ──────────────────────────────────────────────────────────────────
325
-
326
- function rel(absPath) {
327
- return absPath.replace(process.cwd() + '/', '');
328
- }
329
-
330
- function escapeRegex(s) {
331
- return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
332
- }