@lunora/cli 1.0.0-alpha.2 → 1.0.0-alpha.4
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/dist/bin.mjs +1 -1
- package/dist/index.d.mts +12 -2
- package/dist/index.d.ts +12 -2
- package/dist/index.mjs +2 -2
- package/dist/packem_chunks/handler.mjs +3 -1
- package/dist/packem_chunks/handler16.mjs +4 -2
- package/dist/packem_chunks/runInitCommand.mjs +48 -36
- package/dist/packem_shared/{COMMANDS-1V_KEx35.mjs → COMMANDS-CHw4zOZ9.mjs} +18 -1
- package/dist/packem_shared/{runAddCommand-BZGkRnBs.mjs → commands-SUPdjsu5.mjs} +114 -20
- package/dist/packem_shared/runAddCommand-Txwh1Xw1.mjs +4 -0
- package/package.json +4 -4
package/dist/bin.mjs
CHANGED
package/dist/index.d.mts
CHANGED
|
@@ -572,14 +572,22 @@ interface InitCommandOptions {
|
|
|
572
572
|
* and the auth-provider sub-select.
|
|
573
573
|
*/
|
|
574
574
|
prompt?: Pick<OfferDeps, "multiSelect" | "select">;
|
|
575
|
+
/**
|
|
576
|
+
* Override the git ref (branch, tag, or commit) the default template source
|
|
577
|
+
* is fetched from. Takes precedence over the version-derived ref. Ignored
|
|
578
|
+
* when `source` or `from` is set.
|
|
579
|
+
*/
|
|
580
|
+
ref?: string;
|
|
575
581
|
/** Local registry root for the offer's `runAddCommand` (offline / tests). Mirrors `from` but for registry items. */
|
|
576
582
|
registryFrom?: string;
|
|
577
583
|
/** Override the remote registry source base for the offer (default `gh:anolilab/lunora/registry`). */
|
|
578
584
|
registrySource?: string;
|
|
579
585
|
/**
|
|
580
586
|
* Override the remote source giget downloads from. Default:
|
|
581
|
-
* `gh:anolilab/lunora/templates/<templateType
|
|
582
|
-
*
|
|
587
|
+
* `gh:anolilab/lunora/templates/<templateType>#<ref>`, where `<ref>` is
|
|
588
|
+
* the `ref` option when set, else derived from the CLI version (pre-release
|
|
589
|
+
* channels → their branch, stable → `main`). Tests typically use `from`
|
|
590
|
+
* instead to skip the network.
|
|
583
591
|
*/
|
|
584
592
|
source?: string;
|
|
585
593
|
templateType?: Template;
|
|
@@ -707,6 +715,8 @@ interface AddCommandOptions {
|
|
|
707
715
|
out?: string;
|
|
708
716
|
/** Force-overwrite existing files (take the incoming copy) instead of skipping/conflicting. */
|
|
709
717
|
overwrite?: boolean;
|
|
718
|
+
/** Override the git ref (branch, tag, or commit) items are fetched from (default: version-derived); appended to the `source` base when that is set. Ignored when `from` is set. */
|
|
719
|
+
ref?: string;
|
|
710
720
|
/** Override the remote registry source base (default gh:anolilab/lunora/registry). */
|
|
711
721
|
source?: string;
|
|
712
722
|
/** Skip the package.json mutation confirmation prompt. */
|
package/dist/index.d.ts
CHANGED
|
@@ -572,14 +572,22 @@ interface InitCommandOptions {
|
|
|
572
572
|
* and the auth-provider sub-select.
|
|
573
573
|
*/
|
|
574
574
|
prompt?: Pick<OfferDeps, "multiSelect" | "select">;
|
|
575
|
+
/**
|
|
576
|
+
* Override the git ref (branch, tag, or commit) the default template source
|
|
577
|
+
* is fetched from. Takes precedence over the version-derived ref. Ignored
|
|
578
|
+
* when `source` or `from` is set.
|
|
579
|
+
*/
|
|
580
|
+
ref?: string;
|
|
575
581
|
/** Local registry root for the offer's `runAddCommand` (offline / tests). Mirrors `from` but for registry items. */
|
|
576
582
|
registryFrom?: string;
|
|
577
583
|
/** Override the remote registry source base for the offer (default `gh:anolilab/lunora/registry`). */
|
|
578
584
|
registrySource?: string;
|
|
579
585
|
/**
|
|
580
586
|
* Override the remote source giget downloads from. Default:
|
|
581
|
-
* `gh:anolilab/lunora/templates/<templateType
|
|
582
|
-
*
|
|
587
|
+
* `gh:anolilab/lunora/templates/<templateType>#<ref>`, where `<ref>` is
|
|
588
|
+
* the `ref` option when set, else derived from the CLI version (pre-release
|
|
589
|
+
* channels → their branch, stable → `main`). Tests typically use `from`
|
|
590
|
+
* instead to skip the network.
|
|
583
591
|
*/
|
|
584
592
|
source?: string;
|
|
585
593
|
templateType?: Template;
|
|
@@ -707,6 +715,8 @@ interface AddCommandOptions {
|
|
|
707
715
|
out?: string;
|
|
708
716
|
/** Force-overwrite existing files (take the incoming copy) instead of skipping/conflicting. */
|
|
709
717
|
overwrite?: boolean;
|
|
718
|
+
/** Override the git ref (branch, tag, or commit) items are fetched from (default: version-derived); appended to the `source` base when that is set. Ignored when `from` is set. */
|
|
719
|
+
ref?: string;
|
|
710
720
|
/** Override the remote registry source base (default gh:anolilab/lunora/registry). */
|
|
711
721
|
source?: string;
|
|
712
722
|
/** Skip the package.json mutation confirmation prompt. */
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { COMMANDS, VERSION, runCli } from './packem_shared/COMMANDS-
|
|
1
|
+
export { COMMANDS, VERSION, runCli } from './packem_shared/COMMANDS-CHw4zOZ9.mjs';
|
|
2
2
|
export { runCodegenCommand } from './packem_chunks/runCodegenCommand.mjs';
|
|
3
3
|
export { DEFAULT_IMPORT_BATCH_SIZE, runExportCommand, runImportCommand } from './packem_shared/DEFAULT_IMPORT_BATCH_SIZE-Ck-2bU08.mjs';
|
|
4
4
|
export { runDeployCommand } from './packem_chunks/runDeployCommand.mjs';
|
|
@@ -16,4 +16,4 @@ export { createRecordingSpawner, defaultSpawner } from './packem_shared/defaultS
|
|
|
16
16
|
export { default as parseManifest } from './packem_shared/parseManifest--vZf2FY1.mjs';
|
|
17
17
|
export { REQUIRED_COMPATIBILITY_DATE, REQUIRED_FLAG, validateWranglerProject as validateWrangler, validateWranglerConfig } from '@lunora/config';
|
|
18
18
|
export { buildRegistryIndex } from './packem_shared/buildRegistryIndex-BcYe607_.mjs';
|
|
19
|
-
export { runAddCommand, runBuildIndexCommand, runRegistryViewCommand } from './packem_shared/
|
|
19
|
+
export { r as runAddCommand, a as runBuildIndexCommand, b as runRegistryViewCommand } from './packem_shared/commands-SUPdjsu5.mjs';
|
|
@@ -3,7 +3,7 @@ import { findWranglerFile, promptSelect } from '@lunora/config';
|
|
|
3
3
|
import { join } from '@visulima/path';
|
|
4
4
|
import { d as defineHandler } from '../packem_shared/command-BDXcJCCJ.mjs';
|
|
5
5
|
import { n as normalizeFeature, E as EMAIL_ITEM, D as DEFAULT_AUTH_ITEM, p as promptAuthProvider, A as AUTH_PROVIDER_OPTIONS } from '../packem_shared/features-ocSSpZtS.mjs';
|
|
6
|
-
import { runAddCommand } from '../packem_shared/
|
|
6
|
+
import { r as runAddCommand } from '../packem_shared/commands-SUPdjsu5.mjs';
|
|
7
7
|
|
|
8
8
|
const providerToItem = (provider) => {
|
|
9
9
|
const value = provider.trim().toLowerCase();
|
|
@@ -54,6 +54,7 @@ const runAddFeature = async (options) => {
|
|
|
54
54
|
from: options.from,
|
|
55
55
|
logger: options.logger,
|
|
56
56
|
names: [...items],
|
|
57
|
+
ref: options.ref,
|
|
57
58
|
source: options.source,
|
|
58
59
|
yes: true
|
|
59
60
|
});
|
|
@@ -67,6 +68,7 @@ const execute = defineHandler(async ({ argument, cwd, logger, options }) => {
|
|
|
67
68
|
from: options.from,
|
|
68
69
|
logger,
|
|
69
70
|
provider: options.provider,
|
|
71
|
+
ref: options.ref,
|
|
70
72
|
source: options.source,
|
|
71
73
|
yes: options.yes === true
|
|
72
74
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { d as defineHandler } from '../packem_shared/command-BDXcJCCJ.mjs';
|
|
2
|
-
import { runAddCommand, runRegistryViewCommand, runBuildIndexCommand } from '../packem_shared/
|
|
2
|
+
import { r as runAddCommand, b as runRegistryViewCommand, a as runBuildIndexCommand } from '../packem_shared/commands-SUPdjsu5.mjs';
|
|
3
3
|
|
|
4
4
|
const execute = defineHandler(({ argument, cwd, logger, options }) => {
|
|
5
5
|
const subcommand = argument[0];
|
|
@@ -15,12 +15,13 @@ const execute = defineHandler(({ argument, cwd, logger, options }) => {
|
|
|
15
15
|
logger,
|
|
16
16
|
names,
|
|
17
17
|
overwrite: options.overwrite === true,
|
|
18
|
+
ref: options.ref,
|
|
18
19
|
source: options.source,
|
|
19
20
|
yes: options.yes === true
|
|
20
21
|
});
|
|
21
22
|
}
|
|
22
23
|
if (subcommand === "list") {
|
|
23
|
-
return runAddCommand({ cwd, from: options.from, json: options.json === true, list: true, logger, names: [], source: options.source });
|
|
24
|
+
return runAddCommand({ cwd, from: options.from, json: options.json === true, list: true, logger, names: [], ref: options.ref, source: options.source });
|
|
24
25
|
}
|
|
25
26
|
if (subcommand === "view") {
|
|
26
27
|
return runRegistryViewCommand({
|
|
@@ -28,6 +29,7 @@ const execute = defineHandler(({ argument, cwd, logger, options }) => {
|
|
|
28
29
|
from: options.from,
|
|
29
30
|
logger,
|
|
30
31
|
names,
|
|
32
|
+
ref: options.ref,
|
|
31
33
|
source: options.source
|
|
32
34
|
});
|
|
33
35
|
}
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, writeFileSync, readdirSync, mkdtempSync, rmSync, readFileSync } from 'node:fs';
|
|
2
2
|
import { tmpdir } from 'node:os';
|
|
3
|
-
import { fileURLToPath } from 'node:url';
|
|
4
3
|
import { detectFramework as detectFramework$1, isInteractive, promptSelect, promptMultiSelect } from '@lunora/config';
|
|
5
4
|
import { walkSync } from '@visulima/fs';
|
|
6
|
-
import { resolve, join as join$1, relative, dirname as dirname$1 } from '@visulima/path';
|
|
5
|
+
import { resolve, join as join$1, relative, dirname as dirname$1, basename } from '@visulima/path';
|
|
7
6
|
import { downloadTemplate } from 'giget';
|
|
7
|
+
import { modify, applyEdits } from 'jsonc-parser';
|
|
8
8
|
import { join, dirname } from 'node:path';
|
|
9
9
|
import { d as defineHandler } from '../packem_shared/command-BDXcJCCJ.mjs';
|
|
10
10
|
import MagicString from 'magic-string';
|
|
11
11
|
import { Project, SyntaxKind } from 'ts-morph';
|
|
12
|
+
import { c as resolveSourceRef, d as resolveDistTag, r as runAddCommand } from '../packem_shared/commands-SUPdjsu5.mjs';
|
|
12
13
|
import { p as promptAuthProvider, E as EMAIL_ITEM } from '../packem_shared/features-ocSSpZtS.mjs';
|
|
13
|
-
import { runAddCommand } from '../packem_shared/runAddCommand-BZGkRnBs.mjs';
|
|
14
14
|
|
|
15
15
|
const GITHUB_CONTENT = `name: Deploy
|
|
16
16
|
|
|
@@ -20,6 +20,10 @@ on:
|
|
|
20
20
|
pull_request:
|
|
21
21
|
workflow_dispatch:
|
|
22
22
|
|
|
23
|
+
# Prerequisite: commit your pnpm-lock.yaml. \`pnpm install --frozen-lockfile\`
|
|
24
|
+
# (below) and the pnpm cache both require it — run \`pnpm install\` locally and
|
|
25
|
+
# commit the lockfile before pushing, or the first CI run fails.
|
|
26
|
+
#
|
|
23
27
|
# Set these repository secrets (Settings → Secrets and variables → Actions):
|
|
24
28
|
# CLOUDFLARE_API_TOKEN — a Workers-scoped API token
|
|
25
29
|
# CLOUDFLARE_ACCOUNT_ID — your Cloudflare account id
|
|
@@ -66,6 +70,10 @@ jobs:
|
|
|
66
70
|
const GITLAB_CONTENT = `stages:
|
|
67
71
|
- deploy
|
|
68
72
|
|
|
73
|
+
# Prerequisite: commit your pnpm-lock.yaml. \`pnpm install --frozen-lockfile\`
|
|
74
|
+
# (below) requires it — run \`pnpm install\` locally and commit the lockfile
|
|
75
|
+
# before pushing, or the first pipeline fails.
|
|
76
|
+
#
|
|
69
77
|
# Set these as masked CI/CD variables (Settings → CI/CD → Variables):
|
|
70
78
|
# CLOUDFLARE_API_TOKEN — a Workers-scoped API token
|
|
71
79
|
# CLOUDFLARE_ACCOUNT_ID — your Cloudflare account id
|
|
@@ -127,6 +135,9 @@ const scaffoldCiWorkflow = (projectRoot, provider, logger, options = {}) => {
|
|
|
127
135
|
} else {
|
|
128
136
|
logger.success(`--ci ${provider}: wrote ${spec.file}`);
|
|
129
137
|
logger.info(`--ci ${provider}: set CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID as ${spec.secretsHint} to enable deploys.`);
|
|
138
|
+
logger.info(
|
|
139
|
+
`--ci ${provider}: run \`pnpm install\` and commit pnpm-lock.yaml before pushing — the pipeline runs \`pnpm install --frozen-lockfile\`.`
|
|
140
|
+
);
|
|
130
141
|
}
|
|
131
142
|
return result;
|
|
132
143
|
} catch (error) {
|
|
@@ -324,32 +335,6 @@ export const send = mutation
|
|
|
324
335
|
});
|
|
325
336
|
`;
|
|
326
337
|
const DEFAULT_SOURCE_BASE = "gh:anolilab/lunora/templates";
|
|
327
|
-
const DEFAULT_SOURCE_REF_FALLBACK = "alpha";
|
|
328
|
-
const resolveCliVersion = () => {
|
|
329
|
-
try {
|
|
330
|
-
let directory = dirname$1(fileURLToPath(import.meta.url));
|
|
331
|
-
for (let index = 0; index < 5; index += 1) {
|
|
332
|
-
const candidate = join$1(directory, "package.json");
|
|
333
|
-
if (existsSync(candidate)) {
|
|
334
|
-
const parsed = JSON.parse(readFileSync(candidate, "utf8"));
|
|
335
|
-
if (parsed.name === "@lunora/cli" && typeof parsed.version === "string") {
|
|
336
|
-
return parsed.version;
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
const parent = dirname$1(directory);
|
|
340
|
-
if (parent === directory) {
|
|
341
|
-
break;
|
|
342
|
-
}
|
|
343
|
-
directory = parent;
|
|
344
|
-
}
|
|
345
|
-
} catch {
|
|
346
|
-
}
|
|
347
|
-
return "0.0.0";
|
|
348
|
-
};
|
|
349
|
-
const resolveDefaultSourceRef = () => {
|
|
350
|
-
const version = resolveCliVersion();
|
|
351
|
-
return version === "0.0.0" ? DEFAULT_SOURCE_REF_FALLBACK : `v${version}`;
|
|
352
|
-
};
|
|
353
338
|
const isTextFile = (filePath) => {
|
|
354
339
|
const lastDot = filePath.lastIndexOf(".");
|
|
355
340
|
if (lastDot === -1) {
|
|
@@ -358,6 +343,26 @@ const isTextFile = (filePath) => {
|
|
|
358
343
|
return TEXT_EXTENSIONS.has(filePath.slice(lastDot));
|
|
359
344
|
};
|
|
360
345
|
const substitute = (content, name) => content.replaceAll("{{name}}", name);
|
|
346
|
+
const isLunoraDep = (name) => name === "lunorash" || name.startsWith("@lunora/");
|
|
347
|
+
const stampLunoraDeps = (packageJsonText, distTag) => {
|
|
348
|
+
let parsed;
|
|
349
|
+
try {
|
|
350
|
+
parsed = JSON.parse(packageJsonText);
|
|
351
|
+
} catch {
|
|
352
|
+
return packageJsonText;
|
|
353
|
+
}
|
|
354
|
+
let text = packageJsonText;
|
|
355
|
+
for (const section of ["dependencies", "devDependencies"]) {
|
|
356
|
+
for (const name of Object.keys(parsed[section] ?? {})) {
|
|
357
|
+
if (!isLunoraDep(name)) {
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
const edits = modify(text, [section, name], distTag, { formattingOptions: { insertSpaces: true, tabSize: 4 } });
|
|
361
|
+
text = applyEdits(text, edits);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
return text;
|
|
365
|
+
};
|
|
361
366
|
const collectFiles = (directory) => {
|
|
362
367
|
const out = [];
|
|
363
368
|
for (const entry of walkSync(directory, { includeDirs: false, includeFiles: true })) {
|
|
@@ -368,12 +373,16 @@ const collectFiles = (directory) => {
|
|
|
368
373
|
const copyTemplate = (sourceDirectory, target, name) => {
|
|
369
374
|
const files = collectFiles(sourceDirectory);
|
|
370
375
|
const written = [];
|
|
376
|
+
const distTag = resolveDistTag();
|
|
371
377
|
for (const source of files) {
|
|
372
378
|
const relativePath = relative(sourceDirectory, source);
|
|
373
379
|
const destination = join$1(target, relativePath);
|
|
374
380
|
mkdirSync(dirname$1(destination), { recursive: true });
|
|
375
381
|
const raw = readFileSync(source);
|
|
376
|
-
|
|
382
|
+
let text = isTextFile(source) ? substitute(raw.toString("utf8"), name) : void 0;
|
|
383
|
+
if (text !== void 0 && basename(source) === "package.json") {
|
|
384
|
+
text = stampLunoraDeps(text, distTag);
|
|
385
|
+
}
|
|
377
386
|
if (text === void 0) {
|
|
378
387
|
writeFileSync(destination, raw);
|
|
379
388
|
} else {
|
|
@@ -383,11 +392,11 @@ const copyTemplate = (sourceDirectory, target, name) => {
|
|
|
383
392
|
}
|
|
384
393
|
return written;
|
|
385
394
|
};
|
|
386
|
-
const resolveTemplateSource = (templateType, source) => {
|
|
395
|
+
const resolveTemplateSource = (templateType, source, ref) => {
|
|
387
396
|
if (source !== void 0 && source.length > 0) {
|
|
388
397
|
return source;
|
|
389
398
|
}
|
|
390
|
-
return `${DEFAULT_SOURCE_BASE}/${templateType}#${
|
|
399
|
+
return `${DEFAULT_SOURCE_BASE}/${templateType}#${resolveSourceRef(ref)}`;
|
|
391
400
|
};
|
|
392
401
|
const isSafeSource = (source) => {
|
|
393
402
|
if (source.includes("..")) {
|
|
@@ -412,11 +421,12 @@ const scaffoldFromLocal = (fromRoot, templateType, target, name, logger) => {
|
|
|
412
421
|
logScaffoldSuccess(logger, written, target, name);
|
|
413
422
|
return { code: 0, files: written, target };
|
|
414
423
|
};
|
|
415
|
-
const scaffoldFromRemote = async (
|
|
424
|
+
const scaffoldFromRemote = async (options) => {
|
|
425
|
+
const { logger, name, ref, source, target, templateType } = options;
|
|
416
426
|
const stagingRoot = mkdtempSync(join$1(tmpdir(), "lunora-init-fetch-"));
|
|
417
427
|
const stagingDirectory = join$1(stagingRoot, "template");
|
|
418
428
|
try {
|
|
419
|
-
const remote = resolveTemplateSource(templateType, source);
|
|
429
|
+
const remote = resolveTemplateSource(templateType, source, ref);
|
|
420
430
|
logger.info(`fetching template from ${remote}`);
|
|
421
431
|
const downloaded = await downloadTemplate(remote, {
|
|
422
432
|
cwd: stagingRoot,
|
|
@@ -566,6 +576,7 @@ const maybeOfferExtras = async (options, projectDirectory) => {
|
|
|
566
576
|
from: options.registryFrom,
|
|
567
577
|
logger: options.logger,
|
|
568
578
|
names: [...names],
|
|
579
|
+
ref: options.ref,
|
|
569
580
|
source: options.registrySource,
|
|
570
581
|
yes: true
|
|
571
582
|
});
|
|
@@ -607,7 +618,7 @@ const scaffoldNewProject = async (options, cwd) => {
|
|
|
607
618
|
);
|
|
608
619
|
return { code: 1, files: [], target };
|
|
609
620
|
}
|
|
610
|
-
return scaffoldFromRemote(options.
|
|
621
|
+
return scaffoldFromRemote({ logger: options.logger, name, ref: options.ref, source: options.source, target, templateType });
|
|
611
622
|
};
|
|
612
623
|
const runInitCommand = async (options) => {
|
|
613
624
|
const cwd = options.cwd ?? process.cwd();
|
|
@@ -643,10 +654,11 @@ const execute = defineHandler(({ argument, cwd, logger, options }) => {
|
|
|
643
654
|
interactive: options.interactive === true ? true : void 0,
|
|
644
655
|
logger,
|
|
645
656
|
name: argument[0],
|
|
657
|
+
ref: options.ref,
|
|
646
658
|
source: options.source,
|
|
647
659
|
templateType: template,
|
|
648
660
|
yes: options.yes === true
|
|
649
661
|
});
|
|
650
662
|
});
|
|
651
663
|
|
|
652
|
-
export { execute, isTemplate, runInitCommand };
|
|
664
|
+
export { execute, isTemplate, resolveTemplateSource, runInitCommand };
|
|
@@ -15,7 +15,8 @@ const addCommand = {
|
|
|
15
15
|
["lunora add auth --provider clerk", "Add Clerk auth without prompting"],
|
|
16
16
|
["lunora add email", "Add transactional email (Cloudflare Email Workers + dev mail catcher)"],
|
|
17
17
|
["lunora add storage", "Add the R2 storage registry item"],
|
|
18
|
-
["lunora add crons", "Add the scheduled-jobs registry item"]
|
|
18
|
+
["lunora add crons", "Add the scheduled-jobs registry item"],
|
|
19
|
+
["lunora add storage --ref alpha", "Add an item from the alpha branch's registry"]
|
|
19
20
|
],
|
|
20
21
|
group: "Project",
|
|
21
22
|
loader: () => import('../packem_chunks/handler.mjs').then((m) => {
|
|
@@ -27,6 +28,11 @@ const addCommand = {
|
|
|
27
28
|
{ description: "Skip the provider prompt and use the default (email & password)", name: "yes", type: Boolean },
|
|
28
29
|
{ description: "Local registry root (offline; expects <name>/ subdirs)", name: "from", type: String },
|
|
29
30
|
{ description: "Override the remote registry source base (e.g. gh:owner/repo/registry)", name: "source", type: String },
|
|
31
|
+
{
|
|
32
|
+
description: "Fetch items from a git ref (branch, tag, or commit), e.g. --ref alpha. Overrides the version-derived default",
|
|
33
|
+
name: "ref",
|
|
34
|
+
type: String
|
|
35
|
+
},
|
|
30
36
|
{ description: "Permit --source values outside gh:/github:/https://", name: "allow-unsafe-source", type: Boolean }
|
|
31
37
|
]
|
|
32
38
|
};
|
|
@@ -342,6 +348,7 @@ const initCommand = {
|
|
|
342
348
|
["lunora init my-app", "Scaffold with the default (vite) template"],
|
|
343
349
|
["lunora init my-app -t tanstack-start-react", "Scaffold a TanStack Start (React) app"],
|
|
344
350
|
["lunora init my-app -t tanstack-start-solid", "Scaffold a TanStack Start (Solid) app"],
|
|
351
|
+
["lunora init my-app --ref alpha", "Scaffold from the alpha branch's templates"],
|
|
345
352
|
["lunora init --here", "Add Lunora to the current project"],
|
|
346
353
|
["lunora init my-app --ci github", "Scaffold + add a GitHub Actions deploy pipeline"],
|
|
347
354
|
["lunora init my-app --ci gitlab", "Scaffold + add a GitLab CI deploy pipeline"]
|
|
@@ -369,6 +376,11 @@ const initCommand = {
|
|
|
369
376
|
name: "source",
|
|
370
377
|
type: String
|
|
371
378
|
},
|
|
379
|
+
{
|
|
380
|
+
description: "Fetch templates from a git ref (branch, tag, or commit), e.g. --ref alpha. Overrides the version-derived default",
|
|
381
|
+
name: "ref",
|
|
382
|
+
type: String
|
|
383
|
+
},
|
|
372
384
|
{
|
|
373
385
|
description: "Permit --source values outside gh:/github:/https:// (e.g. local file://)",
|
|
374
386
|
name: "allow-unsafe-source",
|
|
@@ -541,6 +553,11 @@ const registryCommand = {
|
|
|
541
553
|
{ description: "add: skip the package.json mutation confirmation prompt", name: "yes", type: Boolean },
|
|
542
554
|
{ description: "Local registry root (offline; expects <name>/ subdirs)", name: "from", type: String },
|
|
543
555
|
{ description: "Override the remote registry source base (e.g. gh:owner/repo/registry)", name: "source", type: String },
|
|
556
|
+
{
|
|
557
|
+
description: "Fetch items from a git ref (branch, tag, or commit), e.g. --ref alpha. Overrides the version-derived default",
|
|
558
|
+
name: "ref",
|
|
559
|
+
type: String
|
|
560
|
+
},
|
|
544
561
|
{ description: "Permit --source values outside gh:/github:/https://", name: "allow-unsafe-source", type: Boolean },
|
|
545
562
|
{ description: "Emit JSON output (add plan / list)", name: "json", type: Boolean },
|
|
546
563
|
{ description: "build: output path for the catalog (default <root>/index.json)", name: "out", type: String },
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { existsSync, readFileSync, writeFileSync, mkdirSync, mkdtempSync, rmSync } from 'node:fs';
|
|
2
|
-
import {
|
|
2
|
+
import { dirname, join } from '@visulima/path';
|
|
3
3
|
import { DEV_VARS_FILE, parseDevVariableEntries, promptYesNo } from '@lunora/config';
|
|
4
4
|
import { modify, applyEdits, parse } from 'jsonc-parser';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
5
6
|
import { collectCatalog, buildRegistryIndex } from './buildRegistryIndex-BcYe607_.mjs';
|
|
6
7
|
import { insertSchemaExtension } from './insertSchemaExtension-BuzF6-t2.mjs';
|
|
7
8
|
import { createHash } from 'node:crypto';
|
|
@@ -9,7 +10,87 @@ import { tmpdir } from 'node:os';
|
|
|
9
10
|
import { downloadTemplate } from 'giget';
|
|
10
11
|
import parseManifest from './parseManifest--vZf2FY1.mjs';
|
|
11
12
|
|
|
12
|
-
const
|
|
13
|
+
const DEFAULT_SOURCE_REF_FALLBACK = "alpha";
|
|
14
|
+
const STABLE_BRANCH = "main";
|
|
15
|
+
const PRERELEASE_CHANNEL_BRANCHES = /* @__PURE__ */ new Set(["alpha", "beta", "next"]);
|
|
16
|
+
const SAFE_REF = /^[\w./@-]+$/;
|
|
17
|
+
const isSafeRef = (ref) => !ref.includes("..") && SAFE_REF.test(ref);
|
|
18
|
+
const resolveCliVersion = () => {
|
|
19
|
+
try {
|
|
20
|
+
let directory = dirname(fileURLToPath(import.meta.url));
|
|
21
|
+
for (let index = 0; index < 6; index += 1) {
|
|
22
|
+
const candidate = join(directory, "package.json");
|
|
23
|
+
if (existsSync(candidate)) {
|
|
24
|
+
const parsed = JSON.parse(readFileSync(candidate, "utf8"));
|
|
25
|
+
if (parsed.name === "@lunora/cli" && typeof parsed.version === "string") {
|
|
26
|
+
return parsed.version;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const parent = dirname(directory);
|
|
30
|
+
if (parent === directory) {
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
directory = parent;
|
|
34
|
+
}
|
|
35
|
+
} catch {
|
|
36
|
+
}
|
|
37
|
+
return "0.0.0";
|
|
38
|
+
};
|
|
39
|
+
const resolveVersionRef = (version) => {
|
|
40
|
+
if (version === "0.0.0") {
|
|
41
|
+
return DEFAULT_SOURCE_REF_FALLBACK;
|
|
42
|
+
}
|
|
43
|
+
const core = version.split("+")[0] ?? version;
|
|
44
|
+
const dashIndex = core.indexOf("-");
|
|
45
|
+
if (dashIndex !== -1) {
|
|
46
|
+
const [channel] = core.slice(dashIndex + 1).split(".");
|
|
47
|
+
if (channel !== void 0 && PRERELEASE_CHANNEL_BRANCHES.has(channel)) {
|
|
48
|
+
return channel;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return STABLE_BRANCH;
|
|
52
|
+
};
|
|
53
|
+
const resolveSourceRef = (ref) => {
|
|
54
|
+
if (ref !== void 0 && ref.length > 0) {
|
|
55
|
+
if (!isSafeRef(ref)) {
|
|
56
|
+
throw new Error(`invalid --ref "${ref}" — a ref may contain letters, digits, ".", "_", "-", "/", "@" and must not contain "..".`);
|
|
57
|
+
}
|
|
58
|
+
return ref;
|
|
59
|
+
}
|
|
60
|
+
return resolveVersionRef(resolveCliVersion());
|
|
61
|
+
};
|
|
62
|
+
const STABLE_DIST_TAG = "latest";
|
|
63
|
+
const resolveDistTag = () => {
|
|
64
|
+
const ref = resolveVersionRef(resolveCliVersion());
|
|
65
|
+
return ref === STABLE_BRANCH ? STABLE_DIST_TAG : ref;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const resolveDepRange = (range) => {
|
|
69
|
+
if (!range.startsWith("workspace:")) {
|
|
70
|
+
return range;
|
|
71
|
+
}
|
|
72
|
+
const rest = range.slice("workspace:".length);
|
|
73
|
+
if (rest === "" || rest === "*" || rest === "^" || rest === "~") {
|
|
74
|
+
return resolveDistTag();
|
|
75
|
+
}
|
|
76
|
+
return rest;
|
|
77
|
+
};
|
|
78
|
+
const UMBRELLA_REEXPORTED_DEPS = /* @__PURE__ */ new Set(["@lunora/client", "@lunora/do", "@lunora/runtime", "@lunora/server", "@lunora/values"]);
|
|
79
|
+
const UMBRELLA_IMPORT_RE = /(['"])@lunora\/(client|do|runtime|server|values)(\/[^'"]*)?\1/gu;
|
|
80
|
+
const projectUsesUmbrella = (projectRoot) => {
|
|
81
|
+
const packageJsonPath = join(projectRoot, "package.json");
|
|
82
|
+
if (!existsSync(packageJsonPath)) {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
try {
|
|
86
|
+
const parsed = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
87
|
+
return parsed.dependencies?.lunorash !== void 0 || parsed.devDependencies?.lunorash !== void 0;
|
|
88
|
+
} catch {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
const rewriteUmbrellaImports = (source) => source.replaceAll(UMBRELLA_IMPORT_RE, (_match, quote, base, subpath) => `${quote}lunorash/${base}${subpath ?? ""}${quote}`);
|
|
93
|
+
const applyDeps = (deps, projectRoot, logger, section = "dependencies", useUmbrella = false) => {
|
|
13
94
|
const entries = Object.entries(deps);
|
|
14
95
|
if (entries.length === 0) {
|
|
15
96
|
return [];
|
|
@@ -23,11 +104,15 @@ const applyDeps = (deps, projectRoot, logger, section = "dependencies") => {
|
|
|
23
104
|
const parsed = JSON.parse(text);
|
|
24
105
|
const added = [];
|
|
25
106
|
for (const [name, range] of entries) {
|
|
107
|
+
if (useUmbrella && UMBRELLA_REEXPORTED_DEPS.has(name)) {
|
|
108
|
+
logger.info(`dep provided by the lunorash umbrella, skipping: ${name}`);
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
26
111
|
if (parsed.dependencies?.[name] !== void 0 || parsed.devDependencies?.[name] !== void 0) {
|
|
27
112
|
logger.info(`dep already present: ${name}`);
|
|
28
113
|
continue;
|
|
29
114
|
}
|
|
30
|
-
const edits = modify(text, [section, name], range, {
|
|
115
|
+
const edits = modify(text, [section, name], resolveDepRange(range), {
|
|
31
116
|
formattingOptions: { insertSpaces: true, tabSize: 4 }
|
|
32
117
|
});
|
|
33
118
|
text = applyEdits(text, edits);
|
|
@@ -148,14 +233,14 @@ const applyBindings = (bindings, projectRoot, logger) => {
|
|
|
148
233
|
}
|
|
149
234
|
return applied;
|
|
150
235
|
};
|
|
151
|
-
const applyItemResources = (manifest, cwd, logger) => {
|
|
236
|
+
const applyItemResources = (manifest, cwd, logger, useUmbrella = false) => {
|
|
152
237
|
const deps = [];
|
|
153
238
|
const bindings = [];
|
|
154
239
|
if (manifest.deps) {
|
|
155
|
-
deps.push(...applyDeps(manifest.deps, cwd, logger));
|
|
240
|
+
deps.push(...applyDeps(manifest.deps, cwd, logger, "dependencies", useUmbrella));
|
|
156
241
|
}
|
|
157
242
|
if (manifest.devDependencies) {
|
|
158
|
-
deps.push(...applyDeps(manifest.devDependencies, cwd, logger, "devDependencies"));
|
|
243
|
+
deps.push(...applyDeps(manifest.devDependencies, cwd, logger, "devDependencies", useUmbrella));
|
|
159
244
|
}
|
|
160
245
|
if (manifest.bindings) {
|
|
161
246
|
bindings.push(...applyBindings(manifest.bindings, cwd, logger));
|
|
@@ -267,7 +352,12 @@ const renderDiff = (oldText, newText) => {
|
|
|
267
352
|
return out;
|
|
268
353
|
};
|
|
269
354
|
|
|
270
|
-
const
|
|
355
|
+
const CODE_FILE_RE = /\.[cm]?[jt]sx?$/u;
|
|
356
|
+
const readItemFile = (itemDirectory, file, useUmbrella) => {
|
|
357
|
+
const source = readFileSync(join(itemDirectory, file.from), "utf8");
|
|
358
|
+
return useUmbrella && CODE_FILE_RE.test(file.to) ? rewriteUmbrellaImports(source) : source;
|
|
359
|
+
};
|
|
360
|
+
const reconcileSchemaExtension = (file, itemKey, itemDirectory, projectRoot, logger, diff, useUmbrella) => {
|
|
271
361
|
const schemaPath = join(projectRoot, "lunora", "schema.ts");
|
|
272
362
|
if (diff) {
|
|
273
363
|
logger.info(`~ would merge .extend(${itemKey}.extension) into lunora/schema.ts (and create ${file.to} if absent)`);
|
|
@@ -276,9 +366,13 @@ const reconcileSchemaExtension = (file, itemKey, itemDirectory, projectRoot, log
|
|
|
276
366
|
const destinationPath = join(projectRoot, file.to);
|
|
277
367
|
if (!existsSync(destinationPath)) {
|
|
278
368
|
mkdirSync(dirname(destinationPath), { recursive: true });
|
|
279
|
-
writeFileSync(destinationPath,
|
|
369
|
+
writeFileSync(destinationPath, readItemFile(itemDirectory, file, useUmbrella), "utf8");
|
|
280
370
|
}
|
|
281
|
-
const
|
|
371
|
+
const baseModule = useUmbrella ? "lunorash/server" : "@lunora/server";
|
|
372
|
+
const existingSchema = existsSync(schemaPath) ? readFileSync(schemaPath, "utf8") : `import { defineSchema } from "${baseModule}";
|
|
373
|
+
|
|
374
|
+
export const schema = defineSchema({});
|
|
375
|
+
`;
|
|
282
376
|
const result = insertSchemaExtension(existingSchema, itemKey);
|
|
283
377
|
if (result.ok) {
|
|
284
378
|
mkdirSync(dirname(schemaPath), { recursive: true });
|
|
@@ -308,9 +402,9 @@ const previewWholeFile = (file, current, incoming, exists, logger) => {
|
|
|
308
402
|
logger.info(` ${line}`);
|
|
309
403
|
}
|
|
310
404
|
};
|
|
311
|
-
const reconcileWholeFile = (file, itemKey, itemDirectory, projectRoot, logger, lock, reconcileOptions) => {
|
|
405
|
+
const reconcileWholeFile = (file, itemKey, itemDirectory, projectRoot, logger, lock, reconcileOptions, useUmbrella) => {
|
|
312
406
|
const destinationPath = join(projectRoot, file.to);
|
|
313
|
-
const incoming =
|
|
407
|
+
const incoming = readItemFile(itemDirectory, file, useUmbrella);
|
|
314
408
|
const exists = existsSync(destinationPath);
|
|
315
409
|
const current = exists ? readFileSync(destinationPath, "utf8") : "";
|
|
316
410
|
const write = (message) => {
|
|
@@ -348,11 +442,11 @@ const reconcileWholeFile = (file, itemKey, itemDirectory, projectRoot, logger, l
|
|
|
348
442
|
logger.warn(`conflict: ${file.to} has local edits and an upstream update — wrote ${file.to}.new (use --overwrite to take theirs)`);
|
|
349
443
|
return { kind: "skipped", path: destinationPath };
|
|
350
444
|
};
|
|
351
|
-
const reconcileFile = (file, itemKey, itemDirectory, projectRoot, logger, lock, reconcileOptions = {}) => {
|
|
445
|
+
const reconcileFile = (file, itemKey, itemDirectory, projectRoot, logger, lock, reconcileOptions = {}, useUmbrella = false) => {
|
|
352
446
|
if (file.merge === "schema-extension") {
|
|
353
|
-
return reconcileSchemaExtension(file, itemKey, itemDirectory, projectRoot, logger, reconcileOptions.diff === true);
|
|
447
|
+
return reconcileSchemaExtension(file, itemKey, itemDirectory, projectRoot, logger, reconcileOptions.diff === true, useUmbrella);
|
|
354
448
|
}
|
|
355
|
-
return reconcileWholeFile(file, itemKey, itemDirectory, projectRoot, logger, lock, reconcileOptions);
|
|
449
|
+
return reconcileWholeFile(file, itemKey, itemDirectory, projectRoot, logger, lock, reconcileOptions, useUmbrella);
|
|
356
450
|
};
|
|
357
451
|
const reconcileItems = (items, cwd, logger, reconcileOptions = {}) => {
|
|
358
452
|
const written = [];
|
|
@@ -360,15 +454,16 @@ const reconcileItems = (items, cwd, logger, reconcileOptions = {}) => {
|
|
|
360
454
|
const depsAdded = [];
|
|
361
455
|
const bindingsApplied = [];
|
|
362
456
|
const lock = readLock(cwd);
|
|
457
|
+
const useUmbrella = projectUsesUmbrella(cwd);
|
|
363
458
|
for (const { directory, manifest } of items) {
|
|
364
459
|
for (const file of manifest.files) {
|
|
365
|
-
const outcome = reconcileFile(file, manifest.name, directory, cwd, logger, lock, reconcileOptions);
|
|
460
|
+
const outcome = reconcileFile(file, manifest.name, directory, cwd, logger, lock, reconcileOptions, useUmbrella);
|
|
366
461
|
(outcome.kind === "written" ? written : skipped).push(outcome.path);
|
|
367
462
|
}
|
|
368
463
|
if (reconcileOptions.diff) {
|
|
369
464
|
continue;
|
|
370
465
|
}
|
|
371
|
-
const applied = applyItemResources(manifest, cwd, logger);
|
|
466
|
+
const applied = applyItemResources(manifest, cwd, logger, useUmbrella);
|
|
372
467
|
depsAdded.push(...applied.deps);
|
|
373
468
|
bindingsApplied.push(...applied.bindings);
|
|
374
469
|
}
|
|
@@ -379,7 +474,6 @@ const reconcileItems = (items, cwd, logger, reconcileOptions = {}) => {
|
|
|
379
474
|
};
|
|
380
475
|
|
|
381
476
|
const DEFAULT_SOURCE_BASE = "gh:anolilab/lunora/registry";
|
|
382
|
-
const DEFAULT_SOURCE_REF = "alpha";
|
|
383
477
|
const VALID_ITEM_NAME = /^[A-Za-z0-9][\w-]*$/u;
|
|
384
478
|
const assertSafeItemName = (name) => {
|
|
385
479
|
if (!VALID_ITEM_NAME.test(name)) {
|
|
@@ -431,7 +525,7 @@ const resolveItemDirectory = async (name, options) => {
|
|
|
431
525
|
}, directory };
|
|
432
526
|
}
|
|
433
527
|
const base = options.source ?? DEFAULT_SOURCE_BASE;
|
|
434
|
-
return fetchToStaging(`${base}/${name}#${
|
|
528
|
+
return fetchToStaging(`${base}/${name}#${resolveSourceRef(options.ref)}`, "item", options.logger);
|
|
435
529
|
};
|
|
436
530
|
const resolveRegistryRoot = async (options) => {
|
|
437
531
|
if (options.from !== void 0) {
|
|
@@ -442,7 +536,7 @@ const resolveRegistryRoot = async (options) => {
|
|
|
442
536
|
}, root: options.from };
|
|
443
537
|
}
|
|
444
538
|
const base = options.source ?? DEFAULT_SOURCE_BASE;
|
|
445
|
-
const { cleanup, directory } = await fetchToStaging(`${base}#${
|
|
539
|
+
const { cleanup, directory } = await fetchToStaging(`${base}#${resolveSourceRef(options.ref)}`, "registry", options.logger);
|
|
446
540
|
return { cleanup, root: directory };
|
|
447
541
|
};
|
|
448
542
|
const readManifest = (itemDirectory, name) => {
|
|
@@ -690,4 +784,4 @@ const runBuildIndexCommand = async (options) => {
|
|
|
690
784
|
return empty;
|
|
691
785
|
};
|
|
692
786
|
|
|
693
|
-
export {
|
|
787
|
+
export { runBuildIndexCommand as a, runRegistryViewCommand as b, resolveSourceRef as c, resolveDistTag as d, runListCommand as e, runAddCommand as r };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lunora/cli",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.4",
|
|
4
4
|
"description": "The Lunora CLI: init, dev, deploy, codegen, run, reset, and migrate commands",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent-skills",
|
|
@@ -53,12 +53,12 @@
|
|
|
53
53
|
"dependencies": {
|
|
54
54
|
"@bomb.sh/tab": "0.0.17",
|
|
55
55
|
"@lunora/codegen": "1.0.0-alpha.2",
|
|
56
|
-
"@lunora/config": "1.0.0-alpha.
|
|
56
|
+
"@lunora/config": "1.0.0-alpha.3",
|
|
57
57
|
"@lunora/container": "1.0.0-alpha.1",
|
|
58
|
-
"@lunora/d1": "1.0.0-alpha.
|
|
58
|
+
"@lunora/d1": "1.0.0-alpha.3",
|
|
59
59
|
"@lunora/seed": "1.0.0-alpha.1",
|
|
60
60
|
"@lunora/server": "1.0.0-alpha.1",
|
|
61
|
-
"@lunora/studio": "1.0.0-alpha.
|
|
61
|
+
"@lunora/studio": "1.0.0-alpha.2",
|
|
62
62
|
"@lunora/values": "1.0.0-alpha.1",
|
|
63
63
|
"@visulima/cerebro": "3.0.0-alpha.32",
|
|
64
64
|
"@visulima/fs": "5.0.0-alpha.32",
|