@orkestrel/scaffold 0.0.63 → 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.
- package/README.md +18 -103
- package/dist/bin/main.js +95 -27
- package/dist/bin/main.js.map +1 -1
- package/dist/host/AGENTS.md +2 -2
- package/dist/host/CLAUDE.md +6 -0
- package/dist/host/agents/orchestration.md +23 -15
- package/dist/host/agents/skills/enterprise-bootstrap/references/bootstrap-reference.md +16 -16
- package/dist/host/agents/skills/enterprise-bootstrap/references/frontend-design.md +6 -6
- package/dist/host/agents/skills/enterprise-bootstrap/references/utilities.md +4 -4
- package/dist/host/agents/skills/orkestrel-debrief/references/field-testing.md +5 -5
- package/dist/host/agents/skills/orkestrel-falsify/references/reconcile.md +1 -1
- package/dist/host/agents/skills/orkestrel-publish/SKILL.md +15 -15
- package/dist/host/agents/skills/orkestrel-publish/references/wave.md +43 -17
- package/dist/host/agents/skills/orkestrel-publish/references/window.md +41 -16
- package/dist/host/claude/agents/orkestrel.md +56 -56
- package/dist/host/claude/agents/reviewer.md +13 -0
- package/dist/host/claude/rules/architecture.md +51 -45
- package/dist/host/claude/rules/documentation.md +18 -1
- package/dist/host/claude/rules/portability.md +2 -0
- package/dist/host/claude/rules/quality.md +1 -1
- package/dist/host/claude/rules/tests.md +12 -11
- package/dist/host/claude/rules/typescript.md +5 -0
- package/dist/host/claude/rules/workspace.md +23 -18
- package/dist/host/claude/rules/writing.md +4 -0
- package/dist/host/codex/agents/orkestrel.toml +3 -3
- package/dist/host/codex/agents/reviewer.toml +4 -2
- package/dist/host/configs/helpers.ts +311 -2
- package/dist/host/configs/policy.ts +1100 -51
- package/dist/host/dotfiles/oxlintrc.json +72 -1
- package/dist/host/guides/guide.md +749 -222
- package/dist/host/guides/scaffold.md +472 -378
- package/dist/host/manifest.json +34 -33
- package/dist/host/scripts/codex.sh +0 -0
- package/dist/host/scripts/cursor.sh +0 -0
- package/dist/host/scripts/deps.sh +0 -0
- package/dist/host/scripts/ollama.sh +0 -0
- package/dist/host/tests/config.test.ts +1200 -16
- package/dist/host/tests/policy.test.ts +157 -173
- package/dist/host/tests/setupPolicy.ts +522 -1007
- package/dist/src/core/index.cjs +371 -277
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +130 -120
- package/dist/src/core/index.d.ts +130 -120
- package/dist/src/core/index.js +371 -276
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +28 -21
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +38 -33
- package/dist/src/server/index.d.ts +38 -33
- package/dist/src/server/index.js +28 -21
- package/dist/src/server/index.js.map +1 -1
- package/package.json +15 -16
|
@@ -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
|
|
65
|
-
`configs/
|
|
66
|
-
in
|
|
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.
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
|
127
|
-
| -------------- | ---------------------------- |
|
|
128
|
-
| `policy` | `tests/policy.test.ts` |
|
|
129
|
-
| `config` | `tests/config.test.ts` | Root configuration resolves its aliases, projects, and outputs
|
|
130
|
-
| `setup` | `tests/setup*.test.ts` | Reusable behavior exported from the root test setup modules works as the consuming suites require
|
|
131
|
-
| `guides` | `tests/guides.test.ts` | Every documented API exists
|
|
132
|
-
| `conformance` | `tests/conformance.test.ts` | Where this package drifts from the official tooling it tracks
|
|
133
|
-
| `distribution` | `tests/distribution.test.ts` | The packed package installs and resolves through its public exports
|
|
134
|
-
| `integration` | `tests/integration.test.ts` | The package's features work together end to end across environments
|
|
135
|
-
| `service` | `tests/service/**/*.test.ts` | The live external services this package drives, driven for real
|
|
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
|
|
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
|
|
21
|
-
|
|
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.
|
|
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 {
|
|
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)
|