scenri 0.8.2 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +107 -0
- package/README.md +2 -0
- package/dist/chunk-4NQKRVO2.js +55 -0
- package/dist/chunk-HLMR33DT.js +246 -0
- package/dist/chunk-OJAG3FRX.js +1940 -0
- package/dist/chunk-PAIAXRAC.js +20 -0
- package/dist/chunk-QDODTMSN.js +62 -0
- package/dist/cli-BVPVS4V5.js +376 -0
- package/dist/index.js +23 -2
- package/dist/install-V6DAA7TT.js +4 -0
- package/dist/{launcher-DIZ4MQRF.js → launcher-IVFIYHPN.js} +6 -2
- package/dist/offer-IVTNHIDJ.js +47 -0
- package/dist/paths-36Y73GGY.js +3 -0
- package/dist/refresh-7HXPUHLQ.js +91 -0
- package/dist/serve.js +434 -2186
- package/dist/src-CSR34EBZ.js +3 -0
- package/launcher/Scenri.icns +0 -0
- package/launcher/launch.mjs +144 -0
- package/launcher/scenri.ico +0 -0
- package/launcher/starting.html +78 -0
- package/package.json +2 -1
- package/studio-dist/assets/index-DSSGRUMi.js +104 -0
- package/studio-dist/assets/{index-DwfWp4jH.css → index-DfAHZRYM.css} +1 -1
- package/studio-dist/index.html +2 -2
- package/studio-dist/assets/index-9Y9YnJq6.js +0 -103
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { ASPECT_TOLERANCE, BUDGET_EXHAUSTED, EDIT_REFERENCE_ROLE_DIRECTIVE, REFERENCE_ROLE_DIRECTIVE, SCHEMA_VERSION, STEM_MIN, SchemaTooNewError, SpendCapError, TRIGRAM_MIN, budgetSize, createCatalogStore, createCore, createStore, defaultHome, fold, ftsMatch, matchesQuery, ratioLabel, searchTerms, termMatches, uniqueProjectSlug, uniqueSetSlug, uniqueSlug } from './chunk-OJAG3FRX.js';
|
|
2
|
+
//# sourceMappingURL=src-CSR34EBZ.js.map
|
|
3
|
+
//# sourceMappingURL=src-CSR34EBZ.js.map
|
|
Binary file
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Scenri desktop bootstrap, v1. A copy of this file lives in
|
|
4
|
+
* ~/.scenri/launcher and is what the Desktop icon runs. It stays small and on
|
|
5
|
+
* node builtins because copies of it live on Desktops indefinitely: it finds
|
|
6
|
+
* the newest valid version under the recorded home and hands off to
|
|
7
|
+
* `node <entry> open`, which holds every decision and is versioned with the
|
|
8
|
+
* app. Change the hand-off and bump the schema in packages/cli/src/desktop;
|
|
9
|
+
* the next start rewrites this copy.
|
|
10
|
+
*/
|
|
11
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
12
|
+
import { appendFileSync, closeSync, existsSync, mkdirSync, openSync, readdirSync, readFileSync } from 'node:fs';
|
|
13
|
+
import { homedir } from 'node:os';
|
|
14
|
+
import { dirname, join } from 'node:path';
|
|
15
|
+
import { fileURLToPath } from 'node:url';
|
|
16
|
+
|
|
17
|
+
const SCHEMA = 1;
|
|
18
|
+
const PKG = 'scenri';
|
|
19
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
20
|
+
|
|
21
|
+
const record = readJson(join(here, 'launcher.json'));
|
|
22
|
+
const home = typeof record?.home === 'string' && record.home ? record.home : join(homedir(), '.scenri');
|
|
23
|
+
const logPath = join(home, 'logs', 'launcher.log');
|
|
24
|
+
|
|
25
|
+
log(`bootstrap v${SCHEMA}: invoked from ${here}`);
|
|
26
|
+
const version = newestVersion(home);
|
|
27
|
+
if (!version) {
|
|
28
|
+
log(`bootstrap v${SCHEMA}: no valid version under ${join(home, 'app', 'versions')}`);
|
|
29
|
+
dialog("Scenri's app files are missing. Open a terminal and run: npx scenri");
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
const entry = join(home, 'app', 'versions', version, 'node_modules', PKG, 'dist', 'index.js');
|
|
33
|
+
const env = { ...process.env, ...(record?.env && typeof record.env === 'object' ? record.env : {}), SCENRI_HOME: home };
|
|
34
|
+
let fd = null;
|
|
35
|
+
try {
|
|
36
|
+
mkdirSync(dirname(logPath), { recursive: true });
|
|
37
|
+
fd = openSync(logPath, 'a');
|
|
38
|
+
} catch {
|
|
39
|
+
/* no log: launch anyway */
|
|
40
|
+
}
|
|
41
|
+
// On macOS the bundle's process is what the Dock bounces, so it stays alive
|
|
42
|
+
// until \`open\` has put something in the browser. On Windows the shortcut's
|
|
43
|
+
// minimised console lives as long as this process, so hand off and leave.
|
|
44
|
+
const wait = process.platform === 'darwin';
|
|
45
|
+
const child = spawn(process.execPath, [entry, 'open'], {
|
|
46
|
+
detached: !wait,
|
|
47
|
+
stdio: ['ignore', fd ?? 'ignore', fd ?? 'ignore'],
|
|
48
|
+
windowsHide: true,
|
|
49
|
+
env,
|
|
50
|
+
});
|
|
51
|
+
if (fd !== null) closeSync(fd);
|
|
52
|
+
log(`bootstrap v${SCHEMA}: handing off to ${entry} (pid ${child.pid ?? 'unknown'})`);
|
|
53
|
+
if (wait) {
|
|
54
|
+
child.on('exit', (code) => process.exit(code ?? 1));
|
|
55
|
+
} else {
|
|
56
|
+
child.unref();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ---- helpers, duplicated from the package on purpose: this file stands alone
|
|
60
|
+
|
|
61
|
+
function readJson(path) {
|
|
62
|
+
try {
|
|
63
|
+
return JSON.parse(readFileSync(path, 'utf8'));
|
|
64
|
+
} catch {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function log(line) {
|
|
70
|
+
try {
|
|
71
|
+
mkdirSync(dirname(logPath), { recursive: true });
|
|
72
|
+
appendFileSync(logPath, `${new Date().toISOString()} ${line}\n`);
|
|
73
|
+
} catch {
|
|
74
|
+
/* a lost line, never a failed launch */
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** The same rule as the updater's versionsDir: name matches, entry exists. */
|
|
79
|
+
function newestVersion(dataHome) {
|
|
80
|
+
const versions = join(dataHome, 'app', 'versions');
|
|
81
|
+
let names = [];
|
|
82
|
+
try {
|
|
83
|
+
names = readdirSync(versions);
|
|
84
|
+
} catch {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
const valid = names.filter((v) => {
|
|
88
|
+
const root = join(versions, v, 'node_modules', PKG);
|
|
89
|
+
const manifest = readJson(join(root, 'package.json'));
|
|
90
|
+
return manifest?.name === PKG && manifest.version === v && existsSync(join(root, 'dist', 'index.js'));
|
|
91
|
+
});
|
|
92
|
+
valid.sort(compareSemver);
|
|
93
|
+
return valid[valid.length - 1] ?? null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function compareSemver(a, b) {
|
|
97
|
+
const pa = /^(\d+)\.(\d+)\.(\d+)$/.exec(a);
|
|
98
|
+
const pb = /^(\d+)\.(\d+)\.(\d+)$/.exec(b);
|
|
99
|
+
if (!pa && !pb) return 0;
|
|
100
|
+
if (!pa) return -1;
|
|
101
|
+
if (!pb) return 1;
|
|
102
|
+
for (let i = 1; i <= 3; i++) {
|
|
103
|
+
const d = Number(pa[i]) - Number(pb[i]);
|
|
104
|
+
if (d !== 0) return d < 0 ? -1 : 1;
|
|
105
|
+
}
|
|
106
|
+
return 0;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** One native sentence when nothing else can speak; the message is an argument, never syntax. */
|
|
110
|
+
function dialog(message) {
|
|
111
|
+
log(`dialog: ${message}`);
|
|
112
|
+
if (process.env.SCENRI_NO_DIALOG === '1') return;
|
|
113
|
+
try {
|
|
114
|
+
if (process.platform === 'darwin') {
|
|
115
|
+
spawnSync(
|
|
116
|
+
'/usr/bin/osascript',
|
|
117
|
+
[
|
|
118
|
+
'-e',
|
|
119
|
+
'on run argv',
|
|
120
|
+
'-e',
|
|
121
|
+
'display dialog (item 1 of argv) with title "Scenri" buttons {"OK"} default button 1 with icon stop',
|
|
122
|
+
'-e',
|
|
123
|
+
'end run',
|
|
124
|
+
'--',
|
|
125
|
+
message,
|
|
126
|
+
],
|
|
127
|
+
{ stdio: 'ignore' },
|
|
128
|
+
);
|
|
129
|
+
} else if (process.platform === 'win32') {
|
|
130
|
+
spawnSync(
|
|
131
|
+
'powershell.exe',
|
|
132
|
+
[
|
|
133
|
+
'-NoProfile',
|
|
134
|
+
'-NonInteractive',
|
|
135
|
+
'-Command',
|
|
136
|
+
"Add-Type -AssemblyName System.Windows.Forms | Out-Null; [System.Windows.Forms.MessageBox]::Show($env:SCENRI_MESSAGE, 'Scenri') | Out-Null",
|
|
137
|
+
],
|
|
138
|
+
{ stdio: 'ignore', windowsHide: true, env: { ...process.env, SCENRI_MESSAGE: message } },
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
} catch {
|
|
142
|
+
/* the log line above is the record */
|
|
143
|
+
}
|
|
144
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>Starting Scenri</title>
|
|
7
|
+
<meta name="color-scheme" content="light dark">
|
|
8
|
+
<meta name="scenri-studio" content="">
|
|
9
|
+
<style>
|
|
10
|
+
:root { color-scheme: light dark; --bg: #ffffff; --fg: #0a0a0a; --mute: #6b6b6b; --tile: #0d0d0d; --ink: #ffffff; }
|
|
11
|
+
@media (prefers-color-scheme: dark) { :root { --bg: #0d0d0d; --fg: #f5f5f5; --mute: #8a8a8a; --tile: #1a1a1a; } }
|
|
12
|
+
html, body { height: 100%; margin: 0; }
|
|
13
|
+
body { display: grid; place-items: center; background: var(--bg); color: var(--fg); font: 15px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, system-ui, sans-serif; }
|
|
14
|
+
main { display: grid; justify-items: center; gap: 18px; text-align: center; padding: 24px; }
|
|
15
|
+
.mark { width: 72px; height: 72px; border-radius: 16px; background: var(--tile); display: grid; place-items: center; }
|
|
16
|
+
.mark svg { width: 40px; height: 40px; fill: var(--ink); }
|
|
17
|
+
h1 { margin: 0; font-size: 17px; font-weight: 600; letter-spacing: -0.01em; }
|
|
18
|
+
p { margin: 0; color: var(--mute); max-width: 34ch; }
|
|
19
|
+
.bar { width: 160px; height: 3px; border-radius: 3px; background: color-mix(in srgb, var(--fg) 12%, transparent); overflow: hidden; }
|
|
20
|
+
.bar i { display: block; width: 40%; height: 100%; border-radius: 3px; background: var(--fg); animation: slide 1.2s ease-in-out infinite; }
|
|
21
|
+
@keyframes slide { 0% { transform: translateX(-100%); } 100% { transform: translateX(400%); } }
|
|
22
|
+
[hidden] { display: none !important; }
|
|
23
|
+
code { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 13px; }
|
|
24
|
+
</style>
|
|
25
|
+
</head>
|
|
26
|
+
<body>
|
|
27
|
+
<main>
|
|
28
|
+
<div class="mark" aria-hidden="true"><svg viewBox="0 0 64 64"><title>Scenri</title><path d="M64 18.71V38.8L51.38 51.39V12.62H57.9C61.27 12.62 64 15.35 64 18.72V18.71Z"/>
|
|
29
|
+
<path d="M12.62 12.62V51.39H6.1C2.73 51.39 0 48.66 0 45.29V25.2L12.62 12.61V12.62Z"/>
|
|
30
|
+
<path d="M51.38 51.38L38.79 64H18.7C15.33 64 12.6 61.27 12.6 57.9V51.38H51.37H51.38Z"/>
|
|
31
|
+
<path d="M12.62 12.62L25.2 0H45.29C48.66 0 51.39 2.73 51.39 6.1V12.62H12.62Z"/></svg></div>
|
|
32
|
+
<h1 id="title">Starting Scenri</h1>
|
|
33
|
+
<p id="text">Scenri is starting on this computer. The studio opens here by itself.</p>
|
|
34
|
+
<div class="bar" id="bar" aria-hidden="true"><i></i></div>
|
|
35
|
+
</main>
|
|
36
|
+
<script>
|
|
37
|
+
(() => {
|
|
38
|
+
// The studio URL rides in the fragment, put there by `scenri open` after it
|
|
39
|
+
// verified the port was free or already Scenri's. Only a loopback URL is
|
|
40
|
+
// accepted, so this file can never be made to send someone elsewhere.
|
|
41
|
+
// Written into the meta by scenri open for each launch: a file URL's
|
|
42
|
+
// fragment does not survive open(1) or Start-Process. The fragment is the
|
|
43
|
+
// fallback for a page opened by hand.
|
|
44
|
+
var meta = document.querySelector('meta[name="scenri-studio"]');
|
|
45
|
+
var target = (meta && meta.getAttribute('content')) || location.hash.slice(1);
|
|
46
|
+
var ok = /^http:\/\/(127\.0\.0\.1|localhost):\d{2,5}\/$/.test(target);
|
|
47
|
+
var title = document.getElementById('title');
|
|
48
|
+
var text = document.getElementById('text');
|
|
49
|
+
var bar = document.getElementById('bar');
|
|
50
|
+
var deadline = Date.now() + 90000;
|
|
51
|
+
var done = false;
|
|
52
|
+
function go() { if (done) return; done = true; location.replace(target); }
|
|
53
|
+
function fail(message) {
|
|
54
|
+
if (done) return; done = true;
|
|
55
|
+
title.textContent = 'Scenri did not start';
|
|
56
|
+
text.innerHTML = message;
|
|
57
|
+
bar.hidden = true;
|
|
58
|
+
}
|
|
59
|
+
if (!ok) { fail('This page needs to be opened by Scenri itself. Open a terminal and run: <code>npx scenri</code>'); return; }
|
|
60
|
+
function probe() {
|
|
61
|
+
if (done) return;
|
|
62
|
+
if (Date.now() > deadline) { fail('Open a terminal and run <code>npx scenri</code> to see why.'); return; }
|
|
63
|
+
// An image load needs no CORS and works from a file: URL in every browser;
|
|
64
|
+
// the opaque fetch is the second opinion for browsers that block images
|
|
65
|
+
// from a local page.
|
|
66
|
+
var img = new Image();
|
|
67
|
+
img.onload = go;
|
|
68
|
+
img.src = target + 'favicon.ico?' + Date.now();
|
|
69
|
+
if (window.fetch) {
|
|
70
|
+
fetch(target + 'api/version', { mode: 'no-cors', cache: 'no-store' }).then(go, () => {});
|
|
71
|
+
}
|
|
72
|
+
setTimeout(probe, 500);
|
|
73
|
+
}
|
|
74
|
+
probe();
|
|
75
|
+
})();
|
|
76
|
+
</script>
|
|
77
|
+
</body>
|
|
78
|
+
</html>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "scenri",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"author": "Tony Gorb <hello@scenri.co>",
|
|
5
5
|
"license": "AGPL-3.0-only",
|
|
6
6
|
"type": "module",
|
|
@@ -53,6 +53,7 @@
|
|
|
53
53
|
"!templates/**/.DS_Store",
|
|
54
54
|
"!templates/previews/*.mjs",
|
|
55
55
|
"studio-dist",
|
|
56
|
+
"launcher",
|
|
56
57
|
"README.md",
|
|
57
58
|
"CHANGELOG.md",
|
|
58
59
|
"LICENSE",
|