@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 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 écrit `SEARKIN_KEY` dans `.env.local`, ainsi que `SEARKIN_SITE_URL` s'il peut la déduire de `NEXT_PUBLIC_SERVER_URL` / `NEXT_PUBLIC_SITE_URL` (ou de l'option `--site-url`) ;
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, pour rendre les canonicals absolus (à défaut : `NEXT_PUBLIC_SERVER_URL`, puis `NEXT_PUBLIC_SITE_URL`) |
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
- const siteUrl = args.siteUrl || readEnvValue(root, ['SEARKIN_SITE_URL', 'NEXT_PUBLIC_SERVER_URL', 'NEXT_PUBLIC_SITE_URL', 'SITE_URL']);
159
- if (siteUrl)
160
- vars.SEARKIN_SITE_URL = siteUrl;
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, config, fetchRedirectRules, isPermanent, normalizePath, robotsFor, searkinGet, } from './shared.js';
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, config().siteUrl) };
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, config as env, outputMode, parseRevalidateBody, verifySignature, VERSION } from './shared.js';
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 = env().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.0";
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.0';
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@searkin/next",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Searkin pour les sites Next.js sans CMS : métadonnées SEO, redirections et rafraîchissement signé, pilotés depuis Searkin.",
5
5
  "license": "MIT",
6
6
  "type": "module",