@searkin/next 0.1.0 → 0.1.2
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 +3 -3
- package/dist/cli.js +53 -21
- package/dist/index.js +2 -2
- package/dist/pages.js +2 -2
- package/dist/shared.d.ts +6 -1
- package/dist/shared.js +4 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,13 +7,13 @@ Compatible avec Next.js 13.4 à 16, en App Router comme en Pages Router.
|
|
|
7
7
|
## Installation
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
npm install @searkin/next
|
|
11
10
|
npx @searkin/next init --key sks_…
|
|
12
11
|
```
|
|
13
12
|
|
|
14
13
|
`init` commence par détecter si le projet utilise l'App Router ou le Pages Router, et s'il a un dossier `src/`. Il effectue ensuite les opérations suivantes :
|
|
15
14
|
|
|
16
|
-
- il
|
|
15
|
+
- il ajoute `@searkin/next` aux dépendances du site, avec le gestionnaire de paquets du projet (npm, pnpm, yarn ou bun, d'après son fichier de verrouillage). L'option `--no-install` l'en dispense ;
|
|
16
|
+
- il écrit `SEARKIN_KEY` dans `.env.local` (et `SEARKIN_SITE_URL` seulement avec l'option `--site-url`) ;
|
|
17
17
|
- il crée la route `app/api/searkin/[...searkin]/route.ts`, ou `pages/api/searkin/[...searkin].ts` en Pages Router ;
|
|
18
18
|
- il crée `middleware.ts` (`proxy.ts` à partir de Next 16). Si ce fichier existe déjà, il n'y touche pas et affiche les deux lignes à ajouter ;
|
|
19
19
|
- il affiche un exemple de `generateMetadata`, sans modifier vos pages.
|
|
@@ -25,7 +25,7 @@ Vous pouvez relancer `init` sans risque. Tout fichier modifié est d'abord sauve
|
|
|
25
25
|
| Variable | Rôle |
|
|
26
26
|
|---|---|
|
|
27
27
|
| `SEARKIN_KEY` | Clé de site `sks_…` générée par Searkin. Elle sert seulement à lire les valeurs de ce site et à vérifier les rafraîchissements |
|
|
28
|
-
| `SEARKIN_SITE_URL` | URL publique
|
|
28
|
+
| `SEARKIN_SITE_URL` | Facultatif. URL publique servant à rendre les canonicals absolus. Par défaut, Searkin fournit l'adresse du site suivi ; à définir seulement si le site est servi ailleurs (préproduction…). À défaut : `NEXT_PUBLIC_SERVER_URL`, puis `NEXT_PUBLIC_SITE_URL`, puis l'adresse fournie par Searkin |
|
|
29
29
|
| `SEARKIN_API` | Facultatif (par défaut : `https://app.searkin.com/api/proxy`) |
|
|
30
30
|
| `SEARKIN_OUTPUT` | `export` si le site est exporté de manière statique (valeur renvoyée par le ping) |
|
|
31
31
|
|
package/dist/cli.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* npx @searkin/next init --key sks_… [--dir <projet>] [--site-url https://…] [--dry-run]
|
|
3
|
+
* npx @searkin/next init --key sks_… [--dir <projet>] [--site-url https://…] [--dry-run] [--no-install]
|
|
4
4
|
*/
|
|
5
|
+
import { spawnSync } from 'node:child_process';
|
|
5
6
|
import { copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
6
7
|
import { dirname, join, relative, resolve } from 'node:path';
|
|
7
8
|
import { VERSION } from './shared.js';
|
|
8
9
|
function parseArgs(argv) {
|
|
9
|
-
const a = { dir: process.cwd(), dryRun: false };
|
|
10
|
+
const a = { dir: process.cwd(), dryRun: false, install: true };
|
|
10
11
|
for (let i = 0; i < argv.length; i++) {
|
|
11
12
|
const v = argv[i];
|
|
12
13
|
if (v === '--key')
|
|
@@ -19,6 +20,8 @@ function parseArgs(argv) {
|
|
|
19
20
|
a.dir = resolve(argv[++i] || '.');
|
|
20
21
|
else if (v === '--dry-run')
|
|
21
22
|
a.dryRun = true;
|
|
23
|
+
else if (v === '--no-install')
|
|
24
|
+
a.install = false;
|
|
22
25
|
else if (v === '--version' || v === '-v')
|
|
23
26
|
a.cmd = 'version';
|
|
24
27
|
else if (!v.startsWith('-') && !a.cmd)
|
|
@@ -64,19 +67,6 @@ function upsertEnv(root, file, vars, dryRun) {
|
|
|
64
67
|
write(root, file, next, dryRun);
|
|
65
68
|
return changed;
|
|
66
69
|
}
|
|
67
|
-
function readEnvValue(root, names) {
|
|
68
|
-
for (const f of ['.env.local', '.env.production', '.env']) {
|
|
69
|
-
const p = join(root, f);
|
|
70
|
-
if (!existsSync(p))
|
|
71
|
-
continue;
|
|
72
|
-
for (const name of names) {
|
|
73
|
-
const m = new RegExp(`^${name}=["']?([^"'\\n]+)`, 'm').exec(readFileSync(p, 'utf8'));
|
|
74
|
-
if (m && /^https?:\/\//.test(m[1]))
|
|
75
|
-
return m[1].trim().replace(/\/+$/, '');
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
return undefined;
|
|
79
|
-
}
|
|
80
70
|
function nextMajor(root) {
|
|
81
71
|
try {
|
|
82
72
|
const pkg = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8'));
|
|
@@ -133,6 +123,46 @@ export async function getServerSideProps({ resolvedUrl }) {
|
|
|
133
123
|
export default function Page({ seo }) {
|
|
134
124
|
return <><SearkinHead seo={seo} />…</>
|
|
135
125
|
}`;
|
|
126
|
+
/** Gestionnaire de paquets du projet, d'après son fichier de verrouillage. */
|
|
127
|
+
function packageManager(root) {
|
|
128
|
+
if (existsSync(join(root, 'pnpm-lock.yaml')))
|
|
129
|
+
return { cmd: 'pnpm', add: ['add'] };
|
|
130
|
+
if (existsSync(join(root, 'yarn.lock')))
|
|
131
|
+
return { cmd: 'yarn', add: ['add'] };
|
|
132
|
+
if (existsSync(join(root, 'bun.lock')) || existsSync(join(root, 'bun.lockb')))
|
|
133
|
+
return { cmd: 'bun', add: ['add'] };
|
|
134
|
+
return { cmd: 'npm', add: ['install'] };
|
|
135
|
+
}
|
|
136
|
+
/** Les fichiers créés importent `@searkin/next` : le paquet doit être une
|
|
137
|
+
* dépendance du site (lancé par `npx`, il ne l'est pas encore). */
|
|
138
|
+
function ensureDependency(root, args) {
|
|
139
|
+
let pkg;
|
|
140
|
+
try {
|
|
141
|
+
pkg = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8'));
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
pkg = {};
|
|
145
|
+
}
|
|
146
|
+
if (pkg.dependencies?.['@searkin/next'] || pkg.devDependencies?.['@searkin/next']) {
|
|
147
|
+
log('Dépendance @searkin/next : déjà présente.');
|
|
148
|
+
return true;
|
|
149
|
+
}
|
|
150
|
+
const pm = packageManager(root);
|
|
151
|
+
const cmd = [pm.cmd, ...pm.add, `@searkin/next@^${VERSION}`];
|
|
152
|
+
if (args.dryRun || !args.install) {
|
|
153
|
+
log(`Dépendance @searkin/next : à installer avec « ${cmd.join(' ')} ».`);
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
log(`Installation : ${cmd.join(' ')}`);
|
|
157
|
+
const res = spawnSync(cmd[0], cmd.slice(1), {
|
|
158
|
+
cwd: root, stdio: 'inherit', shell: process.platform === 'win32',
|
|
159
|
+
});
|
|
160
|
+
if (res.status !== 0) {
|
|
161
|
+
log(`Échec de l'installation : lancez « ${cmd.join(' ')} » à la racine du projet, puis relancez init.`);
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
136
166
|
function init(args) {
|
|
137
167
|
const root = args.dir;
|
|
138
168
|
log(`@searkin/next ${VERSION} — init${args.dryRun ? ' (simulation)' : ''}`);
|
|
@@ -150,18 +180,20 @@ function init(args) {
|
|
|
150
180
|
log(`Projet : ${hasApp ? 'App Router' : ''}${hasApp && hasPages ? ' + ' : ''}${hasPages ? 'Pages Router' : ''}${!hasApp && !hasPages ? 'aucun dossier app/ ni pages/' : ''}${base !== root ? ' (src/)' : ''}, Next ${major}${isExport ? ', output: export' : ''}`);
|
|
151
181
|
if (!hasApp && !hasPages)
|
|
152
182
|
return 1;
|
|
183
|
+
// 0. Dépendance (les fichiers ci-dessous l'importent).
|
|
184
|
+
if (!ensureDependency(root, args))
|
|
185
|
+
return 1;
|
|
153
186
|
// 1. Variables d'environnement.
|
|
154
187
|
if (args.key) {
|
|
155
188
|
if (!/^sks_[A-Za-z0-9_-]{16,}$/.test(args.key))
|
|
156
189
|
log('Attention : la clé ne ressemble pas à une clé de site Searkin (sks_…).');
|
|
157
190
|
const vars = { SEARKIN_KEY: args.key };
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
191
|
+
// Adresse publique : fournie par Searkin avec chaque réponse. SEARKIN_SITE_URL
|
|
192
|
+
// n'est écrite que sur demande (--site-url), pour une préproduction par exemple.
|
|
193
|
+
if (args.siteUrl)
|
|
194
|
+
vars.SEARKIN_SITE_URL = args.siteUrl.replace(/\/+$/, '');
|
|
161
195
|
const changed = upsertEnv(root, join(root, '.env.local'), vars, args.dryRun);
|
|
162
196
|
log(changed.length ? `.env.local : ${changed.join(', ')} écrit(s).` : '.env.local : déjà à jour.');
|
|
163
|
-
if (!siteUrl)
|
|
164
|
-
log(' SEARKIN_SITE_URL non déduit : ajoutez l’URL publique du site (canonicals absolus).');
|
|
165
197
|
}
|
|
166
198
|
// 2. Route /api/searkin/*.
|
|
167
199
|
if (isExport) {
|
|
@@ -225,7 +257,7 @@ function main() {
|
|
|
225
257
|
}
|
|
226
258
|
if (!args.cmd || args.cmd === 'init')
|
|
227
259
|
return init(args);
|
|
228
|
-
log('Usage : npx @searkin/next init --key sks_… [--site-url https://…] [--dir <projet>] [--dry-run]');
|
|
260
|
+
log('Usage : npx @searkin/next init --key sks_… [--site-url https://…] [--dir <projet>] [--dry-run] [--no-install]');
|
|
229
261
|
return 1;
|
|
230
262
|
}
|
|
231
263
|
process.exitCode = main();
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { absoluteUrl, CACHE_TAG,
|
|
1
|
+
import { absoluteUrl, CACHE_TAG, fetchRedirectRules, isPermanent, normalizePath, robotsFor, searkinGet, siteUrlFor, } from './shared.js';
|
|
2
2
|
export { VERSION, CACHE_TAG } from './shared.js';
|
|
3
3
|
/**
|
|
4
4
|
* Valeurs SEO brutes de Searkin pour un chemin ({} si rien n'est défini, null si l'API est
|
|
@@ -20,7 +20,7 @@ export function applySearkinSeo(seo, base = {}) {
|
|
|
20
20
|
if (seo.description)
|
|
21
21
|
out.description = seo.description;
|
|
22
22
|
if (seo.canonical) {
|
|
23
|
-
out.alternates = { ...(base.alternates || {}), canonical: absoluteUrl(seo.canonical,
|
|
23
|
+
out.alternates = { ...(base.alternates || {}), canonical: absoluteUrl(seo.canonical, siteUrlFor(seo)) };
|
|
24
24
|
}
|
|
25
25
|
const robots = robotsFor(seo.robots);
|
|
26
26
|
if (robots)
|
package/dist/pages.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import HeadModule from 'next/head.js';
|
|
2
2
|
import { createElement, Fragment } from 'react';
|
|
3
|
-
import { absoluteUrl,
|
|
3
|
+
import { absoluteUrl, outputMode, siteUrlFor, parseRevalidateBody, verifySignature, VERSION } from './shared.js';
|
|
4
4
|
export { getSearkinSeo } from './index.js';
|
|
5
5
|
// next/head est un module CommonJS : selon le chargeur, le composant est l'export par défaut ou son .default.
|
|
6
6
|
const Head = (HeadModule.default ?? HeadModule);
|
|
@@ -8,7 +8,7 @@ const Head = (HeadModule.default ?? HeadModule);
|
|
|
8
8
|
export function SearkinHead({ seo, path }) {
|
|
9
9
|
if (!seo)
|
|
10
10
|
return null;
|
|
11
|
-
const siteUrl =
|
|
11
|
+
const siteUrl = siteUrlFor(seo);
|
|
12
12
|
const children = [];
|
|
13
13
|
if (seo.title)
|
|
14
14
|
children.push(createElement('title', { key: 'searkin-title' }, seo.title));
|
package/dist/shared.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const VERSION = "0.1.
|
|
1
|
+
export declare const VERSION = "0.1.2";
|
|
2
2
|
export declare const CACHE_TAG = "searkin";
|
|
3
3
|
export type SearkinRobots = 'default' | 'index' | 'noindex' | null;
|
|
4
4
|
export type SearkinSeo = {
|
|
@@ -8,6 +8,8 @@ export type SearkinSeo = {
|
|
|
8
8
|
canonical?: string | null;
|
|
9
9
|
robots?: SearkinRobots;
|
|
10
10
|
updated_at?: string | null;
|
|
11
|
+
/** Adresse publique du site connue de Searkin (canonicals absolus sans SEARKIN_SITE_URL). */
|
|
12
|
+
site_url?: string | null;
|
|
11
13
|
};
|
|
12
14
|
export type SearkinRedirectRule = {
|
|
13
15
|
source: string;
|
|
@@ -30,6 +32,9 @@ type FetchInit = RequestInit & {
|
|
|
30
32
|
};
|
|
31
33
|
/** GET authentifié vers l'API site de Searkin. Renvoie null (et journalise une fois) en cas d'échec. */
|
|
32
34
|
export declare function searkinGet<T>(path: string, init?: FetchInit, missingKey?: string): Promise<T | null>;
|
|
35
|
+
/** Base des canonicals : variable d'environnement (surcharge, ex. préproduction),
|
|
36
|
+
* sinon l'adresse publique renvoyée par Searkin. */
|
|
37
|
+
export declare const siteUrlFor: (seo: SearkinSeo | null | undefined) => string | undefined;
|
|
33
38
|
export declare const absoluteUrl: (value: string, siteUrl?: string) => string;
|
|
34
39
|
export declare const robotsFor: (robots: SearkinRobots | undefined) => {
|
|
35
40
|
index: boolean;
|
package/dist/shared.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Code commun, sans dépendance Node : utilisable côté serveur, en Edge (middleware) et au build.
|
|
2
|
-
export const VERSION = '0.1.
|
|
2
|
+
export const VERSION = '0.1.2';
|
|
3
3
|
export const CACHE_TAG = 'searkin';
|
|
4
4
|
const DEFAULT_API = 'https://app.searkin.com/api/proxy';
|
|
5
5
|
const env = (name) => {
|
|
@@ -64,6 +64,9 @@ export async function searkinGet(path, init = {}, missingKey = 'SEARKIN_KEY abse
|
|
|
64
64
|
return null;
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
|
+
/** Base des canonicals : variable d'environnement (surcharge, ex. préproduction),
|
|
68
|
+
* sinon l'adresse publique renvoyée par Searkin. */
|
|
69
|
+
export const siteUrlFor = (seo) => config().siteUrl || (seo?.site_url ? seo.site_url.replace(/\/+$/, '') : undefined);
|
|
67
70
|
export const absoluteUrl = (value, siteUrl) => {
|
|
68
71
|
if (/^https?:\/\//i.test(value))
|
|
69
72
|
return value;
|
package/package.json
CHANGED