@orkestrel/scaffold 0.0.62 → 0.0.64

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.
Files changed (52) hide show
  1. package/README.md +18 -103
  2. package/dist/bin/main.js +95 -27
  3. package/dist/bin/main.js.map +1 -1
  4. package/dist/host/AGENTS.md +2 -2
  5. package/dist/host/CLAUDE.md +6 -0
  6. package/dist/host/agents/orchestration.md +23 -15
  7. package/dist/host/agents/skills/enterprise-bootstrap/references/bootstrap-reference.md +16 -16
  8. package/dist/host/agents/skills/enterprise-bootstrap/references/frontend-design.md +6 -6
  9. package/dist/host/agents/skills/enterprise-bootstrap/references/utilities.md +4 -4
  10. package/dist/host/agents/skills/orkestrel-debrief/references/field-testing.md +5 -5
  11. package/dist/host/agents/skills/orkestrel-falsify/references/reconcile.md +1 -1
  12. package/dist/host/agents/skills/orkestrel-publish/SKILL.md +15 -15
  13. package/dist/host/agents/skills/orkestrel-publish/references/wave.md +43 -17
  14. package/dist/host/agents/skills/orkestrel-publish/references/window.md +41 -16
  15. package/dist/host/claude/agents/orkestrel.md +56 -56
  16. package/dist/host/claude/agents/reviewer.md +13 -0
  17. package/dist/host/claude/rules/architecture.md +51 -45
  18. package/dist/host/claude/rules/documentation.md +18 -1
  19. package/dist/host/claude/rules/portability.md +2 -0
  20. package/dist/host/claude/rules/quality.md +1 -1
  21. package/dist/host/claude/rules/tests.md +12 -11
  22. package/dist/host/claude/rules/typescript.md +5 -0
  23. package/dist/host/claude/rules/workspace.md +23 -18
  24. package/dist/host/claude/rules/writing.md +4 -0
  25. package/dist/host/codex/agents/orkestrel.toml +3 -3
  26. package/dist/host/codex/agents/reviewer.toml +4 -2
  27. package/dist/host/configs/helpers.ts +311 -2
  28. package/dist/host/configs/policy.ts +1100 -51
  29. package/dist/host/dotfiles/oxlintrc.json +72 -1
  30. package/dist/host/guides/guide.md +749 -222
  31. package/dist/host/guides/scaffold.md +472 -378
  32. package/dist/host/manifest.json +34 -33
  33. package/dist/host/scripts/codex.sh +0 -0
  34. package/dist/host/scripts/cursor.sh +0 -0
  35. package/dist/host/scripts/deps.sh +0 -0
  36. package/dist/host/scripts/ollama.sh +0 -0
  37. package/dist/host/tests/config.test.ts +1200 -16
  38. package/dist/host/tests/policy.test.ts +157 -173
  39. package/dist/host/tests/setupPolicy.ts +522 -1007
  40. package/dist/src/core/index.cjs +373 -279
  41. package/dist/src/core/index.cjs.map +1 -1
  42. package/dist/src/core/index.d.cts +130 -120
  43. package/dist/src/core/index.d.ts +130 -120
  44. package/dist/src/core/index.js +373 -278
  45. package/dist/src/core/index.js.map +1 -1
  46. package/dist/src/server/index.cjs +28 -21
  47. package/dist/src/server/index.cjs.map +1 -1
  48. package/dist/src/server/index.d.cts +38 -33
  49. package/dist/src/server/index.d.ts +38 -33
  50. package/dist/src/server/index.js +28 -21
  51. package/dist/src/server/index.js.map +1 -1
  52. package/package.json +17 -18
@@ -77,6 +77,11 @@ type Result<T, E = Error> = Success<T> | Failure<E>
77
77
  - Every public export has complete TSDoc: description, `@param`, `@returns`, and `@example` where applicable.
78
78
  - The first sentence states what the symbol does in the third person with an `-s` verb — `Creates`,
79
79
  `Returns`, `Checks whether` — and never repeats the symbol's name.
