lightweight-sdk/scripts/create-package.mjs
Nicolas e2d7207237
Some checks failed
CI / build-lint-test (push) Failing after 1m53s
CI / build-lint-test (pull_request) Successful in 1m58s
Bootstrap monorepo pnpm + migration de web-component
- Outillage complet : pnpm workspaces (catalog), Biome, TypeScript
  (project references, CommonJS/Node10), Vitest, TypeDoc (validation
  bloquante en CI), Changesets.
- Organisation des packages en catégories client/common/server.
- Migration de @lightweight-sdk/web-component (packages/client/) depuis
  l'ancien repo lightweight-sdk-web-component : code source copié à
  l'identique, aucune modification. package.json/tsconfig.json/README.md
  adaptés autour. Exclu de la validation TypeDoc stricte et de quelques
  règles Biome (formatage, noShadowRestrictedNames) puisque le code ne
  peut pas être édité pour s'y conformer - détails dans spec/packages.md.
- Workflows Forgejo Actions (.forgejo/workflows/) : ci.yml (lint, build,
  test, docs:check) et release.yml (publication sur le registre npm
  privé via Changesets).
- Script pnpm create-package <client|common|server> <nom> pour scaffolder
  les futurs packages.
- Documentation dans /spec (architecture, conventions, ci-cd, packages).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-18 07:10:13 +02:00

153 lines
4.6 KiB
JavaScript

#!/usr/bin/env node
import { execFileSync } from 'node:child_process';
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
const ROOT = dirname(dirname(fileURLToPath(import.meta.url)));
const CATEGORIES = ['client', 'common', 'server'];
/**
* Prints usage information and exits with an error status.
* @param {string} message the reason the script is aborting
*/
function fail(message) {
console.error(`Erreur : ${message}`);
console.error('Usage : pnpm create-package <client|common|server> <nom-du-package>');
process.exitCode = 1;
process.exit(1);
}
const [, , category, name] = process.argv;
if (!category || !CATEGORIES.includes(category)) {
fail(`catégorie invalide (attendu : ${CATEGORIES.join(', ')})`);
}
if (!name || !/^[a-z][a-z0-9-]*[a-z0-9]$/.test(name)) {
fail('nom de package invalide (attendu : kebab-case, ex. "my-widget")');
}
const packageDir = join(ROOT, 'packages', category, name);
if (existsSync(packageDir)) {
fail(`packages/${category}/${name} existe déjà`);
}
const npmName = `@lightweight-sdk/${name}`;
const isClient = category === 'client';
mkdirSync(join(packageDir, 'src'), { recursive: true });
const packageJson = {
name: npmName,
version: '0.0.1',
description: `TODO: décrire le package ${name}.`,
main: 'dist/index.js',
types: 'dist/index.d.ts',
files: ['dist'],
engines: { node: '>=22' },
publishConfig: {
access: 'restricted',
registry: 'https://git.dev.kyuno.fr/api/packages/kyuno/npm/',
},
typedocOptions: {
entryPoints: ['src/index.ts'],
readme: 'README.md',
},
};
writeFileSync(join(packageDir, 'package.json'), `${JSON.stringify(packageJson, null, '\t')}\n`);
const tsconfig = {
extends: '../../../tsconfig.base.json',
compilerOptions: isClient
? { lib: ['ES2022', 'DOM', 'DOM.Iterable'], outDir: './dist', rootDir: './src' }
: { outDir: './dist', rootDir: './src' },
include: ['src/**/*'],
};
writeFileSync(join(packageDir, 'tsconfig.json'), `${JSON.stringify(tsconfig, null, '\t')}\n`);
const indexTs = `/**
* Placeholder function for the ${npmName} package — replace with real exports.
* @param subject the name to greet
* @returns a greeting for the given subject
*/
export function greet(subject: string): string {
return \`Hello, \${subject}!\`;
}
`;
writeFileSync(join(packageDir, 'src', 'index.ts'), indexTs);
const indexTest = `import { describe, expect, it } from 'vitest';
import { greet } from './index';
describe('greet', () => {
it('greets the given subject', () => {
expect(greet('world')).toBe('Hello, world!');
});
});
`;
writeFileSync(join(packageDir, 'src', 'index.test.ts'), indexTest);
const commonNote =
category === 'common'
? "\n> `common/` doit rester isomorphe : pas d'API exclusivement DOM ou exclusivement Node.\n"
: '';
const readme = `# ${npmName}
${commonNote}
TODO: décrire le package.
## Installation
\`\`\`bash
pnpm add ${npmName}
\`\`\`
## Usage
TODO
`;
writeFileSync(join(packageDir, 'README.md'), readme);
// Édite le tsconfig.json racine (project references)
const rootTsconfigPath = join(ROOT, 'tsconfig.json');
const rootTsconfig = JSON.parse(readFileSync(rootTsconfigPath, 'utf8'));
rootTsconfig.references ??= [];
rootTsconfig.references.push({ path: `./packages/${category}/${name}` });
writeFileSync(rootTsconfigPath, `${JSON.stringify(rootTsconfig, null, '\t')}\n`);
// Ajoute une ligne au registre des packages
const packagesSpecPath = join(ROOT, 'spec', 'packages.md');
const packagesSpec = readFileSync(packagesSpecPath, 'utf8');
const newRow = `| \`${npmName}\` | ${category} | 0.0.1 | brouillon |\n`;
writeFileSync(packagesSpecPath, packagesSpec.trimEnd() + '\n' + newRow);
console.log(`Package ${npmName} créé dans packages/${category}/${name}/.`);
try {
execFileSync('pnpm', ['install'], { cwd: ROOT, stdio: 'inherit' });
execFileSync(
'pnpm',
[
'exec',
'biome',
'check',
'--write',
`packages/${category}/${name}`,
'tsconfig.json',
'spec/packages.md',
],
{ cwd: ROOT, stdio: 'inherit' },
);
} catch (error) {
console.error('Étape automatique échouée, à relancer toi-même :');
console.error(' pnpm install');
console.error(
` pnpm exec biome check --write packages/${category}/${name} tsconfig.json spec/packages.md`,
);
console.error(String(error));
}
console.log('\nProchaines étapes :');
console.log(` cd packages/${category}/${name}`);
console.log(' - compléter la description dans package.json et README.md');
console.log(` - pnpm --filter ${npmName} test`);
console.log(' - pnpm changeset (une fois prêt à publier)');