refactor(linkedin-studio): M0-6 — quick-import.mjs via getDataRoot + fix printed path
This commit is contained in:
parent
55e8816764
commit
0f12306d05
2 changed files with 114 additions and 52 deletions
45
hooks/scripts/__tests__/quick-import.test.mjs
Normal file
45
hooks/scripts/__tests__/quick-import.test.mjs
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
import { describe, test, before, after } from 'node:test';
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import { mkdtempSync, rmSync, existsSync } from 'node:fs';
|
||||||
|
import { join } from 'node:path';
|
||||||
|
import { tmpdir } from 'node:os';
|
||||||
|
|
||||||
|
// M0-6: quick-import must resolve its exports dir under the EXTERNAL data root,
|
||||||
|
// and the manual import command it prints must no longer pin ANALYTICS_ROOT to
|
||||||
|
// the in-plugin tree. The module's executable body (browser open + poll timer)
|
||||||
|
// sits behind a standalone guard, so importing it for tests is side-effect-free.
|
||||||
|
// LINKEDIN_STUDIO_DATA is stubbed before the dynamic import (EXPORTS_DIR binds at
|
||||||
|
// load). Pattern: data-root.test.mjs env-stub + queue-manager.test.mjs.
|
||||||
|
describe('quick-import — external data root + de-pinned command (M0-6)', () => {
|
||||||
|
let dataDir, mod;
|
||||||
|
const saved = { LINKEDIN_STUDIO_DATA: process.env.LINKEDIN_STUDIO_DATA };
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
dataDir = mkdtempSync(join(tmpdir(), 'lis-quickimport-'));
|
||||||
|
process.env.LINKEDIN_STUDIO_DATA = dataDir;
|
||||||
|
mod = await import('../quick-import.mjs');
|
||||||
|
});
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
if (dataDir && existsSync(dataDir)) rmSync(dataDir, { recursive: true, force: true });
|
||||||
|
if (saved.LINKEDIN_STUDIO_DATA === undefined) delete process.env.LINKEDIN_STUDIO_DATA;
|
||||||
|
else process.env.LINKEDIN_STUDIO_DATA = saved.LINKEDIN_STUDIO_DATA;
|
||||||
|
});
|
||||||
|
|
||||||
|
test('EXPORTS_DIR resolves under <dataDir>/analytics/exports', () => {
|
||||||
|
assert.equal(mod.EXPORTS_DIR, join(dataDir, 'analytics', 'exports'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('the printed import command no longer pins a bare in-plugin assets/analytics path', () => {
|
||||||
|
const cmd = mod.buildImportCommand('/plugin/root', 'export.csv');
|
||||||
|
assert.ok(
|
||||||
|
!cmd.includes('assets/analytics'),
|
||||||
|
'manual command must not pin ANALYTICS_ROOT to the in-plugin assets/analytics tree',
|
||||||
|
);
|
||||||
|
assert.ok(
|
||||||
|
cmd.includes('scripts/analytics/src/cli.ts'),
|
||||||
|
'manual command must still invoke the in-plugin tsx CLI',
|
||||||
|
);
|
||||||
|
assert.ok(cmd.includes('export.csv'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -6,16 +6,25 @@ import { existsSync, mkdirSync, readdirSync, statSync, copyFileSync } from 'node
|
||||||
import { join, dirname } from 'node:path';
|
import { join, dirname } from 'node:path';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
import { exec } from 'node:child_process';
|
import { exec } from 'node:child_process';
|
||||||
|
import { getDataRoot } from './data-root.mjs';
|
||||||
|
|
||||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||||
const PLUGIN_ROOT = join(__dirname, '..', '..');
|
const PLUGIN_ROOT = join(__dirname, '..', '..');
|
||||||
const HOME = process.env.HOME || process.env.USERPROFILE || '';
|
const HOME = process.env.HOME || process.env.USERPROFILE || '';
|
||||||
const EXPORTS_DIR = join(PLUGIN_ROOT, 'assets', 'analytics', 'exports');
|
const EXPORTS_DIR = join(getDataRoot('analytics'), 'exports');
|
||||||
const DOWNLOADS_DIR = join(HOME, 'Downloads');
|
const DOWNLOADS_DIR = join(HOME, 'Downloads');
|
||||||
const POLL_INTERVAL = 3000;
|
const POLL_INTERVAL = 3000;
|
||||||
const MAX_WAIT = 300000; // 5 minutes
|
const MAX_WAIT = 300000; // 5 minutes
|
||||||
|
|
||||||
mkdirSync(EXPORTS_DIR, { recursive: true });
|
// Manual import command we print for the user. It must NOT pin ANALYTICS_ROOT to
|
||||||
|
// the in-plugin tree anymore — the CLI defaults to the external data root
|
||||||
|
// (storage.ts:getDataRoot), so the data path is omitted. The plugin path stays
|
||||||
|
// only for the in-plugin tsx executable (cli.ts), which is bundled, not data.
|
||||||
|
export function buildImportCommand(pluginRoot, filename) {
|
||||||
|
return `node --import tsx "${pluginRoot}/scripts/analytics/src/cli.ts" import "${filename}"`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { EXPORTS_DIR };
|
||||||
|
|
||||||
// Snapshot existing CSV files
|
// Snapshot existing CSV files
|
||||||
function getCsvFiles() {
|
function getCsvFiles() {
|
||||||
|
|
@ -34,19 +43,22 @@ function openUrl(url) {
|
||||||
exec(`${cmd} "${url}"`, () => {});
|
exec(`${cmd} "${url}"`, () => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
const beforeFiles = new Set(getCsvFiles());
|
function main() {
|
||||||
|
mkdirSync(EXPORTS_DIR, { recursive: true });
|
||||||
|
|
||||||
console.log('Opening LinkedIn Analytics in your browser...');
|
const beforeFiles = new Set(getCsvFiles());
|
||||||
openUrl('https://www.linkedin.com/analytics/creator/content/');
|
|
||||||
|
|
||||||
console.log('\nInstructions:');
|
console.log('Opening LinkedIn Analytics in your browser...');
|
||||||
console.log(' 1. Click \'Export\' (top right) in LinkedIn Analytics');
|
openUrl('https://www.linkedin.com/analytics/creator/content/');
|
||||||
console.log(' 2. LinkedIn will download a CSV to ~/Downloads');
|
|
||||||
console.log(' 3. This script will detect it automatically\n');
|
|
||||||
console.log('Watching ~/Downloads for new CSV files (max 5 minutes)...\n');
|
|
||||||
|
|
||||||
let elapsed = 0;
|
console.log('\nInstructions:');
|
||||||
const timer = setInterval(() => {
|
console.log(' 1. Click \'Export\' (top right) in LinkedIn Analytics');
|
||||||
|
console.log(' 2. LinkedIn will download a CSV to ~/Downloads');
|
||||||
|
console.log(' 3. This script will detect it automatically\n');
|
||||||
|
console.log('Watching ~/Downloads for new CSV files (max 5 minutes)...\n');
|
||||||
|
|
||||||
|
let elapsed = 0;
|
||||||
|
const timer = setInterval(() => {
|
||||||
elapsed += POLL_INTERVAL;
|
elapsed += POLL_INTERVAL;
|
||||||
|
|
||||||
const currentFiles = getCsvFiles();
|
const currentFiles = getCsvFiles();
|
||||||
|
|
@ -63,7 +75,7 @@ const timer = setInterval(() => {
|
||||||
console.log('File is ready for import. Run:');
|
console.log('File is ready for import. Run:');
|
||||||
console.log(' /linkedin:import\n');
|
console.log(' /linkedin:import\n');
|
||||||
console.log('Or import directly with:');
|
console.log('Or import directly with:');
|
||||||
console.log(` ANALYTICS_ROOT="${PLUGIN_ROOT}/assets/analytics" node --import tsx "${PLUGIN_ROOT}/scripts/analytics/src/cli.ts" import "${filename}"`);
|
console.log(` ${buildImportCommand(PLUGIN_ROOT, filename)}`);
|
||||||
clearInterval(timer);
|
clearInterval(timer);
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
}
|
}
|
||||||
|
|
@ -83,4 +95,9 @@ const timer = setInterval(() => {
|
||||||
clearInterval(timer);
|
clearInterval(timer);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
}, POLL_INTERVAL);
|
}, POLL_INTERVAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
||||||
|
main();
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue