@voxgig/create-sdkgen 0.30.9 → 0.31.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 +15 -1
- package/bin/create-sdkgen +25 -2
- package/dist/copymerge.d.ts +35 -0
- package/dist/copymerge.js +510 -0
- package/dist/copymerge.js.map +1 -0
- package/dist/create-sdkgen.d.ts +2 -0
- package/dist/create-sdkgen.js +30 -1
- package/dist/create-sdkgen.js.map +1 -1
- package/dist/project/standard/CreateRoot.d.ts +3 -1
- package/dist/project/standard/CreateRoot.js +18 -1
- package/dist/project/standard/CreateRoot.js.map +1 -1
- package/dist/scaffold-record.d.ts +9 -0
- package/dist/scaffold-record.js +92 -0
- package/dist/scaffold-record.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/project/standard/.sdk/package.json +3 -3
- package/src/copymerge.ts +711 -0
- package/src/create-sdkgen.ts +43 -1
- package/src/project/standard/CreateRoot.ts +19 -0
- package/src/scaffold-record.ts +119 -0
package/src/create-sdkgen.ts
CHANGED
|
@@ -13,6 +13,13 @@ import Pkg from '../package.json'
|
|
|
13
13
|
|
|
14
14
|
import { TARGETS, forwardingError } from './args'
|
|
15
15
|
|
|
16
|
+
import { gitVersions, withCopyMerge } from './copymerge'
|
|
17
|
+
|
|
18
|
+
import {
|
|
19
|
+
SCAFFOLD_BASE, SCAFFOLD_CONFLICT,
|
|
20
|
+
fingerprint, scaffoldMode, readScaffold, recordScaffold,
|
|
21
|
+
} from './scaffold-record'
|
|
22
|
+
|
|
16
23
|
|
|
17
24
|
type FullCreateSdkGenOptions = {
|
|
18
25
|
fs?: any
|
|
@@ -36,6 +43,8 @@ type GenerateSpec = {
|
|
|
36
43
|
target?: string[]
|
|
37
44
|
feature?: string[]
|
|
38
45
|
install?: boolean // perform installation - run npm install
|
|
46
|
+
force?: boolean // take the incoming scaffold over local changes
|
|
47
|
+
keep?: boolean // keep local changes whose merge stops
|
|
39
48
|
folder?: string // Target folder for project
|
|
40
49
|
|
|
41
50
|
}
|
|
@@ -135,7 +144,40 @@ function CreateSdkGen(opts: FullCreateSdkGenOptions) {
|
|
|
135
144
|
note: JSON.stringify(jopts, null, 2)
|
|
136
145
|
})
|
|
137
146
|
|
|
138
|
-
const
|
|
147
|
+
const kept = new Set<string>([...(rootModule.KEPT ?? []), ...(rootModule.SEEDS ?? [])]
|
|
148
|
+
.map((rel: string) => SDK_FOLDER + '/' + rel))
|
|
149
|
+
|
|
150
|
+
const manage = (rel: string) => !kept.has(rel) &&
|
|
151
|
+
!rel.startsWith(SDK_FOLDER + '/def/') && !rel.startsWith(SDK_FOLDER + '/log/') &&
|
|
152
|
+
!rel.split('/').includes('.jostraca') && !rel.endsWith('.aon')
|
|
153
|
+
|
|
154
|
+
const record = readScaffold(fs, folder)
|
|
155
|
+
|
|
156
|
+
const jres = await withCopyMerge({
|
|
157
|
+
fs,
|
|
158
|
+
folder,
|
|
159
|
+
log,
|
|
160
|
+
mode: scaffoldMode(fs, folder),
|
|
161
|
+
what: 'create-sdkgen ' + name,
|
|
162
|
+
force: !!spec.force,
|
|
163
|
+
keep: !!spec.keep,
|
|
164
|
+
dryrun,
|
|
165
|
+
manage,
|
|
166
|
+
recorded: (rel: string) => record[rel],
|
|
167
|
+
fingerprint,
|
|
168
|
+
basedir: SCAFFOLD_BASE,
|
|
169
|
+
conflictdir: SCAFFOLD_CONFLICT,
|
|
170
|
+
versions: gitVersions,
|
|
171
|
+
}, async (gfs: any) => {
|
|
172
|
+
const res = await jostraca.generate({ ...jopts, fs: () => gfs },
|
|
173
|
+
() => CreateRoot({ model, spec }))
|
|
174
|
+
|
|
175
|
+
if (!dryrun) {
|
|
176
|
+
recordScaffold(gfs, folder, res, Pkg.version, manage, record)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return res
|
|
180
|
+
})
|
|
139
181
|
|
|
140
182
|
showChanges(jopts.log, 'generate-result', jres, process.cwd())
|
|
141
183
|
|
|
@@ -88,6 +88,12 @@ const PROJECT_STUB = `# Project overlay — YOURS. The scaffold creates this fil
|
|
|
88
88
|
# A published package name that does not follow the derivation:
|
|
89
89
|
#
|
|
90
90
|
# main: kit: target: ts: publish: registry: package: '@scope/name'
|
|
91
|
+
#
|
|
92
|
+
# How an add, or a re-scaffold, treats a file in .sdk the project changed:
|
|
93
|
+
# merged with the update (the default), or overwritten, for a project
|
|
94
|
+
# regenerated in full from the toolchain:
|
|
95
|
+
#
|
|
96
|
+
# main: kit: copies: 'overwrite'
|
|
91
97
|
`
|
|
92
98
|
|
|
93
99
|
|
|
@@ -126,6 +132,17 @@ const KEPT = [
|
|
|
126
132
|
].map((rel: string[]) => rel.join('/'))
|
|
127
133
|
|
|
128
134
|
|
|
135
|
+
// Starting points that apidef or the model build rewrites, so a difference
|
|
136
|
+
// is the build's, not the project's: a re-scaffold overwrites them.
|
|
137
|
+
const SEEDS = [
|
|
138
|
+
'model/api/api-info.aontu',
|
|
139
|
+
'model/entity/entity-index.aontu',
|
|
140
|
+
'model/flow/flow-index.aontu',
|
|
141
|
+
'test/.model-config/model-config.json',
|
|
142
|
+
'test/test.json',
|
|
143
|
+
]
|
|
144
|
+
|
|
145
|
+
|
|
129
146
|
function mergeGuide(existing: string | null, template: string): string {
|
|
130
147
|
if (null == existing) {
|
|
131
148
|
return template
|
|
@@ -303,5 +320,7 @@ const CreateRoot = cmp(function CreateRoot(props: any) {
|
|
|
303
320
|
|
|
304
321
|
export {
|
|
305
322
|
CreateRoot,
|
|
323
|
+
KEPT,
|
|
324
|
+
SEEDS,
|
|
306
325
|
sanitizeDefName,
|
|
307
326
|
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/* Copyright (c) 2026 Richard Rodger, MIT License */
|
|
2
|
+
|
|
3
|
+
import Path from 'node:path'
|
|
4
|
+
import { createHash } from 'node:crypto'
|
|
5
|
+
|
|
6
|
+
import { copyMode } from './copymerge'
|
|
7
|
+
import type { CopyMode } from './copymerge'
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
// Relative to the project folder.
|
|
11
|
+
const SCAFFOLD_LOG = '.sdk/log/scaffold.jsonl'
|
|
12
|
+
const SCAFFOLD_BASE = '.sdk/log/scaffold-base'
|
|
13
|
+
const SCAFFOLD_CONFLICT = '.sdk/log/scaffold-conflict'
|
|
14
|
+
const PROJECT_MODEL = '.sdk/model/project.aontu'
|
|
15
|
+
|
|
16
|
+
const COPIES_RE = /^\s*main\s*:\s*kit\s*:\s*copies\s*:\s*['"]?(merge|overwrite)\b/m
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
// As @voxgig/sdkgen fingerprints its copies: CRLF and LF read the same.
|
|
20
|
+
function fingerprint(content: any): string {
|
|
21
|
+
const bytes = Buffer.isBuffer(content) ? content : Buffer.from(String(content))
|
|
22
|
+
const lf = Buffer.from(bytes.toString('latin1').replace(/\r\n/g, '\n'), 'latin1')
|
|
23
|
+
return createHash('sha256').update(lf).digest('hex').slice(0, 16)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
function at(folder: string, rel: string): string {
|
|
28
|
+
return Path.join(folder, ...rel.split('/'))
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
// `main.kit.copies`, read from the project overlay without building the model.
|
|
33
|
+
function scaffoldMode(fs: any, folder: string): CopyMode {
|
|
34
|
+
const path = at(folder, PROJECT_MODEL)
|
|
35
|
+
const text = fs.existsSync(path) ? String(fs.readFileSync(path, 'utf8')) : ''
|
|
36
|
+
return copyMode(COPIES_RE.exec(text)?.[1])
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
// Replays the append-only log: each line holds what one scaffold changed.
|
|
41
|
+
function readScaffold(fs: any, folder: string): Record<string, string> {
|
|
42
|
+
const files: Record<string, string> = {}
|
|
43
|
+
const path = at(folder, SCAFFOLD_LOG)
|
|
44
|
+
|
|
45
|
+
if (!fs.existsSync(path)) {
|
|
46
|
+
return files
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
for (const line of String(fs.readFileSync(path, 'utf8')).split('\n')) {
|
|
50
|
+
let entry: any
|
|
51
|
+
try {
|
|
52
|
+
entry = '' === line.trim() ? undefined : JSON.parse(line)
|
|
53
|
+
}
|
|
54
|
+
catch (err: any) {
|
|
55
|
+
continue
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
for (const [rel, print] of Object.entries(entry?.files ?? {})) {
|
|
59
|
+
if ('string' === typeof print) {
|
|
60
|
+
files[rel] = print
|
|
61
|
+
}
|
|
62
|
+
else if (null === print) {
|
|
63
|
+
delete files[rel]
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return files
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
function recordScaffold(
|
|
73
|
+
fs: any, folder: string, jres: any, version: string,
|
|
74
|
+
manage: (rel: string) => boolean, record: Record<string, string>,
|
|
75
|
+
) {
|
|
76
|
+
const written: string[] = [
|
|
77
|
+
...(jres?.files?.written ?? []), ...(jres?.files?.unchanged ?? []),
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
const files: Record<string, string> = {}
|
|
81
|
+
|
|
82
|
+
for (const path of written) {
|
|
83
|
+
const rel = Path.relative(Path.resolve(folder), Path.resolve(path))
|
|
84
|
+
.split(Path.sep).join('/')
|
|
85
|
+
|
|
86
|
+
if ('' === rel || rel.startsWith('../') || !manage(rel) || !fs.existsSync(path)) {
|
|
87
|
+
continue
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const print = fingerprint(fs.readFileSync(path))
|
|
91
|
+
if (print !== record[rel]) {
|
|
92
|
+
files[rel] = print
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (0 === Object.keys(files).length) {
|
|
97
|
+
return
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const sorted = Object.fromEntries(Object.keys(files).sort()
|
|
101
|
+
.map((rel: string) => [rel, files[rel]]))
|
|
102
|
+
|
|
103
|
+
const log = at(folder, SCAFFOLD_LOG)
|
|
104
|
+
fs.mkdirSync(Path.dirname(log), { recursive: true })
|
|
105
|
+
fs.appendFileSync(log, JSON.stringify({
|
|
106
|
+
at: new Date().toISOString(), op: 'scaffold', version, files: sorted,
|
|
107
|
+
}) + '\n')
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
export {
|
|
112
|
+
SCAFFOLD_LOG,
|
|
113
|
+
SCAFFOLD_BASE,
|
|
114
|
+
SCAFFOLD_CONFLICT,
|
|
115
|
+
fingerprint,
|
|
116
|
+
scaffoldMode,
|
|
117
|
+
readScaffold,
|
|
118
|
+
recordScaffold,
|
|
119
|
+
}
|