@synopackageland/cli 0.3.9 → 0.4.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 +20 -9
- package/dist/bin.js +1 -2
- package/dist/build-spk/generator.d.ts +0 -3
- package/dist/build-spk/generator.js +135 -287
- package/dist/build-spk/native-lifecycle.js +0 -3
- package/dist/build-spk/native-payload.js +0 -1
- package/dist/cli.js +171 -151
- package/dist/compose/analyze.d.ts +1 -2
- package/dist/compose/analyze.js +5 -48
- package/dist/contract/unsupported-types.d.ts +10 -0
- package/dist/contract/unsupported-types.js +49 -0
- package/dist/contract/validate.js +25 -2
- package/dist/contract/web-launch.js +0 -2
- package/dist/deploy/deploy.d.ts +1 -4
- package/dist/deploy/deploy.js +57 -156
- package/dist/deploy/homes-values.js +1 -1
- package/dist/deploy/plan.d.ts +18 -0
- package/dist/deploy/plan.js +54 -0
- package/dist/deploy/port-preflight.d.ts +21 -0
- package/dist/deploy/port-preflight.js +42 -0
- package/dist/deploy/spk-version.d.ts +2 -14
- package/dist/deploy/spk-version.js +25 -111
- package/dist/dev.d.ts +1 -3
- package/dist/dev.js +14 -28
- package/dist/error-registry.js +810 -857
- package/dist/host/dsm-client.js +23 -263
- package/dist/host/dsm-port-profile.d.ts +47 -0
- package/dist/host/dsm-port-profile.js +206 -0
- package/dist/host/port-info-lookup.d.ts +9 -0
- package/dist/host/port-info-lookup.js +87 -0
- package/dist/host/port-info.d.ts +33 -0
- package/dist/host/port-info.js +66 -0
- package/dist/host/types.d.ts +6 -78
- package/dist/index.d.ts +0 -9
- package/dist/index.js +0 -6
- package/dist/info.d.ts +0 -1
- package/dist/info.js +3 -10
- package/dist/init.d.ts +11 -1
- package/dist/init.js +112 -4
- package/dist/log.js +2 -3
- package/dist/native/validate.js +0 -1
- package/dist/standalone/binary.d.ts +9 -0
- package/dist/standalone/binary.js +67 -0
- package/dist/standalone/build.d.ts +25 -0
- package/dist/standalone/build.js +108 -0
- package/dist/standalone/dsm-window.d.ts +26 -0
- package/dist/standalone/dsm-window.js +251 -0
- package/dist/standalone/helper.d.ts +31 -0
- package/dist/standalone/helper.js +104 -0
- package/dist/standalone/i18n.d.ts +28 -0
- package/dist/standalone/i18n.js +161 -0
- package/dist/standalone/inspect-spk.d.ts +21 -0
- package/dist/standalone/inspect-spk.js +160 -0
- package/dist/standalone/manifest.d.ts +71 -0
- package/dist/standalone/manifest.js +276 -0
- package/dist/standalone/root-messages.d.ts +7 -0
- package/dist/standalone/root-messages.js +250 -0
- package/dist/standalone/root-ops.d.ts +42 -0
- package/dist/standalone/root-ops.js +101 -0
- package/dist/standalone/root-window.d.ts +35 -0
- package/dist/standalone/root-window.js +290 -0
- package/dist/standalone/spk.d.ts +22 -0
- package/dist/standalone/spk.js +205 -0
- package/dist/templates/binary/synology-app.yaml +7 -0
- package/dist/templates/hello-files/compose.yaml +0 -1
- package/dist/templates/hello-web/compose.yaml +13 -10
- package/dist/templates/hello-web/html/app.js +1 -23
- package/dist/templates/hello-web/html/index.html +1 -3
- package/dist/templates/hello-web-page/compose.yaml +13 -10
- package/dist/templates/hello-web-page/html/app.js +1 -23
- package/dist/templates/hello-web-page/html/index.html +1 -3
- package/dist/templates/native-go/README.md +268 -0
- package/dist/templates/native-go/backend/cmd/app/main.go +49 -0
- package/dist/templates/native-go/backend/cmd/helper/main.go +38 -0
- package/dist/templates/native-go/backend/go.mod +3 -0
- package/dist/templates/native-go/backend/internal/api/errors.go +33 -0
- package/dist/templates/native-go/backend/internal/api/registry.go +94 -0
- package/dist/templates/native-go/backend/internal/api/registry_test.go +329 -0
- package/dist/templates/native-go/backend/internal/api/server.go +155 -0
- package/dist/templates/native-go/backend/internal/app/root_none.go +9 -0
- package/dist/templates/native-go/backend/internal/app/routes.go +127 -0
- package/dist/templates/native-go/backend/internal/app/routes_test.go +167 -0
- package/dist/templates/native-go/backend/internal/dsm/auth.go +118 -0
- package/dist/templates/native-go/backend/internal/dsm/auth_test.go +139 -0
- package/dist/templates/native-go/backend/internal/helper/control.go +219 -0
- package/dist/templates/native-go/backend/internal/helper/control_test.go +242 -0
- package/dist/templates/native-go/backend/internal/helper/peer_linux.go +44 -0
- package/dist/templates/native-go/backend/internal/helper/peer_other.go +7 -0
- package/dist/templates/native-go/backend/internal/helper/process_linux.go +167 -0
- package/dist/templates/native-go/backend/internal/helper/process_other.go +5 -0
- package/dist/templates/native-go/backend/internal/helper/server_linux.go +83 -0
- package/dist/templates/native-go/backend/internal/helper/status.go +271 -0
- package/dist/templates/native-go/backend/internal/helper/status_test.go +205 -0
- package/dist/templates/native-go/backend/internal/helper/supervision.go +84 -0
- package/dist/templates/native-go/backend/internal/helper/supervision_test.go +124 -0
- package/dist/templates/native-go/backend/internal/helper/supervisor_linux.go +192 -0
- package/dist/templates/native-go/backend/internal/helper/supervisor_linux_test.go +302 -0
- package/dist/templates/native-go/dsm-package.yaml +58 -0
- package/dist/templates/native-go/gitignore +1 -0
- package/dist/templates/native-go/i18n/glossary.json +662 -0
- package/dist/templates/native-go/i18n/messages/de.json +28 -0
- package/dist/templates/native-go/i18n/messages/en.json +28 -0
- package/dist/templates/native-go/i18n/messages/es.json +28 -0
- package/dist/templates/native-go/i18n/messages/fr.json +28 -0
- package/dist/templates/native-go/i18n/messages/it.json +28 -0
- package/dist/templates/native-go/i18n/messages/ja.json +28 -0
- package/dist/templates/native-go/i18n/messages/ko.json +28 -0
- package/dist/templates/native-go/i18n/messages/zh-CN.json +28 -0
- package/dist/templates/native-go/i18n/messages/zh-TW.json +28 -0
- package/dist/templates/native-go/ui/app.css +35 -0
- package/dist/templates/native-go/ui/app.js +94 -0
- package/dist/templates/native-go-better-auth/backend/internal/app/identity.go +208 -0
- package/dist/templates/native-go-better-auth/backend/internal/app/identity_test.go +113 -0
- package/dist/templates/native-go-better-auth/node/app.mjs +72 -0
- package/dist/templates/native-go-better-auth/node/build.mjs +2 -0
- package/dist/templates/native-go-better-auth/node/package-lock.json +862 -0
- package/dist/templates/native-go-better-auth/node/package.json +1 -0
- package/dist/templates/native-go-better-auth/node/test/auth.test.mjs +31 -0
- package/dist/templates/native-go-integration/backend/internal/app/identity_none.go +6 -0
- package/dist/templates/native-go-integration/backend/internal/app/personal.go +203 -0
- package/dist/templates/native-go-integration/backend/internal/app/personal_test.go +119 -0
- package/dist/templates/native-go-root/backend/internal/app/root.go +107 -0
- package/dist/templates/native-go-root/backend/internal/app/root_test.go +124 -0
- package/dist/templates/native-go-root/root/whoami.sh +6 -0
- package/dist/templates/native-go-vue/README.md +110 -0
- package/dist/templates/native-go-vue/frontend/DESIGN.md +78 -0
- package/dist/templates/native-go-vue/frontend/package-lock.json +2507 -0
- package/dist/templates/native-go-vue/frontend/package.json +6 -0
- package/dist/templates/native-go-vue/frontend/src/App.vue +132 -0
- package/dist/templates/native-go-vue/frontend/src/context.ts +17 -0
- package/dist/templates/native-go-vue/frontend/src/main.ts +26 -0
- package/dist/templates/native-go-vue/frontend/src/style.css +14 -0
- package/dist/templates/native-go-vue/frontend/test/apps.test.ts +148 -0
- package/dist/templates/native-go-vue/frontend/tsconfig.json +1 -0
- package/dist/templates/native-go-vue/frontend/vite.config.ts +8 -0
- package/dist/templates/native-go-vue/gitignore +2 -0
- package/dist/types.d.ts +1 -1
- package/dist/validate.js +11 -7
- package/package.json +3 -7
- package/schema/synology-app.schema.json +36 -37
- package/skills/SKILL.md +4 -48
- package/skills/reference/create.md +11 -83
- package/skills/reference/deploy.md +7 -78
- package/skills/reference/diagnose.md +4 -101
- package/skills/reference/files.md +5 -41
- package/skills/reference/identity.md +3 -29
- package/skills/reference/wrap.md +0 -2
- package/templates/binary/synology-app.yaml +7 -0
- package/templates/hello-files/compose.yaml +0 -1
- package/templates/hello-web/compose.yaml +13 -10
- package/templates/hello-web/html/app.js +1 -23
- package/templates/hello-web/html/index.html +1 -3
- package/templates/hello-web-page/compose.yaml +13 -10
- package/templates/hello-web-page/html/app.js +1 -23
- package/templates/hello-web-page/html/index.html +1 -3
- package/templates/native-go/README.md +268 -0
- package/templates/native-go/backend/cmd/app/main.go +49 -0
- package/templates/native-go/backend/cmd/helper/main.go +38 -0
- package/templates/native-go/backend/go.mod +3 -0
- package/templates/native-go/backend/internal/api/errors.go +33 -0
- package/templates/native-go/backend/internal/api/registry.go +94 -0
- package/templates/native-go/backend/internal/api/registry_test.go +329 -0
- package/templates/native-go/backend/internal/api/server.go +155 -0
- package/templates/native-go/backend/internal/app/root_none.go +9 -0
- package/templates/native-go/backend/internal/app/routes.go +127 -0
- package/templates/native-go/backend/internal/app/routes_test.go +167 -0
- package/templates/native-go/backend/internal/dsm/auth.go +118 -0
- package/templates/native-go/backend/internal/dsm/auth_test.go +139 -0
- package/templates/native-go/backend/internal/helper/control.go +219 -0
- package/templates/native-go/backend/internal/helper/control_test.go +242 -0
- package/templates/native-go/backend/internal/helper/peer_linux.go +44 -0
- package/templates/native-go/backend/internal/helper/peer_other.go +7 -0
- package/templates/native-go/backend/internal/helper/process_linux.go +167 -0
- package/templates/native-go/backend/internal/helper/process_other.go +5 -0
- package/templates/native-go/backend/internal/helper/server_linux.go +83 -0
- package/templates/native-go/backend/internal/helper/status.go +271 -0
- package/templates/native-go/backend/internal/helper/status_test.go +205 -0
- package/templates/native-go/backend/internal/helper/supervision.go +84 -0
- package/templates/native-go/backend/internal/helper/supervision_test.go +124 -0
- package/templates/native-go/backend/internal/helper/supervisor_linux.go +192 -0
- package/templates/native-go/backend/internal/helper/supervisor_linux_test.go +302 -0
- package/templates/native-go/dsm-package.yaml +58 -0
- package/templates/native-go/gitignore +1 -0
- package/templates/native-go/i18n/glossary.json +662 -0
- package/templates/native-go/i18n/messages/de.json +28 -0
- package/templates/native-go/i18n/messages/en.json +28 -0
- package/templates/native-go/i18n/messages/es.json +28 -0
- package/templates/native-go/i18n/messages/fr.json +28 -0
- package/templates/native-go/i18n/messages/it.json +28 -0
- package/templates/native-go/i18n/messages/ja.json +28 -0
- package/templates/native-go/i18n/messages/ko.json +28 -0
- package/templates/native-go/i18n/messages/zh-CN.json +28 -0
- package/templates/native-go/i18n/messages/zh-TW.json +28 -0
- package/templates/native-go/ui/app.css +35 -0
- package/templates/native-go/ui/app.js +94 -0
- package/templates/native-go-better-auth/backend/internal/app/identity.go +208 -0
- package/templates/native-go-better-auth/backend/internal/app/identity_test.go +113 -0
- package/templates/native-go-better-auth/node/app.mjs +72 -0
- package/templates/native-go-better-auth/node/build.mjs +2 -0
- package/templates/native-go-better-auth/node/package-lock.json +862 -0
- package/templates/native-go-better-auth/node/package.json +1 -0
- package/templates/native-go-better-auth/node/test/auth.test.mjs +31 -0
- package/templates/native-go-integration/backend/internal/app/identity_none.go +6 -0
- package/templates/native-go-integration/backend/internal/app/personal.go +203 -0
- package/templates/native-go-integration/backend/internal/app/personal_test.go +119 -0
- package/templates/native-go-root/backend/internal/app/root.go +107 -0
- package/templates/native-go-root/backend/internal/app/root_test.go +124 -0
- package/templates/native-go-root/root/whoami.sh +6 -0
- package/templates/native-go-vue/README.md +110 -0
- package/templates/native-go-vue/frontend/DESIGN.md +78 -0
- package/templates/native-go-vue/frontend/package-lock.json +2507 -0
- package/templates/native-go-vue/frontend/package.json +6 -0
- package/templates/native-go-vue/frontend/src/App.vue +132 -0
- package/templates/native-go-vue/frontend/src/context.ts +17 -0
- package/templates/native-go-vue/frontend/src/main.ts +26 -0
- package/templates/native-go-vue/frontend/src/style.css +14 -0
- package/templates/native-go-vue/frontend/test/apps.test.ts +148 -0
- package/templates/native-go-vue/frontend/tsconfig.json +1 -0
- package/templates/native-go-vue/frontend/vite.config.ts +8 -0
- package/templates/native-go-vue/gitignore +2 -0
- package/dist/attestation/app-package.d.ts +0 -86
- package/dist/attestation/app-package.js +0 -206
- package/dist/attestation/install-on-host.d.ts +0 -14
- package/dist/attestation/install-on-host.js +0 -52
- package/dist/attestation/keyring.d.ts +0 -21
- package/dist/attestation/keyring.js +0 -168
- package/dist/attestation/platform-service.d.ts +0 -70
- package/dist/attestation/platform-service.js +0 -250
- package/dist/build-spk/broker-glue.d.ts +0 -16
- package/dist/build-spk/broker-glue.js +0 -40
- package/dist/deploy/catalog-publish.d.ts +0 -3
- package/dist/deploy/catalog-publish.js +0 -7
- package/dist/doctor.d.ts +0 -22
- package/dist/doctor.js +0 -721
- package/dist/error-collector.d.ts +0 -2
- package/dist/error-collector.js +0 -46
- package/dist/host/broker-paths.d.ts +0 -1
- package/dist/host/broker-paths.js +0 -1
- package/dist/host/broker-upgrade-preflight.d.ts +0 -61
- package/dist/host/broker-upgrade-preflight.js +0 -156
- package/dist/host/digests.d.ts +0 -5
- package/dist/host/digests.js +0 -63
- package/dist/host/fake-nas.d.ts +0 -41
- package/dist/host/fake-nas.js +0 -174
- package/dist/mock-broker/bootstrap.d.ts +0 -2
- package/dist/mock-broker/bootstrap.js +0 -8
- package/dist/mock-broker/files.d.ts +0 -60
- package/dist/mock-broker/files.js +0 -408
- package/dist/mock-broker/oidc.d.ts +0 -31
- package/dist/mock-broker/oidc.js +0 -161
- package/dist/mock-broker/path.d.ts +0 -9
- package/dist/mock-broker/path.js +0 -50
- package/dist/mock-broker/server.d.ts +0 -19
- package/dist/mock-broker/server.js +0 -350
- package/dist/publish/build-ledger.d.ts +0 -22
- package/dist/publish/build-ledger.js +0 -26
- package/dist/publish/community-path.d.ts +0 -6
- package/dist/publish/community-path.js +0 -41
- package/dist/publish/curated-release.d.ts +0 -78
- package/dist/publish/curated-release.js +0 -883
- package/dist/publish/file-build-ledger.d.ts +0 -18
- package/dist/publish/file-build-ledger.js +0 -268
- package/dist/publish/github-pr.d.ts +0 -25
- package/dist/publish/github-pr.js +0 -85
- package/dist/publish/github-repo.d.ts +0 -8
- package/dist/publish/github-repo.js +0 -21
- package/dist/publish/platform-service-release.d.ts +0 -60
- package/dist/publish/platform-service-release.js +0 -333
- package/dist/publish/preflight.d.ts +0 -24
- package/dist/publish/preflight.js +0 -138
- package/dist/publish/publish.d.ts +0 -27
- package/dist/publish/publish.js +0 -136
- package/dist/publish/sync-definition.d.ts +0 -4
- package/dist/publish/sync-definition.js +0 -48
- package/dist/release-bin.d.ts +0 -2
- package/dist/release-bin.js +0 -23
- package/dist/templates/hello-web/html/sdk.js +0 -68
- package/dist/templates/hello-web-page/html/sdk.js +0 -68
- package/dist/templates/native/synology-app.yaml +0 -7
- package/dist/test-host.d.ts +0 -15
- package/dist/test-host.js +0 -37
- package/templates/hello-web/html/sdk.js +0 -68
- package/templates/hello-web-page/html/sdk.js +0 -68
- package/templates/native/synology-app.yaml +0 -7
- /package/dist/templates/{native → binary}/native.yaml +0 -0
- /package/templates/{native → binary}/native.yaml +0 -0
|
@@ -1,333 +0,0 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
|
-
import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
|
|
3
|
-
import { tmpdir } from "node:os";
|
|
4
|
-
import { basename, dirname, join, resolve } from "node:path";
|
|
5
|
-
import { pathToFileURL } from "node:url";
|
|
6
|
-
import { buildImmutableAttestationKey, buildImmutableSpkKey, } from "@synopackageland/package-source/index-manifest";
|
|
7
|
-
import { createR2CuratedReleasePublisher, publishCuratedRelease, } from "@synopackageland/package-source/publish-spk";
|
|
8
|
-
import { createPlatformAttestationSignerFromEnv, digestPlatformServiceReleaseClaims, PLATFORM_SERVICE_PACKAGE_IDENTITY, PlatformServiceReleaseError, } from "../attestation/platform-service.js";
|
|
9
|
-
export const SYNOPKGLAND_SOURCE_REVISION_ENV = "SYNOPKGLAND_SOURCE_REVISION";
|
|
10
|
-
export const PLATFORM_SERVICE_OS_MIN_VER = "7.2-69057";
|
|
11
|
-
/** Errors owned by the platform release entry point, separate from signer errors. */
|
|
12
|
-
export class PlatformReleaseError extends Error {
|
|
13
|
-
code;
|
|
14
|
-
constructor(code, detail) {
|
|
15
|
-
super(detail ? `${code}: ${detail}` : code);
|
|
16
|
-
this.code = code;
|
|
17
|
-
this.name = "PlatformReleaseError";
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
/**
|
|
21
|
-
* Build, attest and publish one immutable Broker SPK release.
|
|
22
|
-
*
|
|
23
|
-
* All cheap input gates run before the broker builder is loaded or invoked.
|
|
24
|
-
* Publication is delegated to the package-source three-phase boundary, which
|
|
25
|
-
* enforces artifact -> attestation -> index ordering.
|
|
26
|
-
*/
|
|
27
|
-
export async function releasePlatformService(options) {
|
|
28
|
-
const brokerDir = validatePlatformReleaseRequest(options);
|
|
29
|
-
const builder = options.builder ?? (await loadPlatformServiceBuilder(brokerDir));
|
|
30
|
-
const baseline = readReleaseBaseline(builder);
|
|
31
|
-
if (options.build <= baseline.baselineBuild) {
|
|
32
|
-
throw new PlatformReleaseError("PLATFORM_BUILD_INVALID");
|
|
33
|
-
}
|
|
34
|
-
const version = `${baseline.sourceVersion}-${String(options.build).padStart(4, "0")}`;
|
|
35
|
-
const workspace = mkdtempSync(join(tmpdir(), "synopkgland-platform-release-"));
|
|
36
|
-
try {
|
|
37
|
-
const outputPath = join(workspace, `${PLATFORM_SERVICE_PACKAGE_IDENTITY}.spk`);
|
|
38
|
-
try {
|
|
39
|
-
await builder.buildBrokerSpk({ outputPath, releaseVersion: version });
|
|
40
|
-
if (!existsSync(outputPath)) {
|
|
41
|
-
throw new Error("broker builder did not create the requested output");
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
catch (error) {
|
|
45
|
-
rethrowKnownReleaseError(error);
|
|
46
|
-
throw new PlatformReleaseError("PLATFORM_BUILD_FAILED");
|
|
47
|
-
}
|
|
48
|
-
let artifact;
|
|
49
|
-
try {
|
|
50
|
-
artifact = digestArtifact(outputPath);
|
|
51
|
-
}
|
|
52
|
-
catch (error) {
|
|
53
|
-
rethrowKnownReleaseError(error);
|
|
54
|
-
throw new PlatformReleaseError("PLATFORM_BUILD_FAILED");
|
|
55
|
-
}
|
|
56
|
-
const attestationInput = {
|
|
57
|
-
packageIdentity: PLATFORM_SERVICE_PACKAGE_IDENTITY,
|
|
58
|
-
sourceVersion: baseline.sourceVersion,
|
|
59
|
-
packageBuild: options.build,
|
|
60
|
-
packageVersion: version,
|
|
61
|
-
artifactDigest: artifact.sha256,
|
|
62
|
-
sourceRevision: options.sourceRevision,
|
|
63
|
-
osMinVer: PLATFORM_SERVICE_OS_MIN_VER,
|
|
64
|
-
arch: "noarch",
|
|
65
|
-
};
|
|
66
|
-
let signed;
|
|
67
|
-
try {
|
|
68
|
-
signed = await options.signer.sign(attestationInput);
|
|
69
|
-
assertPlatformClaims(signed.claims, attestationInput);
|
|
70
|
-
}
|
|
71
|
-
catch (error) {
|
|
72
|
-
rethrowKnownReleaseError(error);
|
|
73
|
-
throw new PlatformReleaseError("PLATFORM_RELEASE_FAILED");
|
|
74
|
-
}
|
|
75
|
-
let verified;
|
|
76
|
-
try {
|
|
77
|
-
verified = await options.signer.verify(signed.token);
|
|
78
|
-
assertPlatformClaims(verified, attestationInput);
|
|
79
|
-
if (signed.attestationDigest !== digestPlatformServiceReleaseClaims(verified)) {
|
|
80
|
-
throw new PlatformServiceReleaseError("PLATFORM_ATTESTATION_INVALID", "attestation digest does not match verified claims");
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
catch (error) {
|
|
84
|
-
rethrowKnownReleaseError(error);
|
|
85
|
-
throw new PlatformReleaseError("PLATFORM_RELEASE_FAILED");
|
|
86
|
-
}
|
|
87
|
-
const artifactKey = buildImmutableSpkKey({
|
|
88
|
-
package: PLATFORM_SERVICE_PACKAGE_IDENTITY,
|
|
89
|
-
version,
|
|
90
|
-
sha256: artifact.sha256,
|
|
91
|
-
});
|
|
92
|
-
const attestationKey = buildImmutableAttestationKey({
|
|
93
|
-
package: PLATFORM_SERVICE_PACKAGE_IDENTITY,
|
|
94
|
-
version,
|
|
95
|
-
artifactDigest: artifact.sha256,
|
|
96
|
-
});
|
|
97
|
-
const tokenSha256 = digestBytes(Buffer.from(signed.token, "utf8"), "sha256");
|
|
98
|
-
const attestation = {
|
|
99
|
-
kind: "platform-service-release",
|
|
100
|
-
packageId: PLATFORM_SERVICE_PACKAGE_IDENTITY,
|
|
101
|
-
version,
|
|
102
|
-
packageBuild: options.build,
|
|
103
|
-
artifactDigest: artifact.sha256,
|
|
104
|
-
key: attestationKey,
|
|
105
|
-
token: signed.token,
|
|
106
|
-
digest: signed.attestationDigest,
|
|
107
|
-
tokenSha256,
|
|
108
|
-
verificationJwk: options.signer.verificationJwk,
|
|
109
|
-
};
|
|
110
|
-
const index = {
|
|
111
|
-
packageId: PLATFORM_SERVICE_PACKAGE_IDENTITY,
|
|
112
|
-
version,
|
|
113
|
-
displayname: PLATFORM_SERVICE_PACKAGE_IDENTITY,
|
|
114
|
-
description: "Trusted control plane for Synology App Packages.",
|
|
115
|
-
maintainer: "Synology",
|
|
116
|
-
osMinVer: PLATFORM_SERVICE_OS_MIN_VER,
|
|
117
|
-
artifact: {
|
|
118
|
-
key: artifactKey,
|
|
119
|
-
size: artifact.size,
|
|
120
|
-
md5: artifact.md5,
|
|
121
|
-
sha256: artifact.sha256,
|
|
122
|
-
},
|
|
123
|
-
attestation: {
|
|
124
|
-
kind: "platform-service-release",
|
|
125
|
-
key: attestationKey,
|
|
126
|
-
digest: signed.attestationDigest,
|
|
127
|
-
packageBuild: options.build,
|
|
128
|
-
artifactDigest: artifact.sha256,
|
|
129
|
-
},
|
|
130
|
-
};
|
|
131
|
-
try {
|
|
132
|
-
await publishCuratedRelease({
|
|
133
|
-
artifact: {
|
|
134
|
-
packageId: PLATFORM_SERVICE_PACKAGE_IDENTITY,
|
|
135
|
-
version,
|
|
136
|
-
path: outputPath,
|
|
137
|
-
key: artifactKey,
|
|
138
|
-
size: artifact.size,
|
|
139
|
-
md5: artifact.md5,
|
|
140
|
-
sha256: artifact.sha256,
|
|
141
|
-
},
|
|
142
|
-
attestation,
|
|
143
|
-
index,
|
|
144
|
-
}, options.publisher);
|
|
145
|
-
}
|
|
146
|
-
catch (error) {
|
|
147
|
-
rethrowKnownReleaseError(error);
|
|
148
|
-
throw new PlatformReleaseError("PLATFORM_RELEASE_FAILED");
|
|
149
|
-
}
|
|
150
|
-
return {
|
|
151
|
-
packageIdentity: PLATFORM_SERVICE_PACKAGE_IDENTITY,
|
|
152
|
-
packageBuild: options.build,
|
|
153
|
-
version,
|
|
154
|
-
artifactKey,
|
|
155
|
-
artifactDigest: artifact.sha256,
|
|
156
|
-
attestationKey,
|
|
157
|
-
attestationDigest: signed.attestationDigest,
|
|
158
|
-
};
|
|
159
|
-
}
|
|
160
|
-
finally {
|
|
161
|
-
rmSync(workspace, { recursive: true, force: true });
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
/** Run the trusted platform release verb and emit exactly one line. */
|
|
165
|
-
export async function runPlatformServiceReleaseCli(argv = process.argv, env = process.env, dependencies = {}) {
|
|
166
|
-
const writeStdout = dependencies.writeStdout ?? ((line) => process.stdout.write(line));
|
|
167
|
-
const writeStderr = dependencies.writeStderr ?? ((line) => process.stderr.write(line));
|
|
168
|
-
try {
|
|
169
|
-
if (argv[2] !== "release-platform-service" ||
|
|
170
|
-
!argv[3] ||
|
|
171
|
-
!argv[4] ||
|
|
172
|
-
argv.length > 5) {
|
|
173
|
-
throw new PlatformReleaseError("PLATFORM_RELEASE_USAGE");
|
|
174
|
-
}
|
|
175
|
-
if (argv.slice(2).some((argument) => argument.endsWith(".spk"))) {
|
|
176
|
-
throw new PlatformReleaseError("PLATFORM_ARTIFACT_INPUT_FORBIDDEN");
|
|
177
|
-
}
|
|
178
|
-
const build = parseBuild(argv[4]);
|
|
179
|
-
const sourceRevision = resolveSourceRevision(env);
|
|
180
|
-
const brokerDir = resolve(process.cwd(), argv[3]);
|
|
181
|
-
validatePlatformBrokerDirectory(brokerDir);
|
|
182
|
-
const signer = dependencies.signer ?? (await createPlatformAttestationSignerFromEnv(env));
|
|
183
|
-
const publisher = dependencies.publisher ?? createR2CuratedReleasePublisher();
|
|
184
|
-
const result = await releasePlatformService({
|
|
185
|
-
brokerDir,
|
|
186
|
-
build,
|
|
187
|
-
sourceRevision,
|
|
188
|
-
signer,
|
|
189
|
-
publisher,
|
|
190
|
-
builder: dependencies.builder,
|
|
191
|
-
});
|
|
192
|
-
writeStdout(`${JSON.stringify({
|
|
193
|
-
packageIdentity: result.packageIdentity,
|
|
194
|
-
packageBuild: result.packageBuild,
|
|
195
|
-
version: result.version,
|
|
196
|
-
artifactDigest: result.artifactDigest,
|
|
197
|
-
attestationDigest: result.attestationDigest,
|
|
198
|
-
})}\n`);
|
|
199
|
-
return 0;
|
|
200
|
-
}
|
|
201
|
-
catch (error) {
|
|
202
|
-
writeStderr(`${releaseErrorCode(error)}\n`);
|
|
203
|
-
return 1;
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
function validatePlatformReleaseRequest(options) {
|
|
207
|
-
if (typeof options.brokerDir !== "string" ||
|
|
208
|
-
options.brokerDir.endsWith(".spk") ||
|
|
209
|
-
String(options.build).endsWith(".spk") ||
|
|
210
|
-
typeof options.sourceRevision !== "string" ||
|
|
211
|
-
options.sourceRevision.endsWith(".spk")) {
|
|
212
|
-
throw new PlatformReleaseError("PLATFORM_ARTIFACT_INPUT_FORBIDDEN");
|
|
213
|
-
}
|
|
214
|
-
if (!Number.isSafeInteger(options.build) || options.build <= 0) {
|
|
215
|
-
throw new PlatformReleaseError("PLATFORM_BUILD_INVALID");
|
|
216
|
-
}
|
|
217
|
-
if (!/^[a-f0-9]{40}$/.test(options.sourceRevision)) {
|
|
218
|
-
throw new PlatformReleaseError("PLATFORM_SOURCE_REVISION_MISSING");
|
|
219
|
-
}
|
|
220
|
-
const brokerDir = resolve(options.brokerDir);
|
|
221
|
-
validatePlatformBrokerDirectory(brokerDir);
|
|
222
|
-
return brokerDir;
|
|
223
|
-
}
|
|
224
|
-
function validatePlatformBrokerDirectory(brokerDir) {
|
|
225
|
-
if (basename(brokerDir) !== "broker" ||
|
|
226
|
-
basename(dirname(brokerDir)) !== "packages") {
|
|
227
|
-
throw new PlatformReleaseError("PLATFORM_BROKER_DIR_INVALID");
|
|
228
|
-
}
|
|
229
|
-
let packageJson;
|
|
230
|
-
try {
|
|
231
|
-
packageJson = JSON.parse(readFileSync(join(brokerDir, "package.json"), "utf8"));
|
|
232
|
-
}
|
|
233
|
-
catch {
|
|
234
|
-
throw new PlatformReleaseError("PLATFORM_BROKER_DIR_INVALID");
|
|
235
|
-
}
|
|
236
|
-
if (!packageJson ||
|
|
237
|
-
typeof packageJson !== "object" ||
|
|
238
|
-
Array.isArray(packageJson) ||
|
|
239
|
-
packageJson.name !== "@synology/app-broker") {
|
|
240
|
-
throw new PlatformReleaseError("PLATFORM_BROKER_DIR_INVALID");
|
|
241
|
-
}
|
|
242
|
-
if (existsSync(join(brokerDir, "synology-app.yaml"))) {
|
|
243
|
-
throw new PlatformReleaseError("PLATFORM_APP_PROJECT_FORBIDDEN");
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
function parseBuild(value) {
|
|
247
|
-
if (!/^\d+$/.test(value)) {
|
|
248
|
-
throw new PlatformReleaseError("PLATFORM_BUILD_INVALID");
|
|
249
|
-
}
|
|
250
|
-
const build = Number(value);
|
|
251
|
-
if (!Number.isSafeInteger(build) || build <= 0) {
|
|
252
|
-
throw new PlatformReleaseError("PLATFORM_BUILD_INVALID");
|
|
253
|
-
}
|
|
254
|
-
return build;
|
|
255
|
-
}
|
|
256
|
-
function resolveSourceRevision(env) {
|
|
257
|
-
const raw = env[SYNOPKGLAND_SOURCE_REVISION_ENV] ?? env.GITHUB_SHA;
|
|
258
|
-
const sourceRevision = typeof raw === "string" ? raw.trim() : "";
|
|
259
|
-
if (!/^[a-f0-9]{40}$/.test(sourceRevision)) {
|
|
260
|
-
throw new PlatformReleaseError("PLATFORM_SOURCE_REVISION_MISSING");
|
|
261
|
-
}
|
|
262
|
-
return sourceRevision;
|
|
263
|
-
}
|
|
264
|
-
async function loadPlatformServiceBuilder(brokerDir) {
|
|
265
|
-
const modulePath = join(brokerDir, "dist", "spk", "build.js");
|
|
266
|
-
let loaded;
|
|
267
|
-
try {
|
|
268
|
-
loaded = (await import(pathToFileURL(modulePath).href));
|
|
269
|
-
}
|
|
270
|
-
catch {
|
|
271
|
-
throw new PlatformReleaseError("PLATFORM_BUILDER_UNAVAILABLE");
|
|
272
|
-
}
|
|
273
|
-
if (typeof loaded.buildBrokerSpk !== "function" ||
|
|
274
|
-
typeof loaded.brokerReleaseBaseline !== "function") {
|
|
275
|
-
throw new PlatformReleaseError("PLATFORM_BUILDER_UNAVAILABLE");
|
|
276
|
-
}
|
|
277
|
-
return {
|
|
278
|
-
buildBrokerSpk: loaded.buildBrokerSpk,
|
|
279
|
-
brokerReleaseBaseline: loaded.brokerReleaseBaseline,
|
|
280
|
-
};
|
|
281
|
-
}
|
|
282
|
-
function readReleaseBaseline(builder) {
|
|
283
|
-
let baseline;
|
|
284
|
-
try {
|
|
285
|
-
baseline = builder.brokerReleaseBaseline();
|
|
286
|
-
}
|
|
287
|
-
catch {
|
|
288
|
-
throw new PlatformReleaseError("PLATFORM_BUILDER_UNAVAILABLE");
|
|
289
|
-
}
|
|
290
|
-
if (!baseline ||
|
|
291
|
-
typeof baseline.sourceVersion !== "string" ||
|
|
292
|
-
!/^\d+\.\d+\.\d+$/.test(baseline.sourceVersion) ||
|
|
293
|
-
!Number.isSafeInteger(baseline.baselineBuild) ||
|
|
294
|
-
baseline.baselineBuild < 0) {
|
|
295
|
-
throw new PlatformReleaseError("PLATFORM_BUILDER_UNAVAILABLE");
|
|
296
|
-
}
|
|
297
|
-
return baseline;
|
|
298
|
-
}
|
|
299
|
-
function digestArtifact(path) {
|
|
300
|
-
const bytes = readFileSync(path);
|
|
301
|
-
return {
|
|
302
|
-
size: bytes.byteLength,
|
|
303
|
-
md5: digestBytes(bytes, "md5"),
|
|
304
|
-
sha256: digestBytes(bytes, "sha256"),
|
|
305
|
-
};
|
|
306
|
-
}
|
|
307
|
-
function digestBytes(bytes, algorithm) {
|
|
308
|
-
return createHash(algorithm).update(bytes).digest("hex");
|
|
309
|
-
}
|
|
310
|
-
function assertPlatformClaims(claims, expected) {
|
|
311
|
-
if (!claims ||
|
|
312
|
-
claims.packageIdentity !== expected.packageIdentity ||
|
|
313
|
-
claims.sourceVersion !== expected.sourceVersion ||
|
|
314
|
-
claims.packageBuild !== expected.packageBuild ||
|
|
315
|
-
claims.packageVersion !== expected.packageVersion ||
|
|
316
|
-
claims.artifactDigest !== expected.artifactDigest ||
|
|
317
|
-
claims.sourceRevision !== expected.sourceRevision ||
|
|
318
|
-
claims.osMinVer !== expected.osMinVer ||
|
|
319
|
-
claims.arch !== expected.arch) {
|
|
320
|
-
throw new PlatformServiceReleaseError("PLATFORM_ATTESTATION_INVALID", "platform release claims do not bind the release input");
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
function rethrowKnownReleaseError(error) {
|
|
324
|
-
if (error instanceof PlatformServiceReleaseError || error instanceof PlatformReleaseError) {
|
|
325
|
-
throw error;
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
function releaseErrorCode(error) {
|
|
329
|
-
if (error instanceof PlatformServiceReleaseError || error instanceof PlatformReleaseError) {
|
|
330
|
-
return error.code;
|
|
331
|
-
}
|
|
332
|
-
return "PLATFORM_RELEASE_FAILED";
|
|
333
|
-
}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import type { ProjectRoot } from "../discovery.js";
|
|
2
|
-
export interface PreflightCheck {
|
|
3
|
-
/** Stable id so scripts and docs can refer to a specific gate. */
|
|
4
|
-
id: string;
|
|
5
|
-
title: string;
|
|
6
|
-
ok: boolean;
|
|
7
|
-
detail?: string;
|
|
8
|
-
}
|
|
9
|
-
export interface PreflightResult {
|
|
10
|
-
ok: boolean;
|
|
11
|
-
checks: PreflightCheck[];
|
|
12
|
-
}
|
|
13
|
-
/**
|
|
14
|
-
* Every gate for a public release that can be decided on the developer's own
|
|
15
|
-
* machine. Submitting runs these first so a definition that CI would reject
|
|
16
|
-
* never reaches review; `--check` runs the same set and stops.
|
|
17
|
-
*
|
|
18
|
-
* Deliberately excludes anything needing release authority: Build allocation,
|
|
19
|
-
* attestation signing and catalog publication belong to trusted CI running
|
|
20
|
-
* from the merged revision, and no local run can stand in for a maintainer's
|
|
21
|
-
* approval.
|
|
22
|
-
*/
|
|
23
|
-
export declare function runPublicReleasePreflight(project: ProjectRoot): Promise<PreflightResult>;
|
|
24
|
-
export declare function formatPreflight(result: PreflightResult): string;
|
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
import { existsSync, mkdtempSync, rmSync } from "node:fs";
|
|
2
|
-
import { tmpdir } from "node:os";
|
|
3
|
-
import { join } from "node:path";
|
|
4
|
-
import { CURATED_BUILD_OFFSET, isDevelopmentPackageIdentity, } from "@synopackageland/package-source/index-manifest";
|
|
5
|
-
import { buildSpk } from "../build-spk/generator.js";
|
|
6
|
-
import { classifyComposeRoute, loadCompose } from "../compose/analyze.js";
|
|
7
|
-
import { loadContract } from "../contract/load.js";
|
|
8
|
-
import { isNativeArchFamily } from "../build-spk/dsm-arch.js";
|
|
9
|
-
import { loadNative } from "../native/load.js";
|
|
10
|
-
import { validateProject } from "../validate.js";
|
|
11
|
-
import { validateCuratedCompose } from "./curated-release.js";
|
|
12
|
-
/**
|
|
13
|
-
* Every gate for a public release that can be decided on the developer's own
|
|
14
|
-
* machine. Submitting runs these first so a definition that CI would reject
|
|
15
|
-
* never reaches review; `--check` runs the same set and stops.
|
|
16
|
-
*
|
|
17
|
-
* Deliberately excludes anything needing release authority: Build allocation,
|
|
18
|
-
* attestation signing and catalog publication belong to trusted CI running
|
|
19
|
-
* from the merged revision, and no local run can stand in for a maintainer's
|
|
20
|
-
* approval.
|
|
21
|
-
*/
|
|
22
|
-
export async function runPublicReleasePreflight(project) {
|
|
23
|
-
const checks = [];
|
|
24
|
-
const add = (check) => {
|
|
25
|
-
checks.push(check);
|
|
26
|
-
return check.ok;
|
|
27
|
-
};
|
|
28
|
-
const validation = validateProject(project, { profile: "listing" });
|
|
29
|
-
add({
|
|
30
|
-
id: "listing-validate",
|
|
31
|
-
title: "App Contract 與 runtime 通過 Curated listing profile",
|
|
32
|
-
ok: validation.ok,
|
|
33
|
-
detail: validation.ok ? undefined : validation.errors.map((error) => error.code).join(", "),
|
|
34
|
-
});
|
|
35
|
-
const contract = loadContract(project.contractPath).contract;
|
|
36
|
-
const contractVersion = typeof contract.version === "string" ? contract.version.trim() : "";
|
|
37
|
-
const sourceBuild = /-(\d+)$/.exec(contractVersion);
|
|
38
|
-
const sourceBuildNumber = sourceBuild ? Number(sourceBuild[1]) : undefined;
|
|
39
|
-
add({
|
|
40
|
-
id: "version-namespace",
|
|
41
|
-
title: "App Contract 不手寫 Curated Package Build",
|
|
42
|
-
ok: sourceBuildNumber === undefined ||
|
|
43
|
-
(Number.isSafeInteger(sourceBuildNumber) && sourceBuildNumber < CURATED_BUILD_OFFSET),
|
|
44
|
-
detail: sourceBuildNumber !== undefined &&
|
|
45
|
-
(!Number.isSafeInteger(sourceBuildNumber) || sourceBuildNumber >= CURATED_BUILD_OFFSET)
|
|
46
|
-
? "CURATED_BUILD_MUST_NOT_BE_IN_SOURCE_VERSION"
|
|
47
|
-
: undefined,
|
|
48
|
-
});
|
|
49
|
-
const packageIdentity = typeof contract.package === "string" ? contract.package : "";
|
|
50
|
-
const formalIdentity = packageIdentity.length > 0 && !isDevelopmentPackageIdentity(packageIdentity);
|
|
51
|
-
add({
|
|
52
|
-
id: "formal-identity",
|
|
53
|
-
title: "Package Identity 是正式身分(不是 .dev)",
|
|
54
|
-
ok: formalIdentity,
|
|
55
|
-
detail: formalIdentity
|
|
56
|
-
? undefined
|
|
57
|
-
: packageIdentity.length === 0
|
|
58
|
-
? "App Contract 缺少 package 欄位。"
|
|
59
|
-
: `Curated 不接受 development identity:${packageIdentity}`,
|
|
60
|
-
});
|
|
61
|
-
const nativePath = project.nativePath;
|
|
62
|
-
const isNative = nativePath !== undefined && existsSync(nativePath);
|
|
63
|
-
if (isNative) {
|
|
64
|
-
const parsedNative = loadNative(nativePath);
|
|
65
|
-
const families = Object.keys(parsedNative?.native.targets ?? {}).filter(isNativeArchFamily);
|
|
66
|
-
const pinned = families.filter((family) => {
|
|
67
|
-
const target = parsedNative?.native.targets?.[family];
|
|
68
|
-
return typeof target?.url === "string" && typeof target?.sha256 === "string";
|
|
69
|
-
});
|
|
70
|
-
add({
|
|
71
|
-
id: "curated-runtime",
|
|
72
|
-
title: "每個 native target 都以 url + sha256 釘住",
|
|
73
|
-
ok: families.length > 0 && pinned.length === families.length,
|
|
74
|
-
detail: families.length === 0
|
|
75
|
-
? "native.yaml 沒有宣告任何 target。"
|
|
76
|
-
: pinned.length === families.length
|
|
77
|
-
? undefined
|
|
78
|
-
: `缺少 url 或 sha256:${families.filter((f) => !pinned.includes(f)).join(", ")}`,
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
else {
|
|
82
|
-
const parsedCompose = loadCompose(project.composePath);
|
|
83
|
-
const gate = parsedCompose
|
|
84
|
-
? validateCuratedCompose(parsedCompose.compose)
|
|
85
|
-
: { ok: false, errors: ["CURATED_COMPOSE_MISSING"] };
|
|
86
|
-
add({
|
|
87
|
-
id: "curated-runtime",
|
|
88
|
-
title: "Compose 只用釘住 digest 的公開 image,且沒有 build:",
|
|
89
|
-
ok: gate.ok,
|
|
90
|
-
detail: gate.ok ? undefined : gate.errors.join(", "),
|
|
91
|
-
});
|
|
92
|
-
const route = parsedCompose
|
|
93
|
-
? classifyComposeRoute(contract, parsedCompose)
|
|
94
|
-
: undefined;
|
|
95
|
-
add({
|
|
96
|
-
id: "curated-route",
|
|
97
|
-
title: "Compose 不使用 Broker installer 路線",
|
|
98
|
-
ok: route?.route !== "broker-installer",
|
|
99
|
-
detail: route?.route === "broker-installer" ? "CURATED_BROKER_ROUTE_FORBIDDEN" : undefined,
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
// The strongest local gate: actually produce the artifact. For native this
|
|
103
|
-
// fetches each pinned asset and refuses it unless the bytes hash to the
|
|
104
|
-
// declared sha256, so a bad pin fails here instead of in CI.
|
|
105
|
-
if (checks.every((check) => check.ok)) {
|
|
106
|
-
const staging = mkdtempSync(join(tmpdir(), "synopkgland-preflight-"));
|
|
107
|
-
try {
|
|
108
|
-
const families = isNative
|
|
109
|
-
? Object.keys(loadNative(nativePath)?.native.targets ?? {}).filter(isNativeArchFamily)
|
|
110
|
-
: [undefined];
|
|
111
|
-
for (const family of families) {
|
|
112
|
-
const built = await buildSpk({
|
|
113
|
-
project,
|
|
114
|
-
outputPath: join(staging, `${packageIdentity}-${family ?? "noarch"}.spk`),
|
|
115
|
-
...(family ? { archFamily: family } : {}),
|
|
116
|
-
});
|
|
117
|
-
add({
|
|
118
|
-
id: family ? `spk-builds:${family}` : "spk-builds",
|
|
119
|
-
title: family ? `SPK 可建置(${family},含 sha256 比對)` : "SPK 可建置",
|
|
120
|
-
ok: built.ok,
|
|
121
|
-
detail: built.ok ? undefined : built.errors.join(", "),
|
|
122
|
-
});
|
|
123
|
-
if (!built.ok) {
|
|
124
|
-
break;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
finally {
|
|
129
|
-
rmSync(staging, { recursive: true, force: true });
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
return { ok: checks.every((check) => check.ok), checks };
|
|
133
|
-
}
|
|
134
|
-
export function formatPreflight(result) {
|
|
135
|
-
return result.checks
|
|
136
|
-
.map((check) => `${check.ok ? "✔" : "✖"} ${check.title}${check.detail ? `\n ${check.detail}` : ""}`)
|
|
137
|
-
.join("\n");
|
|
138
|
-
}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import type { ProjectRoot } from "../discovery.js";
|
|
2
|
-
import { type PreflightResult } from "./preflight.js";
|
|
3
|
-
import type { GitHubCli, GitOps } from "./github-pr.js";
|
|
4
|
-
export interface PublishProjectOptions {
|
|
5
|
-
/** Injected in tests; production runs the real local gate set. */
|
|
6
|
-
preflight?: (project: ProjectRoot) => Promise<PreflightResult>;
|
|
7
|
-
project: ProjectRoot;
|
|
8
|
-
communityAppsRoot?: string;
|
|
9
|
-
baseBranch?: string;
|
|
10
|
-
branchPrefix?: string;
|
|
11
|
-
github: GitHubCli;
|
|
12
|
-
git: GitOps;
|
|
13
|
-
now?: () => Date;
|
|
14
|
-
}
|
|
15
|
-
export interface PublishProjectResult {
|
|
16
|
-
preflight?: PreflightResult;
|
|
17
|
-
ok: boolean;
|
|
18
|
-
code?: string;
|
|
19
|
-
message?: string;
|
|
20
|
-
packageIdentity?: string;
|
|
21
|
-
prUrl?: string;
|
|
22
|
-
prNumber?: number;
|
|
23
|
-
branchName?: string;
|
|
24
|
-
noChanges?: boolean;
|
|
25
|
-
}
|
|
26
|
-
export declare function publishProject(options: PublishProjectOptions): Promise<PublishProjectResult>;
|
|
27
|
-
export { resolveCommunityAppsRoot, communityAppDirectory, listCommunityAppDirectories } from "./community-path.js";
|
package/dist/publish/publish.js
DELETED
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
import { join, resolve } from "node:path";
|
|
2
|
-
import { loadContract } from "../contract/load.js";
|
|
3
|
-
import { runPublicReleasePreflight } from "./preflight.js";
|
|
4
|
-
import { assertDirectoryMatchesPackage, COMMUNITY_APPS_APPS_DIR, COMMUNITY_APPS_DIR, communityAppDirectory, resolveCommunityAppsRoot, } from "./community-path.js";
|
|
5
|
-
import { listSyncedRelativePaths, syncPackagingDefinition } from "./sync-definition.js";
|
|
6
|
-
function slugifyPackageIdentity(packageIdentity) {
|
|
7
|
-
return packageIdentity.replace(/[^a-zA-Z0-9._-]+/g, "-");
|
|
8
|
-
}
|
|
9
|
-
function renderPullRequestBody(packageIdentity, appRelativePath, files) {
|
|
10
|
-
return [
|
|
11
|
-
"## Summary",
|
|
12
|
-
"",
|
|
13
|
-
`Publish Packaging Definition for \`${packageIdentity}\`.`,
|
|
14
|
-
"",
|
|
15
|
-
"## Checklist",
|
|
16
|
-
"",
|
|
17
|
-
"- [ ] 變更摘要",
|
|
18
|
-
"- [ ] Image 來源(公開 registry/digest)",
|
|
19
|
-
"- [ ] 是否新增 User File Scope 或 secret",
|
|
20
|
-
"",
|
|
21
|
-
"## Synced paths",
|
|
22
|
-
"",
|
|
23
|
-
`\`${appRelativePath}/\``,
|
|
24
|
-
"",
|
|
25
|
-
files.map((file) => `- \`${file}\``).join("\n"),
|
|
26
|
-
"",
|
|
27
|
-
"_Opened by `synopkgland publish`. PR CI runs validate + preview only; no signing or Package Build allocation._",
|
|
28
|
-
].join("\n");
|
|
29
|
-
}
|
|
30
|
-
export async function publishProject(options) {
|
|
31
|
-
// Every locally decidable gate runs before anything is pushed, so a
|
|
32
|
-
// definition CI would reject never costs a maintainer a review.
|
|
33
|
-
const preflight = options.preflight
|
|
34
|
-
? await options.preflight(options.project)
|
|
35
|
-
: await runPublicReleasePreflight(options.project);
|
|
36
|
-
if (!preflight.ok) {
|
|
37
|
-
return {
|
|
38
|
-
ok: false,
|
|
39
|
-
code: "PREFLIGHT_FAILED",
|
|
40
|
-
message: preflight.checks
|
|
41
|
-
.filter((check) => !check.ok)
|
|
42
|
-
.map((check) => `${check.id}${check.detail ? `: ${check.detail}` : ""}`)
|
|
43
|
-
.join("; "),
|
|
44
|
-
preflight,
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
const communityRoot = options.communityAppsRoot ?? resolveCommunityAppsRoot(options.project.root);
|
|
48
|
-
if (!communityRoot) {
|
|
49
|
-
return {
|
|
50
|
-
ok: false,
|
|
51
|
-
code: "COMMUNITY_APPS_ROOT_MISSING",
|
|
52
|
-
message: "找不到 synopkgland-community-apps/apps。請在 monorepo 根目錄執行,或設定 SYNOPKGLAND_COMMUNITY_APPS_ROOT。",
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
const contract = loadContract(options.project.contractPath).contract;
|
|
56
|
-
const packageIdentity = contract.package;
|
|
57
|
-
if (typeof packageIdentity !== "string" || packageIdentity.length === 0) {
|
|
58
|
-
return {
|
|
59
|
-
ok: false,
|
|
60
|
-
code: "PACKAGE_IDENTITY_MISSING",
|
|
61
|
-
message: "App Contract 缺少有效的 package 欄位。",
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
const targetDir = communityAppDirectory(communityRoot, packageIdentity);
|
|
65
|
-
try {
|
|
66
|
-
assertDirectoryMatchesPackage(packageIdentity, targetDir);
|
|
67
|
-
}
|
|
68
|
-
catch (error) {
|
|
69
|
-
return {
|
|
70
|
-
ok: false,
|
|
71
|
-
code: "PACKAGE_DIRECTORY_MISMATCH",
|
|
72
|
-
message: error instanceof Error ? error.message : String(error),
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
if (resolve(options.project.root) === resolve(targetDir)) {
|
|
76
|
-
return {
|
|
77
|
-
ok: false,
|
|
78
|
-
code: "PUBLISH_SOURCE_IS_COMMUNITY_APP",
|
|
79
|
-
message: "publish 的專案根目錄不能是 Community Apps 目標目錄。請對開發專案執行,不要對 synopkgland-community-apps/apps/<package> 本身執行。",
|
|
80
|
-
packageIdentity,
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
syncPackagingDefinition(options.project, targetDir);
|
|
84
|
-
const appRelativePath = join(COMMUNITY_APPS_DIR, COMMUNITY_APPS_APPS_DIR, packageIdentity);
|
|
85
|
-
const syncedFiles = listSyncedRelativePaths(targetDir);
|
|
86
|
-
const baseBranch = options.baseBranch ?? "master";
|
|
87
|
-
const branchPrefix = options.branchPrefix ?? "publish";
|
|
88
|
-
const stamp = (options.now ?? (() => new Date()))().toISOString().replace(/[:.]/g, "-");
|
|
89
|
-
const branchName = `${branchPrefix}/${slugifyPackageIdentity(packageIdentity)}-${stamp}`;
|
|
90
|
-
if (!options.git.hasChanges([appRelativePath])) {
|
|
91
|
-
return {
|
|
92
|
-
ok: true,
|
|
93
|
-
noChanges: true,
|
|
94
|
-
packageIdentity,
|
|
95
|
-
message: "Community Apps 定義已是最新,未建立 PR。",
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
const previousBranch = options.git.currentBranch();
|
|
99
|
-
try {
|
|
100
|
-
options.git.checkoutNewBranch(branchName);
|
|
101
|
-
options.git.add([appRelativePath]);
|
|
102
|
-
options.git.commit(`Publish ${packageIdentity} packaging definition`, [appRelativePath]);
|
|
103
|
-
options.git.push(branchName, true);
|
|
104
|
-
const pr = await options.github.createPullRequest({
|
|
105
|
-
title: `Publish ${packageIdentity}`,
|
|
106
|
-
body: renderPullRequestBody(packageIdentity, appRelativePath, syncedFiles),
|
|
107
|
-
base: baseBranch,
|
|
108
|
-
head: branchName,
|
|
109
|
-
});
|
|
110
|
-
return {
|
|
111
|
-
ok: true,
|
|
112
|
-
packageIdentity,
|
|
113
|
-
prUrl: pr.url,
|
|
114
|
-
prNumber: pr.number,
|
|
115
|
-
branchName,
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
catch (error) {
|
|
119
|
-
return {
|
|
120
|
-
ok: false,
|
|
121
|
-
code: "PUBLISH_PR_FAILED",
|
|
122
|
-
message: error instanceof Error ? error.message : String(error),
|
|
123
|
-
packageIdentity,
|
|
124
|
-
branchName,
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
finally {
|
|
128
|
-
try {
|
|
129
|
-
options.git.checkoutExisting(previousBranch);
|
|
130
|
-
}
|
|
131
|
-
catch {
|
|
132
|
-
// Best effort restore; caller may be in a temp repo during tests.
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
export { resolveCommunityAppsRoot, communityAppDirectory, listCommunityAppDirectories } from "./community-path.js";
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import type { ProjectRoot } from "../discovery.js";
|
|
2
|
-
export declare function assertDistinctSyncPaths(sourceRoot: string, targetDir: string): void;
|
|
3
|
-
export declare function syncPackagingDefinition(source: ProjectRoot, targetDir: string): void;
|
|
4
|
-
export declare function listSyncedRelativePaths(targetDir: string): string[];
|