arcane-os 0.3.2 → 0.3.3

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 CHANGED
@@ -1,5 +1,18 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.3
4
+
5
+ - Corrected installed-workspace import-map refresh to preserve rich browser
6
+ document inspection records while passing only navigable document paths to
7
+ the public generator, including `.html` and `.htm` pages without modifying
8
+ HTML fragments.
9
+ - Restored one semantic `arcane.lock.json` contract across scaffold, init, and
10
+ installed runtime materialization, replacing stale versions with the exact
11
+ installed package version and npm-alias roots without byte or security
12
+ metadata.
13
+ - Added selected installed-package coverage for stale-lock replacement and the
14
+ public multi-document import-map command through the packed npm artifact.
15
+
3
16
  ## 0.3.2
4
17
 
5
18
  - Preserved complete application, document, model, message, event, process,
package/README.md CHANGED
@@ -19,7 +19,7 @@ version-locked SDK runtime, while an integrated Arcane checkout uses its live
19
19
  `arcane/` runtime. Both profiles preserve the same app URLs, theme, packaging,
20
20
  event, cancellation, and browser run contracts.
21
21
 
22
- This checkout defines the `0.3.2` SDK contract. Applications pin one exact npm
22
+ This checkout defines the `0.3.3` SDK contract. Applications pin one exact npm
23
23
  version and lockfile; registry state is deliberately not baked into application
24
24
  artifacts.
25
25
 
@@ -126,7 +126,7 @@ uses the same controller for automatic memory extraction.
126
126
  Create a new repository-shaped Arcane application with the exact stable SDK:
127
127
 
128
128
  ```bash
129
- npx arcane-os@0.3.2 new my-app --path ./my-app --target portable --git
129
+ npx arcane-os@0.3.3 new my-app --path ./my-app --target portable --git
130
130
  cd my-app
131
131
  npm install
132
132
  npm run check
@@ -137,7 +137,7 @@ To enroll an existing repository, install the exact SDK and initialize only
137
137
  missing Arcane files:
138
138
 
139
139
  ```bash
140
- npm install --save-dev --save-exact arcane-os@0.3.2
140
+ npm install --save-dev --save-exact arcane-os@0.3.3
141
141
  npm exec -- arcane init my-app --target portable
142
142
  ```
143
143
 
@@ -153,7 +153,7 @@ npm exec -- arcane-os targets
153
153
  No global SDK install or standalone Arcane CLI is required. The application
154
154
  repository's exact npm dependency and lockfile own the CLI and toolchain version.
155
155
 
156
- Use `npx arcane-os@0.3.2` for the initial bootstrap because it names this npm
156
+ Use `npx arcane-os@0.3.3` for the initial bootstrap because it names this npm
157
157
  package explicitly; bare `npx arcane` outside an installed project could resolve
158
158
  a different package. Both installed commands invoke the same headless toolchain.
159
159
  Project-local npm scripts use the SDK pinned by that app's `package-lock.json`,
@@ -174,7 +174,7 @@ node ./bin/arcane.mjs new local-app --path ../local-app --target portable --git
174
174
 
175
175
  # From the generated app repository
176
176
  cd ../local-app
177
- npm install --save-dev --save-exact ../arcane-os-sdk/arcane-os-0.3.2.tgz
177
+ npm install --save-dev --save-exact ../arcane-os-sdk/arcane-os-0.3.3.tgz
178
178
  npm run check
179
179
  npm ci
180
180
  ```
@@ -184,7 +184,7 @@ same location. The lockfile retains the selected package dependency while
184
184
  Arcane uses the installed package name and version. Local directory `file:` dependencies are not
185
185
  accepted because npm may install them as links; use a packed `.tgz`. A GitHub
186
186
  runner also needs that tarball at the locked path. After publication, replace
187
- the local declaration with the exact `arcane-os@0.3.2` registry package and
187
+ the local declaration with the exact `arcane-os@0.3.3` registry package and
188
188
  commit the regenerated lock.
189
189
 
190
190
  Generated repositories use `npm ci --ignore-scripts` in CI. Run dependency
@@ -322,7 +322,7 @@ package installation, or assertions.
322
322
 
323
323
  ## Current target support
324
324
 
325
- Version `0.3.2` exposes one browser target and five explicitly paired
325
+ Version `0.3.3` exposes one browser target and five explicitly paired
326
326
  native development targets: a non-runnable portable directory, a
327
327
  Windows x64 unsigned-local-test EXE bundle, Linux x64 and Linux ARM64
