Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
74 commits
Select commit Hold shift + click to select a range
7d036a5
Fix a bug on non-multipage graphics
Dec 20, 2019
331125d
Fixing numerical casting, again
Dec 23, 2019
7accd91
Fix overly greedy number detection
Jan 2, 2020
31d5b3b
Create absolute file path for local require code
Jan 8, 2020
08a10bc
Handle low-significance decimal numbers
Jan 10, 2020
1b656e8
Handle even lower significance decimals
Jan 10, 2020
d6a7527
Remove spaces and special characters from slugs (#37)
ruthwtalbot Mar 23, 2020
b7eb5cf
Add ability to specify fallbackPage in manifest
Mar 26, 2020
67704c5
Fix live reload websocket bug
Apr 1, 2020
454eea5
Fixes #38
Apr 13, 2020
de45c58
templateFilters.js: fix ap_month reference
alykat Apr 20, 2020
feeb732
add territories to templateFilters.js
alykat Apr 30, 2020
d87bc86
parentPage.html: remove "can't see this graphic" link
alykat Jun 12, 2020
cbb05da
Fix typos
Jul 18, 2020
305c044
Update Chokidar
Sep 9, 2020
6400191
Add a sync command
Nov 11, 2020
78ce33f
Don't publish synced files
Nov 11, 2020
692c8ef
Fix globbing error during publish
Nov 12, 2020
cf3cbbf
Document sync process and caveats
Nov 12, 2020
4748198
Allow scripts to require text files
Nov 18, 2020
726c651
Update README
Nov 18, 2020
cc60486
Create missing folders during sync
Dec 8, 2020
a8a7bd7
Well that's dumb
Dec 8, 2020
a789436
Batch file operations during sync
Dec 8, 2020
4a22fef
Actually skip synced files
Dec 9, 2020
dba99bf
Kill Chrome instead of relying on Puppeteer
Dec 29, 2020
48aec2b
Closes #41
Dec 29, 2020
c72f3cf
Add forced push/pull to sync
Jan 4, 2021
58c39d3
Force traliing slashes for graphics routes
Jan 13, 2021
a674cb9
No longer silently catching readJSON errors
Jan 13, 2021
34f76c2
Don't redirect to auth page in airplane mode
Jan 13, 2021
8b225e7
Localize trailing path fix
Jan 14, 2021
9eadde7
update chrome launcher
zachlevitt Feb 2, 2021
fb95b92
Ignore _ prefixed columns
Feb 5, 2021
eb5ea9c
.
Feb 5, 2021
9e4d7ca
add readme
DanielJWood Feb 23, 2021
dba406d
fix rst
DanielJWood Feb 23, 2021
d7001e3
Fix deployment CLI
Feb 23, 2021
a0a767d
Some technical corrections
Feb 23, 2021
104e6f8
Ignore unnamed columns
Feb 23, 2021
81d4b50
Ensure that data is immutable for templating
Mar 3, 2021
12d4bf5
Add target UI display for preview/deployment
Mar 3, 2021
a922296
Fix #44
Mar 22, 2021
a27517e
Fix #43
Mar 22, 2021
7d64a97
Fix #34
Mar 22, 2021
cdeb008
Crash out on bad folder path config
Mar 22, 2021
337e0b6
Add manual column type annotations
Mar 23, 2021
639031b
Update README, add better boolean casting
Mar 23, 2021
6062ba6
Update jump links
Mar 24, 2021
b5515f5
Update dependencies, make fewer test connections to Google
Apr 20, 2021
f380652
Add some logging and pre-auth against Google
Apr 21, 2021
c8c1630
Remove auth preconnection
May 6, 2021
78cc96a
Remove Google resolution code
May 6, 2021
9be7dda
Environment check cleanup
May 6, 2021
ddbca29
Add auth requirement for listing page
Jul 2, 2021
de093cb
Add commonjs module type
Jul 2, 2021
e6c600e
Add documentation
Aug 20, 2021
ca21065
sheetOps.js: widen range of imported data
alykat Jan 3, 2022
c77c462
Replace bundler: browserify -> rollup (#53)
alykat Jul 6, 2022
cb0aae4
update to new AWS SDK (#54)
alykat Jul 14, 2022
e8b8831
suppress circular dependency warnings #55 (#56)
alykat Jul 15, 2022
d65bea4
sync: fix empty directory crashes (#57)
alykat Aug 1, 2022
0aa5ecd
update aws ListObjectsV2 marker for syncing large folders #58 (#59)
alykat Aug 2, 2022
bbb303e
CLI: note optional target flag for syncing
alykat May 31, 2023
95b2e3b
readme: note optional sync deploy target flag
alykat May 31, 2023
d6edd32
update preview breakpoints
alykat Sep 27, 2023
12ed026
fixes smartypants bug; closes #64
brentajones Nov 14, 2023
d3dafd0
Merge pull request #65 from nprapps/smartypants-fix
brentajones Nov 28, 2023
993fe44
Update readme.rst
alykat Apr 3, 2024
9874929
add a note about publishing google sheets to s3
hilaryfung Jun 14, 2024
4c7a6d6
Merge pull request #68 from nprapps/hollerith-documentation
hilaryfung Jun 18, 2024
cc324a4
update example config to ga4
brentajones Aug 11, 2024
3a9669c
update puppeteer-core
hilaryfung Aug 28, 2024
90e9901
update classify and smarty to account for numbers passed in (#73)
alykat Jan 22, 2025
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
17 changes: 11 additions & 6 deletions cli/copyedit.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,25 @@ var path = require("path");
var sheets = require("../lib/sheetOps");
var processHTML = require("../lib/processHTML");
var readJSON = require("../lib/readJSON");
var { completeSlug } = require("./util");

module.exports = async function(config, argv, slugs) {
var template = path.join(config.templatePath, "copyedit.html");
config.user = await sheets.testConnection();
for (var slug of slugs) {
var manifest = await readJSON(path.join(config.graphicsPath, slug, "manifest.json"));
var { sheet } = manifest;
var COPY = await sheets.getSheet(sheet);
var email = await processHTML(template, { sheet, slug, COPY, config })
console.log(`
try {
slug = await completeSlug(config.root, slug);
var manifest = await readJSON(path.join(config.graphicsPath, slug, "manifest.json"));
var { sheet } = manifest;
var COPY = await sheets.getSheet(sheet);
var email = await processHTML(template, { sheet, slug, COPY, config })
console.log(`
GRAPHIC FOR COPY EDIT: ${slug}
=============
${email}

`);
} catch (err) {
console.log(err);
}
}
}
8 changes: 7 additions & 1 deletion cli/deploy.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
var deploy = require("../lib/deployGraphic");
var { completeSlug } = require("./util");

module.exports = async function(config, argv, slugs) {
for (var slug of slugs) {
await deploy(config, slug);
try {
slug = await completeSlug(config.root, slug);
await deploy(config, slug);
} catch (err) {
console.error(err);
}
}
};
11 changes: 9 additions & 2 deletions cli/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@ var configuration = require("../lib/configuration");
var help = function() {
console.log(`
Commands available from the command line:
- ${chalk.blue("create TYPE SLUG [SHEET]")} - create a graphic named SLUG from the template TYPE. SHEET is optional.
- ${chalk.blue("create TYPE SLUG [SHEET]")} - create a graphic named SLUG from the template TYPE.
- ${chalk.blue("copy ORIGINAL SLUG")} - copy ORIGINAL into a new graphic named SLUG, with a new backing sheet
- ${chalk.blue("deploy SLUGS")} - deploy the chosen graphics to S3
- ${chalk.blue("sync SLUGS [--push|--pull] [--target live|--target stage]")} - sync assets for graphics with S3
- ${chalk.blue("copyedit SLUGS")} - display the copy edit e-mail for the chosen SLUGS
- ${chalk.blue("help")} - you're looking at it

[BRACES] signal optional arguments.
`);
};

Expand All @@ -20,7 +23,9 @@ var commands = {
create: require("./create"),
deploy: require("./deploy"),
copy: require("./copy"),
copyedit: require("./copyedit")
copyedit: require("./copyedit"),
sync: require("./sync"),
"try": require("./try")
};

var run = async function() {
Expand All @@ -29,6 +34,8 @@ var run = async function() {

var config = await configuration.load("config.json");

if (!(script in commands)) script = "help";

var command = commands[script];
command(config, argv, positional);
};
Expand Down
13 changes: 13 additions & 0 deletions cli/sync.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
var sync = require("../lib/syncAssets");
var { completeSlug } = require("./util");

module.exports = async function(config, argv, slugs) {
for (var slug of slugs) {
try {
slug = await completeSlug(config.root, slug);
await sync(config, slug);
} catch (err) {
console.log(err);
}
}
};
12 changes: 12 additions & 0 deletions cli/try.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
var { completeSlug } = require("./util");

module.exports = async function(config, argv, slugs) {
for (var slug of slugs) {
try {
slug = await completeSlug(config.root, slug)
} catch (err) {
// failed to complete
console.log(err);
}
}
};
21 changes: 21 additions & 0 deletions cli/util.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
var fs = require("fs").promises;

var completeSlug = async function(folder, fragment) {
var folders = await fs.readdir(folder);
var pattern = new RegExp(`^${fragment}`, "i");
var matched = folders.filter(f => f.match(pattern));
if (matched.length > 1) {
throw `Multiple possible matches for ${fragment}:
${matched.map(m => ` - ${m}`).join("\n")}`;
}
if (matched.length == 0) throw `Unable to find matching slug for ${fragment}`;
var [ completed ] = matched;
if (completed != fragment) {
console.log(`(Autocompleted "${fragment}" as ${completed})`);
}
return completed;
}

module.exports = {
completeSlug
};
5 changes: 3 additions & 2 deletions config.json.example
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"prefix": "dailygraphics/graphics"
}
},
// this is the NPR Visuals Google Analytics property. update to use your own.
"analyticsID": "UA-5828686-75"
// this is the NPR Visuals Google Analytics property. update to use your own.
"ga4ID": "G-LLLW9F9XPC",
"analyticsID": "UA-5828686-75" //legacy UA ID
}
10 changes: 5 additions & 5 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,18 @@ var init = async function() {

var config = await configuration.load("./config.json");

var environmentWhitelist = [];
var envRequirements = [];
if (!process.env.GOOGLE_APPLICATION_CREDENTIALS) {
environmentalWhitelist = ["GOOGLE_OAUTH_CLIENT_ID", "GOOGLE_OAUTH_CONSUMER_SECRET"];
envRequirements = ["GOOGLE_OAUTH_CLIENT_ID", "GOOGLE_OAUTH_CONSUMER_SECRET"];
}

if (!config.deployTo || config.deployTo == "s3") {
environmentWhitelist.push("AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY");
envRequirements.push("AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY");
}

var allSet = true;
environmentWhitelist.forEach(function(env) {
if (!process.env[env]) {
envRequirements.forEach(function(env) {
if (!(env in process.env)) {
console.log(`Missing environment variable: ${env}`);
allSet = false;
}
Expand Down
22 changes: 22 additions & 0 deletions lib/configuration.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
/*

Loads the config.json file and then augments it with extra information:

- sets the absolute paths for the graphics and template folders
- validates those paths
- loads a deployment config (S3 vs local)
- adds command line arguments to the config object

*/

var fs = require("fs").promises;
var readJSON = require("./readJSON");
var path = require("path");
var minimist = require("minimist");
Expand All @@ -10,6 +22,16 @@ var load = async function(configPath) {
config.root = path.join(process.cwd(), config.graphicsPath);
config.templateRoot = path.join(process.cwd(), config.templatePath);

// check for existence of these folders
try {
await fs.stat(config.root);
await fs.stat(config.templateRoot);
} catch (err) {
console.log("Unable to load one of the source directories - check your paths in config.json!");
console.log(`Folder does not exist: ${err.path}`);
process.exit();
}

var deployTo = argv.deployTo || config.deployTo || "s3";
var target = argv.target || "live";

Expand Down
7 changes: 7 additions & 0 deletions lib/copyDirectory.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
/*

Recursive copy for folders
Usually used to create a graphic by merging the graphic template with the _base folder

*/

var fs = require("fs").promises;
var path = require("path");

Expand Down
12 changes: 12 additions & 0 deletions lib/createGraphic.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
/*

Creates a graphic from the template:

- generates the final slug and creates the folder
- copies the base files, then adds the template-specific files
- loads the manifest and creates a Google Sheet if requested
- takes a snapshot of the installed node_modules in the graphics folder for archiving purposes
- saves out the updated manifest

*/

var fs = require("fs").promises;
var path = require("path");

Expand Down
23 changes: 21 additions & 2 deletions lib/deployGraphic.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
/*

Publishes a graphic, either to S3 or a local folder:

- Instantiates an "exporter" function from the factory for writing contents to the destination
- Creates a list of files to deploy from the manifest glob pattern
- Processes and deploys each file, based on its extension
- Publishes the preview page

*/

var fs = require("fs").promises;
var path = require("path");

Expand All @@ -9,11 +20,12 @@ var readJSON = require("./readJSON");
var s3 = require("./s3");
var sheetOps = require("./sheetOps");

// factory functions for functions that do the actual deployment of file contents
var exporters = {
s3: function(config) {
return function(file, contents) {
var { prefix, bucket } = config.deployment;
var s3Path = path.join(prefix, file);
var s3Path = path.posix.join(prefix, file);
return s3.upload(bucket, s3Path, contents);
};
},
Expand Down Expand Up @@ -49,6 +61,8 @@ module.exports = async function(config, slug) {
"*.geojson",
"*.csv"
];
// exclude synced files
patterns.push("!**/synced/**/*");

var exporter = (exporters[config.argv.deployTo || config.deployTo] || exporters.s3)(config);

Expand All @@ -64,12 +78,14 @@ module.exports = async function(config, slug) {
production: true,
root: config.root
});
// add source map reference
src += `\n//# sourceMappingURL=./graphic.js.map`;
// upload the main file
var sourcePath = path.join(slug, relative);
var sourceUpload = exporter(sourcePath, src);
// upload the source map
var mapPath = sourcePath + ".map";
var mapUpload = exporter(mapPath, map);
var mapUpload = exporter(mapPath, JSON.stringify(map));
return Promise.all([sourceUpload, mapUpload]);
},
".html": async function({ full, relative }) {
Expand Down Expand Up @@ -114,4 +130,7 @@ module.exports = async function(config, slug) {
var previewHTML = await processHTML(parent, previewData);
await exporter(previewPath, previewHTML);
console.log("Deploy complete!");
if (COPY && COPY.metadata && COPY.metadata.seamus_id) {
console.log(`This graphic is embedded at Seamus ID ${COPY.metadata.seamus_id}`);
}
};
9 changes: 8 additions & 1 deletion lib/duplicateGraphic.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
/*

Makes a copy of a graphic, including a copy of the Google Sheet in its manifest

*/

var fs = require("fs").promises;
var path = require("path");

var copyDir = require("./copyDirectory");
var readJSON = require("./readJSON");
var { copySheet } = require("./sheetOps");
var { copySheet, clearSheet } = require("./sheetOps");

module.exports = async function(config, original, slug) {
var now = new Date();
Expand All @@ -30,6 +36,7 @@ module.exports = async function(config, original, slug) {
try {
var duplicateSheet = await copySheet(sheet, fullSlug, config.driveFolder);
manifest.sheet = duplicateSheet.id;
await clearSheet(duplicateSheet.id, "metadata!B2:B");
} catch (err) {
console.log(`Unable to make a copy of ${sheet}: "${err.message}"`);
}
Expand Down
40 changes: 30 additions & 10 deletions lib/expandMatch.js
Original file line number Diff line number Diff line change
@@ -1,38 +1,58 @@
/*

Used to find files based on a glob pattern.
Primarily used for the manifest deployment list.

*/

var fs = require("fs").promises;
var minimatch = require("minimatch");
var path = require("path");

// check if a file matches any or all (strict mode) patterns in a list
var filterFile = function(file, patterns, strict) {
return patterns[strict ? "every" : "some"](function(p) {
return minimatch(file, p, { matchBase: true });
});
};

// implements glob matching for a directory based on a list of patterns
// returns all files that match
var expand = async function(from, dir, patterns) {
var fullDir = path.join(from, dir);
var files = await fs.readdir(fullDir);
try {
var files = await fs.readdir(fullDir);
} catch (err) {
console.log(`Unable to read directory ${fullDir} - does it exist?`);
return [];
}
var matching = [];
var affirmative = patterns.filter(p => p[0] != "!");
var negative = patterns.filter(p => p[0] == "!");
for (var i = 0; i < files.length; i++) {
var f = files[i];
var full = path.join(from, dir, f);
var relative = path.relative(from, full);
var stat = await fs.stat(full);
if (stat.isDirectory()) {
var children = await expand(from, relative, patterns);
matching.push(...children);
} else {
var matched = filterFile(relative, affirmative);
var notExcluded = filterFile(relative, negative, true);
if (matched && notExcluded) {
matching.push(full);
try {
var stat = await fs.stat(full);
if (stat.isDirectory()) {
var children = await expand(from, relative, patterns);
matching.push(...children);
} else {
var matched = filterFile(relative, affirmative);
var notExcluded = filterFile(relative, negative, true);
if (matched && notExcluded) {
matching.push(full);
}
}
} catch (err) {
console.log(`Unable to expand matches for path ${f}`);
}
}
return matching;
};

// expand a directory, then return objects with the full and relative paths for each
var expandMatch = async function(from, dir, patterns) {
var paths = await expand(from, dir, patterns);
return paths.map(f => ({
Expand Down
7 changes: 7 additions & 0 deletions lib/googleAuth.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
/*

Loads our token file and attempts to use it to authenticate against Google's OAuth endpoint
This could (should?) be replaced with @nprapps/google-login

*/

var fs = require("fs").promises;
var os = require("os");
var path = require("path");
Expand Down
Loading