Skip to content

fix(template): the generated app could not be built at all - #39

Merged
rubenvdlinde merged 2 commits into
developmentfrom
fix/scaffold-does-not-build
Jul 27, 2026
Merged

fix(template): the generated app could not be built at all#39
rubenvdlinde merged 2 commits into
developmentfrom
fix/scaffold-does-not-build

Conversation

@rubenvdlinde

Copy link
Copy Markdown
Contributor

Fixes #38.

A wizard-generated app failed npm run build out of the box, so every repo OpenBuild published was uninstallable. Three separate causes, each found and verified by running a real build of the emitted 72-file scaffold.

1. terser-webpack-plugin was declared by nobody

[webpack-cli] Error: Cannot find module 'terser-webpack-plugin'
  requireStack: [ node_modules/@nextcloud/webpack-vue-config/webpack.config.js, … ]

@nextcloud/webpack-vue-config requires it but neither depends on it nor lists it as a peer (its peers are @babel/core, babel-loader, css-loader, node-polyfill-webpack-plugin, sass, sass-loader, style-loader, ts-loader). Nothing installed it, so webpack could not start.

2. @nextcloud/axios@2.6.0 dropped its require export — in a minor bump

@nextcloud/axios 2.5.2  exports {".": {types, import, require: "./dist/index.cjs"}}   ✅
@nextcloud/axios 2.6.0  exports {".": {types, import}}          — ESM only            ❌

@nextcloud/vue's CJS build require()s it, so the caret range ^2.5.0 floated into an unusable pair and webpack emitted 14 errors:

ERROR in ./node_modules/@nextcloud/vue/dist/index.cjs 85:0-27
Module not found: "." is not exported under the conditions
["require","module-sync","module","webpack","production","browser"] from @nextcloud/axios

Pinning the direct dependency is not enough — npm nests its own copy under @nextcloud/vue, and that is the copy that breaks:

before override:  @nextcloud/axios -> 2.5.2 ✅
                  @nextcloud/vue/node_modules/@nextcloud/axios -> 2.6.0 ❌   <-- the one that fails
after override:   @nextcloud/axios -> 2.5.2   (single copy, all nesting collapsed)

So this needs an overrides entry, which applies transitively.

3. Node's path was left unpolyfilled

@conduction/nextcloud-vue bundles a @nextcloud/dialogs chunk that reaches for Node's path. Webpack 5 stopped auto-polyfilling core modules, so the browser build could not resolve it.

Mapped just path to path-browserify rather than adding a blanket node-polyfill plugin — it is the only core module the dependency actually touches, and a blanket polyfill would grow every generated app's bundle for nothing.

Verification

Ran the emitted scaffold through a real npm install + npm run build after each fix:

errors
as shipped build cannot start (missing terser)
+ terser 14
+ axios override 1
+ path fallback 0 — real bundles produced

Only the pre-existing entrypoint-size warnings remain.

Suggested follow-up

A CI guard that generates an app and runs npm ci && npm run build. All three defects are invisible to any test that stops at "the files were generated", which is why a scaffold that never built shipped.

Related: #35 — push fails with a misleading github_unreachable; the real cause there is GitHub rejecting the tree because the scaffold ships 9 .github/workflows/* files and the brokered token lacks the workflow permission.

A wizard-generated app failed `npm run build` out of the box, so every repo
OpenBuild published was uninstallable. Three separate causes, each verified by
running a real build of the emitted 72-file scaffold.

1. `terser-webpack-plugin` was declared by nobody. `@nextcloud/webpack-vue-config`
   requires it but does not depend on it and does not list it as a peer, so
   nothing installed it and webpack could not even start:
   `[webpack-cli] Error: Cannot find module 'terser-webpack-plugin'`.

2. `@nextcloud/axios@2.6.0` DROPPED its `require` export condition — a breaking
   change shipped as a minor bump. `@nextcloud/vue`'s CJS build require()s it, so
   the caret range `^2.5.0` floated into an unusable pair and webpack reported 14
   copies of:
   `"." is not exported under the conditions ["require", …] from @nextcloud/axios`.
   Pinning the direct dependency is NOT enough — npm nests its own copy under
   `@nextcloud/vue`, which is the copy that actually breaks — so this needs an
   `overrides` entry to apply transitively. 2.5.2 still ships `./dist/index.cjs`.

3. `@conduction/nextcloud-vue` bundles a `@nextcloud/dialogs` chunk that reaches
   for Node's `path`. Webpack 5 stopped auto-polyfilling core modules, so the
   browser build could not resolve it. Mapping just `path` to `path-browserify`
   rather than adding a blanket node-polyfill plugin: it is the only core module
   the dependency touches, and a blanket polyfill grows every generated app's
   bundle for nothing.

Verified end to end on the emitted scaffold: `npm install` (1321 packages) then
`npm run build` goes from **14 errors → 1 → 0**, producing real bundles. Only
the pre-existing entrypoint-size warnings remain.

Worth a CI guard: generate an app and run `npm ci && npm run build`. All three
defects are invisible to any test that stops at "the files were generated".

