@orkestrel/scaffold 0.0.48 → 0.0.50

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.
File without changes
File without changes
File without changes
File without changes
@@ -15,7 +15,7 @@ import {
15
15
  import { createRequire } from 'node:module'
16
16
  import { dirname, join, resolve } from 'node:path'
17
17
  import { fileURLToPath, pathToFileURL } from 'node:url'
18
- import { build, loadConfigFromFile } from 'vite'
18
+ import { build, createServer, loadConfigFromFile } from 'vite'
19
19
  import { RuleTester } from 'oxlint/plugins-dev'
20
20
  import * as configHelpers from '../configs/helpers.js'
21
21
  import { MOCKING_RULE, NESTED_RULE, PRIVACY_RULE } from '../configs/policy.js'
@@ -152,11 +152,10 @@ describe('root configuration', () => {
152
152
  pool: 'threads',
153
153
  setup: ['./tests/setup.ts'],
154
154
  })
155
- // A row that is a configuration rather than a factory. A workspace with a
156
- // browser application emits one, because that factory refuses overrides and
157
- // so is not a value Vitest may call. It is required here, in a workspace that
158
- // has no browser application, so this proof exercises that resolution
159
- // wherever it runs instead of only where the shape happens to occur.
155
+ // A row that is a configuration rather than a factory. Every generated
156
+ // workspace registers the factory itself, so this shape is required here
157
+ // rather than observed: the proof exercises that resolution wherever it runs
158
+ // instead of only where a hand-written configuration happens to produce it.
160
159
  expected.set('concrete', { include: 'tests/concrete.test.ts', setup: ['./tests/setup.ts'] })
161
160
 
162
161
  const projects = configuration.test?.projects
@@ -297,6 +296,28 @@ describe('root configuration', () => {
297
296
  }).toThrow(/strictly equal/u)
298
297
  })
299
298
 
