@things-factory/figure-ui 10.1.149 → 10.1.150

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@things-factory/figure-ui",
3
- "version": "10.1.149",
3
+ "version": "10.1.150",
4
4
  "publishConfig": {
5
5
  "access": "public",
6
6
  "@things-factory:registry": "https://registry.npmjs.org"
@@ -18,7 +18,7 @@
18
18
  "scripts": {
19
19
  "build": "npm run build:server && npm run build:client",
20
20
  "test": "FIGURE_FREEZE_COMPILED=1 node --import ./test/resolve-ts-siblings.mjs --test test/*.test.ts",
21
- "test:browser": "node --test test/browser/modeller.check.mjs test/browser/modeller-revision.check.mjs test/browser/editing.check.mjs test/browser/links.check.mjs test/browser/section.check.mjs test/browser/screen.check.mjs test/browser/product-page.check.mjs test/browser/parts.check.mjs test/browser/places-mesh.check.mjs",
21
+ "test:browser": "node --test test/browser/build.check.mjs test/browser/modeller.check.mjs test/browser/modeller-revision.check.mjs test/browser/editing.check.mjs test/browser/links.check.mjs test/browser/section.check.mjs test/browser/screen.check.mjs test/browser/product-page.check.mjs test/browser/parts.check.mjs test/browser/places-mesh.check.mjs",
22
22
  "copy:files": "copyfiles -e \"./client/**/*.{ts,js,json}\" -u 1 \"./client/**/*\" dist-client",
23
23
  "build:client": "npm run copy:files && npx tsc --p ./client/tsconfig.json",
24
24
  "build:server": "npx tsc --p ./server/tsconfig.json",
@@ -41,9 +41,9 @@
41
41
  "@operato/slot-layout-editor": "^10.29.25",
42
42
  "@operato/styles": "^10.0.0",
43
43
  "@operato/utils": "^10.0.0",
44
- "@things-factory/ai-assistant": "^10.1.149",
45
- "@things-factory/figure-service": "^10.1.149",
44
+ "@things-factory/ai-assistant": "^10.1.150",
45
+ "@things-factory/figure-service": "^10.1.150",
46
46
  "three": "^0.185.1"
47
47
  },
48
- "gitHead": "816f88a9fa6110935a3f71740458db8f7469d2e3"
48
+ "gitHead": "39c135863f5f7455dcd80b8907550a1c91a86118"
49
49
  }
@@ -0,0 +1,77 @@
1
+ /*
2
+ * Two runs building the same page at the same time, as two sessions on one machine do: each builds in a folder of its
3
+ * own, neither takes away the other's page, and each folder goes when its process ends -- by itself or stopped.
4
+ *
5
+ * Run: node --test test/browser/build.check.mjs
6
+ */
7
+ import { test } from 'node:test'
8
+ import assert from 'node:assert/strict'
9
+ import { spawn } from 'node:child_process'
10
+ import { existsSync } from 'node:fs'
11
+ import { dirname } from 'node:path'
12
+
13
+ const BUILD = new URL('./build.mjs', import.meta.url).href
14
+
15
+ /* A run: builds the page, says where and what it built, and when asked says whether its page is still as it built it. */
16
+ const RUN = `
17
+ import { buildPage } from ${JSON.stringify(BUILD)}
18
+ import { readFileSync, existsSync } from 'node:fs'
19
+ import { createHash } from 'node:crypto'
20
+ const hash = file => (existsSync(file) ? createHash('sha1').update(readFileSync(file)).digest('hex') : null)
21
+ const file = await buildPage('modeller-page.js')
22
+ const built = hash(file)
23
+ console.log(JSON.stringify({ file }))
24
+ process.stdin.on('data', () => console.log(JSON.stringify({ intact: hash(file) === built })))
25
+ `
26
+
27
+ /** Starts a run; `next()` gives the next line it says, as JSON. */
28
+ function start() {
29
+ const child = spawn(process.execPath, ['--input-type=module', '-e', RUN], { stdio: ['pipe', 'pipe', 'inherit'] })
30
+ const lines = []
31
+ const waiting = []
32
+ let rest = ''
33
+ child.stdout.on('data', chunk => {
34
+ rest += chunk
35
+ for (let at = rest.indexOf('\n'); at >= 0; at = rest.indexOf('\n')) {
36
+ const line = rest.slice(0, at)
37
+ rest = rest.slice(at + 1)
38
+ if (!line.startsWith('{')) continue
39
+ const said = JSON.parse(line)
40
+ waiting.length ? waiting.shift()(said) : lines.push(said)
41
+ }
42
+ })
43
+ const ended = new Promise(done => child.on('exit', done))
44
+ /* A run that ends before it says what was asked has failed -- its build did not finish: said at once, not waited out. */
45
+ const quit = ended.then(code => {
46
+ throw Error(`the run ended (${code}) before it said what was asked`)
47
+ })
48
+ quit.catch(() => {})
49
+ return {
50
+ child,
51
+ ended,
52
+ next: () => (lines.length ? Promise.resolve(lines.shift()) : Promise.race([new Promise(done => waiting.push(done)), quit]))
53
+ }
54
+ }
55
+
56
+ test('two runs at once build in folders of their own, keep each other\'s page, and take their folder with them', { timeout: 300000 }, async t => {
57
+ const a = start()
58
+ const b = start()
59
+ /* Whatever fails below, no run is left waiting: a run left behind keeps this file from ending. */
60
+ t.after(() => {
61
+ for (const run of [a, b]) if (run.child.exitCode === null && run.child.signalCode === null) run.child.kill('SIGKILL')
62
+ })
63
+ const [builtA, builtB] = await Promise.all([a.next(), b.next()])
64
+ assert.notEqual(dirname(builtA.file), dirname(builtB.file), 'each run builds in a folder of its own')
65
+
66
+ /* Both have built: each page is still the one its run built, whichever finished last. */
67
+ a.child.stdin.write('\n')
68
+ b.child.stdin.write('\n')
69
+ assert.deepEqual(await Promise.all([a.next(), b.next()]), [{ intact: true }, { intact: true }])
70
+
71
+ /* One ends by itself, the other is stopped: neither leaves its folder behind. */
72
+ a.child.stdin.end()
73
+ b.child.kill('SIGTERM')
74
+ await Promise.all([a.ended, b.ended])
75
+ assert.equal(existsSync(dirname(builtA.file)), false, 'the folder of the run that ended')
76
+ assert.equal(existsSync(dirname(builtB.file)), false, 'the folder of the run that was stopped')
77
+ })
@@ -7,7 +7,7 @@
7
7
  * (its browser bundle, `things-scene.mjs`).
