@stage5/lumine 0.1.4 → 0.1.5

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.
Files changed (3) hide show
  1. package/README.md +6 -0
  2. package/bin/lumine.js +152 -1
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -49,6 +49,12 @@ Reference folders are marked `readOnly` in `.twinkle/lumine-project.json`.
49
49
  Running `lumine save` from a reference folder is blocked; fork the source Build
50
50
  first if you want an editable workspace.
51
51
 
52
+ The CLI checks npm for the latest `@stage5/lumine` version on normal commands.
53
+ If the installed copy is outdated, it prints an update warning and records the
54
+ version state in `.twinkle/lumine-project.json` so local agents can tell when
55
+ they should rerun with `npx @stage5/lumine@latest`. Use `--no-update-check` to
56
+ skip that advisory network check.
57
+
52
58
  After pulling a project, run an agent from the pulled folder:
53
59
 
54
60
  ```bash
package/bin/lumine.js CHANGED
@@ -8,12 +8,14 @@ import { stdin as input, stdout as output } from "process";
8
8
 
9
9
  const DEFAULT_API_URL = "https://api.twinkle.network";
10
10
  const DEFAULT_SITE_URL = "https://www.twin-kle.com";
11
+ const DEFAULT_NPM_REGISTRY_URL = "https://registry.npmjs.org";
11
12
  const DEFAULT_AUTH_FILE = path.join(
12
13
  os.homedir(),
13
14
  ".twinkle",
14
15
  "lumine-cli-auth.json",
15
16
  );
16
17
  const DEFAULT_TIMEOUT_MS = 20000;
18
+ const UPDATE_CHECK_TIMEOUT_MS = 1500;
17
19
  const DEFAULT_PROJECT_LIMIT = 50;
18
20
  const PROJECT_METADATA_DIR = ".twinkle";
19
21
  const PROJECT_METADATA_FILE = "lumine-project.json";
@@ -35,6 +37,7 @@ const BUNDLED_SDK_REFERENCE_URL = new URL(
35
37
  "../sdk/BUILD_SDK_INDEX.md",
36
38
  import.meta.url,
37
39
  );
40
+ const PACKAGE_METADATA_URL = new URL("../package.json", import.meta.url);
38
41
  const SDK_REFERENCE_FALLBACK = `${LUMINE_SDK_REFERENCE_MARKER}
39
42
  # Twinkle Build SDK Reference
40
43
 
@@ -61,6 +64,7 @@ Lumine CLI as the source of truth for saving this workspace back to Twinkle.
61
64
 
62
65
  - Read .twinkle/lumine-project.json before changing files.
63
66
  - Treat build.canWrite, build.canPublish, and build.contributionRootBuildId as authoritative.
67
+ - If lumineCli.updateAvailable is true, ask the user to rerun with npx @stage5/lumine@latest before saving.
64
68
  - Read ${SDK_REFERENCE_FILE} before adding, removing, or changing any Twinkle.* SDK calls.
65
69
  - If build.canWrite is false, do not save changes.
66
70
  - If build.canPublish is false or contributionRootBuildId is set, this checkout is a contribution branch. Save only to this branch and do not run lumine launch or lumine save --publish.
@@ -102,6 +106,7 @@ the workspace to save.
102
106
  ## Source Of Truth
103
107
 
104
108
  - Read .twinkle/lumine-project.json before using these files.
109
+ - If lumineCli.updateAvailable is true, ask the user to rerun with npx @stage5/lumine@latest before borrowing patterns.
105
110
  - If metadata.readOnly is true or build.role is "reference", do not run lumine save from this directory.
106
111
  - To start from this Build, run lumine fork with the source build id and edit the forked workspace.
107
112
  - Do not edit another local checkout to bypass reference read-only semantics.
