scratch4js 1.0.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.
@@ -0,0 +1,61 @@
1
+ /**
2
+ * A costume: an image (bitmap or SVG) a target can display.
3
+ */
4
+ export class Costume extends Asset {
5
+ set bitmapResolution(value: number);
6
+ /** @returns {number} Pixels per unit for bitmaps (1 for SVG, 2 for HD bitmaps). */
7
+ get bitmapResolution(): number;
8
+ set rotationCenterX(value: number);
9
+ /** @returns {number} X of the rotation/anchor center, in costume pixels. */
10
+ get rotationCenterX(): number;
11
+ set rotationCenterY(value: number);
12
+ /** @returns {number} Y of the rotation/anchor center, in costume pixels. */
13
+ get rotationCenterY(): number;
14
+ }
15
+ /**
16
+ * A sound a target can play.
17
+ */
18
+ export class Sound extends Asset {
19
+ set rate(value: number);
20
+ /** @returns {number} Sample rate in Hz. */
21
+ get rate(): number;
22
+ set sampleCount(value: number);
23
+ /** @returns {number} Number of samples in the clip. */
24
+ get sampleCount(): number;
25
+ }
26
+ /**
27
+ * Base class for the two kinds of media a target can own: costumes and sounds.
28
+ * Each asset wraps its raw entry in `project.json` plus the bytes stored in the
29
+ * surrounding zip, keyed by `md5ext` (e.g. `b7f1cf69….wav`).
30
+ *
31
+ * @abstract
32
+ */
33
+ declare class Asset {
34
+ /**
35
+ * @param {object} json - The raw costume/sound entry from project.json.
36
+ * @param {import('./project.js').Project} project - Owning project (holds the bytes).
37
+ */
38
+ constructor(json: object, project: import("./project.js").Project);
39
+ /** @type {object} The underlying JSON; mutate via the accessors below. */
40
+ json: object;
41
+ /** @type {import('./project.js').Project} */
42
+ project: import("./project.js").Project;
43
+ set name(value: string);
44
+ /** @returns {string} Display name shown in the editor. */
45
+ get name(): string;
46
+ /** @returns {string} File extension/type, e.g. `png`, `svg`, `wav`, `mp3`. */
47
+ get dataFormat(): string;
48
+ /** @returns {string} The `<md5>.<ext>` filename of this asset inside the zip. */
49
+ get md5ext(): string;
50
+ set data(bytes: Uint8Array | undefined);
51
+ /**
52
+ * The raw bytes of this asset, read from / written to the project's zip.
53
+ *
54
+ * Assigning new bytes recomputes the MD5 and rewrites `assetId`/`md5ext`, so
55
+ * the file is always addressed by the hash of its current contents.
56
+ *
57
+ * @returns {Uint8Array | undefined}
58
+ */
59
+ get data(): Uint8Array | undefined;
60
+ }
61
+ export {};
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Guess a Scratch `dataFormat` (file extension) from raw asset bytes.
3
+ *
4
+ * @param {Uint8Array} bytes - The asset contents.
5
+ * @returns {string | undefined} The detected format, or undefined if unknown.
6
+ */
7
+ export function sniffFormat(bytes: Uint8Array): string | undefined;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Generate a fresh 20-character Scratch-style unique id.
3
+ *
4
+ * @returns {string} A new id.
5
+ */
6
+ export function uid(): string;
@@ -0,0 +1,6 @@
1
+ export { Project } from "./project.js";
2
+ export { md5 } from "./md5.js";
3
+ export { uid } from "./ids.js";
4
+ export { sniffFormat } from "./format.js";
5
+ export { Target, Stage, Sprite } from "./target.js";
6
+ export { Costume, Sound } from "./assets.js";
@@ -0,0 +1,2 @@
1
+ import t from"@turbowarp/jszip";let e=new Int32Array([0xd76aa478,0xe8c7b756,0x242070db,0xc1bdceee,0xf57c0faf,0x4787c62a,0xa8304613,0xfd469501,0x698098d8,0x8b44f7af,0xffff5bb1,0x895cd7be,0x6b901122,0xfd987193,0xa679438e,0x49b40821,0xf61e2562,0xc040b340,0x265e5a51,0xe9b6c7aa,0xd62f105d,0x2441453,0xd8a1e681,0xe7d3fbc8,0x21e1cde6,0xc33707d6,0xf4d50d87,0x455a14ed,0xa9e3e905,0xfcefa3f8,0x676f02d9,0x8d2a4c8a,0xfffa3942,0x8771f681,0x6d9d6122,0xfde5380c,0xa4beea44,0x4bdecfa9,0xf6bb4b60,0xbebfbc70,0x289b7ec6,0xeaa127fa,0xd4ef3085,0x4881d05,0xd9d4d039,0xe6db99e5,0x1fa27cf8,0xc4ac5665,0xf4292244,0x432aff97,0xab9423a7,0xfc93a039,0x655b59c3,0x8f0ccc92,0xffeff47d,0x85845dd1,0x6fa87e4f,0xfe2ce6e0,0xa3014314,0x4e0811a1,0xf7537e82,0xbd3af235,0x2ad7d2bb,0xeb86d391]),s=[7,12,17,22,7,12,17,22,7,12,17,22,7,12,17,22,5,9,14,20,5,9,14,20,5,9,14,20,5,9,14,20,4,11,16,23,4,11,16,23,4,11,16,23,4,11,16,23,6,10,15,21,6,10,15,21,6,10,15,21,6,10,15,21],r=(t,e)=>t<<e|t>>>32-e,n=[];for(let t=0;t<256;t++)n[t]=(t<16?"0":"")+t.toString(16);let o=t=>n[255&t]+n[t>>>8&255]+n[t>>>16&255]+n[t>>>24&255];function i(t){let n=t instanceof Uint8Array?t:new Uint8Array(t),i=n.length,a=i+1,u=a+(a%64<=56?56-a%64:120-a%64)+8,d=new Uint8Array(u);d.set(n),d[i]=128;let l=i<<3>>>0,m=i>>>29>>>0;d[u-8]=255&l,d[u-7]=l>>>8&255,d[u-6]=l>>>16&255,d[u-5]=l>>>24&255,d[u-4]=255&m,d[u-3]=m>>>8&255,d[u-2]=m>>>16&255,d[u-1]=m>>>24&255;let h=0x67452301,c=0xefcdab89,j=0x98badcfe,f=0x10325476,g=new Int32Array(16);for(let t=0;t<u;t+=64){for(let e=0;e<16;e++){let s=t+4*e;g[e]=d[s]|d[s+1]<<8|d[s+2]<<16|d[s+3]<<24}let n=h,o=c,i=j,a=f;for(let t=0;t<64;t++){let u,d;t<16?(u=o&i|~o&a,d=t):t<32?(u=a&o|~a&i,d=(5*t+1)%16):t<48?(u=o^i^a,d=(3*t+5)%16):(u=i^(o|~a),d=7*t%16),u=u+n+e[t]+g[d]|0,n=a,a=i,i=o,o=o+r(u,s[t])|0}h=h+n|0,c=c+o|0,j=j+i|0,f=f+a|0}return o(h)+o(c)+o(j)+o(f)}class a{constructor(t,e){this.json=t,this.project=e}get name(){return this.json.name}set name(t){this.json.name=String(t)}get dataFormat(){return this.json.dataFormat}get md5ext(){return this.json.md5ext}get data(){return this.project.assets.get(this.json.md5ext)}set data(t){let e=t instanceof Uint8Array?t:new Uint8Array(t),s=this.json.md5ext,r=i(e);this.json.assetId=r,this.json.md5ext=`${r}.${this.json.dataFormat}`,this.project.assets.set(this.json.md5ext,e),s&&s!==this.json.md5ext&&this.project._maybeDropAsset(s)}}class u extends a{get bitmapResolution(){return this.json.bitmapResolution??1}set bitmapResolution(t){this.json.bitmapResolution=Number(t)}get rotationCenterX(){return this.json.rotationCenterX??0}set rotationCenterX(t){this.json.rotationCenterX=Number(t)}get rotationCenterY(){return this.json.rotationCenterY??0}set rotationCenterY(t){this.json.rotationCenterY=Number(t)}}class d extends a{get rate(){return this.json.rate}set rate(t){this.json.rate=Number(t)}get sampleCount(){return this.json.sampleCount}set sampleCount(t){this.json.sampleCount=Number(t)}}let l=(t,e)=>e.every((e,s)=>t[s]===e),m=t=>[...t].map(t=>t.charCodeAt(0));function h(t){if(!t||t.length<4)return;if(l(t,[137,80,78,71]))return"png";if(l(t,[255,216,255]))return"jpg";if(l(t,m("GIF")))return"gif";if(l(t,m("RIFF")))return"wav";if(l(t,m("ID3"))||255===t[0]&&(224&t[1])==224)return"mp3";let e=String.fromCharCode(...t.slice(0,256)).trimStart();if(e.startsWith("<?xml")||e.startsWith("<svg")||e.includes("<svg"))return"svg"}let c="!#%()*+,-./:;=?@[]^_`{|}~ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";function j(){let t="";for(let e=0;e<20;e++)t+=c[Math.floor(Math.random()*c.length)];return t}class f{constructor(t,e){this.json=t,this.project=e}get isStage(){return!!this.json.isStage}get name(){return this.json.name}set name(t){this.json.name=String(t)}get volume(){return this.json.volume??100}set volume(t){this.json.volume=Number(t)}get costumes(){return this.json.costumes.map(t=>new u(t,this.project))}get currentCostume(){return this.json.currentCostume??0}set currentCostume(t){this.json.currentCostume=Number(t)}getCostume(t){let e=this.json.costumes.find(e=>e.name===t);return e?new u(e,this.project):void 0}addCostume(t,e,s={}){let r=e instanceof Uint8Array?e:new Uint8Array(e),n=s.dataFormat??h(r)??"png",o=i(r),a={name:String(t),bitmapResolution:s.bitmapResolution??("svg"===n?1:2),dataFormat:n,assetId:o,md5ext:`${o}.${n}`,rotationCenterX:s.rotationCenterX??0,rotationCenterY:s.rotationCenterY??0};return this.project.assets.set(a.md5ext,r),this.json.costumes.push(a),new u(a,this.project)}removeCostume(t){return this._removeMedia("costumes",t)}get sounds(){return this.json.sounds.map(t=>new d(t,this.project))}getSound(t){let e=this.json.sounds.find(e=>e.name===t);return e?new d(e,this.project):void 0}addSound(t,e,s={}){let r=e instanceof Uint8Array?e:new Uint8Array(e),n=s.dataFormat??h(r)??"wav",o=i(r),a={name:String(t),assetId:o,dataFormat:n,format:"",rate:s.rate??48e3,sampleCount:s.sampleCount??0,md5ext:`${o}.${n}`};return this.project.assets.set(a.md5ext,r),this.json.sounds.push(a),new d(a,this.project)}removeSound(t){return this._removeMedia("sounds",t)}_removeMedia(t,e){let s=this.json[t],r="number"==typeof e?e:s.findIndex(t=>t.name===e);if(r<0||r>=s.length)return!1;let[n]=s.splice(r,1);return"costumes"===t&&this.json.currentCostume>=s.length&&(this.json.currentCostume=Math.max(0,s.length-1)),this.project._maybeDropAsset(n.md5ext),!0}get variableNames(){return Object.values(this.json.variables).map(([t])=>t)}getVariable(t){let e=Object.values(this.json.variables).find(([e])=>e===t);return e?e[1]:void 0}setVariable(t,e){for(let[s,r]of Object.entries(this.json.variables))if(r[0]===t)return r[1]=e,s;let s=j();return this.json.variables[s]=[t,e],s}deleteVariable(t){for(let[e,s]of Object.entries(this.json.variables))if(s[0]===t)return delete this.json.variables[e],!0;return!1}get listNames(){return Object.values(this.json.lists).map(([t])=>t)}getList(t){let e=Object.values(this.json.lists).find(([e])=>e===t);return e?e[1]:void 0}setList(t,e=[]){for(let[s,r]of Object.entries(this.json.lists))if(r[0]===t)return r[1]=e,s;let s=j();return this.json.lists[s]=[t,e],s}deleteList(t){for(let[e,s]of Object.entries(this.json.lists))if(s[0]===t)return delete this.json.lists[e],!0;return!1}get blocks(){return this.json.blocks}}class g extends f{get isStage(){return!0}get tempo(){return this.json.tempo??60}set tempo(t){this.json.tempo=Number(t)}get videoState(){return this.json.videoState??"off"}set videoState(t){this.json.videoState=String(t)}get videoTransparency(){return this.json.videoTransparency??50}set videoTransparency(t){this.json.videoTransparency=Number(t)}get broadcastNames(){return Object.values(this.json.broadcasts)}addBroadcast(t){for(let[e,s]of Object.entries(this.json.broadcasts))if(s===t)return e;let e=`broadcastMsgId-${t}`;return this.json.broadcasts[e]=t,e}}class x extends f{get isStage(){return!1}get x(){return this.json.x??0}set x(t){this.json.x=Number(t)}get y(){return this.json.y??0}set y(t){this.json.y=Number(t)}get size(){return this.json.size??100}set size(t){this.json.size=Number(t)}get direction(){return this.json.direction??90}set direction(t){this.json.direction=Number(t)}get visible(){return this.json.visible??!0}set visible(t){this.json.visible=!!t}get draggable(){return this.json.draggable??!1}set draggable(t){this.json.draggable=!!t}get rotationStyle(){return this.json.rotationStyle??"all around"}set rotationStyle(t){this.json.rotationStyle=String(t)}get layerOrder(){return this.json.layerOrder??0}set layerOrder(t){this.json.layerOrder=Number(t)}}let b={semver:"3.0.0",vm:"14.0.0",agent:""};class p{constructor(t,e=new Map){this.json=t,this.assets=e}static async load(e){let s=await t.loadAsync(e),r=s.file("project.json");if(!r)throw Error("Not a valid sb3: project.json is missing.");let n=JSON.parse(await r.async("string")),o=new Map,i=[];return s.forEach((t,e)=>{e.dir||"project.json"===t||i.push(e.async("uint8array").then(e=>o.set(t,e)))}),await Promise.all(i),new p(n,o)}static create(){return new p({targets:[{isStage:!0,name:"Stage",variables:{},lists:{},broadcasts:{},blocks:{},comments:{},currentCostume:0,costumes:[],sounds:[],volume:100,layerOrder:0,tempo:60,videoTransparency:50,videoState:"on",textToSpeechLanguage:null}],monitors:[],extensions:[],meta:{...b}})}get stage(){return new g(this.json.targets.find(t=>t.isStage),this)}get sprites(){return this.json.targets.filter(t=>!t.isStage).map(t=>new x(t,this))}get targets(){return this.json.targets.map(t=>t.isStage?new g(t,this):new x(t,this))}get meta(){return this.json.meta}get monitors(){return this.json.monitors}get extensions(){return this.json.extensions}sprite(t){let e=this.json.targets.find(e=>!e.isStage&&e.name===t);return e?new x(e,this):void 0}target(t){let e=this.json.targets.find(e=>e.name===t);if(e)return e.isStage?new g(e,this):new x(e,this)}addSprite(t,e={}){if(this.sprite(t))throw Error(`A sprite named "${t}" already exists.`);let s=Math.max(0,...this.json.targets.map(t=>t.layerOrder??0)),r={isStage:!1,name:String(t),variables:{},lists:{},broadcasts:{},blocks:{},comments:{},currentCostume:0,costumes:[],sounds:[],volume:100,layerOrder:s+1,visible:!0,x:0,y:0,size:100,direction:90,draggable:!1,rotationStyle:"all around",...e};return this.json.targets.push(r),new x(r,this)}removeSprite(t){let e="string"==typeof t?t:t.name,s=this.json.targets.findIndex(t=>!t.isStage&&t.name===e);if(s<0)return!1;let[r]=this.json.targets.splice(s,1);for(let t of[...r.costumes,...r.sounds])this._maybeDropAsset(t.md5ext);return!0}_maybeDropAsset(t){!t||this.json.targets.some(e=>e.costumes.some(e=>e.md5ext===t)||e.sounds.some(e=>e.md5ext===t))||this.assets.delete(t)}async save({compressionLevel:e=6}={}){let s=new t;for(let[t,e]of(s.file("project.json",JSON.stringify(this.json)),this.assets))s.file(t,e);return s.generateAsync({type:"uint8array",compression:"DEFLATE",compressionOptions:{level:e}})}}export{u as Costume,p as Project,d as Sound,x as Sprite,g as Stage,f as Target,i as md5,h as sniffFormat,j as uid};
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":["../../src/md5.js","../../src/assets.js","../../src/format.js","../../src/ids.js","../../src/target.js","../../src/project.js"],"sourcesContent":["// Pure-JS MD5 (RFC 1321). Scratch names asset files by the MD5 hash of their\n// bytes, so we need a hash that runs in both Node and the browser without\n// pulling in a dependency.\n\n// prettier-ignore\nconst K = new Int32Array([\n 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, 0xf57c0faf, 0x4787c62a,\n 0xa8304613, 0xfd469501, 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be,\n 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, 0xf61e2562, 0xc040b340,\n 0x265e5a51, 0xe9b6c7aa, 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8,\n 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, 0xa9e3e905, 0xfcefa3f8,\n 0x676f02d9, 0x8d2a4c8a, 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c,\n 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, 0x289b7ec6, 0xeaa127fa,\n 0xd4ef3085, 0x04881d05, 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665,\n 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, 0x655b59c3, 0x8f0ccc92,\n 0xffeff47d, 0x85845dd1, 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1,\n 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391,\n]);\n\n// prettier-ignore\nconst S = [\n 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,\n 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20,\n 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,\n 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21,\n];\n\nconst rotl = (x, c) => (x << c) | (x >>> (32 - c));\n\nconst HEX = [];\nfor (let i = 0; i < 256; i++) HEX[i] = (i < 16 ? '0' : '') + i.toString(16);\nconst hexLE = (x) =>\n HEX[x & 0xff] +\n HEX[(x >>> 8) & 0xff] +\n HEX[(x >>> 16) & 0xff] +\n HEX[(x >>> 24) & 0xff];\n\n/**\n * Compute the lowercase hex MD5 digest of some bytes.\n *\n * @param {Uint8Array | ArrayBuffer} input - Bytes to hash.\n * @returns {string} 32-character lowercase hex digest.\n */\nexport function md5(input) {\n const msg = input instanceof Uint8Array ? input : new Uint8Array(input);\n const n = msg.length;\n\n // Pad: 0x80, zeros, then the original bit length as a 64-bit little-endian int.\n const withOne = n + 1;\n const padZeros =\n withOne % 64 <= 56 ? 56 - (withOne % 64) : 120 - (withOne % 64);\n const total = withOne + padZeros + 8;\n const buf = new Uint8Array(total);\n buf.set(msg);\n buf[n] = 0x80;\n const bitsLo = (n << 3) >>> 0;\n const bitsHi = (n >>> 29) >>> 0;\n buf[total - 8] = bitsLo & 0xff;\n buf[total - 7] = (bitsLo >>> 8) & 0xff;\n buf[total - 6] = (bitsLo >>> 16) & 0xff;\n buf[total - 5] = (bitsLo >>> 24) & 0xff;\n buf[total - 4] = bitsHi & 0xff;\n buf[total - 3] = (bitsHi >>> 8) & 0xff;\n buf[total - 2] = (bitsHi >>> 16) & 0xff;\n buf[total - 1] = (bitsHi >>> 24) & 0xff;\n\n let a0 = 0x67452301;\n let b0 = 0xefcdab89;\n let c0 = 0x98badcfe;\n let d0 = 0x10325476;\n\n const M = new Int32Array(16);\n for (let off = 0; off < total; off += 64) {\n for (let i = 0; i < 16; i++) {\n const j = off + i * 4;\n M[i] =\n buf[j] | (buf[j + 1] << 8) | (buf[j + 2] << 16) | (buf[j + 3] << 24);\n }\n\n let A = a0;\n let B = b0;\n let C = c0;\n let D = d0;\n\n for (let i = 0; i < 64; i++) {\n let F;\n let g;\n if (i < 16) {\n F = (B & C) | (~B & D);\n g = i;\n } else if (i < 32) {\n F = (D & B) | (~D & C);\n g = (5 * i + 1) % 16;\n } else if (i < 48) {\n F = B ^ C ^ D;\n g = (3 * i + 5) % 16;\n } else {\n F = C ^ (B | ~D);\n g = (7 * i) % 16;\n }\n F = (F + A + K[i] + M[g]) | 0;\n A = D;\n D = C;\n C = B;\n B = (B + rotl(F, S[i])) | 0;\n }\n\n a0 = (a0 + A) | 0;\n b0 = (b0 + B) | 0;\n c0 = (c0 + C) | 0;\n d0 = (d0 + D) | 0;\n }\n\n return hexLE(a0) + hexLE(b0) + hexLE(c0) + hexLE(d0);\n}\n","import { md5 } from './md5.js';\n\n/**\n * Base class for the two kinds of media a target can own: costumes and sounds.\n * Each asset wraps its raw entry in `project.json` plus the bytes stored in the\n * surrounding zip, keyed by `md5ext` (e.g. `b7f1cf69….wav`).\n *\n * @abstract\n */\nclass Asset {\n /**\n * @param {object} json - The raw costume/sound entry from project.json.\n * @param {import('./project.js').Project} project - Owning project (holds the bytes).\n */\n constructor(json, project) {\n /** @type {object} The underlying JSON; mutate via the accessors below. */\n this.json = json;\n /** @type {import('./project.js').Project} */\n this.project = project;\n }\n\n /** @returns {string} Display name shown in the editor. */\n get name() {\n return this.json.name;\n }\n set name(value) {\n this.json.name = String(value);\n }\n\n /** @returns {string} File extension/type, e.g. `png`, `svg`, `wav`, `mp3`. */\n get dataFormat() {\n return this.json.dataFormat;\n }\n\n /** @returns {string} The `<md5>.<ext>` filename of this asset inside the zip. */\n get md5ext() {\n return this.json.md5ext;\n }\n\n /**\n * The raw bytes of this asset, read from / written to the project's zip.\n *\n * Assigning new bytes recomputes the MD5 and rewrites `assetId`/`md5ext`, so\n * the file is always addressed by the hash of its current contents.\n *\n * @returns {Uint8Array | undefined}\n */\n get data() {\n return this.project.assets.get(this.json.md5ext);\n }\n set data(bytes) {\n const buf = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);\n const old = this.json.md5ext;\n const hash = md5(buf);\n this.json.assetId = hash;\n this.json.md5ext = `${hash}.${this.json.dataFormat}`;\n this.project.assets.set(this.json.md5ext, buf);\n if (old && old !== this.json.md5ext) this.project._maybeDropAsset(old);\n }\n}\n\n/**\n * A costume: an image (bitmap or SVG) a target can display.\n */\nexport class Costume extends Asset {\n /** @returns {number} Pixels per unit for bitmaps (1 for SVG, 2 for HD bitmaps). */\n get bitmapResolution() {\n return this.json.bitmapResolution ?? 1;\n }\n set bitmapResolution(value) {\n this.json.bitmapResolution = Number(value);\n }\n\n /** @returns {number} X of the rotation/anchor center, in costume pixels. */\n get rotationCenterX() {\n return this.json.rotationCenterX ?? 0;\n }\n set rotationCenterX(value) {\n this.json.rotationCenterX = Number(value);\n }\n\n /** @returns {number} Y of the rotation/anchor center, in costume pixels. */\n get rotationCenterY() {\n return this.json.rotationCenterY ?? 0;\n }\n set rotationCenterY(value) {\n this.json.rotationCenterY = Number(value);\n }\n}\n\n/**\n * A sound a target can play.\n */\nexport class Sound extends Asset {\n /** @returns {number} Sample rate in Hz. */\n get rate() {\n return this.json.rate;\n }\n set rate(value) {\n this.json.rate = Number(value);\n }\n\n /** @returns {number} Number of samples in the clip. */\n get sampleCount() {\n return this.json.sampleCount;\n }\n set sampleCount(value) {\n this.json.sampleCount = Number(value);\n }\n}\n","// Best-effort detection of an asset's type from its leading bytes, so callers\n// can drop in a buffer without spelling out `dataFormat` every time.\n\nconst startsWith = (bytes, sig) => sig.every((b, i) => bytes[i] === b);\nconst ascii = (s) => [...s].map((c) => c.charCodeAt(0));\n\n/**\n * Guess a Scratch `dataFormat` (file extension) from raw asset bytes.\n *\n * @param {Uint8Array} bytes - The asset contents.\n * @returns {string | undefined} The detected format, or undefined if unknown.\n */\nexport function sniffFormat(bytes) {\n if (!bytes || bytes.length < 4) return undefined;\n if (startsWith(bytes, [0x89, 0x50, 0x4e, 0x47])) return 'png';\n if (startsWith(bytes, [0xff, 0xd8, 0xff])) return 'jpg';\n if (startsWith(bytes, ascii('GIF'))) return 'gif';\n if (startsWith(bytes, ascii('RIFF'))) return 'wav';\n if (\n startsWith(bytes, ascii('ID3')) ||\n (bytes[0] === 0xff && (bytes[1] & 0xe0) === 0xe0)\n )\n return 'mp3';\n // Text-based: SVG documents start with an XML prolog or the root tag.\n const head = String.fromCharCode(...bytes.slice(0, 256)).trimStart();\n if (\n head.startsWith('<?xml') ||\n head.startsWith('<svg') ||\n head.includes('<svg')\n )\n return 'svg';\n return undefined;\n}\n","// Scratch generates the keys used for blocks, variables, lists and broadcasts\n// from a fixed \"soup\" of characters. We mirror that here so generated IDs look\n// native and stay clear of JSON-unsafe characters.\nconst SOUP =\n '!#%()*+,-./:;=?@[]^_`{|}~' +\n 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' +\n 'abcdefghijklmnopqrstuvwxyz' +\n '0123456789';\n\n/**\n * Generate a fresh 20-character Scratch-style unique id.\n *\n * @returns {string} A new id.\n */\nexport function uid() {\n let id = '';\n for (let i = 0; i < 20; i++) {\n id += SOUP[Math.floor(Math.random() * SOUP.length)];\n }\n return id;\n}\n","import { Costume, Sound } from './assets.js';\nimport { sniffFormat } from './format.js';\nimport { md5 } from './md5.js';\nimport { uid } from './ids.js';\n\n/**\n * A target is anything that lives in the project's `targets` array: either the\n * single {@link Stage} or a {@link Sprite}. This base class covers everything\n * the two share — name, costumes, sounds, variables, lists and raw blocks.\n *\n * @abstract\n */\nexport class Target {\n /**\n * @param {object} json - The raw target entry from project.json.\n * @param {import('./project.js').Project} project - Owning project.\n */\n constructor(json, project) {\n /** @type {object} The underlying JSON; the accessors below keep it in sync. */\n this.json = json;\n /** @type {import('./project.js').Project} */\n this.project = project;\n }\n\n /** @returns {boolean} True for the stage, false for sprites. */\n get isStage() {\n return Boolean(this.json.isStage);\n }\n\n /** @returns {string} The target's name. */\n get name() {\n return this.json.name;\n }\n set name(value) {\n this.json.name = String(value);\n }\n\n /** @returns {number} Output volume, 0–100. */\n get volume() {\n return this.json.volume ?? 100;\n }\n set volume(value) {\n this.json.volume = Number(value);\n }\n\n // --- Costumes -------------------------------------------------------------\n\n /** @returns {Costume[]} The target's costumes, in order. */\n get costumes() {\n return this.json.costumes.map((c) => new Costume(c, this.project));\n }\n\n /** @returns {number} Index of the currently selected costume. */\n get currentCostume() {\n return this.json.currentCostume ?? 0;\n }\n set currentCostume(value) {\n this.json.currentCostume = Number(value);\n }\n\n /**\n * Find a costume by name.\n *\n * @param {string} name\n * @returns {Costume | undefined}\n */\n getCostume(name) {\n const entry = this.json.costumes.find((c) => c.name === name);\n return entry ? new Costume(entry, this.project) : undefined;\n }\n\n /**\n * Add a costume from raw image bytes. The asset is stored in the zip keyed by\n * its MD5, and a costume entry pointing at it is appended.\n *\n * @param {string} name - Costume name.\n * @param {Uint8Array | ArrayBuffer} data - Image bytes (PNG/SVG/JPG/…).\n * @param {object} [options]\n * @param {string} [options.dataFormat] - Override the detected file type.\n * @param {number} [options.rotationCenterX] - Anchor X (defaults to 0).\n * @param {number} [options.rotationCenterY] - Anchor Y (defaults to 0).\n * @param {number} [options.bitmapResolution] - 1 for SVG, 2 for HD bitmaps.\n * @returns {Costume} The newly added costume.\n */\n addCostume(name, data, options = {}) {\n const bytes = data instanceof Uint8Array ? data : new Uint8Array(data);\n const dataFormat = options.dataFormat ?? sniffFormat(bytes) ?? 'png';\n const hash = md5(bytes);\n const entry = {\n name: String(name),\n bitmapResolution:\n options.bitmapResolution ?? (dataFormat === 'svg' ? 1 : 2),\n dataFormat,\n assetId: hash,\n md5ext: `${hash}.${dataFormat}`,\n rotationCenterX: options.rotationCenterX ?? 0,\n rotationCenterY: options.rotationCenterY ?? 0,\n };\n this.project.assets.set(entry.md5ext, bytes);\n this.json.costumes.push(entry);\n return new Costume(entry, this.project);\n }\n\n /**\n * Remove a costume by name or index.\n *\n * @param {string | number} nameOrIndex\n * @returns {boolean} True if a costume was removed.\n */\n removeCostume(nameOrIndex) {\n return this._removeMedia('costumes', nameOrIndex);\n }\n\n // --- Sounds ---------------------------------------------------------------\n\n /** @returns {Sound[]} The target's sounds, in order. */\n get sounds() {\n return this.json.sounds.map((s) => new Sound(s, this.project));\n }\n\n /**\n * Find a sound by name.\n *\n * @param {string} name\n * @returns {Sound | undefined}\n */\n getSound(name) {\n const entry = this.json.sounds.find((s) => s.name === name);\n return entry ? new Sound(entry, this.project) : undefined;\n }\n\n /**\n * Add a sound from raw audio bytes.\n *\n * @param {string} name - Sound name.\n * @param {Uint8Array | ArrayBuffer} data - Audio bytes (WAV/MP3).\n * @param {object} [options]\n * @param {string} [options.dataFormat] - Override the detected file type.\n * @param {number} [options.rate] - Sample rate in Hz (default 48000).\n * @param {number} [options.sampleCount] - Number of samples (default 0).\n * @returns {Sound} The newly added sound.\n */\n addSound(name, data, options = {}) {\n const bytes = data instanceof Uint8Array ? data : new Uint8Array(data);\n const dataFormat = options.dataFormat ?? sniffFormat(bytes) ?? 'wav';\n const hash = md5(bytes);\n const entry = {\n name: String(name),\n assetId: hash,\n dataFormat,\n format: '',\n rate: options.rate ?? 48000,\n sampleCount: options.sampleCount ?? 0,\n md5ext: `${hash}.${dataFormat}`,\n };\n this.project.assets.set(entry.md5ext, bytes);\n this.json.sounds.push(entry);\n return new Sound(entry, this.project);\n }\n\n /**\n * Remove a sound by name or index.\n *\n * @param {string | number} nameOrIndex\n * @returns {boolean} True if a sound was removed.\n */\n removeSound(nameOrIndex) {\n return this._removeMedia('sounds', nameOrIndex);\n }\n\n /**\n * @param {'costumes' | 'sounds'} kind\n * @param {string | number} nameOrIndex\n * @returns {boolean}\n * @private\n */\n _removeMedia(kind, nameOrIndex) {\n const list = this.json[kind];\n const index =\n typeof nameOrIndex === 'number'\n ? nameOrIndex\n : list.findIndex((m) => m.name === nameOrIndex);\n if (index < 0 || index >= list.length) return false;\n const [removed] = list.splice(index, 1);\n if (kind === 'costumes' && this.json.currentCostume >= list.length) {\n this.json.currentCostume = Math.max(0, list.length - 1);\n }\n this.project._maybeDropAsset(removed.md5ext);\n return true;\n }\n\n // --- Variables & lists ----------------------------------------------------\n\n /** @returns {string[]} Names of this target's variables. */\n get variableNames() {\n return Object.values(this.json.variables).map(([name]) => name);\n }\n\n /**\n * Read a variable's value by name.\n *\n * @param {string} name\n * @returns {string | number | boolean | undefined}\n */\n getVariable(name) {\n const found = Object.values(this.json.variables).find(([n]) => n === name);\n return found ? found[1] : undefined;\n }\n\n /**\n * Create or update a variable by name.\n *\n * @param {string} name\n * @param {string | number | boolean} value\n * @returns {string} The variable's id.\n */\n setVariable(name, value) {\n for (const [id, entry] of Object.entries(this.json.variables)) {\n if (entry[0] === name) {\n entry[1] = value;\n return id;\n }\n }\n const id = uid();\n this.json.variables[id] = [name, value];\n return id;\n }\n\n /**\n * Delete a variable by name.\n *\n * @param {string} name\n * @returns {boolean} True if a variable was deleted.\n */\n deleteVariable(name) {\n for (const [id, entry] of Object.entries(this.json.variables)) {\n if (entry[0] === name) {\n delete this.json.variables[id];\n return true;\n }\n }\n return false;\n }\n\n /** @returns {string[]} Names of this target's lists. */\n get listNames() {\n return Object.values(this.json.lists).map(([name]) => name);\n }\n\n /**\n * Read a list's contents by name.\n *\n * @param {string} name\n * @returns {Array<string | number> | undefined}\n */\n getList(name) {\n const found = Object.values(this.json.lists).find(([n]) => n === name);\n return found ? found[1] : undefined;\n }\n\n /**\n * Create or replace a list by name.\n *\n * @param {string} name\n * @param {Array<string | number>} items\n * @returns {string} The list's id.\n */\n setList(name, items = []) {\n for (const [id, entry] of Object.entries(this.json.lists)) {\n if (entry[0] === name) {\n entry[1] = items;\n return id;\n }\n }\n const id = uid();\n this.json.lists[id] = [name, items];\n return id;\n }\n\n /**\n * Delete a list by name.\n *\n * @param {string} name\n * @returns {boolean} True if a list was deleted.\n */\n deleteList(name) {\n for (const [id, entry] of Object.entries(this.json.lists)) {\n if (entry[0] === name) {\n delete this.json.lists[id];\n return true;\n }\n }\n return false;\n }\n\n /**\n * The raw `blocks` object for advanced scripting edits. Keys are block ids;\n * values are the block definitions. Mutate directly for low-level changes.\n *\n * @returns {object}\n */\n get blocks() {\n return this.json.blocks;\n }\n}\n\n/**\n * The stage: the single backdrop-bearing target that also owns the project's\n * broadcasts.\n */\nexport class Stage extends Target {\n /** @returns {true} Always the stage. Discriminates {@link Stage} from {@link Sprite}. */\n get isStage() {\n return true;\n }\n\n /** @returns {number} Tempo for music blocks, in BPM. */\n get tempo() {\n return this.json.tempo ?? 60;\n }\n set tempo(value) {\n this.json.tempo = Number(value);\n }\n\n /** @returns {string} Video input state: `on`, `off`, or `on-flipped`. */\n get videoState() {\n return this.json.videoState ?? 'off';\n }\n set videoState(value) {\n this.json.videoState = String(value);\n }\n\n /** @returns {number} Video transparency, 0–100. */\n get videoTransparency() {\n return this.json.videoTransparency ?? 50;\n }\n set videoTransparency(value) {\n this.json.videoTransparency = Number(value);\n }\n\n /** @returns {string[]} Names of all broadcast messages in the project. */\n get broadcastNames() {\n return Object.values(this.json.broadcasts);\n }\n\n /**\n * Add a broadcast message if it does not already exist.\n *\n * @param {string} name\n * @returns {string} The broadcast's id.\n */\n addBroadcast(name) {\n for (const [id, n] of Object.entries(this.json.broadcasts)) {\n if (n === name) return id;\n }\n const id = `broadcastMsgId-${name}`;\n this.json.broadcasts[id] = name;\n return id;\n }\n}\n\n/**\n * A sprite: a movable target with a position, size and orientation.\n */\nexport class Sprite extends Target {\n /** @returns {false} Never the stage. Discriminates {@link Sprite} from {@link Stage}. */\n get isStage() {\n return false;\n }\n\n /** @returns {number} X position on the stage (−240…240). */\n get x() {\n return this.json.x ?? 0;\n }\n set x(value) {\n this.json.x = Number(value);\n }\n\n /** @returns {number} Y position on the stage (−180…180). */\n get y() {\n return this.json.y ?? 0;\n }\n set y(value) {\n this.json.y = Number(value);\n }\n\n /** @returns {number} Size as a percentage (100 = original). */\n get size() {\n return this.json.size ?? 100;\n }\n set size(value) {\n this.json.size = Number(value);\n }\n\n /** @returns {number} Direction in degrees (90 = pointing right). */\n get direction() {\n return this.json.direction ?? 90;\n }\n set direction(value) {\n this.json.direction = Number(value);\n }\n\n /** @returns {boolean} Whether the sprite is shown. */\n get visible() {\n return this.json.visible ?? true;\n }\n set visible(value) {\n this.json.visible = Boolean(value);\n }\n\n /** @returns {boolean} Whether the sprite can be dragged in the player. */\n get draggable() {\n return this.json.draggable ?? false;\n }\n set draggable(value) {\n this.json.draggable = Boolean(value);\n }\n\n /** @returns {string} Rotation style: `all around`, `left-right`, `don't rotate`. */\n get rotationStyle() {\n return this.json.rotationStyle ?? 'all around';\n }\n set rotationStyle(value) {\n this.json.rotationStyle = String(value);\n }\n\n /** @returns {number} Stacking order; higher draws on top. */\n get layerOrder() {\n return this.json.layerOrder ?? 0;\n }\n set layerOrder(value) {\n this.json.layerOrder = Number(value);\n }\n}\n","import JSZip from '@turbowarp/jszip';\nimport { Sprite, Stage } from './target.js';\n\n/**\n * The project's `meta` block.\n *\n * @typedef {object} ProjectMeta\n * @property {string} semver - Scratch project schema version (e.g. `3.0.0`).\n * @property {string} vm - VM version that wrote the project.\n * @property {string} agent - User-agent string of the editor, if any.\n */\n\nconst DEFAULT_META = {\n semver: '3.0.0',\n vm: '14.0.0',\n agent: '',\n};\n\n/**\n * A loaded Scratch project (`.sb3`). An sb3 is a zip holding a `project.json`\n * description plus the costume/sound asset files it references. This class is\n * the entry point: load bytes, edit declaratively through {@link Stage} and\n * {@link Sprite}, then save back to bytes.\n *\n * @example\n * import { Project } from 'scratch4js';\n * import { readFile, writeFile } from 'node:fs/promises';\n *\n * const project = await Project.load(await readFile('game.sb3'));\n * const cat = project.sprite('Sprite1');\n * cat.x = 0;\n * cat.size = 150;\n * project.stage.setVariable('score', 0);\n * await writeFile('game.edited.sb3', await project.save());\n */\nexport class Project {\n /**\n * Prefer {@link Project.load}. Construct directly only when you already hold a\n * parsed `project.json` and its asset bytes.\n *\n * @param {object} json - Parsed project.json.\n * @param {Map<string, Uint8Array>} [assets] - Asset bytes keyed by `md5ext`.\n */\n constructor(json, assets = new Map()) {\n /** @type {object} The parsed project.json. */\n this.json = json;\n /** @type {Map<string, Uint8Array>} Asset bytes keyed by `<md5>.<ext>`. */\n this.assets = assets;\n }\n\n /**\n * Load a project from the raw bytes of an `.sb3` file.\n *\n * @param {Uint8Array | ArrayBuffer | Buffer} data - The sb3 zip bytes.\n * @returns {Promise<Project>}\n */\n static async load(data) {\n const zip = await JSZip.loadAsync(data);\n const projectFile = zip.file('project.json');\n if (!projectFile)\n throw new Error('Not a valid sb3: project.json is missing.');\n const json = JSON.parse(await projectFile.async('string'));\n\n const assets = new Map();\n const reads = [];\n zip.forEach((path, file) => {\n if (file.dir || path === 'project.json') return;\n reads.push(\n file.async('uint8array').then((bytes) => assets.set(path, bytes)),\n );\n });\n await Promise.all(reads);\n\n return new Project(json, assets);\n }\n\n /**\n * Create a new, empty project containing just a bare stage.\n *\n * @returns {Project}\n */\n static create() {\n const json = {\n targets: [\n {\n isStage: true,\n name: 'Stage',\n variables: {},\n lists: {},\n broadcasts: {},\n blocks: {},\n comments: {},\n currentCostume: 0,\n costumes: [],\n sounds: [],\n volume: 100,\n layerOrder: 0,\n tempo: 60,\n videoTransparency: 50,\n videoState: 'on',\n textToSpeechLanguage: null,\n },\n ],\n monitors: [],\n extensions: [],\n meta: { ...DEFAULT_META },\n };\n return new Project(json);\n }\n\n /** @returns {Stage} The project's single stage. */\n get stage() {\n const json = this.json.targets.find((t) => t.isStage);\n return new Stage(json, this);\n }\n\n /** @returns {Sprite[]} All non-stage targets, in array order. */\n get sprites() {\n return this.json.targets\n .filter((t) => !t.isStage)\n .map((t) => new Sprite(t, this));\n }\n\n /** @returns {Array<Stage | Sprite>} Every target, stage included. */\n get targets() {\n return this.json.targets.map((t) =>\n t.isStage ? new Stage(t, this) : new Sprite(t, this),\n );\n }\n\n /** @returns {ProjectMeta} The project's `meta` block (semver, vm, agent). */\n get meta() {\n return this.json.meta;\n }\n\n /** @returns {object[]} The raw `monitors` (variable/list watchers) array. */\n get monitors() {\n return this.json.monitors;\n }\n\n /** @returns {string[]} Ids of enabled extensions (e.g. `pen`, `music`). */\n get extensions() {\n return this.json.extensions;\n }\n\n /**\n * Find a sprite by name.\n *\n * @param {string} name\n * @returns {Sprite | undefined}\n */\n sprite(name) {\n const json = this.json.targets.find((t) => !t.isStage && t.name === name);\n return json ? new Sprite(json, this) : undefined;\n }\n\n /**\n * Find any target (sprite or stage) by name.\n *\n * @param {string} name\n * @returns {Stage | Sprite | undefined}\n */\n target(name) {\n const json = this.json.targets.find((t) => t.name === name);\n if (!json) return undefined;\n return json.isStage ? new Stage(json, this) : new Sprite(json, this);\n }\n\n /**\n * Add a new, empty sprite. It starts with no costumes, so add at least one\n * with {@link Sprite#addCostume} before opening the project in the editor.\n *\n * @param {string} name - Sprite name (must be unique among sprites).\n * @param {object} [props] - Initial property overrides (`x`, `y`, `size`, …).\n * @returns {Sprite} The new sprite.\n */\n addSprite(name, props = {}) {\n if (this.sprite(name))\n throw new Error(`A sprite named \"${name}\" already exists.`);\n const maxLayer = Math.max(\n 0,\n ...this.json.targets.map((t) => t.layerOrder ?? 0),\n );\n const json = {\n isStage: false,\n name: String(name),\n variables: {},\n lists: {},\n broadcasts: {},\n blocks: {},\n comments: {},\n currentCostume: 0,\n costumes: [],\n sounds: [],\n volume: 100,\n layerOrder: maxLayer + 1,\n visible: true,\n x: 0,\n y: 0,\n size: 100,\n direction: 90,\n draggable: false,\n rotationStyle: 'all around',\n ...props,\n };\n this.json.targets.push(json);\n return new Sprite(json, this);\n }\n\n /**\n * Remove a sprite by name or instance. Assets it solely owned are dropped.\n *\n * @param {string | Sprite} nameOrSprite\n * @returns {boolean} True if a sprite was removed.\n */\n removeSprite(nameOrSprite) {\n const name =\n typeof nameOrSprite === 'string' ? nameOrSprite : nameOrSprite.name;\n const index = this.json.targets.findIndex(\n (t) => !t.isStage && t.name === name,\n );\n if (index < 0) return false;\n const [removed] = this.json.targets.splice(index, 1);\n for (const media of [...removed.costumes, ...removed.sounds]) {\n this._maybeDropAsset(media.md5ext);\n }\n return true;\n }\n\n /**\n * Drop an asset's bytes if no costume or sound anywhere still references it.\n * Called automatically when media is removed or its bytes are replaced.\n *\n * @param {string} md5ext\n * @private\n */\n _maybeDropAsset(md5ext) {\n if (!md5ext) return;\n const stillUsed = this.json.targets.some(\n (t) =>\n t.costumes.some((c) => c.md5ext === md5ext) ||\n t.sounds.some((s) => s.md5ext === md5ext),\n );\n if (!stillUsed) this.assets.delete(md5ext);\n }\n\n /**\n * Serialize the project back into `.sb3` bytes.\n *\n * @param {object} [options]\n * @param {number} [options.compressionLevel=6] - DEFLATE level, 1–9.\n * @returns {Promise<Uint8Array>} The sb3 zip bytes.\n */\n async save({ compressionLevel = 6 } = {}) {\n const zip = new JSZip();\n zip.file('project.json', JSON.stringify(this.json));\n for (const [path, bytes] of this.assets) {\n zip.file(path, bytes);\n }\n return zip.generateAsync({\n type: 'uint8array',\n compression: 'DEFLATE',\n compressionOptions: { level: compressionLevel },\n });\n }\n}\n"],"names":["K","Int32Array","S","rotl","x","c","HEX","i","hexLE","md5","input","msg","Uint8Array","n","withOne","total","buf","bitsLo","bitsHi","a0","b0","c0","d0","M","off","j","A","B","C","D","F","g","Asset","json","project","value","String","bytes","old","hash","Costume","Number","Sound","startsWith","sig","b","ascii","s","sniffFormat","head","SOUP","uid","id","Math","Target","Boolean","name","entry","undefined","data","options","dataFormat","nameOrIndex","kind","list","index","m","removed","Object","found","items","Stage","Sprite","DEFAULT_META","Project","assets","Map","zip","JSZip","projectFile","Error","JSON","reads","path","file","Promise","t","props","maxLayer","nameOrSprite","media","md5ext","compressionLevel"],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Compute the lowercase hex MD5 digest of some bytes.
3
+ *
4
+ * @param {Uint8Array | ArrayBuffer} input - Bytes to hash.
5
+ * @returns {string} 32-character lowercase hex digest.
6
+ */
7
+ export function md5(input: Uint8Array | ArrayBuffer): string;
@@ -0,0 +1,123 @@
1
+ /**
2
+ * A loaded Scratch project (`.sb3`). An sb3 is a zip holding a `project.json`
3
+ * description plus the costume/sound asset files it references. This class is
4
+ * the entry point: load bytes, edit declaratively through {@link Stage} and
5
+ * {@link Sprite}, then save back to bytes.
6
+ *
7
+ * @example
8
+ * import { Project } from 'scratch4js';
9
+ * import { readFile, writeFile } from 'node:fs/promises';
10
+ *
11
+ * const project = await Project.load(await readFile('game.sb3'));
12
+ * const cat = project.sprite('Sprite1');
13
+ * cat.x = 0;
14
+ * cat.size = 150;
15
+ * project.stage.setVariable('score', 0);
16
+ * await writeFile('game.edited.sb3', await project.save());
17
+ */
18
+ export class Project {
19
+ /**
20
+ * Load a project from the raw bytes of an `.sb3` file.
21
+ *
22
+ * @param {Uint8Array | ArrayBuffer | Buffer} data - The sb3 zip bytes.
23
+ * @returns {Promise<Project>}
24
+ */
25
+ static load(data: Uint8Array | ArrayBuffer | Buffer): Promise<Project>;
26
+ /**
27
+ * Create a new, empty project containing just a bare stage.
28
+ *
29
+ * @returns {Project}
30
+ */
31
+ static create(): Project;
32
+ /**
33
+ * Prefer {@link Project.load}. Construct directly only when you already hold a
34
+ * parsed `project.json` and its asset bytes.
35
+ *
36
+ * @param {object} json - Parsed project.json.
37
+ * @param {Map<string, Uint8Array>} [assets] - Asset bytes keyed by `md5ext`.
38
+ */
39
+ constructor(json: object, assets?: Map<string, Uint8Array>);
40
+ /** @type {object} The parsed project.json. */
41
+ json: object;
42
+ /** @type {Map<string, Uint8Array>} Asset bytes keyed by `<md5>.<ext>`. */
43
+ assets: Map<string, Uint8Array>;
44
+ /** @returns {Stage} The project's single stage. */
45
+ get stage(): Stage;
46
+ /** @returns {Sprite[]} All non-stage targets, in array order. */
47
+ get sprites(): Sprite[];
48
+ /** @returns {Array<Stage | Sprite>} Every target, stage included. */
49
+ get targets(): Array<Stage | Sprite>;
50
+ /** @returns {ProjectMeta} The project's `meta` block (semver, vm, agent). */
51
+ get meta(): ProjectMeta;
52
+ /** @returns {object[]} The raw `monitors` (variable/list watchers) array. */
53
+ get monitors(): object[];
54
+ /** @returns {string[]} Ids of enabled extensions (e.g. `pen`, `music`). */
55
+ get extensions(): string[];
56
+ /**
57
+ * Find a sprite by name.
58
+ *
59
+ * @param {string} name
60
+ * @returns {Sprite | undefined}
61
+ */
62
+ sprite(name: string): Sprite | undefined;
63
+ /**
64
+ * Find any target (sprite or stage) by name.
65
+ *
66
+ * @param {string} name
67
+ * @returns {Stage | Sprite | undefined}
68
+ */
69
+ target(name: string): Stage | Sprite | undefined;
70
+ /**
71
+ * Add a new, empty sprite. It starts with no costumes, so add at least one
72
+ * with {@link Sprite#addCostume} before opening the project in the editor.
73
+ *
74
+ * @param {string} name - Sprite name (must be unique among sprites).
75
+ * @param {object} [props] - Initial property overrides (`x`, `y`, `size`, …).
76
+ * @returns {Sprite} The new sprite.
77
+ */
78
+ addSprite(name: string, props?: object): Sprite;
79
+ /**
80
+ * Remove a sprite by name or instance. Assets it solely owned are dropped.
81
+ *
82
+ * @param {string | Sprite} nameOrSprite
83
+ * @returns {boolean} True if a sprite was removed.
84
+ */
85
+ removeSprite(nameOrSprite: string | Sprite): boolean;
86
+ /**
87
+ * Drop an asset's bytes if no costume or sound anywhere still references it.
88
+ * Called automatically when media is removed or its bytes are replaced.
89
+ *
90
+ * @param {string} md5ext
91
+ * @private
92
+ */
93
+ private _maybeDropAsset;
94
+ /**
95
+ * Serialize the project back into `.sb3` bytes.
96
+ *
97
+ * @param {object} [options]
98
+ * @param {number} [options.compressionLevel=6] - DEFLATE level, 1–9.
99
+ * @returns {Promise<Uint8Array>} The sb3 zip bytes.
100
+ */
101
+ save({ compressionLevel }?: {
102
+ compressionLevel?: number | undefined;
103
+ }): Promise<Uint8Array>;
104
+ }
105
+ /**
106
+ * The project's `meta` block.
107
+ */
108
+ export type ProjectMeta = {
109
+ /**
110
+ * - Scratch project schema version (e.g. `3.0.0`).
111
+ */
112
+ semver: string;
113
+ /**
114
+ * - VM version that wrote the project.
115
+ */
116
+ vm: string;
117
+ /**
118
+ * - User-agent string of the editor, if any.
119
+ */
120
+ agent: string;
121
+ };
122
+ import { Stage } from './target.js';
123
+ import { Sprite } from './target.js';
@@ -0,0 +1,217 @@
1
+ /**
2
+ * A target is anything that lives in the project's `targets` array: either the
3
+ * single {@link Stage} or a {@link Sprite}. This base class covers everything
4
+ * the two share — name, costumes, sounds, variables, lists and raw blocks.
5
+ *
6
+ * @abstract
7
+ */
8
+ export class Target {
9
+ /**
10
+ * @param {object} json - The raw target entry from project.json.
11
+ * @param {import('./project.js').Project} project - Owning project.
12
+ */
13
+ constructor(json: object, project: import("./project.js").Project);
14
+ /** @type {object} The underlying JSON; the accessors below keep it in sync. */
15
+ json: object;
16
+ /** @type {import('./project.js').Project} */
17
+ project: import("./project.js").Project;
18
+ /** @returns {boolean} True for the stage, false for sprites. */
19
+ get isStage(): boolean;
20
+ set name(value: string);
21
+ /** @returns {string} The target's name. */
22
+ get name(): string;
23
+ set volume(value: number);
24
+ /** @returns {number} Output volume, 0–100. */
25
+ get volume(): number;
26
+ /** @returns {Costume[]} The target's costumes, in order. */
27
+ get costumes(): Costume[];
28
+ set currentCostume(value: number);
29
+ /** @returns {number} Index of the currently selected costume. */
30
+ get currentCostume(): number;
31
+ /**
32
+ * Find a costume by name.
33
+ *
34
+ * @param {string} name
35
+ * @returns {Costume | undefined}
36
+ */
37
+ getCostume(name: string): Costume | undefined;
38
+ /**
39
+ * Add a costume from raw image bytes. The asset is stored in the zip keyed by
40
+ * its MD5, and a costume entry pointing at it is appended.
41
+ *
42
+ * @param {string} name - Costume name.
43
+ * @param {Uint8Array | ArrayBuffer} data - Image bytes (PNG/SVG/JPG/…).
44
+ * @param {object} [options]
45
+ * @param {string} [options.dataFormat] - Override the detected file type.
46
+ * @param {number} [options.rotationCenterX] - Anchor X (defaults to 0).
47
+ * @param {number} [options.rotationCenterY] - Anchor Y (defaults to 0).
48
+ * @param {number} [options.bitmapResolution] - 1 for SVG, 2 for HD bitmaps.
49
+ * @returns {Costume} The newly added costume.
50
+ */
51
+ addCostume(name: string, data: Uint8Array | ArrayBuffer, options?: {
52
+ dataFormat?: string | undefined;
53
+ rotationCenterX?: number | undefined;
54
+ rotationCenterY?: number | undefined;
55
+ bitmapResolution?: number | undefined;
56
+ }): Costume;
57
+ /**
58
+ * Remove a costume by name or index.
59
+ *
60
+ * @param {string | number} nameOrIndex
61
+ * @returns {boolean} True if a costume was removed.
62
+ */
63
+ removeCostume(nameOrIndex: string | number): boolean;
64
+ /** @returns {Sound[]} The target's sounds, in order. */
65
+ get sounds(): Sound[];
66
+ /**
67
+ * Find a sound by name.
68
+ *
69
+ * @param {string} name
70
+ * @returns {Sound | undefined}
71
+ */
72
+ getSound(name: string): Sound | undefined;
73
+ /**
74
+ * Add a sound from raw audio bytes.
75
+ *
76
+ * @param {string} name - Sound name.
77
+ * @param {Uint8Array | ArrayBuffer} data - Audio bytes (WAV/MP3).
78
+ * @param {object} [options]
79
+ * @param {string} [options.dataFormat] - Override the detected file type.
80
+ * @param {number} [options.rate] - Sample rate in Hz (default 48000).
81
+ * @param {number} [options.sampleCount] - Number of samples (default 0).
82
+ * @returns {Sound} The newly added sound.
83
+ */
84
+ addSound(name: string, data: Uint8Array | ArrayBuffer, options?: {
85
+ dataFormat?: string | undefined;
86
+ rate?: number | undefined;
87
+ sampleCount?: number | undefined;
88
+ }): Sound;
89
+ /**
90
+ * Remove a sound by name or index.
91
+ *
92
+ * @param {string | number} nameOrIndex
93
+ * @returns {boolean} True if a sound was removed.
94
+ */
95
+ removeSound(nameOrIndex: string | number): boolean;
96
+ /**
97
+ * @param {'costumes' | 'sounds'} kind
98
+ * @param {string | number} nameOrIndex
99
+ * @returns {boolean}
100
+ * @private
101
+ */
102
+ private _removeMedia;
103
+ /** @returns {string[]} Names of this target's variables. */
104
+ get variableNames(): string[];
105
+ /**
106
+ * Read a variable's value by name.
107
+ *
108
+ * @param {string} name
109
+ * @returns {string | number | boolean | undefined}
110
+ */
111
+ getVariable(name: string): string | number | boolean | undefined;
112
+ /**
113
+ * Create or update a variable by name.
114
+ *
115
+ * @param {string} name
116
+ * @param {string | number | boolean} value
117
+ * @returns {string} The variable's id.
118
+ */
119
+ setVariable(name: string, value: string | number | boolean): string;
120
+ /**
121
+ * Delete a variable by name.
122
+ *
123
+ * @param {string} name
124
+ * @returns {boolean} True if a variable was deleted.
125
+ */
126
+ deleteVariable(name: string): boolean;
127
+ /** @returns {string[]} Names of this target's lists. */
128
+ get listNames(): string[];
129
+ /**
130
+ * Read a list's contents by name.
131
+ *
132
+ * @param {string} name
133
+ * @returns {Array<string | number> | undefined}
134
+ */
135
+ getList(name: string): Array<string | number> | undefined;
136
+ /**
137
+ * Create or replace a list by name.
138
+ *
139
+ * @param {string} name
140
+ * @param {Array<string | number>} items
141
+ * @returns {string} The list's id.
142
+ */
143
+ setList(name: string, items?: Array<string | number>): string;
144
+ /**
145
+ * Delete a list by name.
146
+ *
147
+ * @param {string} name
148
+ * @returns {boolean} True if a list was deleted.
149
+ */
150
+ deleteList(name: string): boolean;
151
+ /**
152
+ * The raw `blocks` object for advanced scripting edits. Keys are block ids;
153
+ * values are the block definitions. Mutate directly for low-level changes.
154
+ *
155
+ * @returns {object}
156
+ */
157
+ get blocks(): object;
158
+ }
159
+ /**
160
+ * The stage: the single backdrop-bearing target that also owns the project's
161
+ * broadcasts.
162
+ */
163
+ export class Stage extends Target {
164
+ /** @returns {true} Always the stage. Discriminates {@link Stage} from {@link Sprite}. */
165
+ get isStage(): true;
166
+ set tempo(value: number);
167
+ /** @returns {number} Tempo for music blocks, in BPM. */
168
+ get tempo(): number;
169
+ set videoState(value: string);
170
+ /** @returns {string} Video input state: `on`, `off`, or `on-flipped`. */
171
+ get videoState(): string;
172
+ set videoTransparency(value: number);
173
+ /** @returns {number} Video transparency, 0–100. */
174
+ get videoTransparency(): number;
175
+ /** @returns {string[]} Names of all broadcast messages in the project. */
176
+ get broadcastNames(): string[];
177
+ /**
178
+ * Add a broadcast message if it does not already exist.
179
+ *
180
+ * @param {string} name
181
+ * @returns {string} The broadcast's id.
182
+ */
183
+ addBroadcast(name: string): string;
184
+ }
185
+ /**
186
+ * A sprite: a movable target with a position, size and orientation.
187
+ */
188
+ export class Sprite extends Target {
189
+ /** @returns {false} Never the stage. Discriminates {@link Sprite} from {@link Stage}. */
190
+ get isStage(): false;
191
+ set x(value: number);
192
+ /** @returns {number} X position on the stage (−240…240). */
193
+ get x(): number;
194
+ set y(value: number);
195
+ /** @returns {number} Y position on the stage (−180…180). */
196
+ get y(): number;
197
+ set size(value: number);
198
+ /** @returns {number} Size as a percentage (100 = original). */
199
+ get size(): number;
200
+ set direction(value: number);
201
+ /** @returns {number} Direction in degrees (90 = pointing right). */
202
+ get direction(): number;
203
+ set visible(value: boolean);
204
+ /** @returns {boolean} Whether the sprite is shown. */
205
+ get visible(): boolean;
206
+ set draggable(value: boolean);
207
+ /** @returns {boolean} Whether the sprite can be dragged in the player. */
208
+ get draggable(): boolean;
209
+ set rotationStyle(value: string);
210
+ /** @returns {string} Rotation style: `all around`, `left-right`, `don't rotate`. */
211
+ get rotationStyle(): string;
212
+ set layerOrder(value: number);
213
+ /** @returns {number} Stacking order; higher draws on top. */
214
+ get layerOrder(): number;
215
+ }
216
+ import { Costume } from './assets.js';
217
+ import { Sound } from './assets.js';