@obsidiane/meridiane 0.0.7 → 0.0.11

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
@@ -19,82 +19,30 @@ npm i -g @obsidiane/meridiane
19
19
  ## Usage
20
20
 
21
21
  ```bash
22
- # Générer des fichiers d'init (config + snippets)
23
- npx meridiane init
22
+ # Dev: génère le bridge (+ models par défaut)
23
+ # (build standalone + install local dans node_modules)
24
+ npx meridiane dev <packageName> --spec <url|file> [--preset[=native|all]] [--include <substr>]... [--exclude <substr>]... [--no-models] [--debug]
24
25
 
25
- # Générer une librairie Angular (dans projects/<lib-name>)
26
- npx meridiane lib <lib-name> <npm-package-name> [version] [url-registry]
27
-
28
- # Générer des modèles depuis OpenAPI
29
- npx meridiane models <SPEC_OPENAPI_URL_OU_FICHIER_JSON> [--out=<dir>] [--item-import=../lib/ports/resource-repository.port] [--required-mode=all-optional|spec] [--preset=all|native] [--include=<substr>] [--exclude=<substr>] [--index=1|0]
30
-
31
- # Dev: regen lib + models depuis un backend (localhost par défaut)
32
- npx meridiane dev-bridge <lib-name> <npm-package-name> [version]
33
-
34
- # Dev (repo meridiane): met à jour le bridge du sandbox
35
- npx meridiane sandbox-bridge
26
+ # CI/CD: génère + build Angular + npm pack (artefact prêt à publier)
27
+ npx meridiane build <packageName> --version <semver> --spec <url|file> [--preset[=native|all]] [--include <substr>]... [--exclude <substr>]... [--no-models] [--debug]
36
28
  ```
37
29
 
38
30
  Options :
39
31
  - `--debug` : active des logs supplémentaires (CLI).
40
32
 
41
- `url-registry` est optionnel. Recommandé : gérer le registry via `.npmrc` / variables CI plutôt que de le passer au générateur.
42
-
43
33
  Bonnes pratiques :
44
- - exécuter `meridiane init` une fois au début du repo du bridge (pour `models.config.js`, `.env.example`, snippets Angular)
45
- - committer `models.config.js` et `.env.example`, ne pas committer `.env`
46
- - générer les modèles dans `projects/<lib-name>/src/models` pour publier les types avec le package
47
-
48
- ## Exemple CI (générer + build + publish)
34
+ - utiliser `--preset=native` si vous voulez des modèles “entity-like” (sans variantes `.jsonld/.jsonMergePatch`)
35
+ - utiliser `--no-models` si vous voulez uniquement le runtime (pas besoin de `--spec`)
36
+ - laisser le registry à la CI (`.npmrc`, variables d’environnement, `npm publish --registry …`)
49
37
 
50
- ```bash
51
- npm ci
52
- npx -y @obsidiane/meridiane@0.1.0 lib backend-bridge @acme/backend-bridge 0.1.0
53
- npx -y @obsidiane/meridiane@0.1.0 models http://localhost:8000/api/docs.json --out=projects/backend-bridge/src/models
54
- npx ng build backend-bridge
55
- cd dist/backend-bridge
56
- npm publish
57
- ```
58
-
59
- Si votre workspace ne contient pas `ng-packagr` (souvent le cas dans une app Angular “pure”), installez-le :
60
-
61
- ```bash
62
- npm i -D ng-packagr
63
- ```
38
+ Note (repo Meridiane) :
39
+ - dans le repo Meridiane, `meridiane dev` peut être exécutée sans `packageName` et se préconfigure pour `apps/sandbox` (`@obsidiane/bridge-sandbox`).
64
40
 
65
- ## Configuration (models.config.js)
66
-
67
- Le générateur de modèles charge automatiquement un fichier `models.config.js` s’il existe dans le répertoire courant (CWD) ou dans un dossier parent.
68
-
69
- Copie rapide (après installation) :
41
+ ## Exemple CI (générer + build + publish)
70
42
 