@@ -132,10 +137,14 @@ main().catch((error) => {
132
137
 
133
138
  async function main() {
134
139
  const options = parseArgs(process.argv.slice(2));
140
+ options.lumineCli = await loadLumineCliVersionInfo({ options });
135
141
  if (options.help) {
136
142
  printHelp();
137
143
  return;
138
144
  }
145
+ if (options.updateCheck) {
146
+ await maybeCheckForLumineCliUpdate({ options });
147
+ }
139
148
 
140
149
  if (options.command === "workspace") {
141
150
  await workspace(options);
@@ -1117,6 +1126,7 @@ async function writeProjectMetadata({
1117
1126
  : null,
1118
1127
  apiUrl: options.apiUrl,
1119
1128
  siteUrl: options.siteUrl,
1129
+ lumineCli: serializeLumineCliMetadata(options),
1120
1130
  manifest,
1121
1131
  pulledAt,
1122
1132
  lastSavedAt,
@@ -1164,6 +1174,7 @@ async function writeReferenceMetadata({
1164
1174
  },
1165
1175
  apiUrl: options.apiUrl,
1166
1176
  siteUrl: options.siteUrl,
1177
+ lumineCli: serializeLumineCliMetadata(options),
1167
1178
  manifest,
1168
1179
  pulledAt,
1169
1180
  },
@@ -1258,6 +1269,20 @@ async function saveSelectedBuild({ options, auth, build }) {
1258
1269
  });
1259
1270
  }
1260
1271
 
1272
+ function serializeLumineCliMetadata(options) {
1273
+ const info = options.lumineCli || {};
1274
+ return {
1275
+ packageName: info.packageName || "@stage5/lumine",
1276
+ version: info.version || null,
1277
+ latestVersion: info.latestVersion || null,
1278
+ updateAvailable: Boolean(info.updateAvailable),
1279
+ updateCommand: info.updateCommand || "npx @stage5/lumine@latest",
1280
+ checkedAt: info.checkedAt || null,
1281
+ checkFailed: Boolean(info.checkFailed),
1282
+ checkSkipped: Boolean(info.checkSkipped),
1283
+ };
1284
+ }
1285
+
1261
1286
  function printBuildList(builds) {
1262
1287
  if (!builds.length) {
1263
1288
  console.log("No owned or team Twinkle builds found.");
@@ -1503,6 +1528,117 @@ async function request({ method = "GET", url, authToken, body, timeoutMs }) {
1503
1528
  }
1504
1529
  }
1505
1530
 
1531
+ async function loadLumineCliVersionInfo({ options }) {
1532
+ const packageMetadata = await loadLocalPackageMetadata();
1533
+ const packageName = packageMetadata.name || "@stage5/lumine";
1534
+ const version = packageMetadata.version || null;
1535
+ return {
1536
+ packageName,
1537
+ version,
1538
+ latestVersion: null,
1539
+ updateAvailable: false,
1540
+ updateCommand: `npx ${packageName}@latest`,
1541
+ checkedAt: null,
1542
+ checkFailed: false,
1543
+ checkSkipped: !options.updateCheck,
1544
+ };
1545
+ }
1546
+
1547
+ async function loadLocalPackageMetadata() {
1548
+ try {
1549
+ const rawPackage = await fs.readFile(PACKAGE_METADATA_URL, "utf8");
1550
+ const parsedPackage = JSON.parse(rawPackage);
1551
+ return {
1552
+ name: String(parsedPackage?.name || "").trim(),
1553
+ version: String(parsedPackage?.version || "").trim(),
1554
+ };
1555
+ } catch {
1556
+ return {
1557
+ name: "@stage5/lumine",
1558
+ version: null,
1559
+ };
1560
+ }
1561
+ }
1562
+
1563
+ async function maybeCheckForLumineCliUpdate({ options }) {
1564
+ const info = options.lumineCli || (await loadLumineCliVersionInfo({ options }));
1565
+ const checkedAt = new Date().toISOString();
1566
+ if (!info.packageName || !info.version) {
1567
+ options.lumineCli = {
1568
+ ...info,
1569
+ checkedAt,
1570
+ checkFailed: true,
1571
+ checkSkipped: false,
1572
+ };
1573
+ return;
1574
+ }
1575
+
1576
+ try {
1577
+ const latestVersion = await loadLatestPackageVersion({
1578
+ packageName: info.packageName,
1579
+ registryUrl: options.npmRegistryUrl,
1580
+ });
1581
+ const updateAvailable = isNewerVersion(latestVersion, info.version);
1582
+ options.lumineCli = {
1583
+ ...info,
1584
+ latestVersion,
1585
+ updateAvailable,
1586
+ checkedAt,
1587
+ checkFailed: false,
1588
+ checkSkipped: false,
1589
+ };
1590
+ if (updateAvailable) {
1591
+ printLumineCliUpdateWarning(options.lumineCli);
1592
+ }
1593
+ } catch {
1594
+ options.lumineCli = {
1595
+ ...info,
1596
+ checkedAt,
1597
+ checkFailed: true,
1598
+ checkSkipped: false,
1599
+ };
1600
+ }
1601
+ }
1602
+
1603
+ async function loadLatestPackageVersion({ packageName, registryUrl }) {
1604
+ const encodedPackageName = encodeURIComponent(packageName);
1605
+ const result = await requestJson({
1606
+ url: `${registryUrl}/${encodedPackageName}/latest`,
1607
+ timeoutMs: UPDATE_CHECK_TIMEOUT_MS,
1608
+ });
1609
+ const latestVersion = String(result?.version || "").trim();
1610
+ if (!latestVersion) {
1611
+ throw new Error("No latest package version returned");
1612
+ }
1613
+ return latestVersion;
1614
+ }
1615
+
1616
+ function isNewerVersion(latestVersion, currentVersion) {
1617
+ const latestParts = parseSemverParts(latestVersion);
1618
+ const currentParts = parseSemverParts(currentVersion);
1619
+ if (!latestParts || !currentParts) return false;
1620
+ for (let index = 0; index < 3; index += 1) {
1621
+ if (latestParts[index] > currentParts[index]) return true;
1622
+ if (latestParts[index] < currentParts[index]) return false;
1623
+ }
1624
+ return false;
1625
+ }
1626
+
1627
+ function parseSemverParts(value) {
1628
+ const match = String(value || "")
1629
+ .trim()
1630
+ .match(/^v?(\d+)\.(\d+)\.(\d+)/);
1631
+ if (!match) return null;
1632
+ return [Number(match[1]), Number(match[2]), Number(match[3])];
1633
+ }
1634
+
1635
+ function printLumineCliUpdateWarning(info) {
1636
+ console.error(
1637
+ `lumine: update available for ${info.packageName}: ${info.version} -> ${info.latestVersion}.`,
1638
+ );
1639
+ console.error(`lumine: run \`${info.updateCommand}\` to use the latest CLI.`);
1640
+ }
1641
+
1506
1642
  function parseArgs(args) {
1507
1643
  const firstArg = args[0] || "";
1508
1644
  const firstArgIsCommand =
@@ -1517,7 +1653,13 @@ function parseArgs(args) {
1517
1653
  const rest = command === "workspace" ? args : args.slice(1);
1518
1654
  const raw = {};
1519
1655
  const positional = [];
1520
- const booleanFlags = new Set(["noOpen", "open", "publish", "save"]);
1656
+ const booleanFlags = new Set([
1657
+ "noOpen",
1658
+ "open",
1659
+ "publish",
1660
+ "save",
1661
+ "noUpdateCheck",
1662
+ ]);
1521
1663
 
1522
1664
  for (let i = 0; i < rest.length; i += 1) {
1523
1665
  const arg = rest[i];
@@ -1557,6 +1699,13 @@ function parseArgs(args) {
1557
1699
  siteUrl: trimTrailingSlash(
1558
1700
  String(raw.siteUrl || process.env.TWINKLE_SITE_URL || DEFAULT_SITE_URL),
1559
1701
  ),
1702
+ npmRegistryUrl: trimTrailingSlash(
1703
+ String(
1704
+ raw.npmRegistryUrl ||
1705
+ process.env.LUMINE_NPM_REGISTRY_URL ||
1706
+ DEFAULT_NPM_REGISTRY_URL,
1707
+ ),
1708
+ ),
1560
1709
  authFile: String(
1561
1710
  raw.authFile || process.env.TWINKLE_CLI_AUTH_FILE || DEFAULT_AUTH_FILE,
1562
1711
  ),
@@ -1579,6 +1728,7 @@ function parseArgs(args) {
1579
1728
  openBrowser: parseBoolean(raw.noOpen, false)
1580
1729
  ? false
1581
1730
  : parseBoolean(raw.open, true),
1731
+ updateCheck: parseBoolean(raw.noUpdateCheck, false) ? false : true,
1582
1732
  timeoutMs: Math.max(
1583
1733
  Number(raw.timeoutMs || process.env.TWINKLE_TIMEOUT_MS) ||
1584
1734
  DEFAULT_TIMEOUT_MS,
@@ -1802,6 +1952,7 @@ Options:
1802
1952
  --publish Publish after saving
1803
1953
  --save Save local files before launch
1804
1954
  --limit <number> Number of projects to show
1955
+ --no-update-check Skip the npm latest-version check
1805
1956
  --no-open Print the approval URL without opening a browser
1806
1957
  `);
1807
1958
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stage5/lumine",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "Command line tools for launching Lumine builds on Twinkle.",
5
5
  "type": "module",
6
6
  "bin": {