328
328
  unsigned-local-test DEBs, and an Android development-signed APK. The
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arcane-os",
3
- "version": "0.3.2",
3
+ "version": "0.3.3",
4
4
  "description": "Arcane OS JavaScript SDK, project-local CLI, browser runtime, and repository-portable application packager.",
5
5
  "type": "module",
6
6
  "main": "./src/index.mjs",
@@ -1380,7 +1380,7 @@
1380
1380
  function stop() {
1381
1381
  recordingRequested = false;
1382
1382
  clearSilenceTimer();
1383
- if (captureSession?.recorder.state !== 'inactive') {
1383
+ if (captureSession && captureSession.recorder.state !== 'inactive') {
1384
1384
  captureSession.recorder.stop();
1385
1385
  }
1386
1386
  return true;
@@ -14,7 +14,10 @@
14
14
  "required": ["name", "version"],
15
15
  "properties": {
16
16
  "name": {"const": "arcane-os"},
17
- "version": {"const": "0.3.2"}
17
+ "version": {
18
+ "type": "string",
19
+ "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+(?:-[0-9A-Za-z.-]+)?$"
20
+ }
18
21
  }
19
22
  },
20
23
  "runtime": {
@@ -31,6 +31,11 @@ const SDK_RUNTIME_SOURCE_PROTOCOL='arcane-sdk-runtime-source/1';
31
31
  const SDK_RUNTIME_SOURCE_ARCANE_ROOTS=new Set([
32
32
  'components','css','entities','img','modules','security'
33
33
  ]);
34
+ const SDK_INSTALLED_ARCANE_ROOTS=new Set([
35
+ ...SDK_RUNTIME_SOURCE_ARCANE_ROOTS,
36
+ 'dependencies',
37
+ 'sdk'
38
+ ]);
34
39
  const SDK_RUNTIME_SOURCE_PRIVATE_SEGMENTS=new Set([
35
40
  'node_modules','.git','.hg','.svn','cvs'
36
41
  ]);
@@ -126,7 +131,7 @@ async function canonicalRealDirectory(requested,label){
126
131
  return canonical;
127
132
  }
128
133
 
129
- function sdkRuntimeSourcePathAllowed(relative,{arcaneRoot=false}={}){
134
+ function sdkRuntimeSourcePathAllowed(relative,{arcaneRoots=null}={}){
130
135
  if(relative.length===0)return false;
131
136
  const normalized=relative.map(function normalizeRuntimeSourceSegment(segment){
132
137
  return segment.normalize('NFC').toLowerCase();
@@ -135,11 +140,15 @@ function sdkRuntimeSourcePathAllowed(relative,{arcaneRoot=false}={}){
135
140
  return segment.startsWith('.')||SDK_RUNTIME_SOURCE_PRIVATE_SEGMENTS.has(segment)
136
141
  ||SDK_RUNTIME_SOURCE_PRIVATE_MANIFESTS.has(segment);
137
142
  }))return false;
138
- return !arcaneRoot||SDK_RUNTIME_SOURCE_ARCANE_ROOTS.has(normalized[0]);
143
+ return arcaneRoots===null||arcaneRoots.has(normalized[0]);
139
144
  }
140
145
 
141
146
  function sdkArcaneSourcePathAllowed(relative){
142
- return sdkRuntimeSourcePathAllowed(relative,{arcaneRoot:true});
147
+ return sdkRuntimeSourcePathAllowed(relative,{arcaneRoots:SDK_RUNTIME_SOURCE_ARCANE_ROOTS});
148
+ }
149
+
150
+ function sdkInstalledArcanePathAllowed(relative){
151
+ return sdkRuntimeSourcePathAllowed(relative,{arcaneRoots:SDK_INSTALLED_ARCANE_ROOTS});
143
152
  }
144
153
 
145
154
  function sdkDependencySourcePathAllowed(relative){
@@ -418,7 +427,7 @@ async function sourceRoutes(workspaceRoot,appId,{
418
427
  {
419
428
  prefix:['arcane'],
420
429
  root:runtimeRoot,
421
- allow:sdkArcaneSourcePathAllowed
430
+ allow:sdkInstalledArcanePathAllowed
422
431
  }
423
432
  ]
424
433
  };
@@ -1,7 +1,8 @@
1
- import {readFile,realpath,stat} from 'node:fs/promises';
1
+ import {lstat,readFile,realpath,stat,writeFile} from 'node:fs/promises';
2
2
  import path from 'node:path';
3
3
  import {materializeWorkspaceRuntimeContent} from './workspace-runtime.mjs';
4
4
  import {withWorkspaceOperationLock} from './workspace-operation-lock.mjs';
5
+ import {createWorkspaceLockDocument} from './templates/workspace-template.mjs';
5
6
  import {
6
7
  resolveInstalledSdkInstallation,
7
8
  resolveSdkPackageDeclaration
@@ -55,6 +56,18 @@ async function installedSdkAuthority(canonicalRoot,{sdkPackageSource}={}){
55
56
  return {declaration,installation};
56
57
  }
57
58
 
59
+ async function writeWorkspaceLock(lockPath,lock){
60
+ try{
61
+ const info=await lstat(lockPath);
62
+ if(info.isSymbolicLink()||!info.isFile()){
63
+ fail(`Workspace SDK lock must be a real file: ${lockPath}.`);
64
+ }
65
+ }catch(error){
66
+ if(error?.code!=='ENOENT')throw error;
67
+ }
68
+ await writeFile(lockPath,`${JSON.stringify(lock,null,2)}\n`,'utf8');
69
+ }
70
+
58
71
  export async function materializeInstalledSdkRuntime({
59
72
  workspaceRoot,
60
73
  sdkPackageSource,
@@ -79,12 +92,21 @@ export async function materializeInstalledSdkRuntime({
79
92
  signal,
80
93
  onEvent
81
94
  });
95
+ const lockPath=path.join(canonicalRoot,'arcane.lock.json');
96
+ const lock=createWorkspaceLockDocument({
97
+ dependencyName:authority.installation.dependencyName,
98
+ packageName:authority.installation.packageName,
99
+ packageVersion:authority.installation.packageVersion,
100
+ packageSource:authority.installation.packageSource
101
+ });
102
+ await writeWorkspaceLock(lockPath,lock);
82
103
  return {
83
104
  schemaVersion:1,
84
105
  kind:'arcane-installed-sdk-runtime-materialization',
85
106
  status:'materialized',
86
107
  installation:authority.installation,
87
- workspaceRuntime
108
+ workspaceRuntime,
109
+ workspaceLock:{path:lockPath,document:lock}
88
110
  };
89
111
  });
90
112
  }
@@ -499,16 +499,23 @@ async function collectPackageRecords(context,{signal}={}){
499
499
  return records;
500
500
  }
501
501
 
502
- async function browserDocuments(records){
502
+ async function browserDocuments(records,entry){
503
+ let entryDocument=null;
503
504
  const documents=[];
504
505
  for(const record of records){
505
- if(path.posix.extname(record.destination).toLocaleLowerCase('en-US')!=='.html')continue;
506
+ const extension=path.posix.extname(record.destination).toLocaleLowerCase('en-US');
507
+ if(extension!=='.html'&&extension!=='.htm')continue;
506
508
  const inspected=inspectImportMapHtml(await readFile(record.source,'utf8'),{
507
509
  documentPath:record.destination
508
510
  });
509
- documents.push({path:record.destination,...copyJson(inspected)});
511
+ const document={path:record.destination,...copyJson(inspected)};
512
+ if(record.destination===entry){
513
+ entryDocument=document;
514
+ }else if(inspected.bases.length>0){
515
+ documents.push(document);
516
+ }
510
517
  }
511
- return documents;
518
+ return entryDocument===null?documents:[entryDocument,...documents];
512
519
  }
513
520
 
514
521
  async function optionalDescriptor(context){
@@ -540,7 +547,7 @@ async function inspectContext(context,{signal}={}){
540
547
  }),
541
548
  ...(context.config.adapter===undefined?{}:{adapter:context.config.adapter}),
542
549
  descriptor:await optionalDescriptor(context),
543
- browserDocuments:await browserDocuments(records),
550
+ browserDocuments:await browserDocuments(records,context.config.entry),
544
551
  files:records.map(record=>record.destination),
545
552
  output:path.relative(context.workspaceRoot,context.outputRoot).split(path.sep).join('/')
546
553
  };
package/src/scaffold.mjs CHANGED
@@ -430,6 +430,25 @@ export async function initWorkspace({
430
430
  sdkDeclaration
431
431
  );
432
432
  const writtenFiles=await writeMissingFiles(resolvedRoot,template.files,{signal,onEvent});
433
+ if(workspaceMode==='external'&&writtenFiles.skippedFiles.includes('arcane.lock.json')){
434
+ throwIfAborted(signal);
435
+ await assertNoLinkedAncestors(resolvedRoot,'arcane.lock.json');
436
+ const lockPath=path.join(resolvedRoot,'arcane.lock.json');
437
+ const lockInfo=await lstat(lockPath);
438
+ if(lockInfo.isSymbolicLink()||!lockInfo.isFile()){
439
+ fail('Existing arcane.lock.json must be a real file.');
440
+ }
441
+ await writeFile(
442
+ lockPath,
443
+ template.files.get('arcane.lock.json'),
444
+ 'utf8'
445
+ );
446
+ writtenFiles.skippedFiles=writtenFiles.skippedFiles.filter(
447
+ relative=>relative!=='arcane.lock.json'
448
+ );
449
+ writtenFiles.replacedFiles=['arcane.lock.json'];
450
+ await emit(onEvent,{type:'scaffold.file.replaced',path:'arcane.lock.json'});
451
+ }
433
452
  const packageUpdated=await applyPackageMerge(resolvedRoot,packagePlan,{signal,onEvent});
434
453
  const workspaceRuntime=workspaceMode==='external'
435
454
  ?await materializeWorkspaceRuntimeContent({
@@ -1,4 +1,10 @@
1
- import {SDK_NAME,SDK_VERSION} from '../constants.mjs';
1
+ import {
2
+ ARCANE_PROTOCOL,
3
+ CLI_EVENT_PROTOCOL,
4
+ SDK_NAME,
5
+ SDK_VERSION,
6
+ TARGET_ADAPTER_PROTOCOL
7
+ } from '../constants.mjs';
2
8
 
3
9
  const NPM_PACKAGE_NAME_PATTERN=/^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/u;
4
10
  const LOCAL_TARBALL_PATTERN=/^file:.+\.tgz$/iu;
@@ -22,6 +28,32 @@ function titleCase(appId){
22
28
  .join(' ');
23
29
  }
24
30
 
31
+ export function createWorkspaceLockDocument({
32
+ dependencyName=SDK_NAME,
33
+ packageName=SDK_NAME,
34
+ packageVersion=SDK_VERSION,
35
+ packageSource=`node_modules/${dependencyName}`
36
+ }={}){
37
+ if(packageName!==SDK_NAME||typeof packageVersion!=='string'
38
+ ||!/^[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?$/u.test(packageVersion)
39
+ ||typeof dependencyName!=='string'||dependencyName.length>214
40
+ ||!NPM_PACKAGE_NAME_PATTERN.test(dependencyName)
41
+ ||packageSource!==`node_modules/${dependencyName}`){
42
+ throw new Error('Invalid workspace SDK lock authority.');
43
+ }
44
+ return {
45
+ schemaVersion:1,
46
+ sdk:{name:packageName,version:packageVersion},
47
+ runtime:{root:`${packageSource}/runtime`},
48
+ sdkBrowserRuntime:{root:`${packageSource}/browser-runtime`},
49
+ protocols:{
50
+ arcane:ARCANE_PROTOCOL,
51
+ cliEvents:CLI_EVENT_PROTOCOL,
52
+ targetAdapter:TARGET_ADAPTER_PROTOCOL
53
+ }
54
+ };
55
+ }
56
+
25
57
  export function workspaceTemplate({
26
58
  appId,
27
59
  displayName,
@@ -212,6 +244,10 @@ Every browser release also carries Arcane OS licensing material under \`licenses
212
244
  ]
213
245
  }
214
246
  }));
247
+ files.set('arcane.lock.json',json(createWorkspaceLockDocument({
248
+ dependencyName:sdkDependencyName,
249
+ packageSource:sdkPackageSource
250
+ })));
215
251
  files.set(`apps/${appId}/arcane-app.json`,json({
216
252
  schemaVersion:2,
217
253
  id:appId,
package/src/toolchain.mjs CHANGED
@@ -314,7 +314,9 @@ async function refreshPreparedImportMap(prepared,{signal,onEvent,workspaceOperat
314
314
  appId:prepared.appId,
315
315
  appRoot:prepared.appRoot,
316
316
  entry:manifest.entry,
317
- documents:inspected.browserDocuments,
317
+ documents:inspected.browserDocuments.map(function selectBrowserDocumentPath(document){
318
+ return document.path;
319
+ }),
318
320
  workspaceOperationLease,
319
321
  signal,
320
322
  onEvent