zooid 0.7.4 → 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.
@@ -1,47 +1,75 @@
1
1
  import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
2
2
  import { tmpdir } from 'node:os'
3
3
  import { join } from 'node:path'
4
- import { afterEach, beforeEach, describe, expect, it } from 'vitest'
5
- import { resolveWebRoot, webSourcePackage } from './resolve.js'
4
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
5
+ import { ensureWebRoot, webSourcePackage } from './resolve.js'
6
6
 
7
7
  let root: string
8
8
  beforeEach(() => {
9
9
  root = mkdtempSync(join(tmpdir(), 'zooid-resolve-'))
10
10
  })
11
- afterEach(() => rmSync(root, { recursive: true, force: true }))
12
-
13
- describe('resolveWebRoot', () => {
14
- it('returns dist/web when present (published-package layout)', () => {
15
- const distWeb = join(root, 'dist', 'web')
16
- mkdirSync(distWeb, { recursive: true })
17
- writeFileSync(join(distWeb, 'index.html'), '<html/>')
18
- expect(resolveWebRoot(root)).toBe(distWeb)
11
+ afterEach(() => {
12
+ rmSync(root, { recursive: true, force: true })
13
+ delete process.env.ZOOID_DEV_WEB_ROOT_OVERRIDE
14
+ })
15
+
16
+ function makeSibling(rootDir: string): { cliRoot: string; webDist: string } {
17
+ const cliRoot = join(rootDir, 'zooid', 'packages', 'cli')
18
+ mkdirSync(cliRoot, { recursive: true })
19
+ const webPkg = join(rootDir, 'zoon', 'packages', 'web')
20
+ const webDist = join(webPkg, 'dist')
21
+ mkdirSync(webDist, { recursive: true })
22
+ writeFileSync(join(webPkg, 'package.json'), '{"name":"@zooid/web"}')
23
+ writeFileSync(join(webDist, 'index.html'), '<html/>')
24
+ return { cliRoot, webDist }
25
+ }
26
+
27
+ describe('ensureWebRoot', () => {
28
+ it('env override wins over everything and skips the fetch', async () => {
29
+ const { cliRoot } = makeSibling(root)
30
+ const override = join(root, 'override')
31
+ mkdirSync(override, { recursive: true })
32
+ writeFileSync(join(override, 'index.html'), '<html/>')
33
+ process.env.ZOOID_DEV_WEB_ROOT_OVERRIDE = override
34
+ const fetchBundle = vi.fn()
35
+ const out = await ensureWebRoot({ cliRoot, cacheDir: join(root, 'cache'), version: '0.1.0', fetchBundle })
36
+ expect(out).toBe(override)
37
+ expect(fetchBundle).not.toHaveBeenCalled()
19
38
  })
20
39
 
21
- it('falls back to ../../zoon/packages/web/dist when running from source', () => {
22
- const cliRoot = join(root, 'zooid', 'packages', 'cli')
40
+ it('prefers the monorepo sibling dist over the cache (contributor path)', async () => {
41
+ const { cliRoot, webDist } = makeSibling(root)
42
+ const fetchBundle = vi.fn()
43
+ const out = await ensureWebRoot({ cliRoot, cacheDir: join(root, 'cache'), version: '0.1.0', fetchBundle })
44
+ expect(out).toBe(webDist)
45
+ expect(fetchBundle).not.toHaveBeenCalled()
46
+ })
47
+
48
+ it('falls back to fetchBundle outside the monorepo (installed-package path)', async () => {
49
+ const cliRoot = join(root, 'lonely', 'cli')
23
50
  mkdirSync(cliRoot, { recursive: true })
24
- const webPkg = join(root, 'zoon', 'packages', 'web')
25
- const webDist = join(webPkg, 'dist')
26
- mkdirSync(webDist, { recursive: true })
27
- writeFileSync(join(webPkg, 'package.json'), '{"name":"@zoon/web"}')
28
- writeFileSync(join(webDist, 'index.html'), '<html/>')
29
- expect(resolveWebRoot(cliRoot)).toBe(webDist)
51
+ const cached = join(root, 'cache', '0.1.0')
52
+ const fetchBundle = vi.fn(async () => cached)
53
+ const out = await ensureWebRoot({ cliRoot, cacheDir: join(root, 'cache'), version: '0.1.0', fetchBundle })
54
+ expect(out).toBe(cached)
55
+ expect(fetchBundle).toHaveBeenCalledWith(
56
+ expect.objectContaining({ version: '0.1.0', cacheDir: join(root, 'cache') }),
57
+ )
30
58
  })
31
59
 
32
- it('throws a helpful error when neither layout has been built', () => {
33
- expect(() => resolveWebRoot(root)).toThrow(/@zoon\/web.*build/i)
60
+ it('throws an actionable error when no version pin is available outside the monorepo', async () => {
61
+ const cliRoot = join(root, 'lonely', 'cli')
62
+ mkdirSync(cliRoot, { recursive: true })
63
+ await expect(
64
+ ensureWebRoot({ cliRoot, cacheDir: join(root, 'cache'), version: undefined, fetchBundle: vi.fn() }),
65
+ ).rejects.toThrow(/webVersion/)
34
66
  })
35
67
  })
36
68
 
37
69
  describe('webSourcePackage', () => {
38
- it('returns the source package dir when sibling zoon/packages/web exists', () => {
39
- const cliRoot = join(root, 'zooid', 'packages', 'cli')
40
- mkdirSync(cliRoot, { recursive: true })
41
- const webPkg = join(root, 'zoon', 'packages', 'web')
42
- mkdirSync(webPkg, { recursive: true })
43
- writeFileSync(join(webPkg, 'package.json'), '{"name":"@zoon/web"}')
44
- expect(webSourcePackage(cliRoot)).toBe(webPkg)
70
+ it('returns the source package dir when sibling zooid-clients/packages/web exists', () => {
71
+ const { cliRoot } = makeSibling(root)
72
+ expect(webSourcePackage(cliRoot)).toBe(join(root, 'zoon', 'packages', 'web'))
45
73
  })
46
74
 
47
75
  it('returns null outside the monorepo', () => {
@@ -1,27 +1,39 @@
1
1
  import { existsSync } from 'node:fs'
2
2
  import { dirname, join, resolve } from 'node:path'
3
+ import { fetchWebBundle, type FetchWebBundleOptions } from './fetch.js'
3
4
 
4
5
  const ENV_OVERRIDE = 'ZOOID_DEV_WEB_ROOT_OVERRIDE'
5
6
 
6
- export function resolveWebRoot(cliRoot: string): string {
7
+ export interface EnsureWebRootOptions {
8
+ cliRoot: string
9
+ cacheDir: string
10
+ version: string | undefined
11
+ fetchBundle?: (opts: FetchWebBundleOptions) => Promise<string>
12
+ onProgress?: (msg: string) => void
13
+ }
14
+
15
+ export async function ensureWebRoot(opts: EnsureWebRootOptions): Promise<string> {
7
16
  const override = process.env[ENV_OVERRIDE]
8
17
  if (override && existsSync(join(override, 'index.html'))) return resolve(override)
9
18
 
10
- const published = join(cliRoot, 'dist', 'web')
11
- if (existsSync(join(published, 'index.html'))) return published
12
-
13
- const fromSource = webSourcePackage(cliRoot)
19
+ const fromSource = webSourcePackage(opts.cliRoot)
14
20
  if (fromSource && existsSync(join(fromSource, 'dist', 'index.html'))) {
15
21
  return join(fromSource, 'dist')
16
22
  }
17
23
 
18
- throw new Error(
19
- `@zoon/web build not found.\n Tried: ${published}\n Tried: ${fromSource ?? '(no monorepo source)'}\n` +
20
- `Run \`pnpm -C zoon/packages/web build\` (or set ${ENV_OVERRIDE} to a built dist).`,
21
- )
24
+ if (!opts.version) {
25
+ throw new Error(
26
+ `No @zooid/web version pin (zooid.webVersion) in the cli package.json ` +
27
+ `and no monorepo sibling build found.\n` +
28
+ `Set ${ENV_OVERRIDE} to a built dist, or run from the monorepo.`,
29
+ )
30
+ }
31
+ opts.onProgress?.(`Fetching @zooid/web ${opts.version}…`)
32
+ const fetchBundleFn = opts.fetchBundle ?? fetchWebBundle
33
+ return fetchBundleFn({ version: opts.version, cacheDir: opts.cacheDir })
22
34
  }
23
35
 
24
- // Returns the path to the in-tree @zoon/web package (zoon/packages/web) if the
36
+ // Returns the path to the in-tree @zooid/web package (zooid-clients/packages/web) if the
25
37
  // CLI is running from the monorepo source. Returns null when running from an
26
38
  // installed package (no sibling zoon/ tree).
27
39
  export function webSourcePackage(cliRoot: string): string | null {
@@ -0,0 +1,28 @@
1
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
2
+ import { tmpdir } from 'node:os'
3
+ import { dirname, join } from 'node:path'
4
+ import { createHash } from 'node:crypto'
5
+ import * as tar from 'tar'
6
+
7
+ export function makeBundleTgz(
8
+ files: Record<string, string> = {
9
+ 'package/package.json': '{"name":"@zooid/web"}',
10
+ 'package/dist/index.html': '<html>zoon</html>',
11
+ 'package/dist/assets/app.js': '// app',
12
+ },
13
+ ): { tgz: Buffer; integrity: string } {
14
+ const dir = mkdtempSync(join(tmpdir(), 'zooid-tgz-'))
15
+ try {
16
+ for (const [path, content] of Object.entries(files)) {
17
+ mkdirSync(join(dir, dirname(path)), { recursive: true })
18
+ writeFileSync(join(dir, path), content)
19
+ }
20
+ const tgz = tar
21
+ .create({ sync: true, gzip: true, cwd: dir, portable: true }, ['package'])
22
+ .read() as Buffer
23
+ const integrity = 'sha512-' + createHash('sha512').update(tgz).digest('base64')
24
+ return { tgz, integrity }
25
+ } finally {
26
+ rmSync(dir, { recursive: true, force: true })
27
+ }
28
+ }