@voxgig/sdkgen 4.16.0 → 4.17.1

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 (107) hide show
  1. package/README.md +8 -0
  2. package/bin/voxgig-sdkgen +2 -2
  3. package/dist/action/action.js +2 -2
  4. package/dist/action/action.js.map +1 -1
  5. package/dist/action/check.js +9 -8
  6. package/dist/action/check.js.map +1 -1
  7. package/dist/action/dispatch.js +4 -4
  8. package/dist/action/dispatch.js.map +1 -1
  9. package/dist/action/doctor.js +16 -15
  10. package/dist/action/doctor.js.map +1 -1
  11. package/dist/action/edition.d.ts +4 -0
  12. package/dist/action/{docs.js → edition.js} +57 -54
  13. package/dist/action/edition.js.map +1 -0
  14. package/dist/action/kind.d.ts +1 -2
  15. package/dist/action/kind.js +22 -38
  16. package/dist/action/kind.js.map +1 -1
  17. package/dist/action/package.js +17 -16
  18. package/dist/action/package.js.map +1 -1
  19. package/dist/action/resolve.d.ts +3 -1
  20. package/dist/action/resolve.js +29 -11
  21. package/dist/action/resolve.js.map +1 -1
  22. package/dist/admin/status.d.ts +43 -0
  23. package/dist/admin/status.js +161 -0
  24. package/dist/admin/status.js.map +1 -0
  25. package/dist/cmp/Test.js +11 -0
  26. package/dist/cmp/Test.js.map +1 -1
  27. package/dist/helpers/kindCollection.d.ts +1 -0
  28. package/dist/helpers/kindCollection.js +14 -0
  29. package/dist/helpers/kindCollection.js.map +1 -0
  30. package/dist/helpers/modelcheck.d.ts +1 -1
  31. package/dist/helpers/modelcheck.js +13 -3
  32. package/dist/helpers/modelcheck.js.map +1 -1
  33. package/dist/helpers/shipped.js +2 -3
  34. package/dist/helpers/shipped.js.map +1 -1
  35. package/dist/helpers/testPolicy.d.ts +1 -1
  36. package/dist/helpers/testPolicy.js +6 -37
  37. package/dist/helpers/testPolicy.js.map +1 -1
  38. package/dist/sdkgen.d.ts +1 -1
  39. package/dist/sdkgen.js +22 -89
  40. package/dist/sdkgen.js.map +1 -1
  41. package/dist/tsconfig.tsbuildinfo +1 -1
  42. package/dist/utility.js +9 -1
  43. package/dist/utility.js.map +1 -1
  44. package/model/sdkgen.aon +5 -52
  45. package/package.json +1 -1
  46. package/project/.sdk/src/cmp/go/Config_go.ts +4 -4
  47. package/project/.sdk/src/cmp/go/utility_go.ts +10 -2
  48. package/project/.sdk/src/cmp/js/Package_js.ts +5 -1
  49. package/project/.sdk/src/cmp/js/TestDirect_js.ts +11 -7
  50. package/project/.sdk/src/cmp/js/TestEntity_js.ts +16 -2
  51. package/project/.sdk/src/cmp/js/TestLive_js.ts +32 -0
  52. package/project/.sdk/src/cmp/js/Test_js.ts +2 -0
  53. package/project/.sdk/src/cmp/js/fragment/Direct.test.fragment.js +1 -1
  54. package/project/.sdk/src/cmp/js/fragment/Entity.test.fragment.js +4 -2
  55. package/project/.sdk/src/cmp/php/ReadmeExamplesTest_php.ts +30 -9
  56. package/project/.sdk/src/cmp/php/ReadmeHowto_php.ts +8 -3
  57. package/project/.sdk/src/cmp/php/ReadmeQuick_php.ts +5 -4
  58. package/project/.sdk/src/cmp/php/ReadmeTopQuick_php.ts +2 -2
  59. package/project/.sdk/src/cmp/ts/Package_ts.ts +5 -1
  60. package/project/.sdk/src/cmp/ts/ReadmeHowto_ts.ts +5 -0
  61. package/project/.sdk/src/cmp/ts/TestDirect_ts.ts +15 -11
  62. package/project/.sdk/src/cmp/ts/TestEntity_ts.ts +13 -16
  63. package/project/.sdk/src/cmp/ts/TestLive_ts.ts +32 -0
  64. package/project/.sdk/src/cmp/ts/Test_ts.ts +2 -0
  65. package/project/.sdk/src/cmp/ts/fragment/Direct.test.fragment.ts +1 -0
  66. package/project/.sdk/src/cmp/ts/fragment/Entity.test.fragment.ts +2 -0
  67. package/project/.sdk/tm/c/Makefile +16 -11
  68. package/project/.sdk/tm/c/feature/secrets/sekreto/json.c +3 -3
  69. package/project/.sdk/tm/csharp/test/feature/secrets/SecretsFeatureTest.cs +9 -2
  70. package/project/.sdk/tm/js/test/live-contract.js +183 -0
  71. package/project/.sdk/tm/js/test/live-entity.js +169 -0
  72. package/project/.sdk/tm/js/test/live-runner.js +129 -0
  73. package/project/.sdk/tm/js/test/live-scenarios.js +142 -0
  74. package/project/.sdk/tm/js/test/utility.js +84 -0
  75. package/project/.sdk/tm/php/test/feature/secrets/SecretsTest.php +22 -20
  76. package/project/.sdk/tm/ts/test/live-contract.ts +104 -0
  77. package/project/.sdk/tm/ts/test/live-entity.ts +142 -0
  78. package/project/.sdk/tm/ts/test/live-runner.ts +166 -0
  79. package/project/.sdk/tm/ts/test/live-scenarios.ts +112 -0
  80. package/project/.sdk/tm/ts/test/utility.ts +1 -2
  81. package/project/sdkgen-package.json +1 -1
  82. package/src/action/action.ts +2 -2
  83. package/src/action/check.ts +3 -2
  84. package/src/action/dispatch.ts +4 -4
  85. package/src/action/doctor.ts +10 -9
  86. package/src/action/{docs.ts → edition.ts} +56 -53
  87. package/src/action/kind.ts +12 -33
  88. package/src/action/package.ts +4 -3
  89. package/src/action/resolve.ts +26 -2
  90. package/src/admin/status.ts +124 -0
  91. package/src/cmp/Test.ts +13 -1
  92. package/src/helpers/kindCollection.ts +11 -0
  93. package/src/helpers/modelcheck.ts +10 -3
  94. package/src/helpers/shipped.ts +2 -3
  95. package/src/helpers/testPolicy.ts +7 -45
  96. package/src/sdkgen.ts +15 -94
  97. package/src/utility.ts +8 -2
  98. package/dist/action/docs.d.ts +0 -4
  99. package/dist/action/docs.js.map +0 -1
  100. package/dist/cmp/Docs.d.ts +0 -4
  101. package/dist/cmp/Docs.js +0 -98
  102. package/dist/cmp/Docs.js.map +0 -1
  103. package/dist/cmp/ExternalDocs.d.ts +0 -2
  104. package/dist/cmp/ExternalDocs.js +0 -43
  105. package/dist/cmp/ExternalDocs.js.map +0 -1
  106. package/src/cmp/Docs.ts +0 -125
  107. package/src/cmp/ExternalDocs.ts +0 -54
