sv create allow download from playground
Closes #602
You can now run pnpx https://pkg.pr.new/sveltejs/cli/sv@662 create --from-playground=https://svelte.dev/playground/hello-world. This is currently doing more or less the same the stuff the Download App button in the playground is doing. Apart from that, you can choose additional addons, ts vs js and install deps. External dependencies are auto-detected and installed into the package.json
Todos:
svelte-?version= parameter from the playground urldominikg: one thing to remember is that this is untrusted input basically, the playground could reference malicious packages, so installation with --ignore-scripts and making users aware that they have to check the content beforehand would be good.Idea sv create --from-playground=id-or-hash
Instead of downloading & saving, extracting, installing deps, we could do all of that directly
neat idea! --from-playground should probably accept an id-or-hash-or-url (or perhaps it should accept the full url every time? 🤔).
having it accept the url should make it easier to select and input it into the cli. for instance, it's far easier to click the address bar once (so that it highlights the full url to copy), rather than having to select a specific chunk of it to copy the hash or id.
Nice idea indeed!
Some random thoughts:
template ?Community add-ons is a big topic and it still requires some work in sv, then community will need to have an add-on, then... It will come for sure, but not over night.
I feel that this could be HUGE as community starter !
You wanna demo a library, just share npx sv@latest create --template https://svelte.dev/playground/hello-world?version=5.36.8 in your readme and you are good to go.
urlI'm not sure I'll fully trust a hashed url.
https://svelte.dev/playground/hello-world ?)https://svelte.dev/playground/jycouet/hello-world ? )npx svPersonally i was mainly thinking about option d. Option a would be a great idea to enhance visibility into this feature.
An alternative to d would be to make the "download app" button a popup and the let the user decide on how to proceed. Although im unsure if we have a popup in any other place on the svelte.dev site.
I'm looking into how we can get the actual playground data from svelte.dev today.
You can't really have a SvelteKit project in the playground today, which would make some of these ideas a bit difficult. Something like Svelte Lab would support these integrations pretty well (and it actually already offers npx sv add). So it seems like some of this would require some more investment in the playground first.
Not sure why this would make these ideas difficult? When you press download in the playground today, it's basically doing sv create and then adding some imports here and there. But nothing special.
Getting to the code from the repls is actually easiere than expected, although the saved playground and the hash have different types, apparently.
const link = 'https://svelte.dev/playground/628f435d787a465f9c1f1854134d6f70/';
const url = new URL(link);
const [, playground_id] = url.pathname.match(/\/playground\/([a-f0-9]+)/) || [];
const hash = url.hash.slice(1);
if (!playground_id && !hash) {
throw new Error('Invalid URL format');
}
let data = [];
// forked playgrounds have a playground_id and an optional hash, therefore check the hash first
if (hash) {
data = JSON.parse(await decode_and_decompress_text(hash));
} else {
const res = await fetch(`https://svelte.dev/playground/api/${playground_id}.json`);
data = await res.json();
}
console.log(data);
// Taken from https://github.com/sveltejs/svelte.dev/blob/ba7ad256f786aa5bc67eac3a58608f3f50b59e91/apps/svelte.dev/src/routes/(authed)/playground/%5Bid%5D/gzip.js#L19-L29
/** @param {string} input */
export async function decode_and_decompress_text(input) {
const decoded = atob(input.replaceAll('-', '+').replaceAll('_', '/'));
// putting it directly into the blob gives a corrupted file
const u8 = new Uint8Array(decoded.length);
for (let i = 0; i < decoded.length; i++) {
u8[i] = decoded.charCodeAt(i);
}
const stream = new Blob([u8]).stream().pipeThrough(new DecompressionStream('gzip'));
return new Response(stream).text();
}