112 lines
8.5 KiB
JavaScript
112 lines
8.5 KiB
JavaScript
import {test} from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import {mkdtemp,mkdir,writeFile,readFile,realpath,rm} from 'node:fs/promises';
|
|
import {tmpdir} from 'node:os';
|
|
import {join} from 'node:path';
|
|
import {spawnSync} from 'node:child_process';
|
|
import ts from 'typescript';
|
|
import vm from 'node:vm';
|
|
import {nextCommand,runNext} from '../scripts/next-env.mjs';
|
|
|
|
async function fixture(t){
|
|
const root=await mkdtemp(join(tmpdir(),'91-environments-'));t.after(()=>rm(root,{recursive:true,force:true}));
|
|
await mkdir(join(root,'config'));
|
|
for(const environment of ['development','test','production'])await writeFile(join(root,'config',environment+'.env'),`API_BASE_URL=https://${environment}.api.test\nPORT=21021\nNEXT_PUBLIC_TRACKER_APP_ID=1047\n`);
|
|
return root;
|
|
}
|
|
|
|
test('environment defaults, personal overrides and process values use the reference precedence without changing NODE_ENV semantics',async t=>{
|
|
const root=await fixture(t);
|
|
await writeFile(join(root,'config/test.local.env'),'API_BASE_URL=https://override.api.test\nPORT=21022\nLOCAL_MARKER=yes\n');
|
|
const base=nextCommand('test','dev',[],{root,variables:{}});
|
|
assert.equal(base.env.API_BASE_URL,'https://override.api.test');assert.equal(base.env.PORT,'21022');assert.equal(base.env.LOCAL_MARKER,'yes');
|
|
assert.equal(base.env.APP_ENV,'test');assert.equal(base.env.NODE_ENV,'development');assert.equal(base.env.APP_DIST_DIR,'.next-test');
|
|
const processOverride=nextCommand('test','start',[],{root,variables:{API_BASE_URL:'https://process.api.test/',PORT:'31021',APP_ENV:'production',NODE_ENV:'test',APP_DIST_DIR:'outside'}});
|
|
assert.equal(processOverride.env.API_BASE_URL,'https://process.api.test');assert.equal(processOverride.env.PORT,'31021');
|
|
assert.equal(processOverride.env.APP_ENV,'test');assert.equal(processOverride.env.NODE_ENV,'production');assert.equal(processOverride.env.APP_DIST_DIR,'.next-test');
|
|
});
|
|
|
|
test('development supports legacy .env.local while test and production isolate local overrides',async t=>{
|
|
const root=await fixture(t);
|
|
await writeFile(join(root,'.env.local'),'API_BASE_URL=https://legacy.api.test\nLEGACY_MARKER=dev-only\n');
|
|
await writeFile(join(root,'config/development.local.env'),'API_BASE_URL=https://development-local.api.test\n');
|
|
const dev=nextCommand('development','dev',[],{root,variables:{}});
|
|
assert.equal(dev.env.API_BASE_URL,'https://development-local.api.test');assert.equal(dev.env.LEGACY_MARKER,'dev-only');
|
|
for(const environment of ['test','production']){
|
|
const result=nextCommand(environment,'build',[],{root,variables:{}});
|
|
assert.equal(result.env.API_BASE_URL,`https://${environment}.api.test`);assert.equal(result.env.LEGACY_MARKER,undefined);
|
|
}
|
|
});
|
|
|
|
test('UPSTREAM_API_URL follows profile, local and process precedence while preserving legacy process overrides',async t=>{
|
|
const root=await fixture(t);
|
|
await writeFile(join(root,'config/test.env'),'UPSTREAM_API_URL=http://127.0.0.1:21111\nPORT=21021\n');
|
|
await writeFile(join(root,'config/test.local.env'),'UPSTREAM_API_URL=http://127.0.0.1:21112/\n');
|
|
let result=nextCommand('test','start',[],{root,variables:{}});
|
|
assert.equal(result.env.UPSTREAM_API_URL,'http://127.0.0.1:21112');assert.equal(result.env.API_BASE_URL,result.env.UPSTREAM_API_URL);
|
|
result=nextCommand('test','start',[],{root,variables:{UPSTREAM_API_URL:'https://primary.api.test/',API_BASE_URL:'https://ignored.api.test'}});
|
|
assert.equal(result.env.UPSTREAM_API_URL,'https://primary.api.test');assert.equal(result.env.API_BASE_URL,result.env.UPSTREAM_API_URL);
|
|
result=nextCommand('test','start',[],{root,variables:{API_BASE_URL:'https://legacy-process.api.test'}});
|
|
assert.equal(result.env.UPSTREAM_API_URL,'https://legacy-process.api.test');
|
|
for(const UPSTREAM_API_URL of ['', 'javascript:bad', 'https://user:password@api.test', 'https://api.test/?query=1', 'https://api.test/#fragment']){
|
|
assert.throws(()=>nextCommand('test','start',[],{root,variables:{UPSTREAM_API_URL}}),/UPSTREAM_API_URL/);
|
|
}
|
|
});
|
|
|
|
test('each environment selects its own build directory and supports port/hostname command overrides',async t=>{
|
|
const root=await fixture(t);
|
|
for(const [environment,directory] of [['development','.next-dev'],['test','.next-test'],['production','.next']]){
|
|
for(const command of ['dev','build','start']){
|
|
const result=nextCommand(environment,command,[],{root,variables:{}});
|
|
assert.equal(result.env.APP_DIST_DIR,directory);assert.equal(result.cwd,root);
|
|
assert.equal(result.env.NODE_ENV,command==='dev'?'development':'production');
|
|
if(command==='build')assert.deepEqual(result.args.slice(1),['build']);
|
|
else assert.deepEqual(result.args.slice(2),['--hostname','0.0.0.0','--port','21021']);
|
|
}
|
|
}
|
|
const explicit=nextCommand('test','dev',['--port','31022','--hostname=127.0.0.1','--webpack'],{root,variables:{}});
|
|
assert.deepEqual(explicit.args.slice(1),['dev','--port','31022','--hostname=127.0.0.1','--webpack']);
|
|
});
|
|
|
|
test('current-domain profiles preserve runtime resolution instead of requiring a build-time URL',async t=>{
|
|
const root=await fixture(t);
|
|
for(const environment of ['test','production']){
|
|
await writeFile(join(root,'config',environment+'.env'),'UPSTREAM_API_URL=current\nPORT=21021\n');
|
|
for(const command of ['dev','build','start']){
|
|
const result=nextCommand(environment,command,[],{root,variables:{}});
|
|
assert.equal(result.env.UPSTREAM_API_URL,'current');assert.equal(result.env.API_BASE_URL,'current');
|
|
}
|
|
const override=nextCommand(environment,'start',[],{root,variables:{UPSTREAM_API_URL:'http://127.0.0.1:21111'}});
|
|
assert.equal(override.env.UPSTREAM_API_URL,'http://127.0.0.1:21111');
|
|
}
|
|
});
|
|
|
|
test('invalid environment, command, API or port fails before a child process is started',async t=>{
|
|
const root=await fixture(t);
|
|
for(const [environment,command] of [['staging','dev'],['../test','dev'],['development','deploy']])assert.throws(()=>nextCommand(environment,command,[],{root,variables:{}}),/Usage:/);
|
|
for(const API_BASE_URL of ['', 'javascript:bad', 'https://name:password@api.test','https://api.test/#fragment'])assert.throws(()=>nextCommand('test','dev',[],{root,variables:{API_BASE_URL}}),/API_BASE_URL/);
|
|
for(const PORT of ['0','65536','not-a-port','-1'])assert.throws(()=>nextCommand('test','dev',[],{root,variables:{PORT}}),/PORT/);
|
|
await rm(join(root,'config/test.env'));assert.throws(()=>nextCommand('test','dev',[],{root,variables:{}}),/缺少环境配置/);
|
|
});
|
|
|
|
test('real child execution receives the selected environment, preserves args and propagates its exit status',async t=>{
|
|
const root=await fixture(t),bin=join(root,'node_modules/next/dist/bin');await mkdir(bin,{recursive:true});
|
|
const output=join(root,'captured.json');
|
|
await writeFile(join(bin,'next'),`require('node:fs').writeFileSync(process.env.CAPTURE,JSON.stringify({app:process.env.APP_ENV,node:process.env.NODE_ENV,api:process.env.API_BASE_URL,args:process.argv.slice(2),cwd:process.cwd()}));process.exit(7);`);
|
|
assert.equal(await runNext('test','start',['--port','31021'],{root,variables:{CAPTURE:output}}),7);
|
|
const saved=JSON.parse(await readFile(output,'utf8'));
|
|
assert.deepEqual(saved,{app:'test',node:'production',api:'https://test.api.test',args:['start','--hostname','0.0.0.0','--port','31021'],cwd:await realpath(root)});
|
|
});
|
|
|
|
test('project profiles and Next configuration keep the requested API and port with distinct artifacts',async()=>{
|
|
const source=ts.transpileModule(await readFile(new URL('../next.config.ts',import.meta.url),'utf8'),{compilerOptions:{target:ts.ScriptTarget.ES2022,module:ts.ModuleKind.CommonJS}}).outputText;
|
|
for(const [environment,directory] of [['development','.next-dev'],['test','.next-test'],['production','.next']]){
|
|
const command=nextCommand(environment,'build',[],{variables:{}}),exports={};
|
|
vm.runInNewContext(source,{exports,process:{env:command.env,cwd:()=>process.cwd()}});
|
|
assert.equal(exports.default.distDir,directory);assert.equal(command.env.UPSTREAM_API_URL,environment==='production'?'http://172.104.56.28:26125':'http://172.104.181.99:21022');assert.equal(command.env.API_BASE_URL,command.env.UPSTREAM_API_URL);assert.equal(command.env.PORT,'21021');assert.equal(command.env.NEXT_PUBLIC_TRACKER_APP_ID,'1047');
|
|
const ignored=spawnSync('git',['check-ignore','--no-index',`config/${environment}.local.env`],{encoding:'utf8'});assert.equal(ignored.status,0);
|
|
}
|
|
const pkg=JSON.parse(await readFile(new URL('../package.json',import.meta.url),'utf8'));
|
|
for(const name of ['dev','dev:test','build','build:dev','build:test','start','start:dev','start:test'])assert.match(pkg.scripts[name],/next-env\.mjs/);
|
|
});
|