@voxgig/sdkgen 4.33.0 → 4.34.0

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.
@@ -17,6 +17,9 @@ import { resolveKind, kindModel, kindIndex, kindTrees } from './kind'
17
17
  import type { TreeDef } from './kind'
18
18
 
19
19
  import { registerInstalled } from './resolve'
20
+ import type { Source } from './resolve'
21
+
22
+ import { recordCopies } from './copies'
20
23
 
21
24
  import { aliasCmpTree, pruneStaleTemplates } from './target'
22
25
 
@@ -107,8 +110,12 @@ async function edition_add(
107
110
  // Later items in the command read this in-memory registration.
108
111
  registerInstalled('edition', edition, actx)
109
112
 
113
+ const installed: Source[] = []
114
+
110
115
  const jres = await jostraca.generate(opts, () =>
111
- EditionRoot({ edition, actx }))
116
+ EditionRoot({ edition, actx, installed }))
117
+
118
+ recordCopies(actx, jres, 'edition', installed)
112
119
 
113
120
  return { jres }
114
121
  }
@@ -139,7 +146,7 @@ function preflight(edition: string[], actx: ActionContext) {
139
146
 
140
147
 
141
148
  const EditionRoot = cmp(function EditionRoot(props: any) {
142
- const { ctx$, edition } = props
149
+ const { ctx$, edition, installed } = props
143
150
  const { log } = ctx$
144
151
 
145
152
  Project({}, () => {
@@ -153,6 +160,7 @@ const EditionRoot = cmp(function EditionRoot(props: any) {
153
160
  const source = resolveKind(dref, 'edition', ctx$)
154
161
 
155
162
  dnames.push(source.name)
163
+ installed?.push(source)
156
164
 
157
165
  log.info({
158
166
  point: 'edition-name', edition: source.name, folder: source.folder, ref: dref,
@@ -32,6 +32,9 @@ import { templateReplacements } from '../helpers/stdrep'
32
32
  import { copyOpts } from '../helpers/junk'
33
33
 
34
34
  import { resolveKind, kindModel, kindIndex } from './kind'
35
+ import type { Source } from './resolve'
36
+
37
+ import { recordCopies } from './copies'
35
38
 
36
39
 
37
40
  import {
@@ -111,10 +114,15 @@ async function feature_add(features: string[], actx: ActionContext): Promise<Act
111
114
  note: (actx.opts.dryrun ? '** DRY RUN **' : '')
112
115
  })
113
116
 
114
- const jres = await jostraca.generate(opts, () => FeatureRoot({ features }))
117
+ const installed: Source[] = []
118
+
119
+ const jres = await jostraca.generate(opts, () =>
120
+ FeatureRoot({ features, installed }))
115
121
 
116
122
  showChanges(opts.log, 'feature-result', jres)
117
123
 
124
+ recordCopies(actx, jres, 'feature', installed)
125
+
118
126
  if (actx.opts.dryrun) {
119
127
  showDryrun(opts.log, 'feature-result', jres, actx.folder)
120
128
  }
@@ -131,7 +139,7 @@ async function feature_add(features: string[], actx: ActionContext): Promise<Act
131
139
 
132
140
 
133
141
  const FeatureRoot = cmp(function FeatureRoot(props: any) {
134
- const { ctx$, features } = props
142
+ const { ctx$, features, installed } = props
135
143
  const { model, log } = ctx$
136
144
 
137
145
  const fs = ctx$.fs()
@@ -167,6 +175,7 @@ const FeatureRoot = cmp(function FeatureRoot(props: any) {
167
175
 
168
176
  const fname = source.name
169
177
  fnames.push(fname)
178
+ installed?.push(source)
170
179
 
171
180
  log.info({
172
181
  point: 'feature-build',
@@ -29,6 +29,8 @@ import { cmd_package_check } from './check'
29
29
 
30
30
  import { doctor } from './doctor'
31
31
 
32
+ import { COPIES } from './copies'
33
+
32
34
  import { resolveSource, registerInstalled, nameConflict } from './resolve'
33
35
  import type { Source } from './resolve'
34
36
 
@@ -639,6 +641,16 @@ async function preCheck(
639
641
 
640
642
  const report = res.report
641
643
 
644
+ // Unchanged since an add wrote them, so refreshing them loses nothing.
645
+ if (0 < report.outdated.length) {
646
+ actx.log.info({
647
+ point: 'package-update-outdated', package: pkgname,
648
+ files: report.outdated.length,
649
+ note: pkgname + ': ' + report.outdated.length + ' outdated file(s) ' +
650
+ 'will be refreshed; ' + COPIES + ' shows nothing in them was changed'
651
+ })
652
+ }
653
+
642
654
  const changed = [...report.forked, ...report.edited]
643
655
 
644
656
  if (0 === changed.length) {
@@ -654,19 +666,28 @@ async function preCheck(
654
666
  return
655
667
  }
656
668
 
669
+ const unrecorded = new Set<string>(report.unrecorded)
670
+ const edited = changed.filter((file: string) => !unrecorded.has(file))
671
+ const unknown = changed.filter((file: string) => unrecorded.has(file))
672
+
657
673
  throw new SdkGenError(
658
674
  pkgname + ': ' + changed.length + ' file(s) differ from the installed ' +
659
675
  'source, so updating would overwrite them:\n ' + changed.join('\n ') +
660
- '\n\n This means one of two things, and nothing recorded in the project ' +
661
- 'tells them apart:' +
662
- '\n - they are LOCAL EDITS, and `--force` will discard them;' +
663
- '\n - or ' + pkgname + ' was already updated out of band (an ' +
664
- '`npm update` in another shell), in which case they are merely STALE ' +
665
- 'and nothing is at risk.' +
666
- '\n\n If you did not update it: copy anything you want to keep into ' +
667
- '.sdk/model/, then re-run with --force.' +
668
- '\n If you did: reinstall the version you had, re-run this command, ' +
669
- 'and it will check against the right source.')
676
+ (0 === edited.length ? '' :
677
+ '\n\n ' + edited.length + ' of them changed in this project after ' +
678
+ 'an add wrote them (' + COPIES + ' records what it wrote), so updating ' +
679
+ 'would discard those edits.') +
680
+ (0 === unknown.length ? '' :
681
+ '\n\n ' + unknown.length + ' of them predate ' + COPIES + ', so ' +
682
+ 'nothing tells these two apart:' +
683
+ '\n - they are LOCAL EDITS, and `--force` will discard them;' +
684
+ '\n - or ' + pkgname + ' was already updated out of band (an ' +
685
+ '`npm update` in another shell), in which case they are merely STALE ' +
686
+ 'and nothing is at risk.' +
687
+ '\n If you updated it and changed nothing, --force is safe. To check ' +
688
+ 'first, reinstall the version you had and re-run this command.') +
689
+ '\n\n Copy anything you want to keep into .sdk/model/, then re-run ' +
690
+ 'with --force.')
670
691
  }
671
692
 
672
693
 
@@ -29,6 +29,8 @@ import { removeIndexEntries } from './action'
29
29
 
30
30
  import { doctor } from './doctor'
31
31
 
32
+ import { forgetCopies } from './copies'
33
+
32
34
 
33
35
  // Everything one `remove` would touch, decided before anything is written.
34
36
  type RemovePlan = {
@@ -121,6 +123,8 @@ async function kind_remove(
121
123
  }
122
124
 
123
125
  removed.push(...applyRemove(plan, actx, dryrun))
126
+
127
+ forgetCopies(actx, kind, plan.name)
124
128
  }
125
129
 
126
130
  log.info({
@@ -30,6 +30,9 @@ type Source = {
30
30
  // The sdkgen package that provided it, when the source has a manifest.
31
31
  // Undefined for a bare `.sdk`-shaped folder, which stays legal.
32
32
  package?: string
33
+
34
+ // That package's version, read from the same manifest.
35
+ version?: string
33
36
  }
34
37
 
35
38
 
@@ -113,6 +116,8 @@ function resolveSource(ref: string, kind: string, ctx$: any): Source {
113
116
  const nroot = Path.normalize(root)
114
117
  const rootslash = nroot.endsWith(Path.sep) ? nroot : nroot + Path.sep
115
118
 
119
+ const owner = sourcePackage(fs, folder, kind, origname, ctx$)
120
+
116
121
  return {
117
122
  name,
118
123
  origname,
@@ -125,14 +130,17 @@ function resolveSource(ref: string, kind: string, ctx$: any): Source {
125
130
  // separator path that some readers handle and others do not.
126
131
  model: definitionPathAny(fs, folder, kind, origname),
127
132
 
128
- package: sourcePackage(fs, folder, kind, origname, ctx$),
133
+ ...(null == owner ? {} : {
134
+ package: owner.name,
135
+ ...(null == owner.version ? {} : { version: owner.version }),
136
+ }),
129
137
  }
130
138
  }
131
139
 
132
140
 
133
141
  function sourcePackage(
134
142
  fs: any, folder: string, kind: string, origname: string, ctx$: any,
135
- ): string | undefined {
143
+ ): { name: string, version?: string } | undefined {
136
144
  const read = readManifest(fs, folder)
137
145
 
138
146
  if (null != read.err) {
@@ -164,7 +172,12 @@ function sourcePackage(
164
172
  return undefined
165
173
  }
166
174
 
167
- return read.manifest.name
175
+ const version = read.manifest.version
176
+
177
+ return {
178
+ name: read.manifest.name,
179
+ ...('string' === typeof version && '' !== version ? { version } : {}),
180
+ }
168
181
  }
169
182
 
170
183
 
@@ -56,6 +56,9 @@ import {
56
56
  import { kindModel, kindIndex, resolveKind, escapeRe } from './kind'
57
57
 
58
58
  import { BUNDLED, resolveSource, registerInstalled } from './resolve'
59
+ import type { Source } from './resolve'
60
+
61
+ import { recordCopies } from './copies'
59
62
 
60
63
 
61
64
  const CMD_MAP: any = {
@@ -125,11 +128,15 @@ async function target_add(targets: string[], actx: ActionContext): Promise<Actio
125
128
  ...Object.keys(featuremodel).filter((n: string) => false !== featuremodel[n]?.active),
126
129
  ]))
127
130
 
131
+ const installed: Source[] = []
132
+
128
133
  const jres = await jostraca.generate(opts, () =>
129
- TargetRoot({ targets, features, actx }))
134
+ TargetRoot({ targets, features, actx, installed }))
130
135
 
131
136
  showChanges(opts.log, 'target-result', jres)
132
137
 
138
+ recordCopies(actx, jres, 'target', installed)
139
+
133
140
  if (actx.opts.dryrun) {
134
141
  showDryrun(opts.log, 'target-result', jres, actx.folder)
135
142
  }
@@ -152,7 +159,7 @@ async function target_add(targets: string[], actx: ActionContext): Promise<Actio
152
159
 
153
160
 
154
161
  const TargetRoot = cmp(function TargetRoot(props: any) {
155
- const { ctx$, targets, features, actx } = props
162
+ const { ctx$, targets, features, actx, installed } = props
156
163
  const { model, log } = ctx$
157
164
 
158
165
  const fs = ctx$.fs()
@@ -185,6 +192,7 @@ const TargetRoot = cmp(function TargetRoot(props: any) {
185
192
  const source = resolveKind(tref, 'target', ctx$)
186
193
  const { name: tname, folder: tfolder, origname: torigname, base } = source
187
194
  tnames.push(tname)
195
+ installed?.push(source)
188
196
  const targetNote = tname + (tname != tref ? ' ref:' + tref : '')
189
197
 
190
198
  log.info({
package/src/sdkgen.ts CHANGED
@@ -146,6 +146,7 @@ import {
146
146
  import {
147
147
  action_doctor,
148
148
  doctor,
149
+ copyCheck,
149
150
  } from './action/doctor'
150
151
  import type { DoctorReport } from './action/doctor'
151
152
 
@@ -202,6 +203,16 @@ type ExternalOverride = {
202
203
  const { Jostraca } = JostracaModule
203
204
 
204
205
 
206
+ // The `const` block every action's model carries, and so what an add
207
+ // substitutes into the copies it writes.
208
+ function projectConst(model: any): any {
209
+ const projectconst: any = { name: model.name }
210
+ names(projectconst, model.name)
211
+ projectconst.year = new Date().getFullYear()
212
+ return projectconst
213
+ }
214
+
215
+
205
216
  function modelError(path: string, cause: any, rooterrs: any[]): any {
206
217
  const detail = String(cause?.msg ?? cause?.message ?? cause ?? '').trim()
207
218
 
@@ -299,6 +310,10 @@ function SdkGen(opts: SdkGenOptions) {
299
310
 
300
311
  const root = Path.resolve(folder)
301
312
 
313
+ // Generation renders from the copies in `.sdk`, not from the installed
314
+ // generator, so an outdated copy would otherwise be used without a word.
315
+ await warnCopies(root, model)
316
+
302
317
  const externalOverride = resolveExternalOverride(opts, log)
303
318
  // Snapshot the decision before preflight. In particular, do not check a
304
319
  // missing optional destination once for safety and AGAIN before writing:
@@ -364,6 +379,34 @@ function SdkGen(opts: SdkGenOptions) {
364
379
  }
365
380
 
366
381
 
382
+ // A warning, never a failure: a difference may be a deliberate edit, and a
383
+ // check that cannot run must not stop a build.
384
+ async function warnCopies(root: string, model: any) {
385
+ const sdk = Path.join(root, '.sdk')
386
+
387
+ try {
388
+ if (!fs.existsSync(Path.join(sdk, 'model'))) {
389
+ return
390
+ }
391
+
392
+ // Compared with the values an add substituted. Generation derives its
393
+ // own `const` later, in the project's Root.
394
+ const lines = await copyCheck({
395
+ fs: () => fs, log, folder: sdk,
396
+ model: { ...model, const: projectConst(model) },
397
+ url: '', jostraca, opts: { dryrun: !!opts.dryrun }, flags: {},
398
+ } as ActionContext)
399
+
400
+ for (const note of lines) {
401
+ log.warn({ point: 'generate-copies', note })
402
+ }
403
+ }
404
+ catch (err: any) {
405
+ log.debug({ point: 'generate-copies-unchecked', err: err?.message })
406
+ }
407
+ }
408
+
409
+
367
410
  async function action(
368
411
  args: string[], flags?: Record<string, any>,
369
412
  ): Promise<any> {
@@ -445,11 +488,7 @@ function SdkGen(opts: SdkGenOptions) {
445
488
  throw modelError(path, errs[0], errs)
446
489
  }
447
490
 
448
- model.const = { name: model.name }
449
-
450
- names(model.const, model.name)
451
-
452
- model.const.year = new Date().getFullYear()
491
+ model.const = projectConst(model)
453
492
 
454
493
  return {
455
494
  model,
@@ -1054,6 +1093,7 @@ export {
1054
1093
  deriveEntityNames,
1055
1094
  entityCollection,
1056
1095
  guardModelNames,
1096
+ projectConst,
1057
1097
  isReservedName,
1058
1098
  safeVarName,
1059
1099
  exampleVarName,