71
43
  ```bash
72
- cp node_modules/@obsidiane/meridiane/models.config.example.js ./models.config.js
44
+ npx -y @obsidiane/meridiane@0.1.0 build @acme/backend-bridge --version 0.1.0 --spec https://staging.example/api/docs.json --preset=native
45
+ npm publish dist/backend-bridge
73
46
  ```
74
47
 
75
- Exécuter `meridiane init` est généralement plus simple (ça pose aussi `.env.example` et un snippet Angular).
76
-
77
- ## Configuration via `.env`
78
-
79
- Le CLI charge automatiquement un fichier `.env` (CWD ou dossier parent) avant d’exécuter `lib/models`.
80
-
81
- Variables utiles :
82
- - `MERIDIANE_DEBUG=1`
83
- - `MERIDIANE_LIB_VERSION=0.1.0`
84
- - `MERIDIANE_NPM_REGISTRY_URL=https://…`
85
- - `MERIDIANE_MODELS_OUT=projects/<lib>/src/models`
86
- - `MERIDIANE_MODELS_REQUIRED_MODE=spec`
87
- - `MERIDIANE_MODELS_ITEM_IMPORT=../lib/ports/resource-repository.port`
88
- - `MERIDIANE_MODELS_INDEX=1`
89
-
90
- Ordre de priorité :
91
- 1) flags CLI
92
- 2) `models.config.js`
93
- 3) `.env`
94
- 4) valeurs par défaut
95
-
96
- Sans installation préalable :
97
-
98
- ```bash
99
- npx -y @obsidiane/meridiane lib <lib-name> <npm-package-name> [version] [url-registry]
100
- ```
48
+ Note : Meridiane installe le toolchain (`ng-packagr`, `@angular/*`, …) dans `dist/.meridiane-workspace` si nécessaire.
package/cli.js CHANGED
@@ -1,76 +1,70 @@
1
1
  #!/usr/bin/env node
2
- import {fileURLToPath} from 'node:url';
3
- import path from 'node:path';
4
- import {spawn} from 'node:child_process';
2
+ import process from 'node:process';
3
+ import { Command } from 'commander';
5
4
 
6
- const __filename = fileURLToPath(import.meta.url);
7
- const __dirname = path.dirname(__filename);
5
+ import { runDev } from './tools/dev.js';
6
+ import { runBuild } from './tools/build.js';
7
+ import { createLogger } from './tools/core/logger.js';
8
8
 
