@voxgig/sdkgen-infrapack 0.0.6 → 0.0.8

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.
@@ -65,20 +65,32 @@ main: kit: target: 'seneca-provider': {
65
65
  # `repo` defaults to the SDK's own repository, so normally only the ref is
66
66
  # stated.
67
67
  #
68
- # `path` is the SUBDIRECTORY the SDK package sits in, because npm resolves
69
- # a git dependency against the repository root and sdkgen generates the
70
- # TypeScript SDK into `ts/`. It therefore defaults to `ts` — the layout
71
- # this toolchain produces — and npm spells it `#<ref>::path:<path>`. Set it
72
- # to `.` for an SDK whose package.json IS the repository root.
68
+ # WHICH KIND TO USE DEPENDS ON WHERE THE PACKAGE SITS. npm resolves a git
69
+ # dependency against the repository ROOT, and sdkgen generates the
70
+ # TypeScript SDK into `ts/` — so `git` suits an SDK that IS its repository
71
+ # root, and `release` suits this toolchain's own layout:
72
+ #
73
+ # kind: 'git' github:owner/repo#<ref>
74
+ # kind: 'release' https://github.com/owner/repo/releases/download/
75
+ # <ref>/<asset>.tgz
76
+ #
77
+ # A release asset is `npm pack` output attached to the tag; npm installs an
78
+ # https tarball natively and never looks at the repository layout. `asset`
79
+ # names the file when it is not the usual `<scope>-<name>-<version>.tgz`.
80
+ #
81
+ # There is no subdirectory option, deliberately. npm's spec parser accepts
82
+ # `#<ref>::path:ts` and reports a gitSubdir, so it reads as supported — but
83
+ # the INSTALLER ignores it and fails with ENOENT, on linux, macOS and
84
+ # Windows alike. A knob that produces an uninstallable dependency is worse
85
+ # than no knob.
73
86
  #
74
87
  # `spec` states the whole dependency value outright and wins over
75
- # everything, for anything the shorthand cannot express — a release-tarball
76
- # URL, say.
88
+ # everything.
77
89
  sdk: dep: {
78
- kind: *'npm' | 'git'
90
+ kind: *'npm' | 'git' | 'release'
79
91
  ref: *'' | string
80
92
  repo: *'' | string
81
- path: *'ts' | string
93
+ asset: *'' | string
82
94
  spec: *'' | string
83
95
  }
84
96
 
@@ -1741,14 +1741,30 @@ ${!provider.liveApp ? '' : `
1741
1741
  # lets npm exchange a GitHub OIDC token for a short-lived publish credential,
1742
1742
  # and provenance is attached automatically.
1743
1743
  #
1744
+ # TWO JOBS, BECAUSE THEY NEED DIFFERENT PRIVILEGES.
1745
+ #
1746
+ # verify contents: read, and nothing else. Runs npm install, the build
1747
+ # and the tests — i.e. dependency lifecycle scripts and project
1748
+ # code. It holds no publish credential.
1749
+ # publish id-token: write, contents: read. Installs NO project
1750
+ # dependencies and runs NO project code: this package ships
1751
+ # \`dist\`, which is committed, so nothing needs building to pack.
1752
+ #
1753
+ # THE SPLIT IS THE POINT. A compromised dependency lifecycle script can ask
1754
+ # the runner for any OIDC token the JOB is permitted to mint, so a job that
1755
+ # both installs dependencies and holds \`id-token: write\` can be made to
1756
+ # publish as this package before its own gates finish. Keeping the install in
1757
+ # a job with no id-token, and the credential in a job that installs nothing,
1758
+ # is what makes the isolation real rather than nominal.
1759
+ #
1744
1760
  # The trusted publisher must be registered on npmjs.com for this package
1745
1761
  # against THIS filename (publish.yml); renaming this file breaks publishing
1746
1762
  # until the npm-side config is updated to match.
1747
1763
  #
1748
- # npm cannot publish a package's FIRST version this way — the settings page
1749
- # that configures a trusted publisher only exists once a version is there. So
1750
- # release ${provider.version} by hand once, configure the publisher, and every
1751
- # release after that is a tag push.
1764
+ # npm cannot configure a trusted publisher for a package that does not exist
1765
+ # yet — the settings page appears once a version is on the registry. So the
1766
+ # FIRST version of a new package is published by hand, once, with an
1767
+ # authenticated npm; every release after that is a tag push.
1752
1768
  #
1753
1769
  # Release flow: bump the version in the SDK model
1754
1770
  # (\`main: kit: target: 'seneca-provider': publish: version\`), regenerate,
@@ -1762,25 +1778,24 @@ on:
1762
1778
  workflow_dispatch:
1763
1779
 
1764
1780
  jobs:
1765
- publish:
1766
- name: npm publish
1781
+ verify:
1782
+ name: verify
1767
1783
  runs-on: ubuntu-latest
1768
1784
  timeout-minutes: 15
1785
+
1786
+ # Deliberately the default-minimum. This job runs third-party code.
1769
1787
  permissions:
1770
- id-token: write
1771
1788
  contents: read
1772
1789
 
1790
+ outputs:
1791
+ version: \${{ steps.version.outputs.version }}
1792
+
1773
1793
  steps:
1774
1794
  - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
1775
1795
 
1776
1796
  - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
1777
1797
  with:
1778
1798
  node-version: 24.x
1779
- registry-url: 'https://registry.npmjs.org'
1780
-
1781
- # Trusted publishing requires npm >= 11.5.1.
1782
- - name: Use a trusted-publishing capable npm
1783
- run: npm install -g npm@latest
1784
1799
 
1785
1800
  # install, not ci: this package does not commit a lockfile.
1786
1801
  - run: npm install
@@ -1789,11 +1804,10 @@ jobs:
1789
1804
  # needs it installed explicitly.
1790
1805
  #
1791
1806
  # --no-save IS LOAD-BEARING. Without it npm rewrites the peer ranges in
1792
- # package.json to carets on whatever it resolved, and \`npm publish\`
1793
- # below then ships that rewritten manifest — so an authored \`>=26\`
1794
- # reaches consumers as \`^28.1.0\` and the package refuses to install for
1795
- # anyone on a newer major. The repo looks fine; only the artifact is
1796
- # narrowed. Install into node_modules, leave the manifest alone.
1807
+ # package.json to carets on whatever it resolved, and a later publish
1808
+ # ships that rewritten manifest — so an authored \`>=26\` reaches
1809
+ # consumers as \`^28.1.0\` and the package refuses to install for anyone
1810
+ # on a newer major. The repo looks fine; only the artifact is narrowed.
1797
1811
  - run: npm i --no-save seneca seneca-entity seneca-promisify @seneca/provider @seneca/env
1798
1812
 
1799
1813
  - run: npm run build
@@ -1802,15 +1816,75 @@ jobs:
1802
1816
  # The tag must match what the manifest declares, or a tag push silently
1803
1817
  # republishes whatever version happens to be in package.json.
1804
1818
  - name: Check the tag matches the manifest version
1819
+ id: version
1820
+ run: |
1821
+ set -euo pipefail
1822
+ PKG=\$(node -p "require('./package.json').version")
1823
+ if [ "\${GITHUB_REF_TYPE:-}" = "tag" ]; then
1824
+ TAG="\${GITHUB_REF_NAME#v}"
1825
+ if [ "\$TAG" != "\$PKG" ]; then
1826
+ echo "::error::tag v\$TAG does not match package.json \$PKG"
1827
+ exit 1
1828
+ fi
1829
+ fi
1830
+ echo "version=\$PKG" >> "\$GITHUB_OUTPUT"
1831
+
1832
+ # WHAT THE PUBLISH JOB WILL PACK, checked HERE where the code already
1833
+ # ran. That job builds nothing, so a missing or stale \`dist\` would
1834
+ # otherwise be discovered by consumers rather than by this workflow.
1835
+ - name: The committed dist matches the source
1805
1836
  run: |
1806
- TAG="\${GITHUB_REF_NAME#v}"
1807
- PKG=$(node -p "require('./package.json').version")
1808
- if [ "$TAG" != "$PKG" ]; then
1809
- echo "tag v$TAG does not match package.json $PKG"
1837
+ set -euo pipefail
1838
+ if ! git diff --quiet -- dist; then
1839
+ echo "::error::dist/ is not up to date with src/ -- rebuild and commit it"
1840
+ git diff --stat -- dist
1810
1841
  exit 1
1811
1842
  fi
1812
1843
 
1844
+ publish:
1845
+ name: npm publish
1846
+ needs: verify
1847
+ runs-on: ubuntu-latest
1848
+ timeout-minutes: 15
1849
+
1850
+ # The ONLY job holding the publish credential — and it installs no
1851
+ # project dependencies and runs no project code. See the header.
1852
+ permissions:
1853
+ id-token: write
1854
+ contents: read
1855
+
1856
+ steps:
1857
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
1858
+
1859
+ - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
1860
+ with:
1861
+ node-version: 24.x
1862
+ registry-url: 'https://registry.npmjs.org'
1863
+
1864
+ # Trusted publishing requires npm >= 11.5.1. This is npm itself, not a
1865
+ # project dependency: no package.json here is consulted.
1866
+ - name: Use a trusted-publishing capable npm
1867
+ run: npm install -g npm@latest
1868
+
1869
+ # THE REGISTRY IS THE SOURCE OF TRUTH FOR "IS THIS RELEASED", not the
1870
+ # tag. A re-run of a workflow that already published would otherwise
1871
+ # fail on a version conflict and report a red release that in fact
1872
+ # succeeded.
1873
+ - name: Is this version already on npm?
1874
+ id: registry
1875
+ env:
1876
+ VERSION: \${{ needs.verify.outputs.version }}
1877
+ run: |
1878
+ set -euo pipefail
1879
+ if npm view "${provider.pkgName}@\$VERSION" version >/dev/null 2>&1; then
1880
+ echo "published=true" >> "\$GITHUB_OUTPUT"
1881
+ echo "\$VERSION is already on npm — skipping publish"
1882
+ else
1883
+ echo "published=false" >> "\$GITHUB_OUTPUT"
1884
+ fi
1885
+
1813
1886
  - name: Publish to npm
1887
+ if: steps.registry.outputs.published == 'false'
1814
1888
  run: npm publish --access public
1815
1889
  `)
1816
1890
  })
