pxt-core 13.2.9 → 13.2.10

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/built/pxt.js CHANGED
@@ -108589,6 +108589,58 @@ var pxt;
108589
108589
  pxt.log(`github proxy error: ${e.message}`);
108590
108590
  pxt.debug(e);
108591
108591
  }
108592
+ // lowercase "owner/repo" of the repositories the local server resolves from disk
108593
+ let localRepos = {};
108594
+ /**
108595
+ * Asks the local development server (pxt serve) which GitHub repositories it
108596
+ * resolves from local directories. Requests for those repositories then go to
108597
+ * the local server, which serves the repository's working tree for any ref,
108598
+ * and are never cached. Does nothing unless the editor runs on localhost; a
108599
+ * server that refuses the request (no local token, or not pxt serve) leaves
108600
+ * no repositories resolved locally.
108601
+ */
108602
+ async function initLocalReposAsync() {
108603
+ localRepos = {};
108604
+ if (pxt.U.isNodeJS || !pxt.BrowserUtils.isLocalHost())
108605
+ return;
108606
+ try {
108607
+ const resp = await pxt.Cloud.localRequestAsync("localrepos");
108608
+ if (resp.statusCode != 200)
108609
+ return;
108610
+ const repos = resp.json.repos || [];
108611
+ repos.forEach(slug => localRepos[slug.toLowerCase()] = true);
108612
+ if (repos.length)
108613
+ pxt.log(`github: serving ${repos.join(", ")} from the local server`);
108614
+ }
108615
+ catch (e) {
108616
+ pxt.log(`github: cannot list local repositories: ${e.message}`);
108617
+ }
108618
+ }
108619
+ github.initLocalReposAsync = initLocalReposAsync;
108620
+ /**
108621
+ * Indicates if the repository of a GitHub id is resolved by the local
108622
+ * development server. See initLocalReposAsync.
108623
+ */
108624
+ function isLocalRepo(repo) {
108625
+ const parsed = typeof repo == "string" ? parseRepoId(repo) : repo;
108626
+ return !!parsed && !!localRepos[parsed.slug.toLowerCase()];
108627
+ }
108628
+ github.isLocalRepo = isLocalRepo;
108629
+ function localRepoJsonAsync(path) {
108630
+ return pxt.Cloud.localRequestAsync("gh/" + path)
108631
+ .then(resp => {
108632
+ if (resp.statusCode != 200) {
108633
+ const err = new Error(resp.text || lf("Cannot load {0} from the local server", path));
108634
+ err.statusCode = resp.statusCode;
108635
+ throw err;
108636
+ }
108637
+ return resp.json;
108638
+ });
108639
+ }
108640
+ function localPackageAsync(repo) {
108641
+ return localRepoJsonAsync(join(repo.slug, repo.fileName, "text"))
108642
+ .then(files => ({ files }));
108643
+ }
108592
108644
  function isOrgAsync(owner) {
108593
108645
  return ghRequestAsync({ url: `https://api.github.com/orgs/${owner}`, method: "GET", allowHttpErrors: true })
108594
108646
  .then(resp => resp.statusCode == 200);
@@ -109057,17 +109109,20 @@ var pxt;
109057
109109
  const parsed = parseRepoId(repopath);
109058
109110
  const proxy = shouldUseProxy(useProxy);
109059
109111
  let head = null;
109060
- const fetch = !proxy ?
109061
- ghGetJsonAsync(`https://api.github.com/repos/${parsed.slug}/git/refs/${namespace}/?per_page=100`) :
109062
- // no CDN caching here, bust browser cace
109063
- pxt.U.httpGetJsonAsync(pxt.BrowserUtils.cacheBustingUrl(`${pxt.Cloud.apiRoot}gh/${parsed.slug}/refs${noCache ? "?nocache=1" : ""}`))
109064
- .then(r => {
109065
- let res = Object.keys(r.refs)
109066
- .filter(k => pxt.U.startsWith(k, "refs/" + namespace + "/"))
109067
- .map(k => ({ ref: k, object: { sha: r.refs[k] } }));
109068
- head = r.refs["HEAD"];
109069
- return res;
109070
- });
109112
+ const fetch = isLocalRepo(parsed) ?
109113
+ localRepoJsonAsync(`${parsed.slug}/refs`)
109114
+ .then(r => Object.keys(r.refs).map(k => ({ ref: k, object: { sha: r.refs[k] } }))) :
109115
+ !proxy ?
109116
+ ghGetJsonAsync(`https://api.github.com/repos/${parsed.slug}/git/refs/${namespace}/?per_page=100`) :
109117
+ // no CDN caching here, bust browser cace
109118
+ pxt.U.httpGetJsonAsync(pxt.BrowserUtils.cacheBustingUrl(`${pxt.Cloud.apiRoot}gh/${parsed.slug}/refs${noCache ? "?nocache=1" : ""}`))
109119
+ .then(r => {
109120
+ let res = Object.keys(r.refs)
109121
+ .filter(k => pxt.U.startsWith(k, "refs/" + namespace + "/"))
109122
+ .map(k => ({ ref: k, object: { sha: r.refs[k] } }));
109123
+ head = r.refs["HEAD"];
109124
+ return res;
109125
+ });
109071
109126
  let clean = (x) => x.replace(/^refs\/[^\/]+\//, "");
109072
109127
  return fetch.then((resp) => {
109073
109128
  resp.sort((a, b) => pxt.semver.strcmp(clean(a.ref), clean(b.ref)));
@@ -109104,6 +109159,11 @@ var pxt;
109104
109159
  .then(resolveRefAsync));
109105
109160
  }
109106
109161
  async function pkgConfigAsync(repopath, tag, config) {
109162
+ const parsed = parseRepoId(repopath);
109163
+ if (isLocalRepo(parsed)) {
109164
+ const pkg = await localPackageAsync(parsed);
109165
+ return pxt.Package.parseAndValidConfig(pkg.files[pxt.CONFIG_NAME]);
109166
+ }
109107
109167
  if (!tag)
109108
109168
  tag = await github.db.latestVersionAsync(repopath, config);
109109
109169
  return await github.db.loadConfigAsync(repopath, tag);
@@ -109121,7 +109181,7 @@ var pxt;
109121
109181
  return undefined;
109122
109182
  }
109123
109183
  // always try to upgrade unbound versions
109124
- if (!p.tag) {
109184
+ if (!p.tag && !isLocalRepo(p)) {
109125
109185
  try {
109126
109186
  p.tag = await github.db.latestVersionAsync(p.slug, config);
109127
109187
  }
@@ -109132,7 +109192,9 @@ var pxt;
109132
109192
  throw e;
109133
109193
  }
109134
109194
  }
109135
- const cached = await github.db.loadPackageAsync(p.fullName, p.tag, fallbackPackageFiles);
109195
+ const cached = isLocalRepo(p)
109196
+ ? await localPackageAsync(p)
109197
+ : await github.db.loadPackageAsync(p.fullName, p.tag, fallbackPackageFiles);
109136
109198
  const dv = upgradedDisablesVariants(config, repoWithTag);
109137
109199
  if (dv) {
109138
109200
  const cfg = pxt.Package.parseAndValidConfig(cached.files[pxt.CONFIG_NAME]);
@@ -109147,6 +109209,11 @@ var pxt;
109147
109209
  github.downloadPackageAsync = downloadPackageAsync;
109148
109210
  async function downloadLatestPackageAsync(repo, useProxy, noCache) {
109149
109211
  const packageConfig = await pxt.packagesConfigAsync();
109212
+ if (isLocalRepo(repo)) {
109213
+ // a locally resolved repository has no versions: depend on it without a ref
109214
+ const config = await pkgConfigAsync(repo.fullName, undefined, packageConfig);
109215
+ return { version: toGithubDependencyPath(repo.fullName), config };
109216
+ }
109150
109217
  const tag = await pxt.github.latestVersionAsync(repo.slug, packageConfig, useProxy, noCache);
109151
109218
  // download package into cache
109152
109219
  const repoWithTag = `${repo.fullName}#${tag}`;
@@ -109391,7 +109458,9 @@ var pxt;
109391
109458
  }
109392
109459
  function isRepoApproved(repo, config) {
109393
109460
  var _a, _b;
109394
- if (isOrgApproved(repo, config))
109461
+ // aliased repositories exist only in an editor on localhost that listed them from its own pxt serve,
109462
+ // so approving them widens nothing a public editor approves
109463
+ if (isOrgApproved(repo, config) || isLocalRepo(repo))
109395
109464
  return true;
109396
109465
  const repoFull = (_a = repo === null || repo === void 0 ? void 0 : repo.fullName) === null || _a === void 0 ? void 0 : _a.toLowerCase();
109397
109466
  const repoSlug = (_b = repo === null || repo === void 0 ? void 0 : repo.slug) === null || _b === void 0 ? void 0 : _b.toLowerCase();
@@ -109409,6 +109478,8 @@ var pxt;
109409
109478
  const status = repoStatus(rid, config);
109410
109479
  if (status == GitRepoStatus.Banned)
109411
109480
  return undefined;
109481
+ if (isLocalRepo(rid))
109482
+ return repoFromProxyMeta(rid, status, await localRepoJsonAsync(rid.slug));
109412
109483
  // always try proxy first
109413
109484
  if (hasProxy()) {
109414
109485
  try {
@@ -109423,26 +109494,28 @@ var pxt;
109423
109494
  return mkRepo(r, { config, fullName: rid.fullName, fileName: rid.fileName, tag: rid.tag });
109424
109495
  }
109425
109496
  github.repoAsync = repoAsync;
109497
+ function repoFromProxyMeta(rid, status, meta) {
109498
+ if (!meta)
109499
+ return undefined;
109500
+ return {
109501
+ github: true,
109502
+ owner: rid.owner,
109503
+ fullName: rid.fullName,
109504
+ fileName: rid.fileName,
109505
+ slug: rid.slug,
109506
+ name: rid.fileName ? `${meta.name}-${rid.fileName}` : meta.name,
109507
+ displayName: meta.displayName,
109508
+ description: meta.description,
109509
+ defaultBranch: meta.defaultBranch || "master",
109510
+ tag: rid.tag,
109511
+ status
109512
+ };
109513
+ }
109426
109514
  function proxyRepoAsync(rid, status) {
109427
109515
  // always use proxy
109428
109516
  return ghProxyWithCdnJsonAsync(rid.slug)
109429
- .then(meta => {
109430
- if (!meta)
109431
- return undefined;
109432
- return {
109433
- github: true,
109434
- owner: rid.owner,
109435
- fullName: rid.fullName,
109436
- fileName: rid.fileName,
109437
- slug: rid.slug,
109438
- name: rid.fileName ? `${meta.name}-${rid.fileName}` : meta.name,
109439
- displayName: meta.displayName,
109440
- description: meta.description,
109441
- defaultBranch: meta.defaultBranch || "master",
109442
- tag: rid.tag,
109443
- status
109444
- };
109445
- }).catch(err => {
109517
+ .then(meta => repoFromProxyMeta(rid, status, meta))
109518
+ .catch(err => {
109446
109519
  pxt.reportException(err);
109447
109520
  return undefined;
109448
109521
  });
@@ -109624,6 +109697,9 @@ var pxt;
109624
109697
  const parsed = parseRepoId(repopath);
109625
109698
  if (!parsed)
109626
109699
  return Promise.resolve(null);
109700
+ // a locally resolved repository serves its working tree and has no versions
109701
+ if (isLocalRepo(parsed))
109702
+ return Promise.resolve(undefined);
109627
109703
  return repoAsync(parsed.slug, config)
109628
109704
  .then(scr => {
109629
109705
  if (!scr)
@@ -158696,7 +158772,7 @@ var pxsim;
158696
158772
  this.setState(SimulatorState.Pending);
158697
158773
  }
158698
158774
  focus() {
158699
- const frame = this.simFrames()[0];
158775
+ const frame = this.mainFrame();
158700
158776
  if (frame)
158701
158777
  frame.focus();
158702
158778
  }
@@ -158743,7 +158819,7 @@ var pxsim;
158743
158819
  this.themes = themes;
158744
158820
  }
158745
158821
  startRecording(width) {
158746
- const frame = this.simFrames()[0];
158822
+ const frame = this.mainFrame();
158747
158823
  if (!frame)
158748
158824
  return undefined;
158749
158825
  this.postMessage({
@@ -158751,7 +158827,7 @@ var pxsim;
158751
158827
  action: 'start',
158752
158828
  source: MESSAGE_SOURCE,
158753
158829
  width
158754
- });
158830
+ }, undefined, frame.id);
158755
158831
  }
158756
158832
  stopRecording() {
158757
158833
  this.postMessage({ type: 'recorder', source: MESSAGE_SOURCE, action: 'stop' });
@@ -158825,6 +158901,14 @@ var pxsim;
158825
158901
  frames.unshift(loanedFrame);
158826
158902
  return frames;
158827
158903
  }
158904
+ /**
158905
+ * Returns the main simulator frame: the first frame not running a
158906
+ * simulator extension, or undefined when there is none.
158907
+ * @param skipLoaned excludes the loaned simulator's frame, as `simFrames` does
158908
+ */
158909
+ mainFrame(skipLoaned = false) {
158910
+ return this.simFrames(skipLoaned).find(frame => !frame.dataset[FRAME_DATA_MESSAGE_CHANNEL]);
158911
+ }
158828
158912
  getSimUrl() {
158829
158913
  var _a, _b;
158830
158914
  const simUrl = this.options.simUrl || ((_a = window.pxtConfig) === null || _a === void 0 ? void 0 : _a.simUrl) || ((_b = pxt.webConfig) === null || _b === void 0 ? void 0 : _b.simUrl) || `${location.origin}/sim/simulator.html`;
@@ -158857,8 +158941,8 @@ var pxsim;
158857
158941
  // include index of the source iframe
158858
158942
  broadcastmsg.srcFrameIndex = this.simFrames().findIndex((item) => item.contentWindow === source);
158859
158943
  const sourceFrame = broadcastmsg.srcFrameIndex >= 0 ? this.simFrames()[broadcastmsg.srcFrameIndex] : undefined;
158860
- // jacdac messages from a board sim other than first should be dropped
158861
- if (broadcastmsg.srcFrameIndex > 0 && mkcdFrames.find(f => f === sourceFrame) && messageChannel === "jacdac")
158944
+ // jacdac messages from a board sim other than the main frame should be dropped
158945
+ if (sourceFrame !== this.mainFrame() && mkcdFrames.find(f => f === sourceFrame) && messageChannel === "jacdac")
158862
158946
  return;
158863
158947
  // if the editor is hosted in a multi-editor setting
158864
158948
  // don't start extra frames
@@ -158967,13 +159051,14 @@ var pxsim;
158967
159051
  }
158968
159052
  // now that we have iframe starts,
158969
159053
  // dispatch message to other frames
159054
+ const mainFrame = this.mainFrame();
158970
159055
  for (let i = 0; i < frames.length; ++i) {
158971
159056
  const frame = frames[i];
158972
159057
  // same frame as source
158973
159058
  if (source && frame.contentWindow == source)
158974
159059
  continue;
158975
- // if jacdac message, don't send to other (board) simulator frames
158976
- if (i > 0 && !frame.dataset[FRAME_DATA_MESSAGE_CHANNEL] &&
159060
+ // if jacdac message, don't send to (board) simulator frames other than the main frame
159061
+ if (frame !== mainFrame && !frame.dataset[FRAME_DATA_MESSAGE_CHANNEL] &&
158977
159062
  msg.type === "messagepacket" && msg.channel === "jacdac")
158978
159063
  continue;
158979
159064
  // frame not in DOM
@@ -159078,9 +159163,10 @@ var pxsim;
159078
159163
  this._currentRuntime = undefined;
159079
159164
  this.container.textContent = "";
159080
159165
  }
159081
- if (!this.simFrames().length) {
159082
- this.container.appendChild(this.createFrame());
159083
- this.applyAspectRatio(aspectRatio);
159166
+ if (!this.mainFrame()) {
159167
+ const wrapper = this.createFrame();
159168
+ this.container.insertBefore(wrapper, this.container.firstElementChild);
159169
+ this.applyAspectRatioToFrame(wrapper.firstElementChild, aspectRatio);
159084
159170
  this.setStarting();
159085
159171
  }
159086
159172
  }
@@ -159122,10 +159208,11 @@ var pxsim;
159122
159208
  // returns a simulator iframe that can be hosted anywhere in the page
159123
159209
  // while a loaned simulator is active, all other iframes are suspended
159124
159210
  loanSimulator() {
159211
+ var _a;
159125
159212
  if (this.loanedSimulator)
159126
159213
  return this.loanedSimulator;
159127
159214
  // reuse first simulator or create new one
159128
- this.loanedSimulator = this.container.firstElementChild || this.createFrame();
159215
+ this.loanedSimulator = ((_a = this.mainFrame(true)) === null || _a === void 0 ? void 0 : _a.parentElement) || this.createFrame();
159129
159216
  if (this.loanedSimulator.parentNode)
159130
159217
  this.container.removeChild(this.loanedSimulator);
159131
159218
  return this.loanedSimulator;
@@ -159190,8 +159277,8 @@ var pxsim;
159190
159277
  }
159191
159278
  cleanupFrames() {
159192
159279
  // drop unused extras frames after 5 seconds
159193
- const frames = this.simFrames(true);
159194
- frames.shift(); // drop first frame
159280
+ const mainFrame = this.mainFrame(true);
159281
+ const frames = this.simFrames(true).filter(frame => frame !== mainFrame);
159195
159282
  frames.filter(frame => !frame.dataset[PERMANENT])
159196
159283
  .forEach(frame => {
159197
159284
  if (this.state == SimulatorState.Stopped
@@ -159209,7 +159296,10 @@ var pxsim;
159209
159296
  return;
159210
159297
  const frames = this.simFrames();
159211
159298
  frames.forEach(frame => {
159212
- this.options.removeElement(frame.parentElement, completeHandler);
159299
+ if (frame.dataset[PERMANENT] && this.options.hideElement)
159300
+ this.options.hideElement(frame.parentElement, completeHandler);
159301
+ else
159302
+ this.options.removeElement(frame.parentElement, completeHandler);
159213
159303
  });
159214
159304
  // Execute the complete handler if there are no frames in sim view
159215
159305
  if (frames.length == 0 && completeHandler) {
@@ -159277,10 +159367,10 @@ var pxsim;
159277
159367
  this.singleSimulator = false;
159278
159368
  this.breakpointsSet = false;
159279
159369
  // first frame
159280
- let frame = this.simFrames()[0];
159370
+ let frame = this.mainFrame();
159281
159371
  if (!frame) {
159282
159372
  let wrapper = this.createFrame();
159283
- this.container.appendChild(wrapper);
159373
+ this.container.insertBefore(wrapper, this.container.firstElementChild);
159284
159374
  frame = wrapper.firstElementChild;
159285
159375
  }
159286
159376
  this.startFrame(frame);
@@ -164910,6 +165000,7 @@ const chalk = require('chalk');
164910
165000
  var U = pxt.Util;
164911
165001
  var Cloud = pxt.Cloud;
164912
165002
  const server = require("./server");
165003
+ const localrepos = require("./localrepos");
164913
165004
  const build = require("./buildengine");
164914
165005
  const commandParser = require("./commandparser");
164915
165006
  const hid = require("./hid");
@@ -167641,6 +167732,9 @@ function serveAsync(parsed) {
167641
167732
  saveConfig();
167642
167733
  }
167643
167734
  let localToken = exports.globalConfig.localToken;
167735
+ // --local-repo directories resolve against the directory serve was started from,
167736
+ // which differs from the target root when serve moves into a target found below
167737
+ const invocationDir = process.cwd();
167644
167738
  if (!fs.existsSync("pxtarget.json")) {
167645
167739
  //Specifically when the target is being used as a library
167646
167740
  let targetDepLoc = nodeutil.targetDir;
@@ -167659,6 +167753,8 @@ function serveAsync(parsed) {
167659
167753
  }
167660
167754
  }
167661
167755
  }
167756
+ const targetDir = process.cwd();
167757
+ const localRepos = localrepos.loadLocalRepos(targetDir, parsed.flags["localRepo"] || [], invocationDir);
167662
167758
  return (justServe ? Promise.resolve() : buildAndWatchTargetAsync(includeSourceMaps, rebundle))
167663
167759
  .then(() => server.serveAsync({
167664
167760
  autoStart: !exports.globalConfig.noAutoStart,
@@ -167672,6 +167768,7 @@ function serveAsync(parsed) {
167672
167768
  noauth: parsed.flags["noauth"] || false,
167673
167769
  backport: parsed.flags["backport"] || 0,
167674
167770
  https: parsed.flags["https"] || false,
167771
+ localRepos,
167675
167772
  }));
167676
167773
  }
167677
167774
  exports.serveAsync = serveAsync;
@@ -171655,7 +171752,13 @@ ${pxt.crowdin.KEY_VARIABLE} - crowdin key
171655
171752
  argument: "backport",
171656
171753
  type: "number",
171657
171754
  },
171658
- https: { description: "use https protocol instead of http" }
171755
+ https: { description: "use https protocol instead of http" },
171756
+ localRepo: {
171757
+ description: "serve a GitHub repository from a local directory, as owner/repo=dir; repeatable, and applied over the entries of pxtlocal.json",
171758
+ aliases: ["local-repo"],
171759
+ argument: "repo",
171760
+ repeatable: true
171761
+ }
171659
171762
  }
171660
171763
  }, serveAsync);
171661
171764
  p.defineCommand({
package/built/pxtlib.d.ts CHANGED
@@ -1496,6 +1496,20 @@ declare namespace pxt.github {
1496
1496
  loadTutorialMarkdown(repopath: string, tag?: string): Promise<CachedPackage>;
1497
1497
  cacheReposAsync(response: GHTutorialResponse): Promise<void>;
1498
1498
  }
1499
+ /**
1500
+ * Asks the local development server (pxt serve) which GitHub repositories it
1501
+ * resolves from local directories. Requests for those repositories then go to
1502
+ * the local server, which serves the repository's working tree for any ref,
1503
+ * and are never cached. Does nothing unless the editor runs on localhost; a
1504
+ * server that refuses the request (no local token, or not pxt serve) leaves
1505
+ * no repositories resolved locally.
1506
+ */
1507
+ function initLocalReposAsync(): Promise<void>;
1508
+ /**
1509
+ * Indicates if the repository of a GitHub id is resolved by the local
1510
+ * development server. See initLocalReposAsync.
1511
+ */
1512
+ function isLocalRepo(repo: string | ParsedRepo): boolean;
1499
1513
  function isOrgAsync(owner: string): Promise<boolean>;
1500
1514
  class MemoryGithubDb implements IGithubDb {
1501
1515
  private latestVersions;
package/built/pxtlib.js CHANGED
@@ -10868,6 +10868,58 @@ var pxt;
10868
10868
  pxt.log(`github proxy error: ${e.message}`);
10869
10869
  pxt.debug(e);
10870
10870
  }
10871
+ // lowercase "owner/repo" of the repositories the local server resolves from disk
10872
+ let localRepos = {};
10873
+ /**
10874
+ * Asks the local development server (pxt serve) which GitHub repositories it
10875
+ * resolves from local directories. Requests for those repositories then go to
10876
+ * the local server, which serves the repository's working tree for any ref,
10877
+ * and are never cached. Does nothing unless the editor runs on localhost; a
10878
+ * server that refuses the request (no local token, or not pxt serve) leaves
10879
+ * no repositories resolved locally.
10880
+ */
10881
+ async function initLocalReposAsync() {
10882
+ localRepos = {};
10883
+ if (pxt.U.isNodeJS || !pxt.BrowserUtils.isLocalHost())
10884
+ return;
10885
+ try {
10886
+ const resp = await pxt.Cloud.localRequestAsync("localrepos");
10887
+ if (resp.statusCode != 200)
10888
+ return;
10889
+ const repos = resp.json.repos || [];
10890
+ repos.forEach(slug => localRepos[slug.toLowerCase()] = true);
10891
+ if (repos.length)
10892
+ pxt.log(`github: serving ${repos.join(", ")} from the local server`);
10893
+ }
10894
+ catch (e) {
10895
+ pxt.log(`github: cannot list local repositories: ${e.message}`);
10896
+ }
10897
+ }
10898
+ github.initLocalReposAsync = initLocalReposAsync;
10899
+ /**
10900
+ * Indicates if the repository of a GitHub id is resolved by the local
10901
+ * development server. See initLocalReposAsync.
10902
+ */
10903
+ function isLocalRepo(repo) {
10904
+ const parsed = typeof repo == "string" ? parseRepoId(repo) : repo;
10905
+ return !!parsed && !!localRepos[parsed.slug.toLowerCase()];
10906
+ }
10907
+ github.isLocalRepo = isLocalRepo;
10908
+ function localRepoJsonAsync(path) {
10909
+ return pxt.Cloud.localRequestAsync("gh/" + path)
10910
+ .then(resp => {
10911
+ if (resp.statusCode != 200) {
10912
+ const err = new Error(resp.text || lf("Cannot load {0} from the local server", path));
10913
+ err.statusCode = resp.statusCode;
10914
+ throw err;
10915
+ }
10916
+ return resp.json;
10917
+ });
10918
+ }
10919
+ function localPackageAsync(repo) {
10920
+ return localRepoJsonAsync(join(repo.slug, repo.fileName, "text"))
10921
+ .then(files => ({ files }));
10922
+ }
10871
10923
  function isOrgAsync(owner) {
10872
10924
  return ghRequestAsync({ url: `https://api.github.com/orgs/${owner}`, method: "GET", allowHttpErrors: true })
10873
10925
  .then(resp => resp.statusCode == 200);
@@ -11336,17 +11388,20 @@ var pxt;
11336
11388
  const parsed = parseRepoId(repopath);
11337
11389
  const proxy = shouldUseProxy(useProxy);
11338
11390
  let head = null;
11339
- const fetch = !proxy ?
11340
- ghGetJsonAsync(`https://api.github.com/repos/${parsed.slug}/git/refs/${namespace}/?per_page=100`) :
11341
- // no CDN caching here, bust browser cace
11342
- pxt.U.httpGetJsonAsync(pxt.BrowserUtils.cacheBustingUrl(`${pxt.Cloud.apiRoot}gh/${parsed.slug}/refs${noCache ? "?nocache=1" : ""}`))
11343
- .then(r => {
11344
- let res = Object.keys(r.refs)
11345
- .filter(k => pxt.U.startsWith(k, "refs/" + namespace + "/"))
11346
- .map(k => ({ ref: k, object: { sha: r.refs[k] } }));
11347
- head = r.refs["HEAD"];
11348
- return res;
11349
- });
11391
+ const fetch = isLocalRepo(parsed) ?
11392
+ localRepoJsonAsync(`${parsed.slug}/refs`)
11393
+ .then(r => Object.keys(r.refs).map(k => ({ ref: k, object: { sha: r.refs[k] } }))) :
11394
+ !proxy ?
11395
+ ghGetJsonAsync(`https://api.github.com/repos/${parsed.slug}/git/refs/${namespace}/?per_page=100`) :
11396
+ // no CDN caching here, bust browser cace
11397
+ pxt.U.httpGetJsonAsync(pxt.BrowserUtils.cacheBustingUrl(`${pxt.Cloud.apiRoot}gh/${parsed.slug}/refs${noCache ? "?nocache=1" : ""}`))
11398
+ .then(r => {
11399
+ let res = Object.keys(r.refs)
11400
+ .filter(k => pxt.U.startsWith(k, "refs/" + namespace + "/"))
11401
+ .map(k => ({ ref: k, object: { sha: r.refs[k] } }));
11402
+ head = r.refs["HEAD"];
11403
+ return res;
11404
+ });
11350
11405
  let clean = (x) => x.replace(/^refs\/[^\/]+\//, "");
11351
11406
  return fetch.then((resp) => {
11352
11407
  resp.sort((a, b) => pxt.semver.strcmp(clean(a.ref), clean(b.ref)));
@@ -11383,6 +11438,11 @@ var pxt;
11383
11438
  .then(resolveRefAsync));
11384
11439
  }
11385
11440
  async function pkgConfigAsync(repopath, tag, config) {
11441
+ const parsed = parseRepoId(repopath);
11442
+ if (isLocalRepo(parsed)) {
11443
+ const pkg = await localPackageAsync(parsed);
11444
+ return pxt.Package.parseAndValidConfig(pkg.files[pxt.CONFIG_NAME]);
11445
+ }
11386
11446
  if (!tag)
11387
11447
  tag = await github.db.latestVersionAsync(repopath, config);
11388
11448
  return await github.db.loadConfigAsync(repopath, tag);
@@ -11400,7 +11460,7 @@ var pxt;
11400
11460
  return undefined;
11401
11461
  }
11402
11462
  // always try to upgrade unbound versions
11403
- if (!p.tag) {
11463
+ if (!p.tag && !isLocalRepo(p)) {
11404
11464
  try {
11405
11465
  p.tag = await github.db.latestVersionAsync(p.slug, config);
11406
11466
  }
@@ -11411,7 +11471,9 @@ var pxt;
11411
11471
  throw e;
11412
11472
  }
11413
11473
  }
11414
- const cached = await github.db.loadPackageAsync(p.fullName, p.tag, fallbackPackageFiles);
11474
+ const cached = isLocalRepo(p)
11475
+ ? await localPackageAsync(p)
11476
+ : await github.db.loadPackageAsync(p.fullName, p.tag, fallbackPackageFiles);
11415
11477
  const dv = upgradedDisablesVariants(config, repoWithTag);
11416
11478
  if (dv) {
11417
11479
  const cfg = pxt.Package.parseAndValidConfig(cached.files[pxt.CONFIG_NAME]);
@@ -11426,6 +11488,11 @@ var pxt;
11426
11488
  github.downloadPackageAsync = downloadPackageAsync;
11427
11489
  async function downloadLatestPackageAsync(repo, useProxy, noCache) {
11428
11490
  const packageConfig = await pxt.packagesConfigAsync();
11491
+ if (isLocalRepo(repo)) {
11492
+ // a locally resolved repository has no versions: depend on it without a ref
11493
+ const config = await pkgConfigAsync(repo.fullName, undefined, packageConfig);
11494
+ return { version: toGithubDependencyPath(repo.fullName), config };
11495
+ }
11429
11496
  const tag = await pxt.github.latestVersionAsync(repo.slug, packageConfig, useProxy, noCache);
11430
11497
  // download package into cache
11431
11498
  const repoWithTag = `${repo.fullName}#${tag}`;
@@ -11670,7 +11737,9 @@ var pxt;
11670
11737
  }
11671
11738
  function isRepoApproved(repo, config) {
11672
11739
  var _a, _b;
11673
- if (isOrgApproved(repo, config))
11740
+ // aliased repositories exist only in an editor on localhost that listed them from its own pxt serve,
11741
+ // so approving them widens nothing a public editor approves
11742
+ if (isOrgApproved(repo, config) || isLocalRepo(repo))
11674
11743
  return true;
11675
11744
  const repoFull = (_a = repo === null || repo === void 0 ? void 0 : repo.fullName) === null || _a === void 0 ? void 0 : _a.toLowerCase();
11676
11745
  const repoSlug = (_b = repo === null || repo === void 0 ? void 0 : repo.slug) === null || _b === void 0 ? void 0 : _b.toLowerCase();
@@ -11688,6 +11757,8 @@ var pxt;
11688
11757
  const status = repoStatus(rid, config);
11689
11758
  if (status == GitRepoStatus.Banned)
11690
11759
  return undefined;
11760
+ if (isLocalRepo(rid))
11761
+ return repoFromProxyMeta(rid, status, await localRepoJsonAsync(rid.slug));
11691
11762
  // always try proxy first
11692
11763
  if (hasProxy()) {
11693
11764
  try {
@@ -11702,26 +11773,28 @@ var pxt;
11702
11773
  return mkRepo(r, { config, fullName: rid.fullName, fileName: rid.fileName, tag: rid.tag });
11703
11774
  }
11704
11775
  github.repoAsync = repoAsync;
11776
+ function repoFromProxyMeta(rid, status, meta) {
11777
+ if (!meta)
11778
+ return undefined;
11779
+ return {
11780
+ github: true,
11781
+ owner: rid.owner,
11782
+ fullName: rid.fullName,
11783
+ fileName: rid.fileName,
11784
+ slug: rid.slug,
11785
+ name: rid.fileName ? `${meta.name}-${rid.fileName}` : meta.name,
11786
+ displayName: meta.displayName,
11787
+ description: meta.description,
11788
+ defaultBranch: meta.defaultBranch || "master",
11789
+ tag: rid.tag,
11790
+ status
11791
+ };
11792
+ }
11705
11793
  function proxyRepoAsync(rid, status) {
11706
11794
  // always use proxy
11707
11795
  return ghProxyWithCdnJsonAsync(rid.slug)
11708
- .then(meta => {
11709
- if (!meta)
11710
- return undefined;
11711
- return {
11712
- github: true,
11713
- owner: rid.owner,
11714
- fullName: rid.fullName,
11715
- fileName: rid.fileName,
11716
- slug: rid.slug,
11717
- name: rid.fileName ? `${meta.name}-${rid.fileName}` : meta.name,
11718
- displayName: meta.displayName,
11719
- description: meta.description,
11720
- defaultBranch: meta.defaultBranch || "master",
11721
- tag: rid.tag,
11722
- status
11723
- };
11724
- }).catch(err => {
11796
+ .then(meta => repoFromProxyMeta(rid, status, meta))
11797
+ .catch(err => {
11725
11798
  pxt.reportException(err);
11726
11799
  return undefined;
11727
11800
  });
@@ -11903,6 +11976,9 @@ var pxt;
11903
11976
  const parsed = parseRepoId(repopath);
11904
11977
  if (!parsed)
11905
11978
  return Promise.resolve(null);
11979
+ // a locally resolved repository serves its working tree and has no versions
11980
+ if (isLocalRepo(parsed))
11981
+ return Promise.resolve(undefined);
11906
11982
  return repoAsync(parsed.slug, config)
11907
11983
  .then(scr => {
11908
11984
  if (!scr)
package/built/pxtsim.d.ts CHANGED
@@ -1358,6 +1358,13 @@ declare namespace pxsim {
1358
1358
  restart?: () => void;
1359
1359
  revealElement?: (el: HTMLElement) => void;
1360
1360
  removeElement?: (el: HTMLElement, onComplete?: () => void) => void;
1361
+ /**
1362
+ * Hides the wrapper of a permanent simulator frame when the simulator is
1363
+ * hidden, leaving the element in the container, then calls `onComplete`.
1364
+ * `unhideElement` must reveal it. When unset, `hide()` passes permanent
1365
+ * frames to `removeElement` like any other frame.
1366
+ */
1367
+ hideElement?: (el: HTMLElement, onComplete?: () => void) => void;
1361
1368
  unhideElement?: (el: HTMLElement) => void;
1362
1369
  onDebuggerWarning?: (wrn: DebuggerWarningMessage) => void;
1363
1370
  onDebuggerBreakpoint?: (brk: DebuggerBreakpointMessage) => void;
@@ -1476,6 +1483,12 @@ declare namespace pxsim {
1476
1483
  private setState;
1477
1484
  private freeze;
1478
1485
  private simFrames;
1486
+ /**
1487
+ * Returns the main simulator frame: the first frame not running a
1488
+ * simulator extension, or undefined when there is none.
1489
+ * @param skipLoaned excludes the loaned simulator's frame, as `simFrames` does
1490
+ */
1491
+ private mainFrame;
1479
1492
  private getSimUrl;
1480
1493
  setSingleSimulator(): void;
1481
1494
  postMessage(msg: pxsim.SimulatorMessage, source?: Window, frameID?: string): void;