@voxgig/create-sdkgen 0.28.0 → 0.29.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voxgig/create-sdkgen",
3
- "version": "0.28.0",
3
+ "version": "0.29.0",
4
4
  "main": "dist/create-sdkgen.js",
5
5
  "type": "commonjs",
6
6
  "types": "dist/create-sdkgen.d.ts",
@@ -38,7 +38,9 @@ test, and the only symptom is a tree that disagrees with the model:
38
38
  anything whose output might be committed.
39
39
 
40
40
  The script deletes every target tree, regenerates, and reports what differs —
41
- which is the only way to see output the generator no longer emits. It refuses
41
+ which is the only way to see output the generator no longer emits. A target
42
+ generated at the project root (`output: root: true`) owns every tracked file
43
+ outside `.sdk/`, so those are what it deletes for that target. It refuses
42
44
  to run on a dirty tree, restores the target's own build artifacts and
43
45
  lockfiles (which the generator never writes), and leaves the regenerated tree
44
46
  in place so the diff can be read.
@@ -52,17 +52,34 @@ if [[ -n "$(git status --porcelain)" ]]; then
52
52
  exit 2
53
53
  fi
54
54
 
55
- targets="$(node -e '
56
- const m = require("./.sdk/model/sdk.json")
57
- const t = (m.main && m.main.kit && m.main.kit.target) || {}
58
- console.log(Object.keys(t).sort().join("\n"))
59
- ')"
55
+ # Each target generated in a folder of its own, and the one, if any, that
56
+ # declares `output: root: true`.
57
+ list_targets() {
58
+ node -e '
59
+ const m = require("./.sdk/model/sdk.json")
60
+ const t = (m.main && m.main.kit && m.main.kit.target) || {}
61
+ const root = "root" === process.argv[1]
62
+ console.log(Object.keys(t).sort().filter((n) =>
63
+ root === (true === (t[n] && t[n].output && t[n].output.root))).join("\n"))
64
+ ' "$1"
65
+ }
66
+ targets="$(list_targets folder)"
67
+ atroot="$(list_targets root)"
60
68
 
61
69
  echo 'check-drift: deleting and regenerating every target ...'
62
70
  while IFS= read -r t; do
63
71
  [[ -n "$t" && -d "$t" ]] && rm -rf -- "$t"
64
72
  done <<< "$targets"
65
73
 
74
+ # A target generated at the root owns the repository outside .sdk/, as the
75
+ # others own their folders. Only tracked files go: what git ignores there,
76
+ # such as installed dependencies, is not output.
77
+ if [[ -n "$atroot" ]]; then
78
+ git ls-files -z -- . ':(exclude).sdk' | while IFS= read -r -d '' f; do
79
+ rm -f -- "$f"
80
+ done
81
+ fi
82
+
66
83
  log="$(mktemp)"
67
84
  trap 'rm -f "$log"' EXIT
68
85
  if ! ( cd "$sdk_dir" && npm run generate ) >"$log" 2>&1; then
@@ -15,6 +15,8 @@ import {
15
15
  AgentGuide,
16
16
  Test,
17
17
 
18
+ SdkGenError,
19
+
18
20
  } from '@voxgig/sdkgen'
19
21
 
