@orkestrel/scaffold 0.0.72 → 0.0.73

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.
@@ -5,6 +5,7 @@ import { spawnSync } from 'node:child_process'
5
5
  import {
6
6
  existsSync,
7
7
  globSync,
8
+ lstatSync,
8
9
  mkdtempSync,
9
10
  mkdirSync,
10
11
  readdirSync,
@@ -63,6 +64,33 @@ import { describe, expect, it } from 'vitest'
63
64
 
64
65
  const root = resolve(dirname(fileURLToPath(import.meta.url)), '..')
65
66
 
67
+ // The `src` axis a declaration roll-up reads, derived from a recognized source environment that is
68
+ // a physical directory rather than from a `src` entry of any shape. A regular file, an empty
69
+ // directory, a directory holding no recognized environment, a symbolic link, and an entry this host
70
+ // refuses to inspect each leave a workspace publishing nothing, and reading the entry alone would
71
+ // put every one of them under the face proof. The inspection is caught rather than excused by
72
+ // option: `throwIfNoEntry` excuses an absent path alone and rethrows a permission refusal, and a
73
+ // throw on this line is a collection error that takes this whole file down in that target rather
74
+ // than failing one case. `isPhysicalDirectory` reads the same two facts off an `lstat` and returns
75
+ // false for every inspection error, and this reads them the same way. This is the derivation
76
+ // `targetToEnvironments` uses, which filters the environment names by a contained physical
77
+ // directory, and it answers the same question `blueprintToScripts`
78
+ // answers from a blueprint's own `src` list. A workspace declaring app environments alone publishes
79
+ // no library, so it vendors no `configs/src/` face wrapper and has no roll-up to measure. A face
80
+ // proof is conditioned on this axis rather than on a face wrapper: a workspace that declares the
81
+ // axis and vendors no wrapper carries the defect such a proof reports, so that workspace must still
82
+ // fail the proof. The manifest cases read the same fact from their own `private` flag and name it
83
+ // `manifestPublishes` for the source that decides it there; this reads the axis on disk, so a
84
+ // workspace whose manifest and shape disagree fails one of the two rather than neither.
85
+ const publishes = ['core', 'browser', 'server'].some((environment) => {
86
+ try {
87
+ const entry = lstatSync(resolve(root, 'src', environment))
88
+ return entry.isDirectory() && !entry.isSymbolicLink()
89
+ } catch {
90
+ return false
91
+ }
92
+ })
93
+
66
94
  // A declaration roll-up loads the extractor package, which only a workspace publishing source from
67
95
  // `src` installs. The resolution below is the mechanism its proof is conditioned on.
68
96
  let extractorPath: string | undefined
@@ -199,8 +227,8 @@ describe('root configuration', () => {
199
227
  setup: ['./tests/setup.ts'],
200
228
  })
201
229
  // A row that is a configuration rather than a factory. Every generated
202
- // workspace registers the factory itself, so this shape is required here
203
- // rather than observed: the proof exercises that resolution wherever it runs
230
+ // workspace registers the factory itself, so this shape is required by the
231
+ // expectation rather than observed: the proof exercises that resolution wherever it runs
204
232
  // instead of only where a hand-written configuration happens to produce it.
205
233
  expected.set('concrete', { include: 'tests/concrete.test.ts', setup: ['./tests/setup.ts'] })
206
234
 
@@ -530,7 +558,7 @@ describe('root configuration', () => {
530
558
  if (typeof scripts !== 'object' || scripts === null) {
531
559
  throw new Error('The package manifest carries no scripts')
532
560
  }
533
- const publishes = Object.getOwnPropertyDescriptor(manifest, 'private')?.value !== true
561
+ const manifestPublishes = Object.getOwnPropertyDescriptor(manifest, 'private')?.value !== true
534
562
  const test = Object.getOwnPropertyDescriptor(scripts, 'test')?.value
535
563
  const config = Object.getOwnPropertyDescriptor(scripts, 'test:config')?.value
536
564
  const distribution = Object.getOwnPropertyDescriptor(scripts, 'test:distribution')?.value
@@ -576,9 +604,9 @@ describe('root configuration', () => {
576
604
  )
577
605
  expect(typeof test === 'string' && test.includes('test:distribution')).toBe(false)
578
606
  expect(typeof publish === 'string' && publish.includes('npm run test:distribution')).toBe(
579
- hasDistribution && publishes,
607
+ hasDistribution && manifestPublishes,
580
608
  )
581
- expect(typeof publish === 'string').toBe(publishes)
609
+ expect(typeof publish === 'string').toBe(manifestPublishes)
582
610
  expect(integration).toBe(
583
611
  hasIntegration
584
612
  ? 'vitest run --config vite.config.ts --no-cache --reporter=dot --project integration'
@@ -610,10 +638,10 @@ describe('root configuration', () => {
610
638
  : undefined,
611
639
  )
612
640
  expect(typeof test === 'string' && test.includes('npm run test:service')).toBe(
613
- hasService && !publishes,
641
+ hasService && !manifestPublishes,
614
642
  )
615
643
  expect(typeof publish === 'string' && publish.includes('npm run test:service')).toBe(
616
- hasService && publishes,
644
+ hasService && manifestPublishes,
617
645
  )
618
646
  })
619
647
 
@@ -626,14 +654,14 @@ describe('root configuration', () => {
626
654
  if (typeof scripts !== 'object' || scripts === null) {
627
655
  throw new Error('The package manifest carries no scripts')
628
656
  }
629
- const publishes = Object.getOwnPropertyDescriptor(manifest, 'private')?.value !== true
657
+ const manifestPublishes = Object.getOwnPropertyDescriptor(manifest, 'private')?.value !== true
630
658
  const prepack = Object.getOwnPropertyDescriptor(scripts, 'prepack')?.value
631
- expect(prepack).toBe(publishes ? 'npm run build' : undefined)
659
+ expect(prepack).toBe(manifestPublishes ? 'npm run build' : undefined)
632
660
 
633
661
  const controlled = { ...scripts, prepack: 'npm run control' }
634
662
  expect(() => {
635
663
  const control = Object.getOwnPropertyDescriptor(controlled, 'prepack')?.value
636
- expect(control).toBe(publishes ? 'npm run build' : undefined)
664
+ expect(control).toBe(manifestPublishes ? 'npm run build' : undefined)
637
665
  }).toThrow(/expected/u)
638
666
  })
639
667
 
@@ -2150,54 +2178,81 @@ describe('configuration helpers', () => {
2150
2178
  }
2151
2179
  })
2152
2180
 
2153
- it('reads the compiler scope and fixed extractor override a declaration roll-up requires', () => {
2154
- const compiler = createRequire(import.meta.url).resolve('typescript/bin/tsc')
2155
- // The order mirrors ENVIRONMENTS in src/core/constants.ts; a server-only workspace vendors
2156
- // no core project, so this walks to the first face the workspace actually carries.
2157
- const faces = ['core', 'browser', 'server']
2158
- const face = faces.find((candidate) =>
2159
- existsSync(resolve(root, `configs/src/tsconfig.${candidate}.json`)),
2160
- )
2161
- if (face === undefined) throw new Error('The workspace declares no face project')
2162
- const project = resolve(root, `configs/src/tsconfig.${face}.json`)
2163
- const declared: unknown = JSON.parse(readFileSync(project, 'utf8'))
2164
- if (typeof declared !== 'object' || declared === null) {
2165
- throw new Error(`The ${face} project is not a TypeScript configuration record`)
2166
- }
2167
- const declaredOptions: unknown = Object.getOwnPropertyDescriptor(
2168
- declared,
2169
- 'compilerOptions',
2170
- )?.value
2171
- if (typeof declaredOptions !== 'object' || declaredOptions === null) {
2172
- throw new Error(`The ${face} project carries no compiler options`)
2173
- }
2174
- const declaredLib: unknown = Object.getOwnPropertyDescriptor(declaredOptions, 'lib')?.value
2175
- const declaredTypes: unknown = Object.getOwnPropertyDescriptor(declaredOptions, 'types')?.value
2176
- if (!configHelpers.isStringList(declaredLib) || !configHelpers.isStringList(declaredTypes)) {
2177
- throw new Error(`The ${face} project declares no lib or types`)
2178
- }
2179
- const declaredRootDir: unknown = Object.getOwnPropertyDescriptor(
2180
- declaredOptions,
2181
- 'rootDir',
2182
- )?.value
2183
- if (typeof declaredRootDir !== 'string') {
2184
- throw new Error(`The ${face} project declares no rootDir`)
2185
- }
2186
- const expectedRoot = resolve(dirname(project), declaredRootDir)
2181
+ // The scope reading applies only where the workspace publishes source from `src`, which is what
2182
+ // `publishes` reads, and the name ends at that mechanism. An absent face project is the defect
2183
+ // this case exists to report rather than a second reason to excuse it, so a workspace holding
2184
+ // the axis and vendoring no `configs/src/` wrapper reaches the throw inside the body.
2185
+ // A skip raises the run's skipped count where a guard inside the case would
2186
+ // raise its passed count with nothing measured. Every generated workspace runs its projects
2187
+ // under `--reporter=dot`, which prints a skip as an unnamed `-`, so the case name and the
2188
+ // condition in it are read by re-running the project with a reporter that names skipped cases.
2189
+ it.skipIf(!publishes)(
2190
+ 'reads the compiler scope a declaration roll-up requires [inapplicable where src holds no recognized environment directory]',
2191
+ () => {
2192
+ const compiler = createRequire(import.meta.url).resolve('typescript/bin/tsc')
2193
+ // The order mirrors ENVIRONMENTS in src/core/constants.ts; a server-only workspace vendors
2194
+ // no core project, so this walks to the first face the workspace actually carries.
2195
+ const faces = ['core', 'browser', 'server']
2196
+ const face = faces.find((candidate) =>
2197
+ existsSync(resolve(root, `configs/src/tsconfig.${candidate}.json`)),
2198
+ )
2199
+ if (face === undefined) throw new Error('The workspace declares no face project')
2200
+ const project = resolve(root, `configs/src/tsconfig.${face}.json`)
2201
+ const declared: unknown = JSON.parse(readFileSync(project, 'utf8'))
2202
+ if (typeof declared !== 'object' || declared === null) {
2203
+ throw new Error(`The ${face} project is not a TypeScript configuration record`)
2204
+ }
2205
+ const declaredOptions: unknown = Object.getOwnPropertyDescriptor(
2206
+ declared,
2207
+ 'compilerOptions',
2208
+ )?.value
2209
+ if (typeof declaredOptions !== 'object' || declaredOptions === null) {
2210
+ throw new Error(`The ${face} project carries no compiler options`)
2211
+ }
2212
+ const declaredLib: unknown = Object.getOwnPropertyDescriptor(declaredOptions, 'lib')?.value
2213
+ const declaredTypes: unknown = Object.getOwnPropertyDescriptor(
2214
+ declaredOptions,
2215
+ 'types',
2216
+ )?.value
2217
+ if (!configHelpers.isStringList(declaredLib) || !configHelpers.isStringList(declaredTypes)) {
2218
+ throw new Error(`The ${face} project declares no lib or types`)
2219
+ }
2220
+ const declaredRootDir: unknown = Object.getOwnPropertyDescriptor(
2221
+ declaredOptions,
2222
+ 'rootDir',
2223
+ )?.value
2224
+ if (typeof declaredRootDir !== 'string') {
2225
+ throw new Error(`The ${face} project declares no rootDir`)
2226
+ }
2227
+ const expectedRoot = resolve(dirname(project), declaredRootDir)
2187
2228
 
2188
- const scope = configHelpers.parseProjectScope(
2189
- configHelpers.readCompilerOutput(compiler, ['--showConfig', '-p', project]),
2190
- project,
2191
- )
2192
- if (scope === undefined) throw new Error(`The ${face} project resolved no compiler scope`)
2193
- // The compiler lowercases every resolved library name, so the committed project is the
2194
- // second mechanism this reading is compared against rather than the reading itself.
2195
- expect(scope.lib.map((entry) => entry.toLowerCase())).toStrictEqual(
2196
- declaredLib.map((entry) => entry.toLowerCase()),
2197
- )
2198
- expect(scope.types).toStrictEqual(declaredTypes)
2199
- expect(scope.root).toBe(expectedRoot)
2229
+ const scope = configHelpers.parseProjectScope(
2230
+ configHelpers.readCompilerOutput(compiler, ['--showConfig', '-p', project]),
2231
+ project,
2232
+ )
2233
+ if (scope === undefined) throw new Error(`The ${face} project resolved no compiler scope`)
2234
+ // The compiler lowercases every resolved library name, so the committed project is the
2235
+ // second mechanism this reading is compared against rather than the reading itself.
2236
+ expect(scope.lib.map((entry) => entry.toLowerCase())).toStrictEqual(
2237
+ declaredLib.map((entry) => entry.toLowerCase()),
2238
+ )
2239
+ expect(scope.types).toStrictEqual(declaredTypes)
2240
+ expect(scope.root).toBe(expectedRoot)
2241
+ },
2242
+ )
2200
2243
 
2244
+ // No reading in this case reads anything under `configs/src/`, so each one applies to a workspace
2245
+ // on either axis. The refusals are decided by the text handed to them; the compiler refusal by a
2246
+ // path no workspace shape carries; the manifest name, the module resolution, and the extractor
2247
+ // guard by files and packages a workspace carries on either axis. They sit apart from the
2248
+ // preceding scope reading for that reason: the skip that excuses an app-only workspace from
2249
+ // reading a face project must not excuse it from the helpers behind the roll-up, which it
2250
+ // vendors whether or not it publishes.
2251
+ it('reads the refusals, guards, overrides, and rewrites a declaration roll-up requires from every workspace', () => {
2252
+ const compiler = createRequire(import.meta.url).resolve('typescript/bin/tsc')
2253
+ // Any project path resolves these, because each reading is refused by the text before the
2254
+ // path is read. The root project is the one file every workspace carries.
2255
+ const project = resolve(root, 'tsconfig.json')
2201
2256
  expect(configHelpers.parseProjectScope('not a configuration', project)).toBeUndefined()
2202
2257
  expect(
2203
2258
  configHelpers.parseProjectScope('{"compilerOptions":{"lib":[],"types":[]}}', project),
@@ -2205,11 +2260,12 @@ describe('configuration helpers', () => {
2205
2260
  expect(
2206
2261
  configHelpers.parseProjectScope('{"compilerOptions":{"lib":[1],"rootDir":"."}}', project),
2207
2262
  ).toBeUndefined()
2263
+ // The compiler's own refusal, read from a path no workspace shape carries.
2208
2264
  expect(() =>
2209
2265
  configHelpers.readCompilerOutput(compiler, [
2210
2266
  '--showConfig',
2211
2267
  '-p',
2212
- resolve(root, 'configs/src/tsconfig.absent.json'),
2268
+ resolve(root, 'configs/tsconfig.absent.json'),
2213
2269
  ]),
2214
2270
  ).toThrow('The declaration compiler failed')
2215
2271
 
@@ -4,7 +4,7 @@ let _orkestrel_template = require("@orkestrel/template");
4
4
  let _orkestrel_emitter = require("@orkestrel/emitter");
5
5
  var package_default = {
6
6
  name: "@orkestrel/scaffold",
7
- version: "0.0.72",
7
+ version: "0.0.73",
8
8
  description: "Scaffold workspaces with commands: new, audit, repair, catalog, and overwrite.",
9
9
  keywords: [
10
10
  "audit",