Fixes #38.
Adding the @nextcloud/axios override REPLACED the existing libxmljs2 one
instead of joining it. libxmljs2 is a native XML parser with a history of
CVEs and `^0.37.0` is a security floor, so dropping it would have quietly
lowered the generated app's baseline while fixing its build. Both entries
belong in the map.
@rubenvdlinde
rubenvdlinde merged commit c87d8c4 into development Jul 27, 2026
2 checks passed
@rubenvdlinde
rubenvdlinde deleted the fix/scaffold-does-not-build branch July 27, 2026 21:04
rubenvdlinde added a commit that referenced this pull request Jul 27, 2026
…#40)

Publishing a generated app failed with 502 github_unreachable. GitHub was perfectly reachable — I verified /user, /repos/*, git refs GET and PATCH, blob create and tree create all succeeding through the same broker with the same credential before instrumenting the swallowed log line and finding the truth:

    POST /repos/{owner}/{repo}/git/trees -> 403
    {"message":"Resource not accessible by personal access token"}

The scaffold always emits 9 .github/workflows/* files, and writing those requires the token's workflow permission. A precise, actionable message from GitHub was being discarded in favour of a transport error that sends the reader at the network and the allow-rules.

Two fixes:
- 403 now maps to its own OUTCOME_FORBIDDEN / HTTP 403 rather than being folded into the gateway error.
- brokerJson() logged the swallowed cause at debug and did not log non-2xx responses at all. On any instance running a normal loglevel — which the fleet does deliberately after the 163GB-log incident — an operator saw a bare 502 and nothing else. Now logged at warning with method, path, status and body prefix, which is the whole diagnosis.

Merge note: this branch was stacked on #39 and still carried the package.json version that dropped the libxmljs2 security override. Merged development in and kept development's, so the pin survives.
rubenvdlinde added a commit that referenced this pull request Aug 5, 2026
#135)

The embedded template snapshot's appinfo/info.xml hardcoded
`<licence>agpl</licence>` while the very same file's description read "Free
and open source under the EUPL-1.2 license". Every app OpenBuild has ever
generated inherited that contradiction, and nothing failed.

The `{{license}}` token was already wired end to end — ExportJobService ->
RunExportJob -> PlaceholderResolver, defaulting to EUPL-1.2 at all three
layers — and reached exactly one file, src/manifest.json. info.xml never
consumed it. `<licence>{{license}}</licence>` closes that, and as a side
effect a caller who picks a licence now gets the licence they picked.

EUPL-1.2 is the schema-correct spelling, not a preference: the Nextcloud
appstore's own info.xsd enumerates `EUPL-1.2` as an allowed <licence> value.
It does NOT enumerate `eupl` — which is what upstream nextcloud-app-template's
`main` branch currently ships, and is separately invalid.

A test first, and shown to fail. testGeneratedAppDeclaresTheRequestedLicence()
exports a real app and asserts info.xml declares EUPL-1.2 and does not declare
agpl. Against the pre-fix snapshot it failed with the generated app's info.xml
in the message; after the one-line change all 3 integration tests and all 746
unit tests pass. Nothing in the suite covered this before: the existing
unresolved-placeholder assertion matches /\{\{[a-zA-Z]+\}\}/, and a hardcoded
wrong value contains no placeholder to be left unresolved.

Why this is a one-line Edit and not the whole-tree refresh docs/releasing.md
prescribes. The refresh is unsafe as written, measured three ways:

  1. There is no upstream ref this tree fast-forwards from. The declared
     sourceCommit 7ee06aae is not an ancestor of upstream main OR development;
     `git branch -r --contains` places it only on two abandoned wsl-rescue/*
     branches.

  2. The snapshot carries OpenBuild-only fixes upstream never received — at
     least 10 commits have edited files inside lib/Resources/template/ since,
     among them c87d8c4 "fix(template): the generated app could not be built
     at all (#39)" and 12da26f "fix(export): make exported app a Tier-4
     manifest consumer (ADR-024)". `rsync --delete` reverts both.

  3. The dialects differ. OpenBuild resolves {{token}}; upstream's info.xml
     uses {APP_NAME}/{APP_SUMMARY}/{APP_DESCRIPTION}. PlaceholderResolver does
     not know that dialect and the unresolved-placeholder assertion does not
     match it, so a refreshed tree would ship literal {APP_NAME} into every
     generated app and no test would fail.

.snapshot-meta.json and docs/releasing.md now say so at the point of use, so
the next agent does not run the rsync on the strength of the doc alone.
docs/releasing.md also now records that the "CI drift check" it describes does
not exist — no workflow mentions drift or nextcloud-app-template — which is
why the snapshot sat 86 days stale shipping the wrong licence.

.path-manifest.txt no longer lists .snapshot-meta.json: the regeneration
command in docs/releasing.md excludes it, so the checked-in manifest
disagreed with its own generator.

Co-authored-by: Ruben van der Linde <juan.claude@conduction.nl>
@rubenvdlinde
rubenvdlinde restored the fix/scaffold-does-not-build branch August 8, 2026 17:08
@rubenvdlinde
rubenvdlinde deleted the fix/scaffold-does-not-build branch August 14, 2026 09:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants