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