20
22
  import {
@@ -29,6 +31,7 @@ import { PointUtil, Content } from 'jostraca'
29
31
 
30
32
  import { Top } from './Top'
31
33
  import { BuildSDK } from './BuildSDK'
34
+ import { rootPlan } from './RootPlan'
32
35
 
33
36
 
34
37
  const {
@@ -69,58 +72,79 @@ const Root = cmp(function Root(props: any) {
69
72
  ctx$.stdrep = {}
70
73
  names(ctx$.stdrep, model.Name, 'Project' + 'Name')
71
74
 
75
+ const plan = rootPlan(model.main[KIT], SdkGenError)
76
+
72
77
  Project({}, () => {
73
78
 
74
- Top({})
79
+ if (plan.top) {
80
+ Top({})
81
+ }
75
82
 
76
- BuildSDK({})
83
+ if (plan.build) {
84
+ BuildSDK({})
85
+ }
77
86
 
78
87
  each(target, (target: any) => {
88
+ const place = plan.place[target.name]
89
+
90
+ if (null == place) {
91
+ return
92
+ }
93
+
79
94
  names(target, target.name)
80
95
 
81
- Folder({ name: target.name }, () => {
82
-
83
- const phase = target.phase || {}
84
- const phaseActive = (name: string): boolean =>
85
- false !== (phase[name] && phase[name].active)
86
-
87
- if (phaseActive('entity')) {
88
- each(entity).filter((entity: any) => entity.active).map((entity: any) => {
89
- names(entity, entity.name)
90
- Entity({ target, entity })
91
- })
92
- }
93
-
94
- if (phaseActive('feature')) {
95
- each(feature).filter((feature: any) => feature.active).map((feature: any) => {
96
- names(feature, feature.name)
97
- Feature({ target, feature })
98
- })
99
- }
100
-
101
- Main({ target })
102
-
103
- if (phaseActive('readme')) {
104
- Readme({ target })
105
- }
106
-
107
- // Per-target agent guides: <lang>/AGENTS.md + CLAUDE.md, and (driven
108
- // internally by AgentGuide) a guide per active feature under
109
- // <lang>/src/feature/<name>/. Placement mirrors Readme.
110
- if (phaseActive('agentguide')) {
111
- AgentGuide({ target })
112
- }
113
-
114
- if (phaseActive('test')) {
115
- Test({ target })
116
- }
117
- })
96
+ if ('root' === place) {
97
+ targetPhases(target, entity, feature)
98
+ }
99
+ else {
100
+ Folder({ name: target.name }, () => {
101
+ targetPhases(target, entity, feature)
102
+ })
103
+ }
118
104
  })
119
105
 
120
106
  })
121
107
  })
122
108
 
123
109
 
110
+ function targetPhases(target: any, entity: any, feature: any) {
111
+ const phase = target.phase || {}
112
+ const phaseActive = (name: string): boolean =>
113
+ false !== (phase[name] && phase[name].active)
114
+
115
+ if (phaseActive('entity')) {
116
+ each(entity).filter((entity: any) => entity.active).map((entity: any) => {
117
+ names(entity, entity.name)
118
+ Entity({ target, entity })
119
+ })
120
+ }
121
+
122
+ if (phaseActive('feature')) {
123
+ each(feature).filter((feature: any) => feature.active).map((feature: any) => {
124
+ names(feature, feature.name)
125
+ Feature({ target, feature })
126
+ })
127
+ }
128
+
129
+ Main({ target })
130
+
131
+ if (phaseActive('readme')) {
132
+ Readme({ target })
133
+ }
134
+
135
+ // Per-target agent guides: <lang>/AGENTS.md + CLAUDE.md, and (driven
136
+ // internally by AgentGuide) a guide per active feature under
137
+ // <lang>/src/feature/<name>/. Placement mirrors Readme.
138
+ if (phaseActive('agentguide')) {
139
+ AgentGuide({ target })
140
+ }
141
+
142
+ if (phaseActive('test')) {
143
+ Test({ target })
144
+ }
145
+ }
146
+
147
+
124
148
  function makeFlow(def: any, data: any, stepMakers: Record<string, any>) {
125
149
 
126
150
  const steps: any = {}
@@ -0,0 +1,64 @@
1
+
2
+ // Import-free, so the scaffold's own tests can load it without the toolchain.
3
+
4
+
5
+ type Place = 'folder' | 'root'
6
+
7
+
8
+ type RootPlan = {
9
+ top: boolean
10
+ build: boolean
11
+
12
+ // Generated targets only: absent means not generated at all.
13
+ place: Record<string, Place>
14
+ }
15
+
16
+
17
+ type Fail = new (message: string) => Error
18
+
19
+
20
+ function rootPlan(kit: any, Fail: Fail = Error): RootPlan {
21
+ const phase = kit?.phase || {}
22
+ const phaseActive = (name: string): boolean =>
23
+ false !== (phase[name] && phase[name].active)
24
+
25
+ const top = phaseActive('top')
26
+ const build = phaseActive('build')
27
+
28
+ const target = kit?.target || {}
29
+ const place: Record<string, Place> = {}
30
+
31
+ for (const name of Object.keys(target).sort()) {
32
+ if (null != target[name] && false !== target[name].active) {
33
+ place[name] = true === target[name].output?.root ? 'root' : 'folder'
34
+ }
35
+ }
36
+
37
+ const atRoot = Object.keys(place).filter((name: string) => 'root' === place[name])
38
+
39
+ if (1 < atRoot.length) {
40
+ throw new Fail(
41
+ 'Only one target can be generated at the project root, and ' +
42
+ atRoot.length + ' declare `output: root: true`: ' + atRoot.join(', ') + '.')
43
+ }
44
+
45
+ if (1 === atRoot.length && top) {
46
+ throw new Fail(
47
+ 'Target "' + atRoot[0] + '" is generated at the project root, where the ' +
48
+ 'SDK repository files (README, LICENSE, Makefile, workflows) would ' +
49
+ 'overwrite its own. Declare `main: kit: phase: top: active: false` in ' +
50
+ 'model/project.aontu.')
51
+ }
52
+
53
+ return { top, build, place }
54
+ }
55
+
56
+
57
+ export type {
58
+ Place,
59
+ RootPlan,
60
+ }
61
+
62
+ export {
63
+ rootPlan,
64
+ }