@voxgig/sdkgen 4.23.0 → 4.24.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.
- package/bin/voxgig-sdkgen +18 -2
- package/dist/action/doctor.d.ts +1 -1
- package/dist/action/doctor.js +12 -10
- package/dist/action/doctor.js.map +1 -1
- package/dist/action/edition.js +4 -0
- package/dist/action/edition.js.map +1 -1
- package/dist/action/feature.js +5 -1
- package/dist/action/feature.js.map +1 -1
- package/dist/action/remove.d.ts +16 -0
- package/dist/action/remove.js +345 -0
- package/dist/action/remove.js.map +1 -0
- package/dist/action/target.js +6 -1
- package/dist/action/target.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/project/.sdk/tm/c/core/context.c +3 -1
- package/project/.sdk/tm/c/feature/paging.c +37 -3
- package/project/.sdk/tm/c/feature/test.c +51 -27
- package/project/.sdk/tm/c/utility/prepare_query.c +2 -1
- package/project/.sdk/tm/c/utility/transform_request.c +25 -3
- package/project/.sdk/tm/clojure/src/sdk/core.clj +21 -7
- package/project/.sdk/tm/clojure/src/sdk/features.clj +68 -18
- package/project/.sdk/tm/cpp/core/types.hpp +1 -1
- package/project/.sdk/tm/cpp/feature/paging.hpp +36 -3
- package/project/.sdk/tm/cpp/feature/test.hpp +41 -13
- package/project/.sdk/tm/cpp/utility/pipeline.hpp +16 -4
- package/project/.sdk/tm/csharp/core/Context.cs +1 -1
- package/project/.sdk/tm/csharp/feature/PagingFeature.cs +58 -4
- package/project/.sdk/tm/csharp/feature/TestFeature.cs +13 -21
- package/project/.sdk/tm/csharp/utility/Fetcher.cs +19 -12
- package/project/.sdk/tm/csharp/utility/PrepareQuery.cs +1 -1
- package/project/.sdk/tm/csharp/utility/TransformRequest.cs +23 -3
- package/project/.sdk/tm/elixir/lib/projectname/context.ex +1 -1
- package/project/.sdk/tm/elixir/lib/projectname/feature/paging.ex +52 -10
- package/project/.sdk/tm/elixir/lib/projectname/feature/test.ex +47 -17
- package/project/.sdk/tm/elixir/lib/projectname/utility.ex +30 -9
- package/project/.sdk/tm/go/core/context.go +13 -13
- package/project/.sdk/tm/go/feature/paging_feature.go +40 -2
- package/project/.sdk/tm/go/feature/test_feature.go +3 -15
- package/project/.sdk/tm/go/test/feature_corpus_test.go +1 -0
- package/project/.sdk/tm/go/test/feature_test.go +54 -0
- package/project/.sdk/tm/go/utility/fetcher.go +41 -20
- package/project/.sdk/tm/go/utility/prepare_query.go +1 -1
- package/project/.sdk/tm/go/utility/transform_request.go +22 -3
- package/project/.sdk/tm/java/core/Context.java +2 -1
- package/project/.sdk/tm/java/feature/PagingFeature.java +46 -4
- package/project/.sdk/tm/java/feature/TestFeature.java +10 -13
- package/project/.sdk/tm/java/test/FeatureCorpusTest.java +3 -0
- package/project/.sdk/tm/java/test/FeatureTest.java +42 -0
- package/project/.sdk/tm/java/utility/Fetcher.java +36 -28
- package/project/.sdk/tm/java/utility/PrepareQuery.java +1 -1
- package/project/.sdk/tm/java/utility/TransformRequest.java +18 -3
- package/project/.sdk/tm/js/src/Context.js +7 -2
- package/project/.sdk/tm/js/src/feature/paging/PagingFeature.js +24 -6
- package/project/.sdk/tm/js/src/feature/test/TestFeature.js +7 -5
- package/project/.sdk/tm/js/src/utility/PrepareQueryUtility.js +1 -1
- package/project/.sdk/tm/js/test/feature/Corpus.test.js +4 -0
- package/project/.sdk/tm/kotlin/core/Context.kt +1 -1
- package/project/.sdk/tm/kotlin/feature/PagingFeature.kt +36 -4
- package/project/.sdk/tm/kotlin/feature/TestFeature.kt +11 -14
- package/project/.sdk/tm/kotlin/utility/Fetcher.kt +33 -24
- package/project/.sdk/tm/kotlin/utility/Prepare.kt +1 -1
- package/project/.sdk/tm/kotlin/utility/ResultTransform.kt +18 -3
- package/project/.sdk/tm/lua/core/context.lua +8 -1
- package/project/.sdk/tm/lua/core/control.lua +3 -0
- package/project/.sdk/tm/lua/feature/paging_feature.lua +44 -5
- package/project/.sdk/tm/lua/feature/test_feature.lua +32 -14
- package/project/.sdk/tm/lua/utility/prepare_query.lua +2 -1
- package/project/.sdk/tm/lua/utility/transform_request.lua +19 -3
- package/project/.sdk/tm/ocaml/sdk_features.ml +72 -21
- package/project/.sdk/tm/ocaml/sdk_runtime.ml +23 -8
- package/project/.sdk/tm/perl/core/context.pm +2 -1
- package/project/.sdk/tm/perl/feature/paging_feature.pm +31 -5
- package/project/.sdk/tm/perl/feature/test_feature.pm +46 -16
- package/project/.sdk/tm/perl/feature/timeout_feature.pm +17 -13
- package/project/.sdk/tm/perl/t/feature_corpus.t +14 -1
- package/project/.sdk/tm/perl/utility/fetcher.pm +15 -1
- package/project/.sdk/tm/perl/utility/prepare_query.pm +1 -0
- package/project/.sdk/tm/perl/utility/transform_request.pm +13 -3
- package/project/.sdk/tm/php/core/Context.php +5 -1
- package/project/.sdk/tm/php/core/Control.php +5 -0
- package/project/.sdk/tm/php/feature/PagingFeature.php +26 -5
- package/project/.sdk/tm/php/feature/TestFeature.php +27 -42
- package/project/.sdk/tm/php/test/FeatureCorpusTest.php +17 -1
- package/project/.sdk/tm/php/test/FeatureTest.php +44 -0
- package/project/.sdk/tm/php/utility/Fetcher.php +17 -3
- package/project/.sdk/tm/php/utility/PrepareQuery.php +1 -1
- package/project/.sdk/tm/php/utility/TransformRequest.php +15 -3
- package/project/.sdk/tm/py/pkg/core/context.py +2 -1
- package/project/.sdk/tm/py/pkg/feature/paging_feature.py +29 -4
- package/project/.sdk/tm/py/pkg/feature/test_feature.py +29 -12
- package/project/.sdk/tm/py/pkg/utility/fetcher.py +63 -65
- package/project/.sdk/tm/py/pkg/utility/prepare_query.py +2 -1
- package/project/.sdk/tm/py/pkg/utility/transform_request.py +11 -3
- package/project/.sdk/tm/py/test/test_feature.py +27 -0
- package/project/.sdk/tm/py/test/test_feature_corpus.py +11 -1
- package/project/.sdk/tm/rb/core/context.rb +1 -1
- package/project/.sdk/tm/rb/feature/paging_feature.rb +27 -5
- package/project/.sdk/tm/rb/feature/test_feature.rb +33 -7
- package/project/.sdk/tm/rb/test/feature_corpus_test.rb +12 -1
- package/project/.sdk/tm/rb/test/feature_test.rb +38 -0
- package/project/.sdk/tm/rb/utility/fetcher.rb +49 -3
- package/project/.sdk/tm/rb/utility/prepare_query.rb +1 -1
- package/project/.sdk/tm/rb/utility/transform_request.rb +10 -3
- package/project/.sdk/tm/rust/core/context.rs +2 -6
- package/project/.sdk/tm/rust/feature/paging.rs +44 -3
- package/project/.sdk/tm/rust/feature/secrets.rs +1 -1
- package/project/.sdk/tm/rust/feature/test.rs +66 -33
- package/project/.sdk/tm/rust/utility/fetcher.rs +41 -14
- package/project/.sdk/tm/rust/utility/prepare_query.rs +1 -1
- package/project/.sdk/tm/rust/utility/transform_request.rs +31 -5
- package/project/.sdk/tm/scala/core/Context.scala +2 -1
- package/project/.sdk/tm/scala/feature/PagingFeature.scala +41 -4
- package/project/.sdk/tm/scala/feature/TestFeature.scala +11 -12
- package/project/.sdk/tm/scala/utility/Fetcher.scala +34 -13
- package/project/.sdk/tm/scala/utility/Prepare.scala +1 -1
- package/project/.sdk/tm/scala/utility/Transform.scala +19 -3
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/core/Context.swift +1 -1
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/feature/PagingFeature.swift +31 -4
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/feature/TestFeature.swift +12 -9
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/Fetcher.swift +22 -3
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/Prepare.swift +1 -1
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/ResultUtil.swift +14 -3
- package/project/.sdk/tm/ts/src/Context.ts +7 -2
- package/project/.sdk/tm/ts/src/feature/paging/PagingFeature.ts +19 -3
- package/project/.sdk/tm/ts/src/feature/test/TestFeature.ts +3 -0
- package/project/.sdk/tm/ts/src/utility/PrepareQueryUtility.ts +1 -1
- package/project/.sdk/tm/ts/test/feature/Corpus.test.ts +1 -0
- package/project/.sdk/tm/zig/core/context.zig +1 -1
- package/project/.sdk/tm/zig/core/utility.zig +19 -5
- package/project/.sdk/tm/zig/feature/paging.zig +34 -4
- package/project/.sdk/tm/zig/feature/test.zig +54 -26
- package/project/sdkgen-package.json +1 -1
- package/src/action/doctor.ts +12 -9
- package/src/action/edition.ts +8 -0
- package/src/action/feature.ts +7 -1
- package/src/action/remove.ts +492 -0
- package/src/action/target.ts +8 -1
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
import { kindCollection } from '../helpers/kindCollection'
|
|
2
|
+
|
|
3
|
+
import Path from 'node:path'
|
|
4
|
+
|
|
5
|
+
import { KIT } from '../types'
|
|
6
|
+
|
|
7
|
+
import type {
|
|
8
|
+
ActionContext,
|
|
9
|
+
ActionResult,
|
|
10
|
+
} from '../types'
|
|
11
|
+
|
|
12
|
+
import { SdkGenError } from '../utility'
|
|
13
|
+
|
|
14
|
+
import { definitionPath, definitionFolder, indexName } from '../helpers/definition'
|
|
15
|
+
|
|
16
|
+
import { findFeatureSources, BASE_FEATURE } from '../helpers/featureSource'
|
|
17
|
+
|
|
18
|
+
import { ITEM_NAME_RE } from '../helpers/manifest'
|
|
19
|
+
|
|
20
|
+
import { isJunk } from '../helpers/junk'
|
|
21
|
+
|
|
22
|
+
import { kindDef, kindTrees } from './kind'
|
|
23
|
+
|
|
24
|
+
import { resolveSource, recordedRef } from './resolve'
|
|
25
|
+
|
|
26
|
+
import { removeIndexEntries } from './action'
|
|
27
|
+
|
|
28
|
+
import { doctor } from './doctor'
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
// Everything one `remove` would touch, decided before anything is written.
|
|
32
|
+
type RemovePlan = {
|
|
33
|
+
kind: string
|
|
34
|
+
name: string
|
|
35
|
+
|
|
36
|
+
// Project-relative paths, files only.
|
|
37
|
+
files: string[]
|
|
38
|
+
|
|
39
|
+
// Trees to drop once their files are gone.
|
|
40
|
+
dirs: string[]
|
|
41
|
+
|
|
42
|
+
// Whether the index lists the item.
|
|
43
|
+
indexed: boolean
|
|
44
|
+
|
|
45
|
+
// The generated output directory beside `.sdk`, when the caller asked
|
|
46
|
+
// for it and it exists.
|
|
47
|
+
output?: string
|
|
48
|
+
|
|
49
|
+
// Files `add` did not write as they stand: forked, edited, stale or
|
|
50
|
+
// project-owned. The removal refuses on any of these unless forced.
|
|
51
|
+
refused: string[]
|
|
52
|
+
|
|
53
|
+
// The subset of `refused` that is an ALIASED item's own model file. Same
|
|
54
|
+
// refusal, different advice — see the message in kindRemove.
|
|
55
|
+
aliased: string[]
|
|
56
|
+
|
|
57
|
+
// Things the caller must finish by hand.
|
|
58
|
+
notes: string[]
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
async function kind_remove(
|
|
63
|
+
kind: string, names: string[], actx: ActionContext,
|
|
64
|
+
): Promise<ActionResult> {
|
|
65
|
+
const log = actx.log
|
|
66
|
+
const dryrun = !!actx.opts?.dryrun
|
|
67
|
+
const force = true === actx.flags?.force
|
|
68
|
+
const deleteOutput = true === actx.flags?.deleteOutput
|
|
69
|
+
|
|
70
|
+
kindDef(kind)
|
|
71
|
+
|
|
72
|
+
if (0 === names.length) {
|
|
73
|
+
throw new SdkGenError(kind + ' remove: nothing named' +
|
|
74
|
+
'\n usage: voxgig-sdkgen ' + kind + ' remove <name>[,<name>...]')
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
log.info({
|
|
78
|
+
point: 'remove-start', kind, names,
|
|
79
|
+
note: (dryrun ? '** DRY RUN ** ' : '') + kind + ' remove ' + names.join(',')
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
const plans: RemovePlan[] = []
|
|
83
|
+
for (const name of names) {
|
|
84
|
+
plans.push(await planRemove(kind, name, actx, deleteOutput))
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const refused = plans.filter((p) => 0 < p.refused.length)
|
|
88
|
+
|
|
89
|
+
if (0 < refused.length && !force) {
|
|
90
|
+
// Differentiating an alias's model file is what an alias is for, so the
|
|
91
|
+
// standard advice names the place that file already is.
|
|
92
|
+
const aliased = refused.flatMap((p) => p.aliased)
|
|
93
|
+
|
|
94
|
+
throw new SdkGenError(
|
|
95
|
+
kind + ' remove: refusing to delete what `' + kind + ' add` did not write' +
|
|
96
|
+
refused.map((p) => '\n ' + p.name + ':' +
|
|
97
|
+
p.refused.map((r) => '\n ' + r).join('')).join('') +
|
|
98
|
+
(0 < aliased.length ?
|
|
99
|
+
('\n ' + aliased.join(', ') + ' is an ALIAS\'s own model file: the' +
|
|
100
|
+
' scaffold has none to compare it with, so differentiating it is' +
|
|
101
|
+
' expected and there is nowhere else to move it. Pass --force to' +
|
|
102
|
+
' delete it with the alias.') : '') +
|
|
103
|
+
'\n move any project decision into .sdk/model/, then run again;' +
|
|
104
|
+
' or pass --force to delete these too')
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const removed: string[] = []
|
|
108
|
+
|
|
109
|
+
for (const plan of plans) {
|
|
110
|
+
for (const r of plan.refused) {
|
|
111
|
+
log.warn({
|
|
112
|
+
point: 'remove-forced', kind, [kind]: plan.name, file: r,
|
|
113
|
+
note: plan.name + ': --force, deleting ' + r
|
|
114
|
+
})
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
for (const note of plan.notes) {
|
|
118
|
+
log.warn({ point: 'remove-note', kind, [kind]: plan.name, note })
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
removed.push(...applyRemove(plan, actx, dryrun))
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
log.info({
|
|
125
|
+
point: 'remove-end', kind, names, count: removed.length,
|
|
126
|
+
note: dryrun ?
|
|
127
|
+
('** DRY RUN ** ' + removed.length + ' path(s) would be removed; nothing was written') :
|
|
128
|
+
('removed ' + removed.length + ' path(s)')
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
report: {
|
|
133
|
+
ok: true,
|
|
134
|
+
kind,
|
|
135
|
+
names,
|
|
136
|
+
removed,
|
|
137
|
+
refused: plans.flatMap((p) => p.refused),
|
|
138
|
+
notes: plans.flatMap((p) => p.notes),
|
|
139
|
+
dryrun,
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
async function planRemove(
|
|
146
|
+
kind: string, name: string, actx: ActionContext, deleteOutput: boolean,
|
|
147
|
+
): Promise<RemovePlan> {
|
|
148
|
+
const fs = actx.fs()
|
|
149
|
+
const root = actx.folder
|
|
150
|
+
const model: any = actx.model
|
|
151
|
+
|
|
152
|
+
// A name, never a path: Path.join NORMALISES a traversal rather than
|
|
153
|
+
// refusing it, so `go/../go` is indistinguishable from `go` once joined.
|
|
154
|
+
// Same grammar as the add side, checked before any path is derived.
|
|
155
|
+
if (!ITEM_NAME_RE.test(name)) {
|
|
156
|
+
throw new SdkGenError(
|
|
157
|
+
'Invalid ' + kind + ' name: ' + JSON.stringify(name) +
|
|
158
|
+
'\n a name matches ' + ITEM_NAME_RE.source + ' — it is not a path')
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const declared: any = kindCollection(model, kind)?.[name]
|
|
162
|
+
const modelfile = definitionPath(root, kind, name)
|
|
163
|
+
const hasModel = fs.existsSync(modelfile)
|
|
164
|
+
|
|
165
|
+
if (null == declared && !hasModel) {
|
|
166
|
+
throw new SdkGenError(
|
|
167
|
+
kind + ' not installed: ' + name +
|
|
168
|
+
'\n nothing at ' + rel(root, modelfile) + ', and the model does not declare it')
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if ('feature' === kind && ('test' === name || BASE_FEATURE === name)) {
|
|
172
|
+
throw new SdkGenError(
|
|
173
|
+
'feature remove: `' + name + '` cannot be removed' +
|
|
174
|
+
'\n every target\'s generated test suite depends on it, and `target add`' +
|
|
175
|
+
' installs it unconditionally')
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const plan: RemovePlan = {
|
|
179
|
+
kind, name, files: [], dirs: [], indexed: false,
|
|
180
|
+
refused: [], aliased: [], notes: [],
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Feature overlays belong to both the feature and the target's tree.
|
|
184
|
+
const inScope = (k: string, n: string) =>
|
|
185
|
+
(k === kind && n === name) ||
|
|
186
|
+
('feature' === kind && 'target' === k) ||
|
|
187
|
+
('target' === kind && 'feature' === k)
|
|
188
|
+
|
|
189
|
+
const source = resolveDeclared(kind, name, declared, actx)
|
|
190
|
+
|
|
191
|
+
if (null == source) {
|
|
192
|
+
plan.refused.push('(source not found: the copy cannot be compared with what' +
|
|
193
|
+
' `' + kind + ' add` wrote; every file below is deleted only under --force)')
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// The feature being removed counts as selected, so its own source is
|
|
197
|
+
// compared rather than written off as stale — see checkTarget.
|
|
198
|
+
const findings = null == source ? { all: [], aliased: new Set<string>() } :
|
|
199
|
+
await driftFindings(actx, inScope, 'feature' === kind ? [name] : undefined)
|
|
200
|
+
|
|
201
|
+
if ('feature' === kind) {
|
|
202
|
+
planFeature(plan, actx)
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
for (const tree of kindTrees(kind, name)) {
|
|
206
|
+
const dir = Path.join(root, ...tree.path.split('/'))
|
|
207
|
+
if (!fs.existsSync(dir)) {
|
|
208
|
+
continue
|
|
209
|
+
}
|
|
210
|
+
plan.dirs.push(tree.path)
|
|
211
|
+
plan.files.push(...walk(fs, dir).map((r) => tree.path + '/' + r))
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (hasModel) {
|
|
216
|
+
plan.files.push('model/' + kind + '/' + name + '.aon')
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const wanted = new Set(plan.files)
|
|
220
|
+
for (const f of findings.all) {
|
|
221
|
+
if (wanted.has(f)) {
|
|
222
|
+
plan.refused.push(f)
|
|
223
|
+
if (findings.aliased.has(f)) {
|
|
224
|
+
plan.aliased.push(f)
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const index = Path.join(definitionFolder(root, kind), indexName(kind))
|
|
230
|
+
plan.indexed = fs.existsSync(index) &&
|
|
231
|
+
removeIndexEntries(String(fs.readFileSync(index, 'utf8')), [name]) !==
|
|
232
|
+
String(fs.readFileSync(index, 'utf8'))
|
|
233
|
+
|
|
234
|
+
if ('target' === kind) {
|
|
235
|
+
planOutput(plan, actx, declared, deleteOutput)
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
plan.notes.push(...projectMentions(kind, name, actx))
|
|
239
|
+
|
|
240
|
+
if ('feature' === kind && 0 < plan.files.length) {
|
|
241
|
+
plan.notes.push(name + ': a target\'s cross-feature test suite may still' +
|
|
242
|
+
' name this feature; `target add <t>` re-applies the trim for each target')
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return plan
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
function planFeature(plan: RemovePlan, actx: ActionContext) {
|
|
250
|
+
const fs = actx.fs()
|
|
251
|
+
const root = actx.folder
|
|
252
|
+
const targets: any = (actx.model as any)?.main?.[KIT]?.target ?? {}
|
|
253
|
+
|
|
254
|
+
for (const tname of Object.keys(targets).sort()) {
|
|
255
|
+
const tm = Path.join(root, 'tm', tname)
|
|
256
|
+
if (!fs.existsSync(tm)) {
|
|
257
|
+
continue
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
for (const found of findFeatureSources(fs, tm, [plan.name])) {
|
|
261
|
+
const abs = Path.join(tm, found.path)
|
|
262
|
+
const base = 'tm/' + tname + '/' + found.path
|
|
263
|
+
if (found.folder) {
|
|
264
|
+
plan.dirs.push(base)
|
|
265
|
+
plan.files.push(...walk(fs, abs).map((r) => base + '/' + r))
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
plan.files.push(base)
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
function planOutput(
|
|
276
|
+
plan: RemovePlan, actx: ActionContext, declared: any, deleteOutput: boolean,
|
|
277
|
+
) {
|
|
278
|
+
const fs = actx.fs()
|
|
279
|
+
const root = actx.folder
|
|
280
|
+
|
|
281
|
+
if (null != declared?.output?.path) {
|
|
282
|
+
plan.notes.push(plan.name + ': generates out of tree (' +
|
|
283
|
+
declared.output.path + '); that output is not touched')
|
|
284
|
+
return
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const output = Path.join(root, '..', plan.name)
|
|
288
|
+
|
|
289
|
+
if (!fs.existsSync(output)) {
|
|
290
|
+
return
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (deleteOutput) {
|
|
294
|
+
plan.output = output
|
|
295
|
+
return
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
plan.notes.push(plan.name + ': generated output kept at ' +
|
|
299
|
+
rel(root, output) + '; pass --delete-output to remove it')
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
function resolveDeclared(
|
|
304
|
+
kind: string, name: string, declared: any, actx: ActionContext,
|
|
305
|
+
): any | undefined {
|
|
306
|
+
const ref = recordedRef(declared, name) || name
|
|
307
|
+
try {
|
|
308
|
+
return resolveSource(
|
|
309
|
+
ref, kind, { folder: actx.folder, fs: actx.fs, log: actx.log, model: actx.model })
|
|
310
|
+
}
|
|
311
|
+
catch (err: any) {
|
|
312
|
+
return undefined
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
async function driftFindings(
|
|
318
|
+
actx: ActionContext, scope: (kind: string, name: string) => boolean,
|
|
319
|
+
selected?: string[],
|
|
320
|
+
): Promise<{ all: string[], aliased: Set<string> }> {
|
|
321
|
+
const quiet = quietLog(actx.log)
|
|
322
|
+
const res: any = await doctor({ ...actx, log: quiet }, scope, selected)
|
|
323
|
+
const report = res.report
|
|
324
|
+
|
|
325
|
+
return {
|
|
326
|
+
all: [
|
|
327
|
+
...report.forked,
|
|
328
|
+
...report.edited,
|
|
329
|
+
...report.stale,
|
|
330
|
+
...report.additive,
|
|
331
|
+
...report.aliasedDiff,
|
|
332
|
+
],
|
|
333
|
+
// Kept apart because it needs different advice — see kindRemove.
|
|
334
|
+
aliased: new Set<string>(report.aliasedDiff),
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
// Model files outside the item's own that still name it: a project's
|
|
340
|
+
// declarations are the project's, so they are reported, never edited.
|
|
341
|
+
function projectMentions(kind: string, name: string, actx: ActionContext): string[] {
|
|
342
|
+
const fs = actx.fs()
|
|
343
|
+
const root = actx.folder
|
|
344
|
+
const modeldir = Path.join(root, 'model')
|
|
345
|
+
|
|
346
|
+
if (!fs.existsSync(modeldir)) {
|
|
347
|
+
return []
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const own = kind + '/'
|
|
351
|
+
const re = new RegExp(
|
|
352
|
+
'\\b' + kind + ':\\s*[\'"]?' + escapeRe(name) + '[\'"]?\\s*:')
|
|
353
|
+
|
|
354
|
+
const out: string[] = []
|
|
355
|
+
for (const r of walk(fs, modeldir)) {
|
|
356
|
+
if (!r.endsWith('.aon') || r.startsWith(own)) {
|
|
357
|
+
continue
|
|
358
|
+
}
|
|
359
|
+
let src = ''
|
|
360
|
+
try {
|
|
361
|
+
src = String(fs.readFileSync(Path.join(modeldir, r), 'utf8'))
|
|
362
|
+
}
|
|
363
|
+
catch (err: any) {
|
|
364
|
+
continue
|
|
365
|
+
}
|
|
366
|
+
if (re.test(src)) {
|
|
367
|
+
out.push(name + ': model/' + r + ' still declares main.' + KIT + '.' +
|
|
368
|
+
('edition' === kind ? 'doc.edition' : kind) + '.' + name +
|
|
369
|
+
'; remove that declaration by hand')
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
return out
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
function applyRemove(plan: RemovePlan, actx: ActionContext, dryrun: boolean): string[] {
|
|
378
|
+
const fs = actx.fs()
|
|
379
|
+
const log = actx.log
|
|
380
|
+
const root = actx.folder
|
|
381
|
+
const { kind, name } = plan
|
|
382
|
+
|
|
383
|
+
const touched: string[] = []
|
|
384
|
+
|
|
385
|
+
const say = (file: string, what: string) => {
|
|
386
|
+
touched.push(file)
|
|
387
|
+
log.info({
|
|
388
|
+
point: 'remove-file', kind, [kind]: name, file, dryrun,
|
|
389
|
+
note: (dryrun ? 'would remove ' : 'removed ') + what
|
|
390
|
+
})
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
for (const f of plan.files) {
|
|
394
|
+
say(f, f)
|
|
395
|
+
if (!dryrun) {
|
|
396
|
+
fs.unlinkSync(Path.join(root, ...f.split('/')))
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
for (const d of plan.dirs) {
|
|
401
|
+
if (!dryrun) {
|
|
402
|
+
rmEmptyTree(fs, Path.join(root, ...d.split('/')))
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
if (plan.indexed) {
|
|
407
|
+
const index = Path.join(definitionFolder(root, kind), indexName(kind))
|
|
408
|
+
say('model/' + kind + '/' + indexName(kind), 'the index entry for ' + name)
|
|
409
|
+
if (!dryrun) {
|
|
410
|
+
fs.writeFileSync(index,
|
|
411
|
+
removeIndexEntries(String(fs.readFileSync(index, 'utf8')), [name]))
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
if (null != plan.output) {
|
|
416
|
+
say(rel(root, plan.output), 'generated output ' + rel(root, plan.output))
|
|
417
|
+
if (!dryrun) {
|
|
418
|
+
fs.rmSync(plan.output, { recursive: true, force: true })
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
if (!dryrun) {
|
|
423
|
+
delete kindCollection(actx.model, kind)[name]
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
return touched
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
// Drop a tree whose files have gone. Anything still inside (a droppings
|
|
431
|
+
// file the walk skipped, say) goes with it: the tree is the item's.
|
|
432
|
+
function rmEmptyTree(fs: any, dir: string) {
|
|
433
|
+
if (!fs.existsSync(dir)) {
|
|
434
|
+
return
|
|
435
|
+
}
|
|
436
|
+
fs.rmSync(dir, { recursive: true, force: true })
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
function walk(fs: any, dir: string): string[] {
|
|
441
|
+
const out: string[] = []
|
|
442
|
+
|
|
443
|
+
const descend = (r: string) => {
|
|
444
|
+
const abs = '' === r ? dir : Path.join(dir, r)
|
|
445
|
+
for (const entry of fs.readdirSync(abs).sort()) {
|
|
446
|
+
if (isJunk(entry)) {
|
|
447
|
+
continue
|
|
448
|
+
}
|
|
449
|
+
const er = '' === r ? entry : r + '/' + entry
|
|
450
|
+
if (fs.statSync(Path.join(dir, er)).isDirectory()) {
|
|
451
|
+
descend(er)
|
|
452
|
+
}
|
|
453
|
+
else {
|
|
454
|
+
out.push(er)
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
descend('')
|
|
460
|
+
|
|
461
|
+
return out.sort()
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
function rel(root: string, abs: string): string {
|
|
466
|
+
return Path.relative(root, abs).split(Path.sep).join('/')
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
function escapeRe(s: string): string {
|
|
471
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
function quietLog(log: any): any {
|
|
476
|
+
const noop = () => { }
|
|
477
|
+
const quiet: any = {
|
|
478
|
+
info: noop, debug: noop, warn: noop, error: noop, trace: noop, fatal: noop,
|
|
479
|
+
}
|
|
480
|
+
quiet.child = () => quiet
|
|
481
|
+
return quiet
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
export type {
|
|
486
|
+
RemovePlan,
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
export {
|
|
490
|
+
kind_remove,
|
|
491
|
+
planRemove,
|
|
492
|
+
}
|
package/src/action/target.ts
CHANGED
|
@@ -57,7 +57,8 @@ import { BUNDLED, resolveSource, registerInstalled } from './resolve'
|
|
|
57
57
|
|
|
58
58
|
|
|
59
59
|
const CMD_MAP: any = {
|
|
60
|
-
add: cmd_target_add
|
|
60
|
+
add: cmd_target_add,
|
|
61
|
+
remove: cmd_target_remove,
|
|
61
62
|
}
|
|
62
63
|
|
|
63
64
|
|
|
@@ -80,6 +81,12 @@ async function cmd_target_add(args: string[], actx: ActionContext): Promise<Acti
|
|
|
80
81
|
}
|
|
81
82
|
|
|
82
83
|
|
|
84
|
+
// Loaded on use: remove reaches doctor, and doctor reaches this module.
|
|
85
|
+
async function cmd_target_remove(args: string[], actx: ActionContext): Promise<ActionResult> {
|
|
86
|
+
return require('./remove').kind_remove('target', parseAddNames(args), actx)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
|
|
83
90
|
// Code API
|
|
84
91
|
async function target_add(targets: string[], actx: ActionContext): Promise<ActionResult> {
|
|
85
92
|
const jostraca = actx.jostraca
|