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/cli.js +14 -1
- package/built/commandparser.d.ts +7 -1
- package/built/commandparser.js +7 -1
- package/built/localrepos.d.ts +65 -0
- package/built/localrepos.js +169 -0
- package/built/pxt.js +151 -48
- package/built/pxtlib.d.ts +14 -0
- package/built/pxtlib.js +107 -31
- package/built/pxtsim.d.ts +13 -0
- package/built/pxtsim.js +30 -16
- package/built/server.d.ts +1 -0
- package/built/server.js +14 -0
- package/built/target.js +1 -1
- package/built/targetlight.js +1 -1
- package/built/tests/blocksrunner.js +3 -2
- package/built/tests/blockssetup.js +3 -2
- package/built/tests/cli-test/cli/localrepos.d.ts +65 -0
- package/built/tests/cli-test/tests/cli-test/clirunner.d.ts +2 -0
- package/built/tests/simdriver-test/simdriverrunner.d.ts +2 -0
- package/built/web/main.js +30 -30
- package/built/web/pxtapp.js +2 -2
- package/built/web/pxtasseteditor.js +1 -1
- package/built/web/pxtembed.js +1 -1
- package/built/web/pxtlib.js +1 -1
- package/built/web/pxtsim.js +1 -1
- package/built/web/pxtworker.js +1 -1
- package/built/web/rtlsemantic.css +1 -1
- package/built/web/runnerembed.js +1 -1
- package/built/web/semantic.css +1 -1
- package/package.json +1 -1
- package/react-common/components/controls/TeachingBubble.tsx +1 -1
- package/theme/common.less +5 -0
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 =
|
|
109061
|
-
|
|
109062
|
-
|
|
109063
|
-
|
|
109064
|
-
.
|
|
109065
|
-
|
|
109066
|
-
|
|
109067
|
-
.
|
|
109068
|
-
|
|
109069
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
|
158861
|
-
if (
|
|
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
|
|
158976
|
-
if (
|
|
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.
|
|
159082
|
-
this.
|
|
159083
|
-
this.
|
|
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.
|
|
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
|
|
159194
|
-
frames.
|
|
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.
|
|
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.
|
|
159370
|
+
let frame = this.mainFrame();
|
|
159281
159371
|
if (!frame) {
|
|
159282
159372
|
let wrapper = this.createFrame();
|
|
159283
|
-
this.container.
|
|
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 =
|
|
11340
|
-
|
|
11341
|
-
|
|
11342
|
-
|
|
11343
|
-
.
|
|
11344
|
-
|
|
11345
|
-
|
|
11346
|
-
.
|
|
11347
|
-
|
|
11348
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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;
|