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
9 changes: 9 additions & 0 deletions .github/workflows/build-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ name: Build, Test, and Publish

on:
workflow_dispatch:
repository_dispatch:
types:
- cda-prod-release

permissions:
pages: write
Expand All @@ -15,6 +18,12 @@ jobs:
- name: Checkout code
uses: actions/checkout@v4

- name: Show trigger context
run: |
echo "Event: ${{ github.event_name }}"
echo "Release tag: ${{ github.event.client_payload.release_tag || 'manual' }}"
echo "Source repository: ${{ github.event.client_payload.source_repository || github.repository }}"

- name: Set up Node.js
uses: actions/setup-node@v4
with:
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,9 @@ Throughout CDA, "time series" is arbitrarily referred to in both a one-word ("ti
## Developers
### Versioning
In order to accommodate changes both to the generator and to CDA itself, cwmsjs is versioned in the following format:
`[generator SemVer]-[generation date]`
`[generator SemVer]-[CDA schema version]`

CDA is expected to at some point expose a CalVer for the latest update. When this is available, the generation date will be replaced with the current CDA CalVer.
The generator now uses the live OpenAPI `info.version` published by CDA. If that field is unavailable, it falls back to the current date.

### Publishing
Contributors with authorization can publish a new version of cwmsjs by manually running the "Build, Test, and Publish" GitHub Action.
Expand All @@ -63,6 +63,7 @@ The workflow will build an updated cwmsjs library using the current generator an

- Clone this repository
- Install dependencies with: `npm install`
- Optionally set `CWMS_SCHEMA_URL` if you need to build from a non-default CDA schema endpoint
- Run the generator with:
`npm run build`

Expand Down
2 changes: 1 addition & 1 deletion openapitools.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
"$schema": "./node_modules/@openapitools/openapi-generator-cli/config.schema.json",
"spaces": 2,
"generator-cli": {
"version": "7.4.0"
"version": "5.4.0"
}
}
13 changes: 7 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cwmsjs-generator",
"version": "2.3.2",
"version": "2.4.0",
"description": "OpenAPI generator for building the cwmsjs JavaScript/TypeScript library for CWMS Data API",
"author": "USACE,HEC,CWMS,OpenApi Contributors",
"repository": {
Expand Down Expand Up @@ -30,15 +30,16 @@
"typings": "./dist/index.d.ts",
"scripts": {
"build": "npm run buildApi && npm run buildDocs",
"buildApi": "npm run buildSpec && npm run openapi && npm run modPackage && cd cwmsjs && npm run build",
"buildApi": "npm run buildSpec && npm run openapi && npm run modPackage && npm run postGenerate && cd cwmsjs && npm install && npm run build",
"buildDocs": "npm run docs && npm run examples",
"buildSpec": "npm run getSpec && npm run modSpec",
"clean": "shx mkdir -p ./cwmsjs && shx rm -r ./cwmsjs",
"docs": "cd cwmsjs && npx typedoc src/index.ts",
"docs": "node ./scripts/buildTypedoc.js",
"examples": "node ./scripts/tests2exampledocs.js ",
"getSpec": "wget https://cwms-data.usace.army.mil/cwms-data/swagger-docs -O cwms-swagger-raw.json",
"modPackage": "./scripts/package-updates/modPackage.sh",
"modSpec": "./scripts/spec-updates/modSpec.sh",
"getSpec": "node ./scripts/getSpec.js",
"modPackage": "node ./scripts/package-updates/modPackage.js",
"modSpec": "node ./scripts/spec-updates/modSpec.js",
"postGenerate": "node ./scripts/postGenerate.js",
"link": "cd cwmsjs && npm link && cd ../tests && npm link cwmsjs",
"openapi": "npx @openapitools/openapi-generator-cli generate -g typescript-fetch -o ./cwmsjs -i cwms-swagger-mod.json -c openapi.config.json"
},
Expand Down
107 changes: 107 additions & 0 deletions scripts/buildTypedoc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
const { spawnSync } = require("node:child_process");
const fs = require("node:fs");
const path = require("node:path");

const rootDir = path.resolve(__dirname, "..");
const cwmsjsDir = path.join(rootDir, "cwmsjs");
const docsDir = path.join(cwmsjsDir, "docs");
const tempDocsDir = path.join(cwmsjsDir, "docs-typedoc");
const packageJson = JSON.parse(
fs.readFileSync(path.join(cwmsjsDir, "package.json"), "utf8"),
);

removePath(tempDocsDir, { strict: true });

const result = spawnSync(
`npx typedoc src/index.ts --name "cwmsjs v${packageJson.version}" --out docs-typedoc`,
{
cwd: cwmsjsDir,
stdio: "inherit",
shell: true,
},
);

if (result.error) {
console.error(result.error.message);
process.exit(1);
}

if (result.status !== 0) {
process.exit(result.status ?? 1);
}

fs.mkdirSync(docsDir, { recursive: true });

for (const entry of fs.readdirSync(tempDocsDir, { withFileTypes: true })) {
const sourcePath = path.join(tempDocsDir, entry.name);
const destinationPath = path.join(docsDir, entry.name);

removePath(destinationPath);
copyPath(sourcePath, destinationPath);
}

removePath(tempDocsDir, { strict: true });

function removePath(targetPath, { strict = false } = {}) {
if (!fs.existsSync(targetPath)) {
return;
}

const targetStats = fs.lstatSync(targetPath);

try {
fs.rmSync(targetPath, { recursive: true, force: true });
} catch (error) {
if (process.platform !== "win32") {
throw error;
}

const cleanupCommand = targetStats.isDirectory()
? `if exist "${targetPath}" rmdir /s /q "${targetPath}"`
: `if exist "${targetPath}" del /f /q "${targetPath}"`;
const cleanup = spawnSync(
"cmd.exe",
["/d", "/s", "/c", cleanupCommand],
{
stdio: "inherit",
},
);

if (cleanup.status !== 0 && fs.existsSync(targetPath)) {
throw error;
}
}

if (fs.existsSync(targetPath)) {
if (strict) {
throw new Error(`Unable to remove ${targetPath}`);
}
console.warn(`Skipping cleanup for locked path: ${targetPath}`);
}
}

function copyPath(sourcePath, destinationPath) {
const sourceStats = fs.statSync(sourcePath);

if (sourceStats.isDirectory()) {
fs.mkdirSync(destinationPath, { recursive: true });

for (const entry of fs.readdirSync(sourcePath, { withFileTypes: true })) {
copyPath(
path.join(sourcePath, entry.name),
path.join(destinationPath, entry.name),
);
}
return;
}

try {
fs.copyFileSync(sourcePath, destinationPath);
} catch (error) {
if (error && error.code === "EPERM") {
console.warn(`Skipping locked file: ${destinationPath}`);
return;
}
throw error;
}
}
2 changes: 1 addition & 1 deletion scripts/docker/buildAll.cmd
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
@REM runs all scripts start to finish, using docker where windows might be an issue
echo Fetching and Manipulating spec...
.\scripts\docker\buildSpec.cmd && npm run openapi && .\scripts\docker\runPkg.cmd && cd cwmsjs && npm install && npm run build && cd ..
npm.cmd run buildSpec && npm.cmd run openapi && .\scripts\docker\runPkg.cmd && cd cwmsjs && npm.cmd install && npm.cmd run build && cd ..
echo Done.
4 changes: 2 additions & 2 deletions scripts/docker/buildSpec.cmd
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
@REM Run scripts from windows
docker run --rm -v %cd%:/scripts -w /scripts node:lts bash -c "npm install -g node-jq && npm run buildSpec"
@REM Build the live spec from Windows without docker
npm.cmd run buildSpec
7 changes: 4 additions & 3 deletions scripts/exampletemplate.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<head>
<meta charSet="utf-8" />
<meta http-equiv="x-ua-compatible" content="IE=edge" />
<title>${docName} Example</title>
<title>${docName} Example | cwmsjs v${packageVersion}</title>
<meta name="description" content="Documentation for cwmsjs" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="../assets/style.css" />
Expand Down Expand Up @@ -47,7 +47,7 @@
<ul class="results">
<li class="state loading">Preparing search index...</li>
<li class="state failure">The search index is not available</li>
</ul><a href="/cwms-data-api-client-javascript/" class="title">HOME - cwmsjs - v1.15.0</a>
</ul><a href="/cwms-data-api-client-javascript/" class="title">HOME - cwmsjs - v${packageVersion}</a>
</div>
</div>
</header>
Expand All @@ -59,6 +59,7 @@
<li><a href="/cwms-data-api-client-javascript/examples/${docName}.html">${docName}</a></li>
</ul>
<h1>Example: ${docName}</h1>
<p>cwmsjs v${packageVersion}</p>
</div>
<section class="tsd-panel tsd-comment">
<div class="tsd-comment tsd-typography">
Expand Down Expand Up @@ -92,4 +93,4 @@ <h2>Groundwork-Water + React</h2>
<script src="https://cdn.jsdelivr.net/npm/prismjs@1.26.0/prism.js"></script>
</body>

</html>
</html>
37 changes: 37 additions & 0 deletions scripts/getSpec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
const fs = require("node:fs/promises");

const DEFAULT_SCHEMA_URL =
"https://cwms-data.usace.army.mil/cwms-data/swagger-docs";
const OUTPUT_PATH = "cwms-swagger-raw.json";

async function main() {
const schemaUrl = process.env.CWMS_SCHEMA_URL || DEFAULT_SCHEMA_URL;
const response = await fetch(schemaUrl, {
headers: {
Accept: "application/json",
},
});

if (!response.ok) {
throw new Error(`Failed to download schema: ${response.status} ${response.statusText}`);
}

const body = await response.text();
let parsed;

try {
parsed = JSON.parse(body);
} catch (error) {
throw new Error(`Schema response was not valid JSON: ${error.message}`);
}

await fs.writeFile(OUTPUT_PATH, `${JSON.stringify(parsed)}\n`, "utf8");

const schemaVersion = parsed?.info?.version || "unknown";
console.log(`Saved ${schemaUrl} to ${OUTPUT_PATH} (schema version ${schemaVersion})`);
}

main().catch((error) => {
console.error(error.message);
process.exit(1);
});
45 changes: 45 additions & 0 deletions scripts/package-updates/modPackage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
const fs = require("node:fs");
const path = require("node:path");

const rootDir = path.resolve(__dirname, "..", "..");

function readJson(relativePath) {
return JSON.parse(fs.readFileSync(path.join(rootDir, relativePath), "utf8"));
}

function writeJson(relativePath, value) {
fs.writeFileSync(
path.join(rootDir, relativePath),
`${JSON.stringify(value, null, 2)}\n`,
"utf8",
);
}

function getVersionSuffix() {
const rawSpec = readJson("cwms-swagger-raw.json");
return rawSpec?.info?.version || new Date().toISOString().slice(0, 10).replace(/-/g, ".");
}

function main() {
const rootPackage = readJson("package.json");
const generatedPackage = readJson("cwmsjs/package.json");
const updates = readJson("scripts/package-updates/updates.json");
const versionSuffix = getVersionSuffix();

const nextPackage = {
...generatedPackage,
...updates,
author: rootPackage.author,
generatorVersion: rootPackage.version,
keywords: rootPackage.keywords,
repository: rootPackage.repository,
version: `${rootPackage.version}-${versionSuffix}`,
};

delete nextPackage.publishConfig;

writeJson("cwmsjs/package.json", nextPackage);
fs.copyFileSync(path.join(rootDir, "README.md"), path.join(rootDir, "cwmsjs", "README.md"));
}

main();
60 changes: 60 additions & 0 deletions scripts/postGenerate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
const fs = require("node:fs");
const path = require("node:path");

const rootDir = __dirname ? path.resolve(__dirname, "..") : process.cwd();

function replaceInFile(relativePath, replacements) {
const fullPath = path.join(rootDir, relativePath);
if (!fs.existsSync(fullPath)) {
return;
}

let content = fs.readFileSync(fullPath, "utf8");
for (const [from, to] of replacements) {
if (!content.includes(from)) {
continue;
}
content = content.replace(from, to);
}
fs.writeFileSync(fullPath, content, "utf8");
}

function patchTsConfig(relativePath) {
const fullPath = path.join(rootDir, relativePath);
if (!fs.existsSync(fullPath)) {
return;
}

const tsconfig = JSON.parse(fs.readFileSync(fullPath, "utf8"));
tsconfig.compilerOptions ??= {};
tsconfig.compilerOptions.lib = ["es2018", "dom"];
fs.writeFileSync(fullPath, `${JSON.stringify(tsconfig, null, 2)}\n`, "utf8");
}

replaceInFile("cwmsjs/src/models/AbstractRatingMetadata.ts", [
[
" return TransitionalRatingToJSON(value);",
" return TransitionalRatingToJSON(value as TransitionalRating);",
],
[
" return VirtualRatingToJSON(value);",
" return VirtualRatingToJSON(value as VirtualRating);",
],
]);

replaceInFile("cwmsjs/src/models/LocationLevel.ts", [
[
" return { ...ConstantLocationLevelFromJSONTyped(json, true), ...SeasonalLocationLevelFromJSONTyped(json, true), ...TimeSeriesLocationLevelFromJSONTyped(json, true), ...VirtualLocationLevelFromJSONTyped(json, true) };",
" return { ...ConstantLocationLevelFromJSONTyped(json, true), ...SeasonalLocationLevelFromJSONTyped(json, true), ...TimeSeriesLocationLevelFromJSONTyped(json, true), ...VirtualLocationLevelFromJSONTyped(json, true) } as LocationLevel;",
],
[
" return { ...ConstantLocationLevelToJSON(value), ...SeasonalLocationLevelToJSON(value), ...TimeSeriesLocationLevelToJSON(value), ...VirtualLocationLevelToJSON(value) };",
" return { ...ConstantLocationLevelToJSON(value as any), ...SeasonalLocationLevelToJSON(value as any), ...TimeSeriesLocationLevelToJSON(value as any), ...VirtualLocationLevelToJSON(value as any) };",
],
]);

replaceInFile("cwmsjs/src/runtime.ts", [
["export type FetchAPI = GlobalFetch['fetch'];", "export type FetchAPI = typeof fetch;"],
]);

patchTsConfig("cwmsjs/tsconfig.json");
Loading