@@ -708,14 +708,21 @@ const Main = cmp(function Main(props: any) {
708
708
  // into every generated provider's CI.
709
709
  const liveApp = true === live.app || (loopback(specBase) && '' === (live.base || ''))
710
710
 
711
+ const sdkDep = sdkDependency(model, target, {
712
+ sdkPkg, sdkVersion, sdkRepoUrl: repoInfo(model).repoUrl,
713
+ })
714
+
711
715
  const provider = {
712
716
  Name, lower, ENV, sdkClass, pluginName, fileBase,
713
717
  sdkPkg, sdkVersion, entities,
714
- // Whether the SDK dependency is a git tag rather than a registry
715
- // package. The generated CI note says which, because "npm install is all
716
- // you need" stops being true the moment git is in the path.
717
- sdkGit: sdkDependency(model, target, { sdkVersion, sdkRepoUrl: repoInfo(model).repoUrl })
718
- .startsWith('github:'),
718
+ // The SDK dependency, resolved ONCE. PackageJson emits it and the CI
719
+ // note describes it, and computing it twice is how the second caller
720
+ // came to pass a half-built provider object into it.
721
+ sdkDep,
722
+ // Whether that dependency comes from outside a registry. The generated
723
+ // CI note says so, because "npm install is all you need" stops being
724
+ // true the moment git or a tarball URL is in the path.
725
+ sdkGit: !sdkDep.startsWith('^'),
719
726
  repoUrl: repo.url,
720
727
  // The SDK's own repo, for pointing at the companion test server which is
721
728
  // only distributed in source.
@@ -790,13 +797,21 @@ const Main = cmp(function Main(props: any) {
790
797
  // `kind: 'git'` points at a GIT TAG instead, which needs no registry.
791
798
  //
792
799
  // NPM RESOLVES A GIT DEPENDENCY AGAINST THE REPOSITORY ROOT, and sdkgen
793
- // generates the TypeScript SDK into `ts/` — so the bare
794
- // `github:owner/repo#ref` every example shows would install a directory with
795
- // no package.json in it. npm spells the subdirectory `#<ref>::path:<sub>`
796
- // (npm-package-arg resolves that to gitSubdir), and `path` therefore
797
- // defaults to `ts` rather than to nothing: the default has to match the
798
- // layout this toolchain actually produces, or the shorthand is a trap. `.`
799
- // means the package IS the repository root.
800
+ // generates the TypeScript SDK into `ts/`. So `kind: 'git'` suits an SDK
801
+ // whose package.json IS the repository root, and NOT the layout this
802
+ // toolchain produces.
803
+ //
804
+ // THE `::path:` SUBDIRECTORY SYNTAX DOES NOT WORK, and it looks like it
805
+ // does. npm-package-arg parses `#<ref>::path:ts` and reports
806
+ // `gitSubdir: /ts`, so a spec built that way reads as correct — but the
807
+ // INSTALLER ignores it: npm clones the repository and opens package.json at
808
+ // the clone root, failing with ENOENT on linux, macOS and Windows alike.
809
+ // That was measured, on all three, after the parser had said otherwise.
810
+ //
811
+ // `kind: 'release'` is the form that works for a package in a subdirectory:
812
+ // a GitHub release asset, which is `npm pack` output attached to the tag.
813
+ // npm installs an https tarball natively and never looks at the repository
814
+ // layout at all.
800
815
  //
801
816
  // `spec` still wins over all of it, for anything the shorthand cannot say.
802
817
  function sdkDependency(model: any, target: any, provider: any): string {
@@ -807,7 +822,8 @@ function sdkDependency(model: any, target: any, provider: any): string {
807
822
  return spec
808
823
  }
809
824
 
810
- if ('git' !== String(dep.kind || 'npm')) {
825
+ const kind = String(dep.kind || 'npm')
826
+ if ('git' !== kind && 'release' !== kind) {
811
827
  return `^${provider.sdkVersion}`
812
828
  }
813
829
 
@@ -837,11 +853,17 @@ function sdkDependency(model: any, target: any, provider: any): string {
837
853
  'on, e.g. `sdk.dep.ref: "v' + provider.sdkVersion + '"`.')
838
854
  }
839
855
 
840
- // `.` (and empty) mean the repository root, which needs no path segment.
841
- const sub = String(dep.path ?? 'ts').trim().replace(/^\/+|\/+$/g, '')
856
+ if ('release' === kind) {
857
+ // The asset `npm pack` produces: scope and name flattened, then the
858
+ // version. `asset` overrides it for a project that names its own.
859
+ const asset = String(dep.asset || '').trim() || (
860
+ provider.sdkPkg.replace(/^@/, '').replace(/\//g, '-') +
861
+ '-' + provider.sdkVersion + '.tgz')
862
+
863
+ return `https://github.com/${repo}/releases/download/${ref}/${asset}`
864
+ }
842
865
 
843
- return `github:${repo}#${ref}` +
844
- ('' === sub || '.' === sub ? '' : `::path:${sub}`)
866
+ return `github:${repo}#${ref}`
845
867
  }
846
868
 
847
869
 
@@ -948,7 +970,7 @@ const PackageJson = cmp(function PackageJson(props: any) {
948
970
  // The SDK this plugin wraps. Published-and-pinned by default; a git
949
971
  // tag when the project says so, because an unpublished SDK otherwise
950
972
  // leaves this package unable to install at all. See sdkDependency.
951
- [provider.sdkPkg]: sdkDependency(model, target, provider),
973
+ [provider.sdkPkg]: provider.sdkDep,
952
974
  ...dep('prod'),
953
975
  },
954
976
  peerDependencies: dep('peer'),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voxgig/sdkgen-infrapack",
3
- "version": "0.0.6",
3
+ "version": "0.0.8",
4
4
  "description": "Infrastructure-provider targets for the Voxgig SDK Generator: Seneca provider.",
5
5
  "type": "commonjs",
6
6
  "license": "MIT",
@@ -3,7 +3,7 @@
3
3
  "package": 1
4
4
  },
5
5
  "name": "@voxgig/sdkgen-infrapack",
6
- "version": "0.0.6",
6
+ "version": "0.0.8",
7
7
  "engines": {
8
8
  "sdkgen": ">=4.12.0"
9
9
  },