@voxgig/create-sdkgen 0.28.0 → 0.29.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voxgig/create-sdkgen",
3
- "version": "0.28.0",
3
+ "version": "0.29.1",
4
4
  "main": "dist/create-sdkgen.js",
5
5
  "type": "commonjs",
6
6
  "types": "dist/create-sdkgen.d.ts",
@@ -47,7 +47,8 @@
47
47
  "dist",
48
48
  "bin",
49
49
  "project",
50
- "LICENSE"
50
+ "LICENSE",
51
+ "README.md"
51
52
  ],
52
53
  "devDependencies": {
53
54
  "@types/node": "26.1.2",
@@ -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.
@@ -60,7 +62,29 @@ The setup script configures GitHub Actions as the publishing source. It does
60
62
  not commit, push, merge, or deploy. Push the documentation workflow and project
61
63
  changes to its configured deployment branch to publish the website.
62
64
 
65
+ When a target publishes to npm, sdkgen generates `setup-npm-trust.sh` here.
66
+ For a repository on `github.com`, it registers each npm publish workflow as
67
+ the trusted publisher of its package, from the same model the workflows are
68
+ generated from, and leaves a package that is already set up alone:
69
+
70
+ ```sh
71
+ .sdk/admin/setup-npm-trust.sh --dry-run
72
+ .sdk/admin/setup-npm-trust.sh
73
+ .sdk/admin/setup-npm-trust.sh --check
74
+ ```
75
+
76
+ `--check` changes nothing and exits non-zero on any difference from what the
77
+ workflows need, including a trusted publisher that no workflow names;
78
+ `--replace` revokes those. It needs an npm login with publish rights. For a
79
+ repository hosted anywhere else, npm cannot trust GitHub Actions, so the script
80
+ says so and exits non-zero.
81
+
82
+ npm offers trusted publishing only for a package that already has a version,
83
+ so a new package's first version is published by hand, as `.sdk/PUBLISHING.md`
84
+ describes. Run the script straight after; every later release goes through the
85
+ generated workflow.
86
+
63
87
  The status launcher and the drift check come from create-sdkgen; the status
64
- reporting code comes from sdkgen. Docgen owns the generated Pages setup
65
- script. Put project-specific administration scripts beside them with
66
- different names.
88
+ reporting code and the npm trust script come from sdkgen. Docgen owns the
89
+ generated Pages setup script. Put project-specific administration scripts
90
+ beside them with different names.
@@ -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
@@ -31,7 +31,7 @@
31
31
  "typescript": ">=6.0",
32
32
  "@voxgig/apidef": ">=8.17.0",
33
33
  "@voxgig/model": ">=12.0.0",
34
- "@voxgig/sdkgen": ">=4.25.0",
34
+ "@voxgig/sdkgen": ">=4.30.0",
35
35
  "@voxgig/util": ">=0.5",
36
36
  "@voxgig/struct": ">=0.2",
37
37
  "jostraca": ">=0.39.0",
@@ -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
+ }