citeclaw 2.0.8 → 2.0.9
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 +18 -12
- package/package.json +1 -1
- package/scripts/botcite.js +64 -34
package/README.md
CHANGED
|
@@ -37,18 +37,7 @@ npx citeclaw citoid bibtex "https://aclanthology.org/2023.emnlp-main.398/"
|
|
|
37
37
|
npx citeclaw cite mediawiki "https://arxiv.org/abs/1706.03762"
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
-
Fresh npm installs can run normal citation commands directly. `CiteClaw` will bootstrap Zotero and build a local translator runtime automatically from
|
|
41
|
-
|
|
42
|
-
Runtime state is persisted in a user-level cache directory by default:
|
|
43
|
-
|
|
44
|
-
- Linux: `~/.cache/citeclaw`
|
|
45
|
-
- macOS: `~/Library/Caches/citeclaw`
|
|
46
|
-
- Windows: `%LOCALAPPDATA%\\citeclaw`
|
|
47
|
-
|
|
48
|
-
Override it with `CITECLAW_HOME=/path/to/runtime`.
|
|
49
|
-
|
|
50
|
-
By default, the Zotero translation server checkout is also stored under this runtime root, at `runtime/zotero`.
|
|
51
|
-
Override it with `ZOTERO_DIR=/path/to/zotero`.
|
|
40
|
+
Fresh npm installs can run normal citation commands directly. `CiteClaw` will bootstrap Zotero and build a local translator runtime automatically from the bundled Zotero translator set.
|
|
52
41
|
|
|
53
42
|
## Local Service
|
|
54
43
|
|
|
@@ -176,6 +165,23 @@ Current package metadata and entrypoints live in:
|
|
|
176
165
|
- [package.json](/mnt/e/botcite/package.json)
|
|
177
166
|
- [scripts/citeclaw.js](/mnt/e/botcite/scripts/citeclaw.js)
|
|
178
167
|
|
|
168
|
+
## npm Publishing
|
|
169
|
+
|
|
170
|
+
This repo includes a GitHub Actions workflow for npm publishing:
|
|
171
|
+
|
|
172
|
+
- workflow file: `.github/workflows/npm-publish.yml`
|
|
173
|
+
- trigger: push a tag like `v2.0.8` that matches `package.json`'s `version`
|
|
174
|
+
- required secret: add `NPM_TOKEN` in GitHub repository settings with publish permission for the `citeclaw` package
|
|
175
|
+
|
|
176
|
+
Typical release flow:
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
npm version patch
|
|
180
|
+
git push origin master --follow-tags
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
The workflow installs dependencies with `npm ci`, checks that the tag matches `package.json`, validates the tarball with `npm pack --dry-run`, and then runs `npm publish --access public --provenance`.
|
|
184
|
+
|
|
179
185
|
## Notes
|
|
180
186
|
|
|
181
187
|
- The npm package name and primary CLI are `citeclaw`.
|
package/package.json
CHANGED
package/scripts/botcite.js
CHANGED
|
@@ -19,31 +19,13 @@ const yaml = require( 'js-yaml' );
|
|
|
19
19
|
const citoidApp = require( '../app.js' );
|
|
20
20
|
|
|
21
21
|
const rootDir = path.resolve( __dirname, '..' );
|
|
22
|
+
const zoteroDir = process.env.ZOTERO_DIR || path.join( rootDir, 'vendor', 'zotero' );
|
|
22
23
|
const cnTranslatorsDir = process.env.CN_TRANSLATORS_DIR || path.join( rootDir, 'vendor', 'translators_CN' );
|
|
23
24
|
const officialTranslatorsDir = process.env.OFFICIAL_TRANSLATORS_DIR ||
|
|
24
25
|
path.join( rootDir, 'vendor', 'translators-official' );
|
|
25
26
|
const vendoredStylesDir = path.join( rootDir, 'vendor', 'styles' );
|
|
26
27
|
const vendoredOfficialStylesDir = path.join( rootDir, 'vendor', 'styles-official' );
|
|
27
|
-
|
|
28
|
-
function defaultRuntimeRoot() {
|
|
29
|
-
if ( process.env.CITECLAW_HOME ) {
|
|
30
|
-
return path.resolve( process.env.CITECLAW_HOME );
|
|
31
|
-
}
|
|
32
|
-
if ( process.env.XDG_CACHE_HOME ) {
|
|
33
|
-
return path.join( process.env.XDG_CACHE_HOME, 'citeclaw' );
|
|
34
|
-
}
|
|
35
|
-
if ( process.platform === 'win32' ) {
|
|
36
|
-
const base = process.env.LOCALAPPDATA || process.env.APPDATA || path.join( os.homedir(), 'AppData', 'Local' );
|
|
37
|
-
return path.join( base, 'citeclaw' );
|
|
38
|
-
}
|
|
39
|
-
if ( process.platform === 'darwin' ) {
|
|
40
|
-
return path.join( os.homedir(), 'Library', 'Caches', 'citeclaw' );
|
|
41
|
-
}
|
|
42
|
-
return path.join( os.homedir(), '.cache', 'citeclaw' );
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const localDir = defaultRuntimeRoot();
|
|
46
|
-
const zoteroDir = process.env.ZOTERO_DIR || path.join( localDir, 'runtime', 'zotero' );
|
|
28
|
+
const localDir = path.join( rootDir, '.local' );
|
|
47
29
|
const logDir = path.join( localDir, 'logs' );
|
|
48
30
|
const stateDir = path.join( localDir, 'state' );
|
|
49
31
|
const localTranslatorSourcesDir = path.join( localDir, 'translator-sources' );
|
|
@@ -183,7 +165,7 @@ function usageZotero( subAction = '' ) {
|
|
|
183
165
|
console.error( ' citeclaw zotero login --library-type groups --library-id <group-id> --api-key <key>' );
|
|
184
166
|
console.error( 'notes:' );
|
|
185
167
|
console.error( ' - personal library defaults to library-type users' );
|
|
186
|
-
console.error(
|
|
168
|
+
console.error( ' - credentials are saved to .local/state/zotero-auth.json' );
|
|
187
169
|
console.error( ' - API key can be created at https://www.zotero.org/settings/keys' );
|
|
188
170
|
return;
|
|
189
171
|
}
|
|
@@ -191,7 +173,7 @@ function usageZotero( subAction = '' ) {
|
|
|
191
173
|
console.error( 'usage:' );
|
|
192
174
|
console.error( ' citeclaw zotero logout' );
|
|
193
175
|
console.error( 'notes:' );
|
|
194
|
-
console.error(
|
|
176
|
+
console.error( ' - removes local credentials from .local/state/zotero-auth.json' );
|
|
195
177
|
return;
|
|
196
178
|
}
|
|
197
179
|
if ( action === 'whoami' ) {
|
|
@@ -487,6 +469,46 @@ function runCommandOrThrow( command, args, cwd ) {
|
|
|
487
469
|
return result;
|
|
488
470
|
}
|
|
489
471
|
|
|
472
|
+
function styleLocaleUrls( locale ) {
|
|
473
|
+
const fileName = `locales-${ locale }.xml`;
|
|
474
|
+
return [
|
|
475
|
+
`https://raw.githubusercontent.com/citation-style-language/locales/master/${ fileName }`,
|
|
476
|
+
`https://github.com/citation-style-language/locales/raw/master/${ fileName }`,
|
|
477
|
+
`https://cdn.jsdelivr.net/gh/citation-style-language/locales@master/${ fileName }`
|
|
478
|
+
];
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
function downloadFileWithCurlFallbacks( urls, dest, label, runner ) {
|
|
482
|
+
const runCommand = runner || runCommandOrThrow;
|
|
483
|
+
const candidates = Array.isArray( urls ) ? urls : [ urls ];
|
|
484
|
+
const targetLabel = label || path.basename( dest );
|
|
485
|
+
const tempDest = `${ dest }.tmp`;
|
|
486
|
+
const failures = [];
|
|
487
|
+
fs.mkdirSync( path.dirname( dest ), { recursive: true } );
|
|
488
|
+
|
|
489
|
+
for ( const url of candidates ) {
|
|
490
|
+
try {
|
|
491
|
+
fs.rmSync( tempDest, { force: true } );
|
|
492
|
+
runCommand( 'curl', [
|
|
493
|
+
'-fsSL',
|
|
494
|
+
'--retry', '3',
|
|
495
|
+
'--retry-delay', '1',
|
|
496
|
+
'--retry-all-errors',
|
|
497
|
+
url,
|
|
498
|
+
'-o', tempDest
|
|
499
|
+
] );
|
|
500
|
+
fs.rmSync( dest, { force: true } );
|
|
501
|
+
fs.renameSync( tempDest, dest );
|
|
502
|
+
return;
|
|
503
|
+
} catch ( error ) {
|
|
504
|
+
failures.push( `${ url }: ${ error.message }` );
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
fs.rmSync( tempDest, { force: true } );
|
|
509
|
+
throw new Error( `failed to download ${ targetLabel }: ${ failures.join( ' | ' ) }` );
|
|
510
|
+
}
|
|
511
|
+
|
|
490
512
|
function resolveInstallCommand() {
|
|
491
513
|
if ( commandExists( 'npm' ) ) {
|
|
492
514
|
return { command: 'npm', args: [ 'install' ] };
|
|
@@ -657,7 +679,7 @@ function bootstrapLocalEnvironment() {
|
|
|
657
679
|
runCommandOrThrow( installer.command, installer.args, rootDir );
|
|
658
680
|
}
|
|
659
681
|
if ( !fileExists( path.join( zoteroDir, 'modules', 'translators' ) ) ) {
|
|
660
|
-
throw new Error(
|
|
682
|
+
throw new Error( 'missing vendored zotero contents under vendor/zotero/modules/translators' );
|
|
661
683
|
}
|
|
662
684
|
if ( !fileExists( path.join( zoteroDir, 'node_modules' ) ) ) {
|
|
663
685
|
runCommandOrThrow( installer.command, installer.args, zoteroDir );
|
|
@@ -1522,19 +1544,24 @@ async function runCitationFromPdf( pdfPath, options ) {
|
|
|
1522
1544
|
} );
|
|
1523
1545
|
}
|
|
1524
1546
|
|
|
1525
|
-
function syncStyles( options ) {
|
|
1547
|
+
function syncStyles( options, dependencies ) {
|
|
1548
|
+
const styleOptions = options || {};
|
|
1549
|
+
const hooks = dependencies || {};
|
|
1550
|
+
const downloadFile = hooks.downloadFile || downloadFileWithCurlFallbacks;
|
|
1551
|
+
const downloadUsesCurl = downloadFile === downloadFileWithCurlFallbacks;
|
|
1552
|
+
|
|
1526
1553
|
ensureDirs();
|
|
1527
1554
|
fs.mkdirSync( cslDir, { recursive: true } );
|
|
1528
1555
|
fs.mkdirSync( localeDir, { recursive: true } );
|
|
1529
|
-
const configuredSourceDirs =
|
|
1556
|
+
const configuredSourceDirs = styleOptions.repo ?
|
|
1530
1557
|
[
|
|
1531
|
-
path.isAbsolute(
|
|
1532
|
-
|
|
1533
|
-
path.join( rootDir,
|
|
1558
|
+
path.isAbsolute( styleOptions.repo || '' ) ?
|
|
1559
|
+
styleOptions.repo :
|
|
1560
|
+
path.join( rootDir, styleOptions.repo || 'vendor/styles' )
|
|
1534
1561
|
] :
|
|
1535
1562
|
ensureDefaultStyleSources();
|
|
1536
1563
|
const sourceDirs = configuredSourceDirs.filter( ( sourceDir ) => fileExists( sourceDir ) );
|
|
1537
|
-
if (
|
|
1564
|
+
if ( styleOptions.repo && !sourceDirs.length ) {
|
|
1538
1565
|
throw new Error( `styles source not found: ${ configuredSourceDirs.join( ', ' ) }` );
|
|
1539
1566
|
}
|
|
1540
1567
|
|
|
@@ -1550,13 +1577,12 @@ function syncStyles( options ) {
|
|
|
1550
1577
|
} );
|
|
1551
1578
|
|
|
1552
1579
|
const localeTargets = [ 'en-US', 'zh-CN' ];
|
|
1553
|
-
if ( !commandExists( 'curl' ) ) {
|
|
1580
|
+
if ( downloadUsesCurl && !commandExists( 'curl' ) ) {
|
|
1554
1581
|
throw new Error( 'curl is required for styles sync' );
|
|
1555
1582
|
}
|
|
1556
1583
|
for ( const locale of localeTargets ) {
|
|
1557
|
-
const localeUrl = `https://raw.githubusercontent.com/citation-style-language/locales/master/locales-${ locale }.xml`;
|
|
1558
1584
|
const dest = path.join( localeDir, `locales-${ locale }.xml` );
|
|
1559
|
-
|
|
1585
|
+
downloadFile( styleLocaleUrls( locale ), dest, `locale ${ locale }` );
|
|
1560
1586
|
}
|
|
1561
1587
|
|
|
1562
1588
|
process.stdout.write( `styles synced to ${ cslDir } from ${ sourceDirs.join( ', ' ) } (copied ${ copiedCount } files)\n` );
|
|
@@ -4997,9 +5023,11 @@ function main() {
|
|
|
4997
5023
|
usage();
|
|
4998
5024
|
process.exit( 1 );
|
|
4999
5025
|
}
|
|
5000
|
-
|
|
5026
|
+
try {
|
|
5027
|
+
syncStyles( parsed );
|
|
5028
|
+
} catch ( error ) {
|
|
5001
5029
|
handleCommandError( error, parsed, 'styles', 'sync' );
|
|
5002
|
-
}
|
|
5030
|
+
}
|
|
5003
5031
|
return;
|
|
5004
5032
|
}
|
|
5005
5033
|
|
|
@@ -5158,12 +5186,14 @@ function main() {
|
|
|
5158
5186
|
}
|
|
5159
5187
|
|
|
5160
5188
|
module.exports = {
|
|
5189
|
+
downloadFileWithCurlFallbacks,
|
|
5161
5190
|
detectPdfIdentifierCandidates,
|
|
5162
5191
|
extractBestDoiCandidate,
|
|
5163
5192
|
extractPdfCandidates,
|
|
5164
5193
|
main,
|
|
5165
5194
|
normalizeArxivId,
|
|
5166
5195
|
normalizeDoi,
|
|
5196
|
+
syncStyles,
|
|
5167
5197
|
shouldAttemptPdfOcr
|
|
5168
5198
|
};
|
|
5169
5199
|
|