Back to Powder Tool V600Billion
SOURCE / PINNED RELEASE

Made of little things.

Powder Tool V600Billion

Release
142767edcab8…
Author-recorded commit
6d92971effd0…
License
LICENSE
Author’s source reference
nostr://npub1fllw8kw0thjj55wds0uugcnp5kej2nfxd36eruq39d56wwz8r44q5q78wj/wss%3A%2F%2Fgit.napplet.soy%2F/powder-toy

Archive hash verified: ed7d6a8ea7083197…. The source-to-build association is the author’s claim; it has not been independently rebuilt.

source/scripts/dock.mjs
/**
 * Put the built napplet where the Hangar serves it, in a nappelin.com checkout.
 *
 *   npm run build
 *   npm run dock -- [path to nappelin.com]      (default: ../nappelin.com, or NAPPELIN_DIR)
 *
 * Copies dist/index.html and dist/.nip5a-manifest.json to
 * apps/hangar/public/napplets/powder-toy/ there, with the GPL-3.0 as
 * LICENSE.txt (the Hangar's card links it), and writes provenance.json next
 * to them: this repository and commit, what went in from upstream, and that the
 * napplet declares no roles or intents. Refuses a working tree with changes, so
 * the commit in provenance.json is the source of the bytes (--allow-dirty for a
 * local try-out marks the commit "-dirty"). Pinning is a separate, reviewed step
 * in nappelin.com:
 *   cd apps/hangar && node scripts/pin-napplet.mjs powder-toy
 */
import { execFileSync } from 'node:child_process';
import { createHash } from 'node:crypto';
import { copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
import { join, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';

import { PUBLIC_SOURCE } from './public-source.mjs';
import { upstream } from './upstream.mjs';

const SOURCE = 'https://github.com/BIMbeamFLX/powder-toy-napplet';
const repo = fileURLToPath(new URL('../', import.meta.url));
const dist = join(repo, 'dist');
const args = process.argv.slice(2);
const allowDirty = args.includes('--allow-dirty');
const nappelin = resolve(args.find((arg) => !arg.startsWith('--')) ?? process.env.NAPPELIN_DIR ?? join(repo, '..', 'nappelin.com'));
const hangar = join(nappelin, 'apps', 'hangar');
const dock = join(hangar, 'public', 'napplets', 'powder-toy');
const sha256 = (bytes) => createHash('sha256').update(bytes).digest('hex');
const git = (...command) => execFileSync('git', command, { cwd: repo, encoding: 'utf8' }).trim();

if (!existsSync(join(hangar, 'src', 'catalog.ts'))) {
  throw new Error(`${nappelin} is not a nappelin.com checkout (no apps/hangar/src/catalog.ts). Pass its path: npm run dock -- <path>`);
}
const dirty = git('status', '--porcelain') !== '';
if (dirty && !allowDirty) throw new Error('the working tree has changes; commit them first, so provenance.json names the source (or --allow-dirty)');
const commit = git('rev-parse', 'HEAD') + (dirty ? '-dirty' : '');

if (!existsSync(dist)) throw new Error('no dist/ yet: npm run wasm && npm run build');
const files = readdirSync(dist).sort();
const expected = ['.nip5a-manifest.json', 'index.html'];
if (JSON.stringify(files) !== JSON.stringify(expected)) {
  throw new Error(`dist/ should hold exactly ${expected.join(' and ')}, found: ${files.join(', ')}`);
}
const html = readFileSync(join(dist, 'index.html'));
if (html.length > 10 * 1024 * 1024) throw new Error(`index.html is ${html.length} bytes, over the 10 MB napplet limit`);
if (/<script\b[^>]*\bsrc=/i.test(html.toString('utf8'))) throw new Error('index.html still references an external script');
const manifest = JSON.parse(readFileSync(join(dist, '.nip5a-manifest.json'), 'utf8'));
const pathTag = manifest.tags.find((tag) => tag[0] === 'path' && tag[1] === '/index.html');
if (!pathTag || pathTag[2] !== sha256(html)) throw new Error('the manifest does not pin dist/index.html');

mkdirSync(dock, { recursive: true });
for (const file of expected) copyFileSync(join(dist, file), join(dock, file));
copyFileSync(join(repo, 'LICENSE'), join(dock, 'LICENSE.txt'));
writeFileSync(join(dock, 'provenance.json'), JSON.stringify({
  sha256: sha256(html),
  source: SOURCE,
  publicSource: PUBLIC_SOURCE,
  sourceCommit: commit,
  build: 'npm ci && npm run wasm && npm run build',
  upstream: {
    source: upstream.source,
    tag: upstream.tag,
    commit: upstream.commit,
    emsdk: upstream.build.emsdk,
    meson: upstream.build.meson,
    patches: upstream.build.patches,
    files: upstream.build.files,
  },
  adaptation: 'Upstream source built for the web without threads (scripts/build-wasm.sh, patches/), inlined into one file '
    + 'with a boot script: /powder on napplet storage instead of IndexedDB, the online features answered over Nostr.',
  license: upstream.license,
  archetypes: [],
  intents: [],
}, null, 2) + '\n');
console.log(`docked ${html.length} bytes (${(html.length / 1048576).toFixed(2)} MiB), sha256 ${sha256(html)}, commit ${commit}`);
console.log(`  -> ${dock}`);
console.log('pin it there: cd apps/hangar && node scripts/pin-napplet.mjs powder-toy');