8
8
  */
9
9
  import { execFileSync } from 'node:child_process'
10
- import { mkdirSync, rmSync, existsSync } from 'node:fs'
10
+ import { mkdtempSync, rmSync, existsSync } from 'node:fs'
11
11
  import { tmpdir } from 'node:os'
12
12
  import { dirname, join, resolve } from 'node:path'
13
13
  import { fileURLToPath } from 'node:url'
@@ -20,15 +20,30 @@ const HERE = dirname(fileURLToPath(import.meta.url))
20
20
  const PACKAGE = resolve(HERE, '../..')
21
21
  const ROOT = resolve(PACKAGE, '../..')
22
22
 
23
+ /*
24
+ * Each build is made in a folder of its own and taken away when the process ends. A folder named by the page alone was
25
+ * removed and written again by every build of that page, so runs at the same time on one machine (sessions side by
26
+ * side, a server beside a check) could take away each other's page while it was being built or served.
27
+ */
28
+ const made = []
29
+ const takeAway = () => {
30
+ for (const dir of made.splice(0)) rmSync(dir, { recursive: true, force: true })
31
+ }
32
+ process.on('exit', takeAway)
33
+ for (const signal of ['SIGINT', 'SIGTERM'])
34
+ process.once(signal, () => {
35
+ takeAway()
36
+ process.kill(process.pid, signal)
37
+ })
38
+
23
39
  /*
24
40
  * `standIns` maps a module the page would import to a file beside this one: for a module that cannot run here (the
25
41
  * assistant dock needs the whole app's tool bar), a stand-in that records what the product asked of it.
26
42
  */
27
43
  export async function buildPage(page = 'modeller-page.js', { standIns = {} } = {}) {
28
- const out = join(tmpdir(), 'figure-ui-browser-' + page.replace(/\W+/g, '-'))
44
+ const out = mkdtempSync(join(tmpdir(), 'figure-ui-browser-' + page.replace(/\W+/g, '-') + '-'))
45
+ made.push(out)
29
46
  const emitted = join(out, 'client')
30
- rmSync(out, { recursive: true, force: true })
31
- mkdirSync(out, { recursive: true })
32
47
 
33
48
  /* Types are the unit suite's and tsc's business; here only what runs. */
34
49
  execFileSync(join(ROOT, 'node_modules/.bin/tsc'), ['-p', join(PACKAGE, 'client/tsconfig.json'), '--outDir', emitted, '--noCheck', '--declaration', 'false', '--incremental', 'false'], {
@@ -80,4 +95,9 @@ export async function buildPage(page = 'modeller-page.js', { standIns = {} } = {
80
95
  return file
81
96
  }
82
97
 
83
- if (process.argv[1] === fileURLToPath(import.meta.url)) console.log(await buildPage())
98
+ /* Run on its own it prints where the page is, and leaves it there for whoever asked. */
99
+ if (process.argv[1] === fileURLToPath(import.meta.url)) {
100
+ const file = await buildPage()
101
+ made.length = 0
102
+ console.log(file)
103
+ }