299
+ it('emits every project as a factory so the release mode reaches its proof', () => {
300
+ const projects = configuration.test?.projects
301
+ if (!Array.isArray(projects)) throw new Error('The root configuration carries no projects')
302
+ if (projects.length === 0) throw new Error('The root configuration registers no project')
303
+ // Measured: with `--mode release` on the command line, `import.meta.env.MODE` reads
304
+ // `release` inside a project Vitest calls and `test` inside an inline project
305
+ // configuration. `prepublishOnly` runs the distribution proof with `--mode release`, and
306
+ // that proof fails rather than skips only when it reads `release`, so converting these
307
+ // entries to inline configurations turns the publish gate into a skip while every suite
308
+ // stays green. The control is that conversion applied to one entry.
309
+ const inline = {
310
+ test: {
311
+ name: { label: 'inline' },
312
+ include: ['tests/inline.test.ts'],
313
+ setupFiles: ['./tests/setup.ts'],
314
+ },
315
+ }
316
+ const callable = projects.concat(inline).filter((entry) => typeof entry === 'function')
317
+ for (const entry of projects) expect(callable).toContain(entry)
318
+ expect(callable).not.toContain(inline)
319
+ })
320
+
300
321
  it('requires and validates every selected target wrapper', async () => {
301
322
  const required: string[] = []
302
323
  for (const axis of ['src', 'app']) {
@@ -513,6 +534,108 @@ describe('root configuration', () => {
513
534
  )
514
535
  })
515
536
 
537
+ it('keeps the committed host inventory aligned with the vendored checkout bytes', async () => {
538
+ // Run this gate against a quiescent checkout. It compares two reads and cannot
539
+ // distinguish stale committed data from a source edit made while it runs.
540
+ const packageValue: unknown = JSON.parse(readFileSync(resolve(root, 'package.json'), 'utf8'))
541
+ if (typeof packageValue !== 'object' || packageValue === null) {
542
+ throw new Error('The package manifest is not a record')
543
+ }
544
+ const scripts: unknown = Object.getOwnPropertyDescriptor(packageValue, 'scripts')?.value
545
+ if (typeof scripts !== 'object' || scripts === null) {
546
+ throw new Error('The package manifest carries no scripts')
547
+ }
548
+ const generator: unknown = Object.getOwnPropertyDescriptor(scripts, 'build:inventory')?.value
549
+ expect(generator === undefined || typeof generator === 'string').toBe(true)
550
+ if (generator === undefined) {
551
+ if (existsSync(resolve(root, 'host.json'))) {
552
+ throw new Error('A committed host inventory exists without a generator')
553
+ }
554
+ return
555
+ }
556
+ if (typeof generator !== 'string') {
557
+ throw new Error('The host inventory generator is not a script')
558
+ }
559
+ const committed = resolve(root, 'host.json')
560
+ if (!existsSync(committed)) {
561
+ throw new Error('The committed host inventory is absent at host.json')
562
+ }
563
+ const helper = resolve(root, 'src/server/helpers.ts')
564
+ if (!existsSync(helper)) {
565
+ throw new Error('The committed host inventory has no server stager')
566
+ }
567
+ const server = await createServer({
568
+ configFile: false,
569
+ root,
570
+ ...(configuration.resolve === undefined ? {} : { resolve: configuration.resolve }),
571
+ server: { middlewareMode: true },
572
+ })
573
+ try {
574
+ const loaded: unknown = await server.ssrLoadModule(helper)
575
+ if (typeof loaded !== 'object' || loaded === null) {
576
+ throw new Error('The server helper module did not load')
577
+ }
578
+ const stage: unknown = Reflect.get(loaded, 'stageInventory')
579
+ if (typeof stage !== 'function') {
580
+ throw new Error('The server helper module exports no stageInventory function')
581
+ }
582
+ const workspace = mkdtempSync(join(root, 'host-inventory-'))
583
+ try {
584
+ const generated = join(workspace, 'host.json')
585
+ Reflect.apply(stage, undefined, [root, generated])
586
+ const generatedText = readFileSync(generated, 'utf8')
587
+ const committedText = readFileSync(committed, 'utf8')
588
+ const values: readonly unknown[] = [JSON.parse(generatedText), JSON.parse(committedText)]
589
+ const indexes: Array<Map<string, string>> = []
590
+ for (const value of values) {
591
+ if (typeof value !== 'object' || value === null) {
592
+ throw new Error('A host inventory is not a record')
593
+ }
594
+ const entries: unknown = Object.getOwnPropertyDescriptor(value, 'entries')?.value
595
+ if (!Array.isArray(entries)) throw new Error('A host inventory carries no entry list')
596
+ const index = new Map<string, string>()
597
+ for (const entry of entries) {
598
+ if (typeof entry !== 'object' || entry === null) {
599
+ throw new Error('A host inventory carries a malformed entry')
600
+ }
601
+ const destination: unknown = Object.getOwnPropertyDescriptor(
602
+ entry,
603
+ 'destination',
604
+ )?.value
605
+ const digest: unknown = Object.getOwnPropertyDescriptor(entry, 'digest')?.value
606
+ if (typeof destination !== 'string' || typeof digest !== 'string') {
607
+ throw new Error('A host inventory entry carries no destination digest')
608
+ }
609
+ index.set(destination, digest)
610
+ }
611
+ indexes.push(index)
612
+ }
613
+ const generatedIndex = indexes[0]
614
+ const committedIndex = indexes[1]
615
+ if (generatedIndex === undefined || committedIndex === undefined) {
616
+ throw new Error('The host inventories were not indexed')
617
+ }
618
+ if (generatedText !== committedText) {
619
+ const stale: string[] = []
620
+ for (const [destination, digest] of generatedIndex) {
621
+ if (committedIndex.get(destination) !== digest) stale.push(destination)
622
+ }
623
+ for (const destination of committedIndex.keys()) {
624
+ if (!generatedIndex.has(destination)) stale.push(destination)
625
+ }
626
+ throw new Error(
627
+ `The committed host inventory is stale at ${stale.length > 0 ? stale.sort().join(', ') : 'host.json'}`,
628
+ )
629
+ }
630
+ console.info(`host-inventory: entries=${generatedIndex.size}`)
631
+ } finally {
632
+ rmSync(workspace, { recursive: true, force: true })
633
+ }
634
+ } finally {
635
+ await server.close()
636
+ }
637
+ })
638
+
516
639
  it('keeps policy rules active across every linted workspace path', () => {
517
640
  const parsed: unknown = JSON.parse(readFileSync(resolve(root, '.oxlintrc.json'), 'utf8'))
518
641
  expect(inspectPolicyConfiguration(parsed)).toEqual([])