80
+ - `policy/no-malformed-summary` reads that first sentence over every doc block a top-level export
81
+ declaration follows, and refuses an opening word that is not a third-person `-s` verb and a
82
+ sentence naming the declared symbol. A word ending in `s` that the rule's stop set does not name
83
+ passes whether or not it is a verb — a plural noun such as `Files` included — so read the sentence
84
+ in review as well.
80
85
  - Describe a boolean parameter as "If `true`, …; if `false`, …", and a boolean return as
81
86
  "True if …; false otherwise".
82
87
  - Write a default as "Default: …" and a thrown error as "Thrown when …".
@@ -61,9 +61,11 @@ Define aliases in `tsconfig.json` first. `vite.config.ts` derives from `compiler
61
61
  - `configs/src/` and `configs/app/`: thin per-target wrappers, including optional
62
62
  `configs/src/*bin*` files. Shared logic remains in root configs.
63
63
  - `configs/helpers.ts`, `configs/browsers.ts`, and `configs/policy.ts`: the only permitted leaves
64
- under `configs/`. Each imports nothing from the workspace, which is what keeps it a leaf. Each
65
- `configs/src/*.config.ts` imports the root config rather than a leaf, so shared build logic stays
66
- in one place.
64
+ under `configs/`. Each imports nothing from the workspace, which is what keeps it a leaf, so no
65
+ `configs/types.ts` exists for one to import: each keeps its own types, data, and functions in its
66
+ one file, and the centralized-kind placement in `.claude/rules/architecture.md` does not reach a
67
+ leaf. Each `configs/src/*.config.ts` imports the root config rather than a leaf, so shared build
68
+ logic stays in one place.
67
69
  - Keep `configs/helpers.ts` free of any dependency a core-only workspace does not declare. It is
68
70
  vendored byte-identical to every workspace, so an import there must resolve in all of them.
69
71
  `configs/browsers.ts` exists for that reason: it imports `playwright` and
@@ -71,11 +73,14 @@ Define aliases in `tsconfig.json` first. `vite.config.ts` derives from `compiler
71
73
  - Keep `configs/policy.ts` free of imports entirely. It is the workspace's oxlint plugin, the lint
72
74
  instrument of the policy law, and it is vendored byte-identical to every workspace including a
73
75
  core-only one, so a module that imports nothing at all is the only form that resolves in all of
74
- them. Because it may import nothing, keep its own types, data, and functions in that one file: the
75
- centralized-kind placement in `.claude/rules/architecture.md` does not reach it.
76
- - When a file is vendored byte-identical, import nothing that fails to resolve in any target. Import
77
- no `@orkestrel/*` package from it: every such package is itself a target and cannot depend on
78
- itself.
76
+ them.
77
+ - When a file is vendored byte-identical, import only what resolves in every workspace: a `node:`
78
+ module, or a package `BASE_DEV_DEPENDENCIES` declares. Refuse any other `@orkestrel/*` import;
79
+ `tests/src/server/helpers.test.ts` reads every vendored JavaScript and TypeScript module against
80
+ that set. A base package resolves in its own checkout through its `exports` map to its built
81
+ `dist/` entry, so a vendored module that imports it runs there after `npm run build`; the
82
+ generated root `tsconfig.json` maps the workspace's own published specifiers to its source, so
83
+ `npm run check` there needs no build.
79
84
 
80
85
  Environment rules:
81
86
 
@@ -123,16 +128,16 @@ environment axis is one project per src/app axis × environment:
123
128
  The workspace-proof axis is cross-cutting. Each proof covers the whole workspace rather than
124
129
  one environment, so each is its own project:
125
130
 
126
- | Project | Files | Proves | Gate |
127
- | -------------- | ---------------------------- | ------------------------------------------------------------------------------------------------- | ------------------------------------- |
128
- | `policy` | `tests/policy.test.ts` | Every source file obeys the syntactic coding and placement law | `test` |
129
- | `config` | `tests/config.test.ts` | Root configuration resolves its aliases, projects, and outputs | `test` |
130
- | `setup` | `tests/setup*.test.ts` | Reusable behavior exported from the root test setup modules works as the consuming suites require | `test` |
131
- | `guides` | `tests/guides.test.ts` | Every documented API exists and every public API is documented | `test` |
132
- | `conformance` | `tests/conformance.test.ts` | Where this package drifts from the official tooling it tracks | `test` |
133
- | `distribution` | `tests/distribution.test.ts` | The packed package installs and resolves through its public exports | `prepublishOnly`; absent when private |
134
- | `integration` | `tests/integration.test.ts` | The package's features work together end to end across environments | `test` |
135
- | `service` | `tests/service/**/*.test.ts` | The live external services this package drives, driven for real | `prepublishOnly`; `test` when private |
131
+ | Project | Files | Proves | Gate |
132
+ | -------------- | ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------- |
133
+ | `policy` | `tests/policy.test.ts` | The path- and text-shaped policy laws: mirrors, suppressions, the rule map, filenames, manifest scripts, skills, and bridges | `test` |
134
+ | `config` | `tests/config.test.ts` | Root configuration resolves its aliases, projects, and outputs | `test` |
135
+ | `setup` | `tests/setup*.test.ts` | Reusable behavior exported from the root test setup modules works as the consuming suites require | `test` |
136
+ | `guides` | `tests/guides.test.ts` | Every documented API exists, every public API is documented, every compared summary, example, and pitch equals its source, and every executable fence returns what the guide says it returns | `test` |
137
+ | `conformance` | `tests/conformance.test.ts` | Where this package drifts from the official tooling it tracks | `test` |
138
+ | `distribution` | `tests/distribution.test.ts` | The packed package installs and resolves through its public exports | `prepublishOnly`; absent when private |
139
+ | `integration` | `tests/integration.test.ts` | The package's features work together end to end across environments | `test` |
140
+ | `service` | `tests/service/**/*.test.ts` | The live external services this package drives, driven for real | `prepublishOnly`; `test` when private |
136
141
 
137
142
  - Define the `setup` project only when a root file matches `tests/setup*.test.ts`, exact-case.
138
143
  Include every matching file. When registered, emit `test:setup` and run it from `test`. When no
@@ -107,6 +107,10 @@ a sample string inside a code fence or a test fixture: quote each as itself, exe
107
107
  | `blacklist`, `whitelist` | `denylist`, `allowlist` |
108
108
  | `master`, `slave` | `primary`, `replica` |
109
109
 
110
+ - `policy/no-banned-term` reads every comment and the prose sweep in `tests/setupPolicy.ts` reads
111
+ every authored Markdown file, and each matches the rows this table bans unconditionally. The rule
112
+ and the sweep leave `now`, `new`, `latest`, `once`, `since`, and `master` unmatched because those
113
+ rows carry a permitted sense, so rule a hit in one of those rows yourself.
110
114
  - Sweep case-insensitively and across inflections when checking prose against the preceding table. A
111
115
  pattern for `easy` reaches neither `Easy` nor `easier`, and a temporal `once` most often appears
112
116
  as a sentence-initial `Once`.
@@ -1,5 +1,5 @@
1
1
  name = "orkestrel"
2
- description = "Read-only @orkestrel ecosystem reconciler: turns the evidence the dispatch supplies — manifests, lockfiles, installed declarations, guides, and registry readings — into package maps, drift audits, and dependency sequencing. Collects no live state itself."
2
+ description = "Read-only Orkestrel ecosystem reconciler: turns the evidence the dispatch supplies — manifests, lockfiles, installed declarations, guides, and registry readings — into package maps, dependency sequencing, blast radius, and drift findings. Collects no live state itself, and never treats the embedded catalog as live state."
3
3
  model = "gpt-5.6-terra"
4
4
  model_reasoning_effort = "medium"
5
5
  sandbox_mode = "read-only"
@@ -17,6 +17,6 @@ readings the dispatch carries, never against memory. Where the dispatch omits th
17
17
  evidence a question needs, report that question as unknown, name the reading that would
18
18
  settle it, and stop; live collection belongs to a tool-capable lane or to the
19
19
  Orchestrator before this role is dispatched. Never edit, install, push, or spawn.
20
- Return only the requested primed map, evidence-first health audit, or
21
- dependency blast-radius and topological work-order proposal.
20
+ Return exactly one requested shape: `Map`, `Health`, or `Work order`, as the Claude
21
+ charter defines them. The embedded catalog is discovery data, never live state.
22
22
  """
@@ -1,5 +1,5 @@
1
1
  name = "reviewer"
2
- description = "Codex-side driver for the Claude `reviewer` route — design-fit review of the actual diff. Prepares a journaled launch: returns the brief content, the target path, and the resolved CLI command for the Orchestrator to write and launch, and returns the Opus verdict labeled untrusted. Reviews nothing itself and endorses nothing."
2
+ description = "Codex-side driver for the Claude `reviewer` route — design-fit review of the actual diff, holding the subjective lane by default and the objective lane when the dispatch assigns it. Prepares a journaled launch: returns the brief content, the target path, and the resolved CLI command for the Orchestrator to write and launch, and returns the Opus verdict labeled untrusted. Reviews nothing itself and endorses nothing."
3
3
  model = "gpt-5.6-terra"
4
4
  model_reasoning_effort = "low"
5
5
  sandbox_mode = "read-only"
@@ -11,7 +11,9 @@ in full; read it and follow it.
11
11
  This route pins `--permission-mode plan`.
12
12
 
13
13
  The brief names the lane the route holds, and requires the returned verdict to state
14
- which lane it held. It requires the `orkestrel-falsify` verdict shape and its single
14
+ which lane it held. The Claude charter enumerates the lenses of each lane, and a
15
+ verdict returned under the objective lane holds those lenses in full. The brief
16
+ requires the `orkestrel-falsify` verdict shape and its single
15
17
  terminal line, unless the dispatch names a different skill that fixes one; file:line
16
18
  evidence on every required change; out-of-lane questions returned as referrals rather
17
19
  than verdicts; `UNRESOLVED` rather than `CONFIRMED` on a claim whose only evidence is
@@ -1,18 +1,24 @@
1
1
  import type { Plugin, Rolldown } from 'vite'
2
2
  import { parseSync, transformWithOxc, Visitor } from 'vite'
3
3
  import { fileURLToPath } from 'node:url'
4
- import { isBuiltin } from 'node:module'
4
+ import { spawnSync } from 'node:child_process'
5
+ import { createRequire, isBuiltin } from 'node:module'
6
+ import { tmpdir } from 'node:os'
5
7
  import {
6
8
  closeSync,
7
9
  constants as FS_CONSTANTS,
8
10
  existsSync,
9
11
  fstatSync,
10
12
  lstatSync,
13
+ mkdtempSync,
11
14
  openSync,
15
+ readFileSync,
12
16
  readSync,
13
17
  realpathSync,
18
+ rmSync,
19
+ writeFileSync,
14
20
  } from 'node:fs'
15
- import { dirname, isAbsolute, relative, resolve as resolvePath, sep } from 'node:path'
21
+ import { dirname, isAbsolute, join, relative, resolve as resolvePath, sep } from 'node:path'
16
22
 
17
23
  export function hasAsciiUrlControl(value: string): boolean {
18
24
  for (const character of value) {
@@ -435,6 +441,309 @@ export function outputBoundary(output: string): Plugin {
435
441
  }
436
442
  }
437
443
 
444
+ /**
445
+ * Describes the compiler scope a face resolves to, as its declaration emit and its roll-up both
446
+ * read it.
447
+ */
448
+ export interface ProjectScope {
449
+ readonly lib: readonly string[]
450
+ readonly types: readonly string[]
451
+ readonly root: string
452
+ }
453
+
454
+ /**
455
+ * Describes the `overrideTsconfig` a declaration roll-up hands the extractor, and every option it
456
+ * may read.
457
+ */
458
+ export interface ExtractorOverride {
459
+ readonly compilerOptions: {
460
+ readonly types: readonly string[]
461
+ readonly lib: readonly string[]
462
+ readonly target: string
463
+ readonly module: string
464
+ readonly moduleResolution: string
465
+ readonly skipLibCheck: boolean
466
+ readonly strict: boolean
467
+ }
468
+ readonly files: readonly string[]
469
+ }
470
+
471
+ /**
472
+ * Lists the extractor exports a declaration roll-up dereferences, named as that package publishes
473
+ * them.
474
+ */
475
+ export interface ExtractorModule {
476
+ readonly Extractor: { readonly invoke: (config: unknown, options: unknown) => unknown }
477
+ readonly ExtractorConfig: { readonly prepare: (options: unknown) => unknown }
478
+ }
479
+
480
+ /**
481
+ * Configures one published face's declaration roll-up.
482
+ *
483
+ * @remarks
484
+ * `project` is the absolute path of that face's TypeScript project file. `types` overrides the
485
+ * `types` the extractor's own program reads, and defaults to the face's resolved `types`. `rewrite`
486
+ * receives the finished roll-up and returns what the face ships; a face that omits it ships the
487
+ * roll-up as the extractor wrote it.
488
+ */
489
+ export interface DeclarationRollupOptions {
490
+ readonly project: string
491
+ readonly types?: readonly string[]
492
+ readonly rewrite?: (content: string) => string
493
+ }
494
+
495
+ /**
496
+ * Checks whether a value is a list of strings.
497
+ *
498
+ * @param value - The value to check.
499
+ * @returns True if the value is an array whose every member is a string; false otherwise.
500
+ */
501
+ export function isStringList(value: unknown): value is readonly string[] {
502
+ return Array.isArray(value) && value.every((entry: unknown) => typeof entry === 'string')
503
+ }
504
+
505
+ /**
506
+ * Checks whether a value exposes the extractor entry a declaration roll-up calls.
507
+ *
508
+ * @param value - The loaded extractor module.
509
+ * @returns True if the value carries the `Extractor.invoke` and `ExtractorConfig.prepare` entry
510
+ * points; false otherwise.
511
+ *
512
+ * @remarks
513
+ * The guard reads each member through `Reflect.get`. A loader can hand back a module object whose
514
+ * members are reachable only through its prototype, so an own-descriptor read misses them and
515
+ * refuses a module that carries `Extractor.invoke` and `ExtractorConfig.prepare`.
516
+ */
517
+ export function isExtractorModule(value: unknown): value is ExtractorModule {
518
+ if (typeof value !== 'object' || value === null) return false
519
+ const extractor: unknown = Reflect.get(value, 'Extractor')
520
+ const configuration: unknown = Reflect.get(value, 'ExtractorConfig')
521
+ if (typeof extractor !== 'function' || typeof configuration !== 'function') return false
522
+ return (
523
+ typeof Reflect.get(extractor, 'invoke') === 'function' &&
524
+ typeof Reflect.get(configuration, 'prepare') === 'function'
525
+ )
526
+ }
527
+
528
+ /**
529
+ * Returns the standard output of the workspace TypeScript compiler run as a process.
530
+ *
531
+ * @param compiler - The absolute path of the compiler's JavaScript entry.
532
+ * @param args - The compiler arguments that follow that entry.
533
+ * @returns The compiler's standard output.
534
+ * @throws An error carrying the compiler's own output when it fails or exits non-zero.
535
+ */
536
+ export function readCompilerOutput(compiler: string, args: readonly string[]): string {
537
+ const result = spawnSync(process.execPath, [compiler, ...args], {
538
+ cwd: WORKSPACE_ROOT,
539
+ encoding: 'utf8',
540
+ })
541
+ if (result.error !== undefined || result.status !== 0) {
542
+ throw new Error(
543
+ `[orkestrel-declaration-rollup] The declaration compiler failed:\n${result.stdout ?? ''}${result.stderr ?? ''}`,
544
+ )
545
+ }
546
+ return result.stdout ?? ''
547
+ }
548
+
549
+ /**
550
+ * Parses a `tsc --showConfig` reading into the compiler scope a face's roll-up requires.
551
+ *
552
+ * @param text - The compiler's `--showConfig` output.
553
+ * @param project - The absolute path of the project file that produced that output.
554
+ * @returns That scope with `root` resolved against the project file, or `undefined` when the
555
+ * project resolves no `lib`, no `types`, or no `rootDir`.
556
+ */
557
+ export function parseProjectScope(text: string, project: string): ProjectScope | undefined {
558
+ try {
559
+ const parsed: unknown = JSON.parse(text)
560
+ if (typeof parsed !== 'object' || parsed === null) return undefined
561
+ const options: unknown = Object.getOwnPropertyDescriptor(parsed, 'compilerOptions')?.value
562
+ if (typeof options !== 'object' || options === null) return undefined
563
+ const lib: unknown = Object.getOwnPropertyDescriptor(options, 'lib')?.value
564
+ const types: unknown = Object.getOwnPropertyDescriptor(options, 'types')?.value
565
+ const root: unknown = Object.getOwnPropertyDescriptor(options, 'rootDir')?.value
566
+ if (!isStringList(lib) || !isStringList(types) || typeof root !== 'string') return undefined
567
+ return { lib, types, root: resolvePath(dirname(project), root) }
568
+ } catch {
569
+ return undefined
570
+ }
571
+ }
572
+
573
+ /**
574
+ * Builds the `overrideTsconfig` the extractor analyses one emitted face entry under.
575
+ *
576
+ * @param entry - The absolute path of the emitted declaration entry.
577
+ * @param lib - The face's own resolved `lib`.
578
+ * @param types - The `types` the extractor's program reads.
579
+ * @returns That override.
580
+ *
581
+ * @remarks
582
+ * The extractor runs its own bundled compiler engine, so the override carries the face's resolved
583
+ * `lib` and `types` and nothing else the face resolved. Passing the face's full options, a `paths`
584
+ * table, or a `typescriptCompilerFolder` leaves that engine unable to follow a symbol.
585
+ */
586
+ export function buildExtractorOverride(
587
+ entry: string,
588
+ lib: readonly string[],
589
+ types: readonly string[],
590
+ ): ExtractorOverride {
591
+ return {
592
+ compilerOptions: {
593
+ types,
594
+ lib,
595
+ target: 'ESNext',
596
+ module: 'ESNext',
597
+ moduleResolution: 'bundler',
598
+ skipLibCheck: true,
599
+ strict: true,
600
+ },
601
+ files: [entry],
602
+ }
603
+ }
604
+
605
+ /**
606
+ * Rewrites every core specifier in a roll-up to the workspace package's published name.
607
+ *
608
+ * @param content - The finished roll-up.
609
+ * @returns That roll-up with each core specifier replaced.
610
+ * @throws An error when the workspace manifest names no package.
611
+ *
612
+ * @remarks
613
+ * A browser or server face reaches core through an `@src/core` alias or a relative core path, and
614
+ * the extractor keeps either external and writes it through unchanged. Neither spelling exists in
615
+ * the published tarball, so both become the package's own root export. A core face passes no
616
+ * `rewrite` at all, because that face's declarations quote both spellings as documentation.
617
+ */
618
+ export function rewriteCoreSpecifier(content: string): string {
619
+ const name = packageManifestName(WORKSPACE_ROOT)
620
+ if (name === undefined) {
621
+ throw new Error('[orkestrel-declaration-rollup] The workspace manifest names no package')
622
+ }
623
+ return content.replaceAll(/(?:\.\.\/)+core\/index\.[jt]s/g, name).replaceAll('@src/core', name)
624
+ }
625
+
626
+ /**
627
+ * Rolls one published face's declarations into the single file that face ships.
628
+ *
629
+ * @param options - The face's roll-up options.
630
+ * @returns The Vite plugin that performs that roll-up.
631
+ *
632
+ * @remarks
633
+ * At `closeBundle`, after Vite has written every format of the face, the plugin emits the face's
634
+ * declarations with the workspace compiler run as a process into a scratch directory made fresh
635
+ * under the host temporary directory, hands the emitted entry to the extractor's own engine,
636
+ * applies `rewrite`, and removes the scratch directory. Keeping the scratch outside the face's own
637
+ * output means its unconditional removal can never take a sibling the face already published, such
638
+ * as its own `declarations` folder. The extractor is a publishing workspace's development
639
+ * dependency, so this vendored leaf defers loading it to that hook: a workspace that publishes no
640
+ * library installs no extractor, never reaches the hook, and never resolves the package at build
641
+ * or at check.
642
+ */
643
+ export function declarationRollup(options: DeclarationRollupOptions): Plugin {
644
+ let output = WORKSPACE_ROOT
645
+ let source = ''
646
+ let build = false
647
+ return {
648
+ name: 'orkestrel-declaration-rollup',
649
+ configResolved(config) {
650
+ build = config.command === 'build'
651
+ output = resolvePath(config.root, config.build.outDir)
652
+ source =
653
+ config.build.lib !== false && typeof config.build.lib.entry === 'string'
654
+ ? config.build.lib.entry
655
+ : ''
656
+ },
657
+ closeBundle() {
658
+ if (!build) return
659
+ const declaration = source.replace(/\.tsx?$/, '.d.ts')
660
+ if (declaration === source) {
661
+ throw new Error(
662
+ '[orkestrel-declaration-rollup] The face must build one TypeScript library entry',
663
+ )
664
+ }
665
+ const load = createRequire(import.meta.url)
666
+ const compiler = load.resolve('typescript/bin/tsc')
667
+ const scope = parseProjectScope(
668
+ readCompilerOutput(compiler, ['--showConfig', '-p', options.project]),
669
+ options.project,
670
+ )
671
+ if (scope === undefined) {
672
+ throw new Error(
673
+ '[orkestrel-declaration-rollup] The face project must resolve its lib, types, and root',
674
+ )
675
+ }
676
+ const scratch = mkdtempSync(join(tmpdir(), 'orkestrel-declarations-'))
677
+ const rollup = resolvePath(output, 'index.d.ts')
678
+ try {
679
+ readCompilerOutput(compiler, [
680
+ '-p',
681
+ options.project,
682
+ '--declaration',
683
+ '--emitDeclarationOnly',
684
+ '--noEmit',
685
+ 'false',
686
+ '--outDir',
687
+ scratch,
688
+ ])
689
+ const entry = resolvePath(scratch, relative(scope.root, declaration))
690
+ // A literal `import()` of the extractor reddens `tsc` in a workspace that does not
691
+ // install it, and a variable specifier reddens `import/no-dynamic-require`, so the
692
+ // literal `createRequire` call is the form that clears every gate in an app-only
693
+ // workspace.
694
+ const loaded: unknown = load('@microsoft/api-extractor')
695
+ if (!isExtractorModule(loaded)) {
696
+ throw new Error(
697
+ '[orkestrel-declaration-rollup] The declaration extractor exposes no entry point',
698
+ )
699
+ }
700
+ const prepared: unknown = loaded.ExtractorConfig.prepare({
701
+ configObject: {
702
+ projectFolder: WORKSPACE_ROOT,
703
+ mainEntryPointFilePath: entry,
704
+ bundledPackages: [],
705
+ compiler: {
706
+ overrideTsconfig: buildExtractorOverride(
707
+ entry,
708
+ scope.lib,
709
+ options.types ?? scope.types,
710
+ ),
711
+ },
712
+ apiReport: { enabled: false },
713
+ docModel: { enabled: false },
714
+ tsdocMetadata: { enabled: false },
715
+ dtsRollup: { enabled: true, untrimmedFilePath: rollup },
716
+ messages: {
717
+ compilerMessageReporting: { default: { logLevel: 'none' } },
718
+ extractorMessageReporting: { default: { logLevel: 'none' } },
719
+ tsdocMessageReporting: { default: { logLevel: 'none' } },
720
+ },
721
+ },
722
+ configObjectFullPath: resolvePath(WORKSPACE_ROOT, 'api-extractor.json'),
723
+ packageJsonFullPath: resolvePath(WORKSPACE_ROOT, 'package.json'),
724
+ })
725
+ const outcome: unknown = loaded.Extractor.invoke(prepared, {
726
+ localBuild: true,
727
+ showVerboseMessages: false,
728
+ showDiagnostics: false,
729
+ })
730
+ const succeeded: unknown =
731
+ typeof outcome === 'object' && outcome !== null
732
+ ? Reflect.get(outcome, 'succeeded')
733
+ : undefined
734
+ if (succeeded !== true) {
735
+ throw new Error('[orkestrel-declaration-rollup] The declaration roll-up failed')
736
+ }
737
+ if (options.rewrite !== undefined) {
738
+ writeFileSync(rollup, options.rewrite(readFileSync(rollup, 'utf8')), 'utf8')
739
+ }
740
+ } finally {
741
+ rmSync(scratch, { recursive: true, force: true })
742
+ }
743
+ },
744
+ }
745
+ }
746
+
438
747
  export function decodeAssetSource(source: string): string | undefined {
439
748
  try {
440
749
  return decodeURI(source)