@omega.js/desktop 0.52.0 → 0.54.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/README.md +53 -48
- package/dist/assets/css/core/_initialize.scss +1 -1
- package/dist/assets/js/core/app-shell.js +14 -15
- package/dist/assets/themes/_template/_theme.js +6 -6
- package/dist/assets/themes/base/_includes/global/sections/account.html +4 -4
- package/dist/assets/themes/base/_includes/global/sections/app-sidebar.html +5 -5
- package/dist/assets/themes/base/_layouts/frontend/pages/account/index.html +3 -3
- package/dist/assets/themes/base/_layouts/frontend/pages/alternatives/index.html +1 -1
- package/dist/assets/themes/base/_layouts/frontend/pages/blog/tags/tag.html +1 -1
- package/dist/assets/themes/base/_layouts/frontend/pages/payment/confirmation.html +1 -1
- package/dist/assets/themes/base/_sections/marketing/newsletter-cta/section.js +2 -3
- package/dist/assets/themes/base/_sections/verts/unit/section.html +1 -1
- package/dist/assets/themes/base/_sections/verts/unit/section.js +3 -4
- package/dist/assets/themes/base/_theme.js +4 -3
- package/dist/assets/themes/bootstrap/_theme.js +2 -2
- package/dist/assets/themes/bootstrap/overrides/_links.scss +1 -1
- package/dist/assets/themes/classy/_theme.js +8 -6
- package/dist/assets/themes/classy/css/marketing/_sections.scss +1 -2
- package/dist/assets/themes/classy/js/hero-demo-form.js +3 -2
- package/dist/assets/themes/neobrutalism/_theme.js +5 -5
- package/dist/assets/themes/neobrutalism/js/pages/test/libraries/layers/index.js +1 -1
- package/dist/assets/themes/newsflash/_theme.js +6 -6
- package/dist/assets/themes/newsflash/js/pages/test/libraries/layers/index.js +1 -1
- package/dist/build.js +87 -111
- package/dist/cli-run.js +4 -1
- package/dist/cli.js +3 -3
- package/dist/commands/build.js +2 -2
- package/dist/commands/cdp/capture.js +2 -2
- package/dist/commands/cdp/client.js +1 -1
- package/dist/commands/cdp/quit.js +2 -2
- package/dist/commands/cdp/relaunch.js +2 -2
- package/dist/commands/cdp/theme.js +1 -1
- package/dist/commands/cdp.js +1 -1
- package/dist/commands/clean.js +4 -5
- package/dist/commands/deploy.js +4 -4
- package/dist/commands/dev.js +25 -0
- package/dist/commands/finalize-release.js +4 -4
- package/dist/commands/launch.js +2 -2
- package/dist/commands/lib/deploy-precheck.js +3 -3
- package/dist/commands/lib/ensure-target.js +18 -23
- package/dist/commands/lib/migrate.js +17 -0
- package/dist/commands/logs.js +1 -1
- package/dist/commands/package.js +2 -2
- package/dist/commands/publish.js +2 -2
- package/dist/commands/release.js +4 -4
- package/dist/commands/runner.js +11 -11
- package/dist/commands/sign-windows.js +5 -5
- package/dist/commands/test.js +8 -8
- package/dist/commands/update.js +7 -6
- package/dist/commands/validate-certs.js +4 -4
- package/dist/commands/version.js +3 -3
- package/dist/defaults/.github/workflows/build.yml +18 -18
- package/dist/defaults/_.gitignore +0 -2
- package/dist/defaults/_mas/README.md +3 -3
- package/dist/defaults/config/certs/README.md +1 -1
- package/dist/defaults/config/omega.json5 +43 -43
- package/dist/defaults/docs/README.md +3 -3
- package/dist/defaults/gulpfile.js +1 -1
- package/dist/defaults/hooks/build/post.js +2 -2
- package/dist/defaults/hooks/build/pre.js +2 -2
- package/dist/defaults/hooks/deploy/pre.js +1 -1
- package/dist/defaults/hooks/notarize/post.js +2 -2
- package/dist/defaults/hooks/release/post.js +2 -2
- package/dist/defaults/hooks/release/pre.js +2 -2
- package/dist/defaults/src/assets/js/components/about/index.js +3 -5
- package/dist/defaults/src/assets/js/components/main/index.js +3 -5
- package/dist/defaults/src/assets/js/components/settings/index.js +3 -5
- package/dist/defaults/src/assets/scss/pages/about.scss +1 -1
- package/dist/defaults/src/assets/scss/pages/main.scss +1 -1
- package/dist/defaults/src/assets/scss/pages/settings.scss +1 -1
- package/dist/defaults/src/integrations/context-menu/index.js +13 -13
- package/dist/defaults/src/integrations/menu/index.js +8 -8
- package/dist/defaults/src/integrations/tray/index.js +12 -12
- package/dist/defaults/src/main.js +5 -7
- package/dist/defaults/src/preload.js +4 -6
- package/dist/defaults/test/README.md +5 -5
- package/dist/defaults/test/_init.js +1 -1
- package/dist/gulp/main.js +9 -10
- package/dist/gulp/tasks/audit.js +11 -14
- package/dist/gulp/tasks/build-config.js +8 -8
- package/dist/gulp/tasks/bundle.js +16 -16
- package/dist/gulp/tasks/defaults.js +3 -3
- package/dist/gulp/tasks/distribute.js +2 -2
- package/dist/gulp/tasks/html.js +9 -9
- package/dist/gulp/tasks/package-quick.js +3 -3
- package/dist/gulp/tasks/package.js +3 -3
- package/dist/gulp/tasks/release.js +3 -3
- package/dist/gulp/tasks/sass.js +6 -6
- package/dist/gulp/tasks/serve.js +4 -4
- package/dist/hooks/notarize-artifacts.js +1 -1
- package/dist/hooks/notarize.js +1 -1
- package/dist/index.js +5 -8
- package/dist/lib/_environment-mixin.js +50 -0
- package/dist/lib/_lifecycle-mixin.js +45 -0
- package/dist/lib/analytics.js +33 -35
- package/dist/lib/app-state.js +14 -14
- package/dist/lib/auth-flow.js +18 -18
- package/dist/lib/auth-persistence.js +12 -12
- package/dist/lib/auth.js +421 -0
- package/dist/lib/auto-updater.js +49 -49
- package/dist/lib/context-menu.js +13 -13
- package/dist/lib/context.js +19 -19
- package/dist/lib/deep-link.js +34 -34
- package/dist/lib/fontawesome.js +5 -5
- package/dist/lib/ipc.js +4 -4
- package/dist/lib/menu.js +25 -25
- package/dist/lib/protocol.js +5 -5
- package/dist/lib/remote-config.js +22 -22
- package/dist/lib/remote-scripts.js +21 -21
- package/dist/lib/restart-manager/index.js +29 -29
- package/dist/lib/restart-manager/install.js +1 -1
- package/dist/lib/restart-manager/protocol.js +1 -1
- package/dist/lib/sign-helpers/exec-with-limit.js +1 -1
- package/dist/lib/sign-helpers/sign-events.js +1 -1
- package/dist/lib/startup.js +18 -13
- package/dist/lib/storage.js +10 -10
- package/dist/lib/templating.js +16 -16
- package/dist/lib/theme.js +10 -10
- package/dist/lib/tray.js +27 -27
- package/dist/lib/usage.js +11 -11
- package/dist/lib/window-manager.js +26 -26
- package/dist/main.js +399 -483
- package/dist/preload.js +236 -176
- package/dist/renderer.js +417 -391
- package/dist/test/fixtures/consumer-app/config/omega.json5 +1 -1
- package/dist/test/fixtures/consumer-app/src/assets/js/components/main/index.js +8 -11
- package/dist/test/fixtures/consumer-app/src/main.js +5 -7
- package/dist/test/fixtures/consumer-app/src/preload.js +2 -2
- package/dist/test/harness/boot-entry.js +22 -20
- package/dist/test/harness/main-entry.js +31 -30
- package/dist/test/harness/renderer-entry.js +5 -5
- package/dist/test/harness/renderer-preload.js +137 -141
- package/dist/test/index.js +10 -10
- package/dist/test/runner.js +2 -2
- package/dist/test/runners/boot.js +7 -6
- package/dist/test/runners/electron.js +3 -2
- package/dist/test/runners/render-event.js +2 -2
- package/dist/test/suites/boot/consumer-app-boots.test.js +57 -26
- package/dist/test/suites/boot/restart-manager.test.js +2 -2
- package/dist/test/suites/boot/storage-bundled.test.js +5 -5
- package/dist/test/suites/boot/theme.test.js +13 -13
- package/dist/test/suites/build/audit.test.js +21 -8
- package/dist/test/suites/build/auth-persistence-resolve.test.js +6 -6
- package/dist/test/suites/build/boot-fixture.test.js +2 -2
- package/dist/test/suites/build/boot-runner-timeout.test.js +6 -5
- package/dist/test/suites/build/brand-scss.test.js +1 -1
- package/dist/test/suites/build/build-json-bake.test.js +1 -1
- package/dist/test/suites/build/build-workflow-jobs.test.js +2 -2
- package/dist/test/suites/build/cli.test.js +30 -2
- package/dist/test/suites/build/config-schema.test.js +5 -5
- package/dist/test/suites/build/defaults-em-dash.test.js +22 -0
- package/dist/test/suites/build/defaults-scaffold.test.js +21 -7
- package/dist/test/suites/build/deploy-direct.test.js +7 -5
- package/dist/test/suites/build/deploy-dispatch.test.js +2 -1
- package/dist/test/suites/build/deploy-hook.test.js +7 -5
- package/dist/test/suites/build/dev-verb.test.js +67 -0
- package/dist/test/suites/build/ensure-target.test.js +13 -5
- package/dist/test/suites/build/env-delivery.test.js +2 -2
- package/dist/test/suites/build/esm-only-dependency.test.js +2 -2
- package/dist/test/suites/build/exports.test.js +9 -8
- package/dist/test/suites/build/get-config.test.js +4 -4
- package/dist/test/suites/build/manifest-deps.test.js +1 -1
- package/dist/test/suites/build/merge-line-files.test.js +7 -7
- package/dist/test/suites/build/migrate.test.js +29 -0
- package/dist/test/suites/build/omega-shell.test.js +34 -2
- package/dist/test/suites/build/omega.test.js +350 -0
- package/dist/test/suites/build/project-scripts-deps.test.js +6 -10
- package/dist/test/suites/build/renderer-auth-bridge.test.js +211 -78
- package/dist/test/suites/build/runner-env-write.test.js +73 -0
- package/dist/test/suites/build/runner.test.js +11 -10
- package/dist/test/suites/build/sentry.test.js +2 -2
- package/dist/test/suites/build/setup-scripts.test.js +27 -0
- package/dist/test/suites/build/sign-windows-e2e.test.js +2 -2
- package/dist/test/suites/build/templating.test.js +3 -3
- package/dist/test/suites/build/test-stealth.test.js +7 -9
- package/dist/test/suites/build/url-helpers.test.js +55 -56
- package/dist/test/suites/build/validate-config.test.js +15 -4
- package/dist/test/suites/build/verb-logs.test.js +20 -0
- package/dist/test/suites/build/wave5-pins.test.js +2 -2
- package/dist/test/suites/main/analytics.test.js +59 -59
- package/dist/test/suites/main/app-state.test.js +66 -66
- package/dist/test/suites/main/auth-flow.test.js +41 -41
- package/dist/test/suites/main/auth-persistence.test.js +54 -43
- package/dist/test/suites/main/{client-bridge.integration.test.js → auth.integration.test.js} +10 -9
- package/dist/test/suites/main/auth.test.js +336 -0
- package/dist/test/suites/main/auto-updater.test.js +134 -134
- package/dist/test/suites/main/boot-sequence.test.js +37 -49
- package/dist/test/suites/main/context-menu.test.js +51 -50
- package/dist/test/suites/main/context.test.js +25 -25
- package/dist/test/suites/main/deep-link.test.js +74 -74
- package/dist/test/suites/main/fontawesome.test.js +27 -27
- package/dist/test/suites/main/ipc.test.js +35 -35
- package/dist/test/suites/main/menu.test.js +101 -100
- package/dist/test/suites/main/protocol.test.js +19 -19
- package/dist/test/suites/main/remote-config.test.js +63 -63
- package/dist/test/suites/main/remote-scripts.test.js +103 -103
- package/dist/test/suites/main/request.test.js +71 -0
- package/dist/test/suites/main/restart-manager.test.js +33 -33
- package/dist/test/suites/main/startup-paths-and-ua.test.js +4 -4
- package/dist/test/suites/main/startup.test.js +26 -26
- package/dist/test/suites/main/stealth-window.test.js +1 -1
- package/dist/test/suites/main/storage.test.js +25 -25
- package/dist/test/suites/main/theme.test.js +34 -34
- package/dist/test/suites/main/tray.test.js +79 -79
- package/dist/test/suites/main/url-helpers.test.js +133 -133
- package/dist/test/suites/main/usage.test.js +25 -25
- package/dist/test/suites/main/window-bounds.test.js +27 -27
- package/dist/test/suites/main/window-manager.test.js +44 -44
- package/dist/test/suites/renderer/analytics-bridge.test.js +5 -5
- package/dist/test/suites/renderer/cross-context-helpers.test.js +37 -31
- package/dist/test/suites/renderer/round-trip.test.js +3 -3
- package/dist/test/suites/renderer/tooltips.test.js +15 -15
- package/dist/test/suites/renderer/{window-em-surface.test.js → window-desktop-surface.test.js} +28 -8
- package/dist/test/utils/extended-mode-warning.js +1 -1
- package/dist/utils/boot-harness.js +56 -0
- package/dist/utils/build-pipeline.js +4 -4
- package/dist/utils/mode-helpers.js +2 -15
- package/dist/utils/runner-env.js +13 -28
- package/dist/utils/ship-keys.js +3 -3
- package/dist/utils/signing-status.js +51 -0
- package/dist/utils/test-events.js +7 -0
- package/dist/utils/test-stealth.js +6 -6
- package/dist/utils/url-helpers.js +52 -42
- package/dist/utils/user-agent.js +44 -0
- package/dist/vendor/account/engine.js +3 -3
- package/dist/vendor/account/index.js +14 -45
- package/dist/vendor/account/resolve-account.js +44 -0
- package/dist/vendor/account/schema.js +1 -1
- package/dist/vendor/account/user.js +99 -0
- package/dist/vendor/config/client-config.js +1 -1
- package/dist/vendor/config/company.js +46 -14
- package/dist/vendor/config/defaults.js +30 -7
- package/dist/vendor/config/edit.js +25 -3
- package/dist/vendor/config/env-delivery.js +1 -1
- package/dist/vendor/config/env-schema.js +3 -6
- package/dist/vendor/config/env.js +34 -22
- package/dist/vendor/config/environment.js +11 -30
- package/dist/vendor/config/index.js +21 -28
- package/dist/vendor/config/load.js +16 -9
- package/dist/vendor/config/platforms.js +1 -1
- package/dist/vendor/config/repo.js +10 -27
- package/dist/vendor/config/schema-client.js +64 -0
- package/dist/vendor/config/schema-cloud.js +38 -0
- package/dist/vendor/config/schema-manager.js +118 -0
- package/dist/vendor/config/schema-overrides.js +68 -0
- package/dist/vendor/config/schema.js +104 -160
- package/dist/vendor/config/site-global.js +2 -3
- package/dist/vendor/config/validate.js +97 -78
- package/dist/vendor/config/winback.js +1 -1
- package/dist/vendor/devkit/actions-secrets.js +1 -1
- package/dist/vendor/devkit/agents-md.js +233 -0
- package/dist/vendor/devkit/attach-log-file.js +16 -2
- package/dist/vendor/devkit/build-json.js +1 -1
- package/dist/vendor/devkit/ci-workflows.js +30 -30
- package/dist/vendor/devkit/cli-router.js +16 -11
- package/dist/vendor/devkit/defaults-engine.js +15 -51
- package/dist/vendor/devkit/deploy-snapshot.js +44 -9
- package/dist/vendor/devkit/env-lines.js +183 -0
- package/dist/vendor/devkit/local.js +64 -10
- package/dist/vendor/devkit/lockfile.js +32 -13
- package/dist/vendor/devkit/logger.js +7 -2
- package/dist/vendor/devkit/merge-line-files.js +219 -177
- package/dist/vendor/devkit/omega-bin.js +208 -111
- package/dist/vendor/devkit/preludes/docs-sync.js +52 -0
- package/dist/vendor/devkit/preludes/index.js +1 -0
- package/dist/vendor/devkit/target-picker.js +45 -0
- package/dist/vendor/devkit/test/dashed-files.js +37 -0
- package/dist/vendor/devkit/test/run-verb-under-tee.js +71 -0
- package/dist/vendor/devkit/test/runner-core.js +6 -6
- package/dist/vendor/devkit/update.js +15 -15
- package/dist/vendor/devkit/verb-scripts.js +40 -0
- package/dist/vendor/devkit/verbs.js +170 -0
- package/dist/vendor/monitoring/env.js +2 -2
- package/dist/vendor/monitoring/index.js +1 -1
- package/dist/vendor/monitoring/main.js +1 -1
- package/dist/vendor/monitoring/preload.js +1 -1
- package/dist/vendor/monitoring/renderer.js +1 -1
- package/package.json +19 -26
- package/dist/commands/install.js +0 -37
- package/dist/defaults/AGENTS.md +0 -110
- package/dist/defaults/CLAUDE.md +0 -1
- package/dist/lib/client-bridge.js +0 -374
- package/dist/lib/logger.js +0 -4
- package/dist/test/suites/build/manager.test.js +0 -213
- package/dist/test/suites/main/client-bridge.test.js +0 -262
- package/dist/vendor/config/env-retired.js +0 -137
- package/dist/vendor/config/retired-keys.js +0 -635
- package/docs/analytics.md +0 -140
- package/docs/app-state.md +0 -92
- package/docs/audit.md +0 -69
- package/docs/auto-updater.md +0 -243
- package/docs/boot-sequence.md +0 -39
- package/docs/build-system.md +0 -169
- package/docs/cdp-debugging.md +0 -169
- package/docs/client-bridge.md +0 -269
- package/docs/common-mistakes.md +0 -21
- package/docs/config-schema.md +0 -120
- package/docs/context-menu.md +0 -112
- package/docs/context.md +0 -81
- package/docs/css.md +0 -90
- package/docs/deep-link.md +0 -186
- package/docs/environment-detection.md +0 -112
- package/docs/fontawesome.md +0 -107
- package/docs/hooks.md +0 -89
- package/docs/icons.md +0 -79
- package/docs/index.md +0 -317
- package/docs/installer-options.md +0 -165
- package/docs/ipc.md +0 -61
- package/docs/lib-modules.md +0 -53
- package/docs/logging.md +0 -229
- package/docs/menu.md +0 -160
- package/docs/releasing.md +0 -239
- package/docs/remote-config.md +0 -118
- package/docs/remote-scripts.md +0 -144
- package/docs/restart-manager.md +0 -144
- package/docs/runner.md +0 -290
- package/docs/sentry.md +0 -97
- package/docs/shared/agent-docs.md +0 -89
- package/docs/shared/analytics.md +0 -612
- package/docs/shared/brands.md +0 -57
- package/docs/shared/breaking-changes.md +0 -851
- package/docs/shared/config.md +0 -1952
- package/docs/shared/deploys.md +0 -341
- package/docs/shared/icons.md +0 -219
- package/docs/shared/local-dev.md +0 -167
- package/docs/shared/logging.md +0 -205
- package/docs/shared/monitoring.md +0 -167
- package/docs/shared/publishing.md +0 -187
- package/docs/shared/rulings.md +0 -34
- package/docs/shared/testing.md +0 -147
- package/docs/shared/theming.md +0 -629
- package/docs/shared/translation.md +0 -333
- package/docs/shared/updates.md +0 -61
- package/docs/signing.md +0 -293
- package/docs/startup.md +0 -142
- package/docs/storage.md +0 -59
- package/docs/templating.md +0 -101
- package/docs/test-boot-layer.md +0 -157
- package/docs/test-framework.md +0 -362
- package/docs/themes.md +0 -149
- package/docs/tooltips.md +0 -99
- package/docs/tray.md +0 -164
- package/docs/usage.md +0 -58
- package/docs/verts.md +0 -62
- package/docs/windows.md +0 -149
|
@@ -5,17 +5,17 @@
|
|
|
5
5
|
* That is correct for a STANDALONE target (the target dir is the git root) and dead on
|
|
6
6
|
* arrival inside a brand monorepo: GitHub executes workflows from the REPO
|
|
7
7
|
* ROOT's `.github/workflows/` only, so `targets/extension/.github/workflows/publish.yml`
|
|
8
|
-
* never runs
|
|
8
|
+
* never runs: CI builds and store publishes silently do not exist.
|
|
9
9
|
*
|
|
10
10
|
* In a monorepo the target's workflow is COMPOSED into the root dir instead: one
|
|
11
11
|
* file per target (`<target>-<workflow>.yml`), every post-checkout `run:` step scoped
|
|
12
|
-
* to the target's path, regenerated from the framework template on every setup
|
|
12
|
+
* to the target's path, regenerated from the framework template on every setup, so
|
|
13
13
|
* a re-run updates the target's own file and can never duplicate a job. Each target's
|
|
14
14
|
* runs also get their own concurrency group, so one target's deploy never cancels
|
|
15
15
|
* another's.
|
|
16
16
|
*
|
|
17
17
|
* Scoping is by working directory, not by a `paths:` trigger filter: OMEGA
|
|
18
|
-
* workflows carry NO push triggers by design (deliberate deploys
|
|
18
|
+
* workflows carry NO push triggers by design (deliberate deploys, D13), and a
|
|
19
19
|
* path filter on a dispatch-only workflow filters nothing. It is declared PER
|
|
20
20
|
* STEP, not as a workflow-level `defaults.run.working-directory`: that would
|
|
21
21
|
* also scope the steps that run before actions/checkout (and the jobs that never
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
*
|
|
24
24
|
* Composition RECONCILES (#636): the enabled target set derives the composed
|
|
25
25
|
* file set, so a target the brand's config no longer enables has its composed
|
|
26
|
-
* files DELETED (reconcileComposedWorkflows)
|
|
26
|
+
* files DELETED (reconcileComposedWorkflows): the ratified example of a
|
|
27
27
|
* dropped config key becoming a real removal.
|
|
28
28
|
*/
|
|
29
29
|
const path = require('path');
|
|
@@ -68,7 +68,7 @@ const INSTALL_WORKSPACE_FLAG = '--workspace .';
|
|
|
68
68
|
* an unscoped artifact upload collects an empty repo-root path.
|
|
69
69
|
*
|
|
70
70
|
* Named per action on purpose: a blanket "any input called path" rule would
|
|
71
|
-
* rewrite inputs that mean something else
|
|
71
|
+
* rewrite inputs that mean something else: `actions/checkout`'s `path:` names
|
|
72
72
|
* where to CLONE the repo, not a file in it. An action missing from this table
|
|
73
73
|
* is left alone; add it here when a template starts using it.
|
|
74
74
|
*/
|
|
@@ -164,7 +164,7 @@ function composeWorkflow(contents, options) {
|
|
|
164
164
|
// (and its own engines.node pin) sits beside it.
|
|
165
165
|
composed = renderInstallWorkspace(composed, { composed: true });
|
|
166
166
|
|
|
167
|
-
// Display name carries the target
|
|
167
|
+
// Display name carries the target: two targets' runs are told apart in the
|
|
168
168
|
// Actions list, where only the workflow name shows.
|
|
169
169
|
composed = composed.replace(/^name:[ \t]*(.*)$/m, (full, value) => `name: ${value.trim()} (${targetPath})`);
|
|
170
170
|
|
|
@@ -173,7 +173,7 @@ function composeWorkflow(contents, options) {
|
|
|
173
173
|
composed = composed.replace(/^(concurrency:\n(?:[ \t]+.*\n)*?[ \t]+group:[ \t]*)(.*)$/m, (full, prefix, value) => `${prefix}${targetName}-${value.trim()}`);
|
|
174
174
|
|
|
175
175
|
// Every `run:` step that follows its job's checkout executes in the target dir
|
|
176
|
-
// (`uses:` actions
|
|
176
|
+
// (`uses:` actions, checkout and friends, stay at the repo root, which is
|
|
177
177
|
// what they want).
|
|
178
178
|
composed = scopeRunSteps(composed, targetPath);
|
|
179
179
|
|
|
@@ -238,12 +238,12 @@ function composeTargetWorkflows(options) {
|
|
|
238
238
|
/**
|
|
239
239
|
* Delete the composed workflows of targets this brand no longer has (#636).
|
|
240
240
|
* Composing is per-target and a dropped target's framework never runs again, so
|
|
241
|
-
* the file it wrote at the brand root would outlive it forever
|
|
241
|
+
* the file it wrote at the brand root would outlive it forever: a workflow
|
|
242
242
|
* Actions still lists and still dispatches for a target that is gone.
|
|
243
243
|
*
|
|
244
244
|
* The brand root's `.github/workflows/` is a MIXED dir: the brand's own
|
|
245
245
|
* human-authored workflows live beside the composed ones. So a file goes only
|
|
246
|
-
* past a DOUBLE lock
|
|
246
|
+
* past a DOUBLE lock: it carries the GENERATED header (which also NAMES the
|
|
247
247
|
* target it was composed for) AND it is named the composed way,
|
|
248
248
|
* `<target>-<framework file>.yml`. A human file matches neither and is never
|
|
249
249
|
* even a candidate; nothing here reads or edits one.
|
|
@@ -283,14 +283,14 @@ function reconcileComposedWorkflows(options) {
|
|
|
283
283
|
jetpack.remove(file);
|
|
284
284
|
}
|
|
285
285
|
result.removed.push(`.github/workflows/${name}`);
|
|
286
|
-
logger.log(`Removed .github/workflows/${name}
|
|
286
|
+
logger.log(`Removed .github/workflows/${name}: ${owned.targetPath} is no longer a target of this brand`);
|
|
287
287
|
}
|
|
288
288
|
|
|
289
289
|
return result;
|
|
290
290
|
}
|
|
291
291
|
|
|
292
292
|
/**
|
|
293
|
-
* The workflow file name to dispatch for a target
|
|
293
|
+
* The workflow file name to dispatch for a target: composed in a brand monorepo,
|
|
294
294
|
* the framework's own name standalone. The ONE place deploy verbs and the
|
|
295
295
|
* compose step agree on the name.
|
|
296
296
|
* @param {object} options
|
|
@@ -324,7 +324,7 @@ function composedWorkflowNameFor(name, workflow) {
|
|
|
324
324
|
// A workflow-level `defaults.run.working-directory` reads cleaner but applies to
|
|
325
325
|
// EVERY run step, including the ones that execute before actions/checkout (the
|
|
326
326
|
// git config step, a matrix-resolving step) and the jobs that never check out at
|
|
327
|
-
// all
|
|
327
|
+
// all: the target dir does not exist there yet, and the job dies on step 1.
|
|
328
328
|
function scopeRunSteps(contents, targetPath) {
|
|
329
329
|
const lines = contents.split('\n');
|
|
330
330
|
const output = [];
|
|
@@ -343,7 +343,7 @@ function scopeRunSteps(contents, targetPath) {
|
|
|
343
343
|
|
|
344
344
|
for (const line of lines) {
|
|
345
345
|
const indent = line.search(/\S/);
|
|
346
|
-
// Comments and blank lines never close a block
|
|
346
|
+
// Comments and blank lines never close a block; they belong to what follows
|
|
347
347
|
const structural = indent >= 0 && !line.trimStart().startsWith('#');
|
|
348
348
|
|
|
349
349
|
// A top-level key closes any open steps list and says whether we are in `jobs:`
|
|
@@ -417,8 +417,8 @@ function actionOf(step, keyIndent) {
|
|
|
417
417
|
return line ? line.slice(line.indexOf('uses:') + 5).trim().split('@')[0] : null;
|
|
418
418
|
}
|
|
419
419
|
|
|
420
|
-
// `hashFiles()` globs from GITHUB_WORKSPACE wherever it appears
|
|
421
|
-
// moves it
|
|
420
|
+
// `hashFiles()` globs from GITHUB_WORKSPACE wherever it appears (no step key
|
|
421
|
+
// moves it), so a composed target workflow's patterns carry the target path or match
|
|
422
422
|
// nothing at all.
|
|
423
423
|
function scopeHashFiles(step, targetPath) {
|
|
424
424
|
return step.map((line) => line.replace(/hashFiles\(([^)]*)\)/g, (full, args) => {
|
|
@@ -491,7 +491,7 @@ function scopeValue(value, targetPath) {
|
|
|
491
491
|
}
|
|
492
492
|
|
|
493
493
|
// A target-relative path/glob. Absolute paths and expression-built values name
|
|
494
|
-
// something other than a file in the checkout
|
|
494
|
+
// something other than a file in the checkout; those are left as written.
|
|
495
495
|
function joinTargetPath(value, targetPath) {
|
|
496
496
|
const trimmed = value.trim();
|
|
497
497
|
const scoped = trimmed.replace(/^\.\//, '');
|
|
@@ -503,7 +503,7 @@ function joinTargetPath(value, targetPath) {
|
|
|
503
503
|
return `${targetPath}/${scoped}`;
|
|
504
504
|
}
|
|
505
505
|
|
|
506
|
-
// Declare `working-directory` on one run step's lines
|
|
506
|
+
// Declare `working-directory` on one run step's lines. A step that already
|
|
507
507
|
// declares its own is left alone.
|
|
508
508
|
function scopeRunStep(step, keyIndent, targetPath) {
|
|
509
509
|
const pad = ' '.repeat(keyIndent);
|
|
@@ -544,11 +544,11 @@ function sweepTargetCopy(context) {
|
|
|
544
544
|
jetpack.remove(targetCopy);
|
|
545
545
|
pruneEmptyDirs(path.dirname(targetCopy), targetDir);
|
|
546
546
|
result.removed.push(`.github/workflows/${name}`);
|
|
547
|
-
logger.log(`Removed ${targetPath}/.github/workflows/${name}
|
|
547
|
+
logger.log(`Removed ${targetPath}/.github/workflows/${name}: GitHub only runs workflows from the repo root`);
|
|
548
548
|
return;
|
|
549
549
|
}
|
|
550
550
|
|
|
551
|
-
logger.warn(`Kept ${targetPath}/.github/workflows/${name}
|
|
551
|
+
logger.warn(`Kept ${targetPath}/.github/workflows/${name}: it differs from the current framework template (your edits, or an older framework version), and GitHub NEVER runs a workflow from a target dir. Compare it against ${composedName}, move anything it still needs, then delete ${targetPath}/.github/workflows/${name}`);
|
|
552
552
|
}
|
|
553
553
|
|
|
554
554
|
// Is every line of the target's copy a line the current template still ships, in
|
|
@@ -559,7 +559,7 @@ function sweepTargetCopy(context) {
|
|
|
559
559
|
// same file minus those lines, #334). A line the consumer ADDED or CHANGED is a
|
|
560
560
|
// line no template of this framework ever shipped, so it fails here and the
|
|
561
561
|
// copy is kept. Accepted blind spot: an edit that ONLY deletes lines is still a
|
|
562
|
-
// subsequence and gets swept
|
|
562
|
+
// subsequence and gets swept. Bounded, because a target-dir workflow never runs
|
|
563
563
|
// and the composed root file is regenerated from the current template.
|
|
564
564
|
function isFrameworkGeneration(existing, rendered) {
|
|
565
565
|
const template = rendered.split('\n');
|
|
@@ -587,7 +587,7 @@ function pruneEmptyDirs(dir, rootDir) {
|
|
|
587
587
|
}
|
|
588
588
|
}
|
|
589
589
|
|
|
590
|
-
// posix-style target path
|
|
590
|
+
// posix-style target path: this string ends up inside a YAML workflow.
|
|
591
591
|
function relativePath(from, to) {
|
|
592
592
|
return path.relative(from, to).split(path.sep).join('/');
|
|
593
593
|
}
|
|
@@ -596,14 +596,14 @@ function relativePath(from, to) {
|
|
|
596
596
|
// the file is framework-owned AND which target it belongs to, which is what
|
|
597
597
|
// makes the reconcile able to tell a composed file from a brand's own workflow
|
|
598
598
|
// without guessing from the name alone.
|
|
599
|
-
const generatedMark = (targetPath) => `# GENERATED by the ${targetPath} framework scaffold
|
|
600
|
-
// Reading accepts the
|
|
601
|
-
//
|
|
602
|
-
//
|
|
603
|
-
//
|
|
604
|
-
const GENERATED_MARK = /^# GENERATED by (?:the (\S+) framework scaffold|`omega setup` for (\S+))
|
|
605
|
-
|
|
606
|
-
// Which target a brand-root workflow file was composed for
|
|
599
|
+
const generatedMark = (targetPath) => `# GENERATED by the ${targetPath} framework scaffold. Do not edit.`;
|
|
600
|
+
// Reading also accepts the older forms (the `omega setup` wording, the em dash
|
|
601
|
+
// separator): a dropped target keeps its old-header file forever, which is exactly
|
|
602
|
+
// the file the reconcile exists to remove. Composing only ever writes the current
|
|
603
|
+
// sentence.
|
|
604
|
+
const GENERATED_MARK = /^# GENERATED by (?:the (\S+) framework scaffold|`omega setup` for (\S+))(?:\. Do| \u2014 do) not edit\.$/;
|
|
605
|
+
|
|
606
|
+
// Which target a brand-root workflow file was composed for, null when the file
|
|
607
607
|
// is not a composed one. BOTH locks must hold: the generation mark on line 1,
|
|
608
608
|
// and the `<target>-<framework file>.yml` naming the compose writes.
|
|
609
609
|
function composedWorkflowOwner(name, contents) {
|
|
@@ -633,4 +633,4 @@ function header(targetPath) {
|
|
|
633
633
|
].join('\n');
|
|
634
634
|
}
|
|
635
635
|
|
|
636
|
-
module.exports = { composeWorkflow, composeTargetWorkflows, composedWorkflowName, composedWorkflowNameFor, reconcileComposedWorkflows, renderInstallFirewall, renderInstallWorkspace, FIREWALL_ACTION, FIREWALL_STEP_ID, FIREWALL_TOKEN, INSTALL_WORKSPACE_TOKEN, INSTALL_WORKSPACE_FLAG };
|
|
636
|
+
module.exports = { composeWorkflow, composeTargetWorkflows, composedWorkflowName, composedWorkflowNameFor, composedWorkflowOwner, reconcileComposedWorkflows, renderInstallFirewall, renderInstallWorkspace, FIREWALL_ACTION, FIREWALL_STEP_ID, FIREWALL_TOKEN, INSTALL_WORKSPACE_TOKEN, INSTALL_WORKSPACE_FLAG };
|
|
@@ -2,19 +2,18 @@
|
|
|
2
2
|
//
|
|
3
3
|
// Every framework bin parses argv (src/argv.js) and hands the result to a Main class
|
|
4
4
|
// whose process() resolves a command name and runs commands/<name>.js from the
|
|
5
|
-
// framework's dist
|
|
6
|
-
// drift-identical); this is the single implementation. @omega.js/backend's CLI is a different
|
|
5
|
+
// framework's dist; this is the single implementation. @omega.js/backend's CLI is a different
|
|
7
6
|
// design (colon-style utility commands dispatching stateful command classes)
|
|
8
7
|
// and does not use this.
|
|
9
8
|
//
|
|
10
|
-
// Resolution order
|
|
9
|
+
// Resolution order:
|
|
11
10
|
// 1. Positional command (`mgr build`) — matched against command names and
|
|
12
11
|
// their aliases; unknown names pass through as-is (missing file = error).
|
|
13
12
|
// 2. Flag-style alias (`mgr --build`, `mgr -b`) — first alias-table hit wins.
|
|
14
13
|
// 3. The default command (OMEGA convention: help — `setup` is retired,
|
|
15
14
|
// #675, and every verb runs its own local scaffold).
|
|
16
15
|
//
|
|
17
|
-
// dotenv loading and any process-env fixups (e.g.
|
|
16
|
+
// dotenv loading and any process-env fixups (e.g. desktop's ELECTRON_RUN_AS_NODE
|
|
18
17
|
// strip) stay in the framework's cli.js/bin — they're framework concerns, not
|
|
19
18
|
// dispatch.
|
|
20
19
|
|
|
@@ -28,6 +27,7 @@ const jetpack = require('fs-jetpack');
|
|
|
28
27
|
* @param {string} config.commandsDir - Absolute directory of command modules — <name>.js exporting `async (options) => {}`
|
|
29
28
|
* @param {Object<string, string[]>} [config.aliases] - Command name → positional/flag aliases (e.g. `{ install: ['-i', 'i', '--install'] }`)
|
|
30
29
|
* @param {string} [config.defaultCommand='help'] - Command used when no positional or flag alias matches
|
|
30
|
+
* @param {Function} [config.fallback] - `(command) => handler|null`: the handler for a command with no file, else null for the unknown answer
|
|
31
31
|
* @returns {Function} Main class — bins do `new Main(argv)` then `await main.process(argv)`
|
|
32
32
|
*/
|
|
33
33
|
function createCliRouter(config) {
|
|
@@ -36,6 +36,7 @@ function createCliRouter(config) {
|
|
|
36
36
|
const commandsDir = config.commandsDir;
|
|
37
37
|
const aliases = config.aliases || {};
|
|
38
38
|
const defaultCommand = config.defaultCommand || 'help';
|
|
39
|
+
const fallback = config.fallback || null;
|
|
39
40
|
|
|
40
41
|
if (!commandsDir) {
|
|
41
42
|
throw new Error('[devkit cli-router] commandsDir is required');
|
|
@@ -115,14 +116,18 @@ function createCliRouter(config) {
|
|
|
115
116
|
|
|
116
117
|
// Get the command file path
|
|
117
118
|
const commandFile = path.join(commandsDir, `${command}.js`);
|
|
119
|
+
const exists = jetpack.exists(commandFile);
|
|
118
120
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
121
|
+
// Built-in help (a framework's own commands/help.js would have won above)
|
|
122
|
+
if (!exists && command === 'help') {
|
|
123
|
+
printHelp();
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// A router can own verbs it keeps no file for; a null handler is the unknown answer
|
|
128
|
+
const handler = !exists && fallback ? fallback(command) : null;
|
|
125
129
|
|
|
130
|
+
if (!exists && !handler) {
|
|
126
131
|
// Unknown command: name the valid surface and fail loud — the old path
|
|
127
132
|
// threw a doubled "Error executing… Error: Command…" with no listing.
|
|
128
133
|
// An empty listing means the dist itself is broken, not a typo — say so.
|
|
@@ -138,7 +143,7 @@ function createCliRouter(config) {
|
|
|
138
143
|
|
|
139
144
|
try {
|
|
140
145
|
// Execute the command
|
|
141
|
-
const Command = require(commandFile);
|
|
146
|
+
const Command = handler || require(commandFile);
|
|
142
147
|
await Command(options);
|
|
143
148
|
} catch (e) {
|
|
144
149
|
// The command's own error is the only surface — no wrapper prefix, no
|
|
@@ -1,42 +1,8 @@
|
|
|
1
|
-
// applyDefaults(config)
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// copyDefaults; UJM carries a third copy of the BXM shape). This is the single
|
|
7
|
-
// plain-fs engine, a normalized superset of both.
|
|
8
|
-
//
|
|
9
|
-
// File-map rules: minimatch patterns (dot:true) matched against the RAW path
|
|
10
|
-
// relative to defaultsDir, last-match-wins option merging. Per-rule options:
|
|
11
|
-
// overwrite bool|fn(item) default true — write even if the destination exists
|
|
12
|
-
// skip bool|fn(item) default false — never process this file
|
|
13
|
-
// name fn(item) rename the output file
|
|
14
|
-
// path fn(item) re-destination the output (relative dir)
|
|
15
|
-
// template object render `{{ key.path }}` tokens with this data
|
|
16
|
-
// (tolerant: unknown keys survive verbatim, so GitHub
|
|
17
|
-
// Actions' `${{ secrets.X }}` passes through)
|
|
18
|
-
// merge bool JSON5 defaults-merge with the existing file, written
|
|
19
|
-
// through @omega.js/config's comment-preserving editor
|
|
20
|
-
// (only the differing keys change; comments stay)
|
|
21
|
-
// mergeLines bool OMEGA marker-section line merge (.env/.gitignore/AGENTS.md)
|
|
22
|
-
// retire bool brand-context per-target doc retirement: NEVER scaffold
|
|
23
|
-
// the file; an existing framework-owned-only copy is
|
|
24
|
-
// DELETED (one-time heal — the brand root is the doc
|
|
25
|
-
// home), a copy carrying consumer content stays with a
|
|
26
|
-
// loud move-it warning (never destroyed)
|
|
27
|
-
//
|
|
28
|
-
// Engine built-ins (not expressed in the file map):
|
|
29
|
-
// - `_.name` segments lose the leading `_` (dotfiles ship past npm's filter)
|
|
30
|
-
// - non-final segments starting `_` (but not `_.`) are ARCHIVE dirs — skipped
|
|
31
|
-
// (reference material that ships in the framework package, e.g. EM's `_mas/`)
|
|
32
|
-
// - `.gitkeep` creates the destination directory, the file itself never copies
|
|
33
|
-
// - `.DS_Store` never copies
|
|
34
|
-
// - text writes are skipped when the destination is byte-identical (idempotent
|
|
35
|
-
// re-runs report zero writes)
|
|
36
|
-
// - a destination carrying the OMEGA section markers when the current template
|
|
37
|
-
// no longer does is a PRIOR-GENERATION generated file: framework-owned, so
|
|
38
|
-
// `retire` sweeps it and `overwrite: false` still heals it
|
|
39
|
-
// - binary files (by extension) copy verbatim — never templated/merged/transformed
|
|
1
|
+
// applyDefaults(config): the ONE plain-fs defaults-scaffolding engine every
|
|
2
|
+
// framework's scaffold runs, copying its defaults tree into the consumer
|
|
3
|
+
// project. The file-map options (overwrite, skip, name, path, template, merge,
|
|
4
|
+
// mergeLines, retire), the built-ins and the minimatch matching rules live in
|
|
5
|
+
// docs/devkit/index.md.
|
|
40
6
|
|
|
41
7
|
const path = require('path');
|
|
42
8
|
const fs = require('fs');
|
|
@@ -46,7 +12,7 @@ const { minimatch } = require('minimatch');
|
|
|
46
12
|
const { mergeLineBasedFiles, hasSectionMarkers, getCustomSection } = require('./merge-line-files');
|
|
47
13
|
|
|
48
14
|
// Files with these extensions copy byte-for-byte and never go through
|
|
49
|
-
// template/merge/transform
|
|
15
|
+
// template/merge/transform.
|
|
50
16
|
const BINARY_EXTENSIONS = /\.(jpg|jpeg|png|gif|webp|svg|ico|woff|woff2|ttf|otf|eot|pdf|zip|tar|gz|mp3|mp4|avi|mov)$/i;
|
|
51
17
|
|
|
52
18
|
const RULE_DEFAULTS = {
|
|
@@ -70,7 +36,7 @@ const RULE_DEFAULTS = {
|
|
|
70
36
|
* @param {Object<string, object>} [config.fileMap] - minimatch pattern → rule options (see header)
|
|
71
37
|
* @param {string[]} [config.files] - Only process these absolute source paths (watch single-file mode)
|
|
72
38
|
* @param {Function} [config.transform] - `(contents, item) => contents` global hook, run on every
|
|
73
|
-
* non-binary file after per-rule processing (
|
|
39
|
+
* non-binary file after per-rule processing (e.g. extension's site-token templating pass)
|
|
74
40
|
* @param {object} [config.logger] - `{ log, warn, error }` (defaults to console)
|
|
75
41
|
* @returns {{ written: string[], merged: string[], skipped: string[], removed: string[] }} destination-relative paths per outcome
|
|
76
42
|
*/
|
|
@@ -202,7 +168,7 @@ function applyDefaults(config) {
|
|
|
202
168
|
let didMerge = false;
|
|
203
169
|
|
|
204
170
|
// Per-rule template render happens BEFORE merging, so merges compare
|
|
205
|
-
// rendered framework defaults against the consumer's file
|
|
171
|
+
// rendered framework defaults against the consumer's file.
|
|
206
172
|
if (options.template) {
|
|
207
173
|
contents = renderTemplate(contents, options.template);
|
|
208
174
|
}
|
|
@@ -266,12 +232,10 @@ function isFrameworkOwned(existing, rendered) {
|
|
|
266
232
|
return existing.trim() === rendered.trim();
|
|
267
233
|
}
|
|
268
234
|
|
|
269
|
-
// A generation change: the destination carries the OMEGA marker grammar
|
|
270
|
-
//
|
|
271
|
-
//
|
|
272
|
-
//
|
|
273
|
-
// pointer), so comparing it against the current render can never match and the
|
|
274
|
-
// framework, not the consumer, owns it.
|
|
235
|
+
// A generation change: the destination carries the OMEGA marker grammar (only
|
|
236
|
+
// this framework's scaffold writes it) while the current template dropped it.
|
|
237
|
+
// That file is a PRIOR GENERATION's copy: it can never match the current
|
|
238
|
+
// render, and the framework, not the consumer, owns it.
|
|
275
239
|
function isLegacyMarkerArtifact(existing, rendered) {
|
|
276
240
|
return hasSectionMarkers(existing) && !hasSectionMarkers(rendered);
|
|
277
241
|
}
|
|
@@ -309,7 +273,7 @@ function strippedSegments(segments) {
|
|
|
309
273
|
return segments.map((s) => (s.startsWith('_.') ? s.slice(1) : s));
|
|
310
274
|
}
|
|
311
275
|
|
|
312
|
-
// Last-match-wins option resolution across the file map
|
|
276
|
+
// Last-match-wins option resolution across the file map.
|
|
313
277
|
function resolveOptions(relativePath, fileMap) {
|
|
314
278
|
let options = { ...RULE_DEFAULTS };
|
|
315
279
|
|
|
@@ -323,7 +287,7 @@ function resolveOptions(relativePath, fileMap) {
|
|
|
323
287
|
return options;
|
|
324
288
|
}
|
|
325
289
|
|
|
326
|
-
// Minimal `{{ key.path }}` substitution
|
|
290
|
+
// Minimal `{{ key.path }}` substitution, tolerant of unknown keys.
|
|
327
291
|
// Unknown keys render as the original `{{ ... }}` string, so non-template content
|
|
328
292
|
// with literal braces (e.g. GitHub Actions `${{ secrets.X }}`) survives.
|
|
329
293
|
function renderTemplate(content, context) {
|
|
@@ -334,7 +298,7 @@ function renderTemplate(content, context) {
|
|
|
334
298
|
});
|
|
335
299
|
}
|
|
336
300
|
|
|
337
|
-
// JSON5 defaults merge
|
|
301
|
+
// JSON5 defaults merge: framework defaults provide shape and
|
|
338
302
|
// order; the consumer's values win unless they're the literal string 'default';
|
|
339
303
|
// consumer-only keys survive at every nesting level, EXCEPT one holding the
|
|
340
304
|
// 'default' sentinel, which is the framework's own unset marker and drops (#926).
|
|
@@ -59,6 +59,7 @@ const { setTimeout: delay } = require('node:timers/promises');
|
|
|
59
59
|
const jetpack = require('fs-jetpack');
|
|
60
60
|
const { resolveCompany, COMPANY_RESOLVED_FILE } = require('../config/index.js');
|
|
61
61
|
const { gitAuthEnv, scrubToken } = require('./git-auth.js');
|
|
62
|
+
const { composedWorkflowOwner } = require('./ci-workflows.js');
|
|
62
63
|
|
|
63
64
|
// Constants
|
|
64
65
|
const API_BASE = 'https://api.github.com';
|
|
@@ -342,7 +343,9 @@ async function defaultBranchOf({ owner, repo, token, fetchFn = fetch }) {
|
|
|
342
343
|
* this write exists, and it is the only write a deploy ever makes to a default
|
|
343
344
|
* branch (Ian, 2026-09-14: "if all we are pushing to main is the gh workflow
|
|
344
345
|
* files that's fine"). Each file's bytes are compared with what the branch
|
|
345
|
-
* holds; when every one matches, nothing is written at all.
|
|
346
|
+
* holds; when every one matches, nothing is written at all. A branch file
|
|
347
|
+
* that carries the GENERATED header and is no longer composed (a renamed or
|
|
348
|
+
* dropped target) is RETIRED in the same commit; a hand-written one never is.
|
|
346
349
|
*
|
|
347
350
|
* Through the git DATA api, never a working-tree commit: blobs, a tree on top
|
|
348
351
|
* of the branch's own, a commit whose parent is its head. An EMPTY repo (no
|
|
@@ -356,14 +359,14 @@ async function defaultBranchOf({ owner, repo, token, fetchFn = fetch }) {
|
|
|
356
359
|
* @param {string} options.token - The GitHub token.
|
|
357
360
|
* @param {Function} [options.fetchFn] - Injectable fetch (tests).
|
|
358
361
|
* @param {object} [options.logger] - Logger with `log` (silent when omitted).
|
|
359
|
-
* @returns {Promise<{ pushed: string[], sha: string|null }>} What was written.
|
|
362
|
+
* @returns {Promise<{ pushed: string[], removed: string[], sha: string|null }>} What was written.
|
|
360
363
|
*/
|
|
361
364
|
async function pushWorkflowFiles({ brandRoot, owner, repo, branch, token, fetchFn = fetch, logger }) {
|
|
362
365
|
const files = composedWorkflowFiles(brandRoot);
|
|
363
366
|
|
|
364
367
|
if (files.length === 0) {
|
|
365
368
|
if (logger) logger.log(`No composed workflow in ${brandRoot}/${WORKFLOWS_DIR}: ${branch} is untouched.`);
|
|
366
|
-
return { pushed: [], sha: null };
|
|
369
|
+
return { pushed: [], removed: [], sha: null };
|
|
367
370
|
}
|
|
368
371
|
|
|
369
372
|
const call = async (route, init) => {
|
|
@@ -399,9 +402,31 @@ async function pushWorkflowFiles({ brandRoot, owner, repo, branch, token, fetchF
|
|
|
399
402
|
}
|
|
400
403
|
}
|
|
401
404
|
|
|
402
|
-
|
|
405
|
+
// What the branch holds that this brand no longer composes. The same double
|
|
406
|
+
// lock as the local prune: the GENERATED header and the composed naming, so
|
|
407
|
+
// a hand-written workflow is never a candidate. No listing means no files.
|
|
408
|
+
const composed = new Set(files.map((file) => file.name));
|
|
409
|
+
const listing = await read(
|
|
410
|
+
await call(`/contents/${WORKFLOWS_DIR}?ref=${encodeURIComponent(branch)}`),
|
|
411
|
+
[200, 404],
|
|
412
|
+
`Listing ${WORKFLOWS_DIR}`,
|
|
413
|
+
);
|
|
414
|
+
const retired = [];
|
|
415
|
+
|
|
416
|
+
for (const entry of listing || []) {
|
|
417
|
+
if (entry.type !== 'file' || !entry.name.endsWith('.yml') || composed.has(entry.name)) {
|
|
418
|
+
continue;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const held = await read(await call(`/contents/${entry.path}?ref=${encodeURIComponent(branch)}`), [200], `Reading ${entry.name}`);
|
|
422
|
+
if (composedWorkflowOwner(entry.name, Buffer.from(held.content || '', 'base64').toString('utf8'))) {
|
|
423
|
+
retired.push({ name: entry.name, path: entry.path });
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
if (changed.length === 0 && retired.length === 0) {
|
|
403
428
|
if (logger) logger.log(`${owner}/${repo}#${branch} already carries ${files.map((file) => file.name).join(', ')}: nothing pushed to ${branch}.`);
|
|
404
|
-
return { pushed: [], sha: null };
|
|
429
|
+
return { pushed: [], removed: [], sha: null };
|
|
405
430
|
}
|
|
406
431
|
|
|
407
432
|
// The head this commit sits on. A repo with no commit at all answers 404, and
|
|
@@ -422,15 +447,21 @@ async function pushWorkflowFiles({ brandRoot, owner, repo, branch, token, fetchF
|
|
|
422
447
|
);
|
|
423
448
|
tree.push({ path: file.path, mode: '100644', type: 'blob', sha: blob.sha });
|
|
424
449
|
}
|
|
450
|
+
for (const file of retired) {
|
|
451
|
+
tree.push({ path: file.path, mode: '100644', type: 'blob', sha: null });
|
|
452
|
+
}
|
|
425
453
|
|
|
426
|
-
const names =
|
|
454
|
+
const names = [
|
|
455
|
+
...(changed.length ? [`compose ${changed.map((file) => file.name).join(', ')}`] : []),
|
|
456
|
+
...(retired.length ? [`retire ${retired.map((file) => file.name).join(', ')}`] : []),
|
|
457
|
+
].join(', ');
|
|
427
458
|
const written = await read(
|
|
428
459
|
await call('/git/trees', { method: 'POST', body: JSON.stringify({ ...(baseTree ? { base_tree: baseTree } : {}), tree }) }),
|
|
429
460
|
[201],
|
|
430
461
|
'Writing the tree',
|
|
431
462
|
);
|
|
432
463
|
const commit = await read(
|
|
433
|
-
await call('/git/commits', { method: 'POST', body: JSON.stringify({ message: `chore(ci):
|
|
464
|
+
await call('/git/commits', { method: 'POST', body: JSON.stringify({ message: `chore(ci): ${names}`, tree: written.sha, parents }) }),
|
|
434
465
|
[201],
|
|
435
466
|
'Writing the commit',
|
|
436
467
|
);
|
|
@@ -444,10 +475,14 @@ async function pushWorkflowFiles({ brandRoot, owner, repo, branch, token, fetchF
|
|
|
444
475
|
);
|
|
445
476
|
|
|
446
477
|
if (logger) {
|
|
447
|
-
|
|
478
|
+
const touched = [
|
|
479
|
+
...changed.map((file) => `${file.name} (${file.reason})`),
|
|
480
|
+
...retired.map((file) => `${file.name} (retired)`),
|
|
481
|
+
];
|
|
482
|
+
logger.log(`Pushed ${touched.join(', ')} to ${owner}/${repo}#${branch} as ${shortSha(commit.sha)}: GitHub registers a workflow from the default branch.`);
|
|
448
483
|
}
|
|
449
484
|
|
|
450
|
-
return { pushed: changed.map((file) => file.name), sha: commit.sha };
|
|
485
|
+
return { pushed: changed.map((file) => file.name), removed: retired.map((file) => file.name), sha: commit.sha };
|
|
451
486
|
}
|
|
452
487
|
|
|
453
488
|
/**
|