@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.
- package/README.md +8 -0
- package/bin/voxgig-sdkgen +2 -2
- package/dist/action/action.js +2 -2
- package/dist/action/action.js.map +1 -1
- package/dist/action/check.js +9 -8
- package/dist/action/check.js.map +1 -1
- package/dist/action/dispatch.js +4 -4
- package/dist/action/dispatch.js.map +1 -1
- package/dist/action/doctor.js +16 -15
- package/dist/action/doctor.js.map +1 -1
- package/dist/action/edition.d.ts +4 -0
- package/dist/action/{docs.js → edition.js} +57 -54
- package/dist/action/edition.js.map +1 -0
- package/dist/action/kind.d.ts +1 -2
- package/dist/action/kind.js +22 -38
- package/dist/action/kind.js.map +1 -1
- package/dist/action/package.js +17 -16
- package/dist/action/package.js.map +1 -1
- package/dist/action/resolve.d.ts +3 -1
- package/dist/action/resolve.js +29 -11
- package/dist/action/resolve.js.map +1 -1
- package/dist/admin/status.d.ts +43 -0
- package/dist/admin/status.js +161 -0
- package/dist/admin/status.js.map +1 -0
- package/dist/cmp/Test.js +11 -0
- package/dist/cmp/Test.js.map +1 -1
- package/dist/helpers/kindCollection.d.ts +1 -0
- package/dist/helpers/kindCollection.js +14 -0
- package/dist/helpers/kindCollection.js.map +1 -0
- package/dist/helpers/modelcheck.d.ts +1 -1
- package/dist/helpers/modelcheck.js +13 -3
- package/dist/helpers/modelcheck.js.map +1 -1
- package/dist/helpers/shipped.js +2 -3
- package/dist/helpers/shipped.js.map +1 -1
- package/dist/helpers/testPolicy.d.ts +1 -1
- package/dist/helpers/testPolicy.js +6 -37
- package/dist/helpers/testPolicy.js.map +1 -1
- package/dist/sdkgen.d.ts +1 -1
- package/dist/sdkgen.js +22 -89
- package/dist/sdkgen.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/utility.js +9 -1
- package/dist/utility.js.map +1 -1
- package/model/sdkgen.aon +5 -52
- package/package.json +1 -1
- package/project/.sdk/src/cmp/go/Config_go.ts +4 -4
- package/project/.sdk/src/cmp/go/utility_go.ts +10 -2
- package/project/.sdk/src/cmp/js/Package_js.ts +5 -1
- package/project/.sdk/src/cmp/js/TestDirect_js.ts +11 -7
- package/project/.sdk/src/cmp/js/TestEntity_js.ts +16 -2
- package/project/.sdk/src/cmp/js/TestLive_js.ts +32 -0
- package/project/.sdk/src/cmp/js/Test_js.ts +2 -0
- package/project/.sdk/src/cmp/js/fragment/Direct.test.fragment.js +1 -1
- package/project/.sdk/src/cmp/js/fragment/Entity.test.fragment.js +4 -2
- package/project/.sdk/src/cmp/php/ReadmeExamplesTest_php.ts +30 -9
- package/project/.sdk/src/cmp/php/ReadmeHowto_php.ts +8 -3
- package/project/.sdk/src/cmp/php/ReadmeQuick_php.ts +5 -4
- package/project/.sdk/src/cmp/php/ReadmeTopQuick_php.ts +2 -2
- package/project/.sdk/src/cmp/ts/Package_ts.ts +5 -1
- package/project/.sdk/src/cmp/ts/ReadmeHowto_ts.ts +5 -0
- package/project/.sdk/src/cmp/ts/TestDirect_ts.ts +15 -11
- package/project/.sdk/src/cmp/ts/TestEntity_ts.ts +13 -16
- package/project/.sdk/src/cmp/ts/TestLive_ts.ts +32 -0
- package/project/.sdk/src/cmp/ts/Test_ts.ts +2 -0
- package/project/.sdk/src/cmp/ts/fragment/Direct.test.fragment.ts +1 -0
- package/project/.sdk/src/cmp/ts/fragment/Entity.test.fragment.ts +2 -0
- package/project/.sdk/tm/c/Makefile +16 -11
- package/project/.sdk/tm/c/feature/secrets/sekreto/json.c +3 -3
- package/project/.sdk/tm/csharp/test/feature/secrets/SecretsFeatureTest.cs +9 -2
- package/project/.sdk/tm/js/test/live-contract.js +183 -0
- package/project/.sdk/tm/js/test/live-entity.js +169 -0
- package/project/.sdk/tm/js/test/live-runner.js +129 -0
- package/project/.sdk/tm/js/test/live-scenarios.js +142 -0
- package/project/.sdk/tm/js/test/utility.js +84 -0
- package/project/.sdk/tm/php/test/feature/secrets/SecretsTest.php +22 -20
- package/project/.sdk/tm/ts/test/live-contract.ts +104 -0
- package/project/.sdk/tm/ts/test/live-entity.ts +142 -0
- package/project/.sdk/tm/ts/test/live-runner.ts +166 -0
- package/project/.sdk/tm/ts/test/live-scenarios.ts +112 -0
- package/project/.sdk/tm/ts/test/utility.ts +1 -2
- package/project/sdkgen-package.json +1 -1
- package/src/action/action.ts +2 -2
- package/src/action/check.ts +3 -2
- package/src/action/dispatch.ts +4 -4
- package/src/action/doctor.ts +10 -9
- package/src/action/{docs.ts → edition.ts} +56 -53
- package/src/action/kind.ts +12 -33
- package/src/action/package.ts +4 -3
- package/src/action/resolve.ts +26 -2
- package/src/admin/status.ts +124 -0
- package/src/cmp/Test.ts +13 -1
- package/src/helpers/kindCollection.ts +11 -0
- package/src/helpers/modelcheck.ts +10 -3
- package/src/helpers/shipped.ts +2 -3
- package/src/helpers/testPolicy.ts +7 -45
- package/src/sdkgen.ts +15 -94
- package/src/utility.ts +8 -2
- package/dist/action/docs.d.ts +0 -4
- package/dist/action/docs.js.map +0 -1
- package/dist/cmp/Docs.d.ts +0 -4
- package/dist/cmp/Docs.js +0 -98
- package/dist/cmp/Docs.js.map +0 -1
- package/dist/cmp/ExternalDocs.d.ts +0 -2
- package/dist/cmp/ExternalDocs.js +0 -43
- package/dist/cmp/ExternalDocs.js.map +0 -1
- package/src/cmp/Docs.ts +0 -125
- package/src/cmp/ExternalDocs.ts +0 -54
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
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
|
|
16
|
-
// package table, per-language tabs — so a
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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('
|
|
69
|
+
const cmp = kindTrees('edition', NAME_MARK)
|
|
69
70
|
.find((t: TreeDef) => 'none' === t.replace)
|
|
70
71
|
|
|
71
|
-
return null == cmp ? 'src/cmp/
|
|
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:
|
|
77
|
+
add: cmd_edition_add,
|
|
77
78
|
})
|
|
78
79
|
|
|
79
80
|
|
|
80
|
-
async function
|
|
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
|
|
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
|
|
97
|
+
async function cmd_edition_add(
|
|
97
98
|
args: string[], actx: ActionContext,
|
|
98
99
|
): Promise<ActionResult> {
|
|
99
|
-
return
|
|
100
|
+
return edition_add(parseAddNames(args), actx)
|
|
100
101
|
}
|
|
101
102
|
|
|
102
103
|
|
|
103
104
|
// Code API.
|
|
104
|
-
async function
|
|
105
|
-
|
|
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
|
|
116
|
-
//
|
|
117
|
-
content: loadContent(actx, '
|
|
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: '
|
|
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
|
|
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 `
|
|
145
|
-
preflight(
|
|
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
|
|
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.
|
|
150
|
-
ensureModelInclude(actx, '
|
|
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
|
|
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('
|
|
160
|
+
registerInstalled('edition', edition, actx)
|
|
158
161
|
|
|
159
162
|
const jres = await jostraca.generate(opts, () =>
|
|
160
|
-
|
|
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(
|
|
172
|
+
function preflight(edition: string[], actx: ActionContext) {
|
|
170
173
|
const fs = actx.fs()
|
|
171
174
|
|
|
172
|
-
for (const ref of
|
|
173
|
-
const source = resolveKind(ref, '
|
|
175
|
+
for (const ref of edition) {
|
|
176
|
+
const source = resolveKind(ref, 'edition', actx as any)
|
|
174
177
|
|
|
175
|
-
for (const tree of kindTrees('
|
|
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
|
|
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
|
|
194
|
-
const { ctx$,
|
|
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(
|
|
206
|
+
each(edition, (n: any) => {
|
|
204
207
|
const dref = n.val$
|
|
205
208
|
|
|
206
|
-
log.info({ point: '
|
|
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
|
|
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, '
|
|
214
|
+
const source = resolveKind(dref, 'edition', ctx$)
|
|
212
215
|
|
|
213
216
|
dnames.push(source.name)
|
|
214
217
|
|
|
215
218
|
log.info({
|
|
216
|
-
point: '
|
|
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/
|
|
224
|
-
ctx$, kind: '
|
|
225
|
-
content: ctx$.meta.content.
|
|
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('
|
|
234
|
-
const from = kindTrees('
|
|
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: '
|
|
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
|
|
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: '
|
|
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/
|
|
286
|
-
// the origin suffix resolve nothing — `
|
|
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
|
-
|
|
309
|
-
|
|
311
|
+
action_edition,
|
|
312
|
+
edition_add,
|
|
310
313
|
}
|
package/src/action/kind.ts
CHANGED
|
@@ -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:
|
|
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/
|
|
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/
|
|
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
|
-
|
|
169
|
-
name: '
|
|
170
|
-
rename: aliasModelKey('
|
|
170
|
+
edition: {
|
|
171
|
+
name: 'edition', alias: true, ownedWhenAliased: true,
|
|
172
|
+
rename: aliasModelKey('edition'),
|
|
171
173
|
trees: [
|
|
172
|
-
{ path: 'src/cmp/
|
|
173
|
-
{ path: 'tm/
|
|
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
|
|
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
|
}
|
package/src/action/package.ts
CHANGED
|
@@ -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', '
|
|
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 =
|
|
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 =
|
|
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]
|
package/src/action/resolve.ts
CHANGED
|
@@ -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 =
|
|
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
|
|
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
|
+
}
|