@simplysf/simply-cicd-core 0.11.0 → 0.12.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 +1 -1
- package/lib/build/aerBinary.d.ts +30 -0
- package/lib/build/aerBinary.js +114 -0
- package/lib/build/aerBinary.js.map +1 -0
- package/lib/build/aerPackage.d.ts +64 -0
- package/lib/build/aerPackage.js +126 -0
- package/lib/build/aerPackage.js.map +1 -0
- package/lib/build/packageVersionArtifact.d.ts +51 -0
- package/lib/build/packageVersionArtifact.js +143 -0
- package/lib/build/packageVersionArtifact.js.map +1 -0
- package/lib/build/publishAerPackage.d.ts +49 -0
- package/lib/build/publishAerPackage.js +335 -0
- package/lib/build/publishAerPackage.js.map +1 -0
- package/lib/build/skipGuard.js +1 -0
- package/lib/build/skipGuard.js.map +1 -1
- package/lib/build/utamPageObjects.d.ts +1 -51
- package/lib/build/utamPageObjects.js +4 -124
- package/lib/build/utamPageObjects.js.map +1 -1
- package/lib/index.d.ts +4 -0
- package/lib/index.js +4 -0
- package/lib/index.js.map +1 -1
- package/lib/sfPackages.d.ts +25 -0
- package/lib/sfPackages.js +39 -0
- package/lib/sfPackages.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -59,7 +59,7 @@ The modules `@simplysf/simply-cicd`'s commands run, also exported from the packa
|
|
|
59
59
|
|
|
60
60
|
| Area | Main exports |
|
|
61
61
|
| ------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
62
|
-
| Build | `createScratchOrg`, `deleteScratchOrg`, `cleanupScratchOrgs`, `pushToScratch`, `runScratchApexTests`, `installDependencies`, `createPackageVersion`, `createFallbackTag`, `determinePackageChanges`, `runLwcJest`, `runCheckPermissionSets`, `runFlowDelta`, `runFlexipageDelta`, `publishUtamPageObjects`, `VersionTagFormat`
|
|
62
|
+
| Build | `createScratchOrg`, `deleteScratchOrg`, `cleanupScratchOrgs`, `pushToScratch`, `runScratchApexTests`, `installDependencies`, `createPackageVersion`, `createFallbackTag`, `determinePackageChanges`, `runLwcJest`, `runCheckPermissionSets`, `runFlowDelta`, `runFlexipageDelta`, `publishUtamPageObjects`, `publishAerPackage`, `VersionTagFormat` |
|
|
63
63
|
| Deploy | `deployProject`, `deployHappySoup`, `runDeployStage`, `validateDeployFiles`, `toProjectStageOptions`, `toHappySoupStageOptions`, plus the `deployCommon` helpers (`runDeploymentSteps`, `loadProgress`, `updateProgress`, `installDeployPackageDependencies`, `runDeployApexTests`, …) |
|
|
64
64
|
| Notify | `sendBuildNotification`, `projectNotifyBeforeScript`/`projectNotifyAfterScript`, `happySoupNotifyBeforeScript`/`happySoupNotifyAfterScript`, `getCommitStories`, `getRemoteCommitStories`, `sendNotification`, `renderNotifyTemplate`, `NOTIFY_TEMPLATES` |
|
|
65
65
|
| sfdx-dependabot | `discoverEligibleProjects`, `processProject`, `applyProjectUpdate`, `updateSfdxProject` |
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export type ResolveAerBinaryOptions = {
|
|
2
|
+
/** `--aer-path`. Must exist when given. */
|
|
3
|
+
aerPath?: string;
|
|
4
|
+
/** Defaults to `process.env`. */
|
|
5
|
+
env?: NodeJS.ProcessEnv;
|
|
6
|
+
/** Defaults to `process.platform`. */
|
|
7
|
+
platform?: NodeJS.Platform;
|
|
8
|
+
/** Defaults to `os.homedir()`. */
|
|
9
|
+
homeDir?: string;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Where `@octoberswimmer/aer-sf-plugin` keeps the aer binary it downloads. Mirrors the plugin's own
|
|
13
|
+
* `defaultStorageDir`, so a job that already ran `sf aer apex run test --install-aer` is covered.
|
|
14
|
+
*/
|
|
15
|
+
export declare function aerPluginBinaryPath(env: NodeJS.ProcessEnv, platform: NodeJS.Platform, homeDir: string): string;
|
|
16
|
+
/**
|
|
17
|
+
* Find the aer binary, in the order `@octoberswimmer/aer-sf-plugin` uses: `--aer-path`, then
|
|
18
|
+
* `AER_BIN`, then the plugin's download cache, then `aer` on `PATH`.
|
|
19
|
+
*
|
|
20
|
+
* Unlike the plugin, this never downloads aer. The plugin always takes the latest release, which
|
|
21
|
+
* would let the published package format change under a pipeline with no change to the pipeline.
|
|
22
|
+
*
|
|
23
|
+
* @throws {Error} If `--aer-path` doesn't exist, or no aer binary is found anywhere.
|
|
24
|
+
*/
|
|
25
|
+
export declare function resolveAerBinary(options?: ResolveAerBinaryOptions): Promise<string>;
|
|
26
|
+
/**
|
|
27
|
+
* @returns The version `aer --version` reports, e.g. `v1.4.45`, or `undefined` if it reports
|
|
28
|
+
* nothing recognisable. Only used for the log and the result, so it never fails the run.
|
|
29
|
+
*/
|
|
30
|
+
export declare function readAerVersion(aerBinary: string): Promise<string | undefined>;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2026, SimplySF.
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
import { constants, promises as fs } from 'node:fs';
|
|
17
|
+
import os from 'node:os';
|
|
18
|
+
import path from 'node:path';
|
|
19
|
+
import { execa } from 'execa';
|
|
20
|
+
import { logger } from '../logger.js';
|
|
21
|
+
/** Where aer's releases are published, for the not-found hint. */
|
|
22
|
+
const AER_DIST_REPO = 'https://github.com/octoberswimmer/aer-dist';
|
|
23
|
+
async function isExecutable(file) {
|
|
24
|
+
try {
|
|
25
|
+
const stat = await fs.stat(file);
|
|
26
|
+
if (!stat.isFile()) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
// Windows has no execute bit; existence is the best available check there.
|
|
30
|
+
await fs.access(file, process.platform === 'win32' ? constants.F_OK : constants.X_OK);
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Where `@octoberswimmer/aer-sf-plugin` keeps the aer binary it downloads. Mirrors the plugin's own
|
|
39
|
+
* `defaultStorageDir`, so a job that already ran `sf aer apex run test --install-aer` is covered.
|
|
40
|
+
*/
|
|
41
|
+
export function aerPluginBinaryPath(env, platform, homeDir) {
|
|
42
|
+
const binary = platform === 'win32' ? 'aer.exe' : 'aer';
|
|
43
|
+
let dataDir = env.XDG_DATA_HOME ?? path.join(homeDir, '.local', 'share');
|
|
44
|
+
if (platform === 'win32') {
|
|
45
|
+
dataDir = env.LOCALAPPDATA ?? path.join(homeDir, 'AppData', 'Local');
|
|
46
|
+
}
|
|
47
|
+
else if (platform === 'darwin') {
|
|
48
|
+
dataDir = path.join(homeDir, 'Library', 'Application Support');
|
|
49
|
+
}
|
|
50
|
+
return path.join(dataDir, 'aer-sf-plugin', 'aer-bin', binary);
|
|
51
|
+
}
|
|
52
|
+
async function findOnPath(env, platform) {
|
|
53
|
+
const pathValue = env.PATH ?? env.Path ?? '';
|
|
54
|
+
const names = platform === 'win32' ? ['aer.exe', 'aer'] : ['aer'];
|
|
55
|
+
for (const dir of pathValue.split(path.delimiter).filter(Boolean)) {
|
|
56
|
+
for (const name of names) {
|
|
57
|
+
const candidate = path.join(dir, name);
|
|
58
|
+
// eslint-disable-next-line no-await-in-loop -- first match wins, so the search is sequential
|
|
59
|
+
if (await isExecutable(candidate)) {
|
|
60
|
+
return candidate;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Find the aer binary, in the order `@octoberswimmer/aer-sf-plugin` uses: `--aer-path`, then
|
|
68
|
+
* `AER_BIN`, then the plugin's download cache, then `aer` on `PATH`.
|
|
69
|
+
*
|
|
70
|
+
* Unlike the plugin, this never downloads aer. The plugin always takes the latest release, which
|
|
71
|
+
* would let the published package format change under a pipeline with no change to the pipeline.
|
|
72
|
+
*
|
|
73
|
+
* @throws {Error} If `--aer-path` doesn't exist, or no aer binary is found anywhere.
|
|
74
|
+
*/
|
|
75
|
+
export async function resolveAerBinary(options = {}) {
|
|
76
|
+
const env = options.env ?? process.env;
|
|
77
|
+
const platform = options.platform ?? process.platform;
|
|
78
|
+
const homeDir = options.homeDir ?? os.homedir();
|
|
79
|
+
if (options.aerPath) {
|
|
80
|
+
if (await isExecutable(options.aerPath)) {
|
|
81
|
+
return options.aerPath;
|
|
82
|
+
}
|
|
83
|
+
throw new Error(`--aer-path is set to "${options.aerPath}", but no executable file is there.`);
|
|
84
|
+
}
|
|
85
|
+
if (env.AER_BIN) {
|
|
86
|
+
if (await isExecutable(env.AER_BIN)) {
|
|
87
|
+
return env.AER_BIN;
|
|
88
|
+
}
|
|
89
|
+
logger.warn(`AER_BIN is set to "${env.AER_BIN}", but no executable file is there. Looking elsewhere.`);
|
|
90
|
+
}
|
|
91
|
+
const pluginBinary = aerPluginBinaryPath(env, platform, homeDir);
|
|
92
|
+
if (await isExecutable(pluginBinary)) {
|
|
93
|
+
return pluginBinary;
|
|
94
|
+
}
|
|
95
|
+
const fromPath = await findOnPath(env, platform);
|
|
96
|
+
if (fromPath) {
|
|
97
|
+
return fromPath;
|
|
98
|
+
}
|
|
99
|
+
throw new Error(`Could not find the aer binary. Install a pinned release from ${AER_DIST_REPO} and set AER_BIN (or --aer-path) to it, or put aer on PATH.`);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* @returns The version `aer --version` reports, e.g. `v1.4.45`, or `undefined` if it reports
|
|
103
|
+
* nothing recognisable. Only used for the log and the result, so it never fails the run.
|
|
104
|
+
*/
|
|
105
|
+
export async function readAerVersion(aerBinary) {
|
|
106
|
+
try {
|
|
107
|
+
const { stdout } = await execa(aerBinary, ['--version'], { env: { AER_AUTOUPDATE_DISABLED: '1' } });
|
|
108
|
+
return /v?\d+\.\d+\.\d+\S*/.exec(stdout)?.[0];
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=aerBinary.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aerBinary.js","sourceRoot":"","sources":["../../src/build/aerBinary.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { type SfdxProject } from '@simplysf/simply-core';
|
|
2
|
+
/** The dotenv keys this feature writes, for a downstream job or a consuming repo to fetch by. */
|
|
3
|
+
export declare const AER_PACKAGE_URL_KEY = "AER_PACKAGE_URL";
|
|
4
|
+
export declare const AER_PACKAGE_FILE_KEY = "AER_PACKAGE_FILE";
|
|
5
|
+
export declare const AER_PACKAGE_VERSION_KEY = "AER_PACKAGE_VERSION";
|
|
6
|
+
/** The placeholders a repository URL template may use. */
|
|
7
|
+
export declare const REPOSITORY_URL_PLACEHOLDERS: readonly ["name", "version", "versionNumber", "channel", "namespace", "file"];
|
|
8
|
+
export type RepositoryUrlPlaceholder = (typeof REPOSITORY_URL_PLACEHOLDERS)[number];
|
|
9
|
+
export type RepositoryUrlValues = Record<RepositoryUrlPlaceholder, string>;
|
|
10
|
+
/** `plugins.simply.aer` in `sfdx-project.json`. Every field is optional. */
|
|
11
|
+
export type AerPluginConfig = {
|
|
12
|
+
/** URL template the `.pkg` is uploaded to and dependencies are downloaded from. */
|
|
13
|
+
repositoryUrl?: string;
|
|
14
|
+
};
|
|
15
|
+
/** @returns `plugins.simply.aer`, or an empty object when the project declares none. */
|
|
16
|
+
export declare function readAerPluginConfig(project: SfdxProject): AerPluginConfig;
|
|
17
|
+
/**
|
|
18
|
+
* Reduce a package name or namespace to something aer accepts in a package file name: letters,
|
|
19
|
+
* digits and underscores, starting with a letter.
|
|
20
|
+
*
|
|
21
|
+
* Each run of anything else becomes one underscore, and a name that doesn't start with a letter is
|
|
22
|
+
* prefixed with `P`. The rule is fixed rather than configurable because a dependent repo has to
|
|
23
|
+
* compute the same file name to download the package.
|
|
24
|
+
*
|
|
25
|
+
* @throws {Error} If nothing usable is left, e.g. for a name made only of punctuation.
|
|
26
|
+
*/
|
|
27
|
+
export declare function toAerIdentifier(raw: string): string;
|
|
28
|
+
/**
|
|
29
|
+
* The file name an aer package is published and loaded under, `<package>@<namespace>.pkg`.
|
|
30
|
+
*
|
|
31
|
+
* A package with no namespace uses its own name as the namespace, since aer requires one.
|
|
32
|
+
*/
|
|
33
|
+
export declare function toAerPackageFile(packageName: string, namespacePrefix: string | null | undefined): {
|
|
34
|
+
file: string;
|
|
35
|
+
name: string;
|
|
36
|
+
namespace: string;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Fill in a repository URL template.
|
|
40
|
+
*
|
|
41
|
+
* Every value is URL-encoded, so `@` in the file name becomes `%40` and `/` in a branch can't add a
|
|
42
|
+
* path segment. Anything in braces that isn't a known placeholder fails, which also catches a CI
|
|
43
|
+
* variable such as `${CI_API_V4_URL}` that the shell didn't expand.
|
|
44
|
+
*
|
|
45
|
+
* @throws {Error} If the template uses an unknown placeholder, or uses neither `{version}` nor
|
|
46
|
+
* `{versionNumber}` — every version would then be uploaded to the same URL.
|
|
47
|
+
*/
|
|
48
|
+
export declare function renderRepositoryUrl(template: string, values: RepositoryUrlValues): string;
|
|
49
|
+
/**
|
|
50
|
+
* Split a `Name: value` header, e.g. `Authorization: Bearer abc`, into its two parts.
|
|
51
|
+
*
|
|
52
|
+
* @throws {Error} If there's no colon, the name isn't a valid header name, or the value is empty.
|
|
53
|
+
* The message never includes the value, which is usually a secret.
|
|
54
|
+
*/
|
|
55
|
+
export declare function parseRepositoryHeader(raw: string): {
|
|
56
|
+
name: string;
|
|
57
|
+
value: string;
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Whether there's any Apex under `rootDir` for aer to compile.
|
|
61
|
+
*
|
|
62
|
+
* @returns `false` when `rootDir` doesn't exist — a metadata-only package is a skip, not an error.
|
|
63
|
+
*/
|
|
64
|
+
export declare function hasApexSources(rootDir: string): Promise<boolean>;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2026, SimplySF.
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
import { promises as fs } from 'node:fs';
|
|
17
|
+
import path from 'node:path';
|
|
18
|
+
import { getPluginConfig } from '@simplysf/simply-core';
|
|
19
|
+
/** The dotenv keys this feature writes, for a downstream job or a consuming repo to fetch by. */
|
|
20
|
+
export const AER_PACKAGE_URL_KEY = 'AER_PACKAGE_URL';
|
|
21
|
+
export const AER_PACKAGE_FILE_KEY = 'AER_PACKAGE_FILE';
|
|
22
|
+
export const AER_PACKAGE_VERSION_KEY = 'AER_PACKAGE_VERSION';
|
|
23
|
+
/**
|
|
24
|
+
* What aer accepts as each half of a package file name, `<package>@<namespace>.pkg`. aer reads a
|
|
25
|
+
* package's namespace from its file name and rejects anything else.
|
|
26
|
+
*/
|
|
27
|
+
const AER_IDENTIFIER_PATTERN = /^[A-Za-z][A-Za-z0-9_]*$/;
|
|
28
|
+
/** The placeholders a repository URL template may use. */
|
|
29
|
+
export const REPOSITORY_URL_PLACEHOLDERS = [
|
|
30
|
+
'name',
|
|
31
|
+
'version',
|
|
32
|
+
'versionNumber',
|
|
33
|
+
'channel',
|
|
34
|
+
'namespace',
|
|
35
|
+
'file',
|
|
36
|
+
];
|
|
37
|
+
/** @returns `plugins.simply.aer`, or an empty object when the project declares none. */
|
|
38
|
+
export function readAerPluginConfig(project) {
|
|
39
|
+
return getPluginConfig(project, 'plugins.simply.aer') ?? {};
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Reduce a package name or namespace to something aer accepts in a package file name: letters,
|
|
43
|
+
* digits and underscores, starting with a letter.
|
|
44
|
+
*
|
|
45
|
+
* Each run of anything else becomes one underscore, and a name that doesn't start with a letter is
|
|
46
|
+
* prefixed with `P`. The rule is fixed rather than configurable because a dependent repo has to
|
|
47
|
+
* compute the same file name to download the package.
|
|
48
|
+
*
|
|
49
|
+
* @throws {Error} If nothing usable is left, e.g. for a name made only of punctuation.
|
|
50
|
+
*/
|
|
51
|
+
export function toAerIdentifier(raw) {
|
|
52
|
+
const cleaned = raw.trim().replace(/[^A-Za-z0-9_]+/g, '_');
|
|
53
|
+
const identifier = /^[A-Za-z]/.test(cleaned) ? cleaned : `P${cleaned}`;
|
|
54
|
+
if (!/[A-Za-z0-9]/.test(cleaned) || !AER_IDENTIFIER_PATTERN.test(identifier)) {
|
|
55
|
+
throw new Error(`Cannot derive an aer package name from "${raw}": aer needs a name that starts with a letter and contains only letters, digits and underscores.`);
|
|
56
|
+
}
|
|
57
|
+
return identifier;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* The file name an aer package is published and loaded under, `<package>@<namespace>.pkg`.
|
|
61
|
+
*
|
|
62
|
+
* A package with no namespace uses its own name as the namespace, since aer requires one.
|
|
63
|
+
*/
|
|
64
|
+
export function toAerPackageFile(packageName, namespacePrefix) {
|
|
65
|
+
const name = toAerIdentifier(packageName);
|
|
66
|
+
const namespace = namespacePrefix ? toAerIdentifier(namespacePrefix) : name;
|
|
67
|
+
return { file: `${name}@${namespace}.pkg`, name, namespace };
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Fill in a repository URL template.
|
|
71
|
+
*
|
|
72
|
+
* Every value is URL-encoded, so `@` in the file name becomes `%40` and `/` in a branch can't add a
|
|
73
|
+
* path segment. Anything in braces that isn't a known placeholder fails, which also catches a CI
|
|
74
|
+
* variable such as `${CI_API_V4_URL}` that the shell didn't expand.
|
|
75
|
+
*
|
|
76
|
+
* @throws {Error} If the template uses an unknown placeholder, or uses neither `{version}` nor
|
|
77
|
+
* `{versionNumber}` — every version would then be uploaded to the same URL.
|
|
78
|
+
*/
|
|
79
|
+
export function renderRepositoryUrl(template, values) {
|
|
80
|
+
const used = new Set();
|
|
81
|
+
const rendered = template.replace(/\{([^{}]*)\}/g, (match, key) => {
|
|
82
|
+
if (!REPOSITORY_URL_PLACEHOLDERS.includes(key)) {
|
|
83
|
+
throw new Error(`Unknown placeholder ${match} in the aer repository URL. Use any of: ${REPOSITORY_URL_PLACEHOLDERS.map((p) => `{${p}}`).join(', ')}.`);
|
|
84
|
+
}
|
|
85
|
+
used.add(key);
|
|
86
|
+
return encodeURIComponent(values[key]);
|
|
87
|
+
});
|
|
88
|
+
if (!used.has('version') && !used.has('versionNumber')) {
|
|
89
|
+
throw new Error('The aer repository URL must include {version} or {versionNumber}, or every package version would be uploaded to the same URL.');
|
|
90
|
+
}
|
|
91
|
+
return rendered;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Split a `Name: value` header, e.g. `Authorization: Bearer abc`, into its two parts.
|
|
95
|
+
*
|
|
96
|
+
* @throws {Error} If there's no colon, the name isn't a valid header name, or the value is empty.
|
|
97
|
+
* The message never includes the value, which is usually a secret.
|
|
98
|
+
*/
|
|
99
|
+
export function parseRepositoryHeader(raw) {
|
|
100
|
+
const separator = raw.indexOf(':');
|
|
101
|
+
const name = separator === -1 ? '' : raw.slice(0, separator).trim();
|
|
102
|
+
const value = separator === -1 ? '' : raw.slice(separator + 1).trim();
|
|
103
|
+
if (!/^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/.test(name) || value.length === 0) {
|
|
104
|
+
throw new Error('The aer repository header must look like "Name: value", e.g. "Authorization: Bearer <token>".');
|
|
105
|
+
}
|
|
106
|
+
return { name, value };
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Whether there's any Apex under `rootDir` for aer to compile.
|
|
110
|
+
*
|
|
111
|
+
* @returns `false` when `rootDir` doesn't exist — a metadata-only package is a skip, not an error.
|
|
112
|
+
*/
|
|
113
|
+
export async function hasApexSources(rootDir) {
|
|
114
|
+
let entries;
|
|
115
|
+
try {
|
|
116
|
+
entries = await fs.readdir(rootDir, { recursive: true, withFileTypes: true });
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
if (error.code === 'ENOENT') {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
throw error;
|
|
123
|
+
}
|
|
124
|
+
return entries.some((entry) => entry.isFile() && ['.cls', '.trigger'].includes(path.extname(entry.name)));
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=aerPackage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aerPackage.js","sourceRoot":"","sources":["../../src/build/aerPackage.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/** The dotenv key `create-package-version` and `create-fallback-tag` both write their `04t` under. */
|
|
2
|
+
export declare const SUBSCRIBER_PACKAGE_VERSION_ID_KEY = "SUBSCRIBER_PACKAGE_VERSION_ID";
|
|
3
|
+
/**
|
|
4
|
+
* Reduce a git tag suffix to something semver accepts as a prerelease identifier.
|
|
5
|
+
*
|
|
6
|
+
* Semver's prerelease alphabet is `[0-9A-Za-z-]`; a branch name's `/` and `.` are the common
|
|
7
|
+
* offenders, so each run of anything else collapses to a single hyphen.
|
|
8
|
+
*/
|
|
9
|
+
export declare function sanitizePrereleaseId(raw: string): string;
|
|
10
|
+
/**
|
|
11
|
+
* Map a Salesforce package version onto a semver version.
|
|
12
|
+
*
|
|
13
|
+
* The build number becomes the first prerelease identifier and the git tag's suffix, if any, the
|
|
14
|
+
* second — so `1.2.3.4` on a release branch is `1.2.3-4`, and the same version built from `beta`
|
|
15
|
+
* is `1.2.3-4.beta`. This is the transformation `sfdx-project.json` already uses for a dependency
|
|
16
|
+
* pin (`"package": "MyPackage@1.2.3-4"`), not a new convention.
|
|
17
|
+
*
|
|
18
|
+
* @throws {Error} If the version isn't `major.minor.patch.build`. Publishing a version derived
|
|
19
|
+
* from something unrecognised is worse than failing the job.
|
|
20
|
+
*/
|
|
21
|
+
export declare function toNpmVersion(salesforceVersion: string, tagSuffix?: string): string;
|
|
22
|
+
/**
|
|
23
|
+
* The npm dist-tag for a build: `latest` for a release-branch build, else the sanitized tag suffix,
|
|
24
|
+
* so `npm install <pkg>@beta` reaches the newest build off the `beta` package branch.
|
|
25
|
+
*/
|
|
26
|
+
export declare function toDistTag(tagSuffix?: string): string;
|
|
27
|
+
/** @returns The last value assigned to `key` in dotenv-shaped `content`, or `undefined`. */
|
|
28
|
+
export declare function readEnvFileValue(content: string, key: string): string | undefined;
|
|
29
|
+
export type ResolveSubscriberPackageVersionIdOptions = {
|
|
30
|
+
/** `--subscriber-package-version-id`, if passed. */
|
|
31
|
+
flagValue?: string;
|
|
32
|
+
/** The environment to read `SUBSCRIBER_PACKAGE_VERSION_ID` from. Defaults to `process.env`. */
|
|
33
|
+
env?: NodeJS.ProcessEnv;
|
|
34
|
+
/** Dotenv file to fall back to — the one `create-package-version` wrote earlier in this job. */
|
|
35
|
+
envFile: string;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Find the `04t` this run should publish page objects for.
|
|
39
|
+
*
|
|
40
|
+
* Three sources, because the command is useful from two pipeline shapes and they surface the value
|
|
41
|
+
* differently: an explicit flag; `SUBSCRIBER_PACKAGE_VERSION_ID` in the environment, which is how a
|
|
42
|
+
* *separate* job receives `create-package-version`'s dotenv report artifact; and the dotenv file
|
|
43
|
+
* itself, which is how the *same* job sees it, since a dotenv report only ever reaches other jobs.
|
|
44
|
+
*
|
|
45
|
+
* An explicit flag that isn't a `04t` throws — the caller asked for something specific and got it
|
|
46
|
+
* wrong. A malformed value discovered rather than passed only warns, and the search continues.
|
|
47
|
+
*
|
|
48
|
+
* @returns The ID, or `undefined` when no source has one.
|
|
49
|
+
* @throws {Error} If `flagValue` is not a subscriber package version ID.
|
|
50
|
+
*/
|
|
51
|
+
export declare function resolveSubscriberPackageVersionId(options: ResolveSubscriberPackageVersionIdOptions): Promise<string | undefined>;
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2026, SimplySF.
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
import { promises as fs } from 'node:fs';
|
|
17
|
+
import { isSubscriberPackageVersionId } from '@simplysf/simply-core';
|
|
18
|
+
import { logger } from '../logger.js';
|
|
19
|
+
// Helpers for any artifact published per package version (UTAM page objects, aer packages): finding
|
|
20
|
+
// the `04t` a build produced and mapping its Salesforce version number onto a semver version.
|
|
21
|
+
/** The dotenv key `create-package-version` and `create-fallback-tag` both write their `04t` under. */
|
|
22
|
+
export const SUBSCRIBER_PACKAGE_VERSION_ID_KEY = 'SUBSCRIBER_PACKAGE_VERSION_ID';
|
|
23
|
+
/** `sf package version report` returns `Version` as `major.minor.patch.build`, which is not semver. */
|
|
24
|
+
const SALESFORCE_VERSION_PATTERN = /^(?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+)\.(?<build>\d+)$/;
|
|
25
|
+
/**
|
|
26
|
+
* Strip leading and trailing hyphens. Done by index rather than `/^-+|-+$/`, whose trailing
|
|
27
|
+
* alternative backtracks quadratically on a long run of interior hyphens.
|
|
28
|
+
*/
|
|
29
|
+
function trimHyphens(value) {
|
|
30
|
+
let start = 0;
|
|
31
|
+
let end = value.length;
|
|
32
|
+
while (start < end && value[start] === '-')
|
|
33
|
+
start++;
|
|
34
|
+
while (end > start && value[end - 1] === '-')
|
|
35
|
+
end--;
|
|
36
|
+
return value.slice(start, end);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Reduce a git tag suffix to something semver accepts as a prerelease identifier.
|
|
40
|
+
*
|
|
41
|
+
* Semver's prerelease alphabet is `[0-9A-Za-z-]`; a branch name's `/` and `.` are the common
|
|
42
|
+
* offenders, so each run of anything else collapses to a single hyphen.
|
|
43
|
+
*/
|
|
44
|
+
export function sanitizePrereleaseId(raw) {
|
|
45
|
+
const cleaned = trimHyphens(raw.replace(/[^0-9A-Za-z-]+/g, '-'));
|
|
46
|
+
// Semver forbids leading zeros in an all-numeric identifier, so a branch literally named `007`
|
|
47
|
+
// would otherwise produce a version every registry rejects. Prefixing keeps it traceable.
|
|
48
|
+
return /^0\d+$/.test(cleaned) ? `v${cleaned}` : cleaned;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Map a Salesforce package version onto a semver version.
|
|
52
|
+
*
|
|
53
|
+
* The build number becomes the first prerelease identifier and the git tag's suffix, if any, the
|
|
54
|
+
* second — so `1.2.3.4` on a release branch is `1.2.3-4`, and the same version built from `beta`
|
|
55
|
+
* is `1.2.3-4.beta`. This is the transformation `sfdx-project.json` already uses for a dependency
|
|
56
|
+
* pin (`"package": "MyPackage@1.2.3-4"`), not a new convention.
|
|
57
|
+
*
|
|
58
|
+
* @throws {Error} If the version isn't `major.minor.patch.build`. Publishing a version derived
|
|
59
|
+
* from something unrecognised is worse than failing the job.
|
|
60
|
+
*/
|
|
61
|
+
export function toNpmVersion(salesforceVersion, tagSuffix) {
|
|
62
|
+
const match = SALESFORCE_VERSION_PATTERN.exec(salesforceVersion.trim());
|
|
63
|
+
if (!match?.groups) {
|
|
64
|
+
throw new Error(`Cannot derive an npm version from Salesforce package version "${salesforceVersion}": expected major.minor.patch.build.`);
|
|
65
|
+
}
|
|
66
|
+
const { major, minor, patch, build } = match.groups;
|
|
67
|
+
const sanitizedSuffix = tagSuffix ? sanitizePrereleaseId(tagSuffix) : '';
|
|
68
|
+
const prerelease = sanitizedSuffix ? `${build}.${sanitizedSuffix}` : build;
|
|
69
|
+
return `${major}.${minor}.${patch}-${prerelease}`;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* The npm dist-tag for a build: `latest` for a release-branch build, else the sanitized tag suffix,
|
|
73
|
+
* so `npm install <pkg>@beta` reaches the newest build off the `beta` package branch.
|
|
74
|
+
*/
|
|
75
|
+
export function toDistTag(tagSuffix) {
|
|
76
|
+
const sanitized = tagSuffix ? sanitizePrereleaseId(tagSuffix) : '';
|
|
77
|
+
return sanitized.length > 0 ? sanitized : 'latest';
|
|
78
|
+
}
|
|
79
|
+
/** @returns The last value assigned to `key` in dotenv-shaped `content`, or `undefined`. */
|
|
80
|
+
export function readEnvFileValue(content, key) {
|
|
81
|
+
let found;
|
|
82
|
+
for (const line of content.split(/\r?\n/)) {
|
|
83
|
+
const trimmed = line.trim();
|
|
84
|
+
if (trimmed.length === 0 || trimmed.startsWith('#')) {
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
const separator = trimmed.indexOf('=');
|
|
88
|
+
if (separator !== -1 && trimmed.slice(0, separator).trim() === key) {
|
|
89
|
+
// Last wins: the file is appended to, so a later line is the newer value.
|
|
90
|
+
found = trimmed.slice(separator + 1).trim();
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return found;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Find the `04t` this run should publish page objects for.
|
|
97
|
+
*
|
|
98
|
+
* Three sources, because the command is useful from two pipeline shapes and they surface the value
|
|
99
|
+
* differently: an explicit flag; `SUBSCRIBER_PACKAGE_VERSION_ID` in the environment, which is how a
|
|
100
|
+
* *separate* job receives `create-package-version`'s dotenv report artifact; and the dotenv file
|
|
101
|
+
* itself, which is how the *same* job sees it, since a dotenv report only ever reaches other jobs.
|
|
102
|
+
*
|
|
103
|
+
* An explicit flag that isn't a `04t` throws — the caller asked for something specific and got it
|
|
104
|
+
* wrong. A malformed value discovered rather than passed only warns, and the search continues.
|
|
105
|
+
*
|
|
106
|
+
* @returns The ID, or `undefined` when no source has one.
|
|
107
|
+
* @throws {Error} If `flagValue` is not a subscriber package version ID.
|
|
108
|
+
*/
|
|
109
|
+
export async function resolveSubscriberPackageVersionId(options) {
|
|
110
|
+
const { flagValue, env = process.env, envFile } = options;
|
|
111
|
+
if (flagValue) {
|
|
112
|
+
if (!isSubscriberPackageVersionId(flagValue)) {
|
|
113
|
+
throw new Error(`--subscriber-package-version-id must be a subscriber package version ID (04t), got "${flagValue}".`);
|
|
114
|
+
}
|
|
115
|
+
return flagValue;
|
|
116
|
+
}
|
|
117
|
+
const fromEnv = env[SUBSCRIBER_PACKAGE_VERSION_ID_KEY];
|
|
118
|
+
if (fromEnv) {
|
|
119
|
+
if (isSubscriberPackageVersionId(fromEnv)) {
|
|
120
|
+
logger.info(`Using ${SUBSCRIBER_PACKAGE_VERSION_ID_KEY} from the environment: ${fromEnv}`);
|
|
121
|
+
return fromEnv;
|
|
122
|
+
}
|
|
123
|
+
logger.warn(`Ignoring ${SUBSCRIBER_PACKAGE_VERSION_ID_KEY}="${fromEnv}" from the environment: not a 04t ID.`);
|
|
124
|
+
}
|
|
125
|
+
let content;
|
|
126
|
+
try {
|
|
127
|
+
content = await fs.readFile(envFile, 'utf8');
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
return undefined;
|
|
131
|
+
}
|
|
132
|
+
const fromFile = readEnvFileValue(content, SUBSCRIBER_PACKAGE_VERSION_ID_KEY);
|
|
133
|
+
if (!fromFile) {
|
|
134
|
+
return undefined;
|
|
135
|
+
}
|
|
136
|
+
if (!isSubscriberPackageVersionId(fromFile)) {
|
|
137
|
+
logger.warn(`Ignoring ${SUBSCRIBER_PACKAGE_VERSION_ID_KEY}="${fromFile}" in ${envFile}: not a 04t ID.`);
|
|
138
|
+
return undefined;
|
|
139
|
+
}
|
|
140
|
+
logger.info(`Using ${SUBSCRIBER_PACKAGE_VERSION_ID_KEY} from ${envFile}: ${fromFile}`);
|
|
141
|
+
return fromFile;
|
|
142
|
+
}
|
|
143
|
+
//# sourceMappingURL=packageVersionArtifact.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"packageVersionArtifact.js","sourceRoot":"","sources":["../../src/build/packageVersionArtifact.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export type PublishAerPackageOptions = {
|
|
2
|
+
/** The packaging Dev Hub's alias. Must already be authenticated. */
|
|
3
|
+
packagingDevhub: string;
|
|
4
|
+
/** URL template to upload to. Overrides `plugins.simply.aer.repositoryUrl`. */
|
|
5
|
+
repositoryUrl?: string;
|
|
6
|
+
/** One `Name: value` header sent with every repository request. Never logged. */
|
|
7
|
+
repositoryHeader?: string;
|
|
8
|
+
subscriberPackageVersionId?: string;
|
|
9
|
+
/** Directories of `.pkg` files for dependencies this command can't download, e.g. managed packages. */
|
|
10
|
+
dependencyPackageDirs?: string[];
|
|
11
|
+
/** The aer binary. Otherwise `AER_BIN`, the aer sf plugin's download, then `PATH`. */
|
|
12
|
+
aerPath?: string;
|
|
13
|
+
/** Build the package and copy it to the working directory, but don't upload it. */
|
|
14
|
+
dryRun?: boolean;
|
|
15
|
+
/** Dotenv file to append the published URL, file name and version to. */
|
|
16
|
+
out?: string;
|
|
17
|
+
/** Dotenv file to read `SUBSCRIBER_PACKAGE_VERSION_ID` from. */
|
|
18
|
+
envFile?: string;
|
|
19
|
+
debug?: boolean;
|
|
20
|
+
disabled?: boolean;
|
|
21
|
+
};
|
|
22
|
+
export type PublishAerPackageResult = {
|
|
23
|
+
skipped: boolean;
|
|
24
|
+
published: boolean;
|
|
25
|
+
/** True when this exact version was already in the repository, so nothing needed uploading. */
|
|
26
|
+
alreadyPublished?: boolean;
|
|
27
|
+
packageName?: string;
|
|
28
|
+
namespace?: string;
|
|
29
|
+
version?: string;
|
|
30
|
+
url?: string;
|
|
31
|
+
/** The package's file name, or its local path on `--dry-run`. */
|
|
32
|
+
file?: string;
|
|
33
|
+
aerVersion?: string;
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Build the project's aer package (`.pkg`) for one package version and upload it to an artifact
|
|
37
|
+
* repository, under a path derived from the Salesforce version number.
|
|
38
|
+
*
|
|
39
|
+
* Runs after `create-package-version`, whose `04t` it takes as input, so that every Unlocked
|
|
40
|
+
* Package version has a matching aer package a consumer can test against locally. See
|
|
41
|
+
* `docs/design/0059-aer-package-publish.md` in `simply-plugins` for the reasoning, particularly why
|
|
42
|
+
* the file name carries the namespace and the version lives only in the path.
|
|
43
|
+
*
|
|
44
|
+
* Skips (without failing) when the package didn't change, when the job is disabled, when no `04t`
|
|
45
|
+
* can be found, when the package has no Apex, or when this exact version is already uploaded.
|
|
46
|
+
* Everything past those guards throws on failure: a missing aer package would silently break every
|
|
47
|
+
* consumer pinned to that version.
|
|
48
|
+
*/
|
|
49
|
+
export declare function publishAerPackage(options: PublishAerPackageOptions): Promise<PublishAerPackageResult>;
|