Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,7 @@ This is the current list:
|   |:white_check_mark:| BN012 | unreferrenced Target Endpoints | Check that each TargetEndpoint can be reached. |
|   |:white_check_mark:| BN013 | Unreferenced resources. | Warn for resources that not referenced in any policy. Unreferenced resources are dead code. |
|   |:white_check_mark:| BN014 | Duplicate policies. | Warn if there are identically configured, if differently named, policies. |
|   |:white_check_mark:| BN015 | Manifest file exists. | The bundle root must contain the manifest .xml file (eg. apiproxy.xml or sharedflowbundle.xml); without it, the bundle cannot be imported into Apigee. |
| Proxy Definition |   |   |   |   |
|   |:white_check_mark:| PD001 | RouteRules to Targets | RouteRules should map to defined Targets. |
|   |:white_check_mark:| PD002 | Unreachable Route Rules - defaults | Only one RouteRule should be present without a condition. |
Expand Down
2 changes: 1 addition & 1 deletion lib/package/Bundle.js
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ function processFileSystem(config, bundle, cb) {
);
}

bundle.filePath = bundle.root + "/" + files[0];
bundle.filePath = files.length ? bundle.root + "/" + files[0] : undefined;

bundle.proxyRoot = bundle.root;
const bundleTypeName = config.source.bundleType;
Expand Down
52 changes: 52 additions & 0 deletions lib/package/plugins/BN015-checkManifestExists.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
Copyright © 2026 Google LLC

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

https://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

const ruleId = require("../lintUtil.js").getRuleId(),
debug = require("debug")("apigeelint:" + ruleId);

const plugin = {
ruleId,
name: "Manifest file exists",
message:
"The bundle root must contain exactly one manifest .xml file (eg. apiproxy.xml or sharedflowbundle.xml). Without it the bundle cannot be imported into Apigee.",
fatal: false,
severity: 2, //1 == warn 2 == error
nodeType: "Bundle",
enabled: true,
};

const onBundle = function (bundle, cb) {
let flagged = false;
// Bundle.js sets bundle.filePath only when exactly one .xml file is
// found at the bundle root. When the manifest is missing entirely,
// filePath is left undefined.
if (!bundle.filePath) {
debug(`No manifest file found in bundle root: ${bundle.root}`);
bundle.addMessage({
plugin,
message: `Missing manifest file: no .xml file found at bundle root "${bundle.root}".`,
});
flagged = true;
}
if (typeof cb == "function") {
cb(null, flagged);
}
};

module.exports = {
plugin,
onBundle,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<ProxyEndpoint name="default">
<HTTPProxyConnection>
<BasePath>/</BasePath>
<Properties/>
<VirtualHost>secure</VirtualHost>
</HTTPProxyConnection>

<PreFlow name="PreFlow">
<Request />
<Response />
</PreFlow>
<PostFlow name="PostFlow">
<Request />
<Response />
</PostFlow>
<RouteRule name="no-route">
<!-- this is ok -->
</RouteRule>

</ProxyEndpoint>
79 changes: 79 additions & 0 deletions test/specs/BN015-checkManifestExists.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
Copyright © 2026 Google LLC

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

https://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

const assert = require("node:assert"),
path = require("node:path"),
testID = "BN015",
debug = require("debug")("apigeelint:" + testID),
bl = require("../../lib/package/bundleLinter.js");

describe("BN015 - Check manifest file exists", function () {
it("should flag a bundle that is missing the manifest .xml file", () => {
const configuration = {
debug: true,
source: {
type: "filesystem",
path: path.resolve(
__dirname,
"../fixtures/resources/missingManifest/apiproxy",
),
bundleType: "apiproxy",
},
excluded: {},
setExitCode: false,
output: () => {}, // suppress output
};

bl.lint(configuration, (bundle) => {
const items = bundle.getReport();
assert.ok(items);
assert.ok(items.length);
const bn015Messages = items[0].messages.filter(
(m) => m.ruleId == testID,
);
assert.equal(bn015Messages.length, 1);
assert.equal(bn015Messages[0].severity, 2);
debug(`bn015Messages: ${JSON.stringify(bn015Messages)}`);
});
});

it("should not flag a bundle that has a manifest .xml file", () => {
const configuration = {
debug: true,
source: {
type: "filesystem",
path: path.resolve(
__dirname,
"../fixtures/resources/sampleProxy/24Solver/apiproxy",
),
bundleType: "apiproxy",
},
excluded: {},
setExitCode: false,
output: () => {}, // suppress output
};

bl.lint(configuration, (bundle) => {
const items = bundle.getReport();
assert.ok(items);
assert.ok(items.length);
const bn015Messages = items[0].messages.filter(
(m) => m.ruleId == testID,
);
assert.equal(bn015Messages.length, 0);
});
});
});
18 changes: 18 additions & 0 deletions test/specs/testBundleEx.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,24 @@ describe("BundleEx", function () {
assert.ok(missingFolderMsg, "Should warn about missing apiproxy folder");
});

it("Should leave filePath undefined when the manifest .xml file is missing", function () {
const missingManifestPath = path.resolve(
__dirname,
"../fixtures/resources/missingManifest/apiproxy",
);
const configuration = {
source: {
type: "filesystem",
path: missingManifestPath,
bundleType: "apiproxy",
},
};

const bundle = new Bundle(configuration);
assert.ok(bundle);
assert.equal(bundle.filePath, undefined);
});

it("Should handle messages without a plugin object", function () {
const configuration = {
source: {
Expand Down