9
- function runTool(scriptName, args) {
10
- const scriptPath = path.join(__dirname, 'tools', scriptName);
11
- const child = spawn(process.execPath, [scriptPath, ...args], {
12
- stdio: 'inherit',
13
- env: process.env,
14
- });
9
+ const program = new Command();
15
10
 
16
- child.on('exit', (code) => {
17
- process.exit(code ?? 0);
18
- });
19
- }
20
-
21
- function printHelp() {
22
- console.log(`
23
- Usage:
24
- meridiane init [--force]
25
- meridiane lib <lib-name> <npm-package-name> [version] [url-registry]
26
- meridiane models <SPEC_OPENAPI_URL_OU_FICHIER_JSON> [options]
27
- meridiane dev-bridge <lib-name> <npm-package-name> [version] [options]
28
- meridiane sandbox-bridge [options]
11
+ program
12
+ .name('meridiane')
13
+ .description('Generate an Angular bridge + models from an API Platform OpenAPI spec.')
14
+ .showHelpAfterError()
15
+ .showSuggestionAfterError();
29
16
 
30
- Options:
31
- --debug Active les logs (CLI et runtime)
17
+ function presetParser(value, previous) {
18
+ if (value === undefined && previous === undefined) return true; // `--preset` (no value) => native
19
+ return value ?? previous;
20
+ }
32
21
 
33
- Examples:
34
- npx -y @obsidiane/meridiane init
35
- npx -y @obsidiane/meridiane lib backend-bridge @acme/backend-bridge 0.1.0
36
- npx -y @obsidiane/meridiane models http://localhost:8000/api/docs.json --out=projects/backend-bridge/src/models
37
- npx -y @obsidiane/meridiane dev-bridge bridge-sandbox @obsidiane/bridge-sandbox 0.1.0
38
- npx -y @obsidiane/meridiane sandbox-bridge
39
- `);
22
+ function collect(value, previous = []) {
23
+ return [...previous, value];
40
24
  }
41
25
 
42
- const argv = process.argv.slice(2);
43
- const debugIndex = argv.indexOf('--debug');
44
- if (debugIndex >= 0) {
45
- argv.splice(debugIndex, 1);
46
- process.env.MERIDIANE_DEBUG = process.env.MERIDIANE_DEBUG || '1';
26
+ function commonOptions(cmd) {
27
+ return cmd
28
+ .option('--spec <urlOrFile>', 'OpenAPI spec source (URL or local JSON file)')
29
+ .option('--preset [mode]', 'Preset: native|all (default: all; `--preset` alone => native)', presetParser)
30
+ .option('--include <substr>', 'Include schema names containing substring (repeatable, supports commas)', collect, [])
31
+ .option('--exclude <substr>', 'Exclude schema names containing substring (repeatable, supports commas)', collect, [])
32
+ .option('--no-models', 'Skip models generation (then --spec is not required)')
33
+ .option('--debug', 'Enable debug logs');
47
34
  }
48
35
 
49
- const [cmd, ...rest] = argv;
36
+ commonOptions(
37
+ program
38
+ .command('dev')
39
+ .argument(
40
+ '[packageName]',
41
+ 'NPM package name of the generated bridge (e.g. @acme/backend-bridge). Optional in this repo (defaults to @obsidiane/bridge-sandbox).'
42
+ )
43
+ .action(async (packageName, opts) => {
44
+ const log = createLogger({ debug: !!opts?.debug });
45
+ try {
46
+ await runDev(packageName, opts);
47
+ } catch (err) {
48
+ log.error(err);
49
+ process.exit(1);
50
+ }
51
+ })
52
+ );
50
53
 
51
- switch (cmd) {
52
- case 'init':
53
- runTool('init.js', rest);
54
- break;
55
- case 'lib':
56
- runTool('generate-lib.js', rest);
57
- break;
58
- case 'models':
59
- runTool('generate-models.js', rest);
60
- break;
61
- case 'dev-bridge':
62
- runTool('dev-bridge.js', rest);
63
- break;
64
- case 'sandbox-bridge':
65
- runTool('sandbox-bridge.js', rest);
66
- break;
67
- case '-h':
68
- case '--help':
69
- case undefined:
70
- printHelp();
71
- break;
72
- default:
73
- console.error(`Unknown command: ${cmd}`);
74
- printHelp();
75
- process.exit(1);
76
- }
54
+ commonOptions(
55
+ program
56
+ .command('build')
57
+ .argument('<packageName>', 'NPM package name of the generated bridge (e.g. @acme/backend-bridge)')
58
+ .requiredOption('--version <semver>', 'Version to write in the generated bridge package.json (CI/CD)')
59
+ .action(async (packageName, opts) => {
60
+ const log = createLogger({ debug: !!opts?.debug });
61
+ try {
62
+ await runBuild(packageName, opts);
63
+ } catch (err) {
64
+ log.error(err);
65
+ process.exit(1);
66
+ }
67
+ })
68
+ );
69
+
70
+ program.parse(process.argv);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@obsidiane/meridiane",
3
- "version": "0.0.7",
3
+ "version": "0.0.11",
4
4
  "description": "Générateur de librairie Angular et modèles TypeScript (OpenAPI) pour un bridge Symfony/API Platform + Mercure.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -14,21 +14,19 @@
14
14
  "pack:check": "npm_config_cache=./.npm-cache npm pack --dry-run"
15
15
  },
16
16
  "bin": {
17
- "meridiane": "cli.js",
18
- "meridiane-generate-lib": "tools/generate-lib.js",
19
- "meridiane-generate-models": "tools/generate-models.js"
17
+ "meridiane": "cli.js"
20
18
  },
21
19
  "files": [
22
20
  "cli.js",
23
21
  "tools/**",
24
22
  "templates/**",
25
- "models.config.example.js",
26
23
  "README.md"
27
24
  ],
28
25
  "engines": {
29
26
  "node": ">=18"
30
27
  },
31
28
  "dependencies": {
29
+ "commander": "^14.0.2",
32
30
  "handlebars": "^4.7.8",
33
31
  "tslib": "^2.3.0"
34
32
  }
@@ -3,7 +3,8 @@
3
3
  "version": "0.0.1",
4
4
  "peerDependencies": {
5
5
  "@angular/common": "^20.1.0",
6
- "@angular/core": "^20.1.0"
6
+ "@angular/core": "^20.1.0",
7
+ "rxjs": "^7.8.0"
7
8
  },
8
9
  "dependencies": {
9
10
  "tslib": "^2.8.1"
@@ -1,2 +1 @@
1
1
  export {};
2
-
package/tools/build.js ADDED
@@ -0,0 +1,104 @@
1
+ #!/usr/bin/env node
2
+ import process from 'node:process';
3
+ import { spawn } from 'node:child_process';
4
+ import path from 'node:path';
5
+
6
+ import { deriveLibName } from './core/lib-name.js';
7
+ import { normalizePreset, splitListArgs } from './core/paths.js';
8
+ import { readOpenApiSpec } from './core/spec.js';
9
+ import { createLogger } from './core/logger.js';
10
+ import { generateBridgeWorkspace } from './core/generate.js';
11
+ import { ensureStandaloneWorkspace, ensureToolchainInstalled } from './core/standalone.js';
12
+
13
+ function run(cmd, args, { cwd, env } = {}) {
14
+ return new Promise((resolve) => {
15
+ const child = spawn(cmd, args, {
16
+ stdio: 'inherit',
17
+ cwd: cwd ?? process.cwd(),
18
+ env: env ?? process.env,
19
+ });
20
+ child.on('exit', (code) => resolve(code ?? 0));
21
+ });
22
+ }
23
+
24
+ export async function runBuild(packageName, opts) {
25
+ const cwd = process.cwd();
26
+ const debug = !!opts.debug;
27
+ const log = createLogger({ debug });
28
+
29
+ const version = opts.version;
30
+ if (!version) throw new Error('Missing --version');
31
+
32
+ const noModels = opts.models === false;
33
+ const preset = normalizePreset(opts.preset);
34
+ const include = splitListArgs(opts.include);
35
+ const exclude = splitListArgs(opts.exclude);
36
+
37
+ const libName = deriveLibName(packageName);
38
+ const requiredMode = 'spec';
39
+
40
+ // Standalone mode (native): works from any repo (backend pipeline or otherwise).
41
+ log.title('build — standalone');
42
+ log.info(`repo: ${cwd}`);
43
+ log.info(`package: ${packageName}@${version}`);
44
+ log.info(`lib: ${libName}`);
45
+ if (noModels) log.info('models: désactivés (--no-models)');
46
+ else log.info(`spec: ${opts.spec} | preset: ${preset}`);
47
+
48
+ const { workspaceRoot, distRoot, npmCacheDir, requireFromWs } = await ensureStandaloneWorkspace({ repoRoot: cwd, log });
49
+ log.info(`workspace: ${workspaceRoot}`);
50
+
51
+ let spec;
52
+ if (!noModels) {
53
+ log.step('lecture de la spec OpenAPI');
54
+ spec = await readOpenApiSpec(opts.spec);
55
+ log.success('spec chargée');
56
+ }
57
+
58
+ await ensureToolchainInstalled({ workspaceRoot, requireFromWs, npmCacheDir, log });
59
+
60
+ await generateBridgeWorkspace({
61
+ cwd: workspaceRoot,
62
+ libName,
63
+ packageName,
64
+ version,
65
+ noModels,
66
+ spec,
67
+ requiredMode,
68
+ preset,
69
+ include,
70
+ exclude,
71
+ debug,
72
+ log,
73
+ distRoot,
74
+ });
75
+
76
+ const ngPackageJsonPath = path.join(workspaceRoot, 'projects', libName, 'ng-package.json');
77
+ const tsconfigProdPath = path.join(workspaceRoot, 'projects', libName, 'tsconfig.lib.prod.json');
78
+
79
+ log.step('ng-packagr');
80
+ const ngPackagrPath = requireFromWs.resolve('ng-packagr/src/cli/main.js');
81
+ const packagrCode = await run(process.execPath, [ngPackagrPath, '-p', ngPackageJsonPath, '-c', tsconfigProdPath], {
82
+ cwd: workspaceRoot,
83
+ });
84
+ if (packagrCode !== 0) process.exit(packagrCode);
85
+
86
+ const distDir = path.join(distRoot, libName);
87
+ log.step(`npm pack (${path.relative(cwd, distDir)})`);
88
+ const packCode = await run('npm', ['pack'], {
89
+ cwd: distDir,
90
+ env: {
91
+ ...process.env,
92
+ npm_config_cache: npmCacheDir,
93
+ npm_config_update_notifier: 'false',
94
+ npm_config_fund: 'false',
95
+ npm_config_audit: 'false',
96
+ },
97
+ });
98
+ if (packCode === 0) log.success(`package prêt: ${path.relative(cwd, distDir)}`);
99
+ process.exit(packCode);
100
+ }
101
+
102
+ if (import.meta.url === `file://${process.argv[1]}`) {
103
+ throw new Error('This module is not meant to be executed directly.');
104
+ }
@@ -0,0 +1,152 @@
1
+ import fs from 'node:fs/promises';
2
+ import { existsSync } from 'node:fs';
3
+ import path from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+
6
+ import { readJson, writeJsonIfChanged } from './json.js';
7
+ import { renderTemplateToFile } from '../generator/models/handlebars.js';
8
+ import { ensureCleanDir } from '../generator/models/utils.js';
9
+ import { buildModelsFromOpenAPI } from '../generator/models/openapi-to-models.js';
10
+
11
+ const __filename = fileURLToPath(import.meta.url);
12
+ const __dirname = path.dirname(__filename);
13
+
14
+ const pkgRoot = path.resolve(__dirname, '../..'); // packages/meridiane
15
+
16
+ async function replacePlaceholdersInDir(dir, placeholders) {
17
+ const entries = await fs.readdir(dir, { withFileTypes: true });
18
+ for (const entry of entries) {
19
+ const fullPath = path.join(dir, entry.name);
20
+ if (entry.isDirectory()) {
21
+ await replacePlaceholdersInDir(fullPath, placeholders);
22
+ } else if (entry.isFile()) {
23
+ let content = await fs.readFile(fullPath, 'utf8');
24
+ for (const [key, value] of Object.entries(placeholders)) {
25
+ content = content.replaceAll(key, value);
26
+ }
27
+ await fs.writeFile(fullPath, content);
28
+ }
29
+ }
30
+ }
31
+
32
+ async function generateLibrary({ cwd, libName, packageName, version, debug }) {
33
+ const tplDir = path.resolve(pkgRoot, 'templates/_lib_template');
34
+ const targetDir = path.resolve(cwd, 'projects', libName);
35
+
36
+ if (debug) console.log('[meridiane] generate lib', { libName, packageName, version, targetDir: path.relative(cwd, targetDir) });
37
+
38
+ await fs.rm(targetDir, { recursive: true, force: true });
39
+ await fs.mkdir(targetDir, { recursive: true });
40
+ await fs.cp(tplDir, targetDir, { recursive: true });
41
+
42
+ await replacePlaceholdersInDir(targetDir, { '__LIB_NAME__': libName });
43
+
44
+ const libPackageJsonPath = path.join(targetDir, 'package.json');
45
+ if (existsSync(libPackageJsonPath)) {
46
+ const libPkg = await readJson(libPackageJsonPath);
47
+ libPkg.name = packageName;
48
+ libPkg.version = version;
49
+ await writeJsonIfChanged(libPackageJsonPath, libPkg);
50
+ }
51
+
52
+ // angular.json / workspace patching is intentionally not done here (Meridiane is standalone).
53
+ }
54
+
55
+ async function generateModels({ cwd, libName, spec, requiredMode, preset, include, exclude, debug }) {
56
+ const outDir = path.resolve(cwd, 'projects', libName, 'src', 'models');
57
+ const templatesDir = path.resolve(pkgRoot, 'tools', 'generator', 'models', 'templates');
58
+
59
+ if (debug) console.log('[meridiane] generate models', { outDir: path.relative(cwd, outDir), requiredMode, preset, includeCount: include.length, excludeCount: exclude.length });
60
+
61
+ await ensureCleanDir(outDir);
62
+
63
+ // Write models + index.ts.
64
+ const { models } = buildModelsFromOpenAPI(spec, {
65
+ requiredMode,
66
+ preset,
67
+ includeSchemaNames: include,
68
+ excludeSchemaNames: exclude,
69
+ });
70
+
71
+ for (const m of models) {
72
+ await renderTemplateToFile({
73
+ templatePath: path.join(templatesDir, 'model.hbs'),
74
+ outPath: path.join(outDir, `${m.name}.ts`),
75
+ ctx: {
76
+ name: m.name,
77
+ props: m.props,
78
+ imports: m.imports,
79
+ extendsTypes: m.extendsTypes || [],
80
+ },
81
+ });
82
+ }
83
+
84
+ await renderTemplateToFile({
85
+ templatePath: path.join(templatesDir, 'index.hbs'),
86
+ outPath: path.join(outDir, 'index.ts'),
87
+ ctx: { models: models.map((m) => ({ name: m.name })) },
88
+ });
89
+
90
+ if (debug) console.log(`[meridiane] ${models.length} model(s) generated`);
91
+ }
92
+
93
+ /**
94
+ * @param {{
95
+ * cwd: string,
96
+ * libName: string,
97
+ * packageName: string,
98
+ * version: string,
99
+ * noModels: boolean,
100
+ * spec?: any,
101
+ * requiredMode: 'all-optional'|'spec',
102
+ * preset: 'all'|'native',
103
+ * include: string[],
104
+ * exclude: string[],
105
+ * debug: boolean,
106
+ * log?: { step?: (msg: string) => void, info?: (msg: string) => void, success?: (msg: string) => void, debug?: (msg: string, data?: any) => void },
107
+ * workspaceMode?: 'angular'|'standalone',
108
+ * distRoot?: string,
109
+ * }} params
110
+ */
111
+ export async function generateBridgeWorkspace(params) {
112
+ const {
113
+ cwd,
114
+ libName,
115
+ packageName,
116
+ version,
117
+ noModels,
118
+ spec,
119
+ requiredMode,
120
+ preset,
121
+ include,
122
+ exclude,
123
+ debug,
124
+ log,
125
+ distRoot,
126
+ } = params;
127
+
128
+ log?.step?.(`génération de la librairie (projects/${libName})`);
129
+ await generateLibrary({ cwd, libName, packageName, version, debug });
130
+
131
+ // If a dist root is provided, force ng-packagr output to that directory.
132
+ if (distRoot) {
133
+ const ngPackagePath = path.resolve(cwd, 'projects', libName, 'ng-package.json');
134
+ const ngPkg = await readJson(ngPackagePath);
135
+ const desiredDestAbs = path.resolve(distRoot, libName);
136
+ const projectDir = path.dirname(ngPackagePath);
137
+ ngPkg.dest = path.relative(projectDir, desiredDestAbs).split(path.sep).join('/');
138
+ await writeJsonIfChanged(ngPackagePath, ngPkg);
139
+ log?.debug?.('ng-package.json dest overridden', { dest: ngPkg.dest });
140
+ }
141
+
142
+ if (noModels) {
143
+ log?.info?.('models ignorés (--no-models)');
144
+ if (debug) console.log('[meridiane] --no-models enabled, skipping models generation');
145
+ return;
146
+ }
147
+
148
+ if (!spec) throw new Error('Internal error: spec is required when noModels=false');
149
+ log?.step?.('génération des models (OpenAPI)');
150
+ await generateModels({ cwd, libName, spec, requiredMode, preset, include, exclude, debug });
151
+ log?.success?.('génération terminée');
152
+ }
@@ -0,0 +1,71 @@
1
+ import fs from 'node:fs/promises';
2
+
3
+ function stripJsonComments(input) {
4
+ let out = '';
5
+ let i = 0;
6
+ let inString = false;
7
+ let stringQuote = '"';
8
+ while (i < input.length) {
9
+ const ch = input[i];
10
+
11
+ if (inString) {
12
+ out += ch;
13
+ if (ch === '\\') {
14
+ // skip escaped char
15
+ i += 2;
16
+ continue;
17
+ }
18
+ if (ch === stringQuote) inString = false;
19
+ i += 1;
20
+ continue;
21
+ }
22
+
23
+ if (ch === '"' || ch === "'") {
24
+ inString = true;
25
+ stringQuote = ch;
26
+ out += ch;
27
+ i += 1;
28
+ continue;
29
+ }
30
+
31
+ // line comment
32
+ if (ch === '/' && input[i + 1] === '/') {
33
+ i += 2;
34
+ while (i < input.length && input[i] !== '\n') i += 1;
35
+ continue;
36
+ }
37
+
38
+ // block comment
39
+ if (ch === '/' && input[i + 1] === '*') {
40
+ i += 2;
41
+ while (i < input.length && !(input[i] === '*' && input[i + 1] === '/')) i += 1;
42
+ i += 2;
43
+ continue;
44
+ }
45
+
46
+ out += ch;
47
+ i += 1;
48
+ }
49
+ return out;
50
+ }
51
+
52
+ export async function readJson(filePath) {
53
+ const raw = await fs.readFile(filePath, 'utf8');
54
+ try {
55
+ return JSON.parse(raw);
56
+ } catch {
57
+ return JSON.parse(stripJsonComments(raw));
58
+ }
59
+ }
60
+
61
+ export async function writeJsonIfChanged(filePath, data) {
62
+ const next = JSON.stringify(data, null, 2) + '\n';
63
+ try {
64
+ const prev = await fs.readFile(filePath, 'utf8');
65
+ if (prev === next) return false;
66
+ } catch {
67
+ // ignore (file missing / unreadable)
68
+ }
69
+ await fs.writeFile(filePath, next, 'utf8');
70
+ return true;
71
+ }
@@ -0,0 +1,15 @@
1
+ export function deriveLibName(packageName) {
2
+ const raw = String(packageName || '').trim();
3
+ if (!raw) throw new Error('Missing packageName');
4
+
5
+ const base = raw.startsWith('@') ? (raw.split('/')[1] || '') : raw;
6
+ const normalized = base
7
+ .toLowerCase()
8
+ .replace(/[^a-z0-9-]+/g, '-')
9
+ .replace(/-+/g, '-')
10
+ .replace(/^-|-$/g, '');
11
+
12
+ if (!normalized) throw new Error(`Invalid packageName: ${packageName}`);
13
+ return normalized;
14
+ }
15
+
@@ -0,0 +1,51 @@
1
+ import process from 'node:process';
2
+
3
+ function color(code, text) {
4
+ if (!process.stdout.isTTY) return text;
5
+ return `\u001b[${code}m${text}\u001b[0m`;
6
+ }
7
+
8
+ function line(prefix, text) {
9
+ return `${prefix} ${text}`;
10
+ }
11
+
12
+ export function createLogger({ debug = false } = {}) {
13
+ const prefix = color('35', 'meridiane');
14
+
15
+ return {
16
+ debugEnabled: debug,
17
+
18
+ title(text) {
19
+ console.log(line('🌙', `${prefix} ${text}`));
20
+ },
21
+
22
+ step(text) {
23
+ console.log(line('🔧', `${prefix} ${text}`));
24
+ },
25
+
26
+ info(text) {
27
+ console.log(line('ℹ️', `${prefix} ${text}`));
28
+ },
29
+
30
+ success(text) {
31
+ console.log(line('✅', `${prefix} ${text}`));
32
+ },
33
+
34
+ warn(text) {
35
+ console.warn(line('⚠️', `${prefix} ${text}`));
36
+ },
37
+
38
+ debug(text, data) {
39
+ if (!debug) return;
40
+ if (data === undefined) console.log(line('🐛', `${prefix} ${text}`));
41
+ else console.log(line('🐛', `${prefix} ${text} ${JSON.stringify(data, null, 2)}`));
42
+ },
43
+
44
+ error(err) {
45
+ const msg = err?.message ?? String(err);
46
+ console.error(line('❌', `${prefix} ${msg}`));
47
+ if (debug && err?.stack) console.error(err.stack);
48
+ },
49
+ };
50
+ }
51
+