@@ -1,4 +1,5 @@
1
- // `docs add <ref>` the third kind. See docs/design/sdkgen-packages.md §20.
1
+ import { kindCollection } from '../helpers/kindCollection'
2
+ // `edition add <ref>` — the third kind. See edition/design/sdkgen-packages.md §20.
2
3
  //
3
4
  // WHAT A DOCS ITEM IS
4
5
  //
@@ -12,11 +13,11 @@
12
13
  //
13
14
  // Three reasons, argued in §20.2 and worth restating where the code is:
14
15
  //
15
- // - a docs item's INPUT is the target collection — a page per SDK, the
16
- // package table, per-language tabs — so a docs item inside
16
+ // - a edition item's INPUT is the target collection — a page per SDK, the
17
+ // package table, per-language tabs — so a edition item inside
17
18
  // `main.kit.target` would enumerate itself;
18
19
  // - `action/feature.ts` fans out with `each(target, …)` and warns
19
- // `feature-source-missing` per target with no source, so a docs item in
20
+ // `feature-source-missing` per target with no source, so a edition item in
20
21
  // that collection would collect one warning per feature, forever
21
22
  // (`srcfeature: false` does not help — that flag is read at generate time
22
23
  // and never by the add-time fan-out);
@@ -30,7 +31,7 @@
30
31
  // aliasing and the index come along unchanged — and the kind's trees, whose
31
32
  // paths come from the registry rather than being spelled here.
32
33
  //
33
- // It does NOT fan out over targets, trim, or prune. A docs item reads the
34
+ // It does NOT fan out over targets, trim, or prune. A edition item reads the
34
35
  // target collection at GENERATE time, not at add time: the opposite direction
35
36
  // and the opposite moment from a feature's fan-out, so sharing that machinery
36
37
  // would have been a false economy.
@@ -58,26 +59,26 @@ import { aliasCmpTree, pruneStaleTemplates } from './target'
58
59
  import { parseAddNames, loadContent, ensureModelInclude } from './action'
59
60
 
60
61
 
61
- // The PREFIX of a docs item's component tree (`src/cmp/docs/`), for the
62
+ // The PREFIX of a edition item's component tree (`src/cmp/edition/`), for the
62
63
  // alias rewrite. Derived from the registry's declaration rather than written
63
- // out a second time, so the two cannot disagree about where docs components
64
+ // out a second time, so the two cannot disagree about where edition components
64
65
  // live.
65
66
  const NAME_MARK = '\u0001name\u0001'
66
67
 
67
68
  function cmpBase(): string {
68
- const cmp = kindTrees('docs', NAME_MARK)
69
+ const cmp = kindTrees('edition', NAME_MARK)
69
70
  .find((t: TreeDef) => 'none' === t.replace)
70
71
 
71
- return null == cmp ? 'src/cmp/docs/' : cmp.path.split(NAME_MARK)[0]
72
+ return null == cmp ? 'src/cmp/edition/' : cmp.path.split(NAME_MARK)[0]
72
73
  }
73
74
 
74
75
 
75
76
  const CMD_MAP: any = Object.assign(Object.create(null), {
76
- add: cmd_docs_add,
77
+ add: cmd_edition_add,
77
78
  })
