@lampdotdev/lamp 0.36.2-nightly.24 → 0.36.2-nightly.25

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/README.md CHANGED
@@ -18,7 +18,7 @@ Mac app, `projectOf`), or any folder with an `index.html`; without one, the fold
18
18
  | `lamp new <folder> [--name "Its Name"] [--web \| --mac]` | Makes a new web game's folder from Lamp's starting files, or with `--web` a new web app's, or with `--mac` a new Mac app in Swift. |
19
19
  | `lamp init [--update]` | Puts Lamp's game runtime beside a page that's already there, changing none of the page's own files. `--update` writes again the Lamp files the folder has (`lamp.js` too), adding the game's only to a page that's a game already. |
20
20
  | `lamp unpack <file.lampapp> [folder]` | Unpacks a Lamp app file into a new folder beside it, named for it. A file carries none of Lamp's own files: a page gets them beside it as it's unpacked (the runtime for a game, lamp.js, the libraries its import map names), and a Mac app gets Package.swift, build.sh and LampSupport.swift from Lamp's starting files as it's first built (`writeMacStarter`), where it has none. One with a widget, a share extension or a 3D scene is left to Lamp's app, which writes the files they're built with. |
21
- | `lamp export [--to file] [--anyway]` | Makes the app's Lamp app file as Lamp's Share… does (`Sharing.export`, followed by `cli/lampapp.mjs`): its own files as its kind travels, Assets/, APP.md, what it declares, its credits with the app added to the chain, the licenses they name, its icon's design and lamp-app.json, never Lamp's own files. Refused when a license its credits name is gone or something in it is shaped like a key; `--anyway` sends work its credits give no license for. The shapes of keys, the names a license goes by and the sizes another Lamp opens are in `Resources/share-rules.json`, which Lamp's app reads too (`ShareRules`). |
21
+ | `lamp export [--to file] [--anyway]` | Makes the app's Lamp app file as Lamp's Share… does (`Sharing.export`, followed by `cli/lampapp.mjs`): its own files as its kind travels, Assets/, APP.md, what it declares, its credits with the app added to the chain, the licenses they name, its icon's design and lamp-app.json, never Lamp's own files, nor what the folder's `.lampignore` names. Refused when a license its credits name is gone or something in it is shaped like a key; `--anyway` sends work its credits give no license for. The shapes of keys, the names a license goes by and the sizes another Lamp opens are in `Resources/share-rules.json`, which Lamp's app reads too (`ShareRules`). |
22
22
  | `lamp dev [--open] [--lan] [--port N]` | Runs the page and says how each change went. Each save goes into every open copy of it, with the card Lamp's app puts on an app taking a change (`page.js`, left out of lamp's pictures; `lamp changing "<words>"` says on it what's changing). `--open` opens a window for it; `--lan` lets other devices on the network open it. |
23
23
  | `lamp open` | Shows the page in a window, running it in the background if it isn't running, and returns at once. |
24
24
  | `lamp open --in-lamp` | Gives a Mac app to Lamp's app (`cli/outside.mjs`), over the socket the person's own coding agent uses (`OutsideServer`, `OutsideWork`): `open_project` takes the folder on, with Lamp's review the first time, and from then on `look`, `reload` and `dev` call `apply_change`, so a change goes into the open app as Lamp's app puts it there (`LiveReload`) and its outcome is said: live, held, reopened, not_built, crashed. `use` calls `use_app`, in plain sight in the app Lamp's app has open, and `stop` calls `close_app`. A folder Lamp's app no longer has (its refusal says `taken_on: false`) is lamp's own again, said once. `lamp open --here` has lamp run its own copy again. `LAMP_APP` names another Lamp by its bundle id (a Lamp Dev instance). A Mac app `lamp new --mac` makes has the `lamp-app.json` Lamp's app takes a folder on by. |
@@ -26,7 +26,7 @@ Mac app, `projectOf`), or any folder with an `index.html`; without one, the fold
26
26
  | `lamp use key w 2` | Tries it as a person would, then looks: `click`, `type`, `key` (several at once with `w+d`), `drag`, `scroll`, `wait`, and in a game on Lamp's runtime `stick` and `button`, which hold Lamp's own controller as Lamp's app does (`window.__lampDrive`) and give it back when the person plays. Several moves with `then` between them go one after another, with one look at the end. |
27
27
  | `lamp reload [--fresh]` | Opens the page again from its files, a game from where the player is, or with `--fresh` from its beginning (`window.lampFreshStart`). |
