arcane-os 0.5.11 → 0.5.13
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/CHANGELOG.md +33 -0
- package/README.md +8 -8
- package/docs/architecture.md +1 -1
- package/docs/reference/README.md +5 -5
- package/docs/reference/ai/browser-speech.md +73 -7
- package/docs/reference/ai/twin-cloud.md +1 -1
- package/docs/reference/asset-versioning.md +69 -0
- package/docs/reference/cli.md +1 -1
- package/docs/reference/core/arcane-ai-contracts.md +2 -3
- package/docs/reference/core/arcane-api.md +5 -5
- package/docs/reference/core/ollama-module.md +1 -1
- package/docs/reference/core/reference/arcane-api/platform-installation-users-system.md +2 -2
- package/docs/reference/inventory/package-api.json +1 -1
- package/docs/reference/inventory/runtime-components.json +1 -1
- package/docs/reference/inventory/runtime-modules.json +3 -3
- package/docs/reference/protocols.md +5 -5
- package/docs/reference/runtime-components.md +5 -2
- package/docs/reference/runtime-modules.md +39 -4
- package/docs/reference/sdk-api.md +6 -6
- package/package.json +1 -1
- package/runtime/arcane/components/calculator.html +1 -1
- package/runtime/arcane/components/chart.html +2 -7
- package/runtime/arcane/components/dashboard-config.html +3 -8
- package/runtime/arcane/components/file-manager.html +6 -6
- package/runtime/arcane/components/markdown-document.html +1 -6
- package/runtime/arcane/components/markdown-editor.html +2 -7
- package/runtime/arcane/components/media-embed.html +1 -1
- package/runtime/arcane/components/screen-capture.html +1 -1
- package/runtime/arcane/components/voice-transcription.html +4 -9
- package/runtime/arcane/modules/AI.js +59 -10
- package/runtime/arcane/modules/HTMLImport.js +184 -13
- package/src/dev-server.mjs +72 -5
- package/src/import-map.mjs +548 -16
- package/src/installed-sdk-runtime.mjs +1 -0
- package/src/packager/core.mjs +60 -1
- package/src/workspace-runtime.mjs +19 -7
package/src/import-map.mjs
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import {lstat,mkdir,readFile as readFileFromDisk,readdir,realpath,writeFile} from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import {pathToFileURL} from 'node:url';
|
|
4
|
+
import {SDK_VERSION} from './constants.mjs';
|
|
4
5
|
|
|
5
6
|
export const IMPORT_MAP_RELATIVE_PATH='modules/arcane.importmap.json';
|
|
6
7
|
export const MANAGED_IMPORT_MAP_ATTRIBUTE='data-arcane-import-map';
|
|
7
8
|
|
|
8
9
|
const JAVASCRIPT_EXTENSION=/\.(?:js|mjs)$/u;
|
|
10
|
+
const RESOURCE_EXTENSION=/\.(?:m?js|html?|css|wasm|svg|png|gif|jpe?g|webp|avif|ico|bmp|woff2?|ttf|otf|eot|mp3|wav|ogg|oga|opus|m4a|mp4|webm|ogv|vtt)(?:[?#]|$)/iu;
|
|
9
11
|
const PERSISTENT_CHAT_IMPORT='#arcane/persistent-ai-chat-session';
|
|
10
12
|
const PERSISTENT_CHAT_MODULE='modules/PersistentAIChatSession.js';
|
|
11
13
|
const SDK_BROWSER_ENTRY='sdk/event-manager.mjs';
|
|
@@ -17,6 +19,7 @@ const STATIC_RUNTIME_PACKAGE_IMPORTS=new Map([
|
|
|
17
19
|
]);
|
|
18
20
|
const SDK_BROWSER_SELF_IMPORTS=new Map([
|
|
19
21
|
['arcane-os/event-manager',SDK_BROWSER_ENTRY],
|
|
22
|
+
['arcane-os/logging','sdk/logging.mjs'],
|
|
20
23
|
['arcane-os/ai/browser-wasm',SDK_BROWSER_AI_ENTRY],
|
|
21
24
|
['arcane-os/ai/browser-speech',SDK_BROWSER_SPEECH_ENTRY]
|
|
22
25
|
]);
|
|
@@ -646,6 +649,7 @@ function tokenize(source){
|
|
|
646
649
|
function matchingToken(tokens,start,opening,closing){
|
|
647
650
|
let depth=0;
|
|
648
651
|
for(let index=start;index<tokens.length;index+=1){
|
|
652
|
+
if(tokens[index].type!=='punctuator')continue;
|
|
649
653
|
if(tokens[index].value===opening)depth+=1;
|
|
650
654
|
else if(tokens[index].value===closing){
|
|
651
655
|
depth-=1;
|
|
@@ -691,6 +695,7 @@ function topLevelCommas(tokens,start,end){
|
|
|
691
695
|
const commas=[];
|
|
692
696
|
const depths={parenthesis:0,bracket:0,brace:0};
|
|
693
697
|
for(let index=start;index<end;index+=1){
|
|
698
|
+
if(tokens[index].type!=='punctuator')continue;
|
|
694
699
|
const value=tokens[index].value;
|
|
695
700
|
if(value==='(')depths.parenthesis+=1;
|
|
696
701
|
else if(value===')'&&depths.parenthesis>0)depths.parenthesis-=1;
|
|
@@ -711,7 +716,10 @@ function importRecord(kind,token){
|
|
|
711
716
|
|
|
712
717
|
export function scanModuleImports(source,{importer='<module>'}={}){
|
|
713
718
|
if(typeof source!=='string')throw new TypeError('scanModuleImports source must be a string.');
|
|
714
|
-
|
|
719
|
+
return moduleImportsFromTokens(tokenize(source),importer);
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
function moduleImportsFromTokens(tokens,importer){
|
|
715
723
|
const imports=[];
|
|
716
724
|
let hasModuleSyntax=false;
|
|
717
725
|
for(let index=0;index<tokens.length;index+=1){
|
|
@@ -784,6 +792,348 @@ export function scanModuleImports(source,{importer='<module>'}={}){
|
|
|
784
792
|
};
|
|
785
793
|
}
|
|
786
794
|
|
|
795
|
+
export function versionAssetUrl(value,version=SDK_VERSION){
|
|
796
|
+
if(typeof value!=='string')return value;
|
|
797
|
+
return applyReferenceEdits(value,assetUrlVersionEdits(value,version));
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
function assetUrlVersionEdits(value,version){
|
|
801
|
+
if(typeof value!=='string'||!value||!version||value.startsWith('#')
|
|
802
|
+
||value.startsWith('//')||/^[A-Za-z][A-Za-z0-9+.-]*:/u.test(value)
|
|
803
|
+
||/^\s/u.test(value))return [];
|
|
804
|
+
const fragmentStart=value.indexOf('#');
|
|
805
|
+
const address=fragmentStart<0?value:value.slice(0,fragmentStart);
|
|
806
|
+
const queryStart=address.indexOf('?');
|
|
807
|
+
const pathname=queryStart<0?address:address.slice(0,queryStart);
|
|
808
|
+
if(!pathname)return [];
|
|
809
|
+
const versionValue=encodeURIComponent(String(version));
|
|
810
|
+
if(queryStart<0)return [{start:address.length,end:address.length,value:`?arcaneVersion=${versionValue}`}];
|
|
811
|
+
const query=address.slice(queryStart+1);
|
|
812
|
+
const edits=[];
|
|
813
|
+
let offset=queryStart+1;
|
|
814
|
+
for(const parameter of query.split('&')){
|
|
815
|
+
const equals=parameter.indexOf('=');
|
|
816
|
+
const key=equals<0?parameter:parameter.slice(0,equals);
|
|
817
|
+
let decodedKey=key;
|
|
818
|
+
try{decodedKey=decodeURIComponent(key.replaceAll('+',' '));}
|
|
819
|
+
catch{decodedKey=key;}
|
|
820
|
+
if(decodedKey==='arcaneVersion'){
|
|
821
|
+
edits.push({
|
|
822
|
+
start:offset+(equals<0?parameter.length:equals+1),
|
|
823
|
+
end:offset+parameter.length,
|
|
824
|
+
value:`${equals<0?'=':''}${versionValue}`
|
|
825
|
+
});
|
|
826
|
+
}
|
|
827
|
+
offset+=parameter.length+1;
|
|
828
|
+
}
|
|
829
|
+
if(edits.length===0)edits.push({
|
|
830
|
+
start:address.length,
|
|
831
|
+
end:address.length,
|
|
832
|
+
value:`${query&&!query.endsWith('&')?'&':''}arcaneVersion=${versionValue}`
|
|
833
|
+
});
|
|
834
|
+
return edits;
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
function applyReferenceEdits(source,edits){
|
|
838
|
+
let result=source;
|
|
839
|
+
for(const edit of edits.sort(function descendingReferenceOffset(left,right){
|
|
840
|
+
return right.start-left.start;
|
|
841
|
+
})){
|
|
842
|
+
result=result.slice(0,edit.start)+edit.value+result.slice(edit.end);
|
|
843
|
+
}
|
|
844
|
+
return result;
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
function stringReferenceEdit(source,token,version){
|
|
848
|
+
const urlEdits=assetUrlVersionEdits(token.value,version);
|
|
849
|
+
if(urlEdits.length===0)return null;
|
|
850
|
+
const quote=source[token.start];
|
|
851
|
+
const positions=[];
|
|
852
|
+
let cursor=token.start+1;
|
|
853
|
+
while(cursor<token.end-1){
|
|
854
|
+
const start=cursor;
|
|
855
|
+
if(source[cursor]==='\\'){
|
|
856
|
+
const decoded=decodedEscape(source,cursor+1);
|
|
857
|
+
for(let index=0;index<decoded.value.length;index+=1)positions.push(start-token.start);
|
|
858
|
+
cursor=decoded.next;
|
|
859
|
+
}else{
|
|
860
|
+
positions.push(cursor-token.start);
|
|
861
|
+
cursor+=1;
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
positions.push(token.end-token.start-1);
|
|
865
|
+
const edits=urlEdits.map(function authoredStringReference(edit){
|
|
866
|
+
return {
|
|
867
|
+
start:positions[edit.start],
|
|
868
|
+
end:positions[edit.end],
|
|
869
|
+
value:edit.value.replaceAll('\\','\\\\').replaceAll(quote,`\\${quote}`)
|
|
870
|
+
};
|
|
871
|
+
});
|
|
872
|
+
const original=source.slice(token.start,token.end);
|
|
873
|
+
const value=applyReferenceEdits(original,edits);
|
|
874
|
+
return value===original?null:{start:token.start,end:token.end,value};
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
function callArgumentTokens(tokens,opening){
|
|
878
|
+
const closing=matchingToken(tokens,opening,'(',')');
|
|
879
|
+
if(closing<0)return [];
|
|
880
|
+
const separators=topLevelCommas(tokens,opening+1,closing);
|
|
881
|
+
const boundaries=[opening,...separators,closing];
|
|
882
|
+
const argumentsList=[];
|
|
883
|
+
for(let index=0;index<boundaries.length-1;index+=1){
|
|
884
|
+
argumentsList.push(tokens.slice(boundaries[index]+1,boundaries[index+1]));
|
|
885
|
+
}
|
|
886
|
+
return argumentsList;
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
function literalExpressionResults(tokens){
|
|
890
|
+
if(tokens.length===1&&tokens[0].type==='string')return tokens;
|
|
891
|
+
if(tokens[0]?.value==='('&&matchingToken(tokens,0,'(',')')===tokens.length-1){
|
|
892
|
+
return literalExpressionResults(tokens.slice(1,-1));
|
|
893
|
+
}
|
|
894
|
+
let depth=0;
|
|
895
|
+
let conditional=-1;
|
|
896
|
+
let nestedConditionals=0;
|
|
897
|
+
for(let index=0;index<tokens.length;index+=1){
|
|
898
|
+
if(tokens[index].type!=='punctuator')continue;
|
|
899
|
+
const value=tokens[index].value;
|
|
900
|
+
if(['(','[','{'].includes(value)){depth+=1;continue;}
|
|
901
|
+
if([')',']','}'].includes(value)){depth-=1;continue;}
|
|
902
|
+
if(depth!==0)continue;
|
|
903
|
+
if(value==='?'){
|
|
904
|
+
if(conditional<0)conditional=index;
|
|
905
|
+
else nestedConditionals+=1;
|
|
906
|
+
}else if(value===':'&&conditional>=0){
|
|
907
|
+
if(nestedConditionals>0){nestedConditionals-=1;continue;}
|
|
908
|
+
return [
|
|
909
|
+
...literalExpressionResults(tokens.slice(conditional+1,index)),
|
|
910
|
+
...literalExpressionResults(tokens.slice(index+1))
|
|
911
|
+
];
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
return [];
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
function globalResourceCallee(tokens,index){
|
|
918
|
+
if(!identifierIsProperty(tokens,index))return index;
|
|
919
|
+
if((tokens[index-1]?.value==='.'||tokens[index-1]?.value==='?.')
|
|
920
|
+
&&new Set(['globalThis','window','self']).has(tokens[index-2]?.value)
|
|
921
|
+
&&!identifierIsProperty(tokens,index-2))return index-2;
|
|
922
|
+
return -1;
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
function localResourceBase(tokens){
|
|
926
|
+
if(!tokens)return false;
|
|
927
|
+
if(tokens.some(function nonExpressionToken(token){return !['identifier','punctuator'].includes(token.type);}))return false;
|
|
928
|
+
const base=tokens.map(function baseToken(token){return token.value;}).join('');
|
|
929
|
+
return [
|
|
930
|
+
'import.meta.url','document.baseURI','location.href','window.location.href',
|
|
931
|
+
'globalThis.location.href','self.location.href','window.document.baseURI',
|
|
932
|
+
'globalThis.document.baseURI'
|
|
933
|
+
].includes(base);
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
function literalResourceFetch(argumentsList){
|
|
937
|
+
if(argumentsList.length===1)return true;
|
|
938
|
+
const options=argumentsList[1];
|
|
939
|
+
if(options?.[0]?.value!=='{'||options.at(-1)?.value!=='}')return false;
|
|
940
|
+
for(let index=1;index<options.length-1;index+=1){
|
|
941
|
+
const token=options[index];
|
|
942
|
+
if(token.value==='...'||(token.value==='.'&&options[index+1]?.value==='.'))return false;
|
|
943
|
+
if(token.value==='body')return false;
|
|
944
|
+
if(options[index+1]?.value!==':')continue;
|
|
945
|
+
if(token.value==='method'&&(options[index+2]?.type!=='string'
|
|
946
|
+
||!['GET','HEAD'].includes(options[index+2].value.toUpperCase())))return false;
|
|
947
|
+
}
|
|
948
|
+
return true;
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
function reportAssetReference(onReference,url,kind,baseKind){
|
|
952
|
+
if(typeof onReference==='function')onReference({url,kind,baseHref:null,...(baseKind?{baseKind}:{})});
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
function javascriptReferenceEdits(source,version,onReference){
|
|
956
|
+
const tokens=tokenize(source);
|
|
957
|
+
const selected=new Map();
|
|
958
|
+
const imports=moduleImportsFromTokens(tokens,'asset source').imports;
|
|
959
|
+
const byOffset=new Map(tokens.map(function tokenByOffset(token){
|
|
960
|
+
return [token.start,token];
|
|
961
|
+
}));
|
|
962
|
+
for(const entry of imports){
|
|
963
|
+
reportAssetReference(onReference,entry.specifier,'import');
|
|
964
|
+
if(/^(?:\.{1,2}\/|\/)/u.test(entry.specifier)){
|
|
965
|
+
selected.set(entry.offset,byOffset.get(entry.offset));
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
for(let index=0;index<tokens.length;index+=1){
|
|
969
|
+
const token=tokens[index];
|
|
970
|
+
if(token.type!=='identifier'||tokens[index+1]?.value!=='(')continue;
|
|
971
|
+
if(!['URL','Worker','SharedWorker','importScripts','fetch'].includes(token.value))continue;
|
|
972
|
+
const callee=globalResourceCallee(tokens,index);
|
|
973
|
+
if(callee<0)continue;
|
|
974
|
+
const constructor=tokens[callee-1]?.value==='new';
|
|
975
|
+
if(['URL','Worker','SharedWorker'].includes(token.value)&&!constructor)continue;
|
|
976
|
+
const argumentsList=callArgumentTokens(tokens,index+1);
|
|
977
|
+
if(token.value==='fetch'&&!literalResourceFetch(argumentsList))continue;
|
|
978
|
+
if(token.value==='URL'&&!localResourceBase(argumentsList[1]))continue;
|
|
979
|
+
const selectedArguments=token.value==='importScripts'?argumentsList:argumentsList.slice(0,1);
|
|
980
|
+
for(const argument of selectedArguments){
|
|
981
|
+
for(const literal of literalExpressionResults(argument)){
|
|
982
|
+
if((token.value==='fetch'||token.value==='URL')
|
|
983
|
+
&&!RESOURCE_EXTENSION.test(literal.value))continue;
|
|
984
|
+
if((token.value==='fetch'||token.value==='URL')
|
|
985
|
+
&&/\.html?(?:[?#]|$)/iu.test(literal.value))continue;
|
|
986
|
+
const kind=token.value==='fetch'?'fetch'
|
|
987
|
+
:token.value==='URL'&&!/\.(?:js|mjs)(?:[?#]|$)/iu.test(literal.value)?'asset':'script';
|
|
988
|
+
const documentBase=['Worker','SharedWorker','fetch'].includes(token.value)
|
|
989
|
+
||(token.value==='URL'&&argumentsList[1]?.[0]?.value!=='import');
|
|
990
|
+
reportAssetReference(onReference,literal.value,kind,documentBase?'document':undefined);
|
|
991
|
+
selected.set(literal.start,literal);
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
return [...selected.values()].map(function versionJavaScriptReference(token){
|
|
996
|
+
return stringReferenceEdit(source,token,version);
|
|
997
|
+
}).filter(Boolean);
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
function cssStringEnd(source,start){
|
|
1001
|
+
const quote=source[start];
|
|
1002
|
+
let cursor=start+1;
|
|
1003
|
+
while(cursor<source.length){
|
|
1004
|
+
if(source[cursor]==='\\'){cursor+=2;continue;}
|
|
1005
|
+
if(source[cursor]===quote)return cursor+1;
|
|
1006
|
+
cursor+=1;
|
|
1007
|
+
}
|
|
1008
|
+
return source.length;
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
function cssReferenceEdits(source,version,onReference,versionReference=versionAssetUrl){
|
|
1012
|
+
const edits=[];
|
|
1013
|
+
let cursor=0;
|
|
1014
|
+
let importUrlStart=-1;
|
|
1015
|
+
function skipSpaceAndComments(start){
|
|
1016
|
+
let position=start;
|
|
1017
|
+
while(position<source.length){
|
|
1018
|
+
if(/\s/u.test(source[position])){position+=1;continue;}
|
|
1019
|
+
if(source.startsWith('/*',position)){
|
|
1020
|
+
const close=source.indexOf('*/',position+2);
|
|
1021
|
+
position=close<0?source.length:close+2;
|
|
1022
|
+
continue;
|
|
1023
|
+
}
|
|
1024
|
+
break;
|
|
1025
|
+
}
|
|
1026
|
+
return position;
|
|
1027
|
+
}
|
|
1028
|
+
function addReference(start,end,kind='asset'){
|
|
1029
|
+
const original=source.slice(start,end);
|
|
1030
|
+
if(original.includes('\\'))return;
|
|
1031
|
+
reportAssetReference(onReference,original,kind);
|
|
1032
|
+
const value=versionReference(original,version);
|
|
1033
|
+
if(value!==original)edits.push({start,end,value});
|
|
1034
|
+
}
|
|
1035
|
+
while(cursor<source.length){
|
|
1036
|
+
if(source.startsWith('/*',cursor)){
|
|
1037
|
+
const end=source.indexOf('*/',cursor+2);
|
|
1038
|
+
cursor=end<0?source.length:end+2;
|
|
1039
|
+
continue;
|
|
1040
|
+
}
|
|
1041
|
+
if(source[cursor]==='"'||source[cursor]==="'"){
|
|
1042
|
+
cursor=cssStringEnd(source,cursor);
|
|
1043
|
+
continue;
|
|
1044
|
+
}
|
|
1045
|
+
const rest=source.slice(cursor);
|
|
1046
|
+
if(/^@import(?![\w-])/iu.test(rest)){
|
|
1047
|
+
const start=skipSpaceAndComments(cursor+7);
|
|
1048
|
+
importUrlStart=start;
|
|
1049
|
+
if(source[start]==='"'||source[start]==="'"){
|
|
1050
|
+
const end=cssStringEnd(source,start);
|
|
1051
|
+
addReference(start+1,end-1,'style');
|
|
1052
|
+
cursor=end;
|
|
1053
|
+
continue;
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
if(!/[\w-]/u.test(source[cursor-1]??'')&&/^url\s*\(/iu.test(rest)){
|
|
1057
|
+
const kind=cursor===importUrlStart?'style':'asset';
|
|
1058
|
+
const open=source.indexOf('(',cursor);
|
|
1059
|
+
const start=skipSpaceAndComments(open+1);
|
|
1060
|
+
if(source[start]==='"'||source[start]==="'"){
|
|
1061
|
+
const end=cssStringEnd(source,start);
|
|
1062
|
+
addReference(start+1,end-1,kind);
|
|
1063
|
+
cursor=end;
|
|
1064
|
+
}else{
|
|
1065
|
+
let end=start;
|
|
1066
|
+
while(end<source.length&&source[end]!==')'){
|
|
1067
|
+
end+=source[end]==='\\'?2:1;
|
|
1068
|
+
}
|
|
1069
|
+
let valueEnd=Math.min(end,source.length);
|
|
1070
|
+
while(valueEnd>start&&/\s/u.test(source[valueEnd-1]))valueEnd-=1;
|
|
1071
|
+
addReference(start,valueEnd,kind);
|
|
1072
|
+
cursor=Math.min(end+1,source.length);
|
|
1073
|
+
}
|
|
1074
|
+
continue;
|
|
1075
|
+
}
|
|
1076
|
+
cursor+=1;
|
|
1077
|
+
}
|
|
1078
|
+
return edits;
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
function importMapReferenceEdits(source,version,onReference){
|
|
1082
|
+
const tokens=tokenize(source);
|
|
1083
|
+
const edits=[];
|
|
1084
|
+
function objectProperties(start){
|
|
1085
|
+
if(tokens[start]?.type!=='punctuator'||tokens[start].value!=='{')return [];
|
|
1086
|
+
const properties=[];
|
|
1087
|
+
let cursor=start+1;
|
|
1088
|
+
while(cursor<tokens.length&&!(tokens[cursor].type==='punctuator'&&tokens[cursor].value==='}')){
|
|
1089
|
+
const key=tokens[cursor];
|
|
1090
|
+
if(key.type!=='string'||tokens[cursor+1]?.value!==':')return [];
|
|
1091
|
+
const valueIndex=cursor+2;
|
|
1092
|
+
const value=tokens[valueIndex];
|
|
1093
|
+
if(!value)return [];
|
|
1094
|
+
properties.push({key:key.value,valueIndex,value});
|
|
1095
|
+
let end=valueIndex;
|
|
1096
|
+
if(value.type==='punctuator'&&(value.value==='{'||value.value==='[')){
|
|
1097
|
+
end=matchingToken(tokens,valueIndex,value.value,value.value==='{'?'}':']');
|
|
1098
|
+
if(end<0)return [];
|
|
1099
|
+
}
|
|
1100
|
+
cursor=end+1;
|
|
1101
|
+
if(tokens[cursor]?.value===',')cursor+=1;
|
|
1102
|
+
else if(tokens[cursor]?.value!=='}')return [];
|
|
1103
|
+
}
|
|
1104
|
+
return properties;
|
|
1105
|
+
}
|
|
1106
|
+
function addImports(start){
|
|
1107
|
+
for(const property of objectProperties(start)){
|
|
1108
|
+
if(property.value.type!=='string')continue;
|
|
1109
|
+
if(property.key.endsWith('/')||property.value.value.split(/[?#]/u)[0].endsWith('/'))continue;
|
|
1110
|
+
reportAssetReference(onReference,property.value.value,'import');
|
|
1111
|
+
const edit=stringReferenceEdit(source,property.value,version);
|
|
1112
|
+
if(edit)edits.push(edit);
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
for(const property of objectProperties(0)){
|
|
1116
|
+
if(property.key==='imports')addImports(property.valueIndex);
|
|
1117
|
+
if(property.key==='scopes'){
|
|
1118
|
+
for(const scope of objectProperties(property.valueIndex))addImports(scope.valueIndex);
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
return edits;
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
export function rewriteAssetReferences(source,{filePath,version=SDK_VERSION,onReference}={}){
|
|
1125
|
+
if(typeof source!=='string')throw new TypeError('Asset reference source must be a string.');
|
|
1126
|
+
const extension=path.extname(String(filePath??'')).toLowerCase();
|
|
1127
|
+
if(extension==='.js'||extension==='.mjs'){
|
|
1128
|
+
return applyReferenceEdits(source,javascriptReferenceEdits(source,version,onReference));
|
|
1129
|
+
}
|
|
1130
|
+
if(extension==='.css')return applyReferenceEdits(source,cssReferenceEdits(source,version,onReference));
|
|
1131
|
+
if(extension==='.html'||extension==='.htm'){
|
|
1132
|
+
return applyReferenceEdits(source,htmlReferenceEdits(source,version,onReference));
|
|
1133
|
+
}
|
|
1134
|
+
return source;
|
|
1135
|
+
}
|
|
1136
|
+
|
|
787
1137
|
function registerSpecifier(registry,specifier,target){
|
|
788
1138
|
registry.set(specifier,{specifier,target});
|
|
789
1139
|
}
|
|
@@ -798,7 +1148,7 @@ function validateInventory(files){
|
|
|
798
1148
|
return exact;
|
|
799
1149
|
}
|
|
800
1150
|
|
|
801
|
-
export async function buildImportMap({files,signal}={}){
|
|
1151
|
+
export async function buildImportMap({files,signal,version=SDK_VERSION}={}){
|
|
802
1152
|
throwIfAborted(signal);
|
|
803
1153
|
const inventory=validateInventory(files);
|
|
804
1154
|
const modules=[...inventory]
|
|
@@ -858,9 +1208,14 @@ export async function buildImportMap({files,signal}={}){
|
|
|
858
1208
|
'./arcane/sdk/dependencies/event-pubsub/index.js'
|
|
859
1209
|
);
|
|
860
1210
|
}
|
|
1211
|
+
for(const relative of [...inventory].sort(compareText)){
|
|
1212
|
+
if(JAVASCRIPT_EXTENSION.test(relative)){
|
|
1213
|
+
registerSpecifier(namedRegistry,`./arcane/${relative}`,`./arcane/${relative}`);
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
861
1216
|
const imports={};
|
|
862
1217
|
for(const entry of [...namedRegistry.values()].sort((left,right)=>compareText(left.specifier,right.specifier))){
|
|
863
|
-
imports[entry.specifier]=entry.target;
|
|
1218
|
+
imports[entry.specifier]=versionAssetUrl(entry.target,version);
|
|
864
1219
|
}
|
|
865
1220
|
return {
|
|
866
1221
|
imports,
|
|
@@ -918,9 +1273,22 @@ async function physicalRuntime(workspaceRoot,signal){
|
|
|
918
1273
|
|
|
919
1274
|
async function managedImportMapBuild(resolvedWorkspace,signal){
|
|
920
1275
|
const runtime=await physicalRuntime(resolvedWorkspace,signal);
|
|
921
|
-
const
|
|
1276
|
+
const version=await readWorkspaceAssetVersion(resolvedWorkspace);
|
|
1277
|
+
const built=await buildImportMap({files:runtime.files,signal,version});
|
|
922
1278
|
const json=`${JSON.stringify({imports:built.imports},null,2).replaceAll('<','\\u003c')}\n`;
|
|
923
|
-
return {built,json};
|
|
1279
|
+
return {built,json,version};
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
export async function readWorkspaceAssetVersion(workspaceRoot){
|
|
1283
|
+
let source;
|
|
1284
|
+
try{source=await readFileFromDisk(path.join(workspaceRoot,'arcane.lock.json'),'utf8');}
|
|
1285
|
+
catch(error){
|
|
1286
|
+
if(error?.code==='ENOENT')return SDK_VERSION;
|
|
1287
|
+
throw error;
|
|
1288
|
+
}
|
|
1289
|
+
const document=JSON.parse(source);
|
|
1290
|
+
const version=document?.sdk?.version;
|
|
1291
|
+
return typeof version==='string'&&version?version:SDK_VERSION;
|
|
924
1292
|
}
|
|
925
1293
|
|
|
926
1294
|
function asciiLower(value){
|
|
@@ -946,7 +1314,9 @@ function htmlTagName(value){
|
|
|
946
1314
|
function parseTagAttributes(openTag){
|
|
947
1315
|
const attributes=new Map();
|
|
948
1316
|
const duplicates=new Set();
|
|
1317
|
+
const positions=new Map();
|
|
949
1318
|
Object.defineProperty(attributes,'duplicates',{value:duplicates});
|
|
1319
|
+
Object.defineProperty(attributes,'positions',{value:positions});
|
|
950
1320
|
const tagHead=openTag.match(/^<[A-Za-z][^\t\n\f\r />]*(?=[\t\n\f\r />])/u);
|
|
951
1321
|
if(!tagHead)fail('Application HTML contains a malformed structural start tag.');
|
|
952
1322
|
let index=tagHead[0].length;
|
|
@@ -963,25 +1333,34 @@ function parseTagAttributes(openTag){
|
|
|
963
1333
|
const name=asciiLower(openTag.slice(start,index));
|
|
964
1334
|
while(/[\t\n\f\r ]/u.test(openTag[index]??''))index+=1;
|
|
965
1335
|
let value='';
|
|
1336
|
+
let valueStart=index;
|
|
1337
|
+
let valueEnd=index;
|
|
1338
|
+
let quote='';
|
|
966
1339
|
if(openTag[index]==='='){
|
|
967
1340
|
index+=1;
|
|
968
1341
|
while(/[\t\n\f\r ]/u.test(openTag[index]??''))index+=1;
|
|
969
|
-
|
|
1342
|
+
quote=openTag[index];
|
|
970
1343
|
if(quote==='\''||quote==='"'){
|
|
971
1344
|
index+=1;
|
|
972
|
-
|
|
1345
|
+
valueStart=index;
|
|
973
1346
|
while(index<openTag.length&&openTag[index]!==quote)index+=1;
|
|
1347
|
+
valueEnd=index;
|
|
974
1348
|
value=openTag.slice(valueStart,index);
|
|
975
1349
|
if(openTag[index]===quote)index+=1;
|
|
976
1350
|
}else{
|
|
977
|
-
|
|
1351
|
+
quote='';
|
|
1352
|
+
valueStart=index;
|
|
978
1353
|
while(index<openTag.length&&!/[\t\n\f\r >]/u.test(openTag[index]))index+=1;
|
|
1354
|
+
valueEnd=index;
|
|
979
1355
|
value=openTag.slice(valueStart,index);
|
|
980
1356
|
}
|
|
981
1357
|
}
|
|
982
1358
|
if(name){
|
|
983
1359
|
if(attributes.has(name))duplicates.add(name);
|
|
984
|
-
else
|
|
1360
|
+
else{
|
|
1361
|
+
attributes.set(name,value);
|
|
1362
|
+
positions.set(name,{start:valueStart,end:valueEnd,quote});
|
|
1363
|
+
}
|
|
985
1364
|
}
|
|
986
1365
|
}
|
|
987
1366
|
return attributes;
|
|
@@ -1004,7 +1383,9 @@ function decodeStructuralAttribute(value,label){
|
|
|
1004
1383
|
return String.fromCodePoint(point);
|
|
1005
1384
|
}
|
|
1006
1385
|
);
|
|
1007
|
-
if(
|
|
1386
|
+
if(/&(?:#[^;\s&]*|[A-Za-z][A-Za-z0-9]*);/u.test(source.replace(
|
|
1387
|
+
/&(?:#[0-9]+|#x[a-f0-9]+|amp|apos|gt|lt|quot);/giu,''
|
|
1388
|
+
))){
|
|
1008
1389
|
fail(
|
|
1009
1390
|
`Application HTML ${label} contains an unsupported or ambiguous character reference.`
|
|
1010
1391
|
);
|
|
@@ -1087,13 +1468,13 @@ function rawElementEnd(html,tag,openEnd){
|
|
|
1087
1468
|
const closePattern=new RegExp(`<\\/${tag}(?=[\\t\\n\\f\\r />]|$)`,'gi');
|
|
1088
1469
|
closePattern.lastIndex=openEnd;
|
|
1089
1470
|
const close=closePattern.exec(html);
|
|
1090
|
-
if(!close)return {end:html.length,closed:false};
|
|
1471
|
+
if(!close)return {end:html.length,contentEnd:html.length,closed:false};
|
|
1091
1472
|
const end=htmlTagEnd(html,close.index+close[0].length);
|
|
1092
1473
|
const closeTag=html.slice(close.index,end);
|
|
1093
1474
|
if(!new RegExp(`^<\\/${tag}[\\t\\n\\f\\r ]*>$`,'i').test(closeTag)){
|
|
1094
1475
|
fail(`Application HTML contains a malformed </${tag}> end tag.`);
|
|
1095
1476
|
}
|
|
1096
|
-
return {end,closed:true};
|
|
1477
|
+
return {end,contentEnd:close.index,closed:true};
|
|
1097
1478
|
}
|
|
1098
1479
|
|
|
1099
1480
|
function commentEnd(html,start){
|
|
@@ -1179,6 +1560,8 @@ function scanHtmlStructure(html){
|
|
|
1179
1560
|
const links=[];
|
|
1180
1561
|
const bases=[];
|
|
1181
1562
|
const metas=[];
|
|
1563
|
+
const elements=[];
|
|
1564
|
+
const styles=[];
|
|
1182
1565
|
let headClose=-1;
|
|
1183
1566
|
let bodyClose=-1;
|
|
1184
1567
|
let cursor=0;
|
|
@@ -1225,6 +1608,7 @@ function scanHtmlStructure(html){
|
|
|
1225
1608
|
cursor=openEnd;
|
|
1226
1609
|
continue;
|
|
1227
1610
|
}
|
|
1611
|
+
if(tag!=='template')elements.push({tag,start,end:openEnd,open});
|
|
1228
1612
|
if(tag==='link'){
|
|
1229
1613
|
links.push({start,end:openEnd,open});
|
|
1230
1614
|
cursor=openEnd;
|
|
@@ -1256,13 +1640,158 @@ function scanHtmlStructure(html){
|
|
|
1256
1640
|
if(tag==='script')scripts.push({
|
|
1257
1641
|
start,
|
|
1258
1642
|
openEnd,
|
|
1643
|
+
contentEnd:raw.contentEnd,
|
|
1259
1644
|
end:raw.end,
|
|
1260
1645
|
open,
|
|
1261
1646
|
closed:raw.closed
|
|
1262
1647
|
});
|
|
1648
|
+
if(tag==='style')styles.push({start:openEnd,end:raw.contentEnd});
|
|
1263
1649
|
cursor=raw.end;
|
|
1264
1650
|
}
|
|
1265
|
-
return {scripts,links,bases,metas,headClose,bodyClose};
|
|
1651
|
+
return {scripts,links,bases,metas,elements,styles,headClose,bodyClose};
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1654
|
+
function htmlAttributeView(value){
|
|
1655
|
+
let decoded='';
|
|
1656
|
+
const positions=[];
|
|
1657
|
+
for(let cursor=0;cursor<value.length;){
|
|
1658
|
+
const entity=value.slice(cursor).match(/^&(?:#([0-9]+)|#x([a-f0-9]+)|(amp|apos|gt|lt|quot));/iu);
|
|
1659
|
+
if(entity){
|
|
1660
|
+
const point=entity[1]||entity[2]
|
|
1661
|
+
?Number.parseInt(entity[1]??entity[2],entity[1]?10:16)
|
|
1662
|
+
:null;
|
|
1663
|
+
if(point!==null&&(!Number.isSafeInteger(point)||point<=0||point>0x10ffff))return null;
|
|
1664
|
+
const character=point===null
|
|
1665
|
+
?{amp:'&',apos:"'",gt:'>',lt:'<',quot:'"'}[asciiLower(entity[3])]
|
|
1666
|
+
:String.fromCodePoint(point);
|
|
1667
|
+
for(let index=0;index<character.length;index+=1)positions.push(cursor);
|
|
1668
|
+
decoded+=character;
|
|
1669
|
+
cursor+=entity[0].length;
|
|
1670
|
+
}else{
|
|
1671
|
+
if(/^&[A-Za-z][A-Za-z0-9]*;/u.test(value.slice(cursor)))return null;
|
|
1672
|
+
positions.push(cursor);
|
|
1673
|
+
decoded+=value[cursor++];
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1676
|
+
positions.push(value.length);
|
|
1677
|
+
return {decoded,positions};
|
|
1678
|
+
}
|
|
1679
|
+
|
|
1680
|
+
function versionHtmlAttribute(value,version){
|
|
1681
|
+
// Map decoded URL positions back to authored HTML so existing query spelling survives.
|
|
1682
|
+
const view=htmlAttributeView(value);
|
|
1683
|
+
if(!view)return value;
|
|
1684
|
+
const {decoded,positions}=view;
|
|
1685
|
+
const edits=assetUrlVersionEdits(decoded,version).map(function authoredAttributeEdit(edit){
|
|
1686
|
+
return {
|
|
1687
|
+
start:positions[edit.start],
|
|
1688
|
+
end:positions[edit.end],
|
|
1689
|
+
value:edit.value.replaceAll('&','&')
|
|
1690
|
+
};
|
|
1691
|
+
});
|
|
1692
|
+
return applyReferenceEdits(value,edits);
|
|
1693
|
+
}
|
|
1694
|
+
|
|
1695
|
+
function htmlStyleReferenceEdits(source,version,onReference){
|
|
1696
|
+
const view=htmlAttributeView(source);
|
|
1697
|
+
if(!view)return [];
|
|
1698
|
+
const edits=cssReferenceEdits(view.decoded,version,onReference);
|
|
1699
|
+
return edits.map(function authoredStyleReference(edit){
|
|
1700
|
+
const start=view.positions[edit.start];
|
|
1701
|
+
const end=view.positions[edit.end];
|
|
1702
|
+
return {start,end,value:versionHtmlAttribute(source.slice(start,end),version)};
|
|
1703
|
+
});
|
|
1704
|
+
}
|
|
1705
|
+
|
|
1706
|
+
function srcsetReferenceEdits(source,version,onReference){
|
|
1707
|
+
const edits=[];
|
|
1708
|
+
let cursor=0;
|
|
1709
|
+
while(cursor<source.length){
|
|
1710
|
+
while(/[\t\n\f\r ,]/u.test(source[cursor]??''))cursor+=1;
|
|
1711
|
+
const start=cursor;
|
|
1712
|
+
while(cursor<source.length&&!/[\t\n\f\r ]/u.test(source[cursor]))cursor+=1;
|
|
1713
|
+
let end=cursor;
|
|
1714
|
+
while(end>start&&source[end-1]===',')end-=1;
|
|
1715
|
+
const original=source.slice(start,end);
|
|
1716
|
+
const view=htmlAttributeView(original);
|
|
1717
|
+
if(view)reportAssetReference(onReference,view.decoded,'asset');
|
|
1718
|
+
const value=versionHtmlAttribute(original,version);
|
|
1719
|
+
if(value!==original)edits.push({start,end,value});
|
|
1720
|
+
if(end<cursor)continue;
|
|
1721
|
+
while(cursor<source.length&&source[cursor]!==',')cursor+=1;
|
|
1722
|
+
}
|
|
1723
|
+
return edits;
|
|
1724
|
+
}
|
|
1725
|
+
|
|
1726
|
+
function htmlReferenceEdits(source,version,onReference){
|
|
1727
|
+
const structure=scanHtmlStructure(source);
|
|
1728
|
+
const edits=[];
|
|
1729
|
+
const sourceTags=new Set(['script','html-import','img','audio','video','source','track','iframe','embed']);
|
|
1730
|
+
const linkResources=new Set(['stylesheet','modulepreload','preload','icon','manifest']);
|
|
1731
|
+
const base=structure.bases[0];
|
|
1732
|
+
const baseHref=base?structuralAttribute(parseTagAttributes(base.open),'href','base'):null;
|
|
1733
|
+
if(baseHref&&(/^[A-Za-z][A-Za-z0-9+.-]*:/u.test(baseHref)||baseHref.startsWith('//')))version=null;
|
|
1734
|
+
function reportHtmlReference(reference){
|
|
1735
|
+
if(typeof onReference==='function')onReference({...reference,baseHref});
|
|
1736
|
+
}
|
|
1737
|
+
function addNestedEdits(offset,nested){
|
|
1738
|
+
for(const edit of nested)edits.push({...edit,start:offset+edit.start,end:offset+edit.end});
|
|
1739
|
+
}
|
|
1740
|
+
for(const element of structure.elements){
|
|
1741
|
+
const attributes=parseTagAttributes(element.open);
|
|
1742
|
+
const selected=[];
|
|
1743
|
+
const activeScript=element.tag!=='script'
|
|
1744
|
+
||['','module','text/javascript','application/javascript'].includes(scriptType(attributes));
|
|
1745
|
+
if(sourceTags.has(element.tag)&&element.tag!=='html-import'&&activeScript)selected.push('src');
|
|
1746
|
+
if(element.tag==='html-import')selected.push('href');
|
|
1747
|
+
if(element.tag==='video')selected.push('poster');
|
|
1748
|
+
if(element.tag==='object')selected.push('data');
|
|
1749
|
+
if(element.tag==='input'&&canonicalHtmlToken(attributes.get('type')??'')==='image')selected.push('src');
|
|
1750
|
+
const relationships=canonicalHtmlToken(attributes.get('rel')??'').split(/[\t\n\f\r ]+/u);
|
|
1751
|
+
if(element.tag==='link'){
|
|
1752
|
+
if(relationships.some(function loadsResource(relationship){return linkResources.has(relationship);})){selected.push('href');}
|
|
1753
|
+
}
|
|
1754
|
+
for(const name of selected){
|
|
1755
|
+
const original=attributes.get(name);
|
|
1756
|
+
if(!original)continue;
|
|
1757
|
+
const destination=canonicalHtmlToken(attributes.get('as')??'');
|
|
1758
|
+
const kind=element.tag==='script'?'script'
|
|
1759
|
+
:element.tag==='html-import'?'component'
|
|
1760
|
+
:element.tag==='iframe'?'document'
|
|
1761
|
+
:element.tag==='link'&&relationships.includes('stylesheet')?'style'
|
|
1762
|
+
:element.tag==='link'&&relationships.includes('modulepreload')?'script'
|
|
1763
|
+
:element.tag==='link'&&relationships.includes('preload')
|
|
1764
|
+
&&['script','worker','style'].includes(destination)?destination==='style'?'style':'script':'asset';
|
|
1765
|
+
const view=htmlAttributeView(original);
|
|
1766
|
+
if(view)reportAssetReference(reportHtmlReference,view.decoded,kind);
|
|
1767
|
+
const value=versionHtmlAttribute(original,version);
|
|
1768
|
+
if(value===original)continue;
|
|
1769
|
+
const position=attributes.positions.get(name);
|
|
1770
|
+
edits.push({start:element.start+position.start,end:element.start+position.end,value});
|
|
1771
|
+
}
|
|
1772
|
+
if((element.tag==='img'||element.tag==='source')&&attributes.has('srcset')){
|
|
1773
|
+
addNestedEdits(element.start+attributes.positions.get('srcset').start,
|
|
1774
|
+
srcsetReferenceEdits(attributes.get('srcset'),version,reportHtmlReference));
|
|
1775
|
+
}
|
|
1776
|
+
if(attributes.has('style')){
|
|
1777
|
+
addNestedEdits(element.start+attributes.positions.get('style').start,
|
|
1778
|
+
htmlStyleReferenceEdits(attributes.get('style'),version,reportHtmlReference));
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1781
|
+
for(const script of structure.scripts){
|
|
1782
|
+
const attributes=parseTagAttributes(script.open);
|
|
1783
|
+
if(attributes.has('src'))continue;
|
|
1784
|
+
const type=scriptType(attributes);
|
|
1785
|
+
const body=source.slice(script.openEnd,script.contentEnd);
|
|
1786
|
+
if(type==='importmap')addNestedEdits(script.openEnd,importMapReferenceEdits(body,version,reportHtmlReference));
|
|
1787
|
+
else if(['','module','text/javascript','application/javascript'].includes(type)){
|
|
1788
|
+
addNestedEdits(script.openEnd,javascriptReferenceEdits(body,version,reportHtmlReference));
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1791
|
+
for(const style of structure.styles){
|
|
1792
|
+
addNestedEdits(style.start,cssReferenceEdits(source.slice(style.start,style.end),version,reportHtmlReference));
|
|
1793
|
+
}
|
|
1794
|
+
return edits;
|
|
1266
1795
|
}
|
|
1267
1796
|
|
|
1268
1797
|
function removeManagedBlocks(html,blocks){
|
|
@@ -1579,7 +2108,7 @@ export async function createApplicationTestImportMapContext({
|
|
|
1579
2108
|
fail(`Application test import-map entry is invalid: ${String(specifier)}.`);
|
|
1580
2109
|
}
|
|
1581
2110
|
const relative=safeRelativePath(
|
|
1582
|
-
target.slice(2),
|
|
2111
|
+
decodeURIComponent(target.slice(2).split(/[?#]/u)[0]),
|
|
1583
2112
|
`application test import-map target for ${specifier}`
|
|
1584
2113
|
);
|
|
1585
2114
|
if(boundary==='source'&&/^(?:dist|test)\//u.test(relative)){
|
|
@@ -1699,10 +2228,13 @@ async function generateImportMapUnlocked({
|
|
|
1699
2228
|
renderManagedHtml(html,'{"imports":{}}\n',baseHref);
|
|
1700
2229
|
documentStates.push({filePath:documentPath,html,label,baseHref});
|
|
1701
2230
|
}
|
|
1702
|
-
const {built,json}=await managedImportMapBuild(resolvedWorkspace,signal);
|
|
2231
|
+
const {built,json,version}=await managedImportMapBuild(resolvedWorkspace,signal);
|
|
1703
2232
|
const renderedDocuments=documentStates.map(item=>({
|
|
1704
2233
|
...item,
|
|
1705
|
-
content:renderManagedHtml(item.html,json,item.baseHref)
|
|
2234
|
+
content:rewriteAssetReferences(renderManagedHtml(item.html,json,item.baseHref),{
|
|
2235
|
+
filePath:item.filePath,
|
|
2236
|
+
version
|
|
2237
|
+
})
|
|
1706
2238
|
}));
|
|
1707
2239
|
|
|
1708
2240
|
throwIfAborted(signal);
|