78
79
 
79
80
 
80
- async function action_docs(
81
+ async function action_edition(
81
82
  args: string[], actx: ActionContext,
82
83
  ): Promise<ActionResult> {
83
84
  const cmdname = args[1]
@@ -85,7 +86,7 @@ async function action_docs(
85
86
 
86
87
  if (null == cmd) {
87
88
  throw new SdkGenError(
88
- 'Unknown docs cmd: ' + cmdname + ' (expected: ' +
89
+ 'Unknown edition cmd: ' + cmdname + ' (expected: ' +
89
90
  Object.keys(CMD_MAP).sort().join(', ') + ')')
90
91
  }
91
92
 
@@ -93,17 +94,19 @@ async function action_docs(
93
94
  }
94
95
 
95
96
 
96
- async function cmd_docs_add(
97
+ async function cmd_edition_add(
97
98
  args: string[], actx: ActionContext,
98
99
  ): Promise<ActionResult> {
99
- return docs_add(parseAddNames(args), actx)
100
+ return edition_add(parseAddNames(args), actx)
100
101
  }
101
102
 
102
103
 
103
104
  // Code API.
104
- async function docs_add(
105
- docs: string[], actx: ActionContext,
105
+ async function edition_add(
106
+ edition: string[], actx: ActionContext,
106
107
  ): Promise<ActionResult> {
108
+ edition = edition.map(ref => !ref.includes('/') && !ref.includes('\\') &&
109
+ !kindCollection(actx.model, 'edition')[ref.split('~')[0]] ? '@voxgig/docgen/project/' + ref : ref)
107
110
  const jostraca = actx.jostraca
108
111
 
109
112
  const opts = {
@@ -112,9 +115,9 @@ async function docs_add(
112
115
  log: actx.log.child({ cmp: 'jostraca' }),
113
116
  meta: {
114
117
  url: actx.url,
115
- // Seeded: no project scaffolded before the docs kind existed has a
116
- // docs index, and every project alive today is in that position.
117
- content: loadContent(actx, 'docs', { docs: '# Docs\n' }),
118
+ // Seeded: no project scaffolded before the edition kind existed has a
119
+ // edition index, and every project alive today is in that position.
120
+ content: loadContent(actx, 'edition', { edition: '# Docs\n' }),
118
121
  },
119
122
  model: actx.model,
120
123
  // Per-call, never left to the Jostraca instance: `generate` runs its own
@@ -130,7 +133,7 @@ async function docs_add(
130
133
  }
131
134
 
132
135
  opts.log.info({
133
- point: 'docs-start',
136
+ point: 'edition-start',
134
137
  note: (actx.opts.dryrun ? '** DRY RUN **' : '')
135
138
  })
136
139
 
@@ -138,26 +141,26 @@ async function docs_add(
138
141
  //
139
142
  // The write pass emits the definition and its index entry before it copies
140
143
  // the trees, so a ref whose definition exists but whose required components
141
- // do not left the command failed AND the project carrying a docs item with
144
+ // do not left the command failed AND the project carrying a edition item with
142
145
  // no implementation — which the next model compile then reads as real.
143
146
  // `package add` already validates a whole package up front for exactly this
144
- // reason; a direct `docs add` needs the same guarantee.
145
- preflight(docs, actx)
147
+ // reason; a direct `edition add` needs the same guarantee.
148
+ preflight(edition, actx)
146
149
 
147
- // The project's own model must INCLUDE the docs index, or everything below
150
+ // The project's own model must INCLUDE the edition index, or everything below
148
151
  // is invisible: no project scaffolded before this kind existed includes it,
149
- // and `main.kit.docs` would simply be absent from the next compile.
150
- ensureModelInclude(actx, 'docs')
152
+ // and `main.kit.edition` would simply be absent from the next compile.
153
+ ensureModelInclude(actx, 'edition')
151
154
 
152
155
  // Into the IN-MEMORY model before anything reads it. Nothing recompiles
153
- // `model/sdk.aontu` mid-process, so without this a second docs item in the
156
+ // `model/sdk.aontu` mid-process, so without this a second edition item in the
154
157
  // same command — and anything else later in it — behaves as if the first
155
158
  // was never installed. One definition of what gets recorded, shared with
156
159
  // `target add` and `package add`.
157
- registerInstalled('docs', docs, actx)
160
+ registerInstalled('edition', edition, actx)
158
161
 
159
162
  const jres = await jostraca.generate(opts, () =>
160
- DocsRoot({ docs, actx }))
163
+ EditionRoot({ edition, actx }))
161
164
 
162
165
  return { jres }
163
166
  }
@@ -166,13 +169,13 @@ async function docs_add(
166
169
  // Resolve every ref and check every REQUIRED tree, throwing before anything
167
170
  // is written. Resolution itself is the other half: a ref that names no
168
171
  // definition fails here rather than partway through the pass.
169
- function preflight(docs: string[], actx: ActionContext) {
172
+ function preflight(edition: string[], actx: ActionContext) {
170
173
  const fs = actx.fs()
171
174
 
172
- for (const ref of docs) {
173
- const source = resolveKind(ref, 'docs', actx as any)
175
+ for (const ref of edition) {
176
+ const source = resolveKind(ref, 'edition', actx as any)
174
177
 
175
- for (const tree of kindTrees('docs', source.origname)) {
178
+ for (const tree of kindTrees('edition', source.origname)) {
176
179
  if (!tree.required) {
177
180
  continue
178
181
  }
@@ -182,7 +185,7 @@ function preflight(docs: string[], actx: ActionContext) {
182
185
  if (!fs.existsSync(from)) {
183
186
  throw new SdkGenError(
184
187
  'Docs ' + source.name + ': required tree not found: ' + from +
185
- '\n a docs item needs its components (' + tree.path +
188
+ '\n a edition item needs its components (' + tree.path +
186
189
  '); nothing has been written')
187
190
  }
188
191
  }
@@ -190,8 +193,8 @@ function preflight(docs: string[], actx: ActionContext) {
190
193
  }
191
194
 
192
195
 
193
- const DocsRoot = cmp(function DocsRoot(props: any) {
194
- const { ctx$, docs } = props
196
+ const EditionRoot = cmp(function EditionRoot(props: any) {
197
+ const { ctx$, edition } = props
195
198
  const { log } = ctx$
196
199
 
197
200
  Project({}, () => {
@@ -200,29 +203,29 @@ const DocsRoot = cmp(function DocsRoot(props: any) {
200
203
  // carry all of them.
201
204
  const dnames: string[] = []
202
205
 
203
- each(docs, (n: any) => {
206
+ each(edition, (n: any) => {
204
207
  const dref = n.val$
205
208
 
206
- log.info({ point: 'docs-build', docs: dref, note: dref })
209
+ log.info({ point: 'edition-build', edition: dref, note: dref })
207
210
 
208
211
  // The shared spine: a BARE name resolves against what the model
209
- // RECORDS, so a docs item installed from a package resolves back to
212
+ // RECORDS, so a edition item installed from a package resolves back to
210
213
  // that package on its next add rather than to the bundled scaffold.
211
- const source = resolveKind(dref, 'docs', ctx$)
214
+ const source = resolveKind(dref, 'edition', ctx$)
212
215
 
213
216
  dnames.push(source.name)
214
217
 
215
218
  log.info({
216
- point: 'docs-name', docs: source.name, folder: source.folder, ref: dref,
219
+ point: 'edition-name', edition: source.name, folder: source.folder, ref: dref,
217
220
  note: source.name +
218
221
  (source.name !== source.origname ?
219
222
  ' (from ' + source.origname + ')' : '') +
220
223
  ' from:' + source.folder
221
224
  })
222
225
 
223
- Folder({ name: 'model/docs' }, () => kindModel({
224
- ctx$, kind: 'docs', source, names: dnames,
225
- content: ctx$.meta.content.docs_index,
226
+ Folder({ name: 'model/edition' }, () => kindModel({
227
+ ctx$, kind: 'edition', source, names: dnames,
228
+ content: ctx$.meta.content.edition_index,
226
229
  }))
227
230
 
228
231
  // Both ends of every tree come from the registry's ONE declaration,
@@ -230,8 +233,8 @@ const DocsRoot = cmp(function DocsRoot(props: any) {
230
233
  // the installed one. Deriving the source path by substituting inside
231
234
  // the destination path would corrupt any item whose name also appears
232
235
  // in the fixed part of the path.
233
- const dest = kindTrees('docs', source.name)
234
- const from = kindTrees('docs', source.origname)
236
+ const dest = kindTrees('edition', source.name)
237
+ const from = kindTrees('edition', source.origname)
235
238
 
236
239
  dest.forEach((tree: TreeDef, i: number) => {
237
240
  // Copy only ADDS and overwrites. A newer version of the package that
@@ -252,7 +255,7 @@ const DocsRoot = cmp(function DocsRoot(props: any) {
252
255
  copyTree(ctx$, source, tree, from[i].path)
253
256
  })
254
257
 
255
- log.info({ point: 'docs-done', docs: source.name, note: source.name })
258
+ log.info({ point: 'edition-done', edition: source.name, note: source.name })
256
259
  })
257
260
  })
258
261
  })
@@ -261,7 +264,7 @@ const DocsRoot = cmp(function DocsRoot(props: any) {
261
264
  // One tree, copied from the origin path to the installed one.
262
265
  //
263
266
  // An optional tree the source does not ship is simply not copied — that is
264
- // what `required: false` means, and a docs item whose every byte is generated
267
+ // what `required: false` means, and a edition item whose every byte is generated
265
268
  // legitimately has no template tree.
266
269
  function copyTree(ctx$: any, source: any, tree: TreeDef, frompath: string) {
267
270
  const fs = ctx$.fs()
@@ -274,7 +277,7 @@ function copyTree(ctx$: any, source: any, tree: TreeDef, frompath: string) {
274
277
  }
275
278
 
276
279
  ctx$.log.info({
277
- point: 'docs-tree-absent', docs: source.name, tree: tree.path, from,
280
+ point: 'edition-tree-absent', edition: source.name, tree: tree.path, from,
278
281
  note: source.name + ': the source ships no ' + frompath +
279
282
  ', nothing to copy'
280
283
  })
@@ -282,8 +285,8 @@ function copyTree(ctx$: any, source: any, tree: TreeDef, frompath: string) {
282
285
  }
283
286
 
284
287
  // An ALIASED component tree cannot be copied verbatim: components are
285
- // dispatched by the convention `cmp/docs/<n>/Main_<n>`, so files keeping
286
- // the origin suffix resolve nothing — `Main_apidocs.ts` is invisible to a
288
+ // dispatched by the convention `cmp/edition/<n>/Main_<n>`, so files keeping
289
+ // the origin suffix resolve nothing — `Main_summary.ts` is invisible to a
287
290
  // lookup for `Main_portal`. Same rule as a target's, so the same function
288
291
  // does it; jostraca's tree Copy has no per-entry rename hook, which is why
289
292
  // an aliased tree is emitted file by file.
@@ -305,6 +308,6 @@ function copyTree(ctx$: any, source: any, tree: TreeDef, frompath: string) {
305
308
 
306
309
 
307
310
  export {
308
- action_docs,
309
- docs_add,
311
+ action_edition,
312
+ edition_add,
310
313
  }
@@ -1,3 +1,4 @@
1
+ import { kindCollection } from '../helpers/kindCollection'
1
2
  // KINDS: the things an `add` can install.
2
3
  //
3
4
  // `target` and `feature` are two of them, `docs` and others are meant to
@@ -20,6 +21,7 @@
20
21
  // are genuinely different work, not the same work with different strings, and
21
22
  // pretending otherwise would buy generality nobody can use.
22
23
 
24
+
23
25
  import Path from 'node:path'
24
26
 
25
27
  import { File, Copy, Content, template } from 'jostraca'
@@ -30,7 +32,7 @@ import { SdkGenError } from '../utility'
30
32
 
31
33
  import { provenanceReplace } from '../helpers/stdrep'
32
34
 
33
- import { resolveSource } from './resolve'
35
+ import { resolveSource, recordedRef, isBare } from './resolve'
34
36
  import type { Source } from './resolve'
35
37
 
36
38
  import { UpdateIndex } from './action'
@@ -92,7 +94,7 @@ type TreeDef = {
92
94
 
93
95
  // Rewrite the ITEM KEY in a copied model file, for an aliased install.
94
96
  //
95
- // Parameterised by kind because `main: kit: docs: <n>:` is the same rewrite
97
+ // Parameterised by kind because `main: kit: doc: edition: <n>:` is the same rewrite
96
98
  // with a different word, and a second copy of this regex is exactly the
97
99
  // same-rule-written-twice defect the registry exists to prevent.
98
100
  //
@@ -156,21 +158,21 @@ const KINDS: Record<string, KindDef> = Object.assign(Object.create(null), {
156
158
 
157
159
  // DOCS — the third kind. See docs/design/sdkgen-packages.md §20.
158
160
  //
159
- // Its trees are NESTED under the kind name (`src/cmp/docs/<n>`, not
161
+ // Its trees are NESTED under the kind name (`src/cmp/edition/<n>`, not
160
162
  // `src/cmp/<n>`) so a docs item and a target may share a name without
161
163
  // sharing a directory. Everything that composes those paths takes them
162
164
  // from here.
163
165
  //
164
- // `tm/docs/{name}` is NOT required: a docs item whose every emitted byte
166
+ // `tm/edition/{name}` is NOT required: a docs item whose every emitted byte
165
167
  // depends on the API — a catalogue entry, a config file — legitimately
166
168
  // ships no template tree, while a static site needs one. So it is
167
169
  // copy-if-present, and `package check` does not demand it.
168
- docs: {
169
- name: 'docs', alias: true, ownedWhenAliased: true,
170
- rename: aliasModelKey('docs'),
170
+ edition: {
171
+ name: 'edition', alias: true, ownedWhenAliased: true,
172
+ rename: aliasModelKey('edition'),
171
173
  trees: [
172
- { path: 'src/cmp/docs/{name}', replace: 'none', required: true },
173
- { path: 'tm/docs/{name}', replace: 'template', required: false },
174
+ { path: 'src/cmp/edition/{name}', replace: 'none', required: true },
175
+ { path: 'tm/edition/{name}', replace: 'template', required: false },
174
176
  ],
175
177
  },
176
178
  })
@@ -208,12 +210,7 @@ function kindDef(kind: string): KindDef {
208
210
  // source.
209
211
  function resolveKind(ref: string, kind: string, ctx$: any): Source {
210
212
  const def = kindDef(kind)
211
- const model = ctx$.model
212
-
213
- const declared: any = model?.main?.[KIT]?.[kind]?.[ref]
214
- const recorded = (isBare(ref) && recordedRef(declared, ref)) || ref
215
-
216
- const source = resolveSource(recorded, kind, ctx$)
213
+ const source = resolveSource(ref, kind, ctx$)
217
214
 
218
215
  // Asked of the RESOLVER rather than by re-reading the ref. `~` separates an
219
216
  // alias only in the last segment, and a check that looked for one anywhere
@@ -334,24 +331,6 @@ function kindModel(props: {
334
331
  // ONE definition, used by the add actions and by doctor. This reconstruction
335
332
  // was written twice and the two copies had already diverged on exactly this
336
333
  // point, which is the drift the kind spine exists to end.
337
- function recordedRef(declared: any, name: string): string | undefined {
338
- if (null == declared?.base || '' === declared.base) {
339
- return undefined
340
- }
341
-
342
- const origname = declared.origname || name
343
-
344
- return Path.join(declared.base, '..', origname) +
345
- (origname === name ? '' : '~' + name)
346
- }
347
-
348
-
349
- // A bare NAME, as opposed to a ref that locates a source.
350
- function isBare(ref: string): boolean {
351
- return !ref.includes('/') && !ref.includes(Path.sep)
352
- }
353
-
354
-
355
334
  function capitalise(s: string): string {
356
335
  return s.charAt(0).toUpperCase() + s.slice(1)
357
336
  }
@@ -1,3 +1,4 @@
1
+ import { kindCollection } from '../helpers/kindCollection'
1
2
  // `package add` / `package list` — the whole-package verbs.
2
3
  //
3
4
  // See docs/design/sdkgen-packages.md §9.
@@ -78,7 +79,7 @@ const CMD_MAP: any = Object.assign(Object.create(null), {
78
79
  // reads them at generate time rather than at add time, installing it last
79
80
  // keeps the add log in the order a reader would expect and leaves the model
80
81
  // complete before anything reads it.
81
- const ADD_ORDER = ['target', 'feature', 'docs']
82
+ const ADD_ORDER = ['target', 'feature', 'edition']
82
83
 
83
84
 
84
85
  async function action_package(
@@ -663,7 +664,7 @@ function installedFrom(pkgname: string, actx: ActionContext): Installed[] {
663
664
  const found: Installed[] = []
664
665
 
665
666
  for (const kind of Object.keys(KINDS).sort()) {
666
- const items = kit[kind] ?? {}
667
+ const items = kindCollection({ main: { [KIT]: kit } }, kind)
667
668
 
668
669
  for (const name of Object.keys(items).sort()) {
669
670
  const item = items[name]
@@ -1078,7 +1079,7 @@ async function cmd_package_list(
1078
1079
  const groups: Record<string, any[]> = Object.create(null)
1079
1080
 
1080
1081
  for (const kind of Object.keys(KINDS).sort()) {
1081
- const items = kit[kind] ?? {}
1082
+ const items = kindCollection({ main: { [KIT]: kit } }, kind)
1082
1083
 
1083
1084
  for (const name of Object.keys(items).sort()) {
1084
1085
  const item = items[name]
@@ -1,3 +1,4 @@
1
+ import { kindCollection } from '../helpers/kindCollection'
1
2
  // Where a `<kind> add <ref>` gets its definition from.
2
3
  //
3
4
  // One resolver for every KIND of thing an add can install — targets today,
@@ -59,6 +60,9 @@ function lastSegment(ref: string): string {
59
60
 
60
61
 
61
62
  function resolveSource(ref: string, kind: string, ctx$: any): Source {
63
+ // Registration and copying must resolve a bare resync name identically.
64
+ const declared = kindCollection(ctx$.model, kind)?.[ref]
65
+ ref = (isBare(ref) && recordedRef(declared, ref)) || ref
62
66
  const root = ctx$.folder
63
67
  const fs = ctx$.fs()
64
68
 
@@ -275,7 +279,7 @@ function registerInstalled(kind: string, refs: string[], ctx$: any) {
275
279
  return
276
280
  }
277
281
 
278
- const items = kit[kind] = kit[kind] ?? {}
282
+ const items = kindCollection(ctx$.model, kind, true)
279
283
 
280
284
  for (const ref of refs) {
281
285
  let source: Source
@@ -317,7 +321,7 @@ function registerInstalled(kind: string, refs: string[], ctx$: any) {
317
321
  function nameConflict(
318
322
  kind: string, source: Source, ctx$: any,
319
323
  ): { package?: string, base?: string } | undefined {
320
- const declared: any = ctx$.model?.main?.[KIT]?.[kind]?.[source.name]
324
+ const declared: any = kindCollection(ctx$.model, kind)?.[source.name]
321
325
 
322
326
  if (null == declared || 'object' !== typeof declared) {
323
327
  return undefined
@@ -423,6 +427,24 @@ function providesStill(
423
427
  }
424
428
 
425
429
 
430
+ function recordedRef(declared: any, name: string): string | undefined {
431
+ if (null == declared?.base || '' === declared.base) {
432
+ return undefined
433
+ }
434
+
435
+ const origname = declared.origname || name
436
+
437
+ return Path.join(declared.base, '..', origname) +
438
+ (origname === name ? '' : '~' + name)
439
+ }
440
+
441
+
442
+ // A bare NAME, as opposed to a ref that locates a source.
443
+ function isBare(ref: string): boolean {
444
+ return !ref.includes('/') && !ref.includes(Path.sep)
445
+ }
446
+
447
+
426
448
  function normaliseBase(base: string): string {
427
449
  return Path.normalize(String(base ?? '')).split(Path.sep).join('/')
428
450
  }
@@ -439,6 +461,8 @@ export type {
439
461
 
440
462
  export {
441
463
  resolveSource,
464
+ recordedRef,
465
+ isBare,
442
466
  registerInstalled,
443
467
  nameConflict,
444
468
  lastSegment,
@@ -0,0 +1,124 @@
1
+ import Fs from 'node:fs'
2
+ import Path from 'node:path'
3
+ import { spawnSync } from 'node:child_process'
4
+
5
+ function command(cwd: string, bin: string, args: string[]) {
6
+ return spawnSync(bin, args, { cwd, encoding: 'utf8', timeout: 15000, windowsHide: true, env: { ...process.env, GIT_OPTIONAL_LOCKS: '0' } })
7
+ }
8
+ function git(cwd: string, args: string[]): string | null {
9
+ const result = command(cwd, 'git', args)
10
+ return result.status === 0 ? result.stdout.trim() : null
11
+ }
12
+ function directory(path: string): boolean {
13
+ return Fs.existsSync(path) && Fs.statSync(path).isDirectory()
14
+ }
15
+ function entries(value: any): [string, any][] {
16
+ return Object.entries(value || {}).filter(([name]) => !name.includes('$')).sort(([a], [b]) => a.localeCompare(b))
17
+ }
18
+ function workingTree(folder: string) {
19
+ if (!directory(folder)) return 'missing'
20
+ const state = git(folder, ['status', '--porcelain=v1', '--untracked-files=normal', '--', '.'])
21
+ return state === null ? 'not a git repository' : state ? 'modified' : 'clean'
22
+ }
23
+
24
+ // Read the compiled model and working trees. Never fetch, build, test, or publish.
25
+ export function repositoryStatus(root: string) {
26
+ root = Path.resolve(root)
27
+ const sdk = Path.join(root, '.sdk')
28
+ if (!directory(sdk)) throw new Error('Expected an SDK repository containing .sdk: ' + root)
29
+ const file = Path.join(sdk, 'model/sdk.json')
30
+ let model: any, modelError: string | undefined
31
+ if (Fs.existsSync(file)) {
32
+ try {
33
+ model = JSON.parse(Fs.readFileSync(file, 'utf8'))
34
+ if (!model?.main?.kit) throw new Error('missing main.kit')
35
+ } catch (err: any) { modelError = 'Invalid compiled model: ' + err.message }
36
+ }
37
+ const kit = model?.main?.kit || {}
38
+ const upstream = git(root, ['rev-parse', '--abbrev-ref', '--symbolic-full-name', '@{upstream}'])
39
+ const counts = upstream && git(root, ['rev-list', '--left-right', '--count', 'HEAD...@{upstream}'])
40
+ const [ahead, behind] = counts ? counts.split(/\s+/).map(Number) : [null, null]
41
+ const packages = ['sdkgen', 'apidef', 'model', 'docgen'].map(name => {
42
+ const file = Path.join(sdk, 'node_modules/@voxgig', name, 'package.json')
43
+ try { return { name: '@voxgig/' + name, version: JSON.parse(Fs.readFileSync(file, 'utf8')).version } }
44
+ catch { return { name: '@voxgig/' + name, version: 'not installed' } }
45
+ })
46
+ return {
47
+ root, name: model?.name || Path.basename(root),
48
+ repository: {
49
+ branch: git(root, ['branch', '--show-current']) || 'detached or not initialised',
50
+ commit: git(root, ['rev-parse', '--short', 'HEAD']), state: workingTree(root), upstream, ahead, behind,
51
+ },
52
+ model: { path: file, compiled: !!model && !modelError, error: modelError,
53
+ generatedAt: Fs.existsSync(file) ? Fs.statSync(file).mtime.toISOString() : null },
54
+ toolchain: { node: process.version, packages },
55
+ targets: entries(kit.target).map(([name, target]) => {
56
+ const folder = Path.resolve(root, target.output?.path || name)
57
+ const present = directory(folder)
58
+ return { name, active: target.active !== false, path: folder, present,
59
+ state: workingTree(folder), readme: Fs.existsSync(Path.join(folder, 'README.md')),
60
+ version: target.publish?.version || null,
61
+ publication: target.publish?.registry?.state || 'not recorded',
62
+ }
63
+ }),
64
+ editions: entries(kit.doc?.edition).map(([name, edition]) => ({ name,
65
+ active: kit.doc?.active !== false && edition.active !== false, kind: edition.kind,
66
+ path: edition.output?.path || null,
67
+ present: !!edition.output?.path && Fs.existsSync(Path.resolve(root, edition.output.path)),
68
+ })),
69
+ }
70
+ }
71
+
72
+ function githubStatus(root: string) {
73
+ const run = (args: string[]) => {
74
+ const result = command(root, 'gh', args)
75
+ if (result.status !== 0) throw new Error(result.error?.message || result.stderr.trim() || 'GitHub query failed')
76
+ return JSON.parse(result.stdout)
77
+ }
78
+ const repository = run(['repo', 'view', '--json', 'nameWithOwner,url'])
79
+ const host = new URL(repository.url).hostname
80
+ const pages = command(root, 'gh', ['api', '--hostname', host, 'repos/' + repository.nameWithOwner + '/pages'])
81
+ let site: any
82
+ try { site = JSON.parse(pages.stdout || '{}') } catch { site = {} }
83
+ if (pages.status !== 0 && String(site.status) !== '404') throw new Error(pages.error?.message || pages.stderr.trim() || 'Pages query failed')
84
+ return { repository: repository.nameWithOwner,
85
+ pages: pages.status === 0 ? { url: site.html_url, status: site.status, buildType: site.build_type } : { status: 'not configured' },
86
+ runs: run(['run', 'list', '--limit', '5', '--json', 'workflowName,status,conclusion,headBranch,url']),
87
+ }
88
+ }
89
+
90
+ export function main(args = process.argv.slice(2)): number {
91
+ const root = args.shift()
92
+ if (args.includes('--help') || root === '--help') {
93
+ console.log('Usage: .sdk/admin/status.sh [--json] [--github]\nRead local repository, SDK, and documentation status. --github also reads Pages and recent CI runs.\nBuild and test results are not inferred from generated files. No commands that change the repository are run.')
94
+ return 0
95
+ }
96
+ if (!root || args.some(arg => !['--json', '--github'].includes(arg))) throw new Error('Usage: status.sh [--json] [--github]')
97
+ const report: any = repositoryStatus(root)
98
+ let failed = !!report.model.error
99
+ if (args.includes('--github')) {
100
+ try { report.github = githubStatus(report.root) }
101
+ catch (err: any) { report.github = { error: err.message }; failed = true }
102
+ }
103
+ if (args.includes('--json')) console.log(JSON.stringify(report, null, 2))
104
+ else {
105
+ console.log('Repository: ' + report.root)
106
+ console.log('Git: ' + report.repository.branch + ' | ' + report.repository.state + ' | ' + (report.repository.commit || 'no commits'))
107
+ console.log('Upstream: ' + (report.repository.upstream ? report.repository.upstream + ' | ahead ' + report.repository.ahead + ', behind ' + report.repository.behind + ' (local tracking refs)' : 'not configured'))
108
+ console.log('Model: ' + (report.model.error || (report.model.compiled ? 'compiled ' + report.model.generatedAt : 'not generated; run npm run generate from .sdk')))
109
+ console.log('Toolchain: Node ' + report.toolchain.node + '; ' + report.toolchain.packages.map((p: any) => p.name + ' ' + p.version).join('; '))
110
+ console.log('\nSDKs and tools (publication state comes from the model):')
111
+ for (const t of report.targets) console.log(' ' + t.name + ' | ' + (t.active ? 'active' : 'inactive') + ' | ' + t.state + ' | version ' + (t.version || 'not recorded') + ' | ' + t.publication + ' | ' + t.path)
112
+ if (!report.targets.length) console.log(' No targets in the compiled model.')
113
+ console.log('\nDocumentation:')
114
+ for (const e of report.editions) console.log(' ' + e.name + ' | ' + (e.active ? 'active' : 'inactive') + ' | ' + (e.present ? 'present' : 'missing') + ' | ' + (e.path || 'no output path'))
115
+ if (!report.editions.length) console.log(' No editions in the compiled model.')
116
+ console.log('\nLocal status does not run builds/tests or check registries. Use --github for remote CI and Pages status.')
117
+ if (report.github) console.log('\nGitHub:\n' + JSON.stringify(report.github, null, 2))
118
+ }
119
+ return failed ? 1 : 0
120
+ }
121
+
122
+ if (require.main === module) {
123
+ try { process.exitCode = main() } catch (err: any) { console.error(err.message); process.exitCode = 1 }
124
+ }
package/src/cmp/Test.ts CHANGED
@@ -1,6 +1,6 @@
1
1
 
2
2
  import {
3
- cmp,
3
+ cmp, File, Content,
4
4
  } from 'jostraca'
5
5
 
6
6
  import { requirePath } from '../utility'
@@ -19,6 +19,18 @@ const Test = cmp(function Test(props: any) {
19
19
  const { model, log } = ctx$
20
20
  const stdrep = ensureStdrep(ctx$)
21
21
 
22
+ const points = Object.values(model.main.kit.entity || {}).flatMap((entity: any) =>
23
+ Object.values(entity.op || {}).flatMap((op: any) => op.points || [])) as any[]
24
+ if (points.some(point => point.contract && JSON.parse(point.contract.json).live)) {
25
+ const supported = ['ts', 'js'].includes(target.name)
26
+ File({ name: 'live-coverage.json' }, () => Content(JSON.stringify({
27
+ version: 1, target: target.name, scenarios: supported ? 'supported' : 'unsupported',
28
+ points: points.map(point => point.contract?.id || point.method + ' ' + point.orig),
29
+ ...(!supported ? { reason: 'This target does not yet execute declarative live recipes. Its existing tests do not establish full operation coverage.' } : {}),
30
+ }, null, 2)))
31
+ if (!supported) log.warn({ point: 'live-scenarios-unsupported', note: 'Declarative live recipes are not executed by target ' + target.name })
32
+ }
33
+
22
34
  const Test_sdk = requirePath(ctx$, `./cmp/${target.name}/Test_${target.name}`)
23
35
  Test_sdk['Test']({ model, target, stdrep })
24
36
 
@@ -0,0 +1,11 @@
1
+ import { KIT } from '../types'
2
+
3
+ // Physical kind names and model namespaces are independent.
4
+ export function kindCollection(model: any, kind: string, create = false): any {
5
+ const kit = create ? (model.main ??= {})[KIT] ??= {} : model?.main?.[KIT]
6
+ if (kind === 'edition') {
7
+ const doc = create ? kit.doc ??= {} : kit?.doc
8
+ return create ? doc.edition ??= {} : doc?.edition ?? {}
9
+ }
10
+ return create ? kit[kind] ??= {} : kit?.[kind] ?? {}
11
+ }