28
28
  | `lamp stop` | Stops running it. |
29
- | `lamp share --words "…"` | Puts the page on lamp.dev: anyone with the link plays it there and can send an idea. Again, it sends the page as it is now to the same link. The app's icon goes with it (`iconOf`: a square `icon.png` or `apple-touch-icon.png` beside the page, a PNG its index.html names as its icon, or Lamp's `.lamp/icon.png`, made 512 pixels a side when bigger), for its page and the browser's tab. |
29
+ | `lamp share --words "…"` | Puts the page on lamp.dev: anyone with the link plays it there and can send an idea. Again, it sends the page as it is now to the same link. What the folder's `.lampignore` names stays out (`cli/ignore.mjs`: gitignore's simpler half, one pattern a line, `*`, `?`, `**`, a trailing `/` for a folder, no `!`; the app's own file counts for a page in Web/ too), so a page whose data is too big for a link shares from its own folder. The app's icon goes with it (`iconOf`: a square `icon.png` or `apple-touch-icon.png` beside the page, a PNG its index.html names as its icon, or Lamp's `.lamp/icon.png`, made 512 pixels a side when bigger), for its page and the browser's tab. |
30
30
  | `lamp share` (a page of Lamp's) | A web app or game in a Lamp's own folder is lamp's to share like any other page, Lamp open or not, to the link that Lamp keeps for it: lamp takes the link from the Lamp's `Wish Links.json` (by the app's instance id in `.lamp/meta.json`, `lampsKeptLink`) the first time, and tells it what went after each share or `unshare` (`keepInLamp`), so its Share Link card shows the link as it is. `ideas` and `unshare` take the link the same way. |
31
31
  | `lamp share` (any other app of Lamp's) | For a folder in Lamp's own (or a link to one) that lamp doesn't share as a page, shared by Lamp's app over its socket (`share_link`, `OutsideWork`): its Share Link card filled as it fills itself (`WishLinkCards.prepare`: the link's last words, the questions and what's new Lamp writes, the app's file, the GitHub release) and shared without its window, with `--line`, `--by`, `--asks`, `--notes`, `--no-file`, `--no-github`, `--put-on-github` in place of the card's own, and `--preview` to see it first. The link's secret stays in Lamp. Which Lamp is the one whose folder it is (`lampOwning`), or the one it was given to (`lamp open --in-lamp`, `givenTo`); any other folder is lamp's to share, whatever Lamp is running, and so is a given folder when its Lamp is too old to share for lamp. |
32
32
  | `lamp ideas [in <n> \| dismiss <n> \| reply <n> \| done <n> \| email <address\|off>] [note]` | The ideas people playing the link have sent and what's been said since, saying what became of one, and answering whoever sent it. |
package/cli/ignore.mjs ADDED
@@ -0,0 +1,56 @@
1
+ // `.lampignore`: what an app's folder keeps out of what it shares (its page on lamp.dev, and the app's file that goes
2
+ // with its link): data only its maker's computer needs, too big to share, say (a 600 MB parts library a page fetches
3
+ // from elsewhere when it isn't there). The patterns are gitignore's simpler half: one a line, `#` for a note, `*`, `?` and
4
+ // `**`, a trailing `/` for a folder, a leading `/` (or any `/` inside) for a path from the folder's top, and otherwise a
5
+ // name matched at any depth. Nothing starting with `!`: what's left out stays out.
6
+
7
+ import { readFileSync } from "node:fs";
8
+ import { join, relative, sep } from "node:path";
9
+
10
+ export const IGNORE_FILE = ".lampignore";
11
+
12
+ /** A pattern as a regular expression over a path from the folder's top ("ldraw/parts/3001.dat"). */
13
+ function compile(line) {
14
+ let pattern = line.trim();
15
+ const folder = pattern.endsWith("/");
16
+ if (folder) pattern = pattern.replace(/\/+$/, "");
17
+ const anchored = pattern.startsWith("/") || pattern.includes("/");
18
+ pattern = pattern.replace(/^\/+/, "");
19
+ if (!pattern) return null;
20
+ let source = "";
21
+ for (let i = 0; i < pattern.length; i++) {
22
+ const c = pattern[i];
23
+ if (c === "*" && pattern[i + 1] === "*") {
24
+ // `**/` matches any folders, none included; a lone `**` anything at all.
25
+ if (pattern[i + 2] === "/") { source += "(?:.*/)?"; i += 2; } else { source += ".*"; i += 1; }
26
+ } else if (c === "*") source += "[^/]*";
27
+ else if (c === "?") source += "[^/]";
28
+ else source += c.replace(/[.+^${}()|[\]\\]/g, "\\$&");
29
+ }
30
+ // What it names is left out with everything under it; a folder pattern names folders only, so it must have more after it.
31
+ return new RegExp(`^${anchored ? "" : "(?:.*/)?"}${source}${folder ? "/" : "(?:/|$)"}`);
32
+ }
33
+
34
+ /**
35
+ * Whether a path from the folder's top ("ldraw/parts/3001.dat"), a folder's when `folder` is true, is one its
36
+ * `.lampignore` leaves out; never, with none.
37
+ */
38
+ export function ignoredIn(folder) {
39
+ let lines = [];
40
+ try { lines = readFileSync(join(folder, IGNORE_FILE), "utf8").split(/\r?\n/); } catch {}
41
+ const tests = lines.map((line) => line.trim()).filter((line) => line && !line.startsWith("#") && !line.startsWith("!")).map(compile).filter(Boolean);
42
+ if (!tests.length) return () => false;
43
+ // A folder's own path is asked about with a trailing slash too, so a folder-only pattern names it (and never a file).
44
+ return (path, folder = false) => tests.some((test) => test.test(path) || (folder && test.test(`${path}/`)));
45
+ }
46
+
47
+ /**
48
+ * Whether a path from a page's top is left out by the page's `.lampignore` or by its app's (`app`, the folder above it
49
+ * when the page is in Web/, whose patterns count from its own top).
50
+ */
51
+ export function ignoredFor(root, app = root) {
52
+ const own = ignoredIn(root);
53
+ if (app === root) return own;
54
+ const outer = ignoredIn(app), under = relative(app, root).split(sep).join("/");
55
+ return (path, folder = false) => own(path, folder) || outer(under ? `${under}/${path}` : path, folder);
56
+ }
package/cli/lampapp.mjs CHANGED
@@ -9,6 +9,7 @@ import { inflateRawSync } from "node:zlib";
9
9
  import { Problem, RESOURCES, manifestOf, projectOf, stateOf } from "./state.mjs";
10
10
  import { LIMITS, nameOf, zipOf } from "./share.mjs";
11
11
  import { isID } from "../site/lib/shared-wishes.js";
12
+ import { ignoredFor, ignoredIn } from "./ignore.mjs";
12
13
 
13
14
  // What a Lamp app file may hold, written once in Resources/share-rules.json, which Lamp's app reads too (`ShareRules`):
14
15
  // the sizes another Lamp opens, the names a license goes by, and the shapes of keys a shared file never carries.
@@ -185,7 +186,7 @@ function filesUnder(dir, prefix = "", { hidden = () => false, skip = () => false
185
186
  try { entries = readdirSync(dir, { withFileTypes: true }); } catch { return found; }
186
187
  for (const entry of entries) {
187
188
  const path = prefix + entry.name;
188
- if ((entry.name.startsWith(".") && !hidden(path)) || skip(path)) continue;
189
+ if ((entry.name.startsWith(".") && !hidden(path)) || skip(path, entry.isDirectory())) continue;
189
190
  if (entry.isDirectory()) found.push(...filesUnder(join(dir, entry.name), path + "/", { hidden: () => false, skip }));
190
191
  else if (entry.isFile()) found.push(path);
191
192
  }
@@ -237,6 +238,10 @@ export function exportApp(given, { anyway = false, link = null } = {}) {
237
238
  }
238
239
 
239
240
  const files = new Map(), from = (path) => readFileSync(join(app, path));
241
+ // The folder's .lampignore covers the app's own files too (its code beside the page, Assets/), as they are from the
242
+ // folder's top; a wish's own copy in .lamp-wish/ isn't the folder's, so its rules don't reach in there.
243
+ const appIgnored = app === folder ? ignoredIn(folder) : () => false;
244
+ const keep = (path) => !appIgnored(path);
240
245
  let kind, site = false, flags = {};
241
246
  if (project.kind === "page") {
242
247
  kind = typeof manifest?.platform === "string" ? manifest.platform : project.game ? "game" : "web-app";
@@ -245,19 +250,21 @@ export function exportApp(given, { anyway = false, link = null } = {}) {
245
250
  // page is Web/ in the file and whose own files beside it go as themselves.
246
251
  // A site stays one at every export, whether its page is at the folder's top or (unpacked) in Web/.
247
252
  site = kind === "web-app" && (manifest?.site === true || !project.lamps);
248
- for (const path of filesUnder(project.root, "", { hidden: (path) => site && SERVED_HIDDEN.has(path), skip: (path) => path === "node_modules" || (!project.lamps && (wishFolder ? path === ".lamp-wish" || path === "LAMP-LICENSE.txt" : WISH_FILES.has(path))) })) {
253
+ // What the folder's .lampignore keeps to this computer stays out of the file, as it does of the page.
254
+ const ignored = ignoredFor(project.root, folder);
255
+ for (const path of filesUnder(project.root, "", { hidden: (path) => site && SERVED_HIDDEN.has(path), skip: (path, dir) => path === "node_modules" || ignored(path, dir) || (!project.lamps && (wishFolder ? path === ".lamp-wish" || path === "LAMP-LICENSE.txt" : WISH_FILES.has(path))) })) {
249
256
  if (!lamps(path)) files.set(`Web/${path}`, readFileSync(join(project.root, path)));
250
257
  }
251
258
  if (project.lamps || wishFolder) {
252
259
  // The Swift beside the page that answers its calls of the device, and a game's phone page.
253
- for (const path of filesUnder(join(app, "Sources"), "Sources/")) if (path.endsWith(".swift") && (kind === "game" ? path === "Sources/Native.swift" : !["Sources/LampSupport.swift", "Sources/LampPage.swift", "Sources/LampControllerKit.swift", "Sources/LampScreen.swift", "Sources/LampWebApp.swift"].includes(path))) files.set(path, from(path));
254
- if (kind === "game") for (const path of [...filesUnder(join(app, "Phone"), "Phone/"), ...filesUnder(join(app, "src"), "src/")]) files.set(path, from(path));
260
+ for (const path of filesUnder(join(app, "Sources"), "Sources/")) if (keep(path) && path.endsWith(".swift") && (kind === "game" ? path === "Sources/Native.swift" : !["Sources/LampSupport.swift", "Sources/LampPage.swift", "Sources/LampControllerKit.swift", "Sources/LampScreen.swift", "Sources/LampWebApp.swift"].includes(path))) files.set(path, from(path));
261
+ if (kind === "game") for (const path of [...filesUnder(join(app, "Phone"), "Phone/"), ...filesUnder(join(app, "src"), "src/")]) if (keep(path)) files.set(path, from(path));
255
262
  }
256
263
  } else {
257
264
  kind = "mac";
258
- for (const path of filesUnder(join(folder, "Sources"), "Sources/")) if (path.endsWith(".swift") && !SWIFT_LAMPS.has(basename(path))) files.set(path, from(path));
265
+ for (const path of filesUnder(join(folder, "Sources"), "Sources/")) if (keep(path) && path.endsWith(".swift") && !SWIFT_LAMPS.has(basename(path))) files.set(path, from(path));
259
266
  for (const [extra, flag] of [["Widget", "widget"], ["Share", "share"]]) {
260
- const code = filesUnder(join(folder, extra), `${extra}/`).filter((path) => path.endsWith(".swift") && !SWIFT_LAMPS.has(basename(path)));
267
+ const code = filesUnder(join(folder, extra), `${extra}/`).filter((path) => keep(path) && path.endsWith(".swift") && !SWIFT_LAMPS.has(basename(path)));
261
268
  for (const path of code) files.set(path, from(path));
262
269
  flags[flag] = code.length > 0;
263
270
  }
@@ -271,11 +278,12 @@ export function exportApp(given, { anyway = false, link = null } = {}) {
271
278
  let attached = 0;
272
279
  // A page at the folder's top with no `.lamp-wish/` keeps an Assets/ there as its own: it went with the page.
273
280
  for (const path of project.kind === "page" && !project.lamps && !wishFolder ? [] : filesUnder(join(app, "Assets"), "Assets/")) {
281
+ if (!keep(path)) continue;
274
282
  attached += statSync(join(app, path)).size;
275
283
  if (attached > MAX_ARCHIVE - 8 * 1024 * 1024) throw new Problem(`${name} is too big to share: its pictures and other files come to over ${Math.floor((MAX_ARCHIVE - 8 * 1024 * 1024) / 1_000_000)} MB`);
276
284
  files.set(path, from(path));
277
285
  }
278
- if (existsSync(join(app, "APP.md"))) files.set("APP.md", from("APP.md"));
286
+ if (keep("APP.md") && existsSync(join(app, "APP.md"))) files.set("APP.md", from("APP.md"));
279
287
  // What the app declares; the Lamp that opens the file checks each of it again as it reads it.
280
288
  let declares = false;
281
289
  try {
@@ -286,7 +294,7 @@ export function exportApp(given, { anyway = false, link = null } = {}) {
286
294
  // icon.png at the top of a page lamp made is the page's, which went with it.
287
295
  for (const icon of ["icon.json", "icon-glass.json", "icon-art.png", "icon.png"]) {
288
296
  if (icon === "icon.png" && project.kind === "page" && !project.lamps && !wishFolder) continue;
289
- if (existsSync(join(app, icon))) files.set(icon, from(icon));
297
+ if (keep(icon) && existsSync(join(app, icon))) files.set(icon, from(icon));
290
298
  }
291
299
  const releases = releasesOf(app);
292
300
  if (releases.length) files.set("releases.json", Buffer.from(JSON.stringify(releases, null, 2)));
@@ -1 +1 @@
1
- {"name":"Zqx App Name","exe":"ZqxAppName","platform":"mac","instance":"EC9221D5-9306-400D-8A12-28239D505919"}
1
+ {"instance":"99F1CF1A-EC7E-4440-8F31-0B2D19F376A5","platform":"mac","name":"Zqx App Name","exe":"ZqxAppName"}
package/cli/share.mjs CHANGED
@@ -17,6 +17,7 @@ import { LIMITS, FILE_LIMITS, PAGE_LIMITS } from "../site/lib/shared-wishes.js";
17
17
  import { ideasOf, makerOf, pageUp, publishLink, removeFile, removeIdea, removeLink, replyIdea, sendFile, siteOrigin } from "./link.mjs";
18
18
  import { exportApp, sourceLinkOf } from "./lampapp.mjs";
19
19
  import { scaled, sizeOf } from "./png.mjs";
20
+ import { ignoredFor } from "./ignore.mjs";
20
21
 
21
22
  export { siteOrigin };
22
23
 
@@ -104,10 +105,12 @@ export function zipOf(files) {
104
105
  */
105
106
  export function pageFiles(layout, { device = ANY_BROWSER } = {}) {
106
107
  const files = new Map();
108
+ // What the folder's .lampignore keeps to this computer (data too big to share, say) never goes.
109
+ const ignored = ignoredFor(layout.root, layout.folder);
107
110
  const walk = (dir, prefix) => {
108
111
  for (const entry of readdirSync(dir, { withFileTypes: true })) {
109
112
  const path = prefix + entry.name;
110
- if (entry.name.startsWith(".") || entry.name === "node_modules" || path === NOTES_FILE) continue;
113
+ if (entry.name.startsWith(".") || entry.name === "node_modules" || path === NOTES_FILE || ignored(path, entry.isDirectory())) continue;
111
114
  if (entry.isDirectory()) walk(join(dir, entry.name), path + "/");
112
115
  else if (entry.isFile()) files.set(path, readFileSync(join(dir, entry.name)));
113
116
  }
@@ -198,7 +198,9 @@ Only when the person asks: a link is public, and anyone who has it can play the
198
198
  and their email, and run `{{lamp}} account <username> --email <theirs>`; they confirm it from the email, and the link
199
199
  moves there at its next share. `{{lamp}} account` says which they're signed in as. A page that's logging errors
200
200
  isn't shared until they're put right. Again, after changes, it sends the page as it is now to the same link, with the
201
- same words unless you give new ones.
201
+ same words unless you give new ones. Files only this computer needs (data too big for a link, which the page fetches
202
+ from elsewhere when it isn't there) go in a `.lampignore` at the folder's top, gitignore's way (`ldraw/`, `*.csv`):
203
+ share and export leave them out. Never share from a copy of the folder made for it.
202
204
  - `{{lamp}} ideas` lists what people playing it have sent, each with the address of a picture of the app as they saw
203
205
  it when they kept one in: look at it before building the idea. Once one is in the game and the game shared again,
204
206
  `{{lamp}} ideas in <number>` says so, and whoever asked to hear is written to; `{{lamp}} ideas dismiss <number>` lets
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lampdotdev/lamp",
3
- "version": "0.36.2-nightly.24",
3
+ "version": "0.36.2-nightly.25",
4
4
  "description": "Runs a web app or game from its folder and puts each saved change into the open page.",
5
5
  "type": "module